feat(service,host): a planning worker's transcript is followed through the shared live layer

Merged
CH
5d ago
+867-99

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=, in live-procedures.ts). It reads the task as the account and seals transcriptLiveStatements for 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.
  • messages.taskWorker with wait=none answers transcriptNow at once, with holds and wakeIn. A worker still starting answers holds: true.
  • Host (compose-tasks.ts). followWorker (behind TASKS_WORKER_TRANSCRIPT_FOLLOW) now runs main's followTranscript over a transcriptFollows keyed by TaskWorkerKey, 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.ts is generic over its key. The agent tab's TranscriptKey is 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 pass mutableFunctionsOf and the seal check.
  • compose-tasks.test.ts replaces 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's compose-tasks and compose-coding-agents tests; knip, biome and oxlint.