tau-all-agent 0.1.4 → 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 +48 -42
- package/extensions/branch-term.ts +8 -13
- package/extensions/btw.ts +4 -4
- package/extensions/ghostty.ts +64 -11
- package/extensions/insights.ts +4 -3
- package/extensions/loop.ts +37 -9
- package/extensions/memory.ts +5 -10
- 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 +66 -0
- package/extensions/review/submit-triage-tool.ts +59 -0
- package/extensions/review/triage.ts +746 -0
- package/extensions/sandbox/index.ts +399 -102
- package/extensions/{interlude.ts → stash.ts} +13 -11
- package/extensions/telegram/README.md +3 -3
- package/extensions/telegram/assistant-result.d.mts +13 -0
- package/extensions/telegram/assistant-result.mjs +52 -0
- package/extensions/telegram/daemon.mjs +70 -28
- package/extensions/telegram/index.ts +35 -17
- package/extensions/tool-display-mode.ts +2 -3
- package/extensions/usage/index.ts +8 -4
- 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/worktree.ts +33 -33
- package/package.json +3 -3
- package/skills/git-clean-history/SKILL.md +1 -0
- package/skills/git-commit/SKILL.md +2 -2
- package/skills/oracle/scripts/oracle +28 -11
- 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,
|
|
@@ -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) {
|
|
@@ -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
|
@@ -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
|
|
@@ -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);
|
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,
|
|
@@ -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
|
|
package/extensions/loop.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { Type } from "typebox";
|
|
10
|
-
import { complete
|
|
10
|
+
import { complete } from "@earendil-works/pi-ai/compat";
|
|
11
|
+
import type { Api, Model, UserMessage } from "@earendil-works/pi-ai";
|
|
11
12
|
import {
|
|
12
13
|
compact,
|
|
13
14
|
defineTool,
|
|
@@ -213,6 +214,8 @@ async function loadState(ctx: ExtensionContext): Promise<LoopStateData> {
|
|
|
213
214
|
|
|
214
215
|
export default function loopExtension(pi: ExtensionAPI): void {
|
|
215
216
|
let loopState: LoopStateData = { active: false };
|
|
217
|
+
let lastAgentEndMessages: Array<{ role?: string; stopReason?: string }> = [];
|
|
218
|
+
let pendingLoopPrompt: ReturnType<typeof setImmediate> | undefined;
|
|
216
219
|
|
|
217
220
|
function persistState(state: LoopStateData): void {
|
|
218
221
|
pi.appendEntry(LOOP_STATE_ENTRY, state);
|
|
@@ -271,6 +274,23 @@ export default function loopExtension(pi: ExtensionAPI): void {
|
|
|
271
274
|
);
|
|
272
275
|
}
|
|
273
276
|
|
|
277
|
+
function scheduleLoopPrompt(ctx: ExtensionContext): void {
|
|
278
|
+
if (pendingLoopPrompt) return;
|
|
279
|
+
|
|
280
|
+
pendingLoopPrompt = setImmediate(() => {
|
|
281
|
+
pendingLoopPrompt = undefined;
|
|
282
|
+
if (!loopState.active || !ctx.isIdle()) return;
|
|
283
|
+
triggerLoopPrompt(ctx);
|
|
284
|
+
});
|
|
285
|
+
pendingLoopPrompt.unref?.();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function cancelScheduledLoopPrompt(): void {
|
|
289
|
+
if (!pendingLoopPrompt) return;
|
|
290
|
+
clearImmediate(pendingLoopPrompt);
|
|
291
|
+
pendingLoopPrompt = undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
274
294
|
async function showLoopSelector(ctx: ExtensionContext): Promise<LoopStateData | null> {
|
|
275
295
|
const items: SelectItem[] = LOOP_PRESETS.map((preset) => ({
|
|
276
296
|
value: preset.value,
|
|
@@ -385,11 +405,11 @@ export default function loopExtension(pi: ExtensionAPI): void {
|
|
|
385
405
|
name: "signal_loop_success",
|
|
386
406
|
label: "Signal Loop Success",
|
|
387
407
|
description:
|
|
388
|
-
"Stop the active loop when the breakout condition is satisfied. Only call
|
|
408
|
+
"Stop the active loop when the breakout condition is satisfied. Only call signal_loop_success when explicitly instructed to do so by the user, tool, or system prompt.",
|
|
389
409
|
promptSnippet: "Signal that the active /loop breakout condition has been satisfied",
|
|
390
410
|
promptGuidelines: [
|
|
391
|
-
"Call
|
|
392
|
-
"Do not call
|
|
411
|
+
"Call signal_loop_success only when the active loop's breakout condition is actually satisfied.",
|
|
412
|
+
"Do not call signal_loop_success unless a loop is currently active.",
|
|
393
413
|
],
|
|
394
414
|
parameters: Type.Object({}),
|
|
395
415
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
@@ -416,7 +436,7 @@ export default function loopExtension(pi: ExtensionAPI): void {
|
|
|
416
436
|
handler: async (args, ctx) => {
|
|
417
437
|
let nextState = parseArgs(args);
|
|
418
438
|
if (!nextState) {
|
|
419
|
-
if (
|
|
439
|
+
if (ctx.mode !== "tui") {
|
|
420
440
|
ctx.ui.notify("Usage: /loop tests | /loop custom <condition> | /loop self", "warning");
|
|
421
441
|
return;
|
|
422
442
|
}
|
|
@@ -458,10 +478,14 @@ export default function loopExtension(pi: ExtensionAPI): void {
|
|
|
458
478
|
},
|
|
459
479
|
});
|
|
460
480
|
|
|
461
|
-
pi.on("agent_end", async (event
|
|
481
|
+
pi.on("agent_end", async (event) => {
|
|
482
|
+
lastAgentEndMessages = event.messages;
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
pi.on("agent_settled", async (_event, ctx) => {
|
|
462
486
|
if (!loopState.active) return;
|
|
463
487
|
|
|
464
|
-
if (ctx.hasUI && wasLastAssistantAborted(
|
|
488
|
+
if (ctx.hasUI && wasLastAssistantAborted(lastAgentEndMessages)) {
|
|
465
489
|
const confirm = await withPromptSignal(pi, () =>
|
|
466
490
|
ctx.ui.confirm("Break active loop?", "Operation aborted. Break out of the loop?"),
|
|
467
491
|
);
|
|
@@ -471,13 +495,13 @@ export default function loopExtension(pi: ExtensionAPI): void {
|
|
|
471
495
|
}
|
|
472
496
|
}
|
|
473
497
|
|
|
474
|
-
|
|
498
|
+
scheduleLoopPrompt(ctx);
|
|
475
499
|
});
|
|
476
500
|
|
|
477
501
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
478
502
|
if (!loopState.active || !loopState.mode || !ctx.model) return;
|
|
479
503
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
|
|
480
|
-
if (!auth.ok
|
|
504
|
+
if (!auth.ok) return;
|
|
481
505
|
|
|
482
506
|
const instructionParts = [
|
|
483
507
|
event.customInstructions,
|
|
@@ -526,4 +550,8 @@ export default function loopExtension(pi: ExtensionAPI): void {
|
|
|
526
550
|
pi.on("session_start", async (_event, ctx) => {
|
|
527
551
|
await restoreLoopState(ctx);
|
|
528
552
|
});
|
|
553
|
+
|
|
554
|
+
pi.on("session_shutdown", async () => {
|
|
555
|
+
cancelScheduledLoopPrompt();
|
|
556
|
+
});
|
|
529
557
|
}
|
package/extensions/memory.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
StringEnum,
|
|
4
|
-
type Api,
|
|
5
|
-
type Model,
|
|
6
|
-
type UserMessage,
|
|
7
|
-
} from "@earendil-works/pi-ai";
|
|
1
|
+
import { complete } from "@earendil-works/pi-ai/compat";
|
|
2
|
+
import { StringEnum, type Api, type Model, type UserMessage } from "@earendil-works/pi-ai";
|
|
8
3
|
import {
|
|
9
4
|
defineTool,
|
|
10
5
|
withFileMutationQueue,
|
|
@@ -1710,7 +1705,7 @@ export default function memoryExtension(pi: ExtensionAPI): void {
|
|
|
1710
1705
|
description: `Update one .agents/memory/core block while enforcing the shared ${CORE_LINE_CAP}-line and ${CORE_CHAR_CAP}-character caps`,
|
|
1711
1706
|
promptSnippet: `Update one core memory block in .agents/memory/core with ${CORE_LINE_CAP}-line and ${CORE_CHAR_CAP}-character cap enforcement`,
|
|
1712
1707
|
promptGuidelines: [
|
|
1713
|
-
"Use
|
|
1708
|
+
"Use memory_update_block when updating .agents/memory/core/directives.md, context.md, focus.md, or pending.md.",
|
|
1714
1709
|
`If a write would exceed the ${CORE_LINE_CAP}-line or ${CORE_CHAR_CAP}-character core cap, run memory_dream or remove content first.`,
|
|
1715
1710
|
],
|
|
1716
1711
|
parameters: MEMORY_UPDATE_BLOCK_PARAMS,
|
|
@@ -1741,7 +1736,7 @@ export default function memoryExtension(pi: ExtensionAPI): void {
|
|
|
1741
1736
|
description: "Append an entry to .agents/memory/log.md using the repo memory log format",
|
|
1742
1737
|
promptSnippet: "Append an entry to the repo memory log at .agents/memory/log.md",
|
|
1743
1738
|
promptGuidelines: [
|
|
1744
|
-
"Use
|
|
1739
|
+
"Use memory_append_log for important decisions, discoveries, plans, experiments, prompt ingests, and attachment additions worth remembering.",
|
|
1745
1740
|
"Use importance labels high, medium, or low.",
|
|
1746
1741
|
"If this entry replaces or corrects prior memory, set supersedes and/or invalidates links explicitly.",
|
|
1747
1742
|
"The memory log is append-only. Do not rewrite or truncate older entries.",
|
|
@@ -1779,7 +1774,7 @@ export default function memoryExtension(pi: ExtensionAPI): void {
|
|
|
1779
1774
|
promptSnippet: "Consolidate repo memory with dream-based core compression",
|
|
1780
1775
|
promptGuidelines: [
|
|
1781
1776
|
"Dream is the only consolidation mechanism for .agents/memory/core.",
|
|
1782
|
-
"Use
|
|
1777
|
+
"Use memory_dream when logs have accumulated, core needs compression, or recent compaction context should be folded into memory.",
|
|
1783
1778
|
],
|
|
1784
1779
|
parameters: MEMORY_DREAM_PARAMS,
|
|
1785
1780
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|