tau-coding-agent 0.1.2 → 0.1.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tau-coding-agent
2
2
 
3
- Coding-focused Tau package for [Pi](https://pi.dev).
3
+ Coding agent built on top of [Pi](https://pi.dev).
4
4
 
5
5
  Tau turns Pi's minimal core into an opinionated, polished coding-agent setup with web search, code review workflows, sandboxing, local memory, usage reporting, and a curated skill set for day-to-day software work.
6
6
 
@@ -18,26 +18,27 @@ pi install -l npm:tau-coding-agent
18
18
 
19
19
  ## Extensions
20
20
 
21
- | Extension | Command | Description |
22
- | ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
23
- | `answer` | `/answer` | Extract and interactively answer agent questions. |
24
- | `branch-term` | `/branch` | Open a new terminal on the current session's git branch. |
25
- | `btw` | `/btw` | Run a one-off side request with read-only tools and no context persistence. |
26
- | `ghostty` | — | Ghostty tab title enhancements while the agent is working, waiting, or idle. |
27
- | `git-diff-stats` | — | Status bar diff stats for local changes in the current repo. |
28
- | `git-pr-status` | — | Status bar PR number and link for the current branch. |
29
- | `insights` | `/insights` | Analyze Pi sessions and suggest reusable instructions, templates, skills, and extensions. |
30
- | `interlude` | `alt+x` | Stash the current message draft, send one interlude message, then restore the draft. |
31
- | `loop` | `/loop` | Repeat a prompt until the agent signals success. |
32
- | `memory` | `/memory` | Opt-in project-local memory for learning and continuity across sessions. |
33
- | `notify` | — | Terminal notification when the agent is waiting for input. |
34
- | `openai-fast` | `/fast` | Toggle priority service tier for supported OpenAI models. |
35
- | `openai-verbosity` | `/verbosity` | Set verbosity for supported OpenAI models. |
36
- | `review` | `/review`, `/triage` | Multi-focus review and PR feedback triage for PRs, branches, commits, and local changes, with integrated follow-up fixes. |
37
- | `sandbox` | `/sandbox` | OS-level sandboxing for bash commands with runtime overrides. |
38
- | `usage` | `/usage` | Historical provider usage breakdown with all-provider history and live quota snapshots. |
39
- | `websearch` | — | Web search via Gemini, OpenAI, or Claude, leveraging Pi or browser session credentials. |
40
- | `worktree` | `/worktree` | Create, list, and archive git worktrees, optionally opening them in a new terminal or tmux pane. |
21
+ | Extension | Command | Description |
22
+ | ------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
23
+ | `answer` | `/answer` | Extract and interactively answer agent questions. |
24
+ | `branch-term` | `/branch` | Open a new terminal on the current session's git branch. |
25
+ | `btw` | `/btw` | Run a one-off side request with read-only tools and no context persistence. |
26
+ | `ghostty` | — | Ghostty tab title enhancements while the agent is working, waiting, or idle. |
27
+ | `git-diff-stats` | — | Status bar diff stats for local changes in the current repo. |
28
+ | `git-pr-status` | — | Status bar PR number and link for the current branch. |
29
+ | `insights` | `/insights` | Analyze Pi sessions and suggest reusable instructions, templates, skills, and extensions. |
30
+ | `interlude` | `alt+x` | Stash the current message draft, send one interlude message, then restore the draft. |
31
+ | `loop` | `/loop` | Repeat a prompt until the agent signals success. |
32
+ | `memory` | `/memory` | Opt-in project-local memory for learning and continuity across sessions. |
33
+ | `notify` | — | Terminal notification when the agent is waiting for input. |
34
+ | `openai-fast` | `/fast` | Toggle priority service tier for supported OpenAI models. |
35
+ | `openai-verbosity` | `/verbosity` | Set verbosity for supported OpenAI models. |
36
+ | `review` | `/review`, `/triage` | Multi-focus review and PR feedback triage for PRs, branches, commits, and local changes, with integrated follow-up fixes. |
37
+ | `sandbox` | `/sandbox` | OS-level sandboxing for bash commands with runtime overrides. |
38
+ | `tool-display-mode` | `ctrl+o` | Cycle tool output between Pi's default rendering, expanded output, and compact summaries. |
39
+ | `usage` | `/usage` | Historical provider usage breakdown with all-provider history and live quota snapshots. |
40
+ | `websearch` | — | Web search via Gemini, OpenAI, or Claude, leveraging Pi or browser session credentials. |
41
+ | `worktree` | `/worktree` | Create, list, and archive git worktrees, optionally opening them in a new terminal or tmux pane. |
41
42
 
42
43
  ## Skills
43
44
 
@@ -74,24 +74,68 @@ const DEFAULT_CONFIG: SandboxConfig = {
74
74
  mode: DEFAULT_PROMPT_MODE,
75
75
  network: {
76
76
  allowedDomains: [
77
+ "localhost",
78
+ "127.0.0.1",
77
79
  "npmjs.org",
78
80
  "*.npmjs.org",
79
81
  "registry.npmjs.org",
80
82
  "registry.yarnpkg.com",
81
83
  "pypi.org",
82
84
  "*.pypi.org",
85
+ "pythonhosted.org",
86
+ "*.pythonhosted.org",
87
+ "crates.io",
88
+ "*.crates.io",
89
+ "proxy.golang.org",
90
+ "sum.golang.org",
91
+ "go.dev",
92
+ "golang.org",
93
+ "rubygems.org",
94
+ "*.rubygems.org",
83
95
  "github.com",
84
96
  "*.github.com",
97
+ "githubusercontent.com",
98
+ "*.githubusercontent.com",
85
99
  "api.github.com",
86
100
  "raw.githubusercontent.com",
101
+ "ghcr.io",
102
+ "docker.io",
103
+ "*.docker.io",
104
+ "production.cloudflare.docker.com",
105
+ "sentry.io",
106
+ "*.sentry.io",
107
+ "datadoghq.com",
108
+ "*.datadoghq.com",
109
+ "datadoghq.eu",
110
+ "*.datadoghq.eu",
111
+ "anthropic.com",
112
+ "*.anthropic.com",
113
+ "claude.ai",
114
+ "*.claude.ai",
115
+ "openai.com",
116
+ "*.openai.com",
117
+ "chatgpt.com",
118
+ "*.chatgpt.com",
119
+ "google.com",
120
+ "*.google.com",
121
+ "googleapis.com",
122
+ "*.googleapis.com",
123
+ "todoist.com",
124
+ "*.todoist.com",
125
+ "twist.com",
126
+ "*.twist.com",
127
+ "doist.com",
128
+ "*.doist.com",
87
129
  ],
88
130
  deniedDomains: [],
131
+ allowLocalBinding: true,
89
132
  },
90
133
  filesystem: {
91
134
  denyRead: ["~/.ssh", "~/.aws", "~/.gnupg"],
92
- allowWrite: [".", "/tmp"],
135
+ allowWrite: [".", "/tmp", "/private/tmp", "~/.cache", "~/.npm"],
93
136
  denyWrite: [".env", ".env.*", "*.pem", "*.key"],
94
- allowGitCommonDir: false,
137
+ allowGitConfig: true,
138
+ allowGitCommonDir: true,
95
139
  },
96
140
  };
97
141
 
@@ -2455,11 +2499,15 @@ export default function (pi: ExtensionAPI) {
2455
2499
  " Network:",
2456
2500
  ` Allowed: ${runtimeConfig.network.allowedDomains.join(", ") || "(none)"}`,
2457
2501
  ` Denied: ${runtimeConfig.network.deniedDomains.join(", ") || "(none)"}`,
2502
+ ` allowLocalBinding: ${runtimeConfig.network.allowLocalBinding ? "true" : "false"}`,
2503
+ ` allowAllUnixSockets: ${runtimeConfig.network.allowAllUnixSockets ? "true" : "false"}`,
2504
+ ` allowUnixSockets: ${runtimeConfig.network.allowUnixSockets?.join(", ") || "(none)"}`,
2458
2505
  "",
2459
2506
  " Filesystem:",
2460
2507
  ` Deny Read: ${runtimeConfig.filesystem.denyRead.join(", ") || "(none)"}`,
2461
2508
  ` Allow Write: ${runtimeConfig.filesystem.allowWrite.join(", ") || "(none)"}`,
2462
2509
  ` Deny Write: ${runtimeConfig.filesystem.denyWrite.join(", ") || "(none)"}`,
2510
+ ` allowGitConfig: ${runtimeConfig.filesystem.allowGitConfig ? "true" : "false"}`,
2463
2511
  ` allowGitCommonDir: ${sandboxConfig?.filesystem.allowGitCommonDir ? "true" : "false"}`,
2464
2512
  "",
2465
2513
  " Advanced:",
@@ -0,0 +1,387 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import {
5
+ CustomEditor,
6
+ createBashToolDefinition,
7
+ createFindToolDefinition,
8
+ createGrepToolDefinition,
9
+ createLsToolDefinition,
10
+ createReadToolDefinition,
11
+ type AgentToolResult,
12
+ type ExtensionAPI,
13
+ type ExtensionContext,
14
+ type KeybindingsManager,
15
+ type Theme,
16
+ type ToolDefinition,
17
+ type ToolInfo,
18
+ type ToolRenderResultOptions,
19
+ } from "@mariozechner/pi-coding-agent";
20
+ import { Container, Text, type Component, type EditorTheme, type TUI } from "@mariozechner/pi-tui";
21
+
22
+ // --- Constants ---
23
+
24
+ const CONFIG_FILE = "tool-display-mode.json";
25
+ const MODES = ["default", "expanded", "minimal"] as const;
26
+ const DEFAULT_MODE = "default";
27
+
28
+ // These are current Pi built-in tool output messages, used only because grep/find/ls
29
+ // do not expose structured zero-result details yet.
30
+ const GREP_NO_MATCHES_OUTPUT = "No matches found"; // core/tools/grep.ts
31
+ const FIND_NO_MATCHES_OUTPUT = "No files found matching pattern"; // core/tools/find.ts
32
+ const LS_EMPTY_DIRECTORY_OUTPUT = "(empty directory)"; // core/tools/ls.ts
33
+
34
+ const TOOL_FACTORIES = {
35
+ read: createReadToolDefinition,
36
+ bash: createBashToolDefinition,
37
+ grep: createGrepToolDefinition,
38
+ find: createFindToolDefinition,
39
+ ls: createLsToolDefinition,
40
+ };
41
+
42
+ // --- Types ---
43
+
44
+ type Mode = (typeof MODES)[number];
45
+ type ToolName = keyof typeof TOOL_FACTORIES;
46
+ type AnyToolDefinition = ToolDefinition<any, any, any>;
47
+ type AnyToolRenderContext = Parameters<NonNullable<AnyToolDefinition["renderResult"]>>[3];
48
+ type JsonObject = Record<string, unknown>;
49
+
50
+ type ToolDisplayModeConfig = {
51
+ mode: Mode;
52
+ };
53
+
54
+ // --- Config ---
55
+
56
+ function emptyConfig(): ToolDisplayModeConfig {
57
+ return { mode: DEFAULT_MODE };
58
+ }
59
+
60
+ function getConfigPath(): string {
61
+ const agentDir = process.env.PI_CODING_AGENT_DIR ?? path.join(os.homedir(), ".pi", "agent");
62
+ return path.join(agentDir, CONFIG_FILE);
63
+ }
64
+
65
+ function isObject(value: unknown): value is JsonObject {
66
+ return typeof value === "object" && value !== null && !Array.isArray(value);
67
+ }
68
+
69
+ function isMode(value: string): value is Mode {
70
+ return (MODES as readonly string[]).includes(value);
71
+ }
72
+
73
+ function parseMode(value: unknown): Mode | undefined {
74
+ if (typeof value !== "string") return undefined;
75
+
76
+ const normalized = value.trim().toLowerCase();
77
+ return isMode(normalized) ? normalized : undefined;
78
+ }
79
+
80
+ function parseConfig(value: unknown): ToolDisplayModeConfig {
81
+ if (!isObject(value)) return emptyConfig();
82
+
83
+ return {
84
+ mode: parseMode(value.mode) ?? DEFAULT_MODE,
85
+ };
86
+ }
87
+
88
+ async function loadConfig(): Promise<ToolDisplayModeConfig> {
89
+ try {
90
+ const raw = await readFile(getConfigPath(), "utf8");
91
+ return parseConfig(JSON.parse(raw) as unknown);
92
+ } catch (error) {
93
+ const code = (error as NodeJS.ErrnoException).code;
94
+ if (code !== "ENOENT") {
95
+ const message = error instanceof Error ? error.message : String(error);
96
+ console.warn(`[tool-display-mode] Failed to load config: ${message}`);
97
+ }
98
+
99
+ return emptyConfig();
100
+ }
101
+ }
102
+
103
+ async function saveConfig(config: ToolDisplayModeConfig): Promise<void> {
104
+ const configPath = getConfigPath();
105
+ await mkdir(path.dirname(configPath), { recursive: true });
106
+ await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
107
+ }
108
+
109
+ // --- Tool definitions ---
110
+
111
+ function getToolDefinition(
112
+ cache: Map<string, Partial<Record<ToolName, AnyToolDefinition>>>,
113
+ name: ToolName,
114
+ cwd: string,
115
+ ): AnyToolDefinition {
116
+ let tools = cache.get(cwd);
117
+ if (!tools) {
118
+ tools = {};
119
+ cache.set(cwd, tools);
120
+ }
121
+
122
+ return (tools[name] ??= TOOL_FACTORIES[name](cwd));
123
+ }
124
+
125
+ function shouldRegisterToolRenderer(tools: ToolInfo[], name: ToolName): boolean {
126
+ const existingTool = tools.find((tool) => tool.name === name);
127
+ return existingTool?.sourceInfo.source === "builtin";
128
+ }
129
+
130
+ function createToolDisplayDefinition(options: {
131
+ cache: Map<string, Partial<Record<ToolName, AnyToolDefinition>>>;
132
+ name: ToolName;
133
+ getMode: () => Mode;
134
+ }): AnyToolDefinition {
135
+ const { cache, name, getMode } = options;
136
+ const base = getToolDefinition(cache, name, process.cwd());
137
+
138
+ return {
139
+ ...base,
140
+
141
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
142
+ return getToolDefinition(cache, name, ctx.cwd).execute(
143
+ toolCallId,
144
+ params,
145
+ signal,
146
+ onUpdate,
147
+ ctx,
148
+ );
149
+ },
150
+
151
+ renderCall(args, theme, context) {
152
+ const renderer = getToolDefinition(cache, name, context.cwd).renderCall;
153
+ return renderer?.(args, theme, { ...context, lastComponent: undefined }) ?? emptyComponent();
154
+ },
155
+
156
+ renderResult(result, options, theme, context) {
157
+ const mode = getMode();
158
+ const renderer = getToolDefinition(cache, name, context.cwd).renderResult;
159
+
160
+ if (mode === "minimal") {
161
+ if (name === "bash") {
162
+ renderer?.(result, options, theme, { ...context, lastComponent: undefined });
163
+ }
164
+
165
+ return renderMinimalResult(name, result, options, theme, context);
166
+ }
167
+
168
+ const expanded = mode === "expanded";
169
+ return (
170
+ renderer?.(result, { ...options, expanded }, theme, {
171
+ ...context,
172
+ expanded,
173
+ lastComponent: undefined,
174
+ }) ?? emptyComponent()
175
+ );
176
+ },
177
+ };
178
+ }
179
+
180
+ // --- Display mode ---
181
+
182
+ function nextMode(currentMode: Mode): Mode {
183
+ const index = MODES.indexOf(currentMode);
184
+ return MODES[(index + 1) % MODES.length] ?? DEFAULT_MODE;
185
+ }
186
+
187
+ function applyMode(ctx: ExtensionContext, mode: Mode): void {
188
+ if (!ctx.hasUI) return;
189
+
190
+ ctx.ui.setToolsExpanded(mode === "expanded");
191
+ }
192
+
193
+ function showModeChange(ctx: ExtensionContext, mode: Mode): void {
194
+ if (!ctx.hasUI) return;
195
+
196
+ ctx.ui.notify(`Tool output: ${mode}`, "info");
197
+ }
198
+
199
+ function reportSaveError(ctx: ExtensionContext, error: unknown): void {
200
+ const message = error instanceof Error ? error.message : String(error);
201
+ if (ctx.hasUI) {
202
+ ctx.ui.notify(`Failed to save ${getConfigPath()}: ${message}`, "error");
203
+ return;
204
+ }
205
+
206
+ console.warn(`[tool-display-mode] Failed to save config: ${message}`);
207
+ }
208
+
209
+ // --- Minimal rendering ---
210
+
211
+ function renderMinimalResult(
212
+ name: ToolName,
213
+ result: AgentToolResult<any>,
214
+ options: ToolRenderResultOptions,
215
+ theme: Theme,
216
+ context: AnyToolRenderContext,
217
+ ): Component {
218
+ if (context.isError) {
219
+ return renderMinimalText(theme, "error", lastNonEmptyLine(textOutput(result)) ?? "error");
220
+ }
221
+
222
+ if (options.isPartial) {
223
+ return renderMinimalText(theme, "muted", "running...");
224
+ }
225
+
226
+ let summary: string;
227
+ switch (name) {
228
+ case "bash":
229
+ summary = bashSummary(result);
230
+ break;
231
+ case "read":
232
+ summary = readSummary(result);
233
+ break;
234
+ case "grep":
235
+ summary = grepSummary(result);
236
+ break;
237
+ case "find":
238
+ summary = resultsSummary(result, "path");
239
+ break;
240
+ case "ls":
241
+ summary = resultsSummary(result, "entry");
242
+ break;
243
+ }
244
+
245
+ return renderMinimalText(theme, "muted", `→ ${summary}`);
246
+ }
247
+
248
+ function renderMinimalText(theme: Theme, color: "error" | "muted", text: string): Text {
249
+ return new Text(theme.fg(color, `\n${text}`), 0, 0);
250
+ }
251
+
252
+ function bashSummary(result: AgentToolResult<any>): string {
253
+ const text = stripTrailingNotice(textOutput(result)).trim();
254
+ if (!text || text === "(no output)") return "no output";
255
+
256
+ const lines = countLines(text);
257
+ return `${lines} ${plural(lines, "line")}`;
258
+ }
259
+
260
+ function readSummary(result: AgentToolResult<any>): string {
261
+ if (result.content.some((content) => content.type === "image")) {
262
+ return "image";
263
+ }
264
+
265
+ const details = result.details as { truncation?: { outputLines?: number } } | undefined;
266
+ const lines =
267
+ details?.truncation?.outputLines ?? countLines(stripTrailingNotice(textOutput(result)));
268
+
269
+ return `${lines} ${plural(lines, "line")}`;
270
+ }
271
+
272
+ function grepSummary(result: AgentToolResult<any>): string {
273
+ const text = stripTrailingNotice(textOutput(result)).trim();
274
+ if (!text || text === GREP_NO_MATCHES_OUTPUT) {
275
+ return "0 lines";
276
+ }
277
+
278
+ const lines = text.split("\n").filter(Boolean);
279
+ const matchLines = lines.filter((line) => /:\d+: /.test(line));
280
+ const count = matchLines.length > 0 ? matchLines.length : lines.length;
281
+ return `${count} ${plural(count, "line")}`;
282
+ }
283
+
284
+ function resultsSummary(result: AgentToolResult<any>, noun: string): string {
285
+ const text = stripTrailingNotice(textOutput(result)).trim();
286
+ if (!text || text === FIND_NO_MATCHES_OUTPUT || text === LS_EMPTY_DIRECTORY_OUTPUT) {
287
+ return `0 ${plural(0, noun)}`;
288
+ }
289
+
290
+ const count = text.split("\n").filter(Boolean).length;
291
+ return `${count} ${plural(count, noun)}`;
292
+ }
293
+
294
+ function textOutput(result: AgentToolResult<any>): string {
295
+ return result.content
296
+ .filter((content) => content.type === "text")
297
+ .map((content) => content.text ?? "")
298
+ .join("\n");
299
+ }
300
+
301
+ function stripTrailingNotice(text: string): string {
302
+ return text.replace(/\n\n\[[\s\S]*\]$/, "");
303
+ }
304
+
305
+ function countLines(text: string): number {
306
+ if (text.length === 0) return 0;
307
+
308
+ const lines = text.split("\n");
309
+ if (lines.at(-1) === "") lines.pop();
310
+ return lines.length;
311
+ }
312
+
313
+ function lastNonEmptyLine(text: string): string | undefined {
314
+ return text
315
+ .split("\n")
316
+ .map((line) => line.trim())
317
+ .filter(Boolean)
318
+ .at(-1);
319
+ }
320
+
321
+ function plural(count: number, singular: string): string {
322
+ return count === 1 ? singular : `${singular}s`;
323
+ }
324
+
325
+ function emptyComponent(): Container {
326
+ return new Container();
327
+ }
328
+
329
+ // --- Editor ---
330
+
331
+ class ToolDisplayEditor extends CustomEditor {
332
+ constructor(
333
+ tui: TUI,
334
+ theme: EditorTheme,
335
+ private readonly appKeybindings: KeybindingsManager,
336
+ private readonly cycleMode: () => void,
337
+ ) {
338
+ super(tui, theme, appKeybindings);
339
+ }
340
+
341
+ override handleInput(data: string): void {
342
+ if (this.appKeybindings.matches(data, "app.tools.expand")) {
343
+ this.cycleMode();
344
+ return;
345
+ }
346
+
347
+ super.handleInput(data);
348
+ }
349
+ }
350
+
351
+ export default function toolDisplayModeExtension(pi: ExtensionAPI): void {
352
+ let mode: Mode = DEFAULT_MODE;
353
+ let registeredToolRenderers = false;
354
+ const toolCache = new Map<string, Partial<Record<ToolName, AnyToolDefinition>>>();
355
+
356
+ const setMode = (ctx: ExtensionContext, next: Mode): void => {
357
+ mode = next;
358
+ applyMode(ctx, mode);
359
+ showModeChange(ctx, mode);
360
+
361
+ void saveConfig({ mode }).catch((error) => reportSaveError(ctx, error));
362
+ };
363
+
364
+ pi.on("session_start", async (_event, ctx) => {
365
+ mode = (await loadConfig()).mode;
366
+
367
+ if (!registeredToolRenderers) {
368
+ const tools = pi.getAllTools();
369
+ for (const name of Object.keys(TOOL_FACTORIES) as ToolName[]) {
370
+ if (!shouldRegisterToolRenderer(tools, name)) continue;
371
+
372
+ pi.registerTool(
373
+ createToolDisplayDefinition({ cache: toolCache, name, getMode: () => mode }),
374
+ );
375
+ }
376
+ registeredToolRenderers = true;
377
+ }
378
+
379
+ applyMode(ctx, mode);
380
+ if (!ctx.hasUI) return;
381
+
382
+ ctx.ui.setEditorComponent(
383
+ (tui, theme, keybindings) =>
384
+ new ToolDisplayEditor(tui, theme, keybindings, () => setMode(ctx, nextMode(mode))),
385
+ );
386
+ });
387
+ }
package/package.json CHANGED
@@ -1,12 +1,24 @@
1
1
  {
2
2
  "name": "tau-coding-agent",
3
- "version": "0.1.2",
4
- "description": "Coding-focused Tau distribution for Pi",
3
+ "version": "0.1.3",
4
+ "description": "Opinionated Pi package with web search, review workflows, sandboxing, memory, usage reporting, skills, and a theme",
5
5
  "keywords": [
6
+ "agent-tools",
7
+ "code-review",
8
+ "coding-agent",
9
+ "memory",
10
+ "pi",
6
11
  "pi-extension",
7
12
  "pi-package",
8
- "pi-skill"
13
+ "pi-skill",
14
+ "pi-theme",
15
+ "sandbox",
16
+ "websearch"
9
17
  ],
18
+ "homepage": "https://github.com/goncalossilva/tau#readme",
19
+ "bugs": {
20
+ "url": "https://github.com/goncalossilva/tau/issues"
21
+ },
10
22
  "license": "MIT",
11
23
  "repository": {
12
24
  "type": "git",
@@ -49,6 +61,7 @@
49
61
  "extensions/openai-verbosity.ts",
50
62
  "extensions/review.ts",
51
63
  "extensions/sandbox/index.ts",
64
+ "extensions/tool-display-mode.ts",
52
65
  "extensions/usage/index.ts",
53
66
  "extensions/websearch/index.ts",
54
67
  "extensions/worktree.ts"