From b2a1997ea0a378e6524fdadce4992f2f69a085cc Mon Sep 17 00:00:00 2001 From: Aliou Diallo Date: Tue, 21 Apr 2026 13:06:49 +0200 Subject: [PATCH] fix(coding-agent): fix grep/find/ls HTML export rendering (#3491) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename BUILTIN_TOOLS to TEMPLATE_RENDERED_TOOLS to match what the set actually represents: tools rendered directly by the HTML template instead of pre-rendered through the TUI→ANSI→HTML pipeline. Move grep and find off the template-rendered exclusion path so they use their existing TUI renderCall/renderResult output in HTML export. Add a dedicated HTML renderer for ls and keep it in the template-rendered set. Unlike grep and find, rendering ls through the terminal-oriented Text component introduced spacing artifacts in exported HTML due to full-width line padding, so ls is rendered natively in the export template instead. --- .../src/core/export-html/index.ts | 10 ++--- .../src/core/export-html/template.js | 44 +++++++++++++------ 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/packages/coding-agent/src/core/export-html/index.ts b/packages/coding-agent/src/core/export-html/index.ts index c7b86e60..8024eb39 100644 --- a/packages/coding-agent/src/core/export-html/index.ts +++ b/packages/coding-agent/src/core/export-html/index.ts @@ -173,8 +173,8 @@ function generateHtml(sessionData: SessionData, themeName?: string): string { .replace("{{HIGHLIGHT_JS}}", hljsJs); } -/** Built-in tool names that have custom rendering in template.js */ -const BUILTIN_TOOLS = new Set(["bash", "read", "write", "edit", "ls", "find", "grep"]); +/** Tools rendered directly by the HTML template (not pre-rendered via TUI→ANSI→HTML pipeline) */ +const TEMPLATE_RENDERED_TOOLS = new Set(["bash", "read", "write", "edit", "ls"]); /** * Pre-render custom tools to HTML using their TUI renderers. @@ -192,7 +192,7 @@ function preRenderCustomTools( // Find tool calls in assistant messages if (msg.role === "assistant" && Array.isArray(msg.content)) { for (const block of msg.content) { - if (block.type === "toolCall" && !BUILTIN_TOOLS.has(block.name)) { + if (block.type === "toolCall" && !TEMPLATE_RENDERED_TOOLS.has(block.name)) { const callHtml = toolRenderer.renderCall(block.id, block.name, block.arguments); if (callHtml) { renderedTools[block.id] = { callHtml }; @@ -204,9 +204,9 @@ function preRenderCustomTools( // Find tool results if (msg.role === "toolResult" && msg.toolCallId) { const toolName = msg.toolName || ""; - // Only render if we have a pre-rendered call OR it's not a built-in tool + // Only render if we have a pre-rendered call OR it's not template-rendered const existing = renderedTools[msg.toolCallId]; - if (existing || !BUILTIN_TOOLS.has(toolName)) { + if (existing || !TEMPLATE_RENDERED_TOOLS.has(toolName)) { const rendered = toolRenderer.renderResult( msg.toolCallId, toolName, diff --git a/packages/coding-agent/src/core/export-html/template.js b/packages/coding-agent/src/core/export-html/template.js index f03aecc3..ff9e4641 100644 --- a/packages/coding-agent/src/core/export-html/template.js +++ b/packages/coding-agent/src/core/export-html/template.js @@ -76,8 +76,8 @@ // Create nodes for (const entry of entries) { - nodeMap.set(entry.id, { - entry, + nodeMap.set(entry.id, { + entry, children: [], label: labelMap.get(entry.id) }); @@ -200,7 +200,7 @@ const stack = []; // Add roots (prioritize branch containing active leaf) - const orderedRoots = [...roots].sort((a, b) => + const orderedRoots = [...roots].sort((a, b) => Number(containsActive.get(b)) - Number(containsActive.get(a)) ); for (let i = orderedRoots.length - 1; i >= 0; i--) { @@ -217,7 +217,7 @@ const multipleChildren = children.length > 1; // Order children (active branch first) - const orderedChildren = [...children].sort((a, b) => + const orderedChildren = [...children].sort((a, b) => Number(containsActive.get(b)) - Number(containsActive.get(a)) ); @@ -880,8 +880,8 @@ const renderResultImages = () => { const images = getResultImages(); if (images.length === 0) return ''; - return '
' + - images.map(img => ``).join('') + + return '
' + + images.map(img => ``).join('') + '
'; }; @@ -964,6 +964,22 @@ } break; } + case 'ls': { + const dirPath = str(args.path); + const limit = args.limit; + + let pathHtml = dirPath === null ? invalidArg : escapeHtml(shortenPath(dirPath || '.')); + if (limit !== undefined) { + pathHtml += ` (limit ${escapeHtml(String(limit))})`; + } + + html += `
ls ${pathHtml}
`; + if (result) { + const output = getResultText().trim(); + if (output) html += formatExpandableOutput(output, 20); + } + break; + } default: { // Check for pre-rendered custom tool HTML const rendered = renderedTools?.[call.id]; @@ -974,7 +990,7 @@ } else { html += `
${escapeHtml(name)}
`; } - + if (rendered.resultHtmlCollapsed && rendered.resultHtmlExpanded && rendered.resultHtmlCollapsed !== rendered.resultHtmlExpanded) { // Both collapsed and expanded differ - render expandable section html += `