Context
Spawned from PR #264 review (issue #229 v1 loop detection).
When the loop gate fires stop, recordSyntheticStop does two things:
- Writes the in-flight tool part to
status: "error" with loopAction: "stop" metadata.
- Writes a synthetic assistant text part with the rendered Chinese summary.
The locked #229 spec says the stop assistant message is the rendered text part, not a raw error state, and the wrapper does not synthesize a tool result on stop. Yet step 1 above is unavoidable at runtime: ai-sdk creates a "running" tool part via tool-input-start BEFORE the wrapper's gate runs, so we have to settle that part somehow. The current implementation settles it as status: "error", which renders as a failed tool bubble in addition to the Chinese summary.
What to do
Hide tool parts that carry metadata.diagnostics.loop.loopAction === "stop" from the message rendering layer in packages/app/src/components/messages (and the desktop conversation transcript). The metadata stays on the part so Export.deriveSnapshotDiagnostics can still emit diagnostics.loop.last and so the export layer keeps the audit trail.
Why P2
The PR #264 fix already prevents real tool execution after stop, and the Chinese summary text part is correct. This issue is purely a polish step: removing the redundant failed-tool bubble that shows up immediately above the summary.
Acceptance
Context
Spawned from PR #264 review (issue #229 v1 loop detection).
When the loop gate fires
stop,recordSyntheticStopdoes two things:status: "error"withloopAction: "stop"metadata.The locked #229 spec says the stop assistant message is the rendered text part, not a raw error state, and the wrapper does not synthesize a tool result on stop. Yet step 1 above is unavoidable at runtime: ai-sdk creates a "running" tool part via
tool-input-startBEFORE the wrapper's gate runs, so we have to settle that part somehow. The current implementation settles it asstatus: "error", which renders as a failed tool bubble in addition to the Chinese summary.What to do
Hide tool parts that carry
metadata.diagnostics.loop.loopAction === "stop"from the message rendering layer inpackages/app/src/components/messages(and the desktop conversation transcript). The metadata stays on the part soExport.deriveSnapshotDiagnosticscan still emitdiagnostics.loop.lastand so the export layer keeps the audit trail.Why P2
The PR #264 fix already prevents real tool execution after stop, and the Chinese summary text part is correct. This issue is purely a polish step: removing the redundant failed-tool bubble that shows up immediately above the summary.
Acceptance
loopAction === "stop"(or renders them collapsed)