From 1b76c07ee727c323381d0391b62862e6a80f457c Mon Sep 17 00:00:00 2001 From: ChroniCat Date: Wed, 18 Mar 2026 11:58:42 +0800 Subject: [PATCH 1/2] fix(control-ui): show tool call arguments in sidebar --- ui/src/ui/chat-markdown.browser.test.ts | 39 +++++++++++++++++- ui/src/ui/chat/tool-cards.ts | 24 +++++------ ui/src/ui/chat/tool-helpers.test.ts | 53 ++++++++++++++++++++++++- ui/src/ui/chat/tool-helpers.ts | 40 +++++++++++++++++++ 4 files changed, 139 insertions(+), 17 deletions(-) diff --git a/ui/src/ui/chat-markdown.browser.test.ts b/ui/src/ui/chat-markdown.browser.test.ts index 17a898bac4c..a3499539f79 100644 --- a/ui/src/ui/chat-markdown.browser.test.ts +++ b/ui/src/ui/chat-markdown.browser.test.ts @@ -31,7 +31,42 @@ describe("chat markdown rendering", () => { await app.updateComplete; - const strong = app.querySelector(".sidebar-markdown strong"); - expect(strong?.textContent).toBe("world"); + const strongNodes = Array.from(app.querySelectorAll(".sidebar-markdown strong")); + expect(strongNodes.map((node) => node.textContent)).toContain("world"); + }); + + it("shows tool call request parameters in the sidebar", async () => { + const app = mountApp("/chat"); + await app.updateComplete; + + const timestamp = Date.now(); + app.chatMessages = [ + { + role: "assistant", + content: [ + { + type: "toolcall", + name: "sessions_spawn", + arguments: { agentId: "research", prompt: "hello" }, + }, + ], + timestamp, + }, + ]; + + await app.updateComplete; + + const toolCard = app.querySelector(".chat-tool-card"); + expect(toolCard).not.toBeNull(); + toolCard?.click(); + + await app.updateComplete; + + const sidebar = app.querySelector(".sidebar-markdown"); + expect(sidebar?.textContent).toContain("Arguments"); + expect(sidebar?.textContent).toContain("agentId"); + expect(sidebar?.textContent).toContain("research"); + expect(sidebar?.textContent).toContain("prompt"); + expect(sidebar?.textContent).toContain("hello"); }); }); diff --git a/ui/src/ui/chat/tool-cards.ts b/ui/src/ui/chat/tool-cards.ts index acd427b9e77..d5975a16102 100644 --- a/ui/src/ui/chat/tool-cards.ts +++ b/ui/src/ui/chat/tool-cards.ts @@ -5,7 +5,7 @@ import type { ToolCard } from "../types/chat-types.ts"; import { TOOL_INLINE_THRESHOLD } from "./constants.ts"; import { extractTextCached } from "./message-extract.ts"; import { isToolResultMessage } from "./message-normalizer.ts"; -import { formatToolOutputForSidebar, getTruncatedPreview } from "./tool-helpers.ts"; +import { buildToolSidebarContent, getTruncatedPreview } from "./tool-helpers.ts"; export function extractToolCards(message: unknown): ToolCard[] { const m = message as Record; @@ -56,14 +56,14 @@ export function renderToolCardSidebar(card: ToolCard, onOpenSidebar?: (content: const canClick = Boolean(onOpenSidebar); const handleClick = canClick ? () => { - if (hasText) { - onOpenSidebar!(formatToolOutputForSidebar(card.text!)); - return; - } - const info = `## ${display.label}\n\n${ - detail ? `**Command:** \`${detail}\`\n\n` : "" - }*No output — tool completed successfully.*`; - onOpenSidebar!(info); + onOpenSidebar!( + buildToolSidebarContent({ + title: display.label, + detail, + args: card.args, + output: card.text, + }), + ); } : undefined; @@ -95,11 +95,7 @@ export function renderToolCardSidebar(card: ToolCard, onOpenSidebar?: (content: ${icons[display.icon]} ${display.label} - ${ - canClick - ? html`${hasText ? "View" : ""} ${icons.check}` - : nothing - } + ${canClick ? html`View ${icons.check}` : nothing} ${isEmpty && !canClick ? html`${icons.check}` : nothing} ${detail ? html`
${detail}
` : nothing} diff --git a/ui/src/ui/chat/tool-helpers.test.ts b/ui/src/ui/chat/tool-helpers.test.ts index f18cd738a7f..7965a7df93e 100644 --- a/ui/src/ui/chat/tool-helpers.test.ts +++ b/ui/src/ui/chat/tool-helpers.test.ts @@ -1,5 +1,10 @@ import { describe, it, expect } from "vitest"; -import { formatToolOutputForSidebar, getTruncatedPreview } from "./tool-helpers.ts"; +import { + buildToolSidebarContent, + formatToolOutputForSidebar, + formatToolPayloadForSidebar, + getTruncatedPreview, +} from "./tool-helpers.ts"; describe("tool-helpers", () => { describe("formatToolOutputForSidebar", () => { @@ -138,4 +143,50 @@ describe("tool-helpers", () => { expect(result.endsWith("…")).toBe(true); }); }); + + describe("formatToolPayloadForSidebar", () => { + it("formats structured arguments as JSON", () => { + const result = formatToolPayloadForSidebar({ agentId: "research", count: 2 }); + + expect(result).toBe(`\`\`\`json +{ + "agentId": "research", + "count": 2 +} +\`\`\``); + }); + + it("returns null for undefined arguments", () => { + expect(formatToolPayloadForSidebar(undefined)).toBeNull(); + }); + }); + + describe("buildToolSidebarContent", () => { + it("includes arguments and output when both are present", () => { + const result = buildToolSidebarContent({ + title: "Session Spawn", + detail: "sessions_spawn research", + args: { agentId: "research" }, + output: '{"status":"ok"}', + }); + + expect(result).toContain("## Session Spawn"); + expect(result).toContain("**Command:** `sessions_spawn research`"); + expect(result).toContain("**Arguments**"); + expect(result).toContain('"agentId": "research"'); + expect(result).toContain("**Output**"); + expect(result).toContain('"status": "ok"'); + }); + + it("shows completion text when output is absent", () => { + const result = buildToolSidebarContent({ + title: "Session Spawn", + args: { agentId: "research" }, + }); + + expect(result).toContain("**Arguments**"); + expect(result).toContain('"agentId": "research"'); + expect(result).toContain("*No output - tool completed successfully.*"); + }); + }); }); diff --git a/ui/src/ui/chat/tool-helpers.ts b/ui/src/ui/chat/tool-helpers.ts index 322b6058f6a..d95a695b2a9 100644 --- a/ui/src/ui/chat/tool-helpers.ts +++ b/ui/src/ui/chat/tool-helpers.ts @@ -22,6 +22,46 @@ export function formatToolOutputForSidebar(text: string): string { return text; } +/** + * Format tool-call input/arguments for display in the sidebar. + * Uses JSON code blocks for structured values and preserves plain text input. + */ +export function formatToolPayloadForSidebar(value: unknown): string | null { + if (value === undefined) { + return null; + } + if (typeof value === "string") { + return formatToolOutputForSidebar(value); + } + try { + return "```json\n" + JSON.stringify(value, null, 2) + "\n```"; + } catch { + return String(value); + } +} + +export function buildToolSidebarContent(params: { + title: string; + detail?: string; + args?: unknown; + output?: string; +}): string { + const sections = [`## ${params.title}`]; + if (params.detail) { + sections.push(`**Command:** \`${params.detail}\``); + } + const args = formatToolPayloadForSidebar(params.args); + if (args !== null) { + sections.push(`**Arguments**\n${args}`); + } + if (typeof params.output === "string") { + sections.push(`**Output**\n${formatToolOutputForSidebar(params.output)}`); + } else { + sections.push("*No output - tool completed successfully.*"); + } + return sections.join("\n\n"); +} + /** * Get a truncated preview of tool output text. * Truncates to first N lines or first N characters, whichever is shorter. From a7eb6c58ab1d6a65607f57a870c44c601b24953c Mon Sep 17 00:00:00 2001 From: ChroniCat Date: Wed, 18 Mar 2026 14:51:57 +0800 Subject: [PATCH 2/2] fix(control-ui): treat null tool args as absent --- ui/src/ui/chat/tool-helpers.test.ts | 4 ++++ ui/src/ui/chat/tool-helpers.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/src/ui/chat/tool-helpers.test.ts b/ui/src/ui/chat/tool-helpers.test.ts index 7965a7df93e..18fdc0d72a4 100644 --- a/ui/src/ui/chat/tool-helpers.test.ts +++ b/ui/src/ui/chat/tool-helpers.test.ts @@ -159,6 +159,10 @@ describe("tool-helpers", () => { it("returns null for undefined arguments", () => { expect(formatToolPayloadForSidebar(undefined)).toBeNull(); }); + + it("returns null for null arguments", () => { + expect(formatToolPayloadForSidebar(null)).toBeNull(); + }); }); describe("buildToolSidebarContent", () => { diff --git a/ui/src/ui/chat/tool-helpers.ts b/ui/src/ui/chat/tool-helpers.ts index d95a695b2a9..0ceb6351c6b 100644 --- a/ui/src/ui/chat/tool-helpers.ts +++ b/ui/src/ui/chat/tool-helpers.ts @@ -27,7 +27,7 @@ export function formatToolOutputForSidebar(text: string): string { * Uses JSON code blocks for structured values and preserves plain text input. */ export function formatToolPayloadForSidebar(value: unknown): string | null { - if (value === undefined) { + if (value === undefined || value === null) { return null; } if (typeof value === "string") {