feat(web): a plan's planning conversation reads on the agent tab's held transcript wire, beside its work (one runtime §3.7 A)

Merged
DA
6d ago
+1317-11

One-agent-runtime §3.7, PR A of the one-held-read plan (docs/proposals/one-agent-runtime.md §3.7). Server and wire only, additive (expand/contract). B (the desktop host's held loop) follows; C (the renderer switch) waits on the Call tab fix in flight.

Compatibility

v0.12.0 is public and reads plan.work, PlanWorkTurn and GET /api/plans/{id}/work. None of them changes: the frame, the projection and the work page are all still served as they are. This PR only adds routes and optional wire keys, so every desktop in the field keeps working. A later contract PR removes the old path once the release that switches to this read is the floor.

What

New routes on plans-app.ts, authorized through readPlan (another account's plan is NOT_FOUND):

  • GET /api/plans/{id}/messages reads the planning conversation on the coding agent transcript's own answer (codingAgentMessagesAnswerSchema):
    • no cursor: the newest 20 turns' journals, oldest first, plus the cursor to follow from. earlier names the turn to page back before.
    • before=<turn>: the 20 turns before it.
    • after=<cursor>: the journals of the turns whose rows moved. It is held while the newest turn runs, or while any worker's session turn is queued or running, and polls every 250 ms so a call feels as live as the frame. It is let go at the agent read's 20 s hold.
    • Per message: only the turns' journals travel; voice rows don't, because the Call tab draws spoken lines from the call transcript. Each journal is the plan's work's own projection (newest parts, strings cut, page HTML and board image stripped, as in #2411). It carries its turnId, the spoken lines that asked for it (asked/askedAt), its span and its record.
    • Workers: every page says where the planner's workers stand (subagents, subagentTitles), read from each worker's own session. These cover the page's turns and the 3 newest turns, and the desktop merges them by call.
  • GET /api/plans/{id}/subagents/{call}/messages: a planning worker's session, read by the agent read itself (transcriptPast, now taking its poll) under the planner's tool set and 250 ms poll.
  • GET /api/plans/{id}/code: the shown-code list on its own, so the later contract can drop the work page outright.

Wire (@sidecar/hosted, optional keys only):

  • codingAgentMessageSchema gains turnId, asked and askedAt.
  • codingAgentMessagesAnswerSchema gains earlier.
  • planCodeAnswerSchema is new.
  • Paths: planMessagesPath, planSubagentMessagesPath, planCodePath.
  • Clients: HostedCodingAgentClient.planMessages (held) and HostedPlanClient.readCode.
  • Goldens re-recorded with LUKE_UPDATE_FIXTURES=1. The diff is only the new optional keys on the three transcript schemas; these are desktop wire schemas, not tool schemas, so no model prompt cache moves.

Store (message-reads.ts): messageCursorNow (where a reader holding every row stands) and childTurnRuns (whether any subagent session under a conversation runs).

Rewrites: function-rewrites.ts gains the three routes; api-rewrites.json and vercel.json are regenerated with functions:rewrites.

Tests

  • packages/database/.../__tests__/message-reads.test.ts (new):
    • the cursor now leaves a later row past it, and reads as the start for another account;
    • child sessions run while a turn is queued or running, and not once settled, for another account, or under the wrong parent.
  • apps/web/tests/plan-work.test.ts, 3 new:
    • Newest page and following on: the newest page carries the turns' journals oldest first, each naming its turn and without page HTML; a read past the cursor hears only the turn that moved; another account reads none.
    • Paging back: a plan at rest answers an empty follow at once, and before reads the older turns.
    • Worker standings: a background worker stands RUNNING from its own session though its call answered at the receipt, then COMPLETED once it answers.
  • apps/web/tests/plans-app.test.ts:
    • the new routes answer NOT_FOUND for another account's plan;
    • a plan never planned on reads as an empty conversation on the agent wire and as no code;
    • after together with before is a 400.
    • The test now provides the model catalog the worker read prices at.
  • Not tested: the 20 s hold itself. These suites run their effects through the test database's runner on the real clock. The hold's answer-at-once paths are covered.
  • ./scripts/check.sh exits 0.

Mac UI

No desktop UI changes. The new clients are unused until PR B wires them.

🤖 Generated with Claude Code


Open workspace in Conductor


Devin Review