tau-all-agent 0.1.4 → 0.1.6
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 +1 -1
- package/extensions/answer.ts +59 -47
- package/extensions/branch-term.ts +8 -13
- package/extensions/btw.ts +77 -6
- package/extensions/ghostty.ts +64 -11
- package/extensions/insights.ts +42 -4
- package/extensions/loop.ts +51 -12
- package/extensions/memory.ts +35 -17
- package/extensions/notify.ts +24 -5
- package/extensions/openai-fast.ts +6 -4
- package/extensions/openai-verbosity.ts +6 -4
- package/extensions/review/fix.ts +239 -0
- package/extensions/review/git.ts +517 -0
- package/extensions/review/index.ts +232 -0
- package/extensions/review/message-queue.ts +249 -0
- package/extensions/review/models.ts +589 -0
- package/extensions/review/prompts.ts +337 -0
- package/extensions/review/renderers/inline.ts +126 -0
- package/extensions/review/request.ts +504 -0
- package/extensions/review/review.ts +1305 -0
- package/extensions/review/runner.ts +522 -0
- package/extensions/review/runtime.ts +275 -0
- package/extensions/review/schema.ts +160 -0
- package/extensions/review/submit-review-tool.ts +62 -0
- package/extensions/review/submit-triage-tool.ts +60 -0
- package/extensions/review/triage.ts +746 -0
- package/extensions/sandbox/index.ts +976 -185
- package/extensions/sandbox/macos-sandbox-shell.mjs +202 -0
- package/extensions/{interlude.ts → stash.ts} +13 -11
- package/extensions/telegram/README.md +9 -5
- package/extensions/telegram/assistant-result.d.mts +13 -0
- package/extensions/telegram/assistant-result.mjs +52 -0
- package/extensions/telegram/daemon.mjs +334 -42
- package/extensions/telegram/index.ts +274 -20
- package/extensions/tool-display-mode.ts +2 -3
- package/extensions/usage/anthropic.ts +5 -6
- package/extensions/usage/github-copilot.ts +30 -9
- package/extensions/usage/index.ts +9 -5
- package/extensions/usage/openai-codex.ts +2 -2
- package/extensions/websearch/README.md +1 -1
- package/extensions/websearch/browser/chromium.ts +42 -31
- package/extensions/websearch/browser/discovery.ts +11 -9
- package/extensions/websearch/browser/firefox.ts +10 -7
- package/extensions/websearch/browser/fs.ts +10 -0
- package/extensions/websearch/browser/sqlite.ts +22 -15
- package/extensions/websearch/config.ts +2 -2
- package/extensions/websearch/index.ts +6 -6
- package/extensions/websearch/providers/pi-model.shared.ts +19 -1
- package/extensions/worktree.ts +33 -33
- package/package.json +4 -4
- package/skills/git-clean-history/SKILL.md +1 -0
- package/skills/git-commit/SKILL.md +2 -2
- package/skills/oracle/SKILL.md +3 -3
- package/skills/oracle/scripts/oracle +82 -61
- package/themes/tau-dark.json +2 -1
- package/extensions/review.ts +0 -4420
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ pi install -l npm:tau-all-agent
|
|
|
27
27
|
| `git-diff-stats` | — | Status bar diff stats for local changes in the current repo. |
|
|
28
28
|
| `git-pr-status` | — | Status bar PR number and link for the current branch. |
|
|
29
29
|
| `insights` | `/insights` | Analyze Pi sessions and suggest reusable instructions, templates, skills, and extensions. |
|
|
30
|
-
| `
|
|
30
|
+
| `stash` | `alt+x` | Stash the current message draft, send one message, then restore it. |
|
|
31
31
|
| `loop` | `/loop` | Repeat a prompt until the agent signals success. |
|
|
32
32
|
| `memory` | `/memory` | Opt-in project-local memory for learning and continuity across sessions. |
|
|
33
33
|
| `notify` | — | Terminal notification when the agent is waiting for input. |
|
package/extensions/answer.ts
CHANGED
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
* 4. Submits the compiled answers when done
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { complete
|
|
14
|
-
import type {
|
|
15
|
-
import { BorderedLoader } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { complete } from "@earendil-works/pi-ai/compat";
|
|
14
|
+
import type { Model, Api, UserMessage } from "@earendil-works/pi-ai";
|
|
15
|
+
import { BorderedLoader, type ExtensionAPI, type Theme } from "@earendil-works/pi-coding-agent";
|
|
16
16
|
import {
|
|
17
17
|
type Component,
|
|
18
18
|
Editor,
|
|
19
19
|
type EditorTheme,
|
|
20
|
+
type Focusable,
|
|
20
21
|
Key,
|
|
21
22
|
matchesKey,
|
|
22
23
|
truncateToWidth,
|
|
@@ -94,14 +95,19 @@ interface ModelRequestAuth {
|
|
|
94
95
|
model: Model<Api>;
|
|
95
96
|
apiKey?: string;
|
|
96
97
|
headers?: Record<string, string>;
|
|
98
|
+
env?: Record<string, string>;
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
type ModelAuthLookup = {
|
|
100
102
|
find: (provider: string, modelId: string) => Model<Api> | undefined;
|
|
101
|
-
getApiKeyAndHeaders: (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
getApiKeyAndHeaders: (model: Model<Api>) => Promise<
|
|
104
|
+
| {
|
|
105
|
+
ok: true;
|
|
106
|
+
apiKey?: string;
|
|
107
|
+
headers?: Record<string, string>;
|
|
108
|
+
env?: Record<string, string>;
|
|
109
|
+
}
|
|
110
|
+
| { ok: false; error: string }
|
|
105
111
|
>;
|
|
106
112
|
};
|
|
107
113
|
|
|
@@ -148,7 +154,7 @@ async function resolveModelRequestAuth(
|
|
|
148
154
|
model: Model<Api>,
|
|
149
155
|
): Promise<ModelRequestAuth | null> {
|
|
150
156
|
const auth = await modelRegistry.getApiKeyAndHeaders(model);
|
|
151
|
-
return auth.ok ? { model, apiKey: auth.apiKey, headers: auth.headers } : null;
|
|
157
|
+
return auth.ok ? { model, apiKey: auth.apiKey, headers: auth.headers, env: auth.env } : null;
|
|
152
158
|
}
|
|
153
159
|
|
|
154
160
|
/**
|
|
@@ -178,12 +184,13 @@ function parseExtractionResult(text: string): ExtractionResult | null {
|
|
|
178
184
|
/**
|
|
179
185
|
* Interactive Q&A component for answering extracted questions
|
|
180
186
|
*/
|
|
181
|
-
class QnAComponent implements Component {
|
|
187
|
+
class QnAComponent implements Component, Focusable {
|
|
182
188
|
private questions: ExtractedQuestion[];
|
|
183
189
|
private answers: string[];
|
|
184
190
|
private currentIndex: number = 0;
|
|
185
191
|
private editor: Editor;
|
|
186
192
|
private tui: TUI;
|
|
193
|
+
private theme: Theme;
|
|
187
194
|
private onDone: (result: string | null) => void;
|
|
188
195
|
private showingConfirmation: boolean = false;
|
|
189
196
|
|
|
@@ -191,30 +198,38 @@ class QnAComponent implements Component {
|
|
|
191
198
|
private cachedWidth?: number;
|
|
192
199
|
private cachedLines?: string[];
|
|
193
200
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
+
private _focused = false;
|
|
202
|
+
|
|
203
|
+
get focused(): boolean {
|
|
204
|
+
return this._focused;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
set focused(value: boolean) {
|
|
208
|
+
this._focused = value;
|
|
209
|
+
this.editor.focused = value;
|
|
210
|
+
}
|
|
201
211
|
|
|
202
|
-
constructor(
|
|
212
|
+
constructor(
|
|
213
|
+
questions: ExtractedQuestion[],
|
|
214
|
+
tui: TUI,
|
|
215
|
+
theme: Theme,
|
|
216
|
+
onDone: (result: string | null) => void,
|
|
217
|
+
) {
|
|
203
218
|
this.questions = questions;
|
|
204
219
|
this.answers = questions.map(() => "");
|
|
205
220
|
this.tui = tui;
|
|
221
|
+
this.theme = theme;
|
|
206
222
|
this.onDone = onDone;
|
|
207
223
|
|
|
208
|
-
|
|
209
|
-
const selectedLine = (s: string) => `\x1b[44m${s}\x1b[0m`;
|
|
224
|
+
const selectedLine = (s: string) => theme.bg("selectedBg", s);
|
|
210
225
|
const editorTheme: EditorTheme = {
|
|
211
|
-
borderColor:
|
|
226
|
+
borderColor: (s) => theme.fg("dim", s),
|
|
212
227
|
selectList: {
|
|
213
228
|
selectedPrefix: selectedLine,
|
|
214
229
|
selectedText: selectedLine,
|
|
215
|
-
description:
|
|
216
|
-
scrollInfo:
|
|
217
|
-
noMatch:
|
|
230
|
+
description: (s) => theme.fg("muted", s),
|
|
231
|
+
scrollInfo: (s) => theme.fg("muted", s),
|
|
232
|
+
noMatch: (s) => theme.fg("warning", s),
|
|
218
233
|
},
|
|
219
234
|
};
|
|
220
235
|
|
|
@@ -228,11 +243,6 @@ class QnAComponent implements Component {
|
|
|
228
243
|
};
|
|
229
244
|
}
|
|
230
245
|
|
|
231
|
-
private allQuestionsAnswered(): boolean {
|
|
232
|
-
this.saveCurrentAnswer();
|
|
233
|
-
return this.answers.every((a) => (a?.trim() || "").length > 0);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
246
|
private saveCurrentAnswer(): void {
|
|
237
247
|
this.answers[this.currentIndex] = this.editor.getText();
|
|
238
248
|
}
|
|
@@ -362,6 +372,7 @@ class QnAComponent implements Component {
|
|
|
362
372
|
return this.cachedLines;
|
|
363
373
|
}
|
|
364
374
|
|
|
375
|
+
const theme = this.theme;
|
|
365
376
|
const lines: string[] = [];
|
|
366
377
|
const boxWidth = Math.min(width - 4, 120); // Allow wider box
|
|
367
378
|
const contentWidth = boxWidth - 4; // 2 chars padding on each side
|
|
@@ -374,11 +385,11 @@ class QnAComponent implements Component {
|
|
|
374
385
|
const paddedContent = " ".repeat(leftPad) + content;
|
|
375
386
|
const contentLen = visibleWidth(paddedContent);
|
|
376
387
|
const rightPad = Math.max(0, boxWidth - contentLen - 2);
|
|
377
|
-
return
|
|
388
|
+
return theme.fg("dim", "│") + paddedContent + " ".repeat(rightPad) + theme.fg("dim", "│");
|
|
378
389
|
};
|
|
379
390
|
|
|
380
391
|
const emptyBoxLine = (): string => {
|
|
381
|
-
return
|
|
392
|
+
return theme.fg("dim", "│") + " ".repeat(boxWidth - 2) + theme.fg("dim", "│");
|
|
382
393
|
};
|
|
383
394
|
|
|
384
395
|
const padToWidth = (line: string): string => {
|
|
@@ -387,10 +398,10 @@ class QnAComponent implements Component {
|
|
|
387
398
|
};
|
|
388
399
|
|
|
389
400
|
// Title
|
|
390
|
-
lines.push(padToWidth(
|
|
391
|
-
const title = `${
|
|
401
|
+
lines.push(padToWidth(theme.fg("dim", "╭" + horizontalLine(boxWidth - 2) + "╮")));
|
|
402
|
+
const title = `${theme.bold(theme.fg("accent", "Questions"))} ${theme.fg("dim", `(${this.currentIndex + 1}/${this.questions.length})`)}`;
|
|
392
403
|
lines.push(padToWidth(boxLine(title)));
|
|
393
|
-
lines.push(padToWidth(
|
|
404
|
+
lines.push(padToWidth(theme.fg("dim", "├" + horizontalLine(boxWidth - 2) + "┤")));
|
|
394
405
|
|
|
395
406
|
// Progress indicator
|
|
396
407
|
const progressParts: string[] = [];
|
|
@@ -398,11 +409,11 @@ class QnAComponent implements Component {
|
|
|
398
409
|
const answered = (this.answers[i]?.trim() || "").length > 0;
|
|
399
410
|
const current = i === this.currentIndex;
|
|
400
411
|
if (current) {
|
|
401
|
-
progressParts.push(
|
|
412
|
+
progressParts.push(theme.fg("accent", "●"));
|
|
402
413
|
} else if (answered) {
|
|
403
|
-
progressParts.push(
|
|
414
|
+
progressParts.push(theme.fg("success", "●"));
|
|
404
415
|
} else {
|
|
405
|
-
progressParts.push(
|
|
416
|
+
progressParts.push(theme.fg("dim", "○"));
|
|
406
417
|
}
|
|
407
418
|
}
|
|
408
419
|
lines.push(padToWidth(boxLine(progressParts.join(" "))));
|
|
@@ -419,7 +430,7 @@ class QnAComponent implements Component {
|
|
|
419
430
|
// Context if present
|
|
420
431
|
if (q.context) {
|
|
421
432
|
lines.push(padToWidth(emptyBoxLine()));
|
|
422
|
-
const contextText =
|
|
433
|
+
const contextText = theme.fg("muted", `${q.context}`);
|
|
423
434
|
const wrappedContext = wrapTextWithAnsi(contextText, contentWidth - 2);
|
|
424
435
|
for (const line of wrappedContext) {
|
|
425
436
|
lines.push(padToWidth(boxLine(line)));
|
|
@@ -430,7 +441,7 @@ class QnAComponent implements Component {
|
|
|
430
441
|
|
|
431
442
|
// Render the editor component (multi-line input) with padding
|
|
432
443
|
// Skip the first and last lines (editor's own border lines)
|
|
433
|
-
const answerPrefix =
|
|
444
|
+
const answerPrefix = theme.bold("A: ");
|
|
434
445
|
const editorWidth = contentWidth - 4 - 3; // Extra padding + space for "A: "
|
|
435
446
|
const editorLines = this.editor.render(editorWidth);
|
|
436
447
|
for (let i = 1; i < editorLines.length - 1; i++) {
|
|
@@ -447,15 +458,15 @@ class QnAComponent implements Component {
|
|
|
447
458
|
|
|
448
459
|
// Confirmation dialog or footer with controls
|
|
449
460
|
if (this.showingConfirmation) {
|
|
450
|
-
lines.push(padToWidth(
|
|
451
|
-
const confirmMsg = `${
|
|
461
|
+
lines.push(padToWidth(theme.fg("dim", "├" + horizontalLine(boxWidth - 2) + "┤")));
|
|
462
|
+
const confirmMsg = `${theme.fg("warning", "Submit all answers?")} ${theme.fg("dim", "(Enter/y to confirm, Esc/n to cancel)")}`;
|
|
452
463
|
lines.push(padToWidth(boxLine(truncateToWidth(confirmMsg, contentWidth))));
|
|
453
464
|
} else {
|
|
454
|
-
lines.push(padToWidth(
|
|
455
|
-
const controls = `${
|
|
465
|
+
lines.push(padToWidth(theme.fg("dim", "├" + horizontalLine(boxWidth - 2) + "┤")));
|
|
466
|
+
const controls = `${theme.fg("dim", "Tab/Enter")} next · ${theme.fg("dim", "Shift+Tab")} prev · ${theme.fg("dim", "Shift+Enter")} newline · ${theme.fg("dim", "Esc")} cancel`;
|
|
456
467
|
lines.push(padToWidth(boxLine(truncateToWidth(controls, contentWidth))));
|
|
457
468
|
}
|
|
458
|
-
lines.push(padToWidth(
|
|
469
|
+
lines.push(padToWidth(theme.fg("dim", "╰" + horizontalLine(boxWidth - 2) + "╯")));
|
|
459
470
|
|
|
460
471
|
this.cachedWidth = width;
|
|
461
472
|
this.cachedLines = lines;
|
|
@@ -467,8 +478,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
467
478
|
pi.registerCommand("answer", {
|
|
468
479
|
description: "Extract questions from last assistant message into interactive Q&A",
|
|
469
480
|
handler: async (_args, ctx) => {
|
|
470
|
-
if (
|
|
471
|
-
ctx.ui.notify("answer requires
|
|
481
|
+
if (ctx.mode !== "tui") {
|
|
482
|
+
ctx.ui.notify("answer requires TUI mode", "error");
|
|
472
483
|
return;
|
|
473
484
|
}
|
|
474
485
|
|
|
@@ -536,6 +547,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
536
547
|
{
|
|
537
548
|
apiKey: extractionSelection.apiKey,
|
|
538
549
|
headers: extractionSelection.headers,
|
|
550
|
+
env: extractionSelection.env,
|
|
539
551
|
signal: loader.signal,
|
|
540
552
|
},
|
|
541
553
|
);
|
|
@@ -571,8 +583,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
571
583
|
}
|
|
572
584
|
|
|
573
585
|
// Show the Q&A component
|
|
574
|
-
const answersResult = await ctx.ui.custom<string | null>((tui,
|
|
575
|
-
return new QnAComponent(extractionResult.questions, tui, done);
|
|
586
|
+
const answersResult = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
587
|
+
return new QnAComponent(extractionResult.questions, tui, theme, done);
|
|
576
588
|
});
|
|
577
589
|
|
|
578
590
|
if (answersResult === null) {
|
|
@@ -13,7 +13,7 @@ import { Box, Text } from "@earendil-works/pi-tui";
|
|
|
13
13
|
|
|
14
14
|
const TERMINAL_FLAG = "branch-term";
|
|
15
15
|
const TMUX_LAYOUT_FLAG = "branch-tmux-layout";
|
|
16
|
-
const
|
|
16
|
+
const BRANCH_ENTRY_TYPE = "branch-term-message";
|
|
17
17
|
const MANUAL_RESUME_INTRO =
|
|
18
18
|
"Branch session ready. Run this command in a separate terminal or tmux pane";
|
|
19
19
|
|
|
@@ -220,15 +220,10 @@ function showCommandMessage(
|
|
|
220
220
|
return;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
pi.
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
details: {
|
|
228
|
-
intro,
|
|
229
|
-
command,
|
|
230
|
-
copiedToClipboard,
|
|
231
|
-
} satisfies CommandMessageDetails,
|
|
223
|
+
pi.appendEntry<CommandMessageDetails>(BRANCH_ENTRY_TYPE, {
|
|
224
|
+
intro,
|
|
225
|
+
command,
|
|
226
|
+
copiedToClipboard,
|
|
232
227
|
});
|
|
233
228
|
}
|
|
234
229
|
|
|
@@ -266,10 +261,10 @@ function createFreshSessionFile(cwd: string, sessionDir: string): string {
|
|
|
266
261
|
}
|
|
267
262
|
|
|
268
263
|
export default function (pi: ExtensionAPI) {
|
|
269
|
-
pi.
|
|
270
|
-
const details =
|
|
264
|
+
pi.registerEntryRenderer<CommandMessageDetails>(BRANCH_ENTRY_TYPE, (entry, _options, theme) => {
|
|
265
|
+
const details = entry.data;
|
|
271
266
|
if (!details || typeof details.intro !== "string" || typeof details.command !== "string") {
|
|
272
|
-
return
|
|
267
|
+
return undefined;
|
|
273
268
|
}
|
|
274
269
|
|
|
275
270
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
package/extensions/btw.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
InMemoryCredentialStore,
|
|
4
|
+
type AssistantMessage,
|
|
5
|
+
type Provider,
|
|
6
|
+
type ThinkingLevel,
|
|
7
|
+
} from "@earendil-works/pi-ai";
|
|
3
8
|
import {
|
|
4
9
|
DefaultResourceLoader,
|
|
10
|
+
ModelRuntime,
|
|
5
11
|
SessionManager,
|
|
6
12
|
buildSessionContext,
|
|
7
13
|
createAgentSession,
|
|
@@ -59,6 +65,15 @@ type ActiveBtwRequest = {
|
|
|
59
65
|
abort?: () => Promise<void>;
|
|
60
66
|
};
|
|
61
67
|
|
|
68
|
+
type BtwModelRegistry = ExtensionCommandContext["modelRegistry"];
|
|
69
|
+
|
|
70
|
+
type BtwModelRuntimeState = {
|
|
71
|
+
runtime: ModelRuntime;
|
|
72
|
+
providers: Map<string, Provider>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const btwModelRuntimes = new WeakMap<BtwModelRegistry, Promise<BtwModelRuntimeState>>();
|
|
76
|
+
|
|
62
77
|
export default function btwExtension(pi: ExtensionAPI): void {
|
|
63
78
|
let activeRequest: ActiveBtwRequest | null = null;
|
|
64
79
|
let statusTimer: ReturnType<typeof setInterval> | null = null;
|
|
@@ -116,8 +131,8 @@ export default function btwExtension(pi: ExtensionAPI): void {
|
|
|
116
131
|
return;
|
|
117
132
|
}
|
|
118
133
|
|
|
119
|
-
if (
|
|
120
|
-
ctx.ui.notify("btw requires
|
|
134
|
+
if (ctx.mode !== "tui") {
|
|
135
|
+
ctx.ui.notify("btw requires TUI mode", "error");
|
|
121
136
|
return;
|
|
122
137
|
}
|
|
123
138
|
|
|
@@ -227,11 +242,12 @@ async function runBtwRequest(
|
|
|
227
242
|
});
|
|
228
243
|
await resourceLoader.reload();
|
|
229
244
|
|
|
245
|
+
const modelRuntime = await createBtwModelRuntime(snapshot.modelRegistry, snapshot.model.provider);
|
|
230
246
|
const { session } = await createAgentSession({
|
|
231
247
|
cwd: snapshot.cwd,
|
|
232
248
|
model: snapshot.model,
|
|
233
249
|
thinkingLevel: snapshot.thinkingLevel,
|
|
234
|
-
|
|
250
|
+
modelRuntime,
|
|
235
251
|
resourceLoader,
|
|
236
252
|
tools: BTW_TOOL_NAMES,
|
|
237
253
|
sessionManager,
|
|
@@ -269,6 +285,61 @@ async function runBtwRequest(
|
|
|
269
285
|
}
|
|
270
286
|
}
|
|
271
287
|
|
|
288
|
+
async function createBtwModelRuntime(
|
|
289
|
+
modelRegistry: BtwModelRegistry,
|
|
290
|
+
providerId: string,
|
|
291
|
+
): Promise<ModelRuntime> {
|
|
292
|
+
const provider = modelRegistry.getProvider(providerId);
|
|
293
|
+
if (!provider) {
|
|
294
|
+
throw new Error(`Model provider is unavailable: ${providerId}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const state = await getBtwModelRuntimeState(modelRegistry);
|
|
298
|
+
if (state.providers.get(providerId) !== provider) {
|
|
299
|
+
state.runtime.registerNativeProvider(withParentAuth(provider, modelRegistry));
|
|
300
|
+
state.providers.set(providerId, provider);
|
|
301
|
+
}
|
|
302
|
+
return state.runtime;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function getBtwModelRuntimeState(
|
|
306
|
+
modelRegistry: BtwModelRegistry,
|
|
307
|
+
): Promise<BtwModelRuntimeState> {
|
|
308
|
+
let state = btwModelRuntimes.get(modelRegistry);
|
|
309
|
+
if (!state) {
|
|
310
|
+
state = ModelRuntime.create({
|
|
311
|
+
credentials: new InMemoryCredentialStore(),
|
|
312
|
+
modelsPath: null,
|
|
313
|
+
}).then((runtime) => ({ runtime, providers: new Map() }));
|
|
314
|
+
btwModelRuntimes.set(modelRegistry, state);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
return await state;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (btwModelRuntimes.get(modelRegistry) === state) {
|
|
321
|
+
btwModelRuntimes.delete(modelRegistry);
|
|
322
|
+
}
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function withParentAuth(provider: Provider, modelRegistry: BtwModelRegistry): Provider {
|
|
328
|
+
return {
|
|
329
|
+
...provider,
|
|
330
|
+
refreshModels: undefined, // The parent runtime owns model catalog refreshes.
|
|
331
|
+
auth: {
|
|
332
|
+
...provider.auth,
|
|
333
|
+
apiKey: {
|
|
334
|
+
name: `${provider.name} session authentication`,
|
|
335
|
+
async resolve() {
|
|
336
|
+
return modelRegistry.getProviderAuth(provider.id);
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
272
343
|
function seedSessionManager(sessionManager: SessionManager, messages: AgentMessage[]): void {
|
|
273
344
|
type SessionMessageInput = Parameters<SessionManager["appendMessage"]>[0];
|
|
274
345
|
|
|
@@ -278,8 +349,8 @@ function seedSessionManager(sessionManager: SessionManager, messages: AgentMessa
|
|
|
278
349
|
}
|
|
279
350
|
|
|
280
351
|
async function showResultDialog(ctx: ExtensionContext, result: BtwResult): Promise<void> {
|
|
281
|
-
await ctx.ui.custom<void>((tui,
|
|
282
|
-
return new BtwResultComponent(result, tui,
|
|
352
|
+
await ctx.ui.custom<void>((tui, theme, _keybindings, done) => {
|
|
353
|
+
return new BtwResultComponent(result, tui, theme, done);
|
|
283
354
|
});
|
|
284
355
|
}
|
|
285
356
|
|
package/extensions/ghostty.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - Shows project/session in the terminal title
|
|
5
5
|
* - Shows a braille spinner in the title while the agent is working
|
|
6
|
+
* - Shows the braille spinner backwards while context is compacting
|
|
6
7
|
* - Shows a ? marker while an extension prompt is waiting for input
|
|
7
8
|
* - Updates title with the current tool name during tool execution
|
|
8
9
|
* - Treats background /review runs as working state via review:start/review:end events
|
|
@@ -22,6 +23,7 @@ let sessionName: string | undefined;
|
|
|
22
23
|
let sessionCwd: string | undefined;
|
|
23
24
|
let currentTool: string | undefined;
|
|
24
25
|
let isWorking = false;
|
|
26
|
+
let isCompacting = false;
|
|
25
27
|
let frameIndex = 0;
|
|
26
28
|
let spinnerTimer: ReturnType<typeof setInterval> | undefined;
|
|
27
29
|
let pendingPromptCount = 0;
|
|
@@ -47,6 +49,15 @@ function currentFrame(): string {
|
|
|
47
49
|
return STATUS_SPINNER_FRAMES[frameIndex % STATUS_SPINNER_FRAMES.length];
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
function resetFrame(): void {
|
|
53
|
+
frameIndex = 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function advanceFrame(): void {
|
|
57
|
+
const step = isCompacting ? -1 : 1;
|
|
58
|
+
frameIndex = (frameIndex + step + STATUS_SPINNER_FRAMES.length) % STATUS_SPINNER_FRAMES.length;
|
|
59
|
+
}
|
|
60
|
+
|
|
50
61
|
function hasPendingPrompts(): boolean {
|
|
51
62
|
return pendingPromptCount > 0;
|
|
52
63
|
}
|
|
@@ -69,11 +80,12 @@ function hasActiveReviewRuns(): boolean {
|
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
function isBusy(): boolean {
|
|
72
|
-
return isWorking || hasActiveReviewRuns();
|
|
83
|
+
return isWorking || isCompacting || hasActiveReviewRuns();
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
function getWorkingExtra(): string | undefined {
|
|
76
87
|
if (currentTool) return currentTool;
|
|
88
|
+
if (isCompacting) return "compacting";
|
|
77
89
|
if (!isWorking && hasActiveReviewRuns()) return "review";
|
|
78
90
|
return undefined;
|
|
79
91
|
}
|
|
@@ -105,7 +117,7 @@ function startSpinnerTimer(ctx: ExtensionContext): void {
|
|
|
105
117
|
clearSpinnerTimer();
|
|
106
118
|
spinnerTimer = setInterval(() => {
|
|
107
119
|
if (!isBusy() || hasPendingPrompts()) return;
|
|
108
|
-
|
|
120
|
+
advanceFrame();
|
|
109
121
|
renderWorkingTitle(ctx);
|
|
110
122
|
}, STATUS_SPINNER_INTERVAL_MS);
|
|
111
123
|
}
|
|
@@ -114,7 +126,7 @@ function startSpinner(ctx: ExtensionContext): void {
|
|
|
114
126
|
clearSpinnerTimer();
|
|
115
127
|
isWorking = true;
|
|
116
128
|
currentTool = undefined;
|
|
117
|
-
|
|
129
|
+
resetFrame();
|
|
118
130
|
renderActiveTitle(ctx);
|
|
119
131
|
|
|
120
132
|
if (!hasPendingPrompts()) {
|
|
@@ -132,7 +144,7 @@ function stopSpinner(ctx: ExtensionContext): void {
|
|
|
132
144
|
return;
|
|
133
145
|
}
|
|
134
146
|
|
|
135
|
-
if (
|
|
147
|
+
if (isBusy()) {
|
|
136
148
|
renderWorkingTitle(ctx);
|
|
137
149
|
startSpinnerTimer(ctx);
|
|
138
150
|
return;
|
|
@@ -161,6 +173,38 @@ function handlePromptEnd(ctx: ExtensionContext): void {
|
|
|
161
173
|
renderActiveTitle(ctx);
|
|
162
174
|
}
|
|
163
175
|
|
|
176
|
+
function startCompaction(ctx: ExtensionContext, signal: AbortSignal): void {
|
|
177
|
+
isWorking = false;
|
|
178
|
+
isCompacting = true;
|
|
179
|
+
currentTool = undefined;
|
|
180
|
+
resetFrame();
|
|
181
|
+
signal.addEventListener("abort", () => stopCompaction(ctx), { once: true });
|
|
182
|
+
|
|
183
|
+
renderActiveTitle(ctx);
|
|
184
|
+
if (!hasPendingPrompts()) {
|
|
185
|
+
startSpinnerTimer(ctx);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function stopCompaction(ctx: ExtensionContext, options?: { render?: boolean }): void {
|
|
190
|
+
if (!isCompacting) return;
|
|
191
|
+
|
|
192
|
+
isCompacting = false;
|
|
193
|
+
|
|
194
|
+
if (options?.render === false) return;
|
|
195
|
+
|
|
196
|
+
if (isBusy()) {
|
|
197
|
+
renderActiveTitle(ctx);
|
|
198
|
+
if (!hasPendingPrompts()) {
|
|
199
|
+
startSpinnerTimer(ctx);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
clearSpinnerTimer();
|
|
205
|
+
renderActiveTitle(ctx);
|
|
206
|
+
}
|
|
207
|
+
|
|
164
208
|
function syncSessionTitle(ctx: ExtensionContext): void {
|
|
165
209
|
renderActiveTitle(ctx);
|
|
166
210
|
}
|
|
@@ -181,17 +225,12 @@ function handleReviewStart(ctx: ExtensionContext): void {
|
|
|
181
225
|
}
|
|
182
226
|
|
|
183
227
|
function handleReviewEnd(ctx: ExtensionContext): void {
|
|
184
|
-
if (isWorking) {
|
|
185
|
-
renderActiveTitle(ctx);
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
228
|
if (hasPendingPrompts()) {
|
|
190
229
|
renderActiveTitle(ctx);
|
|
191
230
|
return;
|
|
192
231
|
}
|
|
193
232
|
|
|
194
|
-
if (
|
|
233
|
+
if (isBusy()) {
|
|
195
234
|
renderActiveTitle(ctx);
|
|
196
235
|
startSpinnerTimer(ctx);
|
|
197
236
|
return;
|
|
@@ -218,10 +257,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
218
257
|
pi.on("agent_start", async (_event, ctx) => {
|
|
219
258
|
if (!ctx.hasUI) return;
|
|
220
259
|
latestCtx = ctx;
|
|
260
|
+
stopCompaction(ctx, { render: false });
|
|
221
261
|
startSpinner(ctx);
|
|
222
262
|
});
|
|
223
263
|
|
|
224
|
-
pi.on("
|
|
264
|
+
pi.on("agent_settled", async (_event, ctx) => {
|
|
225
265
|
if (!ctx.hasUI) return;
|
|
226
266
|
latestCtx = ctx;
|
|
227
267
|
stopSpinner(ctx);
|
|
@@ -243,6 +283,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
243
283
|
renderActiveTitle(ctx);
|
|
244
284
|
});
|
|
245
285
|
|
|
286
|
+
pi.on("session_before_compact", async (event, ctx) => {
|
|
287
|
+
if (!ctx.hasUI) return;
|
|
288
|
+
latestCtx = ctx;
|
|
289
|
+
startCompaction(ctx, event.signal);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
pi.on("session_compact", async (_event, ctx) => {
|
|
293
|
+
if (!ctx.hasUI) return;
|
|
294
|
+
latestCtx = ctx;
|
|
295
|
+
stopCompaction(ctx);
|
|
296
|
+
});
|
|
297
|
+
|
|
246
298
|
pi.events.on("ui:prompt_start", () => {
|
|
247
299
|
pendingPromptCount += 1;
|
|
248
300
|
|
|
@@ -287,6 +339,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
287
339
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
288
340
|
clearSpinnerTimer();
|
|
289
341
|
isWorking = false;
|
|
342
|
+
isCompacting = false;
|
|
290
343
|
currentTool = undefined;
|
|
291
344
|
pendingPromptCount = 0;
|
|
292
345
|
const sessionKey = getSessionKey(ctx);
|