feat: implement diff handling for tool calls and enhance error detail…#333
Conversation
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — two complementary improvements: frontend diff rendering for ACP and native file-editor tool calls, and backend error detail propagation so agent_conversations.error_message is populated on ERROR terminal status rather than only logged.
- Render file diffs in tool-fallback cards —
extractDiffBlocksparses ACP{type:"diff"}content blocks (tolerating both snake_case and camelCase), andextractFileEditorDiffhandles native OpenHandsfile_editorobservations. Tool-fallback shows diffs viaDiffLineRow(exported fromcontent-diff.tsx) throughcomputeLineDiffcomputed insideuseMemo. Diffs replace raw args when present. - Propagate ConversationErrorEvent detail —
_wait_for_done_or_stopreturns a 4therror_detailtuple element, consumed byrun_conversationand passed toupdate_conversation_status(error_message=...). Previously detail was only logged, leavingerror_messageNULL for every conversation that failed with ERROR/STUCK.
DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏
Adding error_message to update_conversation_status's call signature changed the recorded mock call shape, so the "paused" assertions no longer matched and the "failed" test was left checking status only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — test-only additions that validate the error_detail propagation added in the prior commit.
- Unit test for
_wait_for_done_or_stoperror detail — newtest_wait_for_done_or_stop_returns_error_detail_on_terminal_errorcreates a fake conversation in ERROR status and asserts the extracted detail matches theConversationErrorEventcontent. Existing tests updated to destructure the new 4-tuple. - E2E assertions for
error_messagepropagation —test_agent_reply_is_persisted_through_real_sandboxandtest_agent_tool_call_then_reply_persists_full_flownow asserterror_message=Noneon pause.test_llm_failure_marks_conversation_failedasserts"fake upstream failure" in error_message, closing the gap where the prior commit added the feature but didn't validate it end-to-end.
DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏
…r unchanged lines
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — performance guard and UX improvement: collapse long unchanged diff runs into context markers and skip expensive diff computation for very large files.
- Add
collapseUnchangedContextto collapse long unchanged runs — new pure function indiff-utils.tscollapses stretches of unchanged lines beyond a configurable context window (default 3) into a single{type: "collapsed", count}marker, matchinggit diff's hunk convention. Bothtool-fallback.tsxandcontent-diff.tsxuse it so a one-line edit to a large file renders only the relevant context instead of every unchanged line. - Add
MAX_DIFF_LINES_PER_SIDE = 2000guard intool-fallback.tsx— skipscomputeLineDiffentirely when either the old or new text exceeds 2000 lines, showing a "diff too large" i18n message. The O(n·m) DP matrix for a multi-thousand-line file would otherwise freeze the tab. - Add
DiffCollapsedRowcomponent and i18n keys — renders the collapsed-lines indicator using the newcontentDiff.collapsedLineskey across all 9 locales, pluscontentDiff.diffTooLargefor the too-large guard. - Thorough
collapseUnchangedContextunit tests — 6 test cases cover short untouched runs, long mid-file runs with context on both sides, leading-only context, trailing-only context, short gaps that merge without collapsing, and empty input.
DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏
CI runs `vitest run`, which cannot bundle the Bun-native `bun:test` built-in — it only worked locally under `bun test`, which shims it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Summary
Edit/Write-style tool calls rendered as raw stringified JSON args instead of a readable diff.ToolFallbacknow renders a real line-by-line diff (reusingDiffLineRowfromcontent-diff.tsx) whenever a tool-call part carries diff data, falling back to the existing JSON view otherwise.eventsToThreadMessagesnow extracts diff content from two distinct event shapes, since edits can come from either backend path:ACPToolCallEvents that report edits as{type: "diff", path, old_text, new_text}content blocks.file_editortool'sstr_replace/create/insertcommands, which report before/after content asold_content/new_contenton theFileEditorObservationthat follows theActionEvent— a completely different shape with nodiff-typed content block. Mirrors the SDK's ownFileEditorObservation._has_meaningful_diffsoviewcalls, errors, and no-op edits correctly fall back to the plain args view instead of showing an empty/misleading diff.services/ai-agent:_wait_for_done_or_stopnow returns theConversationErrorEventdetail alongside(stopped, errored, shutdown)sorun_conversationcan persist it toagent_conversations.error_messageon a terminalERROR/STUCKstatus. Previously this detail was only logged, leavingerror_messageNULLfor every conversation that failed this way.Fixes #315
Test plan
bun run testinapps/web— 323 tests passing (incl. new coverage for both the ACP diff-block path and the nativefile_editorpath:str_replace,create,view, and error cases)pytest tests/test_executor.pyinservices/ai-agent— 29 tests passing (incl. new coverage forerror_detailpropagation on a terminalERRORstatus)tsc --noEmit/biome checkon the changed frontend files — cleanfile_editortool calls (the actual edits) now produce a diff artifact, whileviewcalls and no-ops correctly do not🤖 Generated with Claude Code