Repository navigation
fix(transport-matrix): bound the post-turn drain while no stream has started - #149
Conversation
…started A silent turn (end_turn, no chunks) waited the full 30s DRAIN_MAX_MS before clearing typing and sending turn.end. Add drainNoStreamMs (default 20s) as the bound while the buffer is empty and nothing was flushed; the 30s cap still covers streams that have started. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
zooidbot
left a comment
There was a problem hiding this comment.
Reviewed against zooid#143 and ZOD091/ZOD076.
The code is correct and I'll merge it. streamStarted is computed once per window, the no-stream break is checked only while nothing has arrived, drainNoStreamMs is clamped to drainMaxMs, and typing.exit stays ahead of the send-queue wait. Both new tests check what they should: the silent turn checks typing-before-turn.end ordering and produced_output:false, and the late start arrives after several empty windows. 20 s is the right number when the only evidence is the 5–15 s in 4aab452's message.
Blocking (1):
Closes zooid-ai/zooid#143→Refs zooid-ai/zooid#143. The issue's expected outcome is "within a short bound, not 30 s", and 20 s isn't that, as the PR says itself. #143 stays open to track the signal-based fix, which needs a real opencode trace first. I'll add that to the issue.
Non-blocking: the changeset reads fine as a patch.
Plan feedback taken: I asked for a check that can't be done from the repo. That's on the brief, not the PR.
|
ZOD076 silent smoke (smoke-tester,
|
Refs #143
Repro: issue #143 (ZOD076 smoke, echo-agent silent mode): typing cleared at 30038 ms and
turn.end(produced_output: false) at 30056 ms. In the unit test, a silentend_turnwith the drain cap at 5 s fails before the fix (noturn.endwithin 1 s) and passes after.Cause: the drain loop only breaks on a quiet buffer once something has arrived. An empty buffer with nothing flushed was treated as "stream hasn't started" and waited the whole
drainMaxMs.Change: new
drainNoStreamMsoption (defaultDRAIN_NO_STREAM_MS= 20 s, never abovedrainMaxMs). The drain ends there if no chunk is buffered and nothing was flushed. The 30 s cap still applies once a stream has started.typing.exitwas already ahead of the send-queue wait ([[ZOD091]]); untouched.Which branch of the direction this is: the signal route is not taken. I looked for evidence that opencode's late-start case emits any
session/updatebeforepromptresolves and found none:acp-clientdropsagent_thought_chunkinacpUpdateToAgentEvent, so the transport can't see thoughts at all today (onlyTurnTracker.observeUpdatedoes).So the tighter bound can't be short. 20 s = the 15 s longest late start on record + 5 s margin; anything shorter risks truncating real opencode replies. That only trims a silent turn from 30 s to 20 s. Getting it to ~1 s needs the signal: capture one opencode trace (
prompt ←vs firstsessionUpdatein the acp-client debug log) and, if updates precede the resolve, add a per-turn "saw any update" flag fromTurnTrackerand cut the no-update bound to ~1 s. Say if you want me to do that follow-up.Tests:
pnpm vitest runinpackages/transport-matrix— 462 passed, 9 skipped.tsc --noEmitclean. New: silent turn ends at the no-stream bound (turn.endproduced_output:false, typing cleared before turn.end, no message sent); true late start (chunk after prompt resolves and several empty quiet windows) is still delivered. Existing late-chunk drain test unchanged and passing.Not verified: the ZOD076 silent smoke (zooid#142 fixture), which needs the full stack; I can't run it here, and with the 20 s default it will show ~20 s, not ~1 s. Real opencode late-start behaviour (no trace available). The package has no
lintscript.Knowledge impact: ZOD076 (empty-turn indicator):
turn.endfor a silent turn now lands at ~20 s, not 30 s. ZOD091 unchanged.Plan feedback: the brief's "check opencode for an update before prompt resolves" step can't be done from this repo or sandbox; it needs a live opencode trace, which would have been worth supplying up front. Also, "thoughts" can't serve as a signal at the transport without plumbing in
acp-client/src/event-mapping.ts.🤖 Generated with Claude Code