Repository navigation
host: a tool result never closes its wait, so the 60s steer line reads the turn's age #1644
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: af6aca314c394e8484759f1d65326b0b
Branch: dev/3-130-host-a-tool-result-never-closes-its-wait
Base: c26089f
At: 2026-10-29T05:49:00Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
steering-interrupt.js closes a foreground tool wait by reading a field that tool/result does not carry, so nothing ever closes and the 60s line silently becomes "the turn has been running 60s".
Verified code facts (read at main 3f58b9a8):
core/src/host/steering-interrupt.js:42-48—end(id, key)starts withif (key === '') return.core/src/host/steering-interrupt.js:68-70—case 'tool/result': end(id, String(event.data?.callId ?? '')).core/src/host/steering-interrupt.js:62-64—tool/calldoes have a top-levelcallId, so opens are recorded; only closes are lost.- DSH's own event map says the two shapes differ:
'tool/call': { turn, step, callId, name, arguments }but'tool/result': { turn, step, message: ToolResultMessage, error?, meta? }— the call id arrives asdata.message.toolCallId(also reachable asdata.message.source.callId), never as a top-leveldata.callId. This repo already knows both shapes:core/src/host/flybot-feed.js:241readsevent.data?.callId ?? event.data?.message?.toolCallId.
Consequence. Within one turn open only grows. startedAtOf() (:34-37) then returns the time of the turn's first tool/call, so now - waitStartedAt > 60_000 stops meaning "an open wait has lasted 60s" and starts meaning "the turn has lasted 60s". A human steer that arrives after that aborts a turn whose tools have all returned and that is not waiting on anything.
Reproduction (real install(), real payloads). tool/call at T0; a genuine tool/result at T0+5s whose id is in data.message.toolCallId; a user/message with source.kind === 'user' at T0+61s → the turn is cancelled with {kind:'hook', reason:'ds-bot/interrupt-long-wait'}, options.keepInbox: true, although nothing was waiting. Control: the same sequence with a synthetic data:{callId} result does not cancel.
Why the shipped test is green. core/test/steering-interrupt.test.js builds results as { type:'tool/result', time, data:{ callId } } — a shape the host never writes — so the 5/5 pass pins a fixture, not the wire format.
Expected. A landed tool/result closes that call whatever the payload shape carries the id in; the 60s threshold applies only while a foreground wait is genuinely open. A returned tool means no interrupt, no matter how long the turn has been running.
Acceptance.
- (a) the reproduction above cancels nothing;
- (b) a genuinely open wait past 60s plus an arriving human line cancels exactly once, with
keepInbox; - (c) at or below 60s, after the wait closed, and in a group room, nothing changes;
- (d) the tests build
tool/resultpayloads in the real shape (data.message.toolCallId), plus one case pinning that a result carrying only that field closes the wait — a red test on today's code.
Scope. core/src/host/steering-interrupt.js, its test, and the patches/ds-bot regeneration plus the one changelog fragment. Keep the change to the read: the trigger-timing question (deciding on agent/inbox/spliced arrival instead of a logged user/message) is a separate decision and must not be folded in here. core/src/host/turns.js:404 (endCall(id, entry, event.data?.callId)) has the same read; if the same one-line shape fixes it, include it, otherwise report it and leave it for its own issue.
Written-by: FlyBot Lead
Activity
fix(host): close a tool wait from the result the host writes (ThinkFl…
HugoXK commentedon Oct 29, 2026
Release
Written-by: FlyBot Lead
Released: af6aca314c394e8484759f1d65326b0b
Reason: Released: merged as #1645 (merge 4f2185d)
At: 2026-10-29T06:42:55Z
fix(host): close a tool wait from the result the host writes (ThinkFl…