diff --git a/src/renderer/components/thread/ChatPane/parts/items/ActiveSubAgentTile.tsx b/src/renderer/components/thread/ChatPane/parts/items/ActiveSubAgentTile.tsx index 30f0897ea..61b3a20d2 100644 --- a/src/renderer/components/thread/ChatPane/parts/items/ActiveSubAgentTile.tsx +++ b/src/renderer/components/thread/ChatPane/parts/items/ActiveSubAgentTile.tsx @@ -280,7 +280,9 @@ function ActiveSubAgentRow({ const isRunning = item?.state !== "completed" || payload?.status === "running" || workflowIsLive; if (registrationOnly || !item || !payload?.name) return null; - const display = deriveToolDisplay(payload); + // The dock groups rows under kind headers (Subagents/Crossagents), so the + // per-row "Agent:"/"Crossagent:" prefix would just repeat the header. + const display = deriveToolDisplay(payload, { bareAgentTitle: true }); const args = payload.args && typeof payload.args === "object" && !Array.isArray(payload.args) ? (payload.args as Record) diff --git a/src/renderer/components/thread/ChatPane/parts/items/SubAgentOverlay.test.tsx b/src/renderer/components/thread/ChatPane/parts/items/SubAgentOverlay.test.tsx index 9f35f2fc7..25b8b052f 100644 --- a/src/renderer/components/thread/ChatPane/parts/items/SubAgentOverlay.test.tsx +++ b/src/renderer/components/thread/ChatPane/parts/items/SubAgentOverlay.test.tsx @@ -342,6 +342,9 @@ describe("SubAgentContent", () => { expect(screen.queryByText("Background tasks")).not.toBeInTheDocument(); expect(screen.getByRole("button", { name: "Close subagents panel" })).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Close Crossagents panel" })).toBeInTheDocument(); + // Rows sit under the kind header, so they keep the bare label. + expect(screen.getByText("Codex · GPT-5.5")).toBeInTheDocument(); + expect(screen.queryByText(/^Crossagent:/)).not.toBeInTheDocument(); }); it("renders child messages through the main timeline parser", async () => { diff --git a/src/renderer/components/thread/ChatPane/parts/items/toolDisplay.test.ts b/src/renderer/components/thread/ChatPane/parts/items/toolDisplay.test.ts index 794f622ed..d4bc66d3b 100644 --- a/src/renderer/components/thread/ChatPane/parts/items/toolDisplay.test.ts +++ b/src/renderer/components/thread/ChatPane/parts/items/toolDisplay.test.ts @@ -260,6 +260,33 @@ describe("deriveToolDisplay", () => { ); }); + it("drops the kind prefix from bare agent titles used by grouped docks", () => { + const crossagent = makePayload({ + name: "Drive collection closures — Factory Droid · DeepSeek V4 Flash 0731 (Droid Core) - High", + isCrossagent: true, + }); + expect(deriveToolDisplay(crossagent).title).toBe( + "Crossagent: Drive collection closures — Factory Droid · DeepSeek V4 Flash 0731 (Droid Core) - High", + ); + expect(deriveToolDisplay(crossagent, { bareAgentTitle: true }).title).toBe( + "Drive collection closures — Factory Droid · DeepSeek V4 Flash 0731 (Droid Core) - High", + ); + + const subagent = makePayload({ + name: "probe worker alpha", + isSubAgent: true, + args: { subagent_type: "general-purpose" }, + }); + expect(deriveToolDisplay(subagent, { bareAgentTitle: true }).title).toBe("probe worker alpha"); + + const claudeTask = makePayload({ + name: "Task", + isSubAgent: true, + args: { subagent_type: "general-purpose" }, + }); + expect(deriveToolDisplay(claudeTask, { bareAgentTitle: true }).title).toBe("general-purpose"); + }); + it("recognizes Claude Workflow tool calls as background work", () => { const payload = makePayload({ name: "Workflow", diff --git a/src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts b/src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts index b163da160..d60b52a07 100644 --- a/src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts +++ b/src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts @@ -94,7 +94,19 @@ export { } from "@/shared/toolCallClassification"; export { isWorkflowTool }; -export function deriveToolDisplay(payload: ToolCallPayload): ToolDisplay { +export interface ToolDisplayOptions { + /** + * Omit the "Agent:"/"Crossagent:" kind prefix from delegated-agent titles — + * rows already grouped under a kind header (e.g. the Crossagents dock) + * would just repeat the header on every item. + */ + bareAgentTitle?: boolean; +} + +export function deriveToolDisplay( + payload: ToolCallPayload, + options?: ToolDisplayOptions, +): ToolDisplay { const args = readArgsObject(payload); const mcp = parseMcpName(payload); @@ -118,7 +130,7 @@ export function deriveToolDisplay(payload: ToolCallPayload): ToolDisplay { }; } - const claude = mapClaudeRawTool(payload.name, args); + const claude = mapClaudeRawTool(payload.name, args, options); if (claude) return claude; if (payload.isSubAgent === true || payload.isCrossagent === true) { @@ -127,6 +139,7 @@ export function deriveToolDisplay(payload: ToolCallPayload): ToolDisplay { preferFallback: payload.title !== undefined, isCrossagent: payload.isCrossagent === true, isResume: payload.isSubAgentResume === true, + ...(options?.bareAgentTitle ? { bare: true } : {}), ...(payload.subAgentType ? { subAgentType: payload.subAgentType } : {}), }), Icon: Bot, @@ -142,6 +155,7 @@ export function deriveToolDisplay(payload: ToolCallPayload): ToolDisplay { function mapClaudeRawTool( name: string, args: Record | undefined, + options?: ToolDisplayOptions, ): ToolDisplay | null { switch (name) { case "Read": @@ -156,7 +170,14 @@ function mapClaudeRawTool( return withPath("List", args, ["path"], FolderSearch); case "Task": case "Agent": - return { title: formatAgentTitle(args), Icon: Bot }; + return { + title: formatAgentTitle( + args, + undefined, + options?.bareAgentTitle ? { bare: true } : undefined, + ), + Icon: Bot, + }; case "BashOutput": return { title: titleWithValue(i18n._(msg`Bash output`), args, "bash_id"), Icon: Terminal }; case "KillBash": @@ -339,6 +360,7 @@ function formatAgentTitle( preferFallback?: boolean; isCrossagent?: boolean; isResume?: boolean; + bare?: boolean; subAgentType?: string; }, ): string { @@ -357,6 +379,10 @@ function formatAgentTitle( } return subagent ? i18n._(msg`Agent Resume: ${subagent}`) : i18n._(msg`Agent Resume`); } + if (options?.bare) { + if (description) return description; + return subagent ?? (options.isCrossagent ? i18n._(msg`Crossagent`) : i18n._(msg`Agent`)); + } if (options?.isCrossagent) { if (description) { return subagent