* feat(workers): join from the app, share by QR, and a status-bar Compute control Remote workers shipped with a hole in the middle: the control plane could mint join codes, and on the other machine there was nothing to paste them into. Becoming a worker meant launching with OMNIVOICE_WORKER_MODE and OMNIVOICE_WORKER_TOKEN in the environment and relaunching — on the machine that is usually the least convenient one to configure by hand. Backend - GET /workers/agent, POST /workers/agent/join, POST /workers/agent/enabled. Join redeems a code and starts the agent live; no restart. - Worker mode now persists in settings as well as the environment (env still wins, and the panel is told so it can disable a switch it cannot honour), and it is written only after a join that actually worked — a failed enrolment must not have the app retrying on every launch. - The endpoint carried by the redeemed code is remembered. Without that a machine that joined from the UI came back up enrolled but with nowhere to dial, and the only fix was OMNIVOICE_WORKER_ENDPOINT. UI - "Lend this machine's GPU": paste the code, Join. Once joined it offers a switch rather than another code, because the pinned certificate survives. - <OneTimeSecret/> renders join codes and connection strings as a QR next to the text, with a live expiry countdown, and is used by both halves. QR generation is best-effort: a string past the format's capacity still shows the code and Copy, because losing the QR is a degraded share and losing the only copy of a one-time secret is data loss. - Status-bar Compute control: pick local or a machine, flip the feature, mint a join code — without opening Settings. Absent entirely until the user has opted in or enrolled something. - Remote workers now reads as a device list: status dot, address, latency, live task meter, resident models, last seen; housekeeping actions revealed on hover; a three-step empty state. - Approve is on the row. A worker could connect, sit there labelled "Not approved" and never be usable, with no way out of it in the UI. Fixes found on the way - Status dots and menu surfaces in the GPU picker were painted from fixed Tailwind palette classes (bg-emerald-400, text-amber-400, hover:bg-white/5), so on Midnight or Catppuccin they showed Gruvbox colours next to the theme's own. Both controls now paint from themed --color-* tokens, shared in computeTarget.jsx along with the JSON wrapper all three copies duplicated. - Button funnels every child into one <span>, so an icon passed as a child renders glued to its label — the flex gap only applies to the `leading` slot. Six buttons across these panels were affected. - InboundNodePanel passed `variant="warning"` to Badge, which takes `tone`; the "on your network" warning rendered as an ordinary neutral pill. Docs updated in the same change (docs/remote-workers.md): the join flow, the QR, the status-bar control, and the new environment variable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(changelog): stamp the remote-workers entries with their PR ref Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(workers): a join is not done until the control plane accepts it Review findings on #1516: - Greptile P1: `start()` only SCHEDULES the dial-out loop, so a control plane that rejected this worker — expired token, wrong address, a server that never answers — looked identical to a successful join. The route persisted worker mode, reported success, and the machine retried forever on every launch. The agent now signals first registration, and join waits for it before persisting anything. - CodeRabbit: a failed REJOIN left the machine unable to reconnect to the control plane it was already serving, because pinning the new certificate overwrites the old one on disk. Snapshot the pinned certificate, endpoint and setting up front, and restore them (and the running agent) when the join fails. - CodeRabbit: join and the enable toggle awaited stop()/start() with no exclusion, so two concurrent requests could interleave their pairs and have `start()` return early — reporting success for a control plane it never dialled. Both now hold one lifecycle lock. - CodeRabbit: with OMNIVOICE_WORKER_MODE set, the toggle still started or stopped the agent and wrote a setting the rest of the app ignores, contradicting the env_pinned status it reports. It now answers 409 and says which variable is in charge. - CodeRabbit: the QR code kept encoding the previous secret until the new one finished encoding, so the code on screen could disagree with the text beside it. CI: regenerated tests/fixtures/api_routes.txt for the three /workers/agent routes. Tests: a join the control plane never accepts is a 409 that persists nothing and leaves no agent dialling; a failed rejoin restores the previous certificate, endpoint and setting; an env-pinned machine refuses the toggle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(workers): the environment pin governs joining too, not just the toggle CodeRabbit, #1516: - join_control_plane skipped the env_pinned guard set_agent_enabled enforces, and joining is precisely what ENABLES worker mode: under OMNIVOICE_WORKER_MODE it wrote a setting nothing consults, and with the variable pinned off it handed back a machine that reported a successful join and lent nothing. One shared guard now covers both routes. - Two of the three rollback assertions could not fail before the fix (nothing wrote those settings on the failure path). The test now pins the behaviour only the rollback produces: the previous enrollment is dialling again, rather than left stopped until someone notices. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1.4 MiB
1.4 MiB