fix(coding-agent): fix grep/find/ls HTML export rendering (#3491)

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.
This commit is contained in:
Aliou Diallo
2026-04-21 13:06:49 +02:00
committed by GitHub
parent c6cef7c806
commit b2a1997ea0
2 changed files with 35 additions and 19 deletions

View File

@@ -173,8 +173,8 @@ function generateHtml(sessionData: SessionData, themeName?: string): string {
.replace("{{HIGHLIGHT_JS}}", hljsJs); .replace("{{HIGHLIGHT_JS}}", hljsJs);
} }
/** Built-in tool names that have custom rendering in template.js */ /** Tools rendered directly by the HTML template (not pre-rendered via TUI→ANSI→HTML pipeline) */
const BUILTIN_TOOLS = new Set(["bash", "read", "write", "edit", "ls", "find", "grep"]); const TEMPLATE_RENDERED_TOOLS = new Set(["bash", "read", "write", "edit", "ls"]);
/** /**
* Pre-render custom tools to HTML using their TUI renderers. * Pre-render custom tools to HTML using their TUI renderers.
@@ -192,7 +192,7 @@ function preRenderCustomTools(
// Find tool calls in assistant messages // Find tool calls in assistant messages
if (msg.role === "assistant" && Array.isArray(msg.content)) { if (msg.role === "assistant" && Array.isArray(msg.content)) {
for (const block of 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); const callHtml = toolRenderer.renderCall(block.id, block.name, block.arguments);
if (callHtml) { if (callHtml) {
renderedTools[block.id] = { callHtml }; renderedTools[block.id] = { callHtml };
@@ -204,9 +204,9 @@ function preRenderCustomTools(
// Find tool results // Find tool results
if (msg.role === "toolResult" && msg.toolCallId) { if (msg.role === "toolResult" && msg.toolCallId) {
const toolName = msg.toolName || ""; 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]; const existing = renderedTools[msg.toolCallId];
if (existing || !BUILTIN_TOOLS.has(toolName)) { if (existing || !TEMPLATE_RENDERED_TOOLS.has(toolName)) {
const rendered = toolRenderer.renderResult( const rendered = toolRenderer.renderResult(
msg.toolCallId, msg.toolCallId,
toolName, toolName,

View File

@@ -964,6 +964,22 @@
} }
break; break;
} }
case 'ls': {
const dirPath = str(args.path);
const limit = args.limit;
let pathHtml = dirPath === null ? invalidArg : escapeHtml(shortenPath(dirPath || '.'));
if (limit !== undefined) {
pathHtml += ` <span class="line-count">(limit ${escapeHtml(String(limit))})</span>`;
}
html += `<div class="tool-header"><span class="tool-name">ls</span> <span class="tool-path">${pathHtml}</span></div>`;
if (result) {
const output = getResultText().trim();
if (output) html += formatExpandableOutput(output, 20);
}
break;
}
default: { default: {
// Check for pre-rendered custom tool HTML // Check for pre-rendered custom tool HTML
const rendered = renderedTools?.[call.id]; const rendered = renderedTools?.[call.id];