Follow-up from #6. Two renderers for the same event shape now live in two places:
formatToolCallShort in src/subagent.ts — feeds the live widget, tight budget.
formatToolCallFull in src/subagent-diagnostics.ts — feeds the failure body's activity trail, generous budget (256-char/line, 20 events).
They cover the same set of tools (bash, read, write, edit, grep, find, ls, unknown) with different truncation rules and slightly different field-lookup fallbacks. If a new tool ships or an existing tool's argument shape changes, both sites have to be kept in sync — and only one is covered by the new unit tests.
Goal
Single source of truth for "render a ToolCallEvent for human display", parameterized on budget.
Sketch
interface RenderOptions {
maxLineChars: number; // per-line cap
pathStyle: 'full' | 'collapsed'; // widget wants collapsed, trail wants full
}
function formatToolCall(event: ToolCallEvent, opts: RenderOptions): string
formatToolCallShort becomes formatToolCall(e, { maxLineChars: 80, pathStyle: 'collapsed' }).
formatToolCallFull becomes formatToolCall(e, { maxLineChars: 256, pathStyle: 'full' }).
Non-goals
- Changing the widget's visual output. The unified renderer should produce byte-identical output to the current
formatToolCallShort when called with the widget's options.
- Consolidating
TrackedRun.lastToolCall (subagent.ts) with activityTrail (diagnostics). They're different concerns — live status vs. post-mortem.
Testing
Move the widget's implicit expectations into explicit tests in subagent-diagnostics.test.ts before the refactor, so the byte-for-byte invariant is pinned.
Deferred from #6 as taste rather than blocker; file this so it doesn't drop.
Follow-up from #6. Two renderers for the same event shape now live in two places:
formatToolCallShortinsrc/subagent.ts— feeds the live widget, tight budget.formatToolCallFullinsrc/subagent-diagnostics.ts— feeds the failure body's activity trail, generous budget (256-char/line, 20 events).They cover the same set of tools (
bash,read,write,edit,grep,find,ls, unknown) with different truncation rules and slightly different field-lookup fallbacks. If a new tool ships or an existing tool's argument shape changes, both sites have to be kept in sync — and only one is covered by the new unit tests.Goal
Single source of truth for "render a
ToolCallEventfor human display", parameterized on budget.Sketch
formatToolCallShortbecomesformatToolCall(e, { maxLineChars: 80, pathStyle: 'collapsed' }).formatToolCallFullbecomesformatToolCall(e, { maxLineChars: 256, pathStyle: 'full' }).Non-goals
formatToolCallShortwhen called with the widget's options.TrackedRun.lastToolCall(subagent.ts) withactivityTrail(diagnostics). They're different concerns — live status vs. post-mortem.Testing
Move the widget's implicit expectations into explicit tests in
subagent-diagnostics.test.tsbefore the refactor, so the byte-for-byte invariant is pinned.Deferred from #6 as taste rather than blocker; file this so it doesn't drop.