mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-21 05:28:11 +00:00
* fix(frontend): keep orphan tool messages visible LangGraph `messages-tuple` stream mode can emit tool-result events out of order or replay them from subagent state (e.g. the bash subagent under LocalSandboxProvider with allow_host_bash: true). When that happens, the tool message arrives after a terminal assistant/human group, so getMessageGroups' lastOpenGroup() returns null. The previous behaviour was console.error + drop, which silently hid the tool result from the UI - the user could not see the tool output or tool-call records even though the agent executed the action correctly (backend + Langfuse trace were both fine). Fallback now attaches the orphan tool message to the most recent group so the UI shows it. Adds a unit test that covers the orphan-tool path and a duplicate-stream regression test. * test(frontend): make orphan-tool replay test actually reach the fallback The previous fixture was `human → ai(tool_calls) → tool → tool(replay)` with no terminal group in between, so both tool messages hit the unchanged happy path (`open.messages.push(...)`). Neither message was an orphan, the new `else if (groups.length > 0)` fallback was never exercised, and the `>= 1` length assertion was satisfied trivially by t-1a alone. Interleave a terminal assistant message between the original tool result and the replayed one, so t-1b arrives when lastOpenGroup() returns null. Now the strict assertion that t-1b is reachable can only pass via the new fallback branch. Review feedback from @willem-bd on PR #3880. * fix(frontend): satisfy noUncheckedIndexedAccess in orphan-tool fallback The fallback branch pushed into `groups[groups.length - 1].messages` directly, which trips `noUncheckedIndexedAccess` under the project's strict TS config and breaks lint-frontend, e2e-tests, and the full-stack render CI layer. Take `groups[groups.length - 1]` into a local `lastGroup` and check for `undefined` explicitly. The `else if (groups.length > 0)` guard becomes redundant once `lastGroup` is checked, so the branches are folded into the outer `else` to keep the structure flat. Behavior is unchanged: orphan tools still attach to the most recent group, and the empty-groups diagnostic still logs at ERROR level. Review feedback from @willem-bd on PR #3880. --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>