From e3eeb1bafde2edd643238abf89237361b88930eb Mon Sep 17 00:00:00 2001 From: can1357 Date: Fri, 23 Jan 2026 13:46:01 +0100 Subject: [PATCH] refactor(coding-agent): restructured tool rendering to merge call and result displays - Changed tool result rendering to merge call and result displays with tool arguments in headers across 13 tools. - Added args parameter to render methods enabling display of contextual metadata like patterns, paths, and commands. - Changed Read tool title to display line range when offset or limit arguments provided. - Changed worker instantiation to use direct URL import instead of pre-bundled worker files. - Removed streaming preview rendering logic from write tool. --- packages/coding-agent/CHANGELOG.md | 3 ++ packages/coding-agent/src/lsp/render.ts | 11 +++- packages/coding-agent/src/task/executor.ts | 19 +------ packages/coding-agent/src/tools/bash.ts | 52 +++++++++++-------- packages/coding-agent/src/tools/calculator.ts | 7 ++- packages/coding-agent/src/tools/fetch.ts | 1 + packages/coding-agent/src/tools/find.ts | 14 ++++- packages/coding-agent/src/tools/grep.ts | 11 +++- packages/coding-agent/src/tools/ls.ts | 7 ++- packages/coding-agent/src/tools/notebook.ts | 5 +- packages/coding-agent/src/tools/read.ts | 8 ++- packages/coding-agent/src/tools/ssh.ts | 9 +++- packages/coding-agent/src/tools/todo-write.ts | 1 + packages/coding-agent/src/tools/write.ts | 33 +----------- .../coding-agent/src/web/search/render.ts | 7 ++- 15 files changed, 105 insertions(+), 83 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 86c62ad99..487254059 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -29,6 +29,9 @@ - Changed Notebook and TodoWrite tools to use new TUI rendering components for consistent output format - Refactored render-utils to move tree-related utilities to TUI module (getTreeBranch, getTreeContinuePrefix) - Changed import organization in sdk.ts for consistency +- Changed tool result rendering to merge call and result displays, showing tool arguments (command, pattern, query, path) in result headers for Bash, Calculator, Fetch, Find, Grep, Ls, LSP, Notebook, Read, SSH, TodoWrite, Web Search, and Write tools +- Changed Read tool title to display line range when offset or limit arguments are provided +- Changed worker instantiation to use direct URL import instead of pre-bundled worker files ## [8.0.0] - 2026-01-23 ### Added diff --git a/packages/coding-agent/src/lsp/render.ts b/packages/coding-agent/src/lsp/render.ts index e6ec462fa..70eb2c6a2 100644 --- a/packages/coding-agent/src/lsp/render.ts +++ b/packages/coding-agent/src/lsp/render.ts @@ -48,6 +48,7 @@ export function renderResult( result: AgentToolResult, options: RenderResultOptions, theme: Theme, + args?: LspParams & { file?: string; files?: string[] }, ): Component { const content = result.content?.[0]; if (!content || content.type !== "text" || !("text" in content) || !content.text) { @@ -93,7 +94,14 @@ export function renderResult( } } - const header = renderStatusLine({ icon: state, title: "LSP", description: label }, theme); + const meta: string[] = []; + if (args?.action) meta.push(args.action); + if (args?.file) { + meta.push(args.file); + } else if (args?.files?.length) { + meta.push(`${args.files.length} file(s)`); + } + const header = renderStatusLine({ icon: state, title: "LSP", description: label, meta }, theme); return { render: (width: number) => renderOutputBlock( @@ -592,4 +600,5 @@ function severityToColor(severity: string): "error" | "warning" | "accent" | "di export const lspToolRenderer = { renderCall, renderResult, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/task/executor.ts b/packages/coding-agent/src/task/executor.ts index 11ee77526..0771def99 100644 --- a/packages/coding-agent/src/task/executor.ts +++ b/packages/coding-agent/src/task/executor.ts @@ -4,7 +4,6 @@ * Runs each subagent in a Bun Worker and forwards AgentEvents for progress tracking. */ -import { homedir } from "node:os"; import path from "node:path"; import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import type { ModelRegistry } from "$c/config/model-registry"; @@ -39,21 +38,6 @@ import type { SubagentWorkerResponse, } from "./worker-protocol"; -const workerBundlePromise: Promise = (async () => { - const workerSrc = path.join(import.meta.dir, "worker.ts"); - const bundlePath = path.join(homedir(), ".omp", "worker-bundle.js"); - const result = await Bun.build({ - entrypoints: [workerSrc], - target: "bun", - format: "esm", - }); - if (!result.success) { - throw new Error(`Failed to bundle worker: ${result.logs.map((l) => l.message).join("\n")}`); - } - await Bun.write(bundlePath, await result.outputs[0].text()); - return bundlePath; -})(); - /** Options for worker execution */ export interface ExecutorOptions { cwd: string; @@ -327,8 +311,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise { const outputLines: string[] = []; + if (cmdText) { + outputLines.push(uiTheme.fg("dim", cmdText)); + } if (displayOutput) { if (expanded) { outputLines.push(...displayOutput.split("\n").map((line) => uiTheme.fg("toolOutput", line))); @@ -246,7 +255,7 @@ export const bashToolRenderer = { .split("\n") .map((line) => uiTheme.fg("toolOutput", line)) .join("\n"); - const textContent = `\n${styledOutput}`; + const textContent = styledOutput; const result = truncateToVisualLines(textContent, previewLines, width); if (result.skippedCount > 0) { outputLines.push( @@ -275,4 +284,5 @@ export const bashToolRenderer = { invalidate: () => {}, }; }, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/tools/calculator.ts b/packages/coding-agent/src/tools/calculator.ts index 1bdbab937..548c52fe2 100644 --- a/packages/coding-agent/src/tools/calculator.ts +++ b/packages/coding-agent/src/tools/calculator.ts @@ -461,6 +461,7 @@ export const calculatorToolRenderer = { result: { content: Array<{ type: string; text?: string }>; details?: CalculatorToolDetails }, { expanded }: RenderResultOptions, uiTheme: Theme, + args?: CalculatorRenderArgs, ): Component { const details = result.details; const textContent = result.content?.find((c) => c.type === "text")?.text ?? ""; @@ -475,8 +476,11 @@ export const calculatorToolRenderer = { return new Text(formatEmptyMessage("No results", uiTheme), 0, 0); } + const description = args?.calculations?.[0]?.expression + ? truncate(args.calculations[0].expression, TRUNCATE_LENGTHS.TITLE, "...") + : undefined; const header = renderStatusLine( - { icon: "success", title: "Calc", meta: [formatCount("result", outputs.length)] }, + { icon: "success", title: "Calc", description, meta: [formatCount("result", outputs.length)] }, uiTheme, ); const lines = renderTreeList( @@ -492,4 +496,5 @@ export const calculatorToolRenderer = { return new Text([header, ...lines].join("\n"), 0, 0); }, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/tools/fetch.ts b/packages/coding-agent/src/tools/fetch.ts index 1bf0ca5a9..e5108b42b 100644 --- a/packages/coding-agent/src/tools/fetch.ts +++ b/packages/coding-agent/src/tools/fetch.ts @@ -1095,4 +1095,5 @@ export function renderFetchResult( export const fetchToolRenderer = { renderCall: renderFetchCall, renderResult: renderFetchResult, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/tools/find.ts b/packages/coding-agent/src/tools/find.ts index 66ee93be4..9ddbaa918 100644 --- a/packages/coding-agent/src/tools/find.ts +++ b/packages/coding-agent/src/tools/find.ts @@ -390,6 +390,7 @@ export const findToolRenderer = { result: { content: Array<{ type: string; text?: string }>; details?: FindToolDetails; isError?: boolean }, { expanded }: RenderResultOptions, uiTheme: Theme, + args?: FindRenderArgs, ): Component { const details = result.details; @@ -408,7 +409,12 @@ export const findToolRenderer = { const lines = textContent.split("\n").filter((l) => l.trim()); const header = renderStatusLine( - { icon: "success", title: "Find", meta: [formatCount("file", lines.length)] }, + { + icon: "success", + title: "Find", + description: args?.pattern, + meta: [formatCount("file", lines.length)], + }, uiTheme, ); const listLines = renderTreeList( @@ -438,7 +444,10 @@ export const findToolRenderer = { const meta: string[] = [formatCount("file", fileCount)]; if (details?.scopePath) meta.push(`in ${details.scopePath}`); if (truncated) meta.push(uiTheme.fg("warning", "truncated")); - const header = renderStatusLine({ icon: truncated ? "warning" : "success", title: "Find", meta }, uiTheme); + const header = renderStatusLine( + { icon: truncated ? "warning" : "success", title: "Find", description: args?.pattern, meta }, + uiTheme, + ); const fileLines = renderFileList( { @@ -461,4 +470,5 @@ export const findToolRenderer = { return new Text([header, ...fileLines, ...extraLines].join("\n"), 0, 0); }, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/tools/grep.ts b/packages/coding-agent/src/tools/grep.ts index 57e53576e..3623976e5 100644 --- a/packages/coding-agent/src/tools/grep.ts +++ b/packages/coding-agent/src/tools/grep.ts @@ -627,6 +627,7 @@ export const grepToolRenderer = { result: { content: Array<{ type: string; text?: string }>; details?: GrepToolDetails; isError?: boolean }, { expanded }: RenderResultOptions, uiTheme: Theme, + args?: GrepRenderArgs, ): Component { const details = result.details; @@ -643,8 +644,9 @@ export const grepToolRenderer = { return new Text(formatEmptyMessage("No matches found", uiTheme), 0, 0); } const lines = textContent.split("\n").filter((line) => line.trim() !== ""); + const description = args?.pattern ?? undefined; const header = renderStatusLine( - { icon: "success", title: "Grep", meta: [formatCount("item", lines.length)] }, + { icon: "success", title: "Grep", description, meta: [formatCount("item", lines.length)] }, uiTheme, ); const listLines = renderTreeList( @@ -686,7 +688,11 @@ export const grepToolRenderer = { const meta = [...summaryParts]; if (details?.scopePath) meta.push(`in ${details.scopePath}`); if (truncated) meta.push(uiTheme.fg("warning", "truncated")); - const header = renderStatusLine({ icon: truncated ? "warning" : "success", title: "Grep", meta }, uiTheme); + const description = args?.pattern ?? undefined; + const header = renderStatusLine( + { icon: truncated ? "warning" : "success", title: "Grep", description, meta }, + uiTheme, + ); const fileEntries: Array<{ path: string; count?: number }> = details?.fileMatches?.length ? details.fileMatches.map((entry) => ({ path: entry.path, count: entry.count })) @@ -717,4 +723,5 @@ export const grepToolRenderer = { return new Text([header, ...fileLines, ...extraLines].join("\n"), 0, 0); }, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/tools/ls.ts b/packages/coding-agent/src/tools/ls.ts index 5138b12ae..978837fa2 100644 --- a/packages/coding-agent/src/tools/ls.ts +++ b/packages/coding-agent/src/tools/ls.ts @@ -221,6 +221,7 @@ export const lsToolRenderer = { result: { content: Array<{ type: string; text?: string }>; details?: LsToolDetails; isError?: boolean }, { expanded }: RenderResultOptions, uiTheme: Theme, + args?: LsRenderArgs, ): Component { const details = result.details; const textContent = result.content?.find((c) => c.type === "text")?.text ?? ""; @@ -269,7 +270,10 @@ export const lsToolRenderer = { uiTheme.sep.dot, ); const meta = truncated ? [summaryText, uiTheme.fg("warning", "truncated")] : [summaryText]; - const header = renderStatusLine({ icon: truncated ? "warning" : "success", title: "Ls", meta }, uiTheme); + const header = renderStatusLine( + { icon: truncated ? "warning" : "success", title: "Ls", description: args?.path || ".", meta }, + uiTheme, + ); const fileLines = renderFileList( { @@ -293,4 +297,5 @@ export const lsToolRenderer = { return new Text([header, ...fileLines, ...extraLines].join("\n"), 0, 0); }, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/tools/notebook.ts b/packages/coding-agent/src/tools/notebook.ts index b85889270..9a57e9e9a 100644 --- a/packages/coding-agent/src/tools/notebook.ts +++ b/packages/coding-agent/src/tools/notebook.ts @@ -220,6 +220,7 @@ export const notebookToolRenderer = { result: { content: Array<{ type: string; text?: string }>; details?: NotebookToolDetails }, { expanded }: RenderResultOptions, uiTheme: Theme, + args?: NotebookRenderArgs, ): Component { const content = result.content?.[0]; if (content?.type === "text" && content.text?.startsWith("Error:")) { @@ -244,13 +245,14 @@ export const notebookToolRenderer = { const codeText = cellSource.join(""); const language = cellType === "markdown" ? "markdown" : undefined; + const notebookLabel = args?.notebookPath ? `${actionLabel} ${args.notebookPath}` : "Notebook"; return { render: (width: number) => renderCodeCell( { code: codeText, language, - title: "Notebook", + title: notebookLabel, status: "complete", output: outputLines.join("\n"), codeMaxLines: expanded ? Number.POSITIVE_INFINITY : COLLAPSED_TEXT_LIMIT, @@ -262,4 +264,5 @@ export const notebookToolRenderer = { invalidate: () => {}, }; }, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/tools/read.ts b/packages/coding-agent/src/tools/read.ts index 377d41b5f..a56c8e9bb 100644 --- a/packages/coding-agent/src/tools/read.ts +++ b/packages/coding-agent/src/tools/read.ts @@ -880,7 +880,12 @@ export const readToolRenderer = { warningLines.push(uiTheme.fg("warning", wrapBrackets(warning, uiTheme))); } - const title = filePath ? `Read ${filePath}` : "Read"; + let title = filePath ? `Read ${filePath}` : "Read"; + if (args?.offset !== undefined || args?.limit !== undefined) { + const startLine = args.offset ?? 1; + const endLine = args.limit !== undefined ? startLine + args.limit - 1 : ""; + title += `:${startLine}${endLine ? `-${endLine}` : ""}`; + } return { render: (width: number) => renderCodeCell( @@ -898,4 +903,5 @@ export const readToolRenderer = { invalidate: () => {}, }; }, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/tools/ssh.ts b/packages/coding-agent/src/tools/ssh.ts index f5ba00939..a09ca3025 100644 --- a/packages/coding-agent/src/tools/ssh.ts +++ b/packages/coding-agent/src/tools/ssh.ts @@ -251,10 +251,16 @@ export const sshToolRenderer = { }, options: RenderResultOptions & { renderContext?: SshRenderContext }, uiTheme: Theme, + args?: SshRenderArgs, ): Component { const { expanded, renderContext } = options; const details = result.details; - const header = renderStatusLine({ icon: "success", title: "SSH" }, uiTheme); + const host = args?.host || uiTheme.format.ellipsis; + const command = args?.command || uiTheme.format.ellipsis; + const header = renderStatusLine( + { icon: "success", title: "SSH", description: `[${host}] $ ${command}` }, + uiTheme, + ); const outputLines: string[] = []; const textContent = result.content?.find((c) => c.type === "text")?.text ?? ""; @@ -318,4 +324,5 @@ export const sshToolRenderer = { invalidate: () => {}, }; }, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/tools/todo-write.ts b/packages/coding-agent/src/tools/todo-write.ts index 54508f4a1..2b2bd4f3d 100644 --- a/packages/coding-agent/src/tools/todo-write.ts +++ b/packages/coding-agent/src/tools/todo-write.ts @@ -244,4 +244,5 @@ export const todoWriteToolRenderer = { return new Text([header, ...lines].join("\n"), 0, 0); }, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/tools/write.ts b/packages/coding-agent/src/tools/write.ts index f5194a146..d9aa53c82 100644 --- a/packages/coding-agent/src/tools/write.ts +++ b/packages/coding-agent/src/tools/write.ts @@ -128,8 +128,6 @@ interface WriteRenderArgs { content?: string; } -const WRITE_STREAMING_PREVIEW_LINES = 12; - function countLines(text: string): number { if (!text) return 0; return text.split("\n").length; @@ -150,36 +148,6 @@ export const writeToolRenderer = { const pathDisplay = filePath || uiTheme.format.ellipsis; const status = options?.spinnerFrame !== undefined ? "running" : "pending"; - if (args.content) { - const contentLines = args.content.split("\n"); - const displayLines = contentLines.slice(-WRITE_STREAMING_PREVIEW_LINES); - const hidden = contentLines.length - displayLines.length; - const outputLines: string[] = []; - if (hidden > 0) { - outputLines.push(uiTheme.fg("dim", `${uiTheme.format.ellipsis} (${hidden} earlier lines)`)); - } - outputLines.push(uiTheme.fg("dim", `${uiTheme.format.ellipsis} (streaming)`)); - - return { - render: (width: number) => - renderCodeCell( - { - code: displayLines.join("\n"), - language: getLanguageFromPath(rawPath), - title: filePath ? `Write ${filePath}` : "Write", - status, - spinnerFrame: options?.spinnerFrame, - output: outputLines.join("\n"), - codeMaxLines: WRITE_STREAMING_PREVIEW_LINES, - expanded: true, - width, - }, - uiTheme, - ), - invalidate: () => {}, - }; - } - const text = renderStatusLine({ icon: status, title: "Write", description: pathDisplay }, uiTheme); return new Text(text, 0, 0); }, @@ -225,4 +193,5 @@ export const writeToolRenderer = { invalidate: () => {}, }; }, + mergeCallAndResult: true, }; diff --git a/packages/coding-agent/src/web/search/render.ts b/packages/coding-agent/src/web/search/render.ts index ecede5665..7f3c73596 100644 --- a/packages/coding-agent/src/web/search/render.ts +++ b/packages/coding-agent/src/web/search/render.ts @@ -71,6 +71,7 @@ export function renderWebSearchResult( result: { content: Array<{ type: string; text?: string }>; details?: WebSearchRenderDetails }, options: RenderResultOptions, theme: Theme, + args?: { query?: string; provider?: string }, ): Component { const { expanded } = options; const details = result.details; @@ -116,12 +117,13 @@ export function renderWebSearchResult( : provider === "exa" ? "Exa" : "Unknown"; + const queryPreview = args?.query ? truncate(args.query, 80, theme.format.ellipsis) : undefined; const header = renderStatusLine( { icon: sourceCount > 0 ? "success" : "warning", title: "Web Search", - description: providerLabel, - meta: [formatCount("source", sourceCount)], + description: queryPreview ?? providerLabel, + meta: queryPreview ? [providerLabel, formatCount("source", sourceCount)] : [formatCount("source", sourceCount)], }, theme, ); @@ -271,4 +273,5 @@ export function renderWebSearchCall( export const webSearchToolRenderer = { renderCall: renderWebSearchCall, renderResult: renderWebSearchResult, + mergeCallAndResult: true, };