tau-all-agent 0.2.0 → 0.3.1
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/extensions/answer.ts +115 -85
- package/extensions/branch-term/index.ts +9 -5
- package/extensions/btw.ts +70 -36
- package/extensions/insights.ts +70 -56
- package/extensions/loop.ts +14 -52
- package/extensions/memory.ts +65 -33
- package/extensions/notify.ts +5 -3
- package/extensions/review/fix.ts +10 -16
- package/extensions/review/git.ts +7 -1
- package/extensions/review/index.ts +2 -2
- package/extensions/review/models.ts +12 -2
- package/extensions/review/review.ts +3 -5
- package/extensions/review/runtime.ts +9 -8
- package/extensions/review/triage.ts +2 -4
- package/extensions/sandbox/README.md +7 -0
- package/extensions/sandbox/bash.ts +83 -85
- package/extensions/sandbox/command.ts +15 -1
- package/extensions/sandbox/config.ts +26 -15
- package/extensions/sandbox/index.ts +21 -11
- package/extensions/sandbox/permissions/network.ts +8 -3
- package/extensions/sandbox/permissions/unsandboxed.ts +1 -240
- package/extensions/sandbox/runtime.ts +54 -20
- package/extensions/subagent/README.md +58 -1
- package/extensions/subagent/config.ts +118 -0
- package/extensions/subagent/index.ts +54 -22
- package/extensions/telegram/README.md +2 -0
- package/extensions/telegram/assistant-result.d.mts +4 -1
- package/extensions/telegram/assistant-result.mjs +31 -16
- package/extensions/telegram/daemon.mjs +146 -194
- package/extensions/telegram/index.ts +11 -7
- package/extensions/tool-display-mode.ts +29 -50
- package/extensions/usage/README.md +5 -0
- package/extensions/usage/index.ts +341 -349
- package/extensions/websearch/README.md +8 -1
- package/extensions/websearch/config.ts +2 -1
- package/extensions/websearch/index.ts +18 -9
- package/extensions/websearch/providers/{openai-codex.pi.ts → openai.pi.ts} +51 -16
- package/extensions/websearch/providers/shared.ts +10 -3
- package/extensions/websearch/types.ts +2 -1
- package/extensions/worktree.ts +33 -89
- package/package.json +1 -1
- package/skills/oracle/SKILL.md +14 -8
- package/skills/oracle/scripts/oracle +43 -19
- package/skills/update-changelog/SKILL.md +8 -4
package/extensions/insights.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
matchesKey,
|
|
19
19
|
truncateToWidth,
|
|
20
20
|
type Component,
|
|
21
|
+
type KeybindingsManager,
|
|
21
22
|
type TUI,
|
|
22
23
|
visibleWidth,
|
|
23
24
|
wrapTextWithAnsi,
|
|
@@ -43,7 +44,9 @@ const REDUCED_TRANSCRIPT_HEAD_CHARS = 12_000;
|
|
|
43
44
|
const REDUCED_TRANSCRIPT_TAIL_CHARS = 12_000;
|
|
44
45
|
const REDUCED_TRANSCRIPT_MAX_CHARS = FULL_TRANSCRIPT_MAX_CHARS;
|
|
45
46
|
|
|
46
|
-
const MIN_REPORT_WIDTH = 42;
|
|
47
|
+
const MIN_REPORT_WIDTH = 42;
|
|
48
|
+
const REPORT_CHROME_ROWS = 6;
|
|
49
|
+
const MIN_REPORT_ROWS = REPORT_CHROME_ROWS + 3; // Two overlay margins and one reading row.
|
|
47
50
|
|
|
48
51
|
const AGENT_ROOT = getAgentDir();
|
|
49
52
|
const CACHE_ROOT = path.join(AGENT_ROOT, "insights");
|
|
@@ -309,8 +312,6 @@ class InsightsReportComponent implements Component {
|
|
|
309
312
|
private readonly markdown: Markdown;
|
|
310
313
|
private scrollOffset = 0;
|
|
311
314
|
private renderWidth?: number;
|
|
312
|
-
private cachedWidth?: number;
|
|
313
|
-
private cachedLines?: string[];
|
|
314
315
|
private cachedBodyWidth?: number;
|
|
315
316
|
private cachedBodyLines?: string[];
|
|
316
317
|
|
|
@@ -318,14 +319,13 @@ class InsightsReportComponent implements Component {
|
|
|
318
319
|
private readonly result: InsightsResult,
|
|
319
320
|
private readonly tui: TUI,
|
|
320
321
|
private readonly theme: Theme,
|
|
322
|
+
private readonly keybindings: KeybindingsManager,
|
|
321
323
|
private readonly onDone: () => void,
|
|
322
324
|
) {
|
|
323
325
|
this.markdown = new Markdown(result.reportMarkdown, 0, 0, getMarkdownTheme());
|
|
324
326
|
}
|
|
325
327
|
|
|
326
328
|
invalidate(): void {
|
|
327
|
-
this.cachedWidth = undefined;
|
|
328
|
-
this.cachedLines = undefined;
|
|
329
329
|
this.cachedBodyWidth = undefined;
|
|
330
330
|
this.cachedBodyLines = undefined;
|
|
331
331
|
this.markdown.invalidate();
|
|
@@ -343,52 +343,60 @@ class InsightsReportComponent implements Component {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
const width = this.renderWidth ?? this.tui.terminal.columns;
|
|
346
|
-
if (width < MIN_REPORT_WIDTH) return;
|
|
346
|
+
if (width < MIN_REPORT_WIDTH || this.tui.terminal.rows < MIN_REPORT_ROWS) return;
|
|
347
347
|
|
|
348
348
|
const bodyHeight = this.getBodyHeight();
|
|
349
|
-
const boxWidth =
|
|
349
|
+
const boxWidth = width;
|
|
350
350
|
const bodyLines = this.getBodyLines(this.getContentWidth(boxWidth));
|
|
351
351
|
const maxScroll = Math.max(0, bodyLines.length - bodyHeight);
|
|
352
352
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
this.scrollOffset
|
|
357
|
-
} else if (
|
|
358
|
-
this.scrollOffset
|
|
359
|
-
} else if (
|
|
360
|
-
this.scrollOffset
|
|
361
|
-
} else if (
|
|
353
|
+
this.scrollOffset = clamp(this.scrollOffset, 0, maxScroll);
|
|
354
|
+
const kb = this.keybindings;
|
|
355
|
+
if (kb.matches(data, "tui.altScreen.pageUp")) {
|
|
356
|
+
this.scrollOffset -= bodyHeight;
|
|
357
|
+
} else if (kb.matches(data, "tui.altScreen.pageDown")) {
|
|
358
|
+
this.scrollOffset += bodyHeight;
|
|
359
|
+
} else if (kb.matches(data, "tui.altScreen.halfPageUp")) {
|
|
360
|
+
this.scrollOffset -= Math.max(1, Math.floor(bodyHeight / 2));
|
|
361
|
+
} else if (kb.matches(data, "tui.altScreen.halfPageDown")) {
|
|
362
|
+
this.scrollOffset += Math.max(1, Math.floor(bodyHeight / 2));
|
|
363
|
+
} else if (kb.matches(data, "tui.altScreen.top")) {
|
|
362
364
|
this.scrollOffset = 0;
|
|
363
|
-
} else if (
|
|
365
|
+
} else if (kb.matches(data, "tui.altScreen.bottom")) {
|
|
364
366
|
this.scrollOffset = maxScroll;
|
|
367
|
+
} else if (kb.matches(data, "tui.altScreen.lineUp")) {
|
|
368
|
+
this.scrollOffset -= 1;
|
|
369
|
+
} else if (kb.matches(data, "tui.altScreen.lineDown")) {
|
|
370
|
+
this.scrollOffset += 1;
|
|
371
|
+
} else if (matchesKey(data, Key.up) || data.toLowerCase() === "k") {
|
|
372
|
+
this.scrollOffset -= 1;
|
|
373
|
+
} else if (matchesKey(data, Key.down) || data.toLowerCase() === "j") {
|
|
374
|
+
this.scrollOffset += 1;
|
|
365
375
|
} else {
|
|
366
376
|
return;
|
|
367
377
|
}
|
|
368
378
|
|
|
369
|
-
this.
|
|
379
|
+
this.scrollOffset = clamp(this.scrollOffset, 0, maxScroll);
|
|
370
380
|
this.tui.requestRender();
|
|
371
381
|
}
|
|
372
382
|
|
|
373
383
|
render(width: number): string[] {
|
|
374
384
|
this.renderWidth = width;
|
|
375
|
-
if (width <
|
|
376
|
-
|
|
385
|
+
if (width < 1 || this.tui.terminal.rows <= 2) return [];
|
|
386
|
+
if (width < MIN_REPORT_WIDTH || this.tui.terminal.rows < MIN_REPORT_ROWS) {
|
|
377
387
|
return [
|
|
378
388
|
truncateToWidth(
|
|
379
|
-
this.theme.fg(
|
|
389
|
+
this.theme.fg(
|
|
390
|
+
"muted",
|
|
391
|
+
`Esc close · Resize to ${MIN_REPORT_WIDTH + 2}+ columns, ${MIN_REPORT_ROWS}+ rows.`,
|
|
392
|
+
),
|
|
380
393
|
width,
|
|
381
394
|
"",
|
|
382
395
|
),
|
|
383
|
-
truncateToWidth(this.theme.fg("dim", "Enter/Esc close"), width, ""),
|
|
384
396
|
];
|
|
385
397
|
}
|
|
386
398
|
|
|
387
|
-
|
|
388
|
-
return this.cachedLines;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const boxWidth = this.getBoxWidth(width);
|
|
399
|
+
const boxWidth = width;
|
|
392
400
|
const contentWidth = this.getContentWidth(boxWidth);
|
|
393
401
|
const bodyLines = this.getBodyLines(contentWidth);
|
|
394
402
|
const bodyHeight = this.getBodyHeight();
|
|
@@ -396,20 +404,9 @@ class InsightsReportComponent implements Component {
|
|
|
396
404
|
this.scrollOffset = clamp(this.scrollOffset, 0, maxScroll);
|
|
397
405
|
|
|
398
406
|
const title = this.theme.fg("accent", this.theme.bold("Insights"));
|
|
399
|
-
const metadata = [
|
|
400
|
-
`${this.result.aggregate.scope} scope · ${formatCount(this.result.aggregate.sessionsAnalyzed)} analyzed · ${formatCount(this.result.aggregate.sessionsWithFacets)} classified`,
|
|
401
|
-
`${this.result.modelId ?? "current model"} · generated ${formatIsoShort(this.result.generatedAt)}`,
|
|
402
|
-
];
|
|
403
|
-
|
|
404
407
|
const lines: string[] = [];
|
|
405
408
|
lines.push(this.borderLine("╭", "╮", boxWidth));
|
|
406
409
|
lines.push(this.boxLine(title, boxWidth));
|
|
407
|
-
for (const line of metadata) {
|
|
408
|
-
for (const wrapped of wrapTextWithAnsi(this.theme.fg("muted", line), contentWidth)) {
|
|
409
|
-
lines.push(this.boxLine(wrapped, boxWidth));
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
lines.push(this.separatorLine(boxWidth));
|
|
413
410
|
|
|
414
411
|
const visibleBody = bodyLines.slice(this.scrollOffset, this.scrollOffset + bodyHeight);
|
|
415
412
|
for (const line of visibleBody) {
|
|
@@ -421,22 +418,30 @@ class InsightsReportComponent implements Component {
|
|
|
421
418
|
|
|
422
419
|
lines.push(this.separatorLine(boxWidth));
|
|
423
420
|
const scrollText = `${formatCount(Math.min(bodyLines.length, this.scrollOffset + 1))}-${formatCount(Math.min(bodyLines.length, this.scrollOffset + visibleBody.length))}/${formatCount(bodyLines.length)}`;
|
|
424
|
-
const
|
|
425
|
-
|
|
421
|
+
const navigation = [
|
|
422
|
+
["pageUp", "pageDown", "page"],
|
|
423
|
+
["top", "bottom", "ends"],
|
|
424
|
+
] as const;
|
|
425
|
+
const hints = [
|
|
426
|
+
"↑↓/j/k scroll",
|
|
427
|
+
...navigation.flatMap(([up, down, label]) => {
|
|
428
|
+
const keys = [up, down].map((action) =>
|
|
429
|
+
this.keybindings
|
|
430
|
+
.getKeys(`tui.altScreen.${action}`)
|
|
431
|
+
.find((key) => !["enter", "escape", "ctrl+c", "q"].includes(key)),
|
|
432
|
+
);
|
|
433
|
+
return keys.some(Boolean) ? [`${keys.map((key) => key ?? "—").join("/")} ${label}`] : [];
|
|
434
|
+
}),
|
|
435
|
+
];
|
|
436
|
+
lines.push(this.boxLine(this.theme.fg("dim", hints.join(" · ")), boxWidth));
|
|
437
|
+
lines.push(this.boxLine(this.theme.fg("dim", `Enter/Esc close · ${scrollText}`), boxWidth));
|
|
426
438
|
lines.push(this.borderLine("╰", "╯", boxWidth));
|
|
427
439
|
|
|
428
|
-
this.cachedWidth = width;
|
|
429
|
-
this.cachedLines = lines;
|
|
430
440
|
return lines;
|
|
431
441
|
}
|
|
432
442
|
|
|
433
443
|
private getBodyHeight(): number {
|
|
434
|
-
return
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
private invalidateFrame(): void {
|
|
438
|
-
this.cachedWidth = undefined;
|
|
439
|
-
this.cachedLines = undefined;
|
|
444
|
+
return this.tui.terminal.rows - 2 - REPORT_CHROME_ROWS;
|
|
440
445
|
}
|
|
441
446
|
|
|
442
447
|
private getBodyLines(contentWidth: number): string[] {
|
|
@@ -444,16 +449,20 @@ class InsightsReportComponent implements Component {
|
|
|
444
449
|
return this.cachedBodyLines;
|
|
445
450
|
}
|
|
446
451
|
|
|
447
|
-
const
|
|
452
|
+
const metadata = [
|
|
453
|
+
`${this.result.aggregate.scope} scope · ${formatCount(this.result.aggregate.sessionsAnalyzed)} analyzed · ${formatCount(this.result.aggregate.sessionsWithFacets)} classified`,
|
|
454
|
+
`${this.result.modelId ?? "current model"} · generated ${formatIsoShort(this.result.generatedAt)}`,
|
|
455
|
+
];
|
|
456
|
+
const lines = [
|
|
457
|
+
...metadata.flatMap((line) => wrapTextWithAnsi(this.theme.fg("muted", line), contentWidth)),
|
|
458
|
+
"",
|
|
459
|
+
...this.markdown.render(contentWidth),
|
|
460
|
+
];
|
|
448
461
|
this.cachedBodyWidth = contentWidth;
|
|
449
462
|
this.cachedBodyLines = lines;
|
|
450
463
|
return lines;
|
|
451
464
|
}
|
|
452
465
|
|
|
453
|
-
private getBoxWidth(width: number): number {
|
|
454
|
-
return Math.min(width - 2, 140);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
466
|
private getContentWidth(boxWidth: number): number {
|
|
458
467
|
return boxWidth - 4;
|
|
459
468
|
}
|
|
@@ -586,9 +595,14 @@ async function runInsightsCommand(
|
|
|
586
595
|
ctx.ui.notify(`Failed to save insights report: ${message}`, "warning");
|
|
587
596
|
}
|
|
588
597
|
|
|
589
|
-
await ctx.ui.custom<void>(
|
|
590
|
-
|
|
591
|
-
|
|
598
|
+
await ctx.ui.custom<void>(
|
|
599
|
+
(tui, theme, keybindings, done) =>
|
|
600
|
+
new InsightsReportComponent(result, tui, theme, keybindings, done),
|
|
601
|
+
{
|
|
602
|
+
overlay: true,
|
|
603
|
+
overlayOptions: { anchor: "bottom-center", width: "100%", maxHeight: "100%", margin: 1 },
|
|
604
|
+
},
|
|
605
|
+
);
|
|
592
606
|
|
|
593
607
|
if (reportPath) {
|
|
594
608
|
ctx.ui.notify(`Insights report saved to ${reportPath}`, "info");
|
package/extensions/loop.ts
CHANGED
|
@@ -11,21 +11,19 @@ import type { Api, Model, UserMessage } from "@earendil-works/pi-ai";
|
|
|
11
11
|
import {
|
|
12
12
|
compact,
|
|
13
13
|
defineTool,
|
|
14
|
-
DynamicBorder,
|
|
15
14
|
type ExtensionAPI,
|
|
16
15
|
type ExtensionContext,
|
|
17
16
|
} from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import { Container, type SelectItem, SelectList, Text } from "@earendil-works/pi-tui";
|
|
19
17
|
|
|
20
18
|
const LOOP_PRESETS = [
|
|
21
|
-
{ value: "tests", label: "Until tests pass"
|
|
22
|
-
{ value: "custom", label: "Until custom condition"
|
|
23
|
-
{ value: "self", label: "Self driven (agent decides)"
|
|
19
|
+
{ value: "tests", label: "Until tests pass" },
|
|
20
|
+
{ value: "custom", label: "Until custom condition" },
|
|
21
|
+
{ value: "self", label: "Self driven (agent decides)" },
|
|
24
22
|
] as const;
|
|
25
23
|
|
|
26
24
|
const LOOP_STATE_ENTRY = "loop-state";
|
|
27
|
-
const OPENAI_FAST_MODEL_ID = "gpt-
|
|
28
|
-
const ANTHROPIC_FAST_MODEL_ID = "claude-haiku-
|
|
25
|
+
const OPENAI_FAST_MODEL_ID = "gpt-6-luna";
|
|
26
|
+
const ANTHROPIC_FAST_MODEL_ID = "claude-haiku-5-5";
|
|
29
27
|
|
|
30
28
|
const SUMMARY_SYSTEM_PROMPT = `You summarize loop breakout conditions for a status widget.
|
|
31
29
|
Return a concise phrase (max 6 words) that says when the loop should stop.
|
|
@@ -315,49 +313,13 @@ export default function loopExtension(pi: ExtensionAPI): void {
|
|
|
315
313
|
}
|
|
316
314
|
|
|
317
315
|
async function showLoopSelector(ctx: ExtensionContext): Promise<LoopStateData | null> {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const selection = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
325
|
-
const container = new Container();
|
|
326
|
-
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
327
|
-
container.addChild(new Text(theme.fg("accent", theme.bold("Select a loop preset"))));
|
|
328
|
-
|
|
329
|
-
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
330
|
-
selectedPrefix: (text) => theme.fg("accent", text),
|
|
331
|
-
selectedText: (text) => theme.fg("accent", text),
|
|
332
|
-
description: (text) => theme.fg("muted", text),
|
|
333
|
-
scrollInfo: (text) => theme.fg("dim", text),
|
|
334
|
-
noMatch: (text) => theme.fg("warning", text),
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
selectList.onSelect = (item) => done(item.value);
|
|
338
|
-
selectList.onCancel = () => done(null);
|
|
339
|
-
|
|
340
|
-
container.addChild(selectList);
|
|
341
|
-
container.addChild(new Text(theme.fg("dim", "Press enter to confirm or esc to cancel")));
|
|
342
|
-
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
343
|
-
|
|
344
|
-
return {
|
|
345
|
-
render(width: number) {
|
|
346
|
-
return container.render(width);
|
|
347
|
-
},
|
|
348
|
-
invalidate() {
|
|
349
|
-
container.invalidate();
|
|
350
|
-
},
|
|
351
|
-
handleInput(data: string) {
|
|
352
|
-
selectList.handleInput(data);
|
|
353
|
-
tui.requestRender();
|
|
354
|
-
},
|
|
355
|
-
};
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
if (!selection) return null;
|
|
316
|
+
const selection = await ctx.ui.select(
|
|
317
|
+
"Select a loop preset",
|
|
318
|
+
LOOP_PRESETS.map((preset) => preset.label),
|
|
319
|
+
);
|
|
320
|
+
const preset = LOOP_PRESETS.find((preset) => preset.label === selection);
|
|
359
321
|
|
|
360
|
-
switch (
|
|
322
|
+
switch (preset?.value) {
|
|
361
323
|
case "tests":
|
|
362
324
|
return { active: true, mode: "tests", prompt: buildPrompt("tests") };
|
|
363
325
|
case "self":
|
|
@@ -492,16 +454,16 @@ export default function loopExtension(pi: ExtensionAPI): void {
|
|
|
492
454
|
)?.stopReason;
|
|
493
455
|
});
|
|
494
456
|
|
|
495
|
-
pi.on("agent_settled", async (
|
|
457
|
+
pi.on("agent_settled", async (event, ctx) => {
|
|
496
458
|
if (!loopState.active) return;
|
|
497
459
|
|
|
498
|
-
if (lastAssistantStopReason === "error") {
|
|
460
|
+
if (lastAssistantStopReason === "error" && !event.aborted) {
|
|
499
461
|
await clearLoopState(ctx);
|
|
500
462
|
ctx.ui.notify("Loop stopped after an agent error", "error");
|
|
501
463
|
return;
|
|
502
464
|
}
|
|
503
465
|
|
|
504
|
-
if (lastAssistantStopReason === "aborted") {
|
|
466
|
+
if (event.aborted || lastAssistantStopReason === "aborted") {
|
|
505
467
|
const generation = loopGeneration;
|
|
506
468
|
const confirm =
|
|
507
469
|
!ctx.hasUI ||
|
package/extensions/memory.ts
CHANGED
|
@@ -233,13 +233,14 @@ async function toolDream(
|
|
|
233
233
|
cwd: string,
|
|
234
234
|
ctx: ExtensionContext,
|
|
235
235
|
reason?: string,
|
|
236
|
+
signal = ctx.signal,
|
|
236
237
|
): Promise<{
|
|
237
238
|
content: Array<{ type: "text"; text: string }>;
|
|
238
239
|
details: DreamResult;
|
|
239
240
|
usage?: Usage;
|
|
240
241
|
}> {
|
|
241
242
|
try {
|
|
242
|
-
const execution = await runMemoryDream(cwd, ctx, reason);
|
|
243
|
+
const execution = await runMemoryDream(cwd, ctx, reason, signal);
|
|
243
244
|
return {
|
|
244
245
|
content: [{ type: "text", text: execution.result.summary }],
|
|
245
246
|
details: execution.result,
|
|
@@ -331,10 +332,11 @@ async function updateCoreBlock(
|
|
|
331
332
|
cwd: string,
|
|
332
333
|
name: MemoryBlockName,
|
|
333
334
|
content: string,
|
|
335
|
+
signal?: AbortSignal,
|
|
334
336
|
): Promise<{ totalLines: number; totalChars: number }> {
|
|
335
337
|
const paths = getMemoryPaths(cwd);
|
|
336
338
|
return withMemoryMutationQueue(Object.values(paths.coreFiles), async () =>
|
|
337
|
-
updateCoreBlockUnsafe(cwd, name, content),
|
|
339
|
+
updateCoreBlockUnsafe(cwd, name, content, signal),
|
|
338
340
|
);
|
|
339
341
|
}
|
|
340
342
|
|
|
@@ -348,18 +350,23 @@ async function appendMemoryLog(
|
|
|
348
350
|
supersedes?: string[];
|
|
349
351
|
invalidates?: string[];
|
|
350
352
|
},
|
|
353
|
+
signal?: AbortSignal,
|
|
351
354
|
): Promise<LogEntry> {
|
|
352
355
|
const paths = getMemoryPaths(cwd);
|
|
353
356
|
return withMemoryMutationQueue([paths.logFile, paths.stateFile], async () =>
|
|
354
|
-
appendMemoryLogUnsafe(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
357
|
+
appendMemoryLogUnsafe(
|
|
358
|
+
cwd,
|
|
359
|
+
{
|
|
360
|
+
timestamp: nowIso(),
|
|
361
|
+
type: entry.type,
|
|
362
|
+
importance: entry.importance ?? DEFAULT_LOG_IMPORTANCE,
|
|
363
|
+
title: normalizeTitle(entry.title) || "Memory log entry",
|
|
364
|
+
body: normalizeMarkdownBlock(entry.body),
|
|
365
|
+
supersedes: normalizeMemoryReferences(entry.supersedes),
|
|
366
|
+
invalidates: normalizeMemoryReferences(entry.invalidates),
|
|
367
|
+
},
|
|
368
|
+
signal,
|
|
369
|
+
),
|
|
363
370
|
);
|
|
364
371
|
}
|
|
365
372
|
|
|
@@ -398,6 +405,7 @@ async function runMemoryDream(
|
|
|
398
405
|
cwd: string,
|
|
399
406
|
ctx: ExtensionContext,
|
|
400
407
|
reason?: string,
|
|
408
|
+
signal = ctx.signal,
|
|
401
409
|
): Promise<DreamExecution> {
|
|
402
410
|
const paths = getMemoryPaths(cwd);
|
|
403
411
|
const lockPaths = [
|
|
@@ -409,6 +417,7 @@ async function runMemoryDream(
|
|
|
409
417
|
];
|
|
410
418
|
|
|
411
419
|
const snapshot = await withMemoryMutationQueue(lockPaths, async () => {
|
|
420
|
+
signal?.throwIfAborted();
|
|
412
421
|
ensureMemoryInitialized(await pathExists(paths.memoryRoot));
|
|
413
422
|
|
|
414
423
|
const replay = await collectDreamReplay(cwd);
|
|
@@ -419,6 +428,7 @@ async function runMemoryDream(
|
|
|
419
428
|
};
|
|
420
429
|
});
|
|
421
430
|
|
|
431
|
+
signal?.throwIfAborted();
|
|
422
432
|
if (!shouldRunDream(snapshot.replay, reason)) {
|
|
423
433
|
const summary = "Memory dream: nothing to consolidate.";
|
|
424
434
|
notify(ctx, summary, "info");
|
|
@@ -436,15 +446,17 @@ async function runMemoryDream(
|
|
|
436
446
|
}
|
|
437
447
|
|
|
438
448
|
const selection = await selectDreamModel(ctx);
|
|
449
|
+
signal?.throwIfAborted();
|
|
439
450
|
const response = await ctx.modelRegistry.complete(
|
|
440
451
|
selection.model,
|
|
441
452
|
{
|
|
442
453
|
systemPrompt: MEMORY_DREAM_SYSTEM_PROMPT,
|
|
443
454
|
messages: [buildDreamUserMessage(snapshot.replay, reason)],
|
|
444
455
|
},
|
|
445
|
-
{ signal
|
|
456
|
+
{ signal },
|
|
446
457
|
);
|
|
447
458
|
|
|
459
|
+
signal?.throwIfAborted();
|
|
448
460
|
if (response.stopReason === "aborted") {
|
|
449
461
|
throw new Error("Memory dream aborted.");
|
|
450
462
|
}
|
|
@@ -481,6 +493,7 @@ async function runMemoryDream(
|
|
|
481
493
|
}
|
|
482
494
|
|
|
483
495
|
const result = await withMemoryMutationQueue(lockPaths, async () => {
|
|
496
|
+
signal?.throwIfAborted();
|
|
484
497
|
ensureMemoryInitialized(await pathExists(paths.memoryRoot));
|
|
485
498
|
|
|
486
499
|
const currentReplay = await collectDreamReplay(cwd);
|
|
@@ -488,7 +501,9 @@ async function runMemoryDream(
|
|
|
488
501
|
throw new Error("Memory changed while dream was running. Retry /memory dream.");
|
|
489
502
|
}
|
|
490
503
|
|
|
491
|
-
|
|
504
|
+
// Once the core writes start, finish the whole commit without cancellation checkpoints.
|
|
505
|
+
signal?.throwIfAborted();
|
|
506
|
+
await writeCoreBlocksUnsafe(cwd, finalBlocks, signal);
|
|
492
507
|
|
|
493
508
|
const previousState = await readStateUnsafe(cwd);
|
|
494
509
|
const dreamTimestamp = nowIso();
|
|
@@ -650,7 +665,9 @@ async function updateCoreBlockUnsafe(
|
|
|
650
665
|
cwd: string,
|
|
651
666
|
name: MemoryBlockName,
|
|
652
667
|
content: string,
|
|
668
|
+
signal?: AbortSignal,
|
|
653
669
|
): Promise<{ totalLines: number; totalChars: number }> {
|
|
670
|
+
signal?.throwIfAborted();
|
|
654
671
|
const paths = getMemoryPaths(cwd);
|
|
655
672
|
ensureMemoryInitialized(await pathExists(paths.memoryRoot));
|
|
656
673
|
|
|
@@ -673,12 +690,19 @@ async function updateCoreBlockUnsafe(
|
|
|
673
690
|
);
|
|
674
691
|
}
|
|
675
692
|
|
|
693
|
+
signal?.throwIfAborted();
|
|
676
694
|
await ensureDir(paths.coreDir);
|
|
695
|
+
signal?.throwIfAborted();
|
|
677
696
|
await fs.writeFile(paths.coreFiles[name], nextBlocks[name], "utf8");
|
|
678
697
|
return { totalLines, totalChars };
|
|
679
698
|
}
|
|
680
699
|
|
|
681
|
-
async function appendMemoryLogUnsafe(
|
|
700
|
+
async function appendMemoryLogUnsafe(
|
|
701
|
+
cwd: string,
|
|
702
|
+
entry: LogEntry,
|
|
703
|
+
signal?: AbortSignal,
|
|
704
|
+
): Promise<LogEntry> {
|
|
705
|
+
signal?.throwIfAborted();
|
|
682
706
|
const paths = getMemoryPaths(cwd);
|
|
683
707
|
ensureMemoryInitialized(await pathExists(paths.memoryRoot));
|
|
684
708
|
|
|
@@ -688,6 +712,7 @@ async function appendMemoryLogUnsafe(cwd: string, entry: LogEntry): Promise<LogE
|
|
|
688
712
|
renderLogEntryBody(entry),
|
|
689
713
|
].join("\n");
|
|
690
714
|
|
|
715
|
+
signal?.throwIfAborted();
|
|
691
716
|
await ensureDir(path.dirname(paths.logFile));
|
|
692
717
|
let writeFreshLog = false;
|
|
693
718
|
try {
|
|
@@ -700,6 +725,8 @@ async function appendMemoryLogUnsafe(cwd: string, entry: LogEntry): Promise<LogE
|
|
|
700
725
|
}
|
|
701
726
|
}
|
|
702
727
|
|
|
728
|
+
// Keep the log append and its state update together once mutation starts.
|
|
729
|
+
signal?.throwIfAborted();
|
|
703
730
|
if (writeFreshLog) {
|
|
704
731
|
await fs.writeFile(paths.logFile, `# Memory log\n\n${chunk}\n`, "utf8");
|
|
705
732
|
} else {
|
|
@@ -739,9 +766,14 @@ async function collectDreamReplay(cwd: string): Promise<DreamReplay> {
|
|
|
739
766
|
};
|
|
740
767
|
}
|
|
741
768
|
|
|
742
|
-
async function writeCoreBlocksUnsafe(
|
|
769
|
+
async function writeCoreBlocksUnsafe(
|
|
770
|
+
cwd: string,
|
|
771
|
+
blocks: CoreBlocks,
|
|
772
|
+
signal?: AbortSignal,
|
|
773
|
+
): Promise<void> {
|
|
743
774
|
const paths = getMemoryPaths(cwd);
|
|
744
775
|
await ensureDir(paths.coreDir);
|
|
776
|
+
signal?.throwIfAborted();
|
|
745
777
|
|
|
746
778
|
await Promise.all(
|
|
747
779
|
CORE_BLOCK_NAMES.map((name) =>
|
|
@@ -1077,7 +1109,6 @@ function validatePendingDispositions(
|
|
|
1077
1109
|
|
|
1078
1110
|
function buildMemoryPrompt(readme: string, blocks: CoreBlocks, researchFiles: string[]): string {
|
|
1079
1111
|
const sections = [
|
|
1080
|
-
"<repo_memory>",
|
|
1081
1112
|
"Use repo memory for continuity across sessions in this repo.",
|
|
1082
1113
|
"Source-of-truth memory rules:",
|
|
1083
1114
|
"<memory-readme>",
|
|
@@ -1095,7 +1126,6 @@ function buildMemoryPrompt(readme: string, blocks: CoreBlocks, researchFiles: st
|
|
|
1095
1126
|
`### focus.md\n${blocks.focus.trimEnd() || "(empty)"}`,
|
|
1096
1127
|
"",
|
|
1097
1128
|
`### pending.md\n${blocks.pending.trimEnd() || "(empty)"}`,
|
|
1098
|
-
"</repo_memory>",
|
|
1099
1129
|
];
|
|
1100
1130
|
return sections.join("\n");
|
|
1101
1131
|
}
|
|
@@ -1785,8 +1815,8 @@ export default function memoryExtension(pi: ExtensionAPI): void {
|
|
|
1785
1815
|
`If memory_update_block would exceed the ${CORE_LINE_CAP}-line or ${CORE_CHAR_CAP}-character core cap, use memory_dream for consolidation. Do not discard unresolved work to fit.`,
|
|
1786
1816
|
],
|
|
1787
1817
|
parameters: MEMORY_UPDATE_BLOCK_PARAMS,
|
|
1788
|
-
async execute(_toolCallId, params,
|
|
1789
|
-
const result = await updateCoreBlock(ctx.cwd, params.name, params.content);
|
|
1818
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
1819
|
+
const result = await updateCoreBlock(ctx.cwd, params.name, params.content, signal);
|
|
1790
1820
|
|
|
1791
1821
|
return {
|
|
1792
1822
|
content: [
|
|
@@ -1817,15 +1847,19 @@ export default function memoryExtension(pi: ExtensionAPI): void {
|
|
|
1817
1847
|
"The memory log is append-only. Do not rewrite or truncate older entries.",
|
|
1818
1848
|
],
|
|
1819
1849
|
parameters: MEMORY_APPEND_LOG_PARAMS,
|
|
1820
|
-
async execute(_toolCallId, params,
|
|
1821
|
-
const entry = await appendMemoryLog(
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1850
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
1851
|
+
const entry = await appendMemoryLog(
|
|
1852
|
+
ctx.cwd,
|
|
1853
|
+
{
|
|
1854
|
+
type: params.type,
|
|
1855
|
+
title: params.title,
|
|
1856
|
+
body: params.body,
|
|
1857
|
+
importance: params.importance,
|
|
1858
|
+
supersedes: params.supersedes,
|
|
1859
|
+
invalidates: params.invalidates,
|
|
1860
|
+
},
|
|
1861
|
+
signal,
|
|
1862
|
+
);
|
|
1829
1863
|
|
|
1830
1864
|
return {
|
|
1831
1865
|
content: [
|
|
@@ -1852,8 +1886,8 @@ export default function memoryExtension(pi: ExtensionAPI): void {
|
|
|
1852
1886
|
"Use memory_dream when logs have accumulated, core needs compression, or recent compaction context should be folded into memory.",
|
|
1853
1887
|
],
|
|
1854
1888
|
parameters: MEMORY_DREAM_PARAMS,
|
|
1855
|
-
async execute(_toolCallId, params,
|
|
1856
|
-
return toolDream(ctx.cwd, ctx, params.reason);
|
|
1889
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
1890
|
+
return toolDream(ctx.cwd, ctx, params.reason, signal);
|
|
1857
1891
|
},
|
|
1858
1892
|
}),
|
|
1859
1893
|
);
|
|
@@ -1910,9 +1944,7 @@ export default function memoryExtension(pi: ExtensionAPI): void {
|
|
|
1910
1944
|
return;
|
|
1911
1945
|
}
|
|
1912
1946
|
|
|
1913
|
-
|
|
1914
|
-
systemPrompt: `${event.systemPrompt}\n\n${prompt}`,
|
|
1915
|
-
};
|
|
1947
|
+
event.systemPromptOptions.sections.repo_memory = prompt;
|
|
1916
1948
|
} catch (error) {
|
|
1917
1949
|
if (error instanceof MemoryReadmeMissingError) {
|
|
1918
1950
|
notify(ctx, `Repo memory disabled: ${error.message}`, "warning");
|
package/extensions/notify.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Sends native terminal notifications when Pi is waiting for input.
|
|
5
5
|
* - Agent finished and ready for the next prompt
|
|
6
|
-
* -
|
|
6
|
+
* - Standard extension dialogs are waiting for input (custom UI stays silent)
|
|
7
7
|
* Supports multiple terminal protocols:
|
|
8
8
|
* - OSC 9: Ghostty, Supacode, iTerm2, WezTerm
|
|
9
9
|
* - OSC 99: Kitty
|
|
@@ -114,9 +114,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
114
114
|
currentSessionKey = getSessionKey(ctx);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
pi.on("ui_prompt_start", async () => {
|
|
117
|
+
pi.on("ui_prompt_start", async (event) => {
|
|
118
118
|
promptPending = true;
|
|
119
|
-
|
|
119
|
+
if (event.kind !== "custom") {
|
|
120
|
+
notify("Pi", "Waiting for input");
|
|
121
|
+
}
|
|
120
122
|
});
|
|
121
123
|
|
|
122
124
|
pi.on("ui_prompt_end", async () => {
|