tau-coding-agent 0.1.3 → 0.1.5
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 +49 -43
- package/extensions/branch-term.ts +11 -16
- package/extensions/btw.ts +8 -8
- package/extensions/ghostty.ts +65 -12
- package/extensions/git-diff-stats.ts +1 -1
- package/extensions/git-pr-status.ts +1 -1
- package/extensions/insights.ts +6 -5
- package/extensions/loop.ts +39 -11
- package/extensions/memory.ts +6 -5
- package/extensions/notify.ts +25 -6
- 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 +66 -0
- package/extensions/review/submit-triage-tool.ts +59 -0
- package/extensions/review/triage.ts +746 -0
- package/extensions/sandbox/index.ts +428 -103
- package/extensions/{interlude.ts → stash.ts} +13 -11
- package/extensions/tool-display-mode.ts +173 -17
- package/extensions/usage/anthropic.ts +1 -1
- package/extensions/usage/github-copilot.ts +1 -1
- package/extensions/usage/index.ts +9 -5
- package/extensions/usage/minimax.ts +1 -1
- package/extensions/usage/openai-codex.ts +1 -1
- package/extensions/usage/openrouter.ts +1 -1
- package/extensions/usage/providers.ts +0 -2
- package/extensions/usage/types.ts +1 -2
- package/extensions/usage/zai.ts +1 -1
- 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 +9 -9
- package/extensions/websearch/providers/anthropic.pi.ts +1 -1
- package/extensions/websearch/providers/gemini.pi.ts +1 -1
- package/extensions/websearch/providers/openai-codex.pi.ts +1 -1
- package/extensions/websearch/providers/pi-model.shared.ts +2 -2
- package/extensions/worktree.ts +36 -36
- package/package.json +7 -7
- package/skills/git-clean-history/SKILL.md +1 -0
- package/skills/git-commit/SKILL.md +2 -2
- package/skills/oracle/scripts/oracle +28 -12
- package/themes/tau-dark.json +2 -1
- package/extensions/review.ts +0 -4347
- package/extensions/usage/google-gemini-cli.ts +0 -232
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ pi install -l npm:tau-coding-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,20 +10,21 @@
|
|
|
10
10
|
* 4. Submits the compiled answers when done
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { complete
|
|
14
|
-
import type {
|
|
15
|
-
import { BorderedLoader } from "@
|
|
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,
|
|
23
24
|
type TUI,
|
|
24
25
|
visibleWidth,
|
|
25
26
|
wrapTextWithAnsi,
|
|
26
|
-
} from "@
|
|
27
|
+
} from "@earendil-works/pi-tui";
|
|
27
28
|
|
|
28
29
|
const SYSTEM_PROMPT = `You extract items that need user input from assistant text:
|
|
29
30
|
|
|
@@ -178,12 +179,13 @@ function parseExtractionResult(text: string): ExtractionResult | null {
|
|
|
178
179
|
/**
|
|
179
180
|
* Interactive Q&A component for answering extracted questions
|
|
180
181
|
*/
|
|
181
|
-
class QnAComponent implements Component {
|
|
182
|
+
class QnAComponent implements Component, Focusable {
|
|
182
183
|
private questions: ExtractedQuestion[];
|
|
183
184
|
private answers: string[];
|
|
184
185
|
private currentIndex: number = 0;
|
|
185
186
|
private editor: Editor;
|
|
186
187
|
private tui: TUI;
|
|
188
|
+
private theme: Theme;
|
|
187
189
|
private onDone: (result: string | null) => void;
|
|
188
190
|
private showingConfirmation: boolean = false;
|
|
189
191
|
|
|
@@ -191,30 +193,38 @@ class QnAComponent implements Component {
|
|
|
191
193
|
private cachedWidth?: number;
|
|
192
194
|
private cachedLines?: string[];
|
|
193
195
|
|
|
194
|
-
|
|
195
|
-
private dim = (s: string) => `\x1b[2m${s}\x1b[0m`;
|
|
196
|
-
private bold = (s: string) => `\x1b[1m${s}\x1b[0m`;
|
|
197
|
-
private cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
|
|
198
|
-
private green = (s: string) => `\x1b[32m${s}\x1b[0m`;
|
|
199
|
-
private yellow = (s: string) => `\x1b[33m${s}\x1b[0m`;
|
|
200
|
-
private gray = (s: string) => `\x1b[90m${s}\x1b[0m`;
|
|
196
|
+
private _focused = false;
|
|
201
197
|
|
|
202
|
-
|
|
198
|
+
get focused(): boolean {
|
|
199
|
+
return this._focused;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
set focused(value: boolean) {
|
|
203
|
+
this._focused = value;
|
|
204
|
+
this.editor.focused = value;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
constructor(
|
|
208
|
+
questions: ExtractedQuestion[],
|
|
209
|
+
tui: TUI,
|
|
210
|
+
theme: Theme,
|
|
211
|
+
onDone: (result: string | null) => void,
|
|
212
|
+
) {
|
|
203
213
|
this.questions = questions;
|
|
204
214
|
this.answers = questions.map(() => "");
|
|
205
215
|
this.tui = tui;
|
|
216
|
+
this.theme = theme;
|
|
206
217
|
this.onDone = onDone;
|
|
207
218
|
|
|
208
|
-
|
|
209
|
-
const selectedLine = (s: string) => `\x1b[44m${s}\x1b[0m`;
|
|
219
|
+
const selectedLine = (s: string) => theme.bg("selectedBg", s);
|
|
210
220
|
const editorTheme: EditorTheme = {
|
|
211
|
-
borderColor:
|
|
221
|
+
borderColor: (s) => theme.fg("dim", s),
|
|
212
222
|
selectList: {
|
|
213
223
|
selectedPrefix: selectedLine,
|
|
214
224
|
selectedText: selectedLine,
|
|
215
|
-
description:
|
|
216
|
-
scrollInfo:
|
|
217
|
-
noMatch:
|
|
225
|
+
description: (s) => theme.fg("muted", s),
|
|
226
|
+
scrollInfo: (s) => theme.fg("muted", s),
|
|
227
|
+
noMatch: (s) => theme.fg("warning", s),
|
|
218
228
|
},
|
|
219
229
|
};
|
|
220
230
|
|
|
@@ -228,11 +238,6 @@ class QnAComponent implements Component {
|
|
|
228
238
|
};
|
|
229
239
|
}
|
|
230
240
|
|
|
231
|
-
private allQuestionsAnswered(): boolean {
|
|
232
|
-
this.saveCurrentAnswer();
|
|
233
|
-
return this.answers.every((a) => (a?.trim() || "").length > 0);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
241
|
private saveCurrentAnswer(): void {
|
|
237
242
|
this.answers[this.currentIndex] = this.editor.getText();
|
|
238
243
|
}
|
|
@@ -362,6 +367,7 @@ class QnAComponent implements Component {
|
|
|
362
367
|
return this.cachedLines;
|
|
363
368
|
}
|
|
364
369
|
|
|
370
|
+
const theme = this.theme;
|
|
365
371
|
const lines: string[] = [];
|
|
366
372
|
const boxWidth = Math.min(width - 4, 120); // Allow wider box
|
|
367
373
|
const contentWidth = boxWidth - 4; // 2 chars padding on each side
|
|
@@ -374,11 +380,11 @@ class QnAComponent implements Component {
|
|
|
374
380
|
const paddedContent = " ".repeat(leftPad) + content;
|
|
375
381
|
const contentLen = visibleWidth(paddedContent);
|
|
376
382
|
const rightPad = Math.max(0, boxWidth - contentLen - 2);
|
|
377
|
-
return
|
|
383
|
+
return theme.fg("dim", "│") + paddedContent + " ".repeat(rightPad) + theme.fg("dim", "│");
|
|
378
384
|
};
|
|
379
385
|
|
|
380
386
|
const emptyBoxLine = (): string => {
|
|
381
|
-
return
|
|
387
|
+
return theme.fg("dim", "│") + " ".repeat(boxWidth - 2) + theme.fg("dim", "│");
|
|
382
388
|
};
|
|
383
389
|
|
|
384
390
|
const padToWidth = (line: string): string => {
|
|
@@ -387,10 +393,10 @@ class QnAComponent implements Component {
|
|
|
387
393
|
};
|
|
388
394
|
|
|
389
395
|
// Title
|
|
390
|
-
lines.push(padToWidth(
|
|
391
|
-
const title = `${
|
|
396
|
+
lines.push(padToWidth(theme.fg("dim", "╭" + horizontalLine(boxWidth - 2) + "╮")));
|
|
397
|
+
const title = `${theme.bold(theme.fg("accent", "Questions"))} ${theme.fg("dim", `(${this.currentIndex + 1}/${this.questions.length})`)}`;
|
|
392
398
|
lines.push(padToWidth(boxLine(title)));
|
|
393
|
-
lines.push(padToWidth(
|
|
399
|
+
lines.push(padToWidth(theme.fg("dim", "├" + horizontalLine(boxWidth - 2) + "┤")));
|
|
394
400
|
|
|
395
401
|
// Progress indicator
|
|
396
402
|
const progressParts: string[] = [];
|
|
@@ -398,11 +404,11 @@ class QnAComponent implements Component {
|
|
|
398
404
|
const answered = (this.answers[i]?.trim() || "").length > 0;
|
|
399
405
|
const current = i === this.currentIndex;
|
|
400
406
|
if (current) {
|
|
401
|
-
progressParts.push(
|
|
407
|
+
progressParts.push(theme.fg("accent", "●"));
|
|
402
408
|
} else if (answered) {
|
|
403
|
-
progressParts.push(
|
|
409
|
+
progressParts.push(theme.fg("success", "●"));
|
|
404
410
|
} else {
|
|
405
|
-
progressParts.push(
|
|
411
|
+
progressParts.push(theme.fg("dim", "○"));
|
|
406
412
|
}
|
|
407
413
|
}
|
|
408
414
|
lines.push(padToWidth(boxLine(progressParts.join(" "))));
|
|
@@ -419,7 +425,7 @@ class QnAComponent implements Component {
|
|
|
419
425
|
// Context if present
|
|
420
426
|
if (q.context) {
|
|
421
427
|
lines.push(padToWidth(emptyBoxLine()));
|
|
422
|
-
const contextText =
|
|
428
|
+
const contextText = theme.fg("muted", `${q.context}`);
|
|
423
429
|
const wrappedContext = wrapTextWithAnsi(contextText, contentWidth - 2);
|
|
424
430
|
for (const line of wrappedContext) {
|
|
425
431
|
lines.push(padToWidth(boxLine(line)));
|
|
@@ -430,7 +436,7 @@ class QnAComponent implements Component {
|
|
|
430
436
|
|
|
431
437
|
// Render the editor component (multi-line input) with padding
|
|
432
438
|
// Skip the first and last lines (editor's own border lines)
|
|
433
|
-
const answerPrefix =
|
|
439
|
+
const answerPrefix = theme.bold("A: ");
|
|
434
440
|
const editorWidth = contentWidth - 4 - 3; // Extra padding + space for "A: "
|
|
435
441
|
const editorLines = this.editor.render(editorWidth);
|
|
436
442
|
for (let i = 1; i < editorLines.length - 1; i++) {
|
|
@@ -447,15 +453,15 @@ class QnAComponent implements Component {
|
|
|
447
453
|
|
|
448
454
|
// Confirmation dialog or footer with controls
|
|
449
455
|
if (this.showingConfirmation) {
|
|
450
|
-
lines.push(padToWidth(
|
|
451
|
-
const confirmMsg = `${
|
|
456
|
+
lines.push(padToWidth(theme.fg("dim", "├" + horizontalLine(boxWidth - 2) + "┤")));
|
|
457
|
+
const confirmMsg = `${theme.fg("warning", "Submit all answers?")} ${theme.fg("dim", "(Enter/y to confirm, Esc/n to cancel)")}`;
|
|
452
458
|
lines.push(padToWidth(boxLine(truncateToWidth(confirmMsg, contentWidth))));
|
|
453
459
|
} else {
|
|
454
|
-
lines.push(padToWidth(
|
|
455
|
-
const controls = `${
|
|
460
|
+
lines.push(padToWidth(theme.fg("dim", "├" + horizontalLine(boxWidth - 2) + "┤")));
|
|
461
|
+
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
462
|
lines.push(padToWidth(boxLine(truncateToWidth(controls, contentWidth))));
|
|
457
463
|
}
|
|
458
|
-
lines.push(padToWidth(
|
|
464
|
+
lines.push(padToWidth(theme.fg("dim", "╰" + horizontalLine(boxWidth - 2) + "╯")));
|
|
459
465
|
|
|
460
466
|
this.cachedWidth = width;
|
|
461
467
|
this.cachedLines = lines;
|
|
@@ -467,8 +473,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
467
473
|
pi.registerCommand("answer", {
|
|
468
474
|
description: "Extract questions from last assistant message into interactive Q&A",
|
|
469
475
|
handler: async (_args, ctx) => {
|
|
470
|
-
if (
|
|
471
|
-
ctx.ui.notify("answer requires
|
|
476
|
+
if (ctx.mode !== "tui") {
|
|
477
|
+
ctx.ui.notify("answer requires TUI mode", "error");
|
|
472
478
|
return;
|
|
473
479
|
}
|
|
474
480
|
|
|
@@ -571,8 +577,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
571
577
|
}
|
|
572
578
|
|
|
573
579
|
// Show the Q&A component
|
|
574
|
-
const answersResult = await ctx.ui.custom<string | null>((tui,
|
|
575
|
-
return new QnAComponent(extractionResult.questions, tui, done);
|
|
580
|
+
const answersResult = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
581
|
+
return new QnAComponent(extractionResult.questions, tui, theme, done);
|
|
576
582
|
});
|
|
577
583
|
|
|
578
584
|
if (answersResult === null) {
|
|
@@ -2,18 +2,18 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import type { ExtensionAPI, ExtensionCommandContext } from "@
|
|
5
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import {
|
|
7
7
|
CURRENT_SESSION_VERSION,
|
|
8
8
|
SessionManager,
|
|
9
9
|
highlightCode,
|
|
10
10
|
type SessionHeader,
|
|
11
|
-
} from "@
|
|
12
|
-
import { Box, Text } from "@
|
|
11
|
+
} from "@earendil-works/pi-coding-agent";
|
|
12
|
+
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,5 +1,5 @@
|
|
|
1
|
-
import type { AgentMessage } from "@
|
|
2
|
-
import type { AssistantMessage, ThinkingLevel } from "@
|
|
1
|
+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { AssistantMessage, ThinkingLevel } from "@earendil-works/pi-ai";
|
|
3
3
|
import {
|
|
4
4
|
DefaultResourceLoader,
|
|
5
5
|
SessionManager,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type ExtensionCommandContext,
|
|
12
12
|
type ExtensionContext,
|
|
13
13
|
type Theme,
|
|
14
|
-
} from "@
|
|
14
|
+
} from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import {
|
|
16
16
|
Key,
|
|
17
17
|
Markdown,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
type TUI,
|
|
22
22
|
visibleWidth,
|
|
23
23
|
wrapTextWithAnsi,
|
|
24
|
-
} from "@
|
|
24
|
+
} from "@earendil-works/pi-tui";
|
|
25
25
|
|
|
26
26
|
const STATUS_KEY = "0-btw";
|
|
27
27
|
const RESULT_MARKDOWN_THEME = getMarkdownTheme();
|
|
@@ -116,8 +116,8 @@ export default function btwExtension(pi: ExtensionAPI): void {
|
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
if (
|
|
120
|
-
ctx.ui.notify("btw requires
|
|
119
|
+
if (ctx.mode !== "tui") {
|
|
120
|
+
ctx.ui.notify("btw requires TUI mode", "error");
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -278,8 +278,8 @@ function seedSessionManager(sessionManager: SessionManager, messages: AgentMessa
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
async function showResultDialog(ctx: ExtensionContext, result: BtwResult): Promise<void> {
|
|
281
|
-
await ctx.ui.custom<void>((tui,
|
|
282
|
-
return new BtwResultComponent(result, tui,
|
|
281
|
+
await ctx.ui.custom<void>((tui, theme, _keybindings, done) => {
|
|
282
|
+
return new BtwResultComponent(result, tui, theme, done);
|
|
283
283
|
});
|
|
284
284
|
}
|
|
285
285
|
|
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
|
|
@@ -10,7 +11,7 @@
|
|
|
10
11
|
* Pi now emits native OSC 9;4 progress indicators, so this extension only manages the title.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
14
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
14
15
|
import path from "node:path";
|
|
15
16
|
|
|
16
17
|
const STATUS_SPINNER_INTERVAL_MS = 80;
|
|
@@ -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);
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
isWriteToolResult,
|
|
4
4
|
type ExtensionAPI,
|
|
5
5
|
type ExtensionContext,
|
|
6
|
-
} from "@
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { copyFile, mkdtemp, rm } from "node:fs/promises";
|
package/extensions/insights.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { complete
|
|
1
|
+
import { complete } from "@earendil-works/pi-ai/compat";
|
|
2
|
+
import type { Api, Model, UserMessage } from "@earendil-works/pi-ai";
|
|
2
3
|
import {
|
|
3
4
|
BorderedLoader,
|
|
4
5
|
SessionManager,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
type SessionEntry,
|
|
14
15
|
type SessionHeader,
|
|
15
16
|
type Theme,
|
|
16
|
-
} from "@
|
|
17
|
+
} from "@earendil-works/pi-coding-agent";
|
|
17
18
|
import {
|
|
18
19
|
Key,
|
|
19
20
|
Markdown,
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
type TUI,
|
|
24
25
|
visibleWidth,
|
|
25
26
|
wrapTextWithAnsi,
|
|
26
|
-
} from "@
|
|
27
|
+
} from "@earendil-works/pi-tui";
|
|
27
28
|
import { createHash, randomBytes } from "node:crypto";
|
|
28
29
|
import fs from "node:fs/promises";
|
|
29
30
|
import path from "node:path";
|
|
@@ -480,8 +481,8 @@ async function runInsightsCommand(
|
|
|
480
481
|
return;
|
|
481
482
|
}
|
|
482
483
|
|
|
483
|
-
if (
|
|
484
|
-
ctx.ui.notify("/insights currently requires
|
|
484
|
+
if (ctx.mode !== "tui") {
|
|
485
|
+
ctx.ui.notify("/insights currently requires TUI mode", "warning");
|
|
485
486
|
return;
|
|
486
487
|
}
|
|
487
488
|
|