pi-goal-list-loop-audit 0.33.0 → 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,12 @@ 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
|
+
|
|
51
57
|
/** v0.33.0: 5-cell meter with a rounding guard (command-code's rule — never
|
|
52
58
|
* shows empty or full unless the value truly is 0 or 1). */
|
|
53
59
|
export function meter(frac: number, cells = 5): string {
|
|
@@ -296,7 +302,6 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
296
302
|
: g.status === "auditing"
|
|
297
303
|
? paint(theme, "accent", "⟡")
|
|
298
304
|
: paint(theme, "success", "●");
|
|
299
|
-
const headBase = `${icon} ${truncate(g.objective.replace(/\s+/g, " "), budgetFor(width, 3, 48))}`;
|
|
300
305
|
// v0.24.7: a list item is named as such and points at /list — before,
|
|
301
306
|
// the widget called it "active" and hinted "/goal status", reading as if
|
|
302
307
|
// queue work were a standalone goal.
|
|
@@ -325,7 +330,12 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
325
330
|
// ∞/↓/↑ loop, ⟡ auditing, ⏸ paused) + the type-named footer verbs.
|
|
326
331
|
// Token segment only when a budget is set (v0.22.0): the guard is opt-in,
|
|
327
332
|
// and "0/0 tok" carried no information when off.
|
|
328
|
-
|
|
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}`;
|
|
329
339
|
const lines = [head];
|
|
330
340
|
if (g.status === "auditing") {
|
|
331
341
|
lines.push(`├─ auditor: ${audit?.label ?? "running"}${audit?.currentTool ? ` · ${truncate(audit.currentTool, 30)}` : ""}`);
|
|
@@ -427,10 +437,13 @@ function loopLines(l: LoopState, now: number, theme?: DisplayTheme, width?: numb
|
|
|
427
437
|
segs.push(fmtElapsed(now - Date.parse(l.startedAt)));
|
|
428
438
|
if (l.measureCmd) {
|
|
429
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
|
|
430
441
|
const stallText = `stall ${l.stallCount}/${l.plateauWindow}`;
|
|
431
442
|
segs.push(l.stallCount >= l.plateauWindow - 1 ? paint(theme, "warning", stallText) : stallText);
|
|
432
443
|
}
|
|
433
|
-
const
|
|
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}`];
|
|
434
447
|
const act = extras?.recent?.[extras.recent.length - 1];
|
|
435
448
|
if (act) {
|
|
436
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)})`)}` : ""}`);
|
|
@@ -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
|
@@ -639,13 +639,17 @@ function summarizeToolArg(name: string, input: any): string | undefined {
|
|
|
639
639
|
if (!input || typeof input !== "object") return undefined;
|
|
640
640
|
const v = input.file_path ?? input.path ?? input.command ?? input.pattern ?? input.query ?? input.url ?? input.title;
|
|
641
641
|
if (typeof v !== "string" || v.length === 0) return undefined;
|
|
642
|
-
|
|
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();
|
|
643
647
|
return base.length <= 24 ? base : base.slice(0, 23) + "…";
|
|
644
648
|
}
|
|
645
649
|
function noteToolCall(event: any): void {
|
|
646
650
|
const name = String(event?.toolName ?? "?");
|
|
647
651
|
const id = String(event?.toolCallId ?? event?.id ?? `anon-${Date.now()}`);
|
|
648
|
-
if (inFlightToolCalls.size
|
|
652
|
+
if (inFlightToolCalls.size >= 20) inFlightToolCalls.delete(inFlightToolCalls.keys().next().value!); // v0.33.1: evict BEFORE the 21st
|
|
649
653
|
inFlightToolCalls.set(id, { name, arg: summarizeToolArg(name, event?.input ?? event?.args), at: Date.now() });
|
|
650
654
|
}
|
|
651
655
|
function noteToolResult(event: any): void {
|
|
@@ -865,6 +869,14 @@ function heartbeatTick(): void {
|
|
|
865
869
|
if (!absorbStaleIfSuperseded(ctx)) goStaleTerminal(ctx, "heartbeat probe");
|
|
866
870
|
return;
|
|
867
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
|
+
}
|
|
868
880
|
// v0.32.1: post-compaction resume debt — retry on every heartbeat tick
|
|
869
881
|
// past grace until a turn actually starts. Fixed-offset settles alone
|
|
870
882
|
// can both lose (pi busy at 2s AND at grace+2s = a dangling chain).
|
|
@@ -1104,7 +1116,10 @@ function sendContinuation(goalId: string): void {
|
|
|
1104
1116
|
}
|
|
1105
1117
|
if (!extensionApi || extensionApiStale) return;
|
|
1106
1118
|
try {
|
|
1107
|
-
|
|
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 = ""; } }
|
|
1108
1123
|
extensionApi.sendMessage({
|
|
1109
1124
|
customType: GOAL_EVENT_ENTRY,
|
|
1110
1125
|
content: resync + continuationPrompt(state.goal!),
|
|
@@ -1279,6 +1294,14 @@ function notifyPersistenceState(ctx: ExtensionContext): void {
|
|
|
1279
1294
|
}
|
|
1280
1295
|
|
|
1281
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;
|
|
1282
1305
|
// v0.28.14: never silently orphan a live goal — a paused/active goal
|
|
1283
1306
|
// being replaced is archived honestly first (the old behavior left it in
|
|
1284
1307
|
// goals/ but untracked: "older goals lying around leading to confusion").
|
|
@@ -1397,6 +1420,8 @@ async function fanOutListAuditFindings(ctx: ExtensionContext): Promise<void> {
|
|
|
1397
1420
|
}
|
|
1398
1421
|
|
|
1399
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;
|
|
1400
1425
|
if (!state.goal) return;
|
|
1401
1426
|
const goal = state.goal;
|
|
1402
1427
|
ensureDirs(ctx.cwd);
|
|
@@ -2638,7 +2663,13 @@ function sendLoopTurn(): void {
|
|
|
2638
2663
|
if (!isLoopActive() || !extensionApi) return;
|
|
2639
2664
|
const ctx = freshCtx();
|
|
2640
2665
|
if (!ctx || !ctx.isIdle() || ctx.hasPendingMessages()) {
|
|
2641
|
-
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");
|
|
2642
2673
|
loopTimer = setTimeout(() => sendLoopTurn(), sendRearmDelayMs(loopRearmStreak)); // v0.28.29: backing-off cadence
|
|
2643
2674
|
loopTimer.unref?.();
|
|
2644
2675
|
return;
|
|
@@ -2694,7 +2725,8 @@ function sendLoopTurn(): void {
|
|
|
2694
2725
|
// instruction (metricless loops; metric loops already vary via values).
|
|
2695
2726
|
const variantNote = metricless ? continueVariant(loop.iteration) : "";
|
|
2696
2727
|
try {
|
|
2697
|
-
|
|
2728
|
+
let loopResync = "";
|
|
2729
|
+
if (postCompactResyncPending) { try { loopResync = buildPostCompactResync(); } catch { loopResync = ""; } } // v0.33.1
|
|
2698
2730
|
extensionApi.sendMessage({
|
|
2699
2731
|
customType: GOAL_EVENT_ENTRY,
|
|
2700
2732
|
content: loopResync + loopPrompt(loop, regressionNote, strategyNote, boundsNote, interventionNote, variantNote),
|
|
@@ -5920,6 +5952,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5920
5952
|
writeOwnerFile(ctx.cwd);
|
|
5921
5953
|
sessionReplacementUntil = 0;
|
|
5922
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;
|
|
5923
5958
|
const startReason = typeof event?.reason === "string" ? event.reason : "unknown";
|
|
5924
5959
|
appendLedger(ctx.cwd, "session_rebound", { reason: startReason });
|
|
5925
5960
|
if (extensionApiStale) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.33.
|
|
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",
|