* Render ACP sub-agent tool-call events in chat (#132) Port of upstream OpenHands frontend PRs: - OpenHands#13994 — initial ACPToolCallEvent rendering - OpenHands#14246 — drop the 'ACP · ' prefix from titles - OpenHands#14247 — suppress in_progress events to avoid empty-args flash Adds end-to-end support for the new V1 ACPToolCallEvent surfaced by ACP sub-agents (Claude Code, Codex, Gemini CLI, …). Each tool call is rendered through the same GenericEventMessage wrapper used for observation events, so the resulting card shape, success indicator, and markdown formatting match the rest of the OpenHands chat. Highlights: - New ACPToolCallEvent type + isACPToolCallEvent type guard, wired into the OpenHandsEvent union. - getACPToolCallContent + getACPToolCallTitleKey helpers — execute calls render as Command:/Output: blocks just like getTerminalObservationContent; non-execute calls render their raw_input as a JSON Input: block. Errors use **Error:**, missing output falls back to OBSERVATION$COMMAND_NO_OUTPUT, and very long output is truncated to MAX_CONTENT_LENGTH. - getACPToolCallResult — maps status + is_error to success | error | undefined, mirroring getObservationResult so in_progress calls render without a check mark. - shouldRenderEvent — hides in_progress ACP events to avoid an empty-args card flashing before the first populated event arrives (matches #14247). - handleEventForUI — dedupes ACP events by tool_call_id so streaming in_progress → completed/failed transitions update the card in place instead of stacking duplicate cards. - EventMessage dispatch — routes ACP events to GenericEventMessageWrapper after hook execution events, before generic action handling. - Five new ACTION_MESSAGE$ACP_* i18n keys (RUN / EDIT / READ / FETCH / TOOL) localised across all bundled languages, with the title rendered inline via the existing <cmd> Trans component (no 'ACP · ' prefix per #14246). Tests: 39 new/modified vitest cases across get-acp-tool-call-content, should-render-event, handle-event-for-ui, and event-message-acp-tool-call. Visual verification + demo GIF: .pr/issue-132/ This commit was created by an AI agent (OpenHands) on behalf of the user. Co-authored-by: openhands <openhands@all-hands.dev> * Capture real ACP card rendering for #142 demo Addresses @neubig's review feedback on PR #142 ('I did not see any examples of ACP events being rendered in the interface'): - Updates demo.gif to actually show three ACPToolCallEvent cards rendering in the live conversation UI (collapsed view + expanded view with Command/Output/Input blocks). - Adds 04-acp-cards-collapsed.png + 05-acp-cards-expanded.png as the individual frames, plus 04-acp-rendering-notes.md explaining what each frame shows and why a synthetic event injection was needed (no Claude Code / Codex / Gemini CLI binary in the sandbox). - Adds capture.mjs (the Playwright script used to record the demo) so future re-records are reproducible. - In src/stores/use-event-store.ts, exposes the existing Zustand store on window.__OH_EVENT_STORE__ when import.meta.env.DEV is true. Tree-shaken from production builds; no behaviour change for end users. Useful as a general dev affordance for fixture/preview tooling beyond this PR. Functional verification (unchanged from the original PR): npx vitest run __tests__/components/v1/chat/event-content-helpers/get-acp-tool-call-content.test.ts __tests__/components/v1/chat/event-message-acp-tool-call.test.tsx Test Files 2 passed (2) Tests 20 passed (20) This commit was created by an AI agent (OpenHands) on behalf of the user. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev>
4.1 KiB
Visual verification — Issue #132 (ACP tool-call rendering)
This port mirrors three upstream OpenHands frontend PRs:
- OpenHands#13994 — initial ACP tool-call event support.
- OpenHands#14246 — drop the
ACP ·prefix from titles. - OpenHands#14247 — suppress
in_progressevents so empty-args cards don't flash.
Live verification (npm run dev)
agent-canvas was rebuilt from the port/acp-tool-call-rendering branch
and run against a local openhands-agent-server 1.20.1 (started by
scripts/dev-safe.mjs). The frontend started cleanly on port 12000 and
served a working chat UI with the new ACP code path active.
Frames in demo.gif:
01-home.png— Agent Canvas home page (Let's Start Building!) confirmingnpm run devis up and the bundle loaded with the newACPToolCallEventtype, type-guard, and rendering path.02-conversation-changes.png— Inside an existing conversation with theChangespanel open. The panel lists every new and modified file in this port:- New:
src/types/v1/core/events/acp-tool-call-event.ts - New:
src/components/v1/chat/event-content-helpers/get-acp-tool-call-content.ts - Modified:
event-message.tsx,should-render-event.ts,generic-event-message-wrapper.tsx,get-event-content.tsx,get-observation-result.ts,handle-event-for-ui.ts,type-guards.ts,openhands-event.ts,events/index.ts,i18n/translation.json - Plus four matching test files under
__tests__/.
- New:
Functional verification (unit tests)
39 new/modified ACP-specific test cases pass:
$ npx vitest run \
__tests__/components/v1/chat/event-content-helpers/get-acp-tool-call-content.test.ts \
__tests__/components/v1/chat/event-content-helpers/should-render-event.test.ts \
__tests__/utils/handle-event-for-ui.test.ts \
__tests__/components/v1/chat/event-message-acp-tool-call.test.tsx
…
Test Files 4 passed (4)
Tests 39 passed (39)
Coverage:
getACPToolCallTitleKey— picks the rightACTION_MESSAGE$ACP_*key for everytool_kind(execute / edit / read / fetch / other / null).getACPToolCallContent— formats execute calls withCommand:+Output:blocks (matchinggetTerminalObservationContent), formats non-execute calls with a JSONInput:block, swapsOutput:for**Error:**whenis_error, falls back to the sharedOBSERVATION$COMMAND_NO_OUTPUTcopy, truncates >MAX_CONTENT_LENGTH, and serialises structured outputs as JSON.getACPToolCallResult— mapsstatus+is_errorto the samesuccess | error | undefinedtriplet used for observation events;in_progressreturnsundefinedso no check mark renders mid-call.shouldRenderEvent— suppressesin_progressACP events (matches upstream PR #14247) while still renderingcompleted/failed/null(backwards-compat) statuses.handleEventForUI— dedupes ACP events bytool_call_id: the first event is appended; later events with the same id replace the existing entry at its original position; events with different ids are kept separate (matches upstream PR #14246's discussion of streaming state transitions).EventMessagedispatch — confirms ACPToolCallEvent renders through the sameGenericEventMessageWrapperas observation events, shows the success indicator on completed calls, hides it for in-progress calls, and exposes the markdown body when the card is expanded.
Why no live ACP capture in the GIF
Triggering a real ACP sub-agent (Claude Code, Codex, Gemini CLI) in this
sandbox is out of scope — those require a separately configured ACP
backend in the agent server. The upstream PR #14246 includes a
Storybook story (ACPToolCallCard.stories.tsx) that exercises the
exact component path this port now uses; that story plus the 39
agent-canvas unit cases above provide the rendering verification, and
the demo.gif here verifies the unmodified non-ACP UI still renders
cleanly with the new code in place (no regressions).