Active/standby worker switch with Mac worker and tray UIs #220

Merged
backmeupplz merged 3 commits from worker-standby-switch into main 2026-10-15 19:28:02 +00:00
backmeupplz commented 2026-10-15 18:56:24 +00:00 (Migrated from github.com)

Lets the Windows 4070 Ti worker hand new transcription jobs to a second worker on an Apple Silicon Mac and take them back. The main use is gaming on the PC: a Steam game starting moves new jobs to the Mac, and exiting the game moves them back. Agentboard #129.

Backend (deploys on merge)

  • WorkerClient.standby. A standby worker gets 204 from claim-download and the legacy claim while an active worker has made a job request in the last 90 s. It finishes the jobs it already owns, and it covers on its own when no active worker is online. Workers are active by default, so nothing changes until someone switches.
  • GET /worker/v1/workers and POST /worker/v1/workers/active {name} for switch UIs. Requests to /workers* do not refresh lastSeenAt, so a polling UI can't keep a dead worker looking online.
  • claim-download accepts maxFileSize.

Worker side

  • The Mac downloads files up to 20 MB from the cloud Bot API. Larger files go through the PC's existing local Bot API via scripts/telegram-bot-api-file-proxy.py, so the bot is never logged into a second Bot API server. That proxy is needed because a --local server returns paths on its own disk and answers 404 for /file/ downloads. The proxy is exposed with tailscale serve. The Mac claims big files only while the proxy answers getMe (checked every 30 s), so they wait instead of failing when the PC is off.
  • scripts/mlx-whisper-transcriber.py: large-v3-turbo on the Mac GPU. It uses faster-whisper's decoder and Silero VAD, because turbo returns "Thank you." on silence and its no_speech_prob stays at 0.
  • scripts/worker-switch-menubar.swift + scripts/install-macos-worker.sh: menu bar switch and LaunchAgents. The Mac keeps itself from idle sleep while it is the active worker.
  • scripts/worker-switch-tray.ps1: Windows tray switch that watches Steam's HKCU\Software\Valve\Steam\RunningAppID.
  • The idle "download queue exhausted" line is no longer logged on every poll. The Windows worker.log had reached 1.56 GB, mostly from that line.

Tests

  • test:worker-api (local Mongo) covers standby yield and failover, /workers polling not counting as presence, and maxFileSize.
  • test:worker-client covers routing a big file through the large-file API, and the 20 MB claim limit while that API is down.
  • I checked each new assertion by breaking the code it guards (gate, presence, routing, size limit); each break failed the proof.
  • Local end-to-end run with the real backend app and the real mlx transcriber: standby, failover, switch, the size rule and silence all behave as described.
  • On the PC: the tray's Steam logic against a mock API (game start activates the other worker, exit switches back, single instance), and the file proxy against a fake and the real local Bot API (path rewrite, byte-exact download, traversal and unknown-token 404s, only getMe/getFile forwarded).
  • Lint and CI compile pass.

🤖 Generated with Claude Code

Lets the Windows 4070 Ti worker hand new transcription jobs to a second worker on an Apple Silicon Mac and take them back. The main use is gaming on the PC: a Steam game starting moves new jobs to the Mac, and exiting the game moves them back. Agentboard #129. ## Backend (deploys on merge) - `WorkerClient.standby`. A standby worker gets `204` from `claim-download` and the legacy `claim` while an active worker has made a job request in the last 90 s. It finishes the jobs it already owns, and it covers on its own when no active worker is online. Workers are active by default, so nothing changes until someone switches. - `GET /worker/v1/workers` and `POST /worker/v1/workers/active {name}` for switch UIs. Requests to `/workers*` do not refresh `lastSeenAt`, so a polling UI can't keep a dead worker looking online. - `claim-download` accepts `maxFileSize`. ## Worker side - The Mac downloads files up to 20 MB from the cloud Bot API. Larger files go through the PC's existing local Bot API via `scripts/telegram-bot-api-file-proxy.py`, so the bot is never logged into a second Bot API server. That proxy is needed because a `--local` server returns paths on its own disk and answers 404 for `/file/` downloads. The proxy is exposed with `tailscale serve`. The Mac claims big files only while the proxy answers `getMe` (checked every 30 s), so they wait instead of failing when the PC is off. - `scripts/mlx-whisper-transcriber.py`: large-v3-turbo on the Mac GPU. It uses faster-whisper's decoder and Silero VAD, because turbo returns "Thank you." on silence and its `no_speech_prob` stays at 0. - `scripts/worker-switch-menubar.swift` + `scripts/install-macos-worker.sh`: menu bar switch and LaunchAgents. The Mac keeps itself from idle sleep while it is the active worker. - `scripts/worker-switch-tray.ps1`: Windows tray switch that watches Steam's `HKCU\Software\Valve\Steam\RunningAppID`. - The idle "download queue exhausted" line is no longer logged on every poll. The Windows `worker.log` had reached 1.56 GB, mostly from that line. ## Tests - `test:worker-api` (local Mongo) covers standby yield and failover, `/workers` polling not counting as presence, and `maxFileSize`. - `test:worker-client` covers routing a big file through the large-file API, and the 20 MB claim limit while that API is down. - I checked each new assertion by breaking the code it guards (gate, presence, routing, size limit); each break failed the proof. - Local end-to-end run with the real backend app and the real mlx transcriber: standby, failover, switch, the size rule and silence all behave as described. - On the PC: the tray's Steam logic against a mock API (game start activates the other worker, exit switches back, single instance), and the file proxy against a fake and the real local Bot API (path rewrite, byte-exact download, traversal and unknown-token 404s, only getMe/getFile forwarded). - Lint and CI compile pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
backmeupplz/voicy!220
No description provided.