@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,613 @@
|
|
|
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 { registerToolDisplayOverrides } from "../src/tool-overrides.ts";
|
|
5
|
+
import { DEFAULT_TOOL_DISPLAY_CONFIG, type ToolDisplayConfig } from "../src/types.ts";
|
|
6
|
+
|
|
7
|
+
interface RenderThemeLike {
|
|
8
|
+
fg(color: string, value: string): string;
|
|
9
|
+
bold(value: string): string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface RenderComponentLike {
|
|
13
|
+
render(width: number): string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RenderCallContextLike {
|
|
17
|
+
lastComponent?: unknown;
|
|
18
|
+
state?: Record<string, unknown>;
|
|
19
|
+
invalidate(): void;
|
|
20
|
+
executionStarted: boolean;
|
|
21
|
+
isPartial: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface RegisteredToolLike {
|
|
25
|
+
name: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
parameters?: unknown;
|
|
28
|
+
promptSnippet?: string;
|
|
29
|
+
promptGuidelines?: string[];
|
|
30
|
+
renderCall?: (args: unknown, theme: RenderThemeLike, context: RenderCallContextLike) => RenderComponentLike;
|
|
31
|
+
renderResult?: (result: unknown, options: unknown, theme: unknown) => RenderComponentLike;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ToolEventHandlers {
|
|
35
|
+
session_start?: () => Promise<void> | void;
|
|
36
|
+
before_agent_start?: () => Promise<void> | void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildConfig(overrides: Partial<ToolDisplayConfig>): ToolDisplayConfig {
|
|
40
|
+
return {
|
|
41
|
+
...DEFAULT_TOOL_DISPLAY_CONFIG,
|
|
42
|
+
...overrides,
|
|
43
|
+
boxedToolCalls: overrides.boxedToolCalls ?? false,
|
|
44
|
+
registerToolOverrides: {
|
|
45
|
+
...DEFAULT_TOOL_DISPLAY_CONFIG.registerToolOverrides,
|
|
46
|
+
...overrides.registerToolOverrides,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function withDefaultReadEditOwners(tools: unknown[] = []): unknown[] {
|
|
52
|
+
const names = new Set(
|
|
53
|
+
tools
|
|
54
|
+
.map((tool) => (tool as { name?: unknown }).name)
|
|
55
|
+
.filter((name): name is string => typeof name === "string"),
|
|
56
|
+
);
|
|
57
|
+
const defaults = ["read", "edit"]
|
|
58
|
+
.filter((name) => !names.has(name))
|
|
59
|
+
.map((name) => ({ name, sourceInfo: { source: "builtin", path: `<builtin:${name}>` } }));
|
|
60
|
+
return [...defaults, ...tools];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createExtensionApiStub(allTools: Array<RegisteredToolLike & Record<string, unknown>> = []): {
|
|
64
|
+
api: ExtensionAPI;
|
|
65
|
+
registeredTools: RegisteredToolLike[];
|
|
66
|
+
runtimeTools: Array<RegisteredToolLike & Record<string, unknown>>;
|
|
67
|
+
eventHandlers: ToolEventHandlers;
|
|
68
|
+
} {
|
|
69
|
+
const registeredTools: RegisteredToolLike[] = [];
|
|
70
|
+
const eventHandlers: ToolEventHandlers = {};
|
|
71
|
+
const api = {
|
|
72
|
+
registerTool(tool: RegisteredToolLike): void {
|
|
73
|
+
registeredTools.push(tool);
|
|
74
|
+
},
|
|
75
|
+
on(event: keyof ToolEventHandlers, handler: () => Promise<void> | void): void {
|
|
76
|
+
eventHandlers[event] = handler;
|
|
77
|
+
},
|
|
78
|
+
getAllTools(): unknown[] {
|
|
79
|
+
return withDefaultReadEditOwners(allTools);
|
|
80
|
+
},
|
|
81
|
+
} as unknown as ExtensionAPI;
|
|
82
|
+
|
|
83
|
+
return { api, registeredTools, runtimeTools: allTools, eventHandlers };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function runLifecycle(eventHandlers: ToolEventHandlers): Promise<void> {
|
|
87
|
+
await eventHandlers.session_start?.();
|
|
88
|
+
await eventHandlers.before_agent_start?.();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createTheme(): RenderThemeLike {
|
|
92
|
+
return {
|
|
93
|
+
fg: (_color: string, value: string): string => value,
|
|
94
|
+
bold: (value: string): string => value,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function normalizeRenderedText(component: RenderComponentLike): string {
|
|
99
|
+
return component
|
|
100
|
+
.render(120)
|
|
101
|
+
.map((line) => line.trimEnd())
|
|
102
|
+
.join("\n")
|
|
103
|
+
.trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function renderToolResult(
|
|
107
|
+
tool: RegisteredToolLike | undefined,
|
|
108
|
+
input:
|
|
109
|
+
| string
|
|
110
|
+
| {
|
|
111
|
+
text: string;
|
|
112
|
+
details?: unknown;
|
|
113
|
+
expanded?: boolean;
|
|
114
|
+
isPartial?: boolean;
|
|
115
|
+
isError?: boolean;
|
|
116
|
+
},
|
|
117
|
+
): string {
|
|
118
|
+
assert.ok(tool?.renderResult, `expected renderResult for tool '${tool?.name ?? "unknown"}'`);
|
|
119
|
+
const payload = typeof input === "string" ? { text: input } : input;
|
|
120
|
+
return normalizeRenderedText(
|
|
121
|
+
tool.renderResult(
|
|
122
|
+
{
|
|
123
|
+
content: [{ type: "text", text: payload.text }],
|
|
124
|
+
details: payload.details ?? {},
|
|
125
|
+
isError: payload.isError ?? false,
|
|
126
|
+
},
|
|
127
|
+
{ isPartial: payload.isPartial ?? false, expanded: payload.expanded ?? false },
|
|
128
|
+
createTheme(),
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function renderToolCall(
|
|
134
|
+
tool: RegisteredToolLike | undefined,
|
|
135
|
+
args: { command: string; timeout?: number },
|
|
136
|
+
contextOverrides: Partial<RenderCallContextLike> = {},
|
|
137
|
+
): { output: string; component: RenderComponentLike; context: RenderCallContextLike } {
|
|
138
|
+
assert.ok(tool?.renderCall, `expected renderCall for tool '${tool?.name ?? "unknown"}'`);
|
|
139
|
+
const context: RenderCallContextLike = {
|
|
140
|
+
lastComponent: contextOverrides.lastComponent,
|
|
141
|
+
state: contextOverrides.state ?? {},
|
|
142
|
+
invalidate: contextOverrides.invalidate ?? (() => {}),
|
|
143
|
+
executionStarted: contextOverrides.executionStarted ?? false,
|
|
144
|
+
isPartial: contextOverrides.isPartial ?? false,
|
|
145
|
+
};
|
|
146
|
+
const component = tool.renderCall(args, createTheme(), context);
|
|
147
|
+
return {
|
|
148
|
+
output: normalizeRenderedText(component),
|
|
149
|
+
component,
|
|
150
|
+
context,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
test("current local-style config keeps read/search/MCP output modes distinct", async () => {
|
|
155
|
+
const config = buildConfig({
|
|
156
|
+
readOutputMode: "summary",
|
|
157
|
+
searchOutputMode: "count",
|
|
158
|
+
mcpOutputMode: "summary",
|
|
159
|
+
});
|
|
160
|
+
const { api, registeredTools, runtimeTools, eventHandlers } = createExtensionApiStub([
|
|
161
|
+
{
|
|
162
|
+
name: "mcp",
|
|
163
|
+
description: "Unified MCP gateway for status, discovery, reconnects, and proxy tool calls.",
|
|
164
|
+
parameters: {},
|
|
165
|
+
execute(): void {
|
|
166
|
+
// No-op test stub.
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
registerToolDisplayOverrides(api, () => config);
|
|
172
|
+
await runLifecycle(eventHandlers);
|
|
173
|
+
|
|
174
|
+
const registeredNames = new Set(registeredTools.map((tool) => tool.name));
|
|
175
|
+
const mcpTool = runtimeTools.find((tool) => tool.name === "mcp");
|
|
176
|
+
assert.ok(registeredNames.has("read"));
|
|
177
|
+
assert.ok(registeredNames.has("grep"));
|
|
178
|
+
assert.ok(registeredNames.has("find"));
|
|
179
|
+
assert.ok(registeredNames.has("ls"));
|
|
180
|
+
assert.ok(registeredNames.has("bash"));
|
|
181
|
+
assert.ok(registeredNames.has("edit"));
|
|
182
|
+
assert.ok(registeredNames.has("write"));
|
|
183
|
+
assert.ok(mcpTool?.renderResult);
|
|
184
|
+
|
|
185
|
+
assert.equal(
|
|
186
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "read"), "alpha\nbeta\n"),
|
|
187
|
+
"↳ loaded 2 lines • Ctrl+O to expand",
|
|
188
|
+
);
|
|
189
|
+
assert.equal(
|
|
190
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "grep"), "a.txt:1\nb.txt:2\n"),
|
|
191
|
+
"↳ 2 matches returned • Ctrl+O to expand",
|
|
192
|
+
);
|
|
193
|
+
assert.equal(
|
|
194
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "find"), "a.txt\nb.txt\n"),
|
|
195
|
+
"↳ 2 results returned • Ctrl+O to expand",
|
|
196
|
+
);
|
|
197
|
+
assert.equal(
|
|
198
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "ls"), "a.txt\nb.txt\n"),
|
|
199
|
+
"↳ 2 entries returned • Ctrl+O to expand",
|
|
200
|
+
);
|
|
201
|
+
assert.equal(
|
|
202
|
+
renderToolResult(mcpTool, "one\ntwo\n"),
|
|
203
|
+
"↳ 2 lines returned • Ctrl+O to expand",
|
|
204
|
+
);
|
|
205
|
+
assert.equal(
|
|
206
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "read"), {
|
|
207
|
+
text: "alpha\nbeta\n",
|
|
208
|
+
expanded: true,
|
|
209
|
+
}),
|
|
210
|
+
"alpha\nbeta",
|
|
211
|
+
);
|
|
212
|
+
assert.equal(
|
|
213
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "grep"), {
|
|
214
|
+
text: "a.txt:1\nb.txt:2\n",
|
|
215
|
+
expanded: true,
|
|
216
|
+
}),
|
|
217
|
+
"a.txt:1\nb.txt:2",
|
|
218
|
+
);
|
|
219
|
+
assert.equal(
|
|
220
|
+
renderToolResult(mcpTool, {
|
|
221
|
+
text: "one\ntwo\n",
|
|
222
|
+
expanded: true,
|
|
223
|
+
}),
|
|
224
|
+
"one\ntwo",
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("registerToolDisplayOverrides preserves MCP prompt metadata for proxy and direct wrappers", async () => {
|
|
229
|
+
const { api, runtimeTools, eventHandlers } = createExtensionApiStub([
|
|
230
|
+
{
|
|
231
|
+
name: "mcp",
|
|
232
|
+
description: "Unified MCP gateway for status, discovery, reconnects, and proxy tool calls.",
|
|
233
|
+
parameters: {},
|
|
234
|
+
execute(): void {
|
|
235
|
+
// No-op test stub.
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "exa_web_search_exa",
|
|
240
|
+
label: "MCP exa:web_search_exa",
|
|
241
|
+
description:
|
|
242
|
+
"Search the web for current information. Direct MCP wrapper for 'exa:web_search_exa'. Common args: query*.",
|
|
243
|
+
parameters: {},
|
|
244
|
+
execute(): void {
|
|
245
|
+
// No-op test stub.
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
registerToolDisplayOverrides(api, () => DEFAULT_TOOL_DISPLAY_CONFIG);
|
|
251
|
+
await runLifecycle(eventHandlers);
|
|
252
|
+
|
|
253
|
+
const byName = new Map(runtimeTools.map((tool) => [tool.name, tool]));
|
|
254
|
+
assert.equal(
|
|
255
|
+
byName.get("mcp")?.promptSnippet,
|
|
256
|
+
"Discover, inspect, and call MCP tools across configured servers",
|
|
257
|
+
);
|
|
258
|
+
assert.deepEqual(byName.get("mcp")?.promptGuidelines, [
|
|
259
|
+
"Use mcp for MCP discovery first: search by capability, describe one exact tool, then call it.",
|
|
260
|
+
]);
|
|
261
|
+
assert.equal(
|
|
262
|
+
byName.get("exa_web_search_exa")?.promptSnippet,
|
|
263
|
+
"Search the web for current information",
|
|
264
|
+
);
|
|
265
|
+
assert.equal(byName.get("exa_web_search_exa")?.promptGuidelines, undefined);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("read-only ownership keeps summary line counts confined to read", async () => {
|
|
269
|
+
const config = buildConfig({
|
|
270
|
+
registerToolOverrides: {
|
|
271
|
+
read: true,
|
|
272
|
+
grep: false,
|
|
273
|
+
find: false,
|
|
274
|
+
ls: false,
|
|
275
|
+
bash: false,
|
|
276
|
+
edit: false,
|
|
277
|
+
write: false,
|
|
278
|
+
},
|
|
279
|
+
readOutputMode: "summary",
|
|
280
|
+
});
|
|
281
|
+
const { api, registeredTools, eventHandlers } = createExtensionApiStub();
|
|
282
|
+
|
|
283
|
+
registerToolDisplayOverrides(api, () => config);
|
|
284
|
+
await eventHandlers.before_agent_start?.();
|
|
285
|
+
|
|
286
|
+
assert.deepEqual(
|
|
287
|
+
registeredTools.map((tool) => tool.name),
|
|
288
|
+
["read"],
|
|
289
|
+
);
|
|
290
|
+
assert.equal(
|
|
291
|
+
renderToolResult(registeredTools[0], "single line\n"),
|
|
292
|
+
"↳ loaded 1 line • Ctrl+O to expand",
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("showTruncationHints=false suppresses backend truncation summaries across read/search/MCP modes", async () => {
|
|
297
|
+
const config = buildConfig({
|
|
298
|
+
readOutputMode: "summary",
|
|
299
|
+
searchOutputMode: "count",
|
|
300
|
+
mcpOutputMode: "summary",
|
|
301
|
+
showTruncationHints: false,
|
|
302
|
+
});
|
|
303
|
+
const { api, registeredTools, runtimeTools, eventHandlers } = createExtensionApiStub([
|
|
304
|
+
{
|
|
305
|
+
name: "mcp",
|
|
306
|
+
description: "Unified MCP gateway for status, discovery, reconnects, and proxy tool calls.",
|
|
307
|
+
parameters: {},
|
|
308
|
+
execute(): void {
|
|
309
|
+
// No-op test stub.
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
registerToolDisplayOverrides(api, () => config);
|
|
315
|
+
await runLifecycle(eventHandlers);
|
|
316
|
+
const mcpTool = runtimeTools.find((tool) => tool.name === "mcp");
|
|
317
|
+
|
|
318
|
+
assert.equal(
|
|
319
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "read"), {
|
|
320
|
+
text: "alpha\n",
|
|
321
|
+
details: { truncation: { truncated: true } },
|
|
322
|
+
}),
|
|
323
|
+
"↳ loaded 1 line • Ctrl+O to expand",
|
|
324
|
+
);
|
|
325
|
+
assert.equal(
|
|
326
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "grep"), {
|
|
327
|
+
text: "a.txt:1\n",
|
|
328
|
+
details: { truncation: { truncated: true } },
|
|
329
|
+
}),
|
|
330
|
+
"↳ 1 match returned • Ctrl+O to expand",
|
|
331
|
+
);
|
|
332
|
+
assert.equal(
|
|
333
|
+
renderToolResult(mcpTool, {
|
|
334
|
+
text: "alpha\n",
|
|
335
|
+
details: { truncation: { truncated: true } },
|
|
336
|
+
}),
|
|
337
|
+
"↳ 1 line returned • Ctrl+O to expand",
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("showRtkCompactionHints stays independent from showTruncationHints for summary modes", async () => {
|
|
342
|
+
const config = buildConfig({
|
|
343
|
+
readOutputMode: "summary",
|
|
344
|
+
searchOutputMode: "count",
|
|
345
|
+
mcpOutputMode: "summary",
|
|
346
|
+
showTruncationHints: false,
|
|
347
|
+
showRtkCompactionHints: true,
|
|
348
|
+
});
|
|
349
|
+
const { api, registeredTools, runtimeTools, eventHandlers } = createExtensionApiStub([
|
|
350
|
+
{
|
|
351
|
+
name: "mcp",
|
|
352
|
+
description: "Unified MCP gateway for status, discovery, reconnects, and proxy tool calls.",
|
|
353
|
+
parameters: {},
|
|
354
|
+
execute(): void {
|
|
355
|
+
// No-op test stub.
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
]);
|
|
359
|
+
const rtkDetails = {
|
|
360
|
+
rtkCompaction: {
|
|
361
|
+
applied: true,
|
|
362
|
+
techniques: ["trimmed context"],
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
registerToolDisplayOverrides(api, () => config);
|
|
367
|
+
await runLifecycle(eventHandlers);
|
|
368
|
+
const mcpTool = runtimeTools.find((tool) => tool.name === "mcp");
|
|
369
|
+
|
|
370
|
+
assert.match(
|
|
371
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "read"), {
|
|
372
|
+
text: "alpha\n",
|
|
373
|
+
details: rtkDetails,
|
|
374
|
+
}),
|
|
375
|
+
/compacted by RTK • trimmed context/,
|
|
376
|
+
);
|
|
377
|
+
assert.match(
|
|
378
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "grep"), {
|
|
379
|
+
text: "a.txt:1\n",
|
|
380
|
+
details: rtkDetails,
|
|
381
|
+
}),
|
|
382
|
+
/compacted by RTK • trimmed context/,
|
|
383
|
+
);
|
|
384
|
+
assert.match(
|
|
385
|
+
renderToolResult(mcpTool, {
|
|
386
|
+
text: "alpha\n",
|
|
387
|
+
details: rtkDetails,
|
|
388
|
+
}),
|
|
389
|
+
/compacted by RTK • trimmed context/,
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("showRtkCompactionHints stays independent from showTruncationHints for preview modes", async () => {
|
|
394
|
+
const config = buildConfig({
|
|
395
|
+
readOutputMode: "preview",
|
|
396
|
+
searchOutputMode: "preview",
|
|
397
|
+
mcpOutputMode: "preview",
|
|
398
|
+
previewLines: 1,
|
|
399
|
+
showTruncationHints: false,
|
|
400
|
+
showRtkCompactionHints: true,
|
|
401
|
+
});
|
|
402
|
+
const { api, registeredTools, runtimeTools, eventHandlers } = createExtensionApiStub([
|
|
403
|
+
{
|
|
404
|
+
name: "mcp",
|
|
405
|
+
description: "Unified MCP gateway for status, discovery, reconnects, and proxy tool calls.",
|
|
406
|
+
parameters: {},
|
|
407
|
+
execute(): void {
|
|
408
|
+
// No-op test stub.
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
]);
|
|
412
|
+
const rtkDetails = {
|
|
413
|
+
rtkCompaction: {
|
|
414
|
+
applied: true,
|
|
415
|
+
techniques: ["trimmed context"],
|
|
416
|
+
originalLineCount: 10,
|
|
417
|
+
compactedLineCount: 1,
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
registerToolDisplayOverrides(api, () => config);
|
|
422
|
+
await runLifecycle(eventHandlers);
|
|
423
|
+
const mcpTool = runtimeTools.find((tool) => tool.name === "mcp");
|
|
424
|
+
|
|
425
|
+
assert.match(
|
|
426
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "read"), {
|
|
427
|
+
text: "alpha\nbeta\n",
|
|
428
|
+
details: rtkDetails,
|
|
429
|
+
}),
|
|
430
|
+
/compacted by RTK: trimmed context • 1\/10 lines kept/,
|
|
431
|
+
);
|
|
432
|
+
assert.match(
|
|
433
|
+
renderToolResult(registeredTools.find((tool) => tool.name === "grep"), {
|
|
434
|
+
text: "a.txt:1\nb.txt:2\n",
|
|
435
|
+
details: rtkDetails,
|
|
436
|
+
}),
|
|
437
|
+
/compacted by RTK: trimmed context • 1\/10 lines kept/,
|
|
438
|
+
);
|
|
439
|
+
assert.match(
|
|
440
|
+
renderToolResult(mcpTool, {
|
|
441
|
+
text: "alpha\nbeta\n",
|
|
442
|
+
details: rtkDetails,
|
|
443
|
+
}),
|
|
444
|
+
/compacted by RTK: trimmed context • 1\/10 lines kept/,
|
|
445
|
+
);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
test("bash output modes stay distinct across opencode, summary, and preview", async () => {
|
|
449
|
+
const output = "alpha\nbeta\ngamma\n";
|
|
450
|
+
|
|
451
|
+
const opencodeConfig = buildConfig({
|
|
452
|
+
bashOutputMode: "opencode",
|
|
453
|
+
bashCollapsedLines: 1,
|
|
454
|
+
});
|
|
455
|
+
const opencodeStub = createExtensionApiStub();
|
|
456
|
+
registerToolDisplayOverrides(opencodeStub.api, () => opencodeConfig);
|
|
457
|
+
await opencodeStub.eventHandlers.before_agent_start?.();
|
|
458
|
+
assert.equal(
|
|
459
|
+
renderToolResult(opencodeStub.registeredTools.find((tool) => tool.name === "bash"), output),
|
|
460
|
+
"alpha\n... (2 more lines • Ctrl+O to expand)",
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
const summaryConfig = buildConfig({
|
|
464
|
+
bashOutputMode: "summary",
|
|
465
|
+
bashCollapsedLines: 1,
|
|
466
|
+
});
|
|
467
|
+
const summaryStub = createExtensionApiStub();
|
|
468
|
+
registerToolDisplayOverrides(summaryStub.api, () => summaryConfig);
|
|
469
|
+
await summaryStub.eventHandlers.before_agent_start?.();
|
|
470
|
+
assert.equal(
|
|
471
|
+
renderToolResult(summaryStub.registeredTools.find((tool) => tool.name === "bash"), output),
|
|
472
|
+
"↳ 3 lines returned • Ctrl+O to expand",
|
|
473
|
+
);
|
|
474
|
+
assert.equal(
|
|
475
|
+
renderToolResult(summaryStub.registeredTools.find((tool) => tool.name === "bash"), {
|
|
476
|
+
text: output,
|
|
477
|
+
expanded: true,
|
|
478
|
+
}),
|
|
479
|
+
"alpha\nbeta\ngamma",
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
const previewConfig = buildConfig({
|
|
483
|
+
bashOutputMode: "preview",
|
|
484
|
+
previewLines: 2,
|
|
485
|
+
bashCollapsedLines: 1,
|
|
486
|
+
});
|
|
487
|
+
const previewStub = createExtensionApiStub();
|
|
488
|
+
registerToolDisplayOverrides(previewStub.api, () => previewConfig);
|
|
489
|
+
await previewStub.eventHandlers.before_agent_start?.();
|
|
490
|
+
assert.equal(
|
|
491
|
+
renderToolResult(previewStub.registeredTools.find((tool) => tool.name === "bash"), output),
|
|
492
|
+
"alpha\nbeta\n... (1 more line • Ctrl+O to expand)",
|
|
493
|
+
);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test("bash call spinner appears only while execution is active", async () => {
|
|
497
|
+
const config = buildConfig({
|
|
498
|
+
bashOutputMode: "summary",
|
|
499
|
+
});
|
|
500
|
+
const { api, registeredTools, eventHandlers } = createExtensionApiStub();
|
|
501
|
+
registerToolDisplayOverrides(api, () => config);
|
|
502
|
+
await eventHandlers.before_agent_start?.();
|
|
503
|
+
|
|
504
|
+
const bashTool = registeredTools.find((tool) => tool.name === "bash");
|
|
505
|
+
const idle = renderToolCall(bashTool, { command: "npm test" });
|
|
506
|
+
assert.equal(idle.output, "$ npm test");
|
|
507
|
+
|
|
508
|
+
let invalidateCount = 0;
|
|
509
|
+
const running = renderToolCall(
|
|
510
|
+
bashTool,
|
|
511
|
+
{ command: "npm test" },
|
|
512
|
+
{
|
|
513
|
+
state: {},
|
|
514
|
+
executionStarted: true,
|
|
515
|
+
isPartial: true,
|
|
516
|
+
invalidate: () => {
|
|
517
|
+
invalidateCount++;
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
);
|
|
521
|
+
assert.match(running.output, /^⠋ \$ npm test · 0s$/);
|
|
522
|
+
|
|
523
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
524
|
+
const animatedFrame = normalizeRenderedText(running.component);
|
|
525
|
+
assert.notEqual(animatedFrame, running.output);
|
|
526
|
+
assert.match(animatedFrame, /^⠙ \$ npm test · 0s$/);
|
|
527
|
+
assert.ok(invalidateCount > 0);
|
|
528
|
+
|
|
529
|
+
const complete = renderToolCall(
|
|
530
|
+
bashTool,
|
|
531
|
+
{ command: "npm test" },
|
|
532
|
+
{
|
|
533
|
+
state: running.context.state,
|
|
534
|
+
lastComponent: running.component,
|
|
535
|
+
executionStarted: true,
|
|
536
|
+
isPartial: false,
|
|
537
|
+
},
|
|
538
|
+
);
|
|
539
|
+
assert.equal(complete.output, "$ npm test");
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
test("bash render keeps the running result area empty until output exists", async () => {
|
|
543
|
+
const config = buildConfig({
|
|
544
|
+
bashOutputMode: "summary",
|
|
545
|
+
});
|
|
546
|
+
const { api, registeredTools, eventHandlers } = createExtensionApiStub();
|
|
547
|
+
registerToolDisplayOverrides(api, () => config);
|
|
548
|
+
await eventHandlers.before_agent_start?.();
|
|
549
|
+
|
|
550
|
+
const bashTool = registeredTools.find((tool) => tool.name === "bash");
|
|
551
|
+
assert.equal(
|
|
552
|
+
renderToolResult(bashTool, { text: "", isPartial: true }),
|
|
553
|
+
"",
|
|
554
|
+
);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test("bash render shows live partial output once streaming begins", async () => {
|
|
558
|
+
const config = buildConfig({
|
|
559
|
+
bashOutputMode: "summary",
|
|
560
|
+
previewLines: 2,
|
|
561
|
+
});
|
|
562
|
+
const { api, registeredTools, eventHandlers } = createExtensionApiStub();
|
|
563
|
+
registerToolDisplayOverrides(api, () => config);
|
|
564
|
+
await eventHandlers.before_agent_start?.();
|
|
565
|
+
|
|
566
|
+
const bashTool = registeredTools.find((tool) => tool.name === "bash");
|
|
567
|
+
assert.equal(
|
|
568
|
+
renderToolResult(bashTool, {
|
|
569
|
+
text: "alpha\nbeta\ngamma\n",
|
|
570
|
+
isPartial: true,
|
|
571
|
+
}),
|
|
572
|
+
"alpha\nbeta\n... (1 more line • Ctrl+O to expand)",
|
|
573
|
+
);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test("bash live partial output respects opencode collapse settings", async () => {
|
|
577
|
+
const config = buildConfig({
|
|
578
|
+
bashOutputMode: "opencode",
|
|
579
|
+
bashCollapsedLines: 1,
|
|
580
|
+
previewLines: 4,
|
|
581
|
+
});
|
|
582
|
+
const { api, registeredTools, eventHandlers } = createExtensionApiStub();
|
|
583
|
+
registerToolDisplayOverrides(api, () => config);
|
|
584
|
+
await eventHandlers.before_agent_start?.();
|
|
585
|
+
|
|
586
|
+
const bashTool = registeredTools.find((tool) => tool.name === "bash");
|
|
587
|
+
assert.equal(
|
|
588
|
+
renderToolResult(bashTool, {
|
|
589
|
+
text: "alpha\nbeta\ngamma\n",
|
|
590
|
+
isPartial: true,
|
|
591
|
+
}),
|
|
592
|
+
"alpha\n... (2 more lines • Ctrl+O to expand)",
|
|
593
|
+
);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
test("bash errors render with an explicit failure header and preview", async () => {
|
|
597
|
+
const config = buildConfig({
|
|
598
|
+
bashOutputMode: "summary",
|
|
599
|
+
previewLines: 2,
|
|
600
|
+
});
|
|
601
|
+
const { api, registeredTools, eventHandlers } = createExtensionApiStub();
|
|
602
|
+
registerToolDisplayOverrides(api, () => config);
|
|
603
|
+
await eventHandlers.before_agent_start?.();
|
|
604
|
+
|
|
605
|
+
const bashTool = registeredTools.find((tool) => tool.name === "bash");
|
|
606
|
+
assert.equal(
|
|
607
|
+
renderToolResult(bashTool, {
|
|
608
|
+
text: "npm ERR! missing script: test\nSee npm help run-script\n",
|
|
609
|
+
isError: true,
|
|
610
|
+
}),
|
|
611
|
+
"↳ command failed\nnpm ERR! missing script: test\nSee npm help run-script",
|
|
612
|
+
);
|
|
613
|
+
});
|