import assert from "node:assert/strict"; import test from "node:test"; import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createPowerShellToolDefinition, createReadToolDefinition, createWriteToolDefinition, type ToolDefinition, type ToolRenderers, } from "@selesai/code"; import { createToolRendererResolver, registerToolDisplayOverrides, } from "../src/tool-overrides.ts"; import { BUILT_IN_TOOL_OVERRIDE_NAMES, DEFAULT_TOOL_DISPLAY_CONFIG, type ToolDisplayConfig, type ToolOverrideOwnership, } from "../src/types.ts"; import { createExtensionApiHarness, createTestTheme, renderComponentText, resolveToolRenderers, runEvent, } from "./tool-renderer-harness.ts"; function buildConfig( overrides: Partial = {}, ownership: Partial = {}, ): ToolDisplayConfig { return { ...DEFAULT_TOOL_DISPLAY_CONFIG, // Most resolver assertions inspect the tool component itself; boxing is // covered explicitly by the boxed-shell test. boxedToolCalls: false, ...overrides, registerToolOverrides: { ...DEFAULT_TOOL_DISPLAY_CONFIG.registerToolOverrides, ...ownership, }, }; } function createHostDefinitions(cwd: string): Map { const definitions = [ createReadToolDefinition(cwd), createGrepToolDefinition(cwd), createFindToolDefinition(cwd), createLsToolDefinition(cwd), createBashToolDefinition(cwd), createPowerShellToolDefinition(cwd), createEditToolDefinition(cwd), createWriteToolDefinition(cwd), ]; return new Map(definitions.map((definition) => [definition.name, definition])); } const sentinel = { renderShell: "default" } as ToolRenderers; test("resolver renders every built-in and never re-registers the host definition", () => { const definitions = createHostDefinitions(process.cwd()); const resolver = createToolRendererResolver({ getConfig: () => buildConfig(), writeExecutionMetaByToolCallId: new Map(), }); for (const toolName of BUILT_IN_TOOL_OVERRIDE_NAMES) { const base = definitions.get(toolName); assert.ok(base, `missing host definition for ${toolName}`); const renderers = resolver(toolName, () => base); assert.ok(renderers, `missing renderers for ${toolName}`); assert.notEqual(renderers, base); assert.equal(renderers?.renderShell, "default"); assert.equal(typeof renderers?.renderCall, "function"); assert.equal(typeof renderers?.renderResult, "function"); // Renderers only: the host keeps the definition (and its execution path). assert.equal(definitions.get(toolName), base); assert.equal("execute" in (renderers as object), false); } }); test("resolver falls through to next() for non-built-ins, disabled config, and disabled ownership", () => { const config = buildConfig({ enabled: false }); const resolver = createToolRendererResolver({ getConfig: () => config, writeExecutionMetaByToolCallId: new Map(), }); assert.equal(resolver("read", () => sentinel), sentinel); assert.equal(resolver("mcp__docs__search", () => sentinel), sentinel); assert.equal(resolver("codemode", () => sentinel), sentinel); config.enabled = true; config.registerToolOverrides.read = false; assert.equal(resolver("read", () => sentinel), sentinel); assert.ok(resolver("grep", () => sentinel)); assert.ok(resolver("mcp__docs__search", () => sentinel)); }); test("resolver reads the current config on every resolve", () => { let config = buildConfig({}, { read: false }); const resolver = createToolRendererResolver({ getConfig: () => config, writeExecutionMetaByToolCallId: new Map(), }); assert.equal(resolver("read", () => sentinel), sentinel); config = buildConfig({ boxedToolCalls: false }, { read: true }); assert.ok(resolver("read", () => sentinel)); config = buildConfig({ boxedToolCalls: true }, { read: true }); assert.equal(resolver("read", () => sentinel)?.renderShell, "self"); }); test("boxed shell is self-rendered with the caller's outputPad; unboxed keeps the host shell", () => { const boxed = createToolRendererResolver({ getConfig: () => buildConfig({ boxedToolCalls: true, readOutputMode: "summary" }), writeExecutionMetaByToolCallId: new Map(), })("read", () => undefined); assert.equal(boxed?.renderShell, "self"); const boxedText = renderComponentText( boxed?.renderCall({ path: "src/a.ts" }, createTestTheme() as never, { args: { path: "src/a.ts" }, outputPad: 3, cwd: process.cwd(), } as never), ); const boxedLines = boxedText.split("\n"); assert.match(boxedLines[0], /^╭/); assert.ok( boxedLines.some((line) => /^│ {3}read src\/a\.ts/.test(line)), `expected three-space output padding, got:\n${boxedText}`, ); const unboxed = createToolRendererResolver({ getConfig: () => buildConfig({ boxedToolCalls: false, readOutputMode: "summary" }), writeExecutionMetaByToolCallId: new Map(), })("read", () => undefined); assert.equal(unboxed?.renderShell, "default"); const unboxedText = renderComponentText( unboxed?.renderCall({ path: "src/a.ts" }, createTestTheme() as never, { args: { path: "src/a.ts" }, outputPad: 3, cwd: process.cwd(), } as never), ); assert.doesNotMatch(unboxedText, /╭/); assert.match(unboxedText, /^read src\/a\.ts/m); }); test("read, grep, find, and ls render their call lines and result summaries", () => { const resolver = createToolRendererResolver({ getConfig: () => buildConfig({ readOutputMode: "summary", searchOutputMode: "count", bashOutputMode: "summary" }), writeExecutionMetaByToolCallId: new Map(), }); const renderCall = (toolName: string, args: Record): string => renderComponentText( resolver(toolName, () => undefined)?.renderCall(args, createTestTheme() as never, { args } as never), ); const renderResult = ( toolName: string, result: unknown, options: Record = {}, ): string => renderComponentText( resolver(toolName, () => undefined)?.renderResult(result as never, { expanded: false, isPartial: false, ...options, } as never, createTestTheme() as never, { args: {} } as never), ); assert.equal(renderCall("read", { path: "src/a.ts", offset: 2, limit: 3 }), "read src/a.ts:2-4"); assert.match( renderResult("read", { content: [{ type: "text", text: "one\ntwo\nthree" }], details: { truncation: { truncated: false, totalLines: 3 } }, }), /loaded 3 lines/, ); assert.equal(renderCall("grep", { pattern: "foo", path: "src" }), "grep /foo/ in src"); assert.match( renderResult("grep", { content: [{ type: "text", text: "src/a.ts:1: foo" }], details: { matchLimitReached: false, matches: 1 }, }), /1 match returned/, ); assert.equal(renderCall("find", { pattern: "*.ts", path: "src" }), "find *.ts in src"); assert.match( renderResult("find", { content: [{ type: "text", text: "src/a.ts" }], details: { resultLimitReached: false }, }), /1 result returned/, ); assert.equal(renderCall("ls", { path: "src" }), "ls src"); assert.match( renderResult("ls", { content: [{ type: "text", text: "a.ts\nb.ts" }], details: { entryLimitReached: false }, }), /2 entries returned/, ); }); test("powershell renders exactly like bash", () => { const resolver = createToolRendererResolver({ getConfig: () => buildConfig({ bashOutputMode: "preview", previewLines: 5 }), writeExecutionMetaByToolCallId: new Map(), }); const bash = resolver("bash", () => undefined); const powershell = resolver("powershell", () => undefined); assert.ok(bash && powershell); const args = { command: "echo hi" }; const theme = createTestTheme() as never; assert.equal( renderComponentText(bash.renderCall(args, theme, { args } as never)), renderComponentText(powershell.renderCall(args, theme, { args } as never)), ); const result = { content: [{ type: "text", text: "hi" }], details: { truncation: { truncated: false } } }; assert.equal( renderComponentText(bash.renderResult(result as never, { expanded: false, isPartial: false } as never, theme, { args } as never)), renderComponentText(powershell.renderResult(result as never, { expanded: false, isPartial: false } as never, theme, { args } as never)), ); assert.match(renderComponentText(bash.renderCall(args, theme, { args } as never)), /^\$ echo hi/); }); test("session definitions stay the host's own across extension load and lifecycle events", async () => { const definitions = createHostDefinitions(process.cwd()); const harness = createExtensionApiHarness(); // A session-owned registry: only registerTool would write to it. const sessionDefinitions = new Map(definitions); const pi = harness.api as unknown as { registerTool(tool: ToolDefinition): void; }; const originalRegisterTool = pi.registerTool.bind(harness.api); pi.registerTool = (tool: ToolDefinition): void => { sessionDefinitions.set(tool.name, tool); originalRegisterTool(tool); }; registerToolDisplayOverrides(harness.api, () => buildConfig()); await runEvent(harness, "session_start", {}, { ui: { notify: () => {} } }); await runEvent(harness, "before_agent_start", {}, { ui: { notify: () => {} } }); assert.equal(harness.registeredTools.length, 0); for (const toolName of BUILT_IN_TOOL_OVERRIDE_NAMES) { assert.equal(sessionDefinitions.get(toolName), definitions.get(toolName)); } assert.ok(resolveToolRenderers(harness.resolvers, "read", () => definitions.get("read"))); }); test("registerToolDisplayOverrides registers exactly one resolver per load and no tools", async () => { const harness = createExtensionApiHarness(); registerToolDisplayOverrides(harness.api, () => buildConfig()); assert.equal(harness.resolvers.length, 1); assert.equal(harness.registeredTools.length, 0); await runEvent(harness, "session_start", {}, { ui: { notify: () => {} } }); await runEvent(harness, "before_agent_start", {}, { ui: { notify: () => {} } }); assert.equal(harness.resolvers.length, 1); assert.equal(harness.registeredTools.length, 0); }); test("each load resolves through its own resolver chain", async () => { const first = createExtensionApiHarness(); const second = createExtensionApiHarness(); registerToolDisplayOverrides(first.api, () => buildConfig()); registerToolDisplayOverrides(second.api, () => buildConfig()); const firstRenderers = resolveToolRenderers(first.resolvers, "read"); const secondRenderers = resolveToolRenderers(second.resolvers, "read"); assert.ok(firstRenderers && secondRenderers); assert.notEqual(firstRenderers, secondRenderers); assert.ok(resolveToolRenderers(first.resolvers, "read", () => sentinel)); assert.equal(resolveToolRenderers([], "read", () => sentinel), sentinel); });