pi-goal-list-loop-audit 0.34.14 → 0.34.15

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.
@@ -176,6 +176,8 @@ export interface Goal {
176
176
  * At 3 the goal pauses loudly — a broken auditor model must not spin a
177
177
  * silent retry-forever loop. Cleared on any real auditor run. */
178
178
  auditInfraStreak?: number;
179
+ /** v0.34.15: persisted error-brake rung — survives /reload so the 6-brake park can engage. */
180
+ errorBrakeStreak?: number;
179
181
  /** v0.28.26: the completion claim captured when an audit attempt is
180
182
  * quota-blocked. The quota retry re-runs the AUDITOR directly with this
181
183
  * stored claim instead of re-engaging the agent — re-engaging produced a
@@ -849,7 +849,6 @@ const COMPACTION_GRACE_MS = 3 * 60_000;
849
849
  // budget now spans ~5.5m) and escalate the brake cooldown per consecutive
850
850
  // brake (1m, 2m, 4m, 8m, 16m cap). A successful turn resets both.
851
851
  const ERROR_RETRY_LADDER_MS = [5_000, 15_000, 45_000, 90_000, 180_000];
852
- let errorBrakeStreak = 0;
853
852
  const SEND_REARM_LEDGER_MILESTONES_MS = [2 * 60_000, 5 * 60_000, 10 * 60_000];
854
853
  // v0.28.29: escalation is TIME-based and ACTIVITY-gated. A busy session is
855
854
  // NORMAL — the user conversing, or one long subagent turn — and the old
@@ -1259,6 +1258,39 @@ function freshCtx(): ExtensionContext | null {
1259
1258
  }
1260
1259
  }
1261
1260
 
1261
+ // v0.34.15 (hegemon 2026-08-01): pi ACCEPTED the continuation — footer showed
1262
+ // "1 queued" — but the turn trigger was dead, so the message sat queued while
1263
+ // pi idled. The 0.34.11 watchdog gates on "pi reported NO pending" and the
1264
+ // stall ladder takes ~10 minutes; a send that lands queued-without-a-turn is
1265
+ // a CONFIRMED dead trigger (hegemon law), so probe once, ~45s after every
1266
+ // landed send, and go straight to auto-recovery. A consumed message (even an
1267
+ // instant-429 turn consumes it) or any real activity disarms the probe.
1268
+ function queueStuckProbeMs(): number {
1269
+ return Number(process.env.GLLA_QUEUE_STUCK_MS ?? 45_000);
1270
+ }
1271
+ let queueStuckProbe: ReturnType<typeof setTimeout> | null = null;
1272
+ function armQueueStuckProbe(ctx: ExtensionContext, sentAt: number): void {
1273
+ if (queueStuckProbe) clearTimeout(queueStuckProbe);
1274
+ queueStuckProbe = setTimeout(() => {
1275
+ queueStuckProbe = null;
1276
+ try {
1277
+ if (isForeignCtx(ctx)) return; // stale instance — the live one probes
1278
+ if (!isSupervising()) return; // paused/completed meanwhile
1279
+ if (lastContinuationSentAt !== sentAt) return; // a newer send armed its own probe
1280
+ if (lastRealActivityAt > sentAt) return; // the turn started and worked
1281
+ if (!ctx.isIdle()) return; // a turn is running — healthy
1282
+ if (!ctx.hasPendingMessages()) return; // consumed — even an instant 429 consumes
1283
+ appendLedger(ctx.cwd, "queue_stuck_detected", { waitedMs: Date.now() - sentAt });
1284
+ if (!attemptAutoRecovery(ctx, "queue-stuck continuation")) {
1285
+ const msg = `${goalNoun()}: the continuation is QUEUED but pi won't start a turn — the turn trigger is dead (re-sends only queue). Cure: /reload — the goal resumes itself after the rebuild (autoresume off? /glla resume).`;
1286
+ ctx.ui.notify(msg, "warning");
1287
+ notifyExternal(ctx, msg);
1288
+ }
1289
+ } catch { /* stale ctx — the live instance owns the probe now */ }
1290
+ }, queueStuckProbeMs());
1291
+ queueStuckProbe.unref?.();
1292
+ }
1293
+
1262
1294
  function scheduleContinuation(ctx: ExtensionContext, force = false, delayMs?: number): void {
1263
1295
  abortedStandDown = false; // v0.29.5: any explicit schedule ends the stand-down
1264
1296
  if (!isActionableGoal()) return;
@@ -1315,6 +1347,7 @@ function sendContinuation(goalId: string): void {
1315
1347
  continuationRearmStreak = 0; continuationRearmSince = 0; // v0.28.5 (E3): a landed send clears the storm
1316
1348
  appendLedger(ctx.cwd, "goal_continuation_sent", { goalId });
1317
1349
  lastContinuationSentAt = Date.now();
1350
+ armQueueStuckProbe(ctx, lastContinuationSentAt);
1318
1351
  } catch (err) {
1319
1352
  appendLedger(ctx.cwd, "goal_continuation_send_failed", { goalId, error: err instanceof Error ? err.message : String(err) });
1320
1353
  // v0.26.7: stale runtime = terminal (sends can never land); anything
@@ -2997,6 +3030,7 @@ function sendLoopTurn(): void {
2997
3030
  loopRearmStreak = 0; loopRearmSince = 0; // v0.28.5 (E3): a landed turn clears the storm
2998
3031
  appendLedger(ctx.cwd, "loop_turn_sent", { iteration: loop.iteration });
2999
3032
  lastContinuationSentAt = Date.now();
3033
+ armQueueStuckProbe(ctx, lastContinuationSentAt);
3000
3034
  } catch (err) {
3001
3035
  // stale API — next agent_end reschedules (but if none comes, the
3002
3036
  // heartbeat's stall escalation stops the spin — v0.26.1).
@@ -6731,12 +6765,22 @@ export default function (pi: ExtensionAPI): void {
6731
6765
  // 60-second provider hiccup waiting on a manual /goal resume.
6732
6766
  const detail = text.trim() ? ` (last: ${text.trim().replace(/\s+/g, " ").slice(0, 160)})` : "";
6733
6767
  const reason = `5 consecutive errors${detail}`;
6768
+ // v0.34.15: the streak now lives ON THE GOAL so it survives the
6769
+ // auto-recovery /reloads that used to zero the module counter —
6770
+ // hegemon 2026-08-01: a hard-exhausted MiniMax plan churned 1-minute
6771
+ // probes for an hour because every reload reset the ladder to rung 1
6772
+ // and the 6-brake park (v0.29.9) could never engage.
6773
+ const brakeStreak = state.goal!.errorBrakeStreak ?? 0;
6774
+ // v0.34.15: a quota/rate-limit wall is NOT a flake — the card must
6775
+ // say "resuming won't help; switch /model or wait out the window"
6776
+ // (the raw 429 text was in `detail` but nobody parses JSON on a card).
6777
+ const quotaWall = /rate.?limit|usage limit|quota|insufficient|credits/i.test(detail);
6734
6778
  // v0.29.1: brake-cycle CAP. The v0.28.25 ladder slows the thrash
6735
6779
  // (1m→16m) but never STOPS it — junk-runner/hellhunter/pully each
6736
6780
  // burned 4+ pause↔retry cycles against provider windows that last
6737
6781
  // hours. After 6 consecutive brakes: park. v0.29.9: the park keeps
6738
6782
  // probing at the top of each hour (clock-aligned window resets).
6739
- if (errorBrakeStreak >= 6) {
6783
+ if (brakeStreak >= 6) {
6740
6784
  // v0.29.9: park — but keep probing at the top of each hour
6741
6785
  // (user: "simply adding an hourly retry … just to pick up work
6742
6786
  // faster assuming the retry expired"). Coding-plan rate-limit
@@ -6750,18 +6794,20 @@ export default function (pi: ExtensionAPI): void {
6750
6794
  status: "paused",
6751
6795
  pauseKind: "error",
6752
6796
  pauseReason: `${reason} — 6 error-brakes in a row; the provider has been erroring for an extended window`,
6753
- pauseSuggestedAction: "Probing at the top of each hour — rate-limit windows typically expire on clock-hour boundaries. /goal resume retries now.",
6797
+ pauseSuggestedAction: quotaWall
6798
+ ? "Provider quota/rate-limit wall — resuming won't help until the window resets. Hourly top-of-hour probes will pick work back up; switch /model to a different provider to continue immediately."
6799
+ : "Probing at the top of each hour — rate-limit windows typically expire on clock-hour boundaries. /goal resume retries now.",
6754
6800
  }, ctx);
6755
- ctx.ui.notify(`${goalNoun()} parked: ${reason} — 6 brakes in a row. Hourly top-of-hour probes will pick work back up when the window opens; /goal resume retries now.`, "warning");
6801
+ ctx.ui.notify(`${goalNoun()} parked: ${reason} — 6 brakes in a row. ${quotaWall ? "Quota/rate-limit wall — switching /model continues immediately; otherwise hourly" : "Hourly"} top-of-hour probes will pick work back up when the window opens.`, "warning");
6756
6802
  notifyExternal(ctx, `${goalNoun()} parked: provider erroring across 6 error-brake cycles — hourly top-of-hour probes scheduled.`);
6757
- appendLedger(ctx.cwd, "error_brake_capped", { streak: errorBrakeStreak, reason });
6803
+ appendLedger(ctx.cwd, "error_brake_capped", { streak: brakeStreak, reason });
6758
6804
  const probeMs = msUntilNextHourBoundary(Date.now());
6759
6805
  scheduleQuotaRetry(ctx, probeMs / 1000, reason, () => {
6760
6806
  // Re-check: only probe if STILL parked by the error-brake cap —
6761
6807
  // a user pause/resume/cancel meanwhile is never stomped.
6762
6808
  if (state.goal && state.goal.status === "paused" && state.goal.pauseKind === "error"
6763
6809
  && (state.goal.pauseReason ?? "").includes("error-brakes in a row")) {
6764
- appendLedger(ctx.cwd, "hourly_rate_probe", { goalId: state.goal.id, streak: errorBrakeStreak });
6810
+ appendLedger(ctx.cwd, "hourly_rate_probe", { goalId: state.goal.id, streak: state.goal.errorBrakeStreak ?? 0 });
6765
6811
  updateGoal({ status: "active" }, ctx);
6766
6812
  appendLedger(ctx.cwd, "goal_resumed", { via: "hourly-rate-probe" });
6767
6813
  ctx.ui.notify("Hourly probe: resuming (rate-limit windows typically expire at the top of the hour).", "info");
@@ -6772,17 +6818,19 @@ export default function (pi: ExtensionAPI): void {
6772
6818
  }
6773
6819
  // v0.28.25: the cooldown escalates per CONSECUTIVE brake — a fleet-wide
6774
6820
  // 403 window is not cleared by re-braking every 60 seconds.
6775
- const cooldownMs = 60_000 * 2 ** Math.min(errorBrakeStreak, 4);
6821
+ const cooldownMs = 60_000 * 2 ** Math.min(brakeStreak, 4);
6776
6822
  const cooldownMin = Math.round(cooldownMs / 60_000);
6777
- errorBrakeStreak++;
6778
6823
  updateGoal({
6779
6824
  status: "paused",
6780
6825
  pauseKind: "wait",
6781
6826
  pauseResumeAt: new Date(Date.now() + cooldownMs).toISOString(),
6782
6827
  pauseReason: reason,
6783
- pauseSuggestedAction: `Transient provider flake? The goal auto-resumes once in ${cooldownMin}m if still paused for this reason — or /goal resume now.`,
6828
+ errorBrakeStreak: brakeStreak + 1,
6829
+ pauseSuggestedAction: quotaWall
6830
+ ? `Provider quota/rate-limit wall — resuming won't help until the window resets. Switch /model to a different provider to continue now, or let the probe auto-resume in ${cooldownMin}m.`
6831
+ : `Transient provider flake? The goal auto-resumes once in ${cooldownMin}m if still paused for this reason — or /goal resume now.`,
6784
6832
  }, ctx);
6785
- ctx.ui.notify(`Goal paused: ${reason}.`, "warning");
6833
+ ctx.ui.notify(`Goal paused: ${reason}.${quotaWall ? " Quota/rate-limit wall — resuming won't help until the window resets; switch /model to continue now." : ""}`, "warning");
6786
6834
  notifyExternal(ctx, `Goal paused: ${reason}.`);
6787
6835
  appendLedger(ctx.cwd, "goal_paused", { reason });
6788
6836
  scheduleQuotaRetry(ctx, cooldownMs / 1000, reason, () => {
@@ -6833,7 +6881,8 @@ export default function (pi: ExtensionAPI): void {
6833
6881
  } else {
6834
6882
  consecutiveErrorIterations = 0;
6835
6883
  consecutiveAbortIterations = 0;
6836
- errorBrakeStreak = 0; // v0.28.25: a healthy turn clears the brake cooldown
6884
+ // v0.28.25/v0.34.15: a healthy turn clears the (now persisted) brake streak
6885
+ if ((state.goal?.errorBrakeStreak ?? 0) > 0) updateGoal({ errorBrakeStreak: undefined }, ctx);
6837
6886
  }
6838
6887
 
6839
6888
  // No wall-clock cap by design: a goal ends via completion, explicit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.34.14",
3
+ "version": "0.34.15",
4
4
  "description": "Mission control for autonomous pi: interview-drafted goals, an audited task queue, and forever-loops (metric, spec, project-audit) that run for hours. An isolated extension-less auditor re-verifies every completion with raw evidence; confirmed drafts, decision pauses and consent gates keep you in charge.",
5
5
  "license": "MIT",
6
6
  "author": "dracon",
@@ -53,6 +53,7 @@
53
53
  "interruptedAt": { "type": "string" },
54
54
  "interruptedReason": { "type": "string" },
55
55
  "auditInfraStreak": { "type": "number" },
56
+ "errorBrakeStreak": { "type": "number" },
56
57
  "pendingCompletion": { "type": "object" },
57
58
  "createdVia": { "type": "string" },
58
59
  "activePath": { "type": "string" },