feat(host,desktop): push a followed transcript's pages to the panels that draw it
Draft
CH
5d ago+1495+1495+0123456789012345678901234567890123456789-11-11-01234567890123456789
First of three small PRs that remove Luke's last transcript and voice loops, stacked on #3311. The goal is realtime with no fallbacks. This one adds the host side: a transcript is pushed to the panels that follow it. The window still uses the held read; PR 2 moves it over.
What changed
- Wire (
coding-agent-view.ts):codingAgentTranscriptKeySchemanames the transcript, either an agent's (kind: "agent",agentId, optionalcallIdfor one of its workers) or a task's planning worker's (kind: "taskWorker",taskId,callId).codingAgentTranscriptChangedSchemais{ transcript, page }, wherepageis the existingCodingAgentMessagesAnswerView. Both are recorded inhosted-wire-schemas.test.ts, with goldens generated underLUKE_UPDATE_FIXTURES=1. The goldens are two new files and change nothing else. - Gateway: adds
codingAgents.transcriptFollow/transcriptUnfollow(owned by the coding-agents composer),tasks.workerTranscriptFollow/workerTranscriptUnfollow(owned by the tasks composer), and atranscript.changedevent. - Host (
transcript-follow.ts):followTranscriptreads on the transcript's existing Realtime subscription (transcriptFollows) and tells every page as it reads it. The first page is read from0:0. After a page with messages, the next is read right away. Otherwise the follow waits until the statements move, the subscription stops or opens again, or the service'swakeInclock edge passes.- A seal that fails is asked for again after
RESEAL, as before. A page read the service didn't answer is told and read again afterRESEAL. Not found is told once and ends the follow. - Each composer keeps a
FiberMapof follows. Following again starts over, and sign-out clears them.
- Operator:
transcriptFollow/transcriptUnfollowpick the method bykind;onTranscriptChangedreads the event. - Follow rows (
packages/host/src/act-router/transcript-follow-acts.ts): these follow the inbox follow rows from #3264/#3306, and are generic over the panel. The desktop spreads them over itsWebContents. Web-host spreads them over its tab and delivers through the in-process bridge'sonTranscript, so a web tab follows transcripts the same way a window does.- It answers the
codingAgents.transcriptFollow/Unfollowacts. A panel can hold several follows of one transcript, and they are counted. - The host lets a transcript go once no panel holds a follow, or when a panel's page is gone.
- Each page goes to the panels that follow that transcript, on a new per-window
app:transcriptbridge channel. - Every follow asks the host to start from the beginning, so a panel that arrives late hears the whole transcript. Main stores none of the transcript's words.
- It answers the
- Signed statements stay in the host. Tests check that no told page contains a capability.
Tests
compose-coding-agents.test.ts:- A followed agent transcript is told page by page: again when rows land, at the
wakeInedge, and when the statements move. Unfollowing stops it. - Not found is told once; behind a closed gate the follow is told unanswered; sign-out lets it go.
- A followed agent transcript is told page by page: again when rows land, at the
compose-tasks.test.ts: a planning worker's transcript is followed the same way.in-process-bridge.test.ts: a page follows a transcript through the shell's follow rows and hears each page the host tells.transcript-follow-acts.test.ts: pages are routed per panel, follows are counted, a page that goes away releases its follows, and only a panel can follow.
Checks run locally
pnpm check:affected gets OOM-killed in the sandbox, so I ran its pieces one at a time:
repository-checks.sh,biome check .,oxlint,knipdiscarded-effect(with a larger heap)tscfor features, gateway, host, panel, and desktop- vitest for host, features, gateway, and desktop main
What to check by hand
Nothing visible changes in this PR: no window sends the new acts yet. PR 2 is the visible one.
Made with Luke