v3 M1c (host): provider selector — control panel + live-switch plumbing

Panel: new Providers tab (data-driven from GET /api/providers — labels/accents
from the daemon registry, enable/active/creds from config). z.ai base_url + key,
api_key masked and preserved on save like the HA token.

Daemon: request_provider_switch() lets the panel push an active-provider change
into the live BLE session (same _set_active_provider path as the watch) so it
takes effect now, not on the next 60s poll; _active_session exposes the session
to the panel thread. On-watch cycle groundwork: provnext -> _cycle_provider
(daemon owns the enabled set/order), and pnm/pi/pc pushed in the payload so the
watch's Provider screen can show the name + "1/3".

server.py: ConfigIn gains providers/active_provider/display_order; secrets masked
in _masked; active_provider change notifies the injected _command_sink.
tray wires set_command_sink(request_provider_switch).

Tests: test_server_providers (masking + sink), test_provider_switch (cycle +
offline persist). 106 passed, 2 Linux-only failures (baseline).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wenil
2026-07-10 06:41:19 +03:00
co-authored by Claude Opus 4.8
parent 83bd2bed43
commit 1b14c363c2
6 changed files with 376 additions and 1 deletions
+3 -1
View File
@@ -226,7 +226,8 @@ def main() -> None:
from pystray import Menu, MenuItem
import daemon.autostart_windows as autostart
from daemon.claude_usage_daemon_windows import main as daemon_main, log as daemon_log
from daemon.claude_usage_daemon_windows import (
main as daemon_main, log as daemon_log, request_provider_switch)
from daemon.icon_assets import load_logo_rgba, build_state_icons
# Build per-state icons once at startup; swap icon.icon per tick (never recomposite).
@@ -277,6 +278,7 @@ def main() -> None:
from daemon.config import DEFAULT_PORT
panel_port = DEFAULT_PORT
panel_server.set_status_provider(lambda: _status_dict(ts))
panel_server.set_command_sink(request_provider_switch)
panel_server.serve_in_thread(panel_port)
daemon_log(f"Control panel: http://127.0.0.1:{panel_port}")
except Exception as e: