import assert from "node:assert/strict"; import test from "node:test"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { DEFAULT_TOOL_DISPLAY_CONFIG } from "../src/types.ts"; import { clearWriteExecutionMeta, getWriteExecutionMeta, getWriteToolDisplayDetails, recordWriteExecutionMeta, registerToolDisplayOverrides, WRITE_EXECUTION_META_LIMIT, type WriteExecutionMeta, } from "../src/tool-overrides.ts"; import { createExtensionApiHarness, runEvent } from "./tool-renderer-harness.ts"; function setupExtensionWithConfig( getConfig: () => typeof DEFAULT_TOOL_DISPLAY_CONFIG, ): ReturnType { const harness = createExtensionApiHarness(); registerToolDisplayOverrides(harness.api, getConfig); return harness; } function setupExtension(_cwd: string) { return setupExtensionWithConfig(() => ({ ...DEFAULT_TOOL_DISPLAY_CONFIG, boxedToolCalls: false, })); } async function recordWrite( harness: ReturnType, cwd: string, toolCallId: string, input: Record, details?: unknown, ): Promise { await runEvent(harness, "tool_call", { toolName: "write", toolCallId, input }, { cwd }); const results = await runEvent( harness, "tool_result", { toolName: "write", toolCallId, input, details, isError: false }, { cwd }, ); const first = results[0] as { details?: unknown } | undefined; return first?.details; } test("write execution metadata moves pending entries into render state", () => { const pending = new Map(); const meta: WriteExecutionMeta = { fileExistedBeforeWrite: true, previousContent: "before\n", }; recordWriteExecutionMeta(pending, "write-1", meta); const state: Record = {}; const first = getWriteExecutionMeta( { toolCallId: "write-1", state }, pending, ); const second = getWriteExecutionMeta({ state }, pending); assert.deepEqual(first, meta); assert.equal(pending.size, 0); assert.deepEqual(second, meta); }); test("write execution metadata keeps stale pending entries bounded", () => { const pending = new Map(); const totalWrites = WRITE_EXECUTION_META_LIMIT + 7; for (let index = 0; index < totalWrites; index += 1) { recordWriteExecutionMeta(pending, `write-${index}`, { fileExistedBeforeWrite: true, previousContent: `before-${index}`, }); } const evictedCount = totalWrites - WRITE_EXECUTION_META_LIMIT; assert.equal(pending.size, WRITE_EXECUTION_META_LIMIT); assert.equal(pending.has(`write-${evictedCount - 1}`), false); assert.equal(pending.has(`write-${evictedCount}`), true); assert.deepEqual(pending.get(`write-${totalWrites - 1}`), { fileExistedBeforeWrite: true, previousContent: `before-${totalWrites - 1}`, }); }); test("write execution metadata cleanup clears stale pending entries", () => { const pending = new Map(); recordWriteExecutionMeta(pending, "write-1", { fileExistedBeforeWrite: false, }); recordWriteExecutionMeta(pending, "write-2", { fileExistedBeforeWrite: true, previousContent: "before\n", }); clearWriteExecutionMeta(pending); assert.equal(pending.size, 0); }); test("tool_call records the previous write content and tool_result persists a bounded record", async () => { const cwd = mkdtempSync(join(tmpdir(), "pi-tool-display-write-record-")); try { writeFileSync(join(cwd, "demo.txt"), "before\n"); const harness = setupExtension(cwd); const details = await recordWrite(harness, cwd, "w1", { path: "demo.txt", content: "after\n", }); const record = getWriteToolDisplayDetails(details); assert.deepEqual(record, { fileExistedBeforeWrite: true, previousContent: "before\n", }); const merged = details as Record; assert.deepEqual(merged.toolDisplay, record); assert.equal(getWriteToolDisplayDetails({ toolDisplay: { fileExistedBeforeWrite: "yes" } }), undefined); } finally { rmSync(cwd, { recursive: true, force: true }); } }); test("tool_result keeps other details fields and ignores non-write or unknown tool calls", async () => { const cwd = mkdtempSync(join(tmpdir(), "pi-tool-display-write-merge-")); try { writeFileSync(join(cwd, "demo.txt"), "before\n"); const harness = setupExtension(cwd); const details = (await recordWrite( harness, cwd, "w1", { path: "demo.txt", content: "after\n" }, { existing: "kept", diff: "diff-text" }, )) as Record; assert.equal(details.existing, "kept"); assert.equal(details.diff, "diff-text"); assert.deepEqual(getWriteToolDisplayDetails(details), { fileExistedBeforeWrite: true, previousContent: "before\n", }); const ignored = await runEvent( harness, "tool_result", { toolName: "read", toolCallId: "w1", input: {}, details: undefined, isError: false }, { cwd }, ); assert.equal(ignored[0], undefined); const unknown = await runEvent( harness, "tool_result", { toolName: "write", toolCallId: "never-seen", input: {}, details: undefined, isError: false }, { cwd }, ); assert.equal(unknown[0], undefined); } finally { rmSync(cwd, { recursive: true, force: true }); } }); test("write capture and persistence are gated by the extension and write ownership", async () => { const cwd = mkdtempSync(join(tmpdir(), "pi-tool-display-write-disabled-")); try { writeFileSync(join(cwd, "demo.txt"), "before\n"); const config = { ...DEFAULT_TOOL_DISPLAY_CONFIG, boxedToolCalls: false, }; const harness = setupExtensionWithConfig(() => config); // Extension disabled: no capture and no persisted record. config.enabled = false; await runEvent(harness, "tool_call", { toolName: "write", toolCallId: "off", input: { path: "demo.txt", content: "x\n" } }, { cwd }); const disabledResult = await runEvent( harness, "tool_result", { toolName: "write", toolCallId: "off", input: { path: "demo.txt", content: "x\n" }, details: undefined, isError: false }, { cwd }, ); assert.equal(disabledResult[0], undefined); // Write ownership disabled while the extension is enabled. config.enabled = true; config.registerToolOverrides = { ...config.registerToolOverrides, write: false }; await runEvent(harness, "tool_call", { toolName: "write", toolCallId: "unowned", input: { path: "demo.txt", content: "y\n" } }, { cwd }); const unownedResult = await runEvent( harness, "tool_result", { toolName: "write", toolCallId: "unowned", input: { path: "demo.txt", content: "y\n" }, details: undefined, isError: false }, { cwd }, ); assert.equal(unownedResult[0], undefined); // Enabled at tool_call, disabled before tool_result: the pending record is dropped // and stays dropped when the config is enabled again. config.registerToolOverrides = { ...config.registerToolOverrides, write: true }; await runEvent(harness, "tool_call", { toolName: "write", toolCallId: "between", input: { path: "demo.txt", content: "z\n" } }, { cwd }); config.enabled = false; const betweenResult = await runEvent( harness, "tool_result", { toolName: "write", toolCallId: "between", input: { path: "demo.txt", content: "z\n" }, details: undefined, isError: false }, { cwd }, ); assert.equal(betweenResult[0], undefined); config.enabled = true; const afterReenable = await runEvent( harness, "tool_result", { toolName: "write", toolCallId: "between", input: { path: "demo.txt", content: "z\n" }, details: undefined, isError: false }, { cwd }, ); assert.equal(afterReenable[0], undefined); // Disabled at tool_call, enabled before tool_result: nothing was captured to persist. config.enabled = false; await runEvent(harness, "tool_call", { toolName: "write", toolCallId: "late", input: { path: "demo.txt", content: "w\n" } }, { cwd }); config.enabled = true; const lateResult = await runEvent( harness, "tool_result", { toolName: "write", toolCallId: "late", input: { path: "demo.txt", content: "w\n" }, details: undefined, isError: false }, { cwd }, ); assert.equal(lateResult[0], undefined); } finally { rmSync(cwd, { recursive: true, force: true }); } }); test("a new write to a missing file records fileExistedBeforeWrite false", async () => { const cwd = mkdtempSync(join(tmpdir(), "pi-tool-display-write-new-")); try { const harness = setupExtension(cwd); const details = await recordWrite(harness, cwd, "w1", { path: "new.txt", content: "fresh\n", }); assert.deepEqual(getWriteToolDisplayDetails(details), { fileExistedBeforeWrite: false }); } finally { rmSync(cwd, { recursive: true, force: true }); } });