@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.
- package/CHANGELOG.md +23 -0
- package/dist/config.d.ts +5 -3
- package/dist/config.js +6 -4
- package/dist/core/skills.d.ts +2 -0
- package/dist/core/skills.js +1 -0
- package/dist/defaults/settings.json +0 -3
- package/dist/extensions/auto-session-name.test.ts +157 -0
- package/dist/extensions/auto-session-name.ts +96 -0
- package/dist/extensions/cost-reconcile.test.ts +76 -0
- package/dist/extensions/cost-reconcile.ts +278 -0
- package/dist/extensions/enable-readonly-tools.test.ts +11 -0
- package/dist/extensions/enable-readonly-tools.ts +7 -3
- package/dist/extensions/package.json +3 -0
- package/dist/extensions/pi-tool-display/CHANGELOG.md +329 -0
- package/dist/extensions/pi-tool-display/LICENSE +21 -0
- package/dist/extensions/pi-tool-display/README.md +417 -0
- package/dist/extensions/pi-tool-display/config/config.example.json +40 -0
- package/dist/extensions/pi-tool-display/index.ts +3 -0
- package/dist/extensions/pi-tool-display/package-lock.json +2555 -0
- package/dist/extensions/pi-tool-display/package.json +86 -0
- package/dist/extensions/pi-tool-display/src/agent-dir.ts +35 -0
- package/dist/extensions/pi-tool-display/src/ansi-utils.ts +116 -0
- package/dist/extensions/pi-tool-display/src/bash-display.ts +213 -0
- package/dist/extensions/pi-tool-display/src/capabilities.ts +88 -0
- package/dist/extensions/pi-tool-display/src/config-modal.ts +541 -0
- package/dist/extensions/pi-tool-display/src/config-store.ts +304 -0
- package/dist/extensions/pi-tool-display/src/debug-logger.ts +150 -0
- package/dist/extensions/pi-tool-display/src/diff-presentation.ts +72 -0
- package/dist/extensions/pi-tool-display/src/diff-renderer.ts +2517 -0
- package/dist/extensions/pi-tool-display/src/disposable.ts +34 -0
- package/dist/extensions/pi-tool-display/src/extension-lifecycle.ts +14 -0
- package/dist/extensions/pi-tool-display/src/index.ts +109 -0
- package/dist/extensions/pi-tool-display/src/line-width-safety.ts +93 -0
- package/dist/extensions/pi-tool-display/src/modal-icons.ts +57 -0
- package/dist/extensions/pi-tool-display/src/pending-diff-preview.ts +370 -0
- package/dist/extensions/pi-tool-display/src/presets.ts +117 -0
- package/dist/extensions/pi-tool-display/src/render-utils.ts +186 -0
- package/dist/extensions/pi-tool-display/src/settings-inspector-modal.ts +475 -0
- package/dist/extensions/pi-tool-display/src/thinking-label.ts +343 -0
- package/dist/extensions/pi-tool-display/src/tool-call-box.ts +181 -0
- package/dist/extensions/pi-tool-display/src/tool-metadata.ts +139 -0
- package/dist/extensions/pi-tool-display/src/tool-overrides.ts +2168 -0
- package/dist/extensions/pi-tool-display/src/types.ts +109 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-markdown.ts +64 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-native.ts +66 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-patch.ts +72 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-renderer.ts +419 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-utils.ts +106 -0
- package/dist/extensions/pi-tool-display/src/write-display-utils.ts +31 -0
- package/dist/extensions/pi-tool-display/src/zellij-modal.ts +1027 -0
- package/dist/extensions/pi-tool-display/tests/active-backlog-red.test.ts +65 -0
- package/dist/extensions/pi-tool-display/tests/ansi-utils.test.ts +345 -0
- package/dist/extensions/pi-tool-display/tests/bash-display.test.ts +442 -0
- package/dist/extensions/pi-tool-display/tests/capabilities-edge.test.ts +360 -0
- package/dist/extensions/pi-tool-display/tests/capabilities.test.ts +54 -0
- package/dist/extensions/pi-tool-display/tests/config-modal.test.ts +360 -0
- package/dist/extensions/pi-tool-display/tests/config-store.test.ts +87 -0
- package/dist/extensions/pi-tool-display/tests/custom-tool-overrides.test.ts +415 -0
- package/dist/extensions/pi-tool-display/tests/debug-logger-edge.test.ts +630 -0
- package/dist/extensions/pi-tool-display/tests/debug-logger.test.ts +62 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-ansi.test.ts +55 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-edge.test.ts +886 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-numbering.test.ts +135 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-width.test.ts +454 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-wrap.test.ts +169 -0
- package/dist/extensions/pi-tool-display/tests/index-integration.test.ts +354 -0
- package/dist/extensions/pi-tool-display/tests/pending-diff-preview.test.ts +79 -0
- package/dist/extensions/pi-tool-display/tests/presets-edge.test.ts +229 -0
- package/dist/extensions/pi-tool-display/tests/presets.test.ts +26 -0
- package/dist/extensions/pi-tool-display/tests/reload-behavior.test.ts +1241 -0
- package/dist/extensions/pi-tool-display/tests/render-utils.test.ts +710 -0
- package/dist/extensions/pi-tool-display/tests/settings-inspector.test.ts +450 -0
- package/dist/extensions/pi-tool-display/tests/thinking-label-edge.test.ts +904 -0
- package/dist/extensions/pi-tool-display/tests/thinking-label.test.ts +72 -0
- package/dist/extensions/pi-tool-display/tests/tool-call-box.test.ts +105 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-config.test.ts +613 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +499 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-registration.test.ts +315 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-write-meta.test.ts +68 -0
- package/dist/extensions/pi-tool-display/tests/tool-ui-utils.test.ts +413 -0
- package/dist/extensions/pi-tool-display/tests/user-message-box-performance-red.test.ts +176 -0
- package/dist/extensions/pi-tool-display/tests/user-message-box.test.ts +908 -0
- package/dist/extensions/pi-tool-display/tests/zellij-modal.test.ts +848 -0
- package/dist/extensions/pi-tool-display/tool-display-api-consumer.d.ts +30 -0
- package/dist/extensions/pi-tool-display/tool-display-api-consumer.js +40 -0
- package/dist/extensions/pi-zentui/extensions/zentui/format.ts +46 -1
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +10 -0
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +3 -3
- package/dist/extensions/pi-zentui/test/format.test.ts +50 -2
- package/dist/modes/interactive/components/startup-box.d.ts +38 -0
- package/dist/modes/interactive/components/startup-box.js +90 -0
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.js +85 -122
- package/dist/skills/agent-browser/SKILL.md +1 -0
- package/dist/skills/batch-grill-me/SKILL.md +1 -0
- package/dist/skills/brandkit/SKILL.md +1 -0
- package/dist/skills/design-references/SKILL.md +1 -0
- package/dist/skills/design-taste-frontend/SKILL.md +1 -0
- package/dist/skills/design-taste-frontend-v1/SKILL.md +1 -0
- package/dist/skills/full-output-enforcement/SKILL.md +1 -0
- package/dist/skills/gpt-taste/SKILL.md +1 -0
- package/dist/skills/grill-me/SKILL.md +1 -0
- package/dist/skills/handoff/SKILL.md +1 -0
- package/dist/skills/handoff-text/SKILL.md +1 -0
- package/dist/skills/high-end-visual-design/SKILL.md +1 -0
- package/dist/skills/image-to-code/SKILL.md +1 -0
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +1 -0
- package/dist/skills/imagegen-frontend-web/SKILL.md +1 -0
- package/dist/skills/implanger/SKILL.md +1 -0
- package/dist/skills/improve-codebase/SKILL.md +1 -0
- package/dist/skills/industrial-brutalist-ui/SKILL.md +1 -0
- package/dist/skills/minimalist-ui/SKILL.md +1 -0
- package/dist/skills/pi-subagents/SKILL.md +1 -0
- package/dist/skills/planger/SKILL.md +1 -0
- package/dist/skills/ponytail/SKILL.md +1 -0
- package/dist/skills/ponytail-audit/SKILL.md +1 -0
- package/dist/skills/ponytail-debt/SKILL.md +1 -0
- package/dist/skills/ponytail-gain/SKILL.md +1 -0
- package/dist/skills/ponytail-help/SKILL.md +1 -0
- package/dist/skills/ponytail-review/SKILL.md +1 -0
- package/dist/skills/redesign-existing-projects/SKILL.md +1 -0
- package/dist/skills/selesai-handoff/SKILL.md +1 -0
- package/dist/skills/stitch-design-taste/SKILL.md +1 -0
- package/dist/skills/unlazy/SKILL.md +1 -0
- package/dist/skills/web-design-guidelines/SKILL.md +1 -0
- package/dist/skills/workflow/SKILL.md +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { normalizeToolDisplayConfig } from "../src/config-store.ts";
|
|
5
|
+
import { registerToolDisplayOverrides } from "../src/tool-overrides.ts";
|
|
6
|
+
import { DEFAULT_TOOL_DISPLAY_CONFIG, type ToolDisplayConfig } from "../src/types.ts";
|
|
7
|
+
|
|
8
|
+
interface NormalizedCustomToolOverride {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
kind: "generic" | "mcp";
|
|
11
|
+
outputMode: "hidden" | "summary" | "preview";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ToolDisplayConfigWithCustomOverrides extends ToolDisplayConfig {
|
|
15
|
+
customToolOverrides: Record<string, NormalizedCustomToolOverride>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface RenderThemeLike {
|
|
19
|
+
fg(color: string, value: string): string;
|
|
20
|
+
bold(value: string): string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface RenderComponentLike {
|
|
24
|
+
render(width: number): string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface RuntimeTool extends Record<string, unknown> {
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
parameters?: unknown;
|
|
31
|
+
renderCall?: (...args: unknown[]) => RenderComponentLike;
|
|
32
|
+
renderResult?: (...args: unknown[]) => RenderComponentLike;
|
|
33
|
+
execute?: (...args: unknown[]) => unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ToolEventHandlers {
|
|
37
|
+
session_start?: () => Promise<void> | void;
|
|
38
|
+
before_agent_start?: () => Promise<void> | void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildConfigWithCustomOverrides(
|
|
42
|
+
customToolOverrides: Record<string, unknown>,
|
|
43
|
+
overrides: Partial<ToolDisplayConfig> = {},
|
|
44
|
+
): ToolDisplayConfig {
|
|
45
|
+
return {
|
|
46
|
+
...DEFAULT_TOOL_DISPLAY_CONFIG,
|
|
47
|
+
...overrides,
|
|
48
|
+
boxedToolCalls: overrides.boxedToolCalls ?? false,
|
|
49
|
+
registerToolOverrides: {
|
|
50
|
+
...DEFAULT_TOOL_DISPLAY_CONFIG.registerToolOverrides,
|
|
51
|
+
...overrides.registerToolOverrides,
|
|
52
|
+
},
|
|
53
|
+
customToolOverrides,
|
|
54
|
+
} as ToolDisplayConfig;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createExtensionApiStub(allTools: RuntimeTool[] = []): {
|
|
58
|
+
api: ExtensionAPI;
|
|
59
|
+
registeredTools: RuntimeTool[];
|
|
60
|
+
runtimeTools: RuntimeTool[];
|
|
61
|
+
eventHandlers: ToolEventHandlers;
|
|
62
|
+
} {
|
|
63
|
+
const registeredTools: RuntimeTool[] = [];
|
|
64
|
+
const eventHandlers: ToolEventHandlers = {};
|
|
65
|
+
const api = {
|
|
66
|
+
registerTool(tool: RuntimeTool): void {
|
|
67
|
+
registeredTools.push(tool);
|
|
68
|
+
},
|
|
69
|
+
on(event: keyof ToolEventHandlers, handler: () => Promise<void> | void): void {
|
|
70
|
+
eventHandlers[event] = handler;
|
|
71
|
+
},
|
|
72
|
+
getAllTools(): RuntimeTool[] {
|
|
73
|
+
const names = new Set(allTools.map((tool) => tool.name));
|
|
74
|
+
const defaultBuiltIns: RuntimeTool[] = ["read", "edit"]
|
|
75
|
+
.filter((name) => !names.has(name))
|
|
76
|
+
.map((name) => ({
|
|
77
|
+
name,
|
|
78
|
+
description: `Built-in ${name} tool`,
|
|
79
|
+
sourceInfo: { source: "builtin", path: `<builtin:${name}>` },
|
|
80
|
+
}));
|
|
81
|
+
return [...defaultBuiltIns, ...allTools];
|
|
82
|
+
},
|
|
83
|
+
} as unknown as ExtensionAPI;
|
|
84
|
+
|
|
85
|
+
return { api, registeredTools, runtimeTools: allTools, eventHandlers };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function runLifecycle(eventHandlers: ToolEventHandlers): Promise<void> {
|
|
89
|
+
await eventHandlers.session_start?.();
|
|
90
|
+
await eventHandlers.before_agent_start?.();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createTheme(): RenderThemeLike {
|
|
94
|
+
return {
|
|
95
|
+
fg: (_color: string, value: string): string => value,
|
|
96
|
+
bold: (value: string): string => value,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function renderToText(component: unknown): string {
|
|
101
|
+
const render = component && typeof component === "object"
|
|
102
|
+
? (component as { render?: unknown }).render
|
|
103
|
+
: undefined;
|
|
104
|
+
assert.equal(typeof render, "function", "expected a renderable component");
|
|
105
|
+
return (component as RenderComponentLike)
|
|
106
|
+
.render(120)
|
|
107
|
+
.map((line) => line.trimEnd())
|
|
108
|
+
.join("\n")
|
|
109
|
+
.trim();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function renderToolResult(tool: RuntimeTool, text: string, options: Record<string, unknown> = {}): string {
|
|
113
|
+
return renderToolRawResult(
|
|
114
|
+
tool,
|
|
115
|
+
{ content: [{ type: "text", text }], details: {} },
|
|
116
|
+
options,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function renderToolRawResult(
|
|
121
|
+
tool: RuntimeTool,
|
|
122
|
+
result: Record<string, unknown>,
|
|
123
|
+
options: Record<string, unknown> = {},
|
|
124
|
+
): string {
|
|
125
|
+
assert.equal(typeof tool.renderResult, "function", `expected ${tool.name} to have renderResult`);
|
|
126
|
+
return renderToText(
|
|
127
|
+
tool.renderResult(
|
|
128
|
+
result,
|
|
129
|
+
{ expanded: false, isPartial: false, ...options },
|
|
130
|
+
createTheme(),
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
test("normalizeToolDisplayConfig defaults customToolOverrides to an empty opt-in map", () => {
|
|
136
|
+
const config = normalizeToolDisplayConfig({}) as ToolDisplayConfigWithCustomOverrides;
|
|
137
|
+
|
|
138
|
+
assert.deepEqual(config.customToolOverrides, {});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("normalizeToolDisplayConfig normalizes custom tool override shorthand, defaults, and invalid entries", () => {
|
|
142
|
+
const config = normalizeToolDisplayConfig({
|
|
143
|
+
customToolOverrides: {
|
|
144
|
+
ide_find_symbol: true,
|
|
145
|
+
" agent_gateway ": { outputMode: "preview" },
|
|
146
|
+
mcp_gateway: { enabled: true, kind: "mcp", outputMode: "hidden" },
|
|
147
|
+
disabled_tool: false,
|
|
148
|
+
invalid_kind: { enabled: true, kind: "terminal", outputMode: "verbose" },
|
|
149
|
+
read: { enabled: true, kind: "mcp", outputMode: "summary" },
|
|
150
|
+
"": { enabled: true },
|
|
151
|
+
" ": true,
|
|
152
|
+
},
|
|
153
|
+
}) as ToolDisplayConfigWithCustomOverrides;
|
|
154
|
+
|
|
155
|
+
assert.deepEqual(config.customToolOverrides, {
|
|
156
|
+
ide_find_symbol: { enabled: true, kind: "generic", outputMode: "summary" },
|
|
157
|
+
agent_gateway: { enabled: true, kind: "generic", outputMode: "preview" },
|
|
158
|
+
mcp_gateway: { enabled: true, kind: "mcp", outputMode: "hidden" },
|
|
159
|
+
disabled_tool: { enabled: false, kind: "generic", outputMode: "summary" },
|
|
160
|
+
invalid_kind: { enabled: true, kind: "generic", outputMode: "summary" },
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("enabled generic custom tool override replaces existing extension renderers and leaves other tools alone", async () => {
|
|
165
|
+
const enabledTool: RuntimeTool = {
|
|
166
|
+
name: "ide_find_symbol",
|
|
167
|
+
description: "Find symbols through an IDE index.",
|
|
168
|
+
parameters: {},
|
|
169
|
+
execute: () => {},
|
|
170
|
+
renderCall: () => ({ render: () => ["RAW ENABLED CALL"] }),
|
|
171
|
+
renderResult: () => ({ render: () => ["RAW ENABLED RESULT"] }),
|
|
172
|
+
};
|
|
173
|
+
const disabledTool: RuntimeTool = {
|
|
174
|
+
name: "disabled_noisy_tool",
|
|
175
|
+
description: "Noisy extension tool that should keep its own renderer while disabled.",
|
|
176
|
+
parameters: {},
|
|
177
|
+
execute: () => {},
|
|
178
|
+
renderCall: () => ({ render: () => ["RAW DISABLED CALL"] }),
|
|
179
|
+
renderResult: () => ({ render: () => ["RAW DISABLED RESULT"] }),
|
|
180
|
+
};
|
|
181
|
+
const unlistedTool: RuntimeTool = {
|
|
182
|
+
name: "unlisted_noisy_tool",
|
|
183
|
+
description: "Noisy extension tool that was not explicitly opted in.",
|
|
184
|
+
parameters: {},
|
|
185
|
+
execute: () => {},
|
|
186
|
+
renderCall: () => ({ render: () => ["RAW UNLISTED CALL"] }),
|
|
187
|
+
renderResult: () => ({ render: () => ["RAW UNLISTED RESULT"] }),
|
|
188
|
+
};
|
|
189
|
+
const config = buildConfigWithCustomOverrides({
|
|
190
|
+
ide_find_symbol: { enabled: true, outputMode: "summary" },
|
|
191
|
+
disabled_noisy_tool: { enabled: false, outputMode: "summary" },
|
|
192
|
+
});
|
|
193
|
+
const { api, eventHandlers } = createExtensionApiStub([enabledTool, disabledTool, unlistedTool]);
|
|
194
|
+
|
|
195
|
+
registerToolDisplayOverrides(api, () => config);
|
|
196
|
+
await runLifecycle(eventHandlers);
|
|
197
|
+
|
|
198
|
+
assert.equal(renderToText(enabledTool.renderCall?.({ query: "Widget", limit: 5 }, createTheme())), "ide_find_symbol (2 args)");
|
|
199
|
+
assert.equal(renderToolResult(enabledTool, "alpha\nbeta\ngamma\n"), "↳ 3 lines returned • Ctrl+O to expand");
|
|
200
|
+
assert.equal(renderToText(disabledTool.renderCall?.({}, createTheme())), "RAW DISABLED CALL");
|
|
201
|
+
assert.equal(renderToolResult(disabledTool, "ignored"), "RAW DISABLED RESULT");
|
|
202
|
+
assert.equal(renderToText(unlistedTool.renderCall?.({}, createTheme())), "RAW UNLISTED CALL");
|
|
203
|
+
assert.equal(renderToolResult(unlistedTool, "ignored"), "RAW UNLISTED RESULT");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("custom tool override defaults kind to generic unless the user chooses mcp", async () => {
|
|
207
|
+
const genericTool: RuntimeTool = {
|
|
208
|
+
name: "remote_gateway",
|
|
209
|
+
description: "Calls a remote integration with plain generic rendering.",
|
|
210
|
+
parameters: {},
|
|
211
|
+
execute: () => {},
|
|
212
|
+
};
|
|
213
|
+
const mcpTool: RuntimeTool = {
|
|
214
|
+
name: "remote_gateway_structured",
|
|
215
|
+
description: "Calls a remote integration with user-selected structured rendering.",
|
|
216
|
+
parameters: {},
|
|
217
|
+
execute: () => {},
|
|
218
|
+
};
|
|
219
|
+
const config = buildConfigWithCustomOverrides({
|
|
220
|
+
remote_gateway: { enabled: true },
|
|
221
|
+
remote_gateway_structured: { enabled: true, kind: "mcp" },
|
|
222
|
+
});
|
|
223
|
+
const { api, eventHandlers } = createExtensionApiStub([genericTool, mcpTool]);
|
|
224
|
+
|
|
225
|
+
registerToolDisplayOverrides(api, () => config);
|
|
226
|
+
await runLifecycle(eventHandlers);
|
|
227
|
+
|
|
228
|
+
assert.equal(renderToText(genericTool.renderCall?.({ tool: "read_file", server: "filesystem" }, createTheme())), "remote_gateway (2 args)");
|
|
229
|
+
assert.equal(renderToText(mcpTool.renderCall?.({ tool: "read_file", server: "filesystem" }, createTheme())), "MCP call filesystem:read_file (2 args)");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("custom generic tool override honors per-tool hidden output mode", async () => {
|
|
233
|
+
const quietTool: RuntimeTool = {
|
|
234
|
+
name: "large_payload_tool",
|
|
235
|
+
description: "Produces huge payloads that the user wants hidden.",
|
|
236
|
+
parameters: {},
|
|
237
|
+
execute: () => {},
|
|
238
|
+
};
|
|
239
|
+
const config = buildConfigWithCustomOverrides({
|
|
240
|
+
large_payload_tool: { enabled: true, outputMode: "hidden" },
|
|
241
|
+
});
|
|
242
|
+
const { api, eventHandlers } = createExtensionApiStub([quietTool]);
|
|
243
|
+
|
|
244
|
+
registerToolDisplayOverrides(api, () => config);
|
|
245
|
+
await runLifecycle(eventHandlers);
|
|
246
|
+
|
|
247
|
+
assert.equal(renderToolResult(quietTool, "secret\nnoisy\noutput\n"), "");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("custom tool overrides ignore missing tools instead of registering phantom tools", async () => {
|
|
251
|
+
const config = buildConfigWithCustomOverrides({
|
|
252
|
+
missing_tool: { enabled: true, outputMode: "summary" },
|
|
253
|
+
});
|
|
254
|
+
const { api, registeredTools, runtimeTools, eventHandlers } = createExtensionApiStub([]);
|
|
255
|
+
|
|
256
|
+
registerToolDisplayOverrides(api, () => config);
|
|
257
|
+
await runLifecycle(eventHandlers);
|
|
258
|
+
|
|
259
|
+
assert.equal(runtimeTools.some((tool) => tool.name === "missing_tool"), false);
|
|
260
|
+
assert.equal(registeredTools.some((tool) => tool.name === "missing_tool"), false);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("normalizeToolDisplayConfig treats malformed customToolOverrides containers as empty", () => {
|
|
264
|
+
for (const rawCustomOverrides of [null, true, "ide_find_symbol", [], 42]) {
|
|
265
|
+
const config = normalizeToolDisplayConfig({
|
|
266
|
+
customToolOverrides: rawCustomOverrides,
|
|
267
|
+
}) as ToolDisplayConfigWithCustomOverrides;
|
|
268
|
+
|
|
269
|
+
assert.deepEqual(config.customToolOverrides, {});
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("normalizeToolDisplayConfig preserves supported custom output modes and drops unknown entry fields", () => {
|
|
274
|
+
const config = normalizeToolDisplayConfig({
|
|
275
|
+
customToolOverrides: {
|
|
276
|
+
hidden_tool: { enabled: true, outputMode: "hidden", label: "Ignored Label" },
|
|
277
|
+
summary_tool: { enabled: true, outputMode: "summary", pathFields: ["file_path"] },
|
|
278
|
+
preview_tool: { enabled: true, outputMode: "preview", renderShell: "self" },
|
|
279
|
+
},
|
|
280
|
+
}) as ToolDisplayConfigWithCustomOverrides;
|
|
281
|
+
|
|
282
|
+
assert.deepEqual(config.customToolOverrides, {
|
|
283
|
+
hidden_tool: { enabled: true, kind: "generic", outputMode: "hidden" },
|
|
284
|
+
summary_tool: { enabled: true, kind: "generic", outputMode: "summary" },
|
|
285
|
+
preview_tool: { enabled: true, kind: "generic", outputMode: "preview" },
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("generic custom tool renderCall handles absent, non-object, and nested arguments safely", async () => {
|
|
290
|
+
const argumentProbe: RuntimeTool = {
|
|
291
|
+
name: "argument_probe",
|
|
292
|
+
description: "Noisy extension tool with unpredictable arguments.",
|
|
293
|
+
parameters: {},
|
|
294
|
+
execute: () => {},
|
|
295
|
+
};
|
|
296
|
+
const config = buildConfigWithCustomOverrides({
|
|
297
|
+
argument_probe: { enabled: true, outputMode: "summary" },
|
|
298
|
+
});
|
|
299
|
+
const { api, eventHandlers } = createExtensionApiStub([argumentProbe]);
|
|
300
|
+
|
|
301
|
+
registerToolDisplayOverrides(api, () => config);
|
|
302
|
+
await runLifecycle(eventHandlers);
|
|
303
|
+
|
|
304
|
+
assert.equal(renderToText(argumentProbe.renderCall?.(undefined, createTheme())), "argument_probe (no args)");
|
|
305
|
+
assert.equal(renderToText(argumentProbe.renderCall?.(null, createTheme())), "argument_probe (no args)");
|
|
306
|
+
assert.equal(renderToText(argumentProbe.renderCall?.("raw string args", createTheme())), "argument_probe (no args)");
|
|
307
|
+
assert.equal(renderToText(argumentProbe.renderCall?.(["array", "args"], createTheme())), "argument_probe (no args)");
|
|
308
|
+
assert.equal(
|
|
309
|
+
renderToText(argumentProbe.renderCall?.({ path: "src/index.ts", options: { recursive: true }, tags: ["a", "b"] }, createTheme())),
|
|
310
|
+
"argument_probe (3 args)",
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("generic custom tool preview mode supports collapsed previews, expanded previews, partial state, and empty text", async () => {
|
|
315
|
+
const previewTool: RuntimeTool = {
|
|
316
|
+
name: "preview_payload_tool",
|
|
317
|
+
description: "Produces output that should be previewed instead of summarized.",
|
|
318
|
+
parameters: {},
|
|
319
|
+
execute: () => {},
|
|
320
|
+
};
|
|
321
|
+
const config = buildConfigWithCustomOverrides(
|
|
322
|
+
{ preview_payload_tool: { enabled: true, outputMode: "preview" } },
|
|
323
|
+
{ previewLines: 2 },
|
|
324
|
+
);
|
|
325
|
+
const { api, eventHandlers } = createExtensionApiStub([previewTool]);
|
|
326
|
+
|
|
327
|
+
registerToolDisplayOverrides(api, () => config);
|
|
328
|
+
await runLifecycle(eventHandlers);
|
|
329
|
+
|
|
330
|
+
assert.equal(
|
|
331
|
+
renderToolResult(previewTool, "alpha\nbeta\ngamma\ndelta\n"),
|
|
332
|
+
"alpha\nbeta\n... (2 more lines • Ctrl+O to expand)",
|
|
333
|
+
);
|
|
334
|
+
assert.equal(
|
|
335
|
+
renderToolResult(previewTool, "alpha\nbeta\ngamma\ndelta\n", { expanded: true }),
|
|
336
|
+
"alpha\nbeta\ngamma\ndelta",
|
|
337
|
+
);
|
|
338
|
+
assert.equal(renderToolResult(previewTool, "still running", { isPartial: true }), "running...");
|
|
339
|
+
assert.equal(
|
|
340
|
+
renderToolRawResult(previewTool, { content: [{ type: "image", data: "ignored" }], details: {} }),
|
|
341
|
+
"↳ (no output)",
|
|
342
|
+
);
|
|
343
|
+
assert.equal(renderToolRawResult(previewTool, { details: {} }), "↳ (no output)");
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("explicit mcp custom tool override interprets MCP proxy argument variants", async () => {
|
|
347
|
+
const customMcpProxy: RuntimeTool = {
|
|
348
|
+
name: "custom_gateway",
|
|
349
|
+
description: "Custom gateway that should render as MCP because the user opted in.",
|
|
350
|
+
parameters: {},
|
|
351
|
+
execute: () => {},
|
|
352
|
+
};
|
|
353
|
+
const config = buildConfigWithCustomOverrides({
|
|
354
|
+
custom_gateway: { enabled: true, kind: "mcp", outputMode: "summary" },
|
|
355
|
+
});
|
|
356
|
+
const { api, eventHandlers } = createExtensionApiStub([customMcpProxy]);
|
|
357
|
+
|
|
358
|
+
registerToolDisplayOverrides(api, () => config);
|
|
359
|
+
await runLifecycle(eventHandlers);
|
|
360
|
+
|
|
361
|
+
assert.equal(renderToText(customMcpProxy.renderCall?.({}, createTheme())), "MCP status (no args)");
|
|
362
|
+
assert.equal(renderToText(customMcpProxy.renderCall?.({ connect: "filesystem" }, createTheme())), "MCP connect filesystem (1 arg)");
|
|
363
|
+
assert.equal(renderToText(customMcpProxy.renderCall?.({ describe: "read_file", server: "filesystem" }, createTheme())), "MCP describe read_file @filesystem (2 args)");
|
|
364
|
+
assert.equal(renderToText(customMcpProxy.renderCall?.({ search: "browser", server: "exa" }, createTheme())), "MCP search \"browser\" @exa (2 args)");
|
|
365
|
+
assert.equal(renderToText(customMcpProxy.renderCall?.({ server: "filesystem" }, createTheme())), "MCP tools filesystem (1 arg)");
|
|
366
|
+
assert.equal(renderToText(customMcpProxy.renderCall?.({ tool: "read_file", server: "filesystem" }, createTheme())), "MCP call filesystem:read_file (2 args)");
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("custom tool override preserves execution contract, parameters, and prepareArguments", async () => {
|
|
370
|
+
const execute = (): string => "executed";
|
|
371
|
+
const prepareArguments = (args: unknown): unknown => args;
|
|
372
|
+
const parameters = { type: "object", properties: { query: { type: "string" } } };
|
|
373
|
+
const contractTool: RuntimeTool = {
|
|
374
|
+
name: "contract_tool",
|
|
375
|
+
description: "Tool with runtime behavior that must survive decoration.",
|
|
376
|
+
parameters,
|
|
377
|
+
execute,
|
|
378
|
+
prepareArguments,
|
|
379
|
+
};
|
|
380
|
+
const config = buildConfigWithCustomOverrides({
|
|
381
|
+
contract_tool: { enabled: true, outputMode: "summary" },
|
|
382
|
+
});
|
|
383
|
+
const { api, eventHandlers } = createExtensionApiStub([contractTool]);
|
|
384
|
+
|
|
385
|
+
registerToolDisplayOverrides(api, () => config);
|
|
386
|
+
await runLifecycle(eventHandlers);
|
|
387
|
+
|
|
388
|
+
assert.equal(contractTool.execute, execute);
|
|
389
|
+
assert.equal(contractTool.prepareArguments, prepareArguments);
|
|
390
|
+
assert.equal(contractTool.parameters, parameters);
|
|
391
|
+
assert.equal(typeof contractTool.renderCall, "function");
|
|
392
|
+
assert.equal(typeof contractTool.renderResult, "function");
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("custom tool registered after lifecycle is decorated when it is explicitly opted in", async () => {
|
|
396
|
+
const config = buildConfigWithCustomOverrides({
|
|
397
|
+
late_custom_tool: { enabled: true, outputMode: "summary" },
|
|
398
|
+
});
|
|
399
|
+
const { api, eventHandlers } = createExtensionApiStub([]);
|
|
400
|
+
|
|
401
|
+
registerToolDisplayOverrides(api, () => config);
|
|
402
|
+
await runLifecycle(eventHandlers);
|
|
403
|
+
|
|
404
|
+
const lateTool: RuntimeTool = {
|
|
405
|
+
name: "late_custom_tool",
|
|
406
|
+
description: "Tool registered after lifecycle by another extension.",
|
|
407
|
+
parameters: {},
|
|
408
|
+
execute: () => {},
|
|
409
|
+
};
|
|
410
|
+
(api as unknown as { registerTool(tool: RuntimeTool): void }).registerTool(lateTool);
|
|
411
|
+
|
|
412
|
+
assert.equal(typeof lateTool.renderCall, "function");
|
|
413
|
+
assert.equal(typeof lateTool.renderResult, "function");
|
|
414
|
+
assert.equal(renderToText(lateTool.renderCall?.({ query: "late" }, createTheme())), "late_custom_tool (1 arg)");
|
|
415
|
+
});
|