fix(agents): don't drop engine stream-json events split across pipe chunks (#28)
`spawnEngine`'s `pump` split each stdout chunk on '\n' in isolation, with no
carry between chunks. A pipe read chops stdout at an arbitrary byte offset
(~8KB on macOS, up to 64KB on Linux), so a long stream-json event — a Write or
Edit `tool_use` carrying file content, or a big terminating `result` — arrives
as two 'data' events. Both halves fail JSON.parse and are swallowed by the
`catch { /* partial / non-json line — ignore */ }` right below.
Nothing surfaces the loss, and what goes missing matters:
- the assistant hop never reaches onHopUsage, so no llm_calls row is written
and the turn's spend is under-reported;
- the terminating result event's `usage` and `model` are lost, so the turn is
priced on a fallback guess or not at all;
- the `session_id` sniff misses, so the next wake spawns without --resume and
the agent loses the running task's context.
Every sibling reader in this file already carries partial lines
(ClaudeSession.onStdout, CodexSession.onStdout, CodexAdapter.probeWake), and
sse-parse.ts documents the carry as required; only this one-shot path was left
without it.
Carry the trailing partial line into the next chunk, and flush at close — the
engine's last line usually has no trailing newline, and that last line is
typically the `result` event. Decode through a StringDecoder so a multi-byte
character split across the same boundary is held back rather than emitted as
U+FFFD into the JSON, which would corrupt the reassembled line anyway.
Verified against the previous code: the hop count comes back 0 instead of 1 and
the session id comes back null. X
Xialie Zhuang committed
1c84d42a587cd31d685fb2ad0cb8327ee0ffa6f4
Parent: a5f485b
Committed by GitHub <noreply@github.com>
on 8/20/2026, 12:02:22 AM