pi-compact-tools 0.6.0 → 0.6.3

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.
@@ -1,314 +1,322 @@
1
- import type {
2
- AgentToolResult,
3
- BashToolDetails,
4
- BashToolInput,
5
- ExtensionAPI,
6
- ExtensionContext,
7
- Theme,
8
- ToolDefinition,
9
- ToolRenderResultOptions,
10
- } from "@earendil-works/pi-coding-agent";
11
- import {
12
- createBashToolDefinition,
13
- createEditToolDefinition,
14
- createFindToolDefinition,
15
- createGrepToolDefinition,
16
- createLsToolDefinition,
17
- createPowerShellToolDefinition,
18
- createReadToolDefinition,
19
- createWriteToolDefinition,
20
- } from "@earendil-works/pi-coding-agent";
21
- import type { Component, Container } from "@earendil-works/pi-tui";
22
- import {
23
- classifyCallStatus,
24
- formatDurationMs,
25
- normalizeLineEndings,
26
- type RowStatus,
27
- } from "./compact-tools-core.ts";
28
- import { isFullscreenMode, loadConfig } from "./compact-tools-config.ts";
29
- import { classifyToggleInput } from "./compact-tools-input.ts";
30
- import {
31
- formatReadResultSummary,
32
- getArgumentDetails,
33
- getCallDetails,
34
- getFileOutput,
35
- getTextResult,
36
- } from "./compact-tools-invocation.ts";
37
- import {
38
- CachedContainer,
39
- prefixedText,
40
- renderArguments,
41
- renderOutput,
42
- renderToolCall,
43
- styleMultiline,
44
- wrapEditResult,
45
- } from "./compact-tools-layout.ts";
46
- import { ToolRuntime } from "./compact-tools-runtime.ts";
47
- import { ThinkingCycleController } from "./compact-tools-thinking.ts";
48
- import type {
49
- BuiltInDefinition,
50
- CompactToolName,
51
- RenderContext,
52
- RowState,
53
- ShellToolName,
54
- ToolArgs,
55
- } from "./compact-tools-types.ts";
56
-
57
- const runtime = new ToolRuntime();
58
- const registeredTools = new Set<CompactToolName>();
59
- let unsubscribeTerminalInput: (() => void) | undefined;
60
-
61
- function bindTerminalInput(ctx: ExtensionContext): void {
62
- unsubscribeTerminalInput?.();
63
- unsubscribeTerminalInput = ctx.ui.onTerminalInput((data) => {
64
- const input = classifyToggleInput(data);
65
- if (!input) return undefined;
66
- if (input === "release") return { consume: true };
67
- const result = runtime.toggleTrackedRows();
68
- if (result) ctx.ui.notify(`Compact tool rows: ${result}`, "info");
69
- return { consume: true };
70
- });
71
- }
72
-
73
- function spinnerTone(frameIndex: number, frameCount: number): "muted" | "dim" | "border" {
74
- if (frameCount < 3) return "muted";
75
- const distanceFromCenter = Math.abs(frameIndex / (frameCount - 1) - 0.5) * 2;
76
- if (distanceFromCenter < 0.34) return "border";
77
- return distanceFromCenter < 0.75 ? "dim" : "muted";
78
- }
79
-
80
- function callStatus(ctx: RenderContext, state: RowState): RowStatus {
81
- return classifyCallStatus(ctx.isError, ctx.executionStarted, state.endedAt !== undefined);
82
- }
83
-
84
- function renderIndicator(theme: Theme, state: RowState, status: RowStatus): string {
85
- if (status === "error") return theme.fg("error", "●");
86
- if (status === "success") return theme.fg("success", "●");
87
- const frames = runtime.config.spinner.frames;
88
- const frameIndex = state.frame ?? 0;
89
- return theme.fg(spinnerTone(frameIndex, frames.length), frames[frameIndex] ?? frames[0] ?? "◐");
90
- }
91
-
92
- function formatDuration(state: RowState): string | undefined {
93
- if (state.startedAt === undefined) return undefined;
94
- return formatDurationMs((state.endedAt ?? Date.now()) - state.startedAt);
95
- }
96
-
97
- function renderControls(
98
- theme: Theme,
99
- state: RowState,
100
- running: boolean,
101
- isError: boolean,
102
- summary?: string,
103
- ): Component {
104
- const duration = running && !runtime.animatesRows ? undefined : formatDuration(state);
105
- const status = running
106
- ? (duration ?? "Running")
107
- : `${isError ? "Failed" : "Done"}${duration ? ` in ${duration}` : ""}`;
108
- let details = theme.fg("borderAccent", status);
109
- if (summary) details += theme.fg("borderAccent", ` • ${summary}`);
110
- if (state.hasResult) {
111
- const clickAction = state.expanded ? "to hide" : "for result";
112
- details += theme.fg(
113
- "borderAccent",
114
- `, ${theme.italic("ctrl+o")} toggle all • ${theme.italic("click")} ${clickAction}`,
115
- );
116
- }
117
- return prefixedText(details, theme.fg("border", " └─ "), " ");
118
- }
119
-
120
- function renderCallTitle(name: string, theme: Theme, ctx: RenderContext, state: RowState): string {
121
- return `${renderIndicator(theme, state, callStatus(ctx, state))} ${theme.fg("toolTitle", theme.bold(name))}`;
122
- }
123
-
124
- function renderFileCall(
125
- definition: BuiltInDefinition,
126
- args: ToolArgs,
127
- theme: Theme,
128
- ctx: RenderContext,
129
- ): Container {
130
- const name = definition.name as CompactToolName;
131
- const state = runtime.syncRow(ctx, ctx.state.endedAt === undefined);
132
- runtime.track(ctx, name, state);
133
- runtime.syncExpansion(state, ctx.expanded, name);
134
- const callDetails = getCallDetails(name, args);
135
- const details = callDetails
136
- ? styleMultiline(callDetails, (line) => theme.fg("toolOutput", line))
137
- : undefined;
138
- const container = new CachedContainer();
139
- container.addChild(renderToolCall(renderCallTitle(name, theme, ctx, state), details, theme));
140
- const arguments_ = getArgumentDetails(name, args);
141
- if (Object.keys(arguments_).length > 0) container.addChild(renderArguments(arguments_, theme));
142
- return container;
143
- }
144
-
145
- function updateEditResult(
146
- definition: BuiltInDefinition,
147
- result: AgentToolResult<unknown>,
148
- options: ToolRenderResultOptions,
149
- theme: Theme,
150
- ctx: RenderContext,
151
- state: RowState,
152
- ): void {
153
- if (definition.name !== "edit" || !definition.renderResult) return;
154
- state.originalResultComponent = definition.renderResult(
155
- result,
156
- { ...options, expanded: state.expanded ?? false },
157
- theme,
158
- { ...ctx, state: {}, lastComponent: state.originalResultComponent },
159
- );
160
- }
161
-
162
- function appendFileResult(
163
- container: Container,
164
- definition: BuiltInDefinition,
165
- state: RowState,
166
- output: string,
167
- theme: Theme,
168
- isError: boolean,
169
- ): void {
170
- if (!state.expanded) return;
171
- if (definition.name === "edit") {
172
- if (state.originalResultComponent) container.addChild(wrapEditResult(state.originalResultComponent, theme));
173
- return;
174
- }
175
- const component = renderOutput(output, theme, isError);
176
- if (component) container.addChild(component);
177
- }
178
-
179
- function renderFileResult(
180
- definition: BuiltInDefinition,
181
- result: AgentToolResult<unknown>,
182
- options: ToolRenderResultOptions,
183
- theme: Theme,
184
- ctx: RenderContext,
185
- ): Container {
186
- const name = definition.name as CompactToolName;
187
- const state = runtime.syncRow(ctx, options.isPartial, !options.isPartial);
188
- runtime.track(ctx, name, state);
189
- const output = getFileOutput(name, ctx.args, result, ctx.isError);
190
- runtime.setResultAvailable(state, name, name === "edit" || output.length > 0);
191
- updateEditResult(definition, result, options, theme, ctx, state);
192
- const container = new CachedContainer();
193
- appendFileResult(container, definition, state, output, theme, ctx.isError);
194
- const summary = name === "read" && !ctx.isError ? formatReadResultSummary(result) : undefined;
195
- container.addChild(renderControls(theme, state, options.isPartial, ctx.isError, summary));
196
- return container;
197
- }
198
-
199
- function renderShellCall(
200
- name: ShellToolName,
201
- args: BashToolInput,
202
- theme: Theme,
203
- ctx: RenderContext<BashToolInput>,
204
- ): Component {
205
- const state = runtime.syncRow(ctx, ctx.state.endedAt === undefined);
206
- runtime.track(ctx, name, state);
207
- runtime.syncExpansion(state, ctx.expanded, name);
208
- const command = normalizeLineEndings(args.command ?? "") || "…";
209
- const details = styleMultiline(command, (line) => theme.fg("toolOutput", line));
210
- return renderToolCall(renderCallTitle(name, theme, ctx, state), details, theme);
211
- }
212
-
213
- function renderShellResult(
214
- name: ShellToolName,
215
- result: AgentToolResult<BashToolDetails | undefined>,
216
- options: ToolRenderResultOptions,
217
- theme: Theme,
218
- ctx: RenderContext<BashToolInput>,
219
- ): Component {
220
- const state = runtime.syncRow(ctx, options.isPartial, !options.isPartial);
221
- runtime.track(ctx, name, state);
222
- const output = getTextResult(result);
223
- runtime.setResultAvailable(state, name, output.length > 0);
224
- if (!state.expanded) return renderControls(theme, state, options.isPartial, ctx.isError);
225
- const container = new CachedContainer();
226
- const component = renderOutput(output, theme, ctx.isError);
227
- if (component) container.addChild(component);
228
- container.addChild(renderControls(theme, state, options.isPartial, ctx.isError));
229
- return container;
230
- }
231
-
232
- const toolFactories: Record<CompactToolName, (cwd: string) => BuiltInDefinition> = {
233
- read: createReadToolDefinition,
234
- write: createWriteToolDefinition,
235
- edit: createEditToolDefinition,
236
- bash: createBashToolDefinition,
237
- powershell: createPowerShellToolDefinition,
238
- grep: createGrepToolDefinition,
239
- find: createFindToolDefinition,
240
- ls: createLsToolDefinition,
241
- };
242
-
243
- function createBuiltInDefinition(name: CompactToolName, cwd: string): BuiltInDefinition {
244
- return toolFactories[name](cwd);
245
- }
246
-
247
- function registerFileTool(pi: ExtensionAPI, definition: BuiltInDefinition): void {
248
- pi.registerTool({
249
- ...definition,
250
- execute: runtime.createTimedExecute(definition),
251
- renderShell: "self",
252
- renderCall: (args: ToolArgs, theme: Theme, ctx: RenderContext) => renderFileCall(definition, args, theme, ctx),
253
- renderResult: (
254
- result: AgentToolResult<unknown>,
255
- options: ToolRenderResultOptions,
256
- theme: Theme,
257
- ctx: RenderContext,
258
- ) => renderFileResult(definition, result, options, theme, ctx),
259
- } as ToolDefinition<any, any, RowState>);
260
- }
261
-
262
- function registerShellTool(pi: ExtensionAPI, definition: BuiltInDefinition): void {
263
- const name = definition.name as ShellToolName;
264
- pi.registerTool({
265
- ...definition,
266
- execute: runtime.createTimedExecute(definition),
267
- renderShell: "self",
268
- renderCall: (args: BashToolInput, theme: Theme, ctx: RenderContext<BashToolInput>) =>
269
- renderShellCall(name, args, theme, ctx),
270
- renderResult: (
271
- result: AgentToolResult<BashToolDetails | undefined>,
272
- options: ToolRenderResultOptions,
273
- theme: Theme,
274
- ctx: RenderContext<BashToolInput>,
275
- ) => renderShellResult(name, result, options, theme, ctx),
276
- } as ToolDefinition<any, BashToolDetails | undefined, RowState>);
277
- }
278
-
279
- function registerTools(pi: ExtensionAPI, cwd: string): void {
280
- const enabledTools = new Set(runtime.config.tools);
281
- for (const name of registeredTools) {
282
- if (!enabledTools.has(name)) pi.registerTool(createBuiltInDefinition(name, cwd));
283
- }
284
- registeredTools.clear();
285
- for (const name of runtime.config.tools) {
286
- const definition = createBuiltInDefinition(name, cwd);
287
- if (name === "bash" || name === "powershell") registerShellTool(pi, definition);
288
- else registerFileTool(pi, definition);
289
- registeredTools.add(name);
290
- }
291
- }
292
-
293
- function configure(pi: ExtensionAPI, cwd?: string, projectTrusted = false): void {
294
- runtime.configure(loadConfig(cwd, projectTrusted), isFullscreenMode());
295
- registerTools(pi, cwd ?? process.cwd());
296
- }
297
-
298
- export default function compactTools(pi: ExtensionAPI): void {
299
- const thinkingCycle = new ThinkingCycleController(pi);
300
- configure(pi);
301
- pi.on("session_start", (event, ctx) => {
302
- if (event.reason !== "reload") runtime.clearTimings();
303
- configure(pi, ctx.cwd, ctx.isProjectTrusted());
304
- bindTerminalInput(ctx);
305
- thinkingCycle.bind(ctx);
306
- });
307
- pi.on("resources_discover", (_event, ctx) => configure(pi, ctx.cwd, ctx.isProjectTrusted()));
308
- pi.on("session_shutdown", (event) => {
309
- thinkingCycle.dispose();
310
- unsubscribeTerminalInput?.();
311
- unsubscribeTerminalInput = undefined;
312
- runtime.reset(event.reason !== "reload");
313
- });
314
- }
1
+ import type {
2
+ AgentToolResult,
3
+ BashToolDetails,
4
+ BashToolInput,
5
+ ExtensionAPI,
6
+ ExtensionContext,
7
+ Theme,
8
+ ToolDefinition,
9
+ ToolRenderResultOptions,
10
+ } from "@earendil-works/pi-coding-agent";
11
+ import {
12
+ createBashToolDefinition,
13
+ createEditToolDefinition,
14
+ createFindToolDefinition,
15
+ createGrepToolDefinition,
16
+ createLsToolDefinition,
17
+ createPowerShellToolDefinition,
18
+ createReadToolDefinition,
19
+ createWriteToolDefinition,
20
+ } from "@earendil-works/pi-coding-agent";
21
+ import type { Component, Container } from "@earendil-works/pi-tui";
22
+ import {
23
+ classifyCallStatus,
24
+ formatDurationMs,
25
+ normalizeLineEndings,
26
+ type RowStatus,
27
+ } from "./compact-tools-core.ts";
28
+ import { isFullscreenMode, loadConfig } from "./compact-tools-config.ts";
29
+ import { classifyToggleInput } from "./compact-tools-input.ts";
30
+ import {
31
+ formatResultLineSummary,
32
+ getArgumentDetails,
33
+ getCallDetails,
34
+ getFileOutput,
35
+ getTextResult,
36
+ } from "./compact-tools-invocation.ts";
37
+ import {
38
+ CachedContainer,
39
+ prefixedText,
40
+ renderArguments,
41
+ renderOutput,
42
+ renderToolCall,
43
+ styleMultiline,
44
+ wrapEditResult,
45
+ } from "./compact-tools-layout.ts";
46
+ import { ToolRuntime } from "./compact-tools-runtime.ts";
47
+ import { ThinkingCycleController } from "./compact-tools-thinking.ts";
48
+ import type {
49
+ BuiltInDefinition,
50
+ CompactToolName,
51
+ RenderContext,
52
+ RowState,
53
+ ShellToolName,
54
+ ToolArgs,
55
+ } from "./compact-tools-types.ts";
56
+
57
+ const runtime = new ToolRuntime();
58
+ const registeredTools = new Set<CompactToolName>();
59
+ let unsubscribeTerminalInput: (() => void) | undefined;
60
+
61
+ function bindTerminalInput(ctx: ExtensionContext): void {
62
+ unsubscribeTerminalInput?.();
63
+ unsubscribeTerminalInput = ctx.ui.onTerminalInput((data) => {
64
+ const input = classifyToggleInput(data);
65
+ if (!input) return undefined;
66
+ if (input === "release") return { consume: true };
67
+ runtime.toggleTrackedRows();
68
+ return { consume: true };
69
+ });
70
+ }
71
+
72
+ function spinnerTone(frameIndex: number, frameCount: number): "muted" | "dim" | "border" {
73
+ if (frameCount < 3) return "muted";
74
+ const distanceFromCenter = Math.abs(frameIndex / (frameCount - 1) - 0.5) * 2;
75
+ if (distanceFromCenter < 0.34) return "border";
76
+ return distanceFromCenter < 0.75 ? "dim" : "muted";
77
+ }
78
+
79
+ function callStatus(ctx: RenderContext, state: RowState): RowStatus {
80
+ return classifyCallStatus(ctx.isError, ctx.executionStarted, state.endedAt !== undefined);
81
+ }
82
+
83
+ function renderIndicator(theme: Theme, state: RowState, status: RowStatus): string {
84
+ if (status === "error") return theme.fg("error", "●");
85
+ if (status === "success") return theme.fg("success", "●");
86
+ const frames = runtime.config.spinner.frames;
87
+ const frameIndex = state.frame ?? 0;
88
+ return theme.fg(spinnerTone(frameIndex, frames.length), frames[frameIndex] ?? frames[0] ?? "◐");
89
+ }
90
+
91
+ function formatDuration(state: RowState): string | undefined {
92
+ if (state.startedAt === undefined) return undefined;
93
+ return formatDurationMs((state.endedAt ?? Date.now()) - state.startedAt);
94
+ }
95
+
96
+ function renderControls(
97
+ theme: Theme,
98
+ state: RowState,
99
+ running: boolean,
100
+ isError: boolean,
101
+ lineSummary?: string,
102
+ ): Component {
103
+ const duration = running && !runtime.animatesRows ? undefined : formatDuration(state);
104
+ const status = running
105
+ ? (duration ?? "Running")
106
+ : `${isError ? "Failed" : "Done"}${duration ? ` in ${duration}` : ""}`;
107
+ let details = theme.fg("borderAccent", status);
108
+ if (lineSummary && !running) details += theme.fg("borderAccent", ` (${lineSummary})`);
109
+ if (state.hasResult) {
110
+ const clickAction = state.expanded ? "to hide" : "for result";
111
+ details += theme.fg(
112
+ "borderAccent",
113
+ `, ${theme.italic("ctrl+o")} toggle all • ${theme.italic("click")} ${clickAction}`,
114
+ );
115
+ }
116
+ return prefixedText(details, theme.fg("border", " └─ "), " ");
117
+ }
118
+
119
+ function renderCallTitle(name: string, theme: Theme, ctx: RenderContext, state: RowState): string {
120
+ return `${renderIndicator(theme, state, callStatus(ctx, state))} ${theme.fg("toolTitle", theme.bold(name))}`;
121
+ }
122
+
123
+ function renderFileCall(
124
+ definition: BuiltInDefinition,
125
+ args: ToolArgs,
126
+ theme: Theme,
127
+ ctx: RenderContext,
128
+ ): Container {
129
+ const name = definition.name as CompactToolName;
130
+ const state = runtime.syncRow(ctx, ctx.state.endedAt === undefined);
131
+ runtime.track(ctx, name, state);
132
+ runtime.syncExpansion(state, ctx.expanded, name);
133
+ const callDetails = getCallDetails(name, args);
134
+ const details = callDetails
135
+ ? styleMultiline(callDetails, (line) => theme.fg("toolOutput", line))
136
+ : undefined;
137
+ const container = new CachedContainer();
138
+ container.addChild(renderToolCall(renderCallTitle(name, theme, ctx, state), details, theme));
139
+ const arguments_ = getArgumentDetails(name, args);
140
+ if (Object.keys(arguments_).length > 0) container.addChild(renderArguments(arguments_, theme));
141
+ return container;
142
+ }
143
+
144
+ function updateEditResult(
145
+ definition: BuiltInDefinition,
146
+ result: AgentToolResult<unknown>,
147
+ options: ToolRenderResultOptions,
148
+ theme: Theme,
149
+ ctx: RenderContext,
150
+ state: RowState,
151
+ ): void {
152
+ if (definition.name !== "edit" || !definition.renderResult) return;
153
+ state.originalResultComponent = definition.renderResult(
154
+ result,
155
+ { ...options, expanded: state.expanded ?? false },
156
+ theme,
157
+ { ...ctx, state: {}, lastComponent: state.originalResultComponent },
158
+ );
159
+ }
160
+
161
+ function appendFileResult(
162
+ container: Container,
163
+ definition: BuiltInDefinition,
164
+ state: RowState,
165
+ output: string,
166
+ theme: Theme,
167
+ isError: boolean,
168
+ ): void {
169
+ if (!state.expanded) return;
170
+ if (definition.name === "edit") {
171
+ if (state.originalResultComponent) container.addChild(wrapEditResult(state.originalResultComponent, theme));
172
+ return;
173
+ }
174
+ const component = renderOutput(output, theme, isError);
175
+ if (component) container.addChild(component);
176
+ }
177
+
178
+ function renderFileResult(
179
+ definition: BuiltInDefinition,
180
+ result: AgentToolResult<unknown>,
181
+ options: ToolRenderResultOptions,
182
+ theme: Theme,
183
+ ctx: RenderContext,
184
+ ): Container {
185
+ const name = definition.name as CompactToolName;
186
+ const state = runtime.syncRow(ctx, options.isPartial, !options.isPartial);
187
+ runtime.track(ctx, name, state);
188
+ const output = getFileOutput(name, ctx.args, result, ctx.isError);
189
+ runtime.setResultAvailable(state, name, name === "edit" || output.length > 0);
190
+ updateEditResult(definition, result, options, theme, ctx, state);
191
+ const container = new CachedContainer();
192
+ appendFileResult(container, definition, state, output, theme, ctx.isError);
193
+ if (!options.isPartial && !state.resultLineSummaryComputed) {
194
+ state.resultLineSummary = formatResultLineSummary(name, ctx.args, result, output);
195
+ state.resultLineSummaryComputed = true;
196
+ }
197
+ container.addChild(renderControls(theme, state, options.isPartial, ctx.isError, state.resultLineSummary));
198
+ return container;
199
+ }
200
+
201
+ function renderShellCall(
202
+ name: ShellToolName,
203
+ args: BashToolInput,
204
+ theme: Theme,
205
+ ctx: RenderContext<BashToolInput>,
206
+ ): Component {
207
+ const state = runtime.syncRow(ctx, ctx.state.endedAt === undefined);
208
+ runtime.track(ctx, name, state);
209
+ runtime.syncExpansion(state, ctx.expanded, name);
210
+ const command = normalizeLineEndings(args.command ?? "") || "…";
211
+ const details = styleMultiline(command, (line) => theme.fg("toolOutput", line));
212
+ return renderToolCall(renderCallTitle(name, theme, ctx, state), details, theme);
213
+ }
214
+
215
+ function renderShellResult(
216
+ name: ShellToolName,
217
+ result: AgentToolResult<BashToolDetails | undefined>,
218
+ options: ToolRenderResultOptions,
219
+ theme: Theme,
220
+ ctx: RenderContext<BashToolInput>,
221
+ ): Component {
222
+ const state = runtime.syncRow(ctx, options.isPartial, !options.isPartial);
223
+ runtime.track(ctx, name, state);
224
+ const output = getTextResult(result);
225
+ runtime.setResultAvailable(state, name, output.length > 0);
226
+ if (!options.isPartial && !state.resultLineSummaryComputed) {
227
+ state.resultLineSummary = formatResultLineSummary(name, ctx.args, result, output);
228
+ state.resultLineSummaryComputed = true;
229
+ }
230
+ if (!state.expanded) {
231
+ return renderControls(theme, state, options.isPartial, ctx.isError, state.resultLineSummary);
232
+ }
233
+ const container = new CachedContainer();
234
+ const component = renderOutput(output, theme, ctx.isError);
235
+ if (component) container.addChild(component);
236
+ container.addChild(renderControls(theme, state, options.isPartial, ctx.isError, state.resultLineSummary));
237
+ return container;
238
+ }
239
+
240
+ const toolFactories: Record<CompactToolName, (cwd: string) => BuiltInDefinition> = {
241
+ read: createReadToolDefinition,
242
+ write: createWriteToolDefinition,
243
+ edit: createEditToolDefinition,
244
+ bash: createBashToolDefinition,
245
+ powershell: createPowerShellToolDefinition,
246
+ grep: createGrepToolDefinition,
247
+ find: createFindToolDefinition,
248
+ ls: createLsToolDefinition,
249
+ };
250
+
251
+ function createBuiltInDefinition(name: CompactToolName, cwd: string): BuiltInDefinition {
252
+ return toolFactories[name](cwd);
253
+ }
254
+
255
+ function registerFileTool(pi: ExtensionAPI, definition: BuiltInDefinition): void {
256
+ pi.registerTool({
257
+ ...definition,
258
+ execute: runtime.createTimedExecute(definition),
259
+ renderShell: "self",
260
+ renderCall: (args: ToolArgs, theme: Theme, ctx: RenderContext) => renderFileCall(definition, args, theme, ctx),
261
+ renderResult: (
262
+ result: AgentToolResult<unknown>,
263
+ options: ToolRenderResultOptions,
264
+ theme: Theme,
265
+ ctx: RenderContext,
266
+ ) => renderFileResult(definition, result, options, theme, ctx),
267
+ } as ToolDefinition<any, any, RowState>);
268
+ }
269
+
270
+ function registerShellTool(pi: ExtensionAPI, definition: BuiltInDefinition): void {
271
+ const name = definition.name as ShellToolName;
272
+ pi.registerTool({
273
+ ...definition,
274
+ execute: runtime.createTimedExecute(definition),
275
+ renderShell: "self",
276
+ renderCall: (args: BashToolInput, theme: Theme, ctx: RenderContext<BashToolInput>) =>
277
+ renderShellCall(name, args, theme, ctx),
278
+ renderResult: (
279
+ result: AgentToolResult<BashToolDetails | undefined>,
280
+ options: ToolRenderResultOptions,
281
+ theme: Theme,
282
+ ctx: RenderContext<BashToolInput>,
283
+ ) => renderShellResult(name, result, options, theme, ctx),
284
+ } as ToolDefinition<any, BashToolDetails | undefined, RowState>);
285
+ }
286
+
287
+ function registerTools(pi: ExtensionAPI, cwd: string): void {
288
+ const enabledTools = new Set(runtime.config.tools);
289
+ for (const name of registeredTools) {
290
+ if (!enabledTools.has(name)) pi.registerTool(createBuiltInDefinition(name, cwd));
291
+ }
292
+ registeredTools.clear();
293
+ for (const name of runtime.config.tools) {
294
+ const definition = createBuiltInDefinition(name, cwd);
295
+ if (name === "bash" || name === "powershell") registerShellTool(pi, definition);
296
+ else registerFileTool(pi, definition);
297
+ registeredTools.add(name);
298
+ }
299
+ }
300
+
301
+ function configure(pi: ExtensionAPI, cwd?: string, projectTrusted = false): void {
302
+ runtime.configure(loadConfig(cwd, projectTrusted), isFullscreenMode());
303
+ registerTools(pi, cwd ?? process.cwd());
304
+ }
305
+
306
+ export default function compactTools(pi: ExtensionAPI): void {
307
+ const thinkingCycle = new ThinkingCycleController(pi);
308
+ configure(pi);
309
+ pi.on("session_start", (event, ctx) => {
310
+ if (event.reason !== "reload") runtime.clearTimings();
311
+ configure(pi, ctx.cwd, ctx.isProjectTrusted());
312
+ bindTerminalInput(ctx);
313
+ thinkingCycle.bind(ctx);
314
+ });
315
+ pi.on("resources_discover", (_event, ctx) => configure(pi, ctx.cwd, ctx.isProjectTrusted()));
316
+ pi.on("session_shutdown", (event) => {
317
+ thinkingCycle.dispose();
318
+ unsubscribeTerminalInput?.();
319
+ unsubscribeTerminalInput = undefined;
320
+ runtime.reset(event.reason !== "reload");
321
+ });
322
+ }