pi-goal-list-loop-audit 0.32.1 → 0.33.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.
|
@@ -338,6 +338,7 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
338
338
|
} finally {
|
|
339
339
|
clearInterval(stallTimer);
|
|
340
340
|
unsub();
|
|
341
|
+
args.signal?.removeEventListener("abort", abort); // v0.33.1: don't retain the session via the signal
|
|
341
342
|
// v0.32.0: dispose the auditor session — each complete_goal leaked one
|
|
342
343
|
// session's subscriptions/stream resources for the parent's lifetime.
|
|
343
344
|
(session as any).dispose?.();
|
|
@@ -48,6 +48,37 @@ export function truncate(s: string, max: number): string {
|
|
|
48
48
|
return s.length <= max ? s : s.slice(0, Math.max(0, max - 1)) + "…";
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
52
|
+
/** v0.33.1: painted strings measure by their VISIBLE width. */
|
|
53
|
+
function visibleLen(s: string): number {
|
|
54
|
+
return s.replace(ANSI_RE, "").length;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** v0.33.0: 5-cell meter with a rounding guard (command-code's rule — never
|
|
58
|
+
* shows empty or full unless the value truly is 0 or 1). */
|
|
59
|
+
export function meter(frac: number, cells = 5): string {
|
|
60
|
+
if (!Number.isFinite(frac) || frac <= 0) return "▱".repeat(cells);
|
|
61
|
+
if (frac >= 1) return "▰".repeat(cells);
|
|
62
|
+
let filled = Math.round(frac * cells);
|
|
63
|
+
if (filled === 0) filled = 1;
|
|
64
|
+
if (filled === cells) filled = cells - 1;
|
|
65
|
+
return "▰".repeat(filled) + "▱".repeat(cells - filled);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** v0.33.0: one finished tool call, for the slim card's "last action" line. */
|
|
69
|
+
export interface RecentActionDisplay {
|
|
70
|
+
name: string;
|
|
71
|
+
arg?: string;
|
|
72
|
+
ms: number;
|
|
73
|
+
ok: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** v0.33.0: widget extras — the refire streak plus the recent-action feed. */
|
|
77
|
+
export interface WidgetExtras {
|
|
78
|
+
stalls?: number;
|
|
79
|
+
recent?: RecentActionDisplay[];
|
|
80
|
+
}
|
|
81
|
+
|
|
51
82
|
/**
|
|
52
83
|
* Word-wrap to `width`, capped at `maxLines` (v0.27.1). A pause is the one
|
|
53
84
|
* state where the FULL text matters — the reason often carries a decision
|
|
@@ -134,7 +165,7 @@ export interface AuditDisplayProgress {
|
|
|
134
165
|
* One-line status for ctx.ui.setStatus("pi-glla", …).
|
|
135
166
|
* Returns undefined when nothing is being supervised (clears the segment).
|
|
136
167
|
*/
|
|
137
|
-
export function buildStatusText(state: State, audit?: AuditDisplayProgress | null, now = Date.now(), theme?: DisplayTheme, extras?:
|
|
168
|
+
export function buildStatusText(state: State, audit?: AuditDisplayProgress | null, now = Date.now(), theme?: DisplayTheme, extras?: WidgetExtras): string | undefined {
|
|
138
169
|
if (state.loop?.active) {
|
|
139
170
|
const l = state.loop;
|
|
140
171
|
// v0.26.1: surface the refire streak — a spinning supervisor is the
|
|
@@ -223,7 +254,7 @@ function countTotal(g: Goal): number {
|
|
|
223
254
|
* Widget lines for ctx.ui.setWidget("pi-glla", lines).
|
|
224
255
|
* Returns undefined when nothing is worth showing.
|
|
225
256
|
*/
|
|
226
|
-
export function buildWidgetLines(state: State, audit?: AuditDisplayProgress | null, now = Date.now(), theme?: DisplayTheme, width?: number, extras?:
|
|
257
|
+
export function buildWidgetLines(state: State, audit?: AuditDisplayProgress | null, now = Date.now(), theme?: DisplayTheme, width?: number, extras?: WidgetExtras): string[] | undefined {
|
|
227
258
|
const inner = buildWidgetLinesInner(state, audit, now, theme, width, extras);
|
|
228
259
|
// v0.28.6 (E1): a persistence failure outranks everything — first line,
|
|
229
260
|
// on every render, until a write lands again.
|
|
@@ -234,7 +265,7 @@ export function buildWidgetLines(state: State, audit?: AuditDisplayProgress | nu
|
|
|
234
265
|
return inner;
|
|
235
266
|
}
|
|
236
267
|
|
|
237
|
-
function buildWidgetLinesInner(state: State, audit?: AuditDisplayProgress | null, now = Date.now(), theme?: DisplayTheme, width?: number, extras?:
|
|
268
|
+
function buildWidgetLinesInner(state: State, audit?: AuditDisplayProgress | null, now = Date.now(), theme?: DisplayTheme, width?: number, extras?: WidgetExtras): string[] | undefined {
|
|
238
269
|
if (state.loop?.active) return loopLines(state.loop, now, theme, width, extras);
|
|
239
270
|
const g = state.goal;
|
|
240
271
|
const held = heldLoop(state);
|
|
@@ -242,7 +273,7 @@ function buildWidgetLinesInner(state: State, audit?: AuditDisplayProgress | null
|
|
|
242
273
|
// v0.28.17: no visible goal — the held loop gets its own card.
|
|
243
274
|
return held ? heldLoopLines(held, now, theme, width) : undefined;
|
|
244
275
|
}
|
|
245
|
-
const lines = goalLines(g, state, audit, now, theme, width);
|
|
276
|
+
const lines = goalLines(g, state, audit, now, theme, width, extras);
|
|
246
277
|
// v0.28.17: a held loop rides the goal card as a trailing line.
|
|
247
278
|
if (held) {
|
|
248
279
|
lines.push(`${paint(theme, "warning", "⏸")} ${truncate(held.target, budgetFor(width, 3, 64))}`);
|
|
@@ -262,7 +293,7 @@ function heldLoopLines(l: LoopState, now: number, theme?: DisplayTheme, width?:
|
|
|
262
293
|
|
|
263
294
|
// Branch lines sit flush-left (pi-tasks convention): pi's widget renderer
|
|
264
295
|
// adds its own one-space gutter, so any indent here doubles up.
|
|
265
|
-
function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | undefined, now: number, theme?: DisplayTheme, width?: number): string[] {
|
|
296
|
+
function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | undefined, now: number, theme?: DisplayTheme, width?: number, extras?: WidgetExtras): string[] {
|
|
266
297
|
// Head glyph is ● (not ◆): U+25C6 renders as a color-emoji diamond in some
|
|
267
298
|
// terminal fonts and ignores ANSI color; ● takes the paint everywhere.
|
|
268
299
|
const icon =
|
|
@@ -271,22 +302,41 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
271
302
|
: g.status === "auditing"
|
|
272
303
|
? paint(theme, "accent", "⟡")
|
|
273
304
|
: paint(theme, "success", "●");
|
|
274
|
-
const head = `${icon} ${truncate(g.objective.replace(/\s+/g, " "), budgetFor(width, 3, 64))}`;
|
|
275
305
|
// v0.24.7: a list item is named as such and points at /list — before,
|
|
276
306
|
// the widget called it "active" and hinted "/goal status", reading as if
|
|
277
307
|
// queue work were a standalone goal.
|
|
278
308
|
const isList = g.policy === "list";
|
|
279
309
|
const statusWord = g.status === "active" ? paint(theme, "success", "active") : g.status;
|
|
280
|
-
// v0.
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
|
|
310
|
+
// v0.33.0: slim card — status folds INTO the head line as middot segments
|
|
311
|
+
// (filter(Boolean).join, the universal CLI idiom). Line 2 is the live
|
|
312
|
+
// "last action · next task" line; the footer stays the hint line.
|
|
313
|
+
// v0.28.30: the type stays visible — v0.33.0 names it via the "list item"
|
|
314
|
+
// header segment (list policy) and the distinct card icons (● goal,
|
|
315
|
+
// ∞/↓/↑ loop, ⟡ auditing, ⏸ paused) + the type-named footer verbs.
|
|
285
316
|
// Token segment only when a budget is set (v0.22.0): the guard is opt-in,
|
|
286
317
|
// and "0/0 tok" carried no information when off.
|
|
287
318
|
const tokenLimit = g.usage?.tokensLimit ?? 0;
|
|
288
|
-
const
|
|
289
|
-
|
|
319
|
+
const headSegs: string[] = [];
|
|
320
|
+
if (isList) headSegs.push("list item");
|
|
321
|
+
headSegs.push(statusWord);
|
|
322
|
+
headSegs.push(fmtElapsed(now - Date.parse(g.createdAt)));
|
|
323
|
+
const taskTotal = countTotal(g);
|
|
324
|
+
if (taskTotal > 0) headSegs.push(`${countDone(g)}/${taskTotal} ${paint(theme, "dim", meter(countDone(g) / taskTotal))}`);
|
|
325
|
+
const tokUsed0 = g.usage?.tokensUsed ?? 0;
|
|
326
|
+
if (tokenLimit > 0) headSegs.push(paint(theme, "dim", `${fmtTokens(tokUsed0)}/${fmtTokens(tokenLimit)} ${meter(tokUsed0 / tokenLimit)}`));
|
|
327
|
+
else if (tokUsed0 > 0) headSegs.push(paint(theme, "dim", `${fmtTokens(tokUsed0)} tok`));
|
|
328
|
+
// v0.28.30: the type stays visible — v0.33.0 names it via the "list item"
|
|
329
|
+
// header segment (list policy) and the distinct card icons (● goal,
|
|
330
|
+
// ∞/↓/↑ loop, ⟡ auditing, ⏸ paused) + the type-named footer verbs.
|
|
331
|
+
// Token segment only when a budget is set (v0.22.0): the guard is opt-in,
|
|
332
|
+
// and "0/0 tok" carried no information when off.
|
|
333
|
+
// v0.33.1: the head must FIT the terminal — the segments are fixed, so
|
|
334
|
+
// the objective absorbs whatever room is left (was: objective budgeted
|
|
335
|
+
// alone, segments appended unbudgeted → 140-col heads at width 100).
|
|
336
|
+
const segsText = headSegs.join(` ${paint(theme, "dim", "·")} `);
|
|
337
|
+
const objBudget = width && width > 0 ? Math.max(16, width - 1 - 2 - 3 - visibleLen(segsText)) : 48;
|
|
338
|
+
const head = `${icon} ${truncate(g.objective.replace(/\s+/g, " "), objBudget)} ${paint(theme, "dim", "·")} ${segsText}`;
|
|
339
|
+
const lines = [head];
|
|
290
340
|
if (g.status === "auditing") {
|
|
291
341
|
lines.push(`├─ auditor: ${audit?.label ?? "running"}${audit?.currentTool ? ` · ${truncate(audit.currentTool, 30)}` : ""}`);
|
|
292
342
|
// v0.25.4: auditor-quiet stall — progress events stopped arriving
|
|
@@ -357,8 +407,16 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
357
407
|
}
|
|
358
408
|
return lines;
|
|
359
409
|
}
|
|
410
|
+
// v0.33.0: "last action · next task" — Claude's done-row format meets the
|
|
411
|
+
// pending queue. Segments join with a dim middot; missing ones drop out.
|
|
412
|
+
const act = extras?.recent?.[extras.recent.length - 1];
|
|
413
|
+
const mid: string[] = [];
|
|
414
|
+
if (act) {
|
|
415
|
+
mid.push(`${paint(theme, act.ok ? "success" : "error", act.ok ? "✓" : "✗")} ${act.name}${act.arg ? ` ${paint(theme, "dim", truncate(act.arg, 24))}` : ""}${act.ms > 0 ? ` ${paint(theme, "dim", `(${fmtElapsed(act.ms)})`)}` : ""}`);
|
|
416
|
+
}
|
|
360
417
|
const next = nextPending(g);
|
|
361
|
-
if (next)
|
|
418
|
+
if (next) mid.push(`next: ${truncate(next, budgetFor(width, 9, 40))}`);
|
|
419
|
+
if (mid.length > 0) lines.push(`├─ ${mid.join(` ${paint(theme, "dim", "·")} `)}`);
|
|
362
420
|
const queue = state.list?.length ?? 0;
|
|
363
421
|
const footer = isList
|
|
364
422
|
? `${queue > 0 ? `${queue} queued · ` : ""}/list · /glla`
|
|
@@ -367,34 +425,33 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
367
425
|
return lines;
|
|
368
426
|
}
|
|
369
427
|
|
|
370
|
-
function loopLines(l: LoopState, now: number, theme?: DisplayTheme, width?: number, extras?:
|
|
371
|
-
// v0.
|
|
372
|
-
|
|
373
|
-
//
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
428
|
+
function loopLines(l: LoopState, now: number, theme?: DisplayTheme, width?: number, extras?: WidgetExtras): string[] {
|
|
429
|
+
// v0.33.0: slim loop card — header icon names the kind (∞ metricless,
|
|
430
|
+
// ↓/↑ metric), all state folds into middot segments; line 2 is the live
|
|
431
|
+
// "last action" line; footer is hints. The old per-line "loop ∞ iter" /
|
|
432
|
+
// "best/last/stall" rows collapse into the header.
|
|
433
|
+
const stallNote = (extras?.stalls ?? 0) > 0 ? ` ${paint(theme, "dim", "·")} ${paint(theme, "warning", `stalls:${extras!.stalls}`)}` : "";
|
|
434
|
+
const icon = !l.measureCmd ? paint(theme, "accent", "∞") : paint(theme, "accent", l.direction === "min" ? "↓" : "↑");
|
|
435
|
+
const segs: string[] = [];
|
|
436
|
+
segs.push(`iter ${l.iteration}${l.maxIterations > 0 ? `/${l.maxIterations} ${paint(theme, "dim", meter(l.iteration / l.maxIterations))}` : ""}`);
|
|
437
|
+
segs.push(fmtElapsed(now - Date.parse(l.startedAt)));
|
|
438
|
+
if (l.measureCmd) {
|
|
439
|
+
segs.push(`best ${paint(theme, "success", `${l.bestValue ?? "n/a"}`)}`);
|
|
440
|
+
segs.push(`last ${l.lastValue ?? "n/a"}`); // v0.33.1: a plateauing loop's current reading stays visible
|
|
441
|
+
const stallText = `stall ${l.stallCount}/${l.plateauWindow}`;
|
|
442
|
+
segs.push(l.stallCount >= l.plateauWindow - 1 ? paint(theme, "warning", stallText) : stallText);
|
|
382
443
|
}
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
`└─ ${paint(theme, "dim", l.kind === "audit"
|
|
395
|
-
? "metric: closed findings ('- [x]' count)"
|
|
396
|
-
: truncate(l.measureCmd, budgetFor(width, 3, 56)))}`,
|
|
397
|
-
];
|
|
444
|
+
const segsText = segs.join(` ${paint(theme, "dim", "·")} `);
|
|
445
|
+
const targetBudget = width && width > 0 ? Math.max(16, width - 1 - 2 - 3 - visibleLen(segsText) - visibleLen(stallNote)) : 44;
|
|
446
|
+
const lines = [`${icon} ${truncate(l.target, targetBudget)} ${paint(theme, "dim", "·")} ${segsText}${stallNote}`];
|
|
447
|
+
const act = extras?.recent?.[extras.recent.length - 1];
|
|
448
|
+
if (act) {
|
|
449
|
+
lines.push(`├─ ${paint(theme, act.ok ? "success" : "error", act.ok ? "✓" : "✗")} ${act.name}${act.arg ? ` ${paint(theme, "dim", truncate(act.arg, 24))}` : ""}${act.ms > 0 ? ` ${paint(theme, "dim", `(${fmtElapsed(act.ms)})`)}` : ""}`);
|
|
450
|
+
}
|
|
451
|
+
const footer = !l.measureCmd
|
|
452
|
+
? "metricless (no plateau) · /loop stop · /loop polish"
|
|
453
|
+
: `${l.kind === "audit" ? "metric: closed findings" : truncate(l.measureCmd, budgetFor(width, 3, 30))} · /loop stop`;
|
|
454
|
+
lines.push(`└─ ${paint(theme, "dim", footer)}`);
|
|
398
455
|
if (l.branchName) lines.push(`⎇ ${paint(theme, "muted", truncate(l.branchName, budgetFor(width, 3, 50)))}`);
|
|
399
456
|
return lines;
|
|
400
457
|
}
|
|
@@ -211,6 +211,7 @@ export const SETTINGS_KEYS: Array<keyof Settings> = [
|
|
|
211
211
|
"stallSimilarityThreshold",
|
|
212
212
|
"postaudit",
|
|
213
213
|
"toolOverrides",
|
|
214
|
+
"reviewer", // v0.33.1: legacy alias — menu saves can write it; provenance must know it exists
|
|
214
215
|
];
|
|
215
216
|
|
|
216
217
|
/** Where each effective setting comes from (for the /glla display). */
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -630,6 +630,38 @@ function isSupervising(): boolean {
|
|
|
630
630
|
let latestAuditProgress: AuditDisplayProgress | null = null;
|
|
631
631
|
let uiTicker: NodeJS.Timeout | null = null;
|
|
632
632
|
|
|
633
|
+
// v0.33.0: slim widget "last action" feed — a tiny ring of finished tool
|
|
634
|
+
// calls {name, arg, ms, ok} captured from the tool_call/tool_result stream.
|
|
635
|
+
// Display-only, never persisted; cleared implicitly as new actions land.
|
|
636
|
+
const recentActions: import("../goal-loop-display.js").RecentActionDisplay[] = [];
|
|
637
|
+
const inFlightToolCalls = new Map<string, { name: string; arg?: string; at: number }>();
|
|
638
|
+
function summarizeToolArg(name: string, input: any): string | undefined {
|
|
639
|
+
if (!input || typeof input !== "object") return undefined;
|
|
640
|
+
const v = input.file_path ?? input.path ?? input.command ?? input.pattern ?? input.query ?? input.url ?? input.title;
|
|
641
|
+
if (typeof v !== "string" || v.length === 0) return undefined;
|
|
642
|
+
// v0.33.1: strip control chars BEFORE truncating — a raw \n breaks the
|
|
643
|
+
// widget card into un-prefixed lines (footer spoof), a raw ESC corrupts
|
|
644
|
+
// the TUI. The objective path was already whitespace-collapsed; this
|
|
645
|
+
// path was the gap.
|
|
646
|
+
const base = (name === "bash" ? v : v.split("/").pop() || v).replace(/[\x00-\x1f\x7f-\x9f]/g, " ").replace(/\s+/g, " ").trim();
|
|
647
|
+
return base.length <= 24 ? base : base.slice(0, 23) + "…";
|
|
648
|
+
}
|
|
649
|
+
function noteToolCall(event: any): void {
|
|
650
|
+
const name = String(event?.toolName ?? "?");
|
|
651
|
+
const id = String(event?.toolCallId ?? event?.id ?? `anon-${Date.now()}`);
|
|
652
|
+
if (inFlightToolCalls.size >= 20) inFlightToolCalls.delete(inFlightToolCalls.keys().next().value!); // v0.33.1: evict BEFORE the 21st
|
|
653
|
+
inFlightToolCalls.set(id, { name, arg: summarizeToolArg(name, event?.input ?? event?.args), at: Date.now() });
|
|
654
|
+
}
|
|
655
|
+
function noteToolResult(event: any): void {
|
|
656
|
+
const id = String(event?.toolCallId ?? event?.id ?? "");
|
|
657
|
+
const f = id ? inFlightToolCalls.get(id) : undefined;
|
|
658
|
+
if (id) inFlightToolCalls.delete(id);
|
|
659
|
+
const ok = !Boolean(event?.isError ?? event?.error);
|
|
660
|
+
const name = f?.name ?? String(event?.toolName ?? "?");
|
|
661
|
+
recentActions.push({ name, arg: f?.arg ?? summarizeToolArg(name, event?.input ?? event?.args), ms: f ? Date.now() - f.at : 0, ok });
|
|
662
|
+
if (recentActions.length > 3) recentActions.shift();
|
|
663
|
+
}
|
|
664
|
+
|
|
633
665
|
function refreshUI(ctx: ExtensionContext): void {
|
|
634
666
|
if (!ctx.hasUI) return;
|
|
635
667
|
try {
|
|
@@ -638,7 +670,7 @@ function refreshUI(ctx: ExtensionContext): void {
|
|
|
638
670
|
// uses the room instead of cutting at fixed ~60-char floors.
|
|
639
671
|
const width = process.stdout.columns || 80;
|
|
640
672
|
ctx.ui.setStatus("pi-glla", buildStatusText(state, latestAuditProgress, Date.now(), theme, { stalls: consecutiveStalls }));
|
|
641
|
-
ctx.ui.setWidget("pi-glla", buildWidgetLines(state, latestAuditProgress, Date.now(), theme, width, { stalls: consecutiveStalls }));
|
|
673
|
+
ctx.ui.setWidget("pi-glla", buildWidgetLines(state, latestAuditProgress, Date.now(), theme, width, { stalls: consecutiveStalls, recent: recentActions }));
|
|
642
674
|
} catch {
|
|
643
675
|
// stale ctx — next event refreshes
|
|
644
676
|
}
|
|
@@ -837,6 +869,14 @@ function heartbeatTick(): void {
|
|
|
837
869
|
if (!absorbStaleIfSuperseded(ctx)) goStaleTerminal(ctx, "heartbeat probe");
|
|
838
870
|
return;
|
|
839
871
|
}
|
|
872
|
+
// v0.33.1: nothing supervised → the compact debt/resync belong to a dead
|
|
873
|
+
// goal/loop. Discharge here so a later goal can't inherit a bogus RESYNC
|
|
874
|
+
// block or a spurious forced refire (the old in-guard `else` was
|
|
875
|
+
// unreachable — isSupervising() ≡ isLoopActive() || isActionableGoal()).
|
|
876
|
+
if (!isSupervising() && (postCompactResumeOwed || postCompactResyncPending)) {
|
|
877
|
+
postCompactResumeOwed = false;
|
|
878
|
+
postCompactResyncPending = false;
|
|
879
|
+
}
|
|
840
880
|
// v0.32.1: post-compaction resume debt — retry on every heartbeat tick
|
|
841
881
|
// past grace until a turn actually starts. Fixed-offset settles alone
|
|
842
882
|
// can both lose (pi busy at 2s AND at grace+2s = a dangling chain).
|
|
@@ -1076,7 +1116,10 @@ function sendContinuation(goalId: string): void {
|
|
|
1076
1116
|
}
|
|
1077
1117
|
if (!extensionApi || extensionApiStale) return;
|
|
1078
1118
|
try {
|
|
1079
|
-
|
|
1119
|
+
let resync = "";
|
|
1120
|
+
// v0.33.1: a builder throw (corrupt restored state) must not masquerade
|
|
1121
|
+
// as a transport failure — send without the block instead.
|
|
1122
|
+
if (postCompactResyncPending) { try { resync = buildPostCompactResync(); } catch { resync = ""; } }
|
|
1080
1123
|
extensionApi.sendMessage({
|
|
1081
1124
|
customType: GOAL_EVENT_ENTRY,
|
|
1082
1125
|
content: resync + continuationPrompt(state.goal!),
|
|
@@ -1251,6 +1294,14 @@ function notifyPersistenceState(ctx: ExtensionContext): void {
|
|
|
1251
1294
|
}
|
|
1252
1295
|
|
|
1253
1296
|
function setGoal(goal: Goal, ctx: ExtensionContext, via = "user"): void {
|
|
1297
|
+
// v0.33.1: per-goal module state resets at activation — a new goal must
|
|
1298
|
+
// not inherit the previous goal's compact debt/resync, quota streak,
|
|
1299
|
+
// token-message dedupe set, or widget action feed.
|
|
1300
|
+
postCompactResumeOwed = false;
|
|
1301
|
+
postCompactResyncPending = false;
|
|
1302
|
+
quotaRetryStreak = 0;
|
|
1303
|
+
countedTokenMessages.clear();
|
|
1304
|
+
recentActions.length = 0;
|
|
1254
1305
|
// v0.28.14: never silently orphan a live goal — a paused/active goal
|
|
1255
1306
|
// being replaced is archived honestly first (the old behavior left it in
|
|
1256
1307
|
// goals/ but untracked: "older goals lying around leading to confusion").
|
|
@@ -1369,6 +1420,8 @@ async function fanOutListAuditFindings(ctx: ExtensionContext): Promise<void> {
|
|
|
1369
1420
|
}
|
|
1370
1421
|
|
|
1371
1422
|
function archiveCurrentGoal(ctx: ExtensionContext, status: Status, stopReason?: string): void {
|
|
1423
|
+
postCompactResumeOwed = false; // v0.33.1: the dead goal's compact debt/resync dies with it
|
|
1424
|
+
postCompactResyncPending = false;
|
|
1372
1425
|
if (!state.goal) return;
|
|
1373
1426
|
const goal = state.goal;
|
|
1374
1427
|
ensureDirs(ctx.cwd);
|
|
@@ -2610,7 +2663,13 @@ function sendLoopTurn(): void {
|
|
|
2610
2663
|
if (!isLoopActive() || !extensionApi) return;
|
|
2611
2664
|
const ctx = freshCtx();
|
|
2612
2665
|
if (!ctx || !ctx.isIdle() || ctx.hasPendingMessages()) {
|
|
2613
|
-
if (ctx)
|
|
2666
|
+
if (!ctx) {
|
|
2667
|
+
// v0.33.1: mirror sendContinuation — probe the handle (terminal exit)
|
|
2668
|
+
// and advance the streak so the cadence backs off instead of spinning
|
|
2669
|
+
// a flat 50ms below every watchdog.
|
|
2670
|
+
if (probeExtensionApiStale()) return;
|
|
2671
|
+
loopRearmStreak++;
|
|
2672
|
+
} else accountSendRearm(ctx, "loop");
|
|
2614
2673
|
loopTimer = setTimeout(() => sendLoopTurn(), sendRearmDelayMs(loopRearmStreak)); // v0.28.29: backing-off cadence
|
|
2615
2674
|
loopTimer.unref?.();
|
|
2616
2675
|
return;
|
|
@@ -2666,7 +2725,8 @@ function sendLoopTurn(): void {
|
|
|
2666
2725
|
// instruction (metricless loops; metric loops already vary via values).
|
|
2667
2726
|
const variantNote = metricless ? continueVariant(loop.iteration) : "";
|
|
2668
2727
|
try {
|
|
2669
|
-
|
|
2728
|
+
let loopResync = "";
|
|
2729
|
+
if (postCompactResyncPending) { try { loopResync = buildPostCompactResync(); } catch { loopResync = ""; } } // v0.33.1
|
|
2670
2730
|
extensionApi.sendMessage({
|
|
2671
2731
|
customType: GOAL_EVENT_ENTRY,
|
|
2672
2732
|
content: loopResync + loopPrompt(loop, regressionNote, strategyNote, boundsNote, interventionNote, variantNote),
|
|
@@ -5817,6 +5877,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5817
5877
|
// v0.15.1: ask_user_question answers arrive as tool results, not chat
|
|
5818
5878
|
// messages — count answered (non-cancelled) questionnaires as replies too.
|
|
5819
5879
|
pi.on("tool_result", async (event: any) => {
|
|
5880
|
+
noteToolResult(event); // v0.33.0: slim widget "last action" feed
|
|
5820
5881
|
// v0.24.0: roll loop tool-result fingerprints (same-tool-same-result
|
|
5821
5882
|
// detection) — recorded for ANY tool result while a loop is active.
|
|
5822
5883
|
if (isLoopActive()) {
|
|
@@ -5891,6 +5952,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5891
5952
|
writeOwnerFile(ctx.cwd);
|
|
5892
5953
|
sessionReplacementUntil = 0;
|
|
5893
5954
|
zombieStoodDown = false;
|
|
5955
|
+
staleTerminalDone = false; // v0.33.1: a rebound session must be able to go terminal AGAIN (was: one-shot for the process lifetime)
|
|
5956
|
+
postCompactResumeOwed = false; // v0.33.1: a compact from a previous session must not resync THIS one
|
|
5957
|
+
postCompactResyncPending = false;
|
|
5894
5958
|
const startReason = typeof event?.reason === "string" ? event.reason : "unknown";
|
|
5895
5959
|
appendLedger(ctx.cwd, "session_rebound", { reason: startReason });
|
|
5896
5960
|
if (extensionApiStale) {
|
|
@@ -6390,10 +6454,11 @@ export default function (pi: ExtensionAPI): void {
|
|
|
6390
6454
|
scheduleContinuation(ctx, false);
|
|
6391
6455
|
});
|
|
6392
6456
|
|
|
6393
|
-
pi.on("tool_call", () => {
|
|
6457
|
+
pi.on("tool_call", (event: any) => {
|
|
6394
6458
|
toolCallsThisTurn++;
|
|
6395
6459
|
noteActivity(true);
|
|
6396
6460
|
lastStreamActivityAt = Date.now();
|
|
6461
|
+
noteToolCall(event); // v0.33.0
|
|
6397
6462
|
// v0.24.0: count loop-iteration tool calls (narration-only detection).
|
|
6398
6463
|
if (isLoopActive()) {
|
|
6399
6464
|
state.loop!.toolsThisTurn = (state.loop!.toolsThisTurn ?? 0) + 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.1",
|
|
4
4
|
"description": "Goal. Loop. Audit. Done. \u2014 a pi-coding-agent extension that supervises long-running work, with isolated auditor on each completion. Beat bamboozling by design: the auditor runs in a fresh session with no extensions, no skills, no editor \u2014 only the read tools needed to verify your goal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dracon",
|