pi-goal-list-loop-audit 0.34.10 → 0.34.11

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.
@@ -630,15 +630,30 @@ let heartbeatTimer: NodeJS.Timeout | null = null;
630
630
 
631
631
  const ZOMBIE_RUN_SILENT_MS = 20 * 60_000;
632
632
  const ZOMBIE_RUN_ALERT_THROTTLE_MS = 10 * 60_000;
633
+ // v0.34.11: unanswered-continuation watchdog. Hellhunter 2026-08-01: at a
634
+ // list-transition completion boundary pi ACCEPTED every continuation
635
+ // (sendMessage never threw; session reported idle) but started NO turn —
636
+ // transcript frozen, tokens flat, 10+ minutes of refires into the void.
637
+ // Same family as the post-compaction dropped trigger (v0.26.5), but the
638
+ // pending-latch watchdog needs idle&&pending and pi reported no pending
639
+ // here, and the zombie watchdog needs busy — this shape falls between both
640
+ // chairs. Disarm signal = real activity (agent_end/tool_call) AFTER the
641
+ // last send; a landed turn — even a lazy text-only one — disarms it.
642
+ const CONTINUATION_UNANSWERED_MS = 150_000;
643
+ const CONTINUATION_UNANSWERED_THROTTLE_MS = 300_000;
633
644
  // v0.29.19: dead-turn caps (agent_end exemption path). 6 consecutive
634
645
  // provider-error turns = a real outage, not bad luck — stop honestly.
635
646
  // 3 consecutive user aborts = the user means it (user aborts mean STOP).
636
647
  const LOOP_MAX_CONSECUTIVE_ERRORS = 6;
637
648
  const LOOP_MAX_CONSECUTIVE_ABORTS = 3;
638
649
 
650
+ let lastRealActivityAt = 0;
651
+ let lastContinuationSentAt = 0;
652
+ let lastUnansweredAlertAt = 0;
653
+
639
654
  function noteActivity(real = false): void {
640
655
  lastActivityAt = Date.now();
641
- if (real) consecutiveStalls = 0;
656
+ if (real) { consecutiveStalls = 0; lastRealActivityAt = lastActivityAt; }
642
657
  }
643
658
 
644
659
  function isSupervising(): boolean {
@@ -932,6 +947,25 @@ function heartbeatTick(): void {
932
947
  notifyExternal(ctx, `glla: zombie run suspected (${Math.round(streamSilentMs / 60000)} min busy-silent) — press Esc to abort.`);
933
948
  return;
934
949
  }
950
+ // v0.34.11: unanswered-continuation watchdog — pi took the send but no
951
+ // turn started (no agent_end, no tool call, no stream). Re-sends don't
952
+ // unstick a dropped trigger (hegemon law) — this alert's job is to say
953
+ // the cure LOUDLY at ~2.5 min instead of leaving a silent 20-30 min gap
954
+ // before the zombie/wedge alerts. Does NOT return: the heartbeat refire
955
+ // below keeps sending underneath in case pi unsticks by itself.
956
+ if (
957
+ isSupervising() &&
958
+ lastContinuationSentAt > 0 &&
959
+ lastRealActivityAt < lastContinuationSentAt &&
960
+ Date.now() - lastContinuationSentAt >= CONTINUATION_UNANSWERED_MS &&
961
+ Date.now() - lastUnansweredAlertAt >= CONTINUATION_UNANSWERED_THROTTLE_MS
962
+ ) {
963
+ lastUnansweredAlertAt = Date.now();
964
+ appendLedger(ctx.cwd, "continuation_unanswered", { silentMs: Date.now() - lastContinuationSentAt });
965
+ const msg = `glla: pi accepted the continuation ${Math.round((Date.now() - lastContinuationSentAt) / 60_000)}m ago but NO turn has started — no tool calls, no tokens, transcript frozen (the turn trigger is wedged; same pi failure family as the post-compaction blackhole). Re-sends don't unstick it. Cure: /reload — autoresume re-fires the ${isLoopActive() ? "loop" : "goal/list item"} automatically.`;
966
+ ctx.ui.notify(msg, "warning");
967
+ notifyExternal(ctx, msg);
968
+ }
935
969
  // v0.29.1: stranded-audit recovery. A goal left in "auditing" with NO
936
970
  // in-flight audit means the auditor's result never landed (wedged queue
937
971
  // ate the tool result; compaction/restart mid-audit). Field-observed in
@@ -1164,6 +1198,7 @@ function sendContinuation(goalId: string): void {
1164
1198
  if (resync) postCompactResyncPending = false; // consumed only by a landed send
1165
1199
  continuationRearmStreak = 0; continuationRearmSince = 0; // v0.28.5 (E3): a landed send clears the storm
1166
1200
  appendLedger(ctx.cwd, "goal_continuation_sent", { goalId });
1201
+ lastContinuationSentAt = Date.now();
1167
1202
  } catch (err) {
1168
1203
  appendLedger(ctx.cwd, "goal_continuation_send_failed", { goalId, error: err instanceof Error ? err.message : String(err) });
1169
1204
  // v0.26.7: stale runtime = terminal (sends can never land); anything
@@ -2845,6 +2880,7 @@ function sendLoopTurn(): void {
2845
2880
  // refires with zero visibility into whether sends were landing.
2846
2881
  loopRearmStreak = 0; loopRearmSince = 0; // v0.28.5 (E3): a landed turn clears the storm
2847
2882
  appendLedger(ctx.cwd, "loop_turn_sent", { iteration: loop.iteration });
2883
+ lastContinuationSentAt = Date.now();
2848
2884
  } catch (err) {
2849
2885
  // stale API — next agent_end reschedules (but if none comes, the
2850
2886
  // heartbeat's stall escalation stops the spin — v0.26.1).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.34.10",
3
+ "version": "0.34.11",
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",