feat(harness): store and serialize per-tool args/result in subagent execution#8
Conversation
…xecution Subagent tool calls now carry their input args and output result through the full pipeline so UIs can render them as proper collapsible tool rows instead of a cloud of name badges. Changes: - `toolCallLog` entries now store `args` (from tool-call chunk) and `result` (from tool-result chunk) - `buildSubagentMeta` emits a `<subagent-tool-calls>` JSON block before the existing `<subagent-meta>` tag; results are truncated at 2000 chars - `parseSubagentMeta` reads the new block when present, falls back to the legacy `name:status` list for backward compatibility - `ActiveSubagentState.toolCalls` entries now include `args` and `result` so live (streaming) state is equally rich - `subagent_tool_start` handler stores `subToolArgs` on the live entry - `subagent_tool_end` handler stores `subToolResult` on the live entry, matched by name + null result (handles same-tool repeated calls)
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughSubagent tool-call tracking was expanded: tool-call records now include Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
3 issues found across 3 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/core/src/harness/tools.ts">
<violation number="1" location="packages/core/src/harness/tools.ts:579">
P2: Tool-call metadata size is not bounded because `args` is serialized without truncation while only `result` is capped.</violation>
<violation number="2" location="packages/core/src/harness/tools.ts:601">
P2: Tag-delimited JSON parsing is unsafe: embedded `</subagent-tool-calls>` in tool result content truncates the capture and breaks `JSON.parse`.</violation>
<violation number="3" location="packages/core/src/harness/tools.ts:622">
P2: `parseSubagentMeta` now strips `<subagent-tool-calls>` content even when parsing/metadata validation fails, causing silent loss of legitimate result text.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/core/src/harness/harness.ts`:
- Around line 2659-2664: The assignment to tc.result uses JSON.stringify on
event.subToolResult (unknown) which can throw (e.g., BigInt or circular), and
because this occurs inside applyDisplayStateUpdate() called from emit() without
error handling it can abort before dispatchToListeners(); wrap the stringify in
a safe guard: detect if subToolResult is a string and keep it, otherwise attempt
JSON.stringify in a try/catch and on error fall back to a safe representation
(e.g., String(event.subToolResult) or a sentinel like "[unserializable]" or
null) so tc.result never throws; update the logic around tc.result and the
handling of event.subToolResult in applyDisplayStateUpdate() to use this guarded
conversion.
In `@packages/core/src/harness/tools.ts`:
- Around line 575-586: The embedded JSON in the <subagent-tool-calls> tag can
contain the sentinel sequence and break the non‑greedy parser; instead serialize
the toolCalls payload (the logic that builds toolDetails using truncateToolData
and tc entries), base64‑encode that JSON, and emit it as <subagent-tool-calls
encoding="base64">BASE64_PAYLOAD</subagent-tool-calls> (keep the surrounding
meta emission that includes modelId, durationMs, tools unchanged). Also update
parseSubagentMeta() to detect encoding="base64", base64‑decode the tag content
before calling JSON.parse, and apply the same change to the other similar block
around the tool serialization (the second occurrence near the 601–617 region).
Ensure you still set isError defaulting via tc.isError ?? false and preserve
existing truncation via truncateToolData when creating the JSON prior to
encoding.
In `@packages/core/src/harness/types.ts`:
- Line 468: The toolCalls entry type needs a stable per-invocation ID: add a
string property (e.g., subToolCallId) to the toolCalls array item type in
types.ts and update any code that constructs these entries so it populates
subToolCallId from the stream events. Carry subToolCallId through the
subagent_tool_start and subagent_tool_end event handlers (the places that
emit/consume these events in tools.ts and harness.ts) and update the matching
logic in harness.ts (the routine that currently guesses by name and args) to
match on subToolCallId instead of only name/args so repeated same-name calls are
unambiguous.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: fc027137-27df-4535-9d57-f29b71200493
📒 Files selected for processing (3)
packages/core/src/harness/harness.tspackages/core/src/harness/tools.tspackages/core/src/harness/types.ts
Summary
Subagent tool calls now carry their input args and output result through the full pipeline so UIs can render them as proper collapsible tool rows (e.g. BashTool showing the command + stdout) instead of a cloud of name badges.
Related PRs
parseSubagentToolResult.tsandSubagentInnerToolCallto render full per-tool detail in the chat interface.Changes
toolCallLogentries now storeargs(fromtool-callchunk) andresult(fromtool-resultchunk)buildSubagentMetaemits a<subagent-tool-calls>JSON block before the existing<subagent-meta>tag; results are truncated at 2 000 chars to keep the stored string boundedparseSubagentMetareads the new block when present, falls back to the legacyname:statuslist — fully backward compatibleActiveSubagentState.toolCallsentries now includeargsandresultso the live (streaming) view is equally richsubagent_tool_starthandler storessubToolArgson the live entrysubagent_tool_endhandler storessubToolResult, matched by name +nullresult (correctly handles the same tool being called multiple times)Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation