tau-coding-agent 0.1.6 → 0.2.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/README.md +15 -12
- package/extensions/answer.ts +129 -79
- package/extensions/branch-term/README.md +7 -0
- package/extensions/{branch-term.ts → branch-term/index.ts} +113 -104
- package/extensions/btw.ts +17 -2
- package/extensions/caffeinate/README.md +5 -0
- package/extensions/caffeinate/index.ts +144 -0
- package/extensions/fast.ts +292 -0
- package/extensions/ghostty.ts +214 -211
- package/extensions/git-diff-stats.ts +124 -84
- package/extensions/git-pr-status.ts +274 -208
- package/extensions/insights.ts +109 -191
- package/extensions/loop.ts +149 -145
- package/extensions/memory.ts +138 -86
- package/extensions/notify.ts +14 -26
- package/extensions/openai-verbosity.ts +108 -42
- package/extensions/review/fix.ts +15 -6
- package/extensions/review/git.ts +93 -103
- package/extensions/review/index.ts +60 -62
- package/extensions/review/interrupt.ts +117 -0
- package/extensions/review/message-queue.ts +45 -12
- package/extensions/review/models.ts +79 -3
- package/extensions/review/prompts.ts +41 -38
- package/extensions/review/review.ts +161 -166
- package/extensions/review/runner.ts +139 -147
- package/extensions/review/runtime.ts +173 -120
- package/extensions/review/triage.ts +41 -44
- package/extensions/sandbox/bash.ts +775 -0
- package/extensions/sandbox/command.ts +605 -0
- package/extensions/sandbox/config.ts +764 -0
- package/extensions/sandbox/index.ts +132 -3338
- package/extensions/sandbox/permissions/dialog.ts +118 -0
- package/extensions/sandbox/permissions/filesystem.ts +559 -0
- package/extensions/sandbox/permissions/mach-lookup.ts +186 -0
- package/extensions/sandbox/permissions/network.ts +164 -0
- package/extensions/sandbox/permissions/unsandboxed.ts +270 -0
- package/extensions/sandbox/runtime.ts +616 -0
- package/extensions/stash.ts +28 -15
- package/extensions/subagent/README.md +73 -0
- package/extensions/subagent/index.ts +822 -0
- package/extensions/subagent/interrupt.ts +117 -0
- package/extensions/subagent/permissions.ts +101 -0
- package/extensions/subagent/rpc.ts +177 -0
- package/extensions/tool-display-mode.ts +267 -64
- package/extensions/usage/index.ts +249 -356
- package/extensions/usage/openrouter.ts +10 -2
- package/extensions/websearch/README.md +12 -47
- package/extensions/websearch/config.ts +5 -2
- package/extensions/websearch/index.ts +94 -109
- package/extensions/websearch/output.ts +51 -0
- package/extensions/websearch/providers/anthropic.pi.ts +27 -44
- package/extensions/websearch/providers/gemini.browser.ts +68 -77
- package/extensions/websearch/providers/gemini.pi.ts +17 -17
- package/extensions/websearch/providers/openai-codex.pi.ts +145 -7
- package/extensions/websearch/providers/pi-model.shared.ts +33 -39
- package/extensions/websearch/providers/shared.ts +87 -2
- package/extensions/websearch/types.ts +0 -3
- package/extensions/worktree.ts +132 -172
- package/package.json +10 -5
- package/skills/browser-tools/SKILL.md +29 -234
- package/skills/browser-tools/references/cookies.md +36 -0
- package/skills/browser-tools/references/interaction.md +90 -0
- package/skills/browser-tools/references/logging.md +34 -0
- package/skills/git-clean-history/SKILL.md +6 -6
- package/skills/git-commit/SKILL.md +5 -3
- package/skills/github-pull-request/SKILL.md +60 -0
- package/skills/github-pull-request/references/create.md +40 -0
- package/skills/github-pull-request/references/stewardship.md +60 -0
- package/skills/oracle/SKILL.md +3 -3
- package/skills/oracle/scripts/oracle +10 -1
- package/skills/sentry/SKILL.md +15 -185
- package/skills/sentry/references/events.md +79 -0
- package/skills/sentry/references/issues.md +62 -0
- package/skills/sentry/references/logs.md +46 -0
- package/skills/update-changelog/SKILL.md +27 -121
- package/skills/web-design/SKILL.md +16 -105
- package/themes/tau-dark.json +4 -0
- package/extensions/openai-fast.ts +0 -229
- package/extensions/websearch/providers/openai-codex.browser.ts +0 -77
- package/extensions/websearch/providers/openai-codex.shared.ts +0 -123
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { stripVTControlCharacters } from "node:util";
|
|
3
4
|
import {
|
|
4
5
|
getAgentDir,
|
|
5
6
|
type AppKeybinding,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
createLsToolDefinition,
|
|
11
12
|
createReadToolDefinition,
|
|
12
13
|
type AgentToolResult,
|
|
14
|
+
type BashToolDetails,
|
|
13
15
|
type ExtensionAPI,
|
|
14
16
|
type ExtensionContext,
|
|
15
17
|
type KeybindingsManager,
|
|
@@ -20,18 +22,24 @@ import {
|
|
|
20
22
|
} from "@earendil-works/pi-coding-agent";
|
|
21
23
|
import {
|
|
22
24
|
Container,
|
|
25
|
+
Loader,
|
|
23
26
|
Text,
|
|
27
|
+
truncateToWidth,
|
|
24
28
|
type AutocompleteProvider,
|
|
25
29
|
type Component,
|
|
26
30
|
type EditorComponent,
|
|
27
31
|
type Focusable,
|
|
32
|
+
type TuiMouseEvent,
|
|
33
|
+
type TuiMouseEventResult,
|
|
34
|
+
type TUI,
|
|
28
35
|
} from "@earendil-works/pi-tui";
|
|
29
36
|
|
|
30
37
|
// --- Constants ---
|
|
31
38
|
|
|
32
39
|
const CONFIG_FILE = "tool-display-mode.json";
|
|
33
|
-
const MODES = ["
|
|
34
|
-
const
|
|
40
|
+
const MODES = ["collapsed", "expanded", "minimal"] as const;
|
|
41
|
+
const INITIAL_MODE = MODES[0];
|
|
42
|
+
const ACTIVITY_WIDGET = "tool-display-activity";
|
|
35
43
|
|
|
36
44
|
// These are current Pi built-in tool output messages, used only because grep/find/ls
|
|
37
45
|
// do not expose structured zero-result details yet.
|
|
@@ -39,6 +47,7 @@ const GREP_NO_MATCHES_OUTPUT = "No matches found"; // core/tools/grep.ts
|
|
|
39
47
|
const FIND_NO_MATCHES_OUTPUT = "No files found matching pattern"; // core/tools/find.ts
|
|
40
48
|
const LS_EMPTY_DIRECTORY_OUTPUT = "(empty directory)"; // core/tools/ls.ts
|
|
41
49
|
|
|
50
|
+
// PowerShell is intentionally omitted because Tau does not officially support Windows.
|
|
42
51
|
const TOOL_FACTORIES = {
|
|
43
52
|
read: createReadToolDefinition,
|
|
44
53
|
bash: createBashToolDefinition,
|
|
@@ -55,13 +64,17 @@ type AnyToolDefinition = ToolDefinition<any, any, any>;
|
|
|
55
64
|
type AnyToolRenderContext = Parameters<NonNullable<AnyToolDefinition["renderResult"]>>[3];
|
|
56
65
|
type JsonObject = Record<string, unknown>;
|
|
57
66
|
type EditorFactory = NonNullable<ReturnType<ExtensionContext["ui"]["getEditorComponent"]>>;
|
|
67
|
+
type WorkingIndicator = NonNullable<Parameters<CustomEditor["setWorkingStatusIndicator"]>[0]>;
|
|
68
|
+
type ActivitySource = "subagent" | "review";
|
|
69
|
+
type ActivityState = { text: string; idle: boolean };
|
|
58
70
|
|
|
59
71
|
type ToolDisplayModeConfig = {
|
|
60
72
|
mode: Mode;
|
|
61
73
|
};
|
|
62
74
|
|
|
63
75
|
type CustomEditorLike = EditorComponent &
|
|
64
|
-
Partial<Focusable> &
|
|
76
|
+
Partial<Focusable> &
|
|
77
|
+
Partial<Pick<CustomEditor, "embedWorkingStatus" | "setWorkingStatusIndicator">> & {
|
|
65
78
|
actionHandlers?: Map<AppKeybinding, () => void>;
|
|
66
79
|
onEscape?: () => void;
|
|
67
80
|
onCtrlD?: () => void;
|
|
@@ -73,7 +86,7 @@ type CustomEditorLike = EditorComponent &
|
|
|
73
86
|
// --- Config ---
|
|
74
87
|
|
|
75
88
|
function emptyConfig(): ToolDisplayModeConfig {
|
|
76
|
-
return { mode:
|
|
89
|
+
return { mode: INITIAL_MODE };
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
function getConfigPath(): string {
|
|
@@ -99,7 +112,7 @@ function parseConfig(value: unknown): ToolDisplayModeConfig {
|
|
|
99
112
|
if (!isObject(value)) return emptyConfig();
|
|
100
113
|
|
|
101
114
|
return {
|
|
102
|
-
mode: parseMode(value.mode) ??
|
|
115
|
+
mode: parseMode(value.mode) ?? INITIAL_MODE,
|
|
103
116
|
};
|
|
104
117
|
}
|
|
105
118
|
|
|
@@ -126,61 +139,39 @@ async function saveConfig(config: ToolDisplayModeConfig): Promise<void> {
|
|
|
126
139
|
|
|
127
140
|
// --- Tool definitions ---
|
|
128
141
|
|
|
129
|
-
function getToolDefinition(
|
|
130
|
-
cache: Map<string, Partial<Record<ToolName, AnyToolDefinition>>>,
|
|
131
|
-
name: ToolName,
|
|
132
|
-
cwd: string,
|
|
133
|
-
): AnyToolDefinition {
|
|
134
|
-
let tools = cache.get(cwd);
|
|
135
|
-
if (!tools) {
|
|
136
|
-
tools = {};
|
|
137
|
-
cache.set(cwd, tools);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return (tools[name] ??= TOOL_FACTORIES[name](cwd));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
142
|
function shouldRegisterToolRenderer(tools: ToolInfo[], name: ToolName): boolean {
|
|
144
143
|
const existingTool = tools.find((tool) => tool.name === name);
|
|
145
144
|
return existingTool?.sourceInfo.source === "builtin";
|
|
146
145
|
}
|
|
147
146
|
|
|
148
147
|
function createToolDisplayDefinition(options: {
|
|
149
|
-
cache: Map<string, Partial<Record<ToolName, AnyToolDefinition>>>;
|
|
150
148
|
name: ToolName;
|
|
151
149
|
getMode: () => Mode;
|
|
152
150
|
}): AnyToolDefinition {
|
|
153
|
-
const {
|
|
154
|
-
const base =
|
|
151
|
+
const { name, getMode } = options;
|
|
152
|
+
const base: AnyToolDefinition = TOOL_FACTORIES[name](process.cwd());
|
|
155
153
|
|
|
156
154
|
return {
|
|
157
155
|
...base,
|
|
158
156
|
|
|
159
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
160
|
-
return getToolDefinition(cache, name, ctx.cwd).execute(
|
|
161
|
-
toolCallId,
|
|
162
|
-
params,
|
|
163
|
-
signal,
|
|
164
|
-
onUpdate,
|
|
165
|
-
ctx,
|
|
166
|
-
);
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
renderCall(args, theme, context) {
|
|
170
|
-
const renderer = getToolDefinition(cache, name, context.cwd).renderCall;
|
|
171
|
-
return renderer?.(args, theme, { ...context, lastComponent: undefined }) ?? emptyComponent();
|
|
172
|
-
},
|
|
173
|
-
|
|
174
157
|
renderResult(result, options, theme, context) {
|
|
175
158
|
const mode = getMode();
|
|
176
|
-
const renderer =
|
|
159
|
+
const renderer = base.renderResult;
|
|
160
|
+
const previous = context.lastComponent;
|
|
177
161
|
|
|
178
162
|
if (mode === "minimal") {
|
|
163
|
+
const component =
|
|
164
|
+
previous instanceof MinimalResultComponent
|
|
165
|
+
? previous
|
|
166
|
+
: new MinimalResultComponent(previous);
|
|
179
167
|
if (name === "bash") {
|
|
180
|
-
renderer?.(result, options, theme, {
|
|
168
|
+
component.nativeComponent = renderer?.(result, options, theme, {
|
|
169
|
+
...context,
|
|
170
|
+
lastComponent: component.nativeComponent,
|
|
171
|
+
});
|
|
181
172
|
}
|
|
182
|
-
|
|
183
|
-
return
|
|
173
|
+
component.setText(formatMinimalResult(name, result, options, theme, context));
|
|
174
|
+
return component;
|
|
184
175
|
}
|
|
185
176
|
|
|
186
177
|
const expanded = mode === "expanded";
|
|
@@ -188,7 +179,8 @@ function createToolDisplayDefinition(options: {
|
|
|
188
179
|
renderer?.(result, { ...options, expanded }, theme, {
|
|
189
180
|
...context,
|
|
190
181
|
expanded,
|
|
191
|
-
lastComponent:
|
|
182
|
+
lastComponent:
|
|
183
|
+
previous instanceof MinimalResultComponent ? previous.nativeComponent : previous,
|
|
192
184
|
}) ?? emptyComponent()
|
|
193
185
|
);
|
|
194
186
|
},
|
|
@@ -199,7 +191,7 @@ function createToolDisplayDefinition(options: {
|
|
|
199
191
|
|
|
200
192
|
function nextMode(currentMode: Mode): Mode {
|
|
201
193
|
const index = MODES.indexOf(currentMode);
|
|
202
|
-
return MODES[(index + 1) % MODES.length] ??
|
|
194
|
+
return MODES[(index + 1) % MODES.length] ?? INITIAL_MODE;
|
|
203
195
|
}
|
|
204
196
|
|
|
205
197
|
function applyMode(ctx: ExtensionContext, mode: Mode): void {
|
|
@@ -226,19 +218,25 @@ function reportSaveError(ctx: ExtensionContext, error: unknown): void {
|
|
|
226
218
|
|
|
227
219
|
// --- Minimal rendering ---
|
|
228
220
|
|
|
229
|
-
|
|
221
|
+
class MinimalResultComponent extends Text {
|
|
222
|
+
constructor(public nativeComponent: Component | undefined) {
|
|
223
|
+
super("", 0, 0);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function formatMinimalResult(
|
|
230
228
|
name: ToolName,
|
|
231
229
|
result: AgentToolResult<any>,
|
|
232
230
|
options: ToolRenderResultOptions,
|
|
233
231
|
theme: Theme,
|
|
234
232
|
context: AnyToolRenderContext,
|
|
235
|
-
):
|
|
233
|
+
): string {
|
|
236
234
|
if (context.isError) {
|
|
237
|
-
return
|
|
235
|
+
return theme.fg("error", lastNonEmptyLine(textOutput(result)) ?? "error");
|
|
238
236
|
}
|
|
239
237
|
|
|
240
238
|
if (options.isPartial) {
|
|
241
|
-
return
|
|
239
|
+
return theme.fg("muted", "running...");
|
|
242
240
|
}
|
|
243
241
|
|
|
244
242
|
let summary: string;
|
|
@@ -260,18 +258,15 @@ function renderMinimalResult(
|
|
|
260
258
|
break;
|
|
261
259
|
}
|
|
262
260
|
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function renderMinimalText(theme: Theme, color: "error" | "muted", text: string): Text {
|
|
267
|
-
return new Text(theme.fg(color, text), 0, 0);
|
|
261
|
+
return theme.fg("muted", `↳ ${summary}`);
|
|
268
262
|
}
|
|
269
263
|
|
|
270
264
|
function bashSummary(result: AgentToolResult<any>): string {
|
|
271
|
-
const text =
|
|
265
|
+
const text = textOutput(result).trim();
|
|
272
266
|
if (!text || text === "(no output)") return "no output";
|
|
273
267
|
|
|
274
|
-
const
|
|
268
|
+
const details = result.details as BashToolDetails | undefined;
|
|
269
|
+
const lines = details?.truncation?.outputLines ?? countLines(text);
|
|
275
270
|
return `${lines} ${plural(lines, "line")}`;
|
|
276
271
|
}
|
|
277
272
|
|
|
@@ -337,7 +332,7 @@ function lastNonEmptyLine(text: string): string | undefined {
|
|
|
337
332
|
}
|
|
338
333
|
|
|
339
334
|
function plural(count: number, singular: string): string {
|
|
340
|
-
return count === 1 ? singular : `${singular}s`;
|
|
335
|
+
return count === 1 ? singular : singular === "entry" ? "entries" : `${singular}s`;
|
|
341
336
|
}
|
|
342
337
|
|
|
343
338
|
function emptyComponent(): Container {
|
|
@@ -346,6 +341,28 @@ function emptyComponent(): Container {
|
|
|
346
341
|
|
|
347
342
|
// --- Editor ---
|
|
348
343
|
|
|
344
|
+
/** Adapt public Loader output to CustomEditor's native border layout. Check parity on Pi upgrades. */
|
|
345
|
+
class BackgroundWorkingIndicator extends Loader implements WorkingIndicator {
|
|
346
|
+
readonly kind = "working";
|
|
347
|
+
|
|
348
|
+
dispose(): void {
|
|
349
|
+
this.stop();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
renderInBorder(width: number): string {
|
|
353
|
+
const line = super.render(width + 2)[1] ?? "";
|
|
354
|
+
return truncateToWidth(
|
|
355
|
+
line.startsWith(" ") ? line.slice(1).trimEnd() : line.trimEnd(),
|
|
356
|
+
width,
|
|
357
|
+
"",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
renderSpinnerInBorder(width: number): string {
|
|
362
|
+
return truncateToWidth(this.getRenderedIndicator(), width, "");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
349
366
|
class ToolDisplayEditor implements EditorComponent, Focusable {
|
|
350
367
|
readonly actionHandlers: Map<AppKeybinding, () => void>;
|
|
351
368
|
private readonly customBase: CustomEditorLike;
|
|
@@ -354,11 +371,19 @@ class ToolDisplayEditor implements EditorComponent, Focusable {
|
|
|
354
371
|
private fallbackOnCtrlD?: () => void;
|
|
355
372
|
private fallbackOnPasteImage?: () => void;
|
|
356
373
|
private fallbackOnExtensionShortcut?: (data: string) => boolean;
|
|
374
|
+
private nativeIndicator: WorkingIndicator | undefined;
|
|
375
|
+
private backgroundIndicator: BackgroundWorkingIndicator | undefined;
|
|
376
|
+
private backgroundMessage: string | undefined;
|
|
377
|
+
private workingMessage: string | undefined;
|
|
357
378
|
|
|
358
379
|
constructor(
|
|
359
380
|
private readonly base: EditorComponent,
|
|
360
381
|
private readonly appKeybindings: KeybindingsManager,
|
|
361
382
|
private readonly cycleMode: () => void,
|
|
383
|
+
private readonly tui: TUI,
|
|
384
|
+
private readonly getActivity: () => ActivityState | undefined,
|
|
385
|
+
private readonly setWorkingMessage: (message?: string) => void,
|
|
386
|
+
private readonly statusColor: (text: string) => string,
|
|
362
387
|
) {
|
|
363
388
|
this.customBase = base as CustomEditorLike;
|
|
364
389
|
this.actionHandlers = this.customBase.actionHandlers ?? new Map();
|
|
@@ -393,6 +418,62 @@ class ToolDisplayEditor implements EditorComponent, Focusable {
|
|
|
393
418
|
return this.base.wantsKeyRelease;
|
|
394
419
|
}
|
|
395
420
|
|
|
421
|
+
get embedWorkingStatus(): boolean {
|
|
422
|
+
return (
|
|
423
|
+
this.customBase.embedWorkingStatus === true &&
|
|
424
|
+
typeof this.customBase.setWorkingStatusIndicator === "function"
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
setWorkingStatusIndicator(
|
|
429
|
+
indicator: Parameters<CustomEditor["setWorkingStatusIndicator"]>[0],
|
|
430
|
+
): void {
|
|
431
|
+
this.nativeIndicator = indicator;
|
|
432
|
+
this.refreshActivity();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
refreshActivity(): boolean {
|
|
436
|
+
const activity = this.embedWorkingStatus ? this.getActivity() : undefined;
|
|
437
|
+
const nativeWorking = this.nativeIndicator?.kind === "working";
|
|
438
|
+
const foregroundMessage = activity && nativeWorking ? `Working, ${activity.text}` : undefined;
|
|
439
|
+
if (foregroundMessage !== this.workingMessage) {
|
|
440
|
+
this.workingMessage = foregroundMessage;
|
|
441
|
+
this.setWorkingMessage(foregroundMessage);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const backgroundMessage = activity?.idle && !this.nativeIndicator ? activity.text : undefined;
|
|
445
|
+
if (backgroundMessage) {
|
|
446
|
+
if (!this.backgroundIndicator) {
|
|
447
|
+
this.backgroundIndicator = new BackgroundWorkingIndicator(
|
|
448
|
+
this.tui,
|
|
449
|
+
this.statusColor,
|
|
450
|
+
this.statusColor,
|
|
451
|
+
backgroundMessage,
|
|
452
|
+
);
|
|
453
|
+
} else if (backgroundMessage !== this.backgroundMessage) {
|
|
454
|
+
this.backgroundIndicator.setMessage(backgroundMessage);
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
this.backgroundIndicator?.dispose();
|
|
458
|
+
this.backgroundIndicator = undefined;
|
|
459
|
+
}
|
|
460
|
+
this.backgroundMessage = backgroundMessage;
|
|
461
|
+
this.customBase.setWorkingStatusIndicator?.(this.nativeIndicator ?? this.backgroundIndicator);
|
|
462
|
+
return Boolean(activity && (nativeWorking || this.backgroundIndicator));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
disposeActivity(): void {
|
|
466
|
+
const hadBackground = this.backgroundIndicator !== undefined;
|
|
467
|
+
this.backgroundIndicator?.dispose();
|
|
468
|
+
this.backgroundIndicator = undefined;
|
|
469
|
+
this.backgroundMessage = undefined;
|
|
470
|
+
if (this.workingMessage !== undefined) {
|
|
471
|
+
this.workingMessage = undefined;
|
|
472
|
+
this.setWorkingMessage();
|
|
473
|
+
}
|
|
474
|
+
if (hadBackground) this.customBase.setWorkingStatusIndicator?.(undefined);
|
|
475
|
+
}
|
|
476
|
+
|
|
396
477
|
get borderColor(): ((str: string) => string) | undefined {
|
|
397
478
|
return this.base.borderColor;
|
|
398
479
|
}
|
|
@@ -470,10 +551,12 @@ class ToolDisplayEditor implements EditorComponent, Focusable {
|
|
|
470
551
|
}
|
|
471
552
|
|
|
472
553
|
render(width: number): string[] {
|
|
554
|
+
this.refreshActivity();
|
|
473
555
|
return this.base.render(width);
|
|
474
556
|
}
|
|
475
557
|
|
|
476
558
|
invalidate(): void {
|
|
559
|
+
this.backgroundIndicator?.invalidate();
|
|
477
560
|
this.base.invalidate();
|
|
478
561
|
}
|
|
479
562
|
|
|
@@ -485,24 +568,99 @@ class ToolDisplayEditor implements EditorComponent, Focusable {
|
|
|
485
568
|
|
|
486
569
|
this.base.handleInput(data);
|
|
487
570
|
}
|
|
571
|
+
|
|
572
|
+
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
573
|
+
return this.base.handleMouse?.(event);
|
|
574
|
+
}
|
|
488
575
|
}
|
|
489
576
|
|
|
490
577
|
export default function toolDisplayModeExtension(pi: ExtensionAPI): void {
|
|
491
|
-
let mode: Mode =
|
|
578
|
+
let mode: Mode = INITIAL_MODE;
|
|
492
579
|
let registeredToolRenderers = false;
|
|
493
580
|
let installedEditorFactory: EditorFactory | undefined;
|
|
494
581
|
let previousEditorFactory: EditorFactory | undefined;
|
|
495
|
-
|
|
582
|
+
let editor: ToolDisplayEditor | undefined;
|
|
583
|
+
let context: ExtensionContext | undefined;
|
|
584
|
+
let sessionKey: string | undefined;
|
|
585
|
+
let activityView: object | undefined;
|
|
586
|
+
let requestRender: (() => void) | undefined;
|
|
587
|
+
let promptActive = false;
|
|
588
|
+
let compacting = false;
|
|
589
|
+
const activities = new Map<ActivitySource, string>();
|
|
590
|
+
|
|
591
|
+
const getActivity = (): ActivityState | undefined => {
|
|
592
|
+
if (
|
|
593
|
+
!context ||
|
|
594
|
+
!activityView ||
|
|
595
|
+
context.ui.getEditorComponent() !== installedEditorFactory ||
|
|
596
|
+
context.ui.getToolsExpanded() ||
|
|
597
|
+
promptActive ||
|
|
598
|
+
compacting
|
|
599
|
+
)
|
|
600
|
+
return undefined;
|
|
601
|
+
const text = [activities.get("subagent"), activities.get("review")].filter(Boolean).join(", ");
|
|
602
|
+
return text ? { text, idle: context.isIdle() } : undefined;
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
const refreshEditorActivity = (): boolean => {
|
|
606
|
+
if (context && context.ui.getEditorComponent() !== installedEditorFactory) {
|
|
607
|
+
editor?.disposeActivity();
|
|
608
|
+
editor = undefined;
|
|
609
|
+
}
|
|
610
|
+
return editor?.refreshActivity() ?? false;
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const refreshActivity = (): void => {
|
|
614
|
+
refreshEditorActivity();
|
|
615
|
+
requestRender?.();
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
pi.events.on("tau:activity", (data) => {
|
|
619
|
+
if (!context || !isObject(data) || data.sessionKey !== sessionKey) return;
|
|
620
|
+
if (data.source !== "subagent" && data.source !== "review") return;
|
|
621
|
+
if (data.text !== undefined && typeof data.text !== "string") return;
|
|
622
|
+
const text =
|
|
623
|
+
typeof data.text === "string"
|
|
624
|
+
? stripVTControlCharacters(data.text).replace(/\s+/g, " ").trim()
|
|
625
|
+
: undefined;
|
|
626
|
+
const changed = activities.get(data.source) !== (text || undefined);
|
|
627
|
+
if (text) activities.set(data.source, text);
|
|
628
|
+
else activities.delete(data.source);
|
|
629
|
+
data.handled = refreshEditorActivity();
|
|
630
|
+
if (changed) requestRender?.();
|
|
631
|
+
});
|
|
632
|
+
pi.on("agent_start", refreshActivity);
|
|
633
|
+
pi.on("agent_settled", refreshActivity);
|
|
634
|
+
pi.on("ui_prompt_start", () => {
|
|
635
|
+
promptActive = true;
|
|
636
|
+
refreshActivity();
|
|
637
|
+
});
|
|
638
|
+
pi.on("ui_prompt_end", () => {
|
|
639
|
+
promptActive = false;
|
|
640
|
+
refreshActivity();
|
|
641
|
+
});
|
|
642
|
+
pi.on("session_before_compact", () => {
|
|
643
|
+
compacting = true;
|
|
644
|
+
refreshActivity();
|
|
645
|
+
});
|
|
646
|
+
const finishCompaction = (): void => {
|
|
647
|
+
compacting = false;
|
|
648
|
+
refreshActivity();
|
|
649
|
+
};
|
|
650
|
+
pi.on("session_compact", finishCompaction);
|
|
651
|
+
pi.on("session_compact_failed", finishCompaction);
|
|
496
652
|
|
|
497
653
|
const setMode = (ctx: ExtensionContext, next: Mode): void => {
|
|
498
654
|
mode = next;
|
|
499
655
|
applyMode(ctx, mode);
|
|
656
|
+
refreshActivity();
|
|
500
657
|
showModeChange(ctx, mode);
|
|
501
658
|
|
|
502
659
|
void saveConfig({ mode }).catch((error) => reportSaveError(ctx, error));
|
|
503
660
|
};
|
|
504
661
|
|
|
505
662
|
pi.on("session_start", async (_event, ctx) => {
|
|
663
|
+
if (!ctx.hasUI) return;
|
|
506
664
|
mode = (await loadConfig()).mode;
|
|
507
665
|
|
|
508
666
|
if (!registeredToolRenderers) {
|
|
@@ -510,29 +668,74 @@ export default function toolDisplayModeExtension(pi: ExtensionAPI): void {
|
|
|
510
668
|
for (const name of Object.keys(TOOL_FACTORIES) as ToolName[]) {
|
|
511
669
|
if (!shouldRegisterToolRenderer(tools, name)) continue;
|
|
512
670
|
|
|
513
|
-
pi.registerTool(
|
|
514
|
-
createToolDisplayDefinition({ cache: toolCache, name, getMode: () => mode }),
|
|
515
|
-
);
|
|
671
|
+
pi.registerTool(createToolDisplayDefinition({ name, getMode: () => mode }));
|
|
516
672
|
}
|
|
517
673
|
registeredToolRenderers = true;
|
|
518
674
|
}
|
|
519
675
|
|
|
520
676
|
applyMode(ctx, mode);
|
|
521
|
-
if (!ctx.hasUI) return;
|
|
522
677
|
|
|
523
|
-
if (ctx.ui.getEditorComponent() === installedEditorFactory) return;
|
|
678
|
+
if (installedEditorFactory && ctx.ui.getEditorComponent() === installedEditorFactory) return;
|
|
524
679
|
|
|
680
|
+
context = ctx.mode === "tui" ? ctx : undefined;
|
|
681
|
+
sessionKey =
|
|
682
|
+
ctx.sessionManager.getSessionFile() ?? `session:${ctx.sessionManager.getSessionId()}`;
|
|
525
683
|
previousEditorFactory = ctx.ui.getEditorComponent();
|
|
526
684
|
installedEditorFactory = (tui, theme, keybindings) => {
|
|
685
|
+
editor?.disposeActivity();
|
|
527
686
|
const baseEditor =
|
|
528
687
|
previousEditorFactory?.(tui, theme, keybindings) ??
|
|
529
|
-
new CustomEditor(tui, theme, keybindings);
|
|
530
|
-
|
|
688
|
+
new CustomEditor(tui, theme, keybindings, { embedWorkingStatus: true });
|
|
689
|
+
editor = new ToolDisplayEditor(
|
|
690
|
+
baseEditor,
|
|
691
|
+
keybindings,
|
|
692
|
+
() => setMode(ctx, nextMode(mode)),
|
|
693
|
+
tui,
|
|
694
|
+
getActivity,
|
|
695
|
+
(message) => ctx.ui.setWorkingMessage(message),
|
|
696
|
+
(text) =>
|
|
697
|
+
(
|
|
698
|
+
baseEditor.borderColor ??
|
|
699
|
+
ctx.ui.theme.getThinkingBorderColor(ctx.thinkingLevel ?? "off")
|
|
700
|
+
)(text),
|
|
701
|
+
);
|
|
702
|
+
return editor;
|
|
531
703
|
};
|
|
532
704
|
ctx.ui.setEditorComponent(installedEditorFactory);
|
|
705
|
+
if (ctx.mode === "tui") {
|
|
706
|
+
ctx.ui.setWidget(ACTIVITY_WIDGET, (tui) => {
|
|
707
|
+
const owner = {};
|
|
708
|
+
activityView = owner;
|
|
709
|
+
requestRender = () => tui.requestRender();
|
|
710
|
+
return {
|
|
711
|
+
// Observe native redraws for expansion changes and replacement editors without polling.
|
|
712
|
+
render() {
|
|
713
|
+
if (activityView === owner) refreshEditorActivity();
|
|
714
|
+
return [];
|
|
715
|
+
},
|
|
716
|
+
invalidate() {},
|
|
717
|
+
dispose() {
|
|
718
|
+
if (activityView !== owner) return;
|
|
719
|
+
activityView = undefined;
|
|
720
|
+
requestRender = undefined;
|
|
721
|
+
editor?.disposeActivity();
|
|
722
|
+
},
|
|
723
|
+
};
|
|
724
|
+
});
|
|
725
|
+
}
|
|
533
726
|
});
|
|
534
727
|
|
|
535
728
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
729
|
+
activityView = undefined;
|
|
730
|
+
editor?.disposeActivity();
|
|
731
|
+
if (context) context.ui.setWidget(ACTIVITY_WIDGET, undefined);
|
|
732
|
+
context = undefined;
|
|
733
|
+
sessionKey = undefined;
|
|
734
|
+
activities.clear();
|
|
735
|
+
promptActive = false;
|
|
736
|
+
compacting = false;
|
|
737
|
+
requestRender = undefined;
|
|
738
|
+
editor = undefined;
|
|
536
739
|
if (ctx.hasUI && ctx.ui.getEditorComponent() === installedEditorFactory) {
|
|
537
740
|
ctx.ui.setEditorComponent(previousEditorFactory);
|
|
538
741
|
}
|