B7. The Mac's coding-agent UI: Start split button, agent tabs with live transcript and Stop, and Settings › Coding agents
Stacked on B6 (#1871), which has merged into coding-agents, so this is opened against coding-agents directly. Last PR of the coding-agents branch.
What the Mac gets
- Start split button in the plan toolbar (
desktop/start-agent-button.tsx). The main part starts an agent on the account's default (no model or effort sent). The chevron drops a menu of/api/models, each row under its provider's mark (Anthropic and OpenAI marks bundled inrenderer/provider-marks.tsx; AI Elements' model selector is not in the vendored set), the efforts the chosen model lists, and "Start with · ". Unavailable, with a hover reason, for a plan with no repository. Each press mints an idempotency key; a press asked again after the service did not answer reuses the key for the same choice, and a worded refusal or a different choice mints a new one. - Copy plan moves out of the toolbar into the ⋯ menu (it was already there); the ⇧⌘C chord and the Copied / failed notes stay in the toolbar.
- Agent tabs after Board, Code, and Transcript: the registry (
use-side-panel.ts) now admits a dynamic{ agent }tab beside the fixed three, kept in storage the same way, and read as the board when the open plan has no such agent. Labelled by model with a status dot (starting and running pulse on--loop-motion; completed, failed, cancelled). The strip scrolls on overflow; no tab closes. A Start opens and selects its tab. - The agent tab (
planning/agent-tab.tsx): model · effort · status, Stop while the agent may still write, and the transcript on AI Elements with newreasoning.tsx,tool.tsx, andcode-block.tsx(folded on the platform'sdetails, no new dependency): the plan as one folded user turn, text as markdown, reasoning folded, tool calls folded with input and output, error rows, and a GitHub link that opens in the browser throughGITHUB_OPEN; every other link is inert.ph-no-captureon the root. Auto-follows the newest line as the Transcript tab does. - Live updates (
planning/use-agent-transcript.ts): while the tab shows and the agent is starting or running,GET /api/agents/:id/messages?after=<cursor>is read in a loop of the service's held long-polls, pages applied by message id. The loop ends when the tab is hidden, the plan is left, or a page's status leaves starting/running; an ended agent is read through once. The agents list is read when the plan opens and after Start or Stop; nothing else polls. - Settings › Coding agents (
settings/coding-agents-page.tsx), in the sidebar and the search: the default model with marks and the model's efforts, read and written through/api/account/preferences'scodingAgentpart. Read on page open, so a model chosen from Start's chevron (which the server keeps as the default) shows there next. - Docs: PRIVACY.md and docs/PLANNING.md updated for Start, agent tabs, Settings › Coding agents, Copy in ⋯, and the transcript wording that was no longer true.
The path from the window to the service
Renderer acts → main/ipc/coding-agent-acts.ts (panel only) → HostOperator → gateway methods codingAgents.* → @sidecar/host's compose-coding-agents.ts (stateless, gated like planning) → @sidecar/hosted's new HostedCodingAgentClient (coding-agent-client.ts), with the window-facing answer shapes in coding-agent-view.ts and the models and default wire in models-wire.ts. The held read travels under its own 35 s deadline, past the service's 20 s hold.
Two small server changes (approved by the orchestrator)
coder-host/transcript.tsholds a read while the agent is starting as well as running, so a tab on an agent whose first turn has not landed costs one held read per hold window rather than a spin. The hold still lets go at its expiry.- The messages page now carries
status(the newest turn's, read with the page), so the loop learns the agent ended from the page that ends the hold and reads the list for nothing else.codingAgentMessagesAnswerSchemagainsstatus, and the goldencoding-agent-wire-codingAgentMessagesAnswerSchema.jsonis re-recorded: the diff is exactly the new requiredstatusproperty with the fiveCODING_AGENT_STATUSliterals. - Guard asked for alongside: an agent still without a turn row five minutes after its Start (
CODER.STARTING_GRACE) reads as failed rather than starting forever (coder-host/status.tstakes the agent's standing; an eve that refused the open already discards the agent). README updated.
A renderer bug this surfaced
renderer/act.ts sent every act through one shared Atom.fn, and the atom's promise mode answers every pending caller with the atom's newest result. Two acts out at once crossed answers ("Invalid answer to the act …"), which a 20 s held read would have made certain. Each call is now its own promise on the bridge; use-act.test.ts holds two concurrent acts to their own answers.
Also fixed on the way: a pre-existing Biome error in settings/confirm-dialog.tsx (useIndexOf) that failed pnpm lint on the base branch.
Tests
packages/hosted:coding-agent-client.test.ts(every call, each refusal, the held read's address).packages/host:compose-coding-agents.test.ts(pass-through, invalid params, the gate).apps/desktopmain:coding-agent-acts.test.ts(panel-only refusal, payloads through).- Renderer:
use-side-panel.test.ts(dynamic agent tabs, stored validity),use-agent-transcript.test.ts(the loop's start and stop rules, retry),use-coding-agents.test.ts(list reads, idempotent retry, Stop, status),agent-tab.test.ts(reasoning and tool folded, error row, GitHub link),start-agent-button.test.ts(unavailable with reason, menu with marks and efforts, choice),coding-agents-page.test.ts(Settings round trip and refusal),coding-agent-model.test.ts,use-act.test.ts. - Web: the held-read test now covers the starting hold and the page's status; a starting agent past the grace reads failed; the route test asserts the held read and status.
./scripts/check.sh exits 0. Renderer bundle: 672,594 gzip bytes against the 664,696 baseline (+1.2%, inside the 5% slack; baseline unchanged).
After review
Devin's five flags are addressed in the second commit: Stop stands only while a turn runs (a starting agent has none the service could cancel); a failed re-read of the agents keeps the ones drawn and the strip offers the first read again; a Start landing after the plan changed opens no tab; the Settings page and the Start menu show no default but the one they just read.
Out of scope, as planned: messaging an agent, switching model mid-run, closing tabs. No counted events were added.
🤖 Generated with Claude Code