@selesai/code 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/config.d.ts +5 -3
  3. package/dist/config.js +6 -4
  4. package/dist/core/skills.d.ts +2 -0
  5. package/dist/core/skills.js +1 -0
  6. package/dist/defaults/settings.json +0 -3
  7. package/dist/extensions/auto-session-name.test.ts +157 -0
  8. package/dist/extensions/auto-session-name.ts +96 -0
  9. package/dist/extensions/cost-reconcile.test.ts +76 -0
  10. package/dist/extensions/cost-reconcile.ts +278 -0
  11. package/dist/extensions/enable-readonly-tools.test.ts +11 -0
  12. package/dist/extensions/enable-readonly-tools.ts +7 -3
  13. package/dist/extensions/package.json +3 -0
  14. package/dist/extensions/pi-tool-display/CHANGELOG.md +329 -0
  15. package/dist/extensions/pi-tool-display/LICENSE +21 -0
  16. package/dist/extensions/pi-tool-display/README.md +417 -0
  17. package/dist/extensions/pi-tool-display/config/config.example.json +40 -0
  18. package/dist/extensions/pi-tool-display/index.ts +3 -0
  19. package/dist/extensions/pi-tool-display/package-lock.json +2555 -0
  20. package/dist/extensions/pi-tool-display/package.json +86 -0
  21. package/dist/extensions/pi-tool-display/src/agent-dir.ts +35 -0
  22. package/dist/extensions/pi-tool-display/src/ansi-utils.ts +116 -0
  23. package/dist/extensions/pi-tool-display/src/bash-display.ts +213 -0
  24. package/dist/extensions/pi-tool-display/src/capabilities.ts +88 -0
  25. package/dist/extensions/pi-tool-display/src/config-modal.ts +541 -0
  26. package/dist/extensions/pi-tool-display/src/config-store.ts +304 -0
  27. package/dist/extensions/pi-tool-display/src/debug-logger.ts +150 -0
  28. package/dist/extensions/pi-tool-display/src/diff-presentation.ts +72 -0
  29. package/dist/extensions/pi-tool-display/src/diff-renderer.ts +2517 -0
  30. package/dist/extensions/pi-tool-display/src/disposable.ts +34 -0
  31. package/dist/extensions/pi-tool-display/src/extension-lifecycle.ts +14 -0
  32. package/dist/extensions/pi-tool-display/src/index.ts +109 -0
  33. package/dist/extensions/pi-tool-display/src/line-width-safety.ts +93 -0
  34. package/dist/extensions/pi-tool-display/src/modal-icons.ts +57 -0
  35. package/dist/extensions/pi-tool-display/src/pending-diff-preview.ts +370 -0
  36. package/dist/extensions/pi-tool-display/src/presets.ts +117 -0
  37. package/dist/extensions/pi-tool-display/src/render-utils.ts +186 -0
  38. package/dist/extensions/pi-tool-display/src/settings-inspector-modal.ts +475 -0
  39. package/dist/extensions/pi-tool-display/src/thinking-label.ts +343 -0
  40. package/dist/extensions/pi-tool-display/src/tool-call-box.ts +181 -0
  41. package/dist/extensions/pi-tool-display/src/tool-metadata.ts +139 -0
  42. package/dist/extensions/pi-tool-display/src/tool-overrides.ts +2168 -0
  43. package/dist/extensions/pi-tool-display/src/types.ts +109 -0
  44. package/dist/extensions/pi-tool-display/src/user-message-box-markdown.ts +64 -0
  45. package/dist/extensions/pi-tool-display/src/user-message-box-native.ts +66 -0
  46. package/dist/extensions/pi-tool-display/src/user-message-box-patch.ts +72 -0
  47. package/dist/extensions/pi-tool-display/src/user-message-box-renderer.ts +419 -0
  48. package/dist/extensions/pi-tool-display/src/user-message-box-utils.ts +106 -0
  49. package/dist/extensions/pi-tool-display/src/write-display-utils.ts +31 -0
  50. package/dist/extensions/pi-tool-display/src/zellij-modal.ts +1027 -0
  51. package/dist/extensions/pi-tool-display/tests/active-backlog-red.test.ts +65 -0
  52. package/dist/extensions/pi-tool-display/tests/ansi-utils.test.ts +345 -0
  53. package/dist/extensions/pi-tool-display/tests/bash-display.test.ts +442 -0
  54. package/dist/extensions/pi-tool-display/tests/capabilities-edge.test.ts +360 -0
  55. package/dist/extensions/pi-tool-display/tests/capabilities.test.ts +54 -0
  56. package/dist/extensions/pi-tool-display/tests/config-modal.test.ts +360 -0
  57. package/dist/extensions/pi-tool-display/tests/config-store.test.ts +87 -0
  58. package/dist/extensions/pi-tool-display/tests/custom-tool-overrides.test.ts +415 -0
  59. package/dist/extensions/pi-tool-display/tests/debug-logger-edge.test.ts +630 -0
  60. package/dist/extensions/pi-tool-display/tests/debug-logger.test.ts +62 -0
  61. package/dist/extensions/pi-tool-display/tests/diff-renderer-ansi.test.ts +55 -0
  62. package/dist/extensions/pi-tool-display/tests/diff-renderer-edge.test.ts +886 -0
  63. package/dist/extensions/pi-tool-display/tests/diff-renderer-numbering.test.ts +135 -0
  64. package/dist/extensions/pi-tool-display/tests/diff-renderer-width.test.ts +454 -0
  65. package/dist/extensions/pi-tool-display/tests/diff-renderer-wrap.test.ts +169 -0
  66. package/dist/extensions/pi-tool-display/tests/index-integration.test.ts +354 -0
  67. package/dist/extensions/pi-tool-display/tests/pending-diff-preview.test.ts +79 -0
  68. package/dist/extensions/pi-tool-display/tests/presets-edge.test.ts +229 -0
  69. package/dist/extensions/pi-tool-display/tests/presets.test.ts +26 -0
  70. package/dist/extensions/pi-tool-display/tests/reload-behavior.test.ts +1241 -0
  71. package/dist/extensions/pi-tool-display/tests/render-utils.test.ts +710 -0
  72. package/dist/extensions/pi-tool-display/tests/settings-inspector.test.ts +450 -0
  73. package/dist/extensions/pi-tool-display/tests/thinking-label-edge.test.ts +904 -0
  74. package/dist/extensions/pi-tool-display/tests/thinking-label.test.ts +72 -0
  75. package/dist/extensions/pi-tool-display/tests/tool-call-box.test.ts +105 -0
  76. package/dist/extensions/pi-tool-display/tests/tool-overrides-config.test.ts +613 -0
  77. package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +499 -0
  78. package/dist/extensions/pi-tool-display/tests/tool-overrides-registration.test.ts +315 -0
  79. package/dist/extensions/pi-tool-display/tests/tool-overrides-write-meta.test.ts +68 -0
  80. package/dist/extensions/pi-tool-display/tests/tool-ui-utils.test.ts +413 -0
  81. package/dist/extensions/pi-tool-display/tests/user-message-box-performance-red.test.ts +176 -0
  82. package/dist/extensions/pi-tool-display/tests/user-message-box.test.ts +908 -0
  83. package/dist/extensions/pi-tool-display/tests/zellij-modal.test.ts +848 -0
  84. package/dist/extensions/pi-tool-display/tool-display-api-consumer.d.ts +30 -0
  85. package/dist/extensions/pi-tool-display/tool-display-api-consumer.js +40 -0
  86. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +46 -1
  87. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +10 -0
  88. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
  89. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +3 -3
  90. package/dist/extensions/pi-zentui/test/format.test.ts +50 -2
  91. package/dist/modes/interactive/components/startup-box.d.ts +38 -0
  92. package/dist/modes/interactive/components/startup-box.js +90 -0
  93. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  94. package/dist/modes/interactive/interactive-mode.js +85 -122
  95. package/dist/skills/agent-browser/SKILL.md +1 -0
  96. package/dist/skills/batch-grill-me/SKILL.md +1 -0
  97. package/dist/skills/brandkit/SKILL.md +1 -0
  98. package/dist/skills/design-references/SKILL.md +1 -0
  99. package/dist/skills/design-taste-frontend/SKILL.md +1 -0
  100. package/dist/skills/design-taste-frontend-v1/SKILL.md +1 -0
  101. package/dist/skills/full-output-enforcement/SKILL.md +1 -0
  102. package/dist/skills/gpt-taste/SKILL.md +1 -0
  103. package/dist/skills/grill-me/SKILL.md +1 -0
  104. package/dist/skills/handoff/SKILL.md +1 -0
  105. package/dist/skills/handoff-text/SKILL.md +1 -0
  106. package/dist/skills/high-end-visual-design/SKILL.md +1 -0
  107. package/dist/skills/image-to-code/SKILL.md +1 -0
  108. package/dist/skills/imagegen-frontend-mobile/SKILL.md +1 -0
  109. package/dist/skills/imagegen-frontend-web/SKILL.md +1 -0
  110. package/dist/skills/implanger/SKILL.md +1 -0
  111. package/dist/skills/improve-codebase/SKILL.md +1 -0
  112. package/dist/skills/industrial-brutalist-ui/SKILL.md +1 -0
  113. package/dist/skills/minimalist-ui/SKILL.md +1 -0
  114. package/dist/skills/pi-subagents/SKILL.md +1 -0
  115. package/dist/skills/planger/SKILL.md +1 -0
  116. package/dist/skills/ponytail/SKILL.md +1 -0
  117. package/dist/skills/ponytail-audit/SKILL.md +1 -0
  118. package/dist/skills/ponytail-debt/SKILL.md +1 -0
  119. package/dist/skills/ponytail-gain/SKILL.md +1 -0
  120. package/dist/skills/ponytail-help/SKILL.md +1 -0
  121. package/dist/skills/ponytail-review/SKILL.md +1 -0
  122. package/dist/skills/redesign-existing-projects/SKILL.md +1 -0
  123. package/dist/skills/selesai-handoff/SKILL.md +1 -0
  124. package/dist/skills/stitch-design-taste/SKILL.md +1 -0
  125. package/dist/skills/unlazy/SKILL.md +1 -0
  126. package/dist/skills/web-design-guidelines/SKILL.md +1 -0
  127. package/dist/skills/workflow/SKILL.md +1 -0
  128. package/package.json +3 -3
@@ -0,0 +1,315 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+ import {
7
+ createBashTool,
8
+ createEditTool,
9
+ createFindTool,
10
+ createGrepTool,
11
+ createLsTool,
12
+ createReadTool,
13
+ createWriteTool,
14
+ type ExtensionAPI,
15
+ } from "@earendil-works/pi-coding-agent";
16
+ import { registerToolDisplayOverrides } from "../src/tool-overrides.ts";
17
+ import { DEFAULT_TOOL_DISPLAY_CONFIG } from "../src/types.ts";
18
+
19
+ const TOOL_DISPLAY_PENDING_DECORATIONS_KEY = Symbol.for("pi-tool-display.pendingDecorations.v1");
20
+
21
+ interface RegisteredToolLike {
22
+ name: string;
23
+ description: string;
24
+ parameters: unknown;
25
+ renderShell?: "default" | "self";
26
+ promptSnippet?: string;
27
+ promptGuidelines?: string[];
28
+ renderCall?: (...args: unknown[]) => unknown;
29
+ renderResult?: (...args: unknown[]) => unknown;
30
+ }
31
+
32
+ interface ToolEventHandlers {
33
+ session_start?: () => Promise<void> | void;
34
+ before_agent_start?: () => Promise<void> | void;
35
+ }
36
+
37
+ interface ExecutableToolLike extends RegisteredToolLike {
38
+ execute: (...args: unknown[]) => Promise<{ content?: Array<{ type: string; text?: string }> }>;
39
+ }
40
+
41
+ async function withTempDir(name: string, run: (dir: string) => Promise<void> | void): Promise<void> {
42
+ const dir = mkdtempSync(join(tmpdir(), name));
43
+ try {
44
+ await run(dir);
45
+ } finally {
46
+ rmSync(dir, { recursive: true, force: true });
47
+ }
48
+ }
49
+
50
+ function getTextOutput(result: { content?: Array<{ type: string; text?: string }> }): string {
51
+ return (result.content ?? [])
52
+ .filter((entry) => entry.type === "text")
53
+ .map((entry) => entry.text ?? "")
54
+ .join("");
55
+ }
56
+
57
+ function withDefaultReadEditOwners(tools: unknown[] = []): unknown[] {
58
+ const names = new Set(
59
+ tools
60
+ .map((tool) => (tool as { name?: unknown }).name)
61
+ .filter((name): name is string => typeof name === "string"),
62
+ );
63
+ const defaults = ["read", "edit"]
64
+ .filter((name) => !names.has(name))
65
+ .map((name) => ({ name, sourceInfo: { source: "builtin", path: `<builtin:${name}>` } }));
66
+ return [...defaults, ...tools];
67
+ }
68
+
69
+ function createExtensionApiStub(allTools: unknown[] = []): {
70
+ api: ExtensionAPI;
71
+ registeredTools: RegisteredToolLike[];
72
+ eventHandlers: ToolEventHandlers;
73
+ } {
74
+ const registeredTools: RegisteredToolLike[] = [];
75
+ const eventHandlers: ToolEventHandlers = {};
76
+ const api = {
77
+ registerTool(tool: RegisteredToolLike): void {
78
+ registeredTools.push(tool);
79
+ },
80
+ on(event: keyof ToolEventHandlers, handler: () => Promise<void> | void): void {
81
+ eventHandlers[event] = handler;
82
+ },
83
+ getAllTools(): unknown[] {
84
+ return withDefaultReadEditOwners(allTools);
85
+ },
86
+ } as unknown as ExtensionAPI;
87
+
88
+ return { api, registeredTools, eventHandlers };
89
+ }
90
+
91
+ test("registerToolDisplayOverrides copies built-in prompt metadata onto overridden tools", async () => {
92
+ const { api, registeredTools, eventHandlers } = createExtensionApiStub();
93
+
94
+ registerToolDisplayOverrides(api, () => DEFAULT_TOOL_DISPLAY_CONFIG);
95
+ assert.deepEqual(
96
+ registeredTools.map((tool) => tool.name).sort(),
97
+ ["bash", "edit", "find", "grep", "ls", "read", "write"],
98
+ );
99
+ await eventHandlers.before_agent_start?.();
100
+
101
+ assert.equal(registeredTools.length, 7);
102
+
103
+ const byName = new Map(registeredTools.map((tool) => [tool.name, tool]));
104
+ const cwd = process.cwd();
105
+ const builtInTools = {
106
+ read: createReadTool(cwd),
107
+ grep: createGrepTool(cwd),
108
+ find: createFindTool(cwd),
109
+ ls: createLsTool(cwd),
110
+ bash: createBashTool(cwd),
111
+ edit: createEditTool(cwd),
112
+ write: createWriteTool(cwd),
113
+ };
114
+
115
+ for (const [name, builtInTool] of Object.entries(builtInTools)) {
116
+ const registeredTool = byName.get(name);
117
+ const builtInMetadata = builtInTool as unknown as RegisteredToolLike;
118
+ assert.ok(registeredTool, `expected '${name}' to be registered`);
119
+ assert.equal(registeredTool.promptSnippet, builtInMetadata.promptSnippet);
120
+ }
121
+
122
+ assert.deepEqual(byName.get("read")?.promptGuidelines, (builtInTools.read as unknown as RegisteredToolLike).promptGuidelines);
123
+ assert.deepEqual(byName.get("edit")?.promptGuidelines, (builtInTools.edit as unknown as RegisteredToolLike).promptGuidelines);
124
+ assert.deepEqual(byName.get("write")?.promptGuidelines, (builtInTools.write as unknown as RegisteredToolLike).promptGuidelines);
125
+ assert.equal(byName.get("grep")?.promptGuidelines, undefined);
126
+ assert.equal(byName.get("find")?.promptGuidelines, undefined);
127
+ assert.equal(byName.get("ls")?.promptGuidelines, undefined);
128
+ assert.equal(byName.get("bash")?.promptGuidelines, undefined);
129
+ });
130
+
131
+ test("registerToolDisplayOverrides registers built-in display renderers during extension load for pre-bind history rendering", () => {
132
+ const { api, registeredTools } = createExtensionApiStub();
133
+
134
+ registerToolDisplayOverrides(api, () => DEFAULT_TOOL_DISPLAY_CONFIG);
135
+
136
+ const byName = new Map(registeredTools.map((tool) => [tool.name, tool]));
137
+ for (const name of ["read", "grep", "find", "ls", "bash", "edit", "write"] as const) {
138
+ const registeredTool = byName.get(name);
139
+ assert.ok(registeredTool, `expected '${name}' to be available before session_start`);
140
+ assert.equal(typeof registeredTool.renderCall, "function", `${name} has renderCall before session_start`);
141
+ assert.equal(typeof registeredTool.renderResult, "function", `${name} has renderResult before session_start`);
142
+ }
143
+ });
144
+
145
+ test("registerToolDisplayOverrides clones built-in parameter schemas so Pi TUI keeps extension renderers active", async () => {
146
+ const { api, registeredTools, eventHandlers } = createExtensionApiStub();
147
+
148
+ registerToolDisplayOverrides(api, () => DEFAULT_TOOL_DISPLAY_CONFIG);
149
+ await eventHandlers.before_agent_start?.();
150
+
151
+ const byName = new Map(registeredTools.map((tool) => [tool.name, tool]));
152
+ const cwd = process.cwd();
153
+ const builtInTools = {
154
+ read: createReadTool(cwd),
155
+ grep: createGrepTool(cwd),
156
+ find: createFindTool(cwd),
157
+ ls: createLsTool(cwd),
158
+ bash: createBashTool(cwd),
159
+ edit: createEditTool(cwd),
160
+ write: createWriteTool(cwd),
161
+ };
162
+
163
+ for (const [name, builtInTool] of Object.entries(builtInTools)) {
164
+ const registeredTool = byName.get(name);
165
+ assert.ok(registeredTool, `expected '${name}' to be registered`);
166
+ assert.notEqual(
167
+ registeredTool.parameters,
168
+ builtInTool.parameters,
169
+ `expected '${name}' to use a cloned parameter object`,
170
+ );
171
+ assert.deepEqual(registeredTool.parameters, builtInTool.parameters);
172
+ }
173
+ });
174
+
175
+ test("registerToolDisplayOverrides renders tools self-framed via the upstream renderShell API", async () => {
176
+ const { api, registeredTools, eventHandlers } = createExtensionApiStub();
177
+
178
+ registerToolDisplayOverrides(api, () => DEFAULT_TOOL_DISPLAY_CONFIG);
179
+ await eventHandlers.before_agent_start?.();
180
+
181
+ const byName = new Map(registeredTools.map((tool) => [tool.name, tool]));
182
+ for (const name of ["read", "grep", "find", "ls", "bash", "edit", "write"] as const) {
183
+ assert.equal(byName.get(name)?.renderShell, "self", `${name} should render self-framed`);
184
+ }
185
+ });
186
+
187
+ test("registerToolDisplayOverrides leaves externally owned read/edit/grep tools active", async () => {
188
+ const { api, registeredTools, eventHandlers } = createExtensionApiStub([
189
+ { name: "read", sourceInfo: { source: "local", path: "agent/extensions/example-read/src/read.ts" } },
190
+ { name: "edit", sourceInfo: { source: "local", path: "agent/extensions/example-edit/src/edit.ts" } },
191
+ { name: "grep", sourceInfo: { source: "local", path: "agent/extensions/example-grep/src/grep.ts" } },
192
+ ]);
193
+
194
+ registerToolDisplayOverrides(api, () => DEFAULT_TOOL_DISPLAY_CONFIG);
195
+ await eventHandlers.before_agent_start?.();
196
+
197
+ const registeredNames = new Set(registeredTools.map((tool) => tool.name));
198
+ assert.equal(registeredNames.has("read"), false);
199
+ assert.equal(registeredNames.has("edit"), false);
200
+ assert.equal(registeredNames.has("grep"), false);
201
+ assert.equal(registeredNames.has("find"), true);
202
+ assert.equal(registeredNames.has("ls"), true);
203
+ assert.equal(registeredNames.has("bash"), true);
204
+ assert.equal(registeredNames.has("write"), true);
205
+ });
206
+
207
+ test("bash override uses shellPath from Pi settings", async () => {
208
+ await withTempDir("pi-tool-display-shellpath-", async (dir) => {
209
+ const previousAgentDir = process.env.PI_CODING_AGENT_DIR;
210
+ process.env.PI_CODING_AGENT_DIR = dir;
211
+ writeFileSync(
212
+ join(dir, "settings.json"),
213
+ JSON.stringify({ shellPath: "/definitely/missing/bash" }),
214
+ "utf8",
215
+ );
216
+
217
+ try {
218
+ const { api, registeredTools, eventHandlers } = createExtensionApiStub();
219
+ registerToolDisplayOverrides(api, () => DEFAULT_TOOL_DISPLAY_CONFIG);
220
+ await eventHandlers.before_agent_start?.();
221
+
222
+ const bashTool = registeredTools.find((tool) => tool.name === "bash") as ExecutableToolLike | undefined;
223
+ assert.ok(bashTool, "expected bash override to be registered");
224
+ await assert.rejects(
225
+ bashTool.execute("tool-call-1", { command: "printf test" }, undefined, undefined, { cwd: process.cwd() }),
226
+ /custom shell path not found/i,
227
+ );
228
+ assert.equal(bashTool.description.length > 0, true);
229
+ } finally {
230
+ if (previousAgentDir === undefined) {
231
+ delete process.env.PI_CODING_AGENT_DIR;
232
+ } else {
233
+ process.env.PI_CODING_AGENT_DIR = previousAgentDir;
234
+ }
235
+ }
236
+ });
237
+ });
238
+
239
+ test("bash override uses shellCommandPrefix from Pi settings", async () => {
240
+ await withTempDir("pi-tool-display-shellprefix-", async (dir) => {
241
+ const previousAgentDir = process.env.PI_CODING_AGENT_DIR;
242
+ process.env.PI_CODING_AGENT_DIR = dir;
243
+ writeFileSync(
244
+ join(dir, "settings.json"),
245
+ JSON.stringify({ shellCommandPrefix: "printf 'prefix-output\\n'" }),
246
+ "utf8",
247
+ );
248
+
249
+ try {
250
+ const { api, registeredTools, eventHandlers } = createExtensionApiStub();
251
+ registerToolDisplayOverrides(api, () => DEFAULT_TOOL_DISPLAY_CONFIG);
252
+ await eventHandlers.before_agent_start?.();
253
+
254
+ const bashTool = registeredTools.find((tool) => tool.name === "bash") as ExecutableToolLike | undefined;
255
+ assert.ok(bashTool, "expected bash override to be registered");
256
+ const result = await bashTool.execute(
257
+ "tool-call-2",
258
+ { command: "printf 'command-output\\n'" },
259
+ undefined,
260
+ undefined,
261
+ { cwd: process.cwd() },
262
+ );
263
+ assert.equal(getTextOutput(result).trim(), "prefix-output\ncommand-output");
264
+ } finally {
265
+ if (previousAgentDir === undefined) {
266
+ delete process.env.PI_CODING_AGENT_DIR;
267
+ } else {
268
+ process.env.PI_CODING_AGENT_DIR = previousAgentDir;
269
+ }
270
+ }
271
+ });
272
+ });
273
+
274
+ test("registerToolDisplayOverrides drains pending display decorations from early-loading extensions", () => {
275
+ type GlobalWithPendingDecorations = typeof globalThis & {
276
+ [TOOL_DISPLAY_PENDING_DECORATIONS_KEY]?: Array<{
277
+ tool: Record<string, unknown>;
278
+ adapter?: Record<string, unknown>;
279
+ }>;
280
+ };
281
+ const globalWithPending = globalThis as GlobalWithPendingDecorations;
282
+ const previousPending = globalWithPending[TOOL_DISPLAY_PENDING_DECORATIONS_KEY];
283
+ const queuedTool: Record<string, unknown> = {
284
+ name: "mcp",
285
+ label: "MCP Proxy",
286
+ description: "Unified MCP gateway.",
287
+ parameters: {},
288
+ execute(): void {
289
+ // No-op test stub.
290
+ },
291
+ };
292
+ globalWithPending[TOOL_DISPLAY_PENDING_DECORATIONS_KEY] = [
293
+ {
294
+ adapter: { kind: "mcp" },
295
+ tool: queuedTool,
296
+ },
297
+ ];
298
+
299
+ try {
300
+ const { api, registeredTools } = createExtensionApiStub();
301
+
302
+ registerToolDisplayOverrides(api, () => DEFAULT_TOOL_DISPLAY_CONFIG);
303
+
304
+ assert.equal(registeredTools.some((tool) => tool.name === "mcp"), false);
305
+ assert.equal(typeof queuedTool.renderCall, "function", "expected queued MCP tool to receive renderCall");
306
+ assert.equal(typeof queuedTool.renderResult, "function", "expected queued MCP tool to receive renderResult");
307
+ assert.equal(globalWithPending[TOOL_DISPLAY_PENDING_DECORATIONS_KEY]?.length, 0);
308
+ } finally {
309
+ if (previousPending) {
310
+ globalWithPending[TOOL_DISPLAY_PENDING_DECORATIONS_KEY] = previousPending;
311
+ } else {
312
+ delete globalWithPending[TOOL_DISPLAY_PENDING_DECORATIONS_KEY];
313
+ }
314
+ }
315
+ });
@@ -0,0 +1,68 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import {
4
+ clearWriteExecutionMeta,
5
+ getWriteExecutionMeta,
6
+ recordWriteExecutionMeta,
7
+ WRITE_EXECUTION_META_LIMIT,
8
+ type WriteExecutionMeta,
9
+ } from "../src/tool-overrides.ts";
10
+
11
+ test("write execution metadata moves pending entries into render state", () => {
12
+ const pending = new Map<string, WriteExecutionMeta>();
13
+ const meta: WriteExecutionMeta = {
14
+ fileExistedBeforeWrite: true,
15
+ previousContent: "before\n",
16
+ };
17
+
18
+ recordWriteExecutionMeta(pending, "write-1", meta);
19
+
20
+ const state: Record<string, unknown> = {};
21
+ const first = getWriteExecutionMeta(
22
+ { toolCallId: "write-1", state },
23
+ pending,
24
+ );
25
+ const second = getWriteExecutionMeta({ state }, pending);
26
+
27
+ assert.deepEqual(first, meta);
28
+ assert.equal(pending.size, 0);
29
+ assert.deepEqual(second, meta);
30
+ });
31
+
32
+ test("write execution metadata keeps stale pending entries bounded", () => {
33
+ const pending = new Map<string, WriteExecutionMeta>();
34
+ const totalWrites = WRITE_EXECUTION_META_LIMIT + 7;
35
+
36
+ for (let index = 0; index < totalWrites; index += 1) {
37
+ recordWriteExecutionMeta(pending, `write-${index}`, {
38
+ fileExistedBeforeWrite: true,
39
+ previousContent: `before-${index}`,
40
+ });
41
+ }
42
+
43
+ const evictedCount = totalWrites - WRITE_EXECUTION_META_LIMIT;
44
+
45
+ assert.equal(pending.size, WRITE_EXECUTION_META_LIMIT);
46
+ assert.equal(pending.has(`write-${evictedCount - 1}`), false);
47
+ assert.equal(pending.has(`write-${evictedCount}`), true);
48
+ assert.deepEqual(pending.get(`write-${totalWrites - 1}`), {
49
+ fileExistedBeforeWrite: true,
50
+ previousContent: `before-${totalWrites - 1}`,
51
+ });
52
+ });
53
+
54
+ test("write execution metadata cleanup clears stale pending entries", () => {
55
+ const pending = new Map<string, WriteExecutionMeta>();
56
+
57
+ recordWriteExecutionMeta(pending, "write-1", {
58
+ fileExistedBeforeWrite: false,
59
+ });
60
+ recordWriteExecutionMeta(pending, "write-2", {
61
+ fileExistedBeforeWrite: true,
62
+ previousContent: "before\n",
63
+ });
64
+
65
+ clearWriteExecutionMeta(pending);
66
+
67
+ assert.equal(pending.size, 0);
68
+ });