pi-goal-list-loop-audit 0.31.9 → 0.32.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,9 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
338
338
|
} finally {
|
|
339
339
|
clearInterval(stallTimer);
|
|
340
340
|
unsub();
|
|
341
|
+
// v0.32.0: dispose the auditor session — each complete_goal leaked one
|
|
342
|
+
// session's subscriptions/stream resources for the parent's lifetime.
|
|
343
|
+
(session as any).dispose?.();
|
|
341
344
|
}
|
|
342
345
|
|
|
343
346
|
if (stalled) {
|
|
@@ -188,6 +188,8 @@ export function loadGlobalSettings(): Settings {
|
|
|
188
188
|
/** Every provenance-tracked key (the /glla headless display + UI). */
|
|
189
189
|
export const SETTINGS_KEYS: Array<keyof Settings> = [
|
|
190
190
|
"auditorModel",
|
|
191
|
+
"auditorModelFallback",
|
|
192
|
+
"auditorSameSessionSwap",
|
|
191
193
|
"auditorThinkingLevel",
|
|
192
194
|
"notifyCmd",
|
|
193
195
|
"tokenLimit",
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -223,6 +223,14 @@ let extensionApi: ExtensionAPI | null = null;
|
|
|
223
223
|
// throws FOREVER in this process — retrying for hours is the hegemon
|
|
224
224
|
// failure shape. Detect the stale signature once and go terminally loud.
|
|
225
225
|
let extensionApiStale = false;
|
|
226
|
+
// v0.32.0: CRITICAL — goStaleTerminal must gate on its OWN flag, not
|
|
227
|
+
// extensionApiStale: probeExtensionApiStale() sets extensionApiStale on
|
|
228
|
+
// detection, so the heartbeat's `probe → goStaleTerminal` sequence always
|
|
229
|
+
// found the flag already true and returned silently — orphan-stale recovery
|
|
230
|
+
// (ledger, loop stop, interruptedAt, warn, AUTO-RELOAD SELF-HEAL) was dead
|
|
231
|
+
// code since v0.29.11. Field proof: hegemon sat stale for days and the
|
|
232
|
+
// wezterm self-heal never fired.
|
|
233
|
+
let staleTerminalDone = false;
|
|
226
234
|
|
|
227
235
|
/** v0.26.7: a stale api is terminal for this process — go loudly with
|
|
228
236
|
* restart guidance instead of retrying sends that can never land.
|
|
@@ -292,16 +300,24 @@ function absorbStaleIfSuperseded(ctx: ExtensionContext): boolean {
|
|
|
292
300
|
extensionApiStale = true; // silence the send paths WITHOUT the terminal theatre
|
|
293
301
|
clearLoopTimer();
|
|
294
302
|
if (continuationTimer) { clearTimeout(continuationTimer); continuationTimer = null; }
|
|
303
|
+
// v0.32.0: the superseded module's heartbeat + UI ticker were IMMORTAL
|
|
304
|
+
// (clearInterval appeared nowhere) — N /reloads = N×2 zombie tickers.
|
|
305
|
+
if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; }
|
|
306
|
+
if (uiTicker) { clearInterval(uiTicker); uiTicker = null; }
|
|
295
307
|
return true;
|
|
296
308
|
}
|
|
297
309
|
return false;
|
|
298
310
|
}
|
|
299
311
|
|
|
300
312
|
function goStaleTerminal(ctx: ExtensionContext, where: string): void {
|
|
301
|
-
if (
|
|
313
|
+
if (staleTerminalDone) return; // already terminal — don't re-spam
|
|
314
|
+
staleTerminalDone = true;
|
|
302
315
|
extensionApiStale = true;
|
|
303
316
|
appendLedger(ctx.cwd, "extension_api_stale", { where, kind: isLoopActive() ? "loop" : "goal" });
|
|
304
317
|
const guidance = "pi invalidated this session's extension handle (session replacement — the session was disposed and this process's sends can never land). Run /reload — extensions rebuild IN PLACE, no pi restart needed — then /glla resume (autoresume=on resumes for you). Restart pi only if /reload itself fails.";
|
|
318
|
+
// v0.32.0: kill the continuation re-arm too — otherwise an orphaned goal
|
|
319
|
+
// keeps spinning a flat 50ms retry below every watchdog.
|
|
320
|
+
if (continuationTimer) { clearTimeout(continuationTimer); continuationTimer = null; continuationScheduledFor = null; }
|
|
305
321
|
if (isLoopActive()) {
|
|
306
322
|
clearLoopTimer();
|
|
307
323
|
state.loop = { ...state.loop!, active: false, stopReason: `extension api stale: ${guidance}` };
|
|
@@ -400,6 +416,12 @@ function warnIfStaleAtEntry(ctx: ExtensionContext, what: string): boolean {
|
|
|
400
416
|
// v0.30.0: a successor may already own this session (e.g. /reload
|
|
401
417
|
// re-imported the modules) — the user's command belongs to the fresh
|
|
402
418
|
// instance; say so softly instead of demanding a reload.
|
|
419
|
+
// v0.32.0: the rebind window means a fresh instance is COMING, not here —
|
|
420
|
+
// the old message claimed "handled there" while nothing owned the session.
|
|
421
|
+
if (Date.now() < sessionReplacementUntil) {
|
|
422
|
+
ctx.ui.notify(`glla: this session is rebinding after /reload — ${what} will be handled by the refreshed instance; retry in a moment if it doesn't.`, "info");
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
403
425
|
if (absorbStaleIfSuperseded(ctx)) {
|
|
404
426
|
ctx.ui.notify(`glla: a refreshed instance owns this session — ${what} is handled there; nothing to do.`, "info");
|
|
405
427
|
return true;
|
|
@@ -579,6 +601,8 @@ let carryoverResolved = true;
|
|
|
579
601
|
// set while complete_goal's isolated audit runs, so the heartbeat never
|
|
580
602
|
// refires into an in-flight completion.
|
|
581
603
|
let completionAuditInFlight = false;
|
|
604
|
+
// v0.32.0: consecutive stored-claim quota retries (capped at 5, then hold).
|
|
605
|
+
let quotaRetryStreak = 0;
|
|
582
606
|
let heartbeatTimer: NodeJS.Timeout | null = null;
|
|
583
607
|
|
|
584
608
|
const ZOMBIE_RUN_SILENT_MS = 20 * 60_000;
|
|
@@ -644,6 +668,16 @@ let loopRearmStreak = 0;
|
|
|
644
668
|
// whose turn trigger was still dead — pausing a resumable goal 4 minutes
|
|
645
669
|
// after the compact instead of giving pi room to recover.
|
|
646
670
|
let compactionGraceUntil = 0;
|
|
671
|
+
// v0.32.1 (pi-goal-x's lesson — "recover from compacts smarter"): a compact
|
|
672
|
+
// leaves a RESUME DEBT, not just two fixed-offset settle probes that can both
|
|
673
|
+
// lose (field: hellhunter 4-min dangle 2026-07-31; polis stall same day).
|
|
674
|
+
// postCompactResumeOwed discharges only when a real turn starts (agent_start);
|
|
675
|
+
// every heartbeat tick past grace retries it. postCompactResyncPending arms a
|
|
676
|
+
// deterministic [POST-COMPACTION RESYNC] block on the next continuation/loop
|
|
677
|
+
// message (pi-goal-x's #5) so the compacted agent re-anchors on artifact
|
|
678
|
+
// state instead of lost chat history.
|
|
679
|
+
let postCompactResumeOwed = false;
|
|
680
|
+
let postCompactResyncPending = false;
|
|
647
681
|
const COMPACTION_GRACE_MS = 3 * 60_000;
|
|
648
682
|
// v0.28.25: provider-error retry cadence. Field-observed in dracon-utilities
|
|
649
683
|
// (kimi, 19-session fleet on one provider account): a "concurrent request
|
|
@@ -803,6 +837,24 @@ function heartbeatTick(): void {
|
|
|
803
837
|
if (!absorbStaleIfSuperseded(ctx)) goStaleTerminal(ctx, "heartbeat probe");
|
|
804
838
|
return;
|
|
805
839
|
}
|
|
840
|
+
// v0.32.1: post-compaction resume debt — retry on every heartbeat tick
|
|
841
|
+
// past grace until a turn actually starts. Fixed-offset settles alone
|
|
842
|
+
// can both lose (pi busy at 2s AND at grace+2s = a dangling chain).
|
|
843
|
+
if (postCompactResumeOwed && isSupervising() && !abortedStandDown) {
|
|
844
|
+
try {
|
|
845
|
+
if (ctx.isIdle() && !ctx.hasPendingMessages() && continuationTimer === null && loopTimer === null) {
|
|
846
|
+
if (isLoopActive()) {
|
|
847
|
+
appendLedger(ctx.cwd, "compaction_resume_owed_refire", { kind: "loop" });
|
|
848
|
+
scheduleLoopTick(ctx);
|
|
849
|
+
} else if (isActionableGoal()) {
|
|
850
|
+
appendLedger(ctx.cwd, "compaction_resume_owed_refire", { kind: "goal" });
|
|
851
|
+
scheduleContinuation(ctx, true);
|
|
852
|
+
} else {
|
|
853
|
+
postCompactResumeOwed = false; // nothing to resume — discharge
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
} catch { /* next tick */ }
|
|
857
|
+
}
|
|
806
858
|
// v0.29.16: zombie-run watchdog. pi reports BUSY (a run is "active") but
|
|
807
859
|
// zero stream events for 20 min = the provider stream hung silently —
|
|
808
860
|
// queued continuations can't land, and every other watchdog stays quiet
|
|
@@ -1005,6 +1057,9 @@ function sendContinuation(goalId: string): void {
|
|
|
1005
1057
|
if (!isActionableGoal()) return;
|
|
1006
1058
|
const ctx = freshCtx();
|
|
1007
1059
|
if (!ctx) {
|
|
1060
|
+
// v0.32.0: a stale handle must not spin a flat 50ms re-arm below every
|
|
1061
|
+
// watchdog — the heartbeat's terminal path does the theatre; we just stop.
|
|
1062
|
+
if (probeExtensionApiStale()) return;
|
|
1008
1063
|
// No live ctx — retry shortly; the next session event will refresh it.
|
|
1009
1064
|
continuationScheduledFor = goalId;
|
|
1010
1065
|
continuationTimer = setTimeout(() => sendContinuation(goalId), BACKOFF_IDLE_RETRY_MS);
|
|
@@ -1021,11 +1076,13 @@ function sendContinuation(goalId: string): void {
|
|
|
1021
1076
|
}
|
|
1022
1077
|
if (!extensionApi || extensionApiStale) return;
|
|
1023
1078
|
try {
|
|
1079
|
+
const resync = postCompactResyncPending ? buildPostCompactResync() : "";
|
|
1024
1080
|
extensionApi.sendMessage({
|
|
1025
1081
|
customType: GOAL_EVENT_ENTRY,
|
|
1026
|
-
content: continuationPrompt(state.goal!),
|
|
1082
|
+
content: resync + continuationPrompt(state.goal!),
|
|
1027
1083
|
display: false,
|
|
1028
1084
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
1085
|
+
if (resync) postCompactResyncPending = false; // consumed only by a landed send
|
|
1029
1086
|
continuationRearmStreak = 0; continuationRearmSince = 0; // v0.28.5 (E3): a landed send clears the storm
|
|
1030
1087
|
appendLedger(ctx.cwd, "goal_continuation_sent", { goalId });
|
|
1031
1088
|
} catch (err) {
|
|
@@ -1078,6 +1135,25 @@ function sendLengthContinue(ctx: ExtensionContext, consecutive: number): void {
|
|
|
1078
1135
|
}
|
|
1079
1136
|
}
|
|
1080
1137
|
|
|
1138
|
+
/** v0.32.1: deterministic post-compaction re-anchor (pi-goal-x's #5) —
|
|
1139
|
+
* prepended to the first continuation/loop message after a compact. */
|
|
1140
|
+
function buildPostCompactResync(): string {
|
|
1141
|
+
const lines: string[] = [
|
|
1142
|
+
"[POST-COMPACTION RESYNC] The transcript was just compacted. Trust the artifacts on disk and .pi-glla/ state — NOT your memory of the prior chat. Re-read files before editing them.",
|
|
1143
|
+
];
|
|
1144
|
+
if (state.goal) {
|
|
1145
|
+
lines.push(`Goal ${state.goal.id} — status ${state.goal.status}`);
|
|
1146
|
+
lines.push(`Objective: ${state.goal.objective.slice(0, 200)}`);
|
|
1147
|
+
const next = findNextPendingTask(state.goal.taskList?.tasks ?? []);
|
|
1148
|
+
if (next) lines.push(`Next pending task: \`${next.id}\` — ${next.title}`);
|
|
1149
|
+
const lastAudit = state.goal.auditHistory?.[state.goal.auditHistory.length - 1];
|
|
1150
|
+
if (lastAudit) lines.push(`Last audit: ${lastAudit.approved ? "APPROVED" : lastAudit.impossible ? "IMPOSSIBLE" : "disapproved"} (${lastAudit.at})`);
|
|
1151
|
+
} else if (state.loop?.active) {
|
|
1152
|
+
lines.push(`Loop: ${state.loop.target.slice(0, 160)} — iteration ${state.loop.iteration}`);
|
|
1153
|
+
}
|
|
1154
|
+
return lines.join("\n") + "\n\n";
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1081
1157
|
function continuationPrompt(goal: Goal): string {
|
|
1082
1158
|
// Read the .md file as the template, then substitute {{tokens}}.
|
|
1083
1159
|
// For v0.1.0 we inline-substitute so we don't need fs at runtime.
|
|
@@ -1251,7 +1327,9 @@ async function fanOutListAuditFindings(ctx: ExtensionContext): Promise<void> {
|
|
|
1251
1327
|
// Dedupe against the live queue (a re-run must not double-queue a finding
|
|
1252
1328
|
// that's already waiting) — match on the finding text's first 60 chars.
|
|
1253
1329
|
const queuedText = listQueue().map((i) => i.objective).join("\n");
|
|
1254
|
-
|
|
1330
|
+
// v0.32.0: cap one fan-out — a runaway findings file must not enqueue
|
|
1331
|
+
// hundreds of items on a single Confirm.
|
|
1332
|
+
const fresh = open.filter((f) => !queuedText.includes(f.text.slice(0, 60))).slice(0, 50);
|
|
1255
1333
|
const alreadyQueued = open.length - fresh.length;
|
|
1256
1334
|
const decideNote =
|
|
1257
1335
|
decisions.length > 0
|
|
@@ -1440,6 +1518,14 @@ async function retryStoredCompletionAudit(ctx: ExtensionContext, origin: "quota-
|
|
|
1440
1518
|
pauseSuggestedAction: `Quota auto-retry in ${retryMin}m — or /goal resume to retry now`,
|
|
1441
1519
|
}, liveCtx);
|
|
1442
1520
|
appendLedger(liveCtx.cwd, "goal_paused", { reason: `auditor quota: retry in ${quota.retryAfterSec}s (stored-claim retry)` });
|
|
1521
|
+
// v0.32.0: terminal cap — a permanently dead auditor key must not spawn
|
|
1522
|
+
// one auditor per hour forever. 5 consecutive quota retries → hold.
|
|
1523
|
+
quotaRetryStreak++;
|
|
1524
|
+
if (quotaRetryStreak >= 5) {
|
|
1525
|
+
appendLedger(liveCtx.cwd, "quota_retry_capped", { streak: quotaRetryStreak });
|
|
1526
|
+
liveCtx.ui.notify(`Auditor quota retry gave up after ${quotaRetryStreak} consecutive attempts — the claim stays stored; /goal resume retries by hand.`, "warning");
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1443
1529
|
liveCtx.ui.notify(`Auditor still quota-limited — next auto-retry in ${retryMin}m (your completion claim is stored; no action needed).`, "warning");
|
|
1444
1530
|
scheduleQuotaRetry(liveCtx, quota.retryAfterSec, result.error, () => {
|
|
1445
1531
|
if (state.goal && state.goal.status === "paused" && (state.goal.pauseReason ?? "").startsWith("auditor quota:") && state.goal.pendingCompletion) {
|
|
@@ -1452,6 +1538,7 @@ async function retryStoredCompletionAudit(ctx: ExtensionContext, origin: "quota-
|
|
|
1452
1538
|
// Any other outcome — disapproved, impossible, non-quota infra — belongs
|
|
1453
1539
|
// to the agent: resume and let the continuation drive the next step. The
|
|
1454
1540
|
// verdict is durable in auditHistory + /goal status.
|
|
1541
|
+
quotaRetryStreak = 0;
|
|
1455
1542
|
updateGoal({
|
|
1456
1543
|
status: "active",
|
|
1457
1544
|
auditHistory: history,
|
|
@@ -2579,11 +2666,13 @@ function sendLoopTurn(): void {
|
|
|
2579
2666
|
// instruction (metricless loops; metric loops already vary via values).
|
|
2580
2667
|
const variantNote = metricless ? continueVariant(loop.iteration) : "";
|
|
2581
2668
|
try {
|
|
2669
|
+
const loopResync = postCompactResyncPending ? buildPostCompactResync() : "";
|
|
2582
2670
|
extensionApi.sendMessage({
|
|
2583
2671
|
customType: GOAL_EVENT_ENTRY,
|
|
2584
|
-
content: loopPrompt(loop, regressionNote, strategyNote, boundsNote, interventionNote, variantNote),
|
|
2672
|
+
content: loopResync + loopPrompt(loop, regressionNote, strategyNote, boundsNote, interventionNote, variantNote),
|
|
2585
2673
|
display: false,
|
|
2586
2674
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
2675
|
+
if (loopResync) postCompactResyncPending = false; // consumed only by a landed send
|
|
2587
2676
|
// v0.26.1: the send path is ledgered — the hegemon zombie spun 619
|
|
2588
2677
|
// refires with zero visibility into whether sends were landing.
|
|
2589
2678
|
loopRearmStreak = 0; loopRearmSince = 0; // v0.28.5 (E3): a landed turn clears the storm
|
|
@@ -4265,35 +4354,45 @@ function resolveAuditorModel(ctx: ExtensionContext, ref?: string, fallbackRef?:
|
|
|
4265
4354
|
return matches[0] ? { model: matches[0] } : { reason: "no available model matching" };
|
|
4266
4355
|
};
|
|
4267
4356
|
const isSession = (m: any) => sessionModel && m.provider === sessionModel.provider && m.id === sessionModel.id;
|
|
4268
|
-
|
|
4357
|
+
// v0.32.0: per-pin source labels — when the primary is unset, pins[0] IS
|
|
4358
|
+
// the fallback and the old i===0→"setting" map mislabeled it.
|
|
4359
|
+
const pins: Array<{ pin: string; src: "setting" | "fallback-pin" }> = [];
|
|
4360
|
+
if (ref?.trim()) pins.push({ pin: ref.trim(), src: "setting" });
|
|
4361
|
+
if (fallbackRef?.trim()) pins.push({ pin: fallbackRef.trim(), src: "fallback-pin" });
|
|
4269
4362
|
for (let i = 0; i < pins.length; i++) {
|
|
4270
|
-
const pin = pins[i]!;
|
|
4363
|
+
const { pin } = pins[i]!;
|
|
4271
4364
|
const r = tryRef(pin);
|
|
4272
4365
|
if (!r.model) {
|
|
4273
4366
|
// Unavailable pin → cascade: next pin, then the session model (LOUD).
|
|
4274
4367
|
appendLedger(ctx.cwd, "auditor_model_fallback", { configured: pin, reason: r.reason });
|
|
4275
|
-
|
|
4368
|
+
// v0.32.0: the last pin no longer pre-announces the session fallback —
|
|
4369
|
+
// the post-loop block does that (it notified twice before).
|
|
4370
|
+
ctx.ui.notify(`Auditor model "${pin}" is unavailable (${r.reason})${i + 1 < pins.length ? " — trying the fallback pin" : ""}. Fix via /glla → Auditor model.`, "warning");
|
|
4276
4371
|
continue;
|
|
4277
4372
|
}
|
|
4278
4373
|
if (sameSessionSwap && isSession(r.model) && i + 1 < pins.length) {
|
|
4279
4374
|
// The pin IS the session model — the verifier would be the executor's
|
|
4280
4375
|
// own model; auto-swap down the chain (the user's move).
|
|
4281
|
-
appendLedger(ctx.cwd, "auditor_model_same_as_session", { model: `${r.model.provider}/${r.model.id}`, fallback: pins[i + 1] });
|
|
4282
|
-
ctx.ui.notify(`Session model IS the pinned auditor (${r.model.provider}/${r.model.id}) — auditor auto-swapped to ${pins[i + 1]} so the verifier differs.`, "info");
|
|
4376
|
+
appendLedger(ctx.cwd, "auditor_model_same_as_session", { model: `${r.model.provider}/${r.model.id}`, fallback: pins[i + 1]!.pin });
|
|
4377
|
+
ctx.ui.notify(`Session model IS the pinned auditor (${r.model.provider}/${r.model.id}) — auditor auto-swapped to ${pins[i + 1]!.pin} so the verifier differs.`, "info");
|
|
4283
4378
|
continue;
|
|
4284
4379
|
}
|
|
4285
|
-
|
|
4380
|
+
// v0.32.0: the nudge must fire when the LAST pin stands on the session
|
|
4381
|
+
// model — the old `!fallbackRef` guard went SILENT when the fallback pin
|
|
4382
|
+
// itself resolved to the session model (verifier == executor, and hop 0's
|
|
4383
|
+
// notify had just claimed "auto-swapped so the verifier differs" — false).
|
|
4384
|
+
if (sameSessionSwap && isSession(r.model) && i + 1 >= pins.length) {
|
|
4286
4385
|
// Last resort reached and it IS the session model, with no fallback
|
|
4287
4386
|
// ever pinned — the model stands (the session IS the last resort);
|
|
4288
4387
|
// one loud nudge so the user can wire the swap.
|
|
4289
4388
|
appendLedger(ctx.cwd, "auditor_model_same_as_session", { model: `${r.model.provider}/${r.model.id}`, fallback: null });
|
|
4290
4389
|
ctx.ui.notify(`The session model IS the pinned auditor (${r.model.provider}/${r.model.id}) — pin a different /glla → Auditor fallback model so the verifier can differ.`, "warning");
|
|
4291
4390
|
}
|
|
4292
|
-
return { model: r.model, via: i
|
|
4391
|
+
return { model: r.model, via: pins[i]!.src };
|
|
4293
4392
|
}
|
|
4294
4393
|
if (sessionModel) {
|
|
4295
4394
|
if (pins.length > 0) {
|
|
4296
|
-
appendLedger(ctx.cwd, "auditor_model_fallback", { configured: pins.join(" → "), reason: "all pins exhausted" });
|
|
4395
|
+
appendLedger(ctx.cwd, "auditor_model_fallback", { configured: pins.map((p) => p.pin).join(" → "), reason: "all pins exhausted" });
|
|
4297
4396
|
ctx.ui.notify("All pinned auditor models are unavailable — falling back to the session model. Fix via /glla → Auditor model.", "warning");
|
|
4298
4397
|
return { model: sessionModel, via: "session-fallback" };
|
|
4299
4398
|
}
|
|
@@ -5660,6 +5759,11 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5660
5759
|
continuationRearmStreak = 0; continuationRearmSince = 0;
|
|
5661
5760
|
loopRearmStreak = 0; loopRearmSince = 0;
|
|
5662
5761
|
compactionGraceUntil = Date.now() + COMPACTION_GRACE_MS;
|
|
5762
|
+
// v0.32.1: arm the resume debt + the resync block (the settle probes
|
|
5763
|
+
// below stay as the fast path; the heartbeat now retries the debt on
|
|
5764
|
+
// EVERY post-grace tick until agent_start discharges it).
|
|
5765
|
+
postCompactResumeOwed = true;
|
|
5766
|
+
postCompactResyncPending = true;
|
|
5663
5767
|
const settle = setTimeout(() => {
|
|
5664
5768
|
const c = freshCtx();
|
|
5665
5769
|
if (!c) return;
|
|
@@ -6303,6 +6407,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
6303
6407
|
});
|
|
6304
6408
|
pi.on("agent_start", () => {
|
|
6305
6409
|
lastStreamActivityAt = Date.now();
|
|
6410
|
+
// v0.32.1: a real turn started — the post-compaction resume debt is
|
|
6411
|
+
// discharged (the heartbeat stops retrying it).
|
|
6412
|
+
postCompactResumeOwed = false;
|
|
6306
6413
|
});
|
|
6307
6414
|
pi.on("turn_start", () => {
|
|
6308
6415
|
lastStreamActivityAt = Date.now();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.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",
|