Repository navigation
fix(turns): report partial output truthfully - #419
Conversation
waterbro-8
left a comment
There was a problem hiding this comment.
Code review APPROVE (not product 验收), head 0c09b382a9c9a34d73df4827778699edd6f7c964.
roleweave #411 Split A only:indeterminateMessage 吃到 partialOutput !== undefined,timeout/cancel 只在 turn record 真有 model.delta 时才说 partial output was preserved;否则 “before any output was received”。适配器和超时模型未改,不应 close #411。
覆盖:AC-003(有 delta)、新用例(无 delta)、cancel 无输出路径的文案已改对。CI 11/11 green。
非产品验收。不自动合。胡奕舟 can merge. Gemini stream-json / idle timeout 仍走后续 PR。
PeterGuy326
left a comment
There was a problem hiding this comment.
Review (roleweave #419, first slice of #411)
Re-review of head 0c09b38. Base verified: single commit on top of origin/main.
Verified by execution, in a clean worktree: npm run build exit 0; turns.test.js + turns-cancel.test.js = 29 tests / 29 pass / 0 fail, matching your body exactly. CI green on Node 24 macOS + Ubuntu.
The claim holds, and the way you did it is the reason it holds:
extractPartialOutputfilters strictly one.type === "model.delta", so "partial output was preserved" is backed by an actual model delta and nothing else.- The message argument and the persisted field come from the same
partialOutput !== undefinedexpression (apps/server/src/routes/turns.ts:455and:458). Copy and record cannot diverge, which is the failure mode this fix is about — derivinghasPartialOutputseparately would have left room for exactly the drift #411 reported. - Streaming and timeout semantics untouched: the diff is message branches plus tests;
retryablelogic is unchanged, and the other indeterminate codes still resolve throughINDETERMINATE_MESSAGES. - Moving
turn_timeout/turn_cancelledout of the table into the function is fine, though it does mean those two codes no longer sit with their siblings — worth a comment if the table grows.
Non-blocking
turn_cancelledpositive branch has no test. After your change,turns-cancel.test.ts:64asserts only "before any output was received". ThehasPartialOutput === truepath for cancellation is untested, whileturn_timeouthas both (AC-003plus your new case). Since this PR's whole point is that the two claims must track reality independently per code, add the symmetric cancel-with-delta case.- Empty deltas count as preserved. A turn that emitted only zero-length
model.deltaevents yieldspartialOutput === "", sopartialOutput !== undefinedis true and the message still says "partial output was preserved" whilerecord.outputis the empty string. Strictly the field is present, so this is not a lie, but decide whether an empty join should read as "no output received" — if so, the predicate becomespartialOutput !== undefined && partialOutput !== "". - Rebase note, not a defect here. #418 also edits
apps/server/src/routes/turns.ts(different hunks::69/:386vs:455/:585/:592). Both are mergeable againstmaintoday, but whichever lands second will need a rebase.
Verdict: no correctness concerns in scope. I'd merge this once the cancel-branch test is added, or as-is if you'd rather keep the slice minimal and follow up.
First, isolated #411 fix: indeterminate timeout/cancellation copy now receives whether
model.deltaoutput exists. It only says partial output was preserved when the turn record actually has partial output; otherwise it says no output was received. This intentionally does not change adapters or timeout semantics.Validation:
npm run build;node --test apps/server/dist/test/turns.test.js apps/server/dist/test/turns-cancel.test.js(29 passed).