fix(tools): put body inside StructuredContent so it reaches the consumer#15
Merged
WiktorStarczewski merged 1 commit intomainfrom Apr 27, 2026
Merged
Conversation
Follow-up to #14, which turned out to be only half the fix. PR #14 emptied AskPeerClaudeOutput / SendPeerMessageOutput on the theory that consumers preferring StructuredContent would fall back to Content when StructuredContent was just `{}`. Empirical test against Claude Code showed they don't: the structured channel is treated as the source of truth whenever it's *present*, even if empty, and Content is silently dropped. Net effect: every successful `ask_peer_claude` returned a literal "{}" to the calling model — body invisible. This PR puts the body inside StructuredContent (where the consumer actually looks) and keeps it in Content as a redundant fallback for any consumer that does the opposite: - AskPeerClaudeOutput.Body / .TurnCount / .ToolCallCount / .StopReason / .ElapsedMs / .ErrorSummary — restored as separate structured fields, with `Body` carrying the markdown transcript. - SendPeerMessageOutput aliased to AskPeerClaudeOutput (same surface). - ReadSessionOutput grows a `body` field for format="full"; format= "json" continues to omit it. - The transcriptHeaderLine / withTranscriptHeader helpers introduced by #14 are removed — no longer needed now that metadata rides on its own structured fields. - New regression test `TestAskPeerClaude_BodyReachesStructuredOnlyConsumer` decodes via the structured channel only and asserts body is non-empty. Existing happy-path tests updated to read body from structured rather than from the (now-removed) inline header. - Version bumped 0.3.1-dev → 0.3.2-dev so `get_peer_info` lets a calling Claude tell whether the connected peer has the actual fix. The IsError signaling, errResult helper, and all other tools are unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #14, which turned out to be only half the fix.
PR #14 made
AskPeerClaudeOutput/SendPeerMessageOutputempty on the theory that MCP consumers preferringStructuredContentwould fall back toContentwhenStructuredContentwas just{}. Empirical test against Claude Code showed they don't: the structured channel is treated as the source of truth whenever it's present, even if empty, andContentis silently dropped. Net effect: every successfulask_peer_claudereturned a literal{}to the calling model — body invisible.This PR puts the body inside
StructuredContent(where the consumer actually looks) and keeps it inContentas a redundant fallback for any consumer that does the opposite. The body is now reachable through whichever channel the consumer prefers.Wire-shape changes
AskPeerClaudeOutput/SendPeerMessageOutput:type AskPeerClaudeOutput struct { + Body string `json:"body,omitempty"` + TurnCount int `json:"turnCount,omitempty"` + ToolCallCount int `json:"toolCallCount,omitempty"` + StopReason string `json:"stopReason,omitempty"` + ElapsedMs int64 `json:"elapsedMs,omitempty"` + ErrorSummary string `json:"errorSummary,omitempty"` }ReadSessionOutputgains the samebodyfield (only populated forformat="full";format="json"still omits it).The
transcriptHeaderLine/withTranscriptHeaderhelpers introduced by #14 are removed — no longer needed now that metadata rides on its own structured fields.Tests
ask_peer_claude/send_peer_messageupdated to decode the body fromStructuredContentrather than the (now-removed) inline header.TestAskPeerClaude_BodyReachesStructuredOnlyConsumerdecodes via the structured channel only and asserts the body is non-empty — the actual rendering rule on Claude Code-class clients.TestReadSession_ReturnsMarkdownAndMetadataextended to verifymeta.Bodycarries the rendered markdown.TestReadToolResult_NoStructuredContentis not affected — that tool already chose to drop StructuredContent rather than carry a body in it; whether to fold it into the same pattern is a separate decision (its primary use is large-sidecar reads, where the duplication might matter for size).Version bump
0.3.1-dev → 0.3.2-dev. Same diagnostic motivation as last time: lets a calling Claude tell, viaget_peer_info, whether the connected peer has the actual fix.Test plan
go vet ./...cleango test ./...— all packages green (modulo the pre-existing tailscale-test PATH-stub flake)origin/main(would have caught fix(tools): inline transcript metadata into ask/send_peer_message body #14's incompleteness)