pi-goal-list-loop-audit 0.29.14 → 0.29.16

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.
@@ -388,7 +388,12 @@ function loopLines(l: LoopState, now: number, theme?: DisplayTheme, width?: numb
388
388
  `${paint(theme, "accent", "●")} ${truncate(l.target, budgetFor(width, 3, 64))}`,
389
389
  `├─ loop ${arrow} iter ${l.iteration}/${l.maxIterations > 0 ? l.maxIterations : "∞"} · ${fmtElapsed(now - Date.parse(l.startedAt))}`,
390
390
  `├─ best ${best} · last ${l.lastValue ?? "n/a"} · ${stall}`,
391
- `└─ ${paint(theme, "dim", truncate(l.measureCmd, budgetFor(width, 3, 56)))}`,
391
+ // v0.29.15: the audit loop's measure is orchestrator-owned shell — the
392
+ // raw grep reads like leaked internals ("that weird line"). Name what
393
+ // it measures instead; user-authored measures still show raw.
394
+ `└─ ${paint(theme, "dim", l.kind === "audit"
395
+ ? "metric: closed findings ('- [x]' count)"
396
+ : truncate(l.measureCmd, budgetFor(width, 3, 56)))}`,
392
397
  ];
393
398
  if (l.branchName) lines.push(`⎇ ${paint(theme, "muted", truncate(l.branchName, budgetFor(width, 3, 50)))}`);
394
399
  return lines;
@@ -424,6 +424,15 @@ const countedLoopTokenMessages = new Set<string>();
424
424
 
425
425
  // Heartbeat self-watchdog state: liveness is the loop's own job.
426
426
  let lastActivityAt = Date.now();
427
+ // v0.29.16: zombie-run watchdog clock — updated ONLY by genuine pi stream
428
+ // events (message_update / tool_call / agent_start / turn_start /
429
+ // agent_end), never by heartbeat-internal bookkeeping. A session pi
430
+ // reports as BUSY with zero stream events for N min is a hung provider
431
+ // stream (pi has no read timeout): continuations queue into the void and
432
+ // the busy flag conceals the wedge from every other watchdog (field:
433
+ // hellhunter + hegemon 2026-07-30, MiniMax streams died silently).
434
+ let lastStreamActivityAt = Date.now();
435
+ let lastZombieAlertAt = 0;
427
436
  let lastWedgeAlertAt = 0;
428
437
  let heartbeatNudges = 0;
429
438
  // v0.28.4 (P3): skip nudge accounting for the first agent_end turns after a
@@ -445,6 +454,9 @@ let carryoverResolved = true;
445
454
  let completionAuditInFlight = false;
446
455
  let heartbeatTimer: NodeJS.Timeout | null = null;
447
456
 
457
+ const ZOMBIE_RUN_SILENT_MS = 20 * 60_000;
458
+ const ZOMBIE_RUN_ALERT_THROTTLE_MS = 10 * 60_000;
459
+
448
460
  function noteActivity(real = false): void {
449
461
  lastActivityAt = Date.now();
450
462
  if (real) consecutiveStalls = 0;
@@ -653,6 +665,20 @@ function heartbeatTick(): void {
653
665
  // escalation would PAUSE the goal — silently cancelling the
654
666
  // interruptedAt → hold-on-restart promise the footer shows.
655
667
  if (probeExtensionApiStale()) { goStaleTerminal(ctx, "heartbeat probe"); return; }
668
+ // v0.29.16: zombie-run watchdog. pi reports BUSY (a run is "active") but
669
+ // zero stream events for 20 min = the provider stream hung silently —
670
+ // queued continuations can't land, and every other watchdog stays quiet
671
+ // because busy≠wedged. Detection + loud guidance only: aborting a turn
672
+ // is the user's call (consent line); Esc frees the queue and the
673
+ // heartbeat refires the goal/loop by itself.
674
+ const streamSilentMs = Date.now() - lastStreamActivityAt;
675
+ if (isSupervising() && !idle && streamSilentMs >= ZOMBIE_RUN_SILENT_MS && Date.now() - lastZombieAlertAt >= ZOMBIE_RUN_ALERT_THROTTLE_MS) {
676
+ lastZombieAlertAt = Date.now();
677
+ appendLedger(ctx.cwd, "zombie_run_suspected", { streamSilentMs, pending });
678
+ ctx.ui.notify(`glla: the session has been BUSY with zero stream activity for ${Math.round(streamSilentMs / 60000)} min — the provider stream is hung (pi never times it out; queued continuations can't land). Press Esc to abort the zombie turn — the goal/loop refires itself.`, "warning");
679
+ notifyExternal(ctx, `glla: zombie run suspected (${Math.round(streamSilentMs / 60000)} min busy-silent) — press Esc to abort.`);
680
+ return;
681
+ }
656
682
  // v0.29.1: stranded-audit recovery. A goal left in "auditing" with NO
657
683
  // in-flight audit means the auditor's result never landed (wedged queue
658
684
  // ate the tool result; compaction/restart mid-audit). Field-observed in
@@ -5467,6 +5493,7 @@ export default function (pi: ExtensionAPI): void {
5467
5493
  // continuation loop.
5468
5494
  if (isForeignCtx(ctx)) return;
5469
5495
  noteActivity(true);
5496
+ lastStreamActivityAt = Date.now();
5470
5497
  // v0.27.2: folded-in length-continue (standalone pi-length-continue is
5471
5498
  // deprecated). A response cut by the per-response output cap is NOT a
5472
5499
  // completed turn (no telemetry), NOT a stall (no no-tool nudge), and
@@ -5734,9 +5761,22 @@ export default function (pi: ExtensionAPI): void {
5734
5761
  pi.on("tool_call", () => {
5735
5762
  toolCallsThisTurn++;
5736
5763
  noteActivity(true);
5764
+ lastStreamActivityAt = Date.now();
5737
5765
  // v0.24.0: count loop-iteration tool calls (narration-only detection).
5738
5766
  if (isLoopActive()) {
5739
5767
  state.loop!.toolsThisTurn = (state.loop!.toolsThisTurn ?? 0) + 1;
5740
5768
  }
5741
5769
  });
5770
+
5771
+ // v0.29.16: stream liveness for the zombie-run watchdog — deltas, run
5772
+ // starts, and turn starts all prove the provider stream is alive.
5773
+ pi.on("message_update", () => {
5774
+ lastStreamActivityAt = Date.now();
5775
+ });
5776
+ pi.on("agent_start", () => {
5777
+ lastStreamActivityAt = Date.now();
5778
+ });
5779
+ pi.on("turn_start", () => {
5780
+ lastStreamActivityAt = Date.now();
5781
+ });
5742
5782
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.29.14",
3
+ "version": "0.29.16",
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",