feat(service,host): a planning worker's transcript is followed through the shared live layer
Merged
CH
5d ago+867+867+012345678901234567890123456789-99-99-01234567890123456789
On main. Moves the planning worker's transcript tab onto main's shared live layer (liveRows and the sealed live procedures), in place of main's own loop over the held read. It reuses the agent transcript's live statements and host follower rather than writing them a second time. Realtime is used only where LUKE_REALTIME=on (#4267); otherwise the follow rides the held poll (#4272).
What changes
- Service, live procedure
live.taskWorkerTranscript(GET /api/live/task-worker-transcript?id=&call=, inlive-procedures.ts). It reads the task as the account and sealstranscriptLiveStatementsfor the child conversation the call's worker writes in, under the task's conversation. Before the planner's journal names the call, that child is the conversation the call will open.- Each statement is scoped by the new
taskFollowedBy(taskId, reader), which checks the reader is a member of the task's workspace. - It answers not-found for a task the account doesn't reach or one never planned on, and polled (held past
after) like main's other live reads.
- Each statement is scoped by the new
messages.taskWorkerwithwait=noneanswerstranscriptNowat once, withholdsandwakeIn. A worker still starting answersholds: true.- Host (
compose-tasks.ts).followWorker(behindTASKS_WORKER_TRANSCRIPT_FOLLOW) now runs main'sfollowTranscriptover atranscriptFollowskeyed byTaskWorkerKey, exactly as the agent tab's worker does. That replaces the loop that re-asked the held read and slept a second on empty pages. Sealed statements stay in the host. transcript-follow.tsis generic over its key. The agent tab'sTranscriptKeyis unchanged.
Tests
live-procedures.test.ts: the worker transcript is sealed for the bearer on the worker's child conversation under the task. It's not found for a stranger or a task never planned on, and polled without Realtime.live-statements.test.ts: the planning worker's statements passmutableFunctionsOfand the seal check.compose-tasks.test.tsreplaces main's two held-read worker tests with live-path ones:- pages are told one by one, the next asked at once after one that said something;
- an empty page is asked again only when the statements move, not for the same rows or a long wait;
- the held route is never asked;
- a worker the service doesn't seal yet is sealed again each second, then read.
- Locally: features, host and service typecheck; vitest on
packages/service/src/live, the messages tests,packages/features, and the host'scompose-tasksandcompose-coding-agentstests; knip, biome and oxlint.