Context
PR #378 (StreamDelta union extension) adds thinking, tool-use-start, tool-use-input-delta, tool-use-stop, compact-boundary, and usage variants — fully wired on the SDK path (ClaudeCliAdapter._dispatchMessage).
The subprocess path (ClaudeSubprocessAdapter._handleNdjsonLine) was deferred:
Deferred to follow-up PRs:
Subprocess parity — ClaudeSubprocessAdapter._handleNdjsonLine uses a different wire format (system/init / assistant/message / result, not stream_event content blocks). Extending it for tool-use parity needs the CLI's actual --include-partial-messages event surface verified against a real binary.
— from PR #378 description.
This issue tracks closing that gap so the subscription transport gets the same tool-use / thinking / usage / compact-boundary coverage as the SDK transport.
Why it matters
Today, a user on the subscription transport (transportKind === 'subscription' → ClaudeSubprocessAdapter) sends a turn that produces tool calls or extended thinking, and the chat UI:
- Sees only text deltas (existing behaviour) — falls back to the pre-extension experience.
- Misses tool-call cards, thinking-block panels, usage telemetry, compaction notices.
That's a transport-dependent UX regression — the SDK transport user gets one experience, the subscription user gets another.
Wire format gap
The SDK adapter already maps Anthropic stream_event content blocks:
system + subtype === 'init' → session-id
system + subtype === 'compact_boundary' → compact-boundary
stream_event + content_block_start (type === 'tool_use') → tool-use-start
stream_event + content_block_delta:
text_delta → text
thinking_delta → thinking
input_json_delta → tool-use-input-delta
stream_event + content_block_stop (tool_use only) → tool-use-stop
stream_event + message_start / message_delta + .usage → usage
The subprocess emits a different envelope when --output-format stream-json --include-partial-messages is set. Need to verify the actual event names against a current claude binary, then implement the same dispatcher in _handleNdjsonLine.
Plan
Step 1 — capture real wire output
Run locally:
echo "Use the Bash tool to list /tmp" | claude -p \
--output-format stream-json \
--verbose \
--include-partial-messages \
--allowedTools Bash \
> /tmp/wire.ndjson 2>&1
Note: that command will trigger the real Anthropic Terms of Service flow. Run it against a permissive sandbox vault.
Document the event names + payload shapes in inputs/subproc-tool-wire-2026-XX/ so future contributors don't have to re-capture.
Step 2 — extend _handleNdjsonLine
Mirror the SDK dispatcher. Use the same blockId = ${turnId}-${index} pattern from PR #378 for tool blocks. Surface the same StreamSink (or createPushChannel) helper.
Watch out for:
- The subprocess buffers chunks across
\n boundaries — already handled by the existing stdoutBuffer.
--include-partial-messages may produce duplicate emits (content_block_delta followed by content_block_stop with the same final fragment); Claudian PR #510 fixed similar dedup. Add a per-blockId "already-stopped" guard.
Step 3 — tests
Step 4 — MessageList.vue scroll watcher
PR #379 added a tool-call input-growth observer to the scroll watcher. Verify it works against the subprocess transport too (same streamingToolCalls map → same observable).
Acceptance
References
Context
PR #378 (StreamDelta union extension) adds
thinking,tool-use-start,tool-use-input-delta,tool-use-stop,compact-boundary, andusagevariants — fully wired on the SDK path (ClaudeCliAdapter._dispatchMessage).The subprocess path (
ClaudeSubprocessAdapter._handleNdjsonLine) was deferred:— from PR #378 description.
This issue tracks closing that gap so the subscription transport gets the same tool-use / thinking / usage / compact-boundary coverage as the SDK transport.
Why it matters
Today, a user on the subscription transport (
transportKind === 'subscription'→ClaudeSubprocessAdapter) sends a turn that produces tool calls or extended thinking, and the chat UI:That's a transport-dependent UX regression — the SDK transport user gets one experience, the subscription user gets another.
Wire format gap
The SDK adapter already maps Anthropic
stream_eventcontent blocks:system+subtype === 'init'→session-idsystem+subtype === 'compact_boundary'→compact-boundarystream_event+content_block_start(type === 'tool_use') →tool-use-startstream_event+content_block_delta:text_delta→textthinking_delta→thinkinginput_json_delta→tool-use-input-deltastream_event+content_block_stop(tool_use only) →tool-use-stopstream_event+message_start/message_delta+.usage→usageThe subprocess emits a different envelope when
--output-format stream-json --include-partial-messagesis set. Need to verify the actual event names against a currentclaudebinary, then implement the same dispatcher in_handleNdjsonLine.Plan
Step 1 — capture real wire output
Run locally:
Note: that command will trigger the real Anthropic Terms of Service flow. Run it against a permissive sandbox vault.
Document the event names + payload shapes in
inputs/subproc-tool-wire-2026-XX/so future contributors don't have to re-capture.Step 2 — extend
_handleNdjsonLineMirror the SDK dispatcher. Use the same
blockId = ${turnId}-${index}pattern from PR #378 for tool blocks. Surface the sameStreamSink(orcreatePushChannel) helper.Watch out for:
\nboundaries — already handled by the existingstdoutBuffer.--include-partial-messagesmay produce duplicate emits (content_block_deltafollowed bycontent_block_stopwith the same final fragment); Claudian PR #510 fixed similar dedup. Add a per-blockId"already-stopped" guard.Step 3 — tests
tests/infrastructure/obsidian/ClaudeSubprocessAdapter.deltaExtension.test.ts— one case per new variant. Fixtures from the wire capture in step 1.tests/infrastructure/obsidian/ClaudeSubprocessAdapter.streaming.test.tswith the same coverage as the SDK version on PR feat(agent): extend StreamDelta with thinking/tool-use/compact/usage variants (PR-ASV-2-delta) #378.Step 4 —
MessageList.vuescroll watcherPR #379 added a tool-call input-growth observer to the scroll watcher. Verify it works against the subprocess transport too (same
streamingToolCallsmap → same observable).Acceptance
inputs/.ClaudeSubprocessAdapter._handleNdjsonLineemits all newStreamDeltavariants.content_block_stop-after-_deltarace.References
specs/agent-sidepanel-v2/workflow-state.md→ Increment 2+ research wave.["enhancement"]