feat(service,host): a held transcript read wakes at the edge only the clock reaches
Stacked on #3282 (luke/live-agent-transcript) and #3265 (luke/neon-realtime-agent-status, merged in). Until #3265 lands, its five files (coding-agent-status.ts and its test, status.ts, bounds.ts, and features/package.json) show up in this diff. They are not part of this change.
Why
#3282 moves the transcript's hold into the host and wakes it on Realtime rows. Two things can end a hold with no row changing:
- an agent with no turn passing
STARTING_GRACE, after which it reads as failed; - the window of a title the read waits on closing (
TITLE_TIMEOUT).
In #3282 the host only noticed these when the 20 s hold ran out. This PR wakes it at the right moment instead.
What
- Service:
transcriptNowaddswakeInwhen the page would hold: the milliseconds from the service's own now to the earliest edge.- The starting-grace edge comes from #3265's
startingGraceEdge, using the newest turn and whether a line awaits. - The title edge comes from the new
titleWindowEdge(parent, child?). It is the latest point at which any title the read waits on can still land: a worker's start or the agent's completed turn, plusTITLE_TIMEOUT. LiketitleMayLand, it is none on a deployment that generates no titles. wakeInis a relative delay rather than an instant, so the service's and the desktop's clocks are never compared.
- Wire:
transcriptNowAnswerSchemagains an optionalwakeIn. Its golden is regenerated. - Host:
readUntilSaidraces "the statements moved" againstEffect.sleep(wakeIn)and reads again on whichever comes first.
Tests
coder-host.test.ts(PGlite), using the real relay:- an agent that is starting holds, with
wakeInequal to grace end + 1 − now; - an ended, untitled agent holds on its title, with
wakeInequal tosettledAt+ 20 s + 1 − now; - an untitled worker's edge is its start + 20 s + 1.
- an agent that is starting holds, with
compose-coding-agents.test.ts: with no row moving, a read whose page sayswakeIn: 3000is still held at 2999 ms and answered with the next page at 3000 ms, rather than at the 20 s mark.
pnpm check:affected passes apart from the sandbox's app-menu.test.ts, which needs an Electron binary. Typecheck was rerun on its own after the combined run ran out of memory.
Made with Luke
Summary by cubic
Wakes a held transcript read at the moment the clock alone ends it, instead of at the 20 s hold's end. This diff also carries the branches it stacks on; the change here is the wakeIn field and the host's race.
Previously, two situations with no row change held a read to the hold's end: an agent with no turn passing its starting grace, and the window of an awaited title closing. The service's transcriptNow now answers wakeIn — milliseconds from the service's own now to the earliest edge — and the host races the live statements moving against Effect.sleep(wakeIn).
wakeInis relative, so the service's and desktop's clocks are never compared; it is none on deployments that generate no titles.- The starting-grace edge reuses the shared status rule; the title edge is the new
titleWindowEdge. transcriptNowAnswerSchemagains an optionalwakeIn.
Tests
- The coder-host PGlite tests cover the starting-grace edge, an ended untitled agent's title edge, and an untitled worker's edge.
- The compose test confirms a page with
wakeIn: 3000is answered at 3000 ms, not at the 20 s mark.