feat(desktop): a macOS notification when a coding agent's turn ends, with an unseen dot on its tab (O5)
O5 of the coding-agents rollout: Luke tells the developer when a coding agent's turn ends.
What it does
- A macOS notification when an agent moves from starting or running to completed or failed: titled with the plan's name, saying the model's name and "finished" or "failed", and nothing of the transcript. Not posted while the window is focused on that agent's tab. A Stop is the developer's own and is not announced.
- Clicking it brings Luke forward on that plan with that agent's tab open.
- An unseen dot on a finished agent's tab until it is shown, or until the window comes forward on it. Held in main's document, so a window reload keeps it.
- One notification per turn end, across reloads. Main keeps the ledger, so the renderer reloading announces nothing twice; an agent first seen already ended (as after a relaunch) is not announced.
How
main/agent-notices.tskeeps a ledger of where each agent stands, fed by every coding-agent answer that passes through the acts (list, Start, Stop, transcript page, catalog). While any agent in the ledger is starting or running, its plan's agents are listed again every 30 s on aScheduleforked into the launch's scope. That is the panel's ownGET /api/plans/{id}/agentson a clock rather than a new server read, so agents on plans the developer has left are watched too. No server change.- The panel tells main which agent's tab it shows through one new act,
codingAgents.shown; the click reaches the panel on one new one-shot channel,app:show-agent. The unseen agents ride the app-state document as a newcodingAgentsslice. - Electron's
Notificationsits behind one seam (main/window/notice-poster.ts); the focus events behindwindows.onPanelFocusChanged. modelLabelmoves to#shared/model-labelso main and the renderer name a model the same way.- The notice text is built in one place,
noticeText, which already takes an optional pull-request number: "opened #N" wires up by reading O4'sGET /api/agents/{id}/pull-requestat turn end (whichever of O4/O5 merges second does it).
Tests
main/agent-notices.test.ts: finish while unfocused notifies once and dots the tab; focused on the tab does not, nor does a Stop; the click opens plan + tab; the dot clears on open and on focus; the watch lists only plans with an agent still writing and lets a deleted plan go; catalog name; pull-request wording. ipc/coding-agent-acts.test.ts: every answer reaches the notices; the voice window is refused. planning/use-plans-tab.test.ts: the shown report, the click's routing, the dot beside the arrivals.
Follow-ups (out of scope here)
- A Settings toggle for the notifications (Settings has #1888 in flight).
- "opened #N" on O4's pull-request read.
Dean waived ./scripts/verify.sh for this rollout; CI builds nothing for the Mac, so the Mac is unverified by CI. ./scripts/check.sh exits 0.
š¤ Generated with Claude Code