Skip to content

Commit 798ac94

Browse files
committed
Strip the sub-agent report envelope from expanded task details
A task tool result wraps a sub-agent's reply as `Sub-agent "desc" reported:` followed by raw `## Summary` / `## Findings` markdown headings. The collapsed one-line preview already stripped this envelope, but the expanded detail behind the arrow rendered the result text as plain lines, so the raw prefix and literal `##` markers leaked straight into the transcript. Give "task" results the same always-curated treatment tool_search catalogues already get in resultSummary, and strip the envelope and heading markers from the text that becomes the expanded body.
1 parent c949712 commit 798ac94

3 files changed

Lines changed: 57 additions & 1 deletion

File tree

src/tui-opentui/mcp-view.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,36 @@ describe("collapsed tool results", () => {
214214
expect(row.summary).toBe("Read Linear project Alpha")
215215
})
216216

217+
test("a sub-agent report collapses to its summary line, envelope and headings stripped from the detail", () => {
218+
const row = toolResultRow({
219+
name: "task",
220+
content:
221+
'Sub-agent "explore callers" reported:\n\n## Summary\nFound 3 call sites in src/tui-opentui.\n\n## Findings\n- shell.ts line 40\n- diff.ts line 12',
222+
})
223+
expect(row.summary).toBe("Found 3 call sites in src/tui-opentui.")
224+
expect(isCollapsibleRow(row)).toBe(true)
225+
const detailText = (row.detail ?? [])
226+
.map((line) => line.map((seg) => seg.text).join(""))
227+
.join("\n")
228+
expect(detailText).not.toContain("Sub-agent")
229+
expect(detailText).not.toContain("## ")
230+
expect(detailText).toContain("Found 3 call sites in src/tui-opentui.")
231+
expect(detailText).toContain("Findings")
232+
})
233+
234+
test("a short sub-agent report is still curated, not left as raw envelope", () => {
235+
const row = toolResultRow({
236+
name: "task",
237+
content: 'Sub-agent "quick check" reported:\n\n## Summary\nAll clear.',
238+
})
239+
expect(row.summary).toBe("All clear.")
240+
const detailText = (row.detail ?? [])
241+
.map((line) => line.map((seg) => seg.text).join(""))
242+
.join("\n")
243+
expect(detailText).not.toContain("Sub-agent")
244+
expect(detailText).not.toContain("## ")
245+
})
246+
217247
test("an error result is neither summarised nor collapsed", () => {
218248
const row = toolResultRow({
219249
name: "mcp__linear__list_projects",

src/tui-opentui/mcp-view.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
recordScalar,
2727
type McpRecords,
2828
} from "../tui/mcp-result-format.js"
29-
import { summarizeToolResult } from "../tui/tool-formatter.js"
29+
import { stripTaskReportEnvelope, summarizeToolResult } from "../tui/tool-formatter.js"
3030
import type { StreamRow, StyledBodyLine } from "./stream.js"
3131

3232
export type McpTone =
@@ -280,6 +280,9 @@ const DETAIL_TEXT_MAX = 72
280280
/** The tool a capability search arrives through; its result is a catalogue. */
281281
const TOOL_SEARCH_TOOL = "tool_search"
282282

283+
/** The tool a sub-agent dispatch arrives through; its result is a report. */
284+
const TASK_TOOL = "task"
285+
283286
function titleCase(word: string): string {
284287
return word.length === 0 ? word : `${word[0]!.toUpperCase()}${word.slice(1)}`
285288
}
@@ -448,6 +451,13 @@ function recordSummary(
448451
*/
449452
function resultSummary(input: ToolResultRowInput): ResultSummary | null {
450453
const content = input.content
454+
if (input.name === TASK_TOOL) {
455+
// A worker's reply wraps a "Sub-agent ... reported:" / "## Summary"
456+
// envelope. The one-line preview already strips it; the expanded detail
457+
// must too, or the raw envelope and heading markers leak as plain text.
458+
const { preview } = summarizeToolResult(input.name, content)
459+
return { summary: preview, detail: bodyLines(stripTaskReportEnvelope(content)) }
460+
}
451461
if (input.name === TOOL_SEARCH_TOOL) {
452462
const catalogue = toolCatalogueSummary(content)
453463
if (catalogue !== null) return catalogue

src/tui/tool-formatter.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,22 @@ function pathFromResult(toolName: string, content: string): string | null {
424424
return null;
425425
}
426426

427+
/**
428+
* Task tool results wrap a sub-agent's reply in `Sub-agent "desc" reported:`
429+
* plus raw `## ` markdown headings. The transcript's expanded detail view
430+
* renders plain text, so an unstripped heading would show its literal `##` —
431+
* drop the envelope and heading markers, keeping the report's own words.
432+
*/
433+
export function stripTaskReportEnvelope(content: string): string {
434+
const trimmed = content.trim();
435+
const reported = trimmed.match(/^Sub-agent "([^"]*)" reported:\s*([\s\S]*)$/i);
436+
const body = (reported?.[2] ?? trimmed).trim();
437+
return body
438+
.split("\n")
439+
.map((line) => line.replace(/^##\s+/, ""))
440+
.join("\n");
441+
}
442+
427443
// Task tool results are either "Sub-agent \"desc\" reported:\n\n## Summary\n..."
428444
// or a cancel notice. Pull a one-line human preview without leaking markdown headers.
429445
function summarizeTaskResultPreview(content: string): string {

0 commit comments

Comments
 (0)