pi-goal-list-loop-audit 0.35.12 → 0.35.13

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.35.13 — stale-API recovery loop fix (2026-08-20)
4
+
5
+ ### Stale-handle recovery correctness
6
+ Heartbeat recovery now validates the captured `ExtensionAPI` separately
7
+ from `ExtensionContext`. A context that still answers `isIdle()` cannot
8
+ revive an API that Pi has invalidated, so glla keeps the durable interruption
9
+ parked instead of repeatedly announcing recovery and retrying the same dead
10
+ continuation handle.
11
+
3
12
  ## 0.35.12 — npm 12 pack-report compatibility (2026-08-20)
4
13
 
5
14
  ### Keyed npm 12 dry-run reports
package/README.md CHANGED
@@ -10,7 +10,7 @@ This is a detached process, not a nested session in the main pi process. `comple
10
10
 
11
11
  On Windows, npm installs the `pi.cmd` shim rather than a directly executable `pi` binary. The auditor launches it through an explicitly quoted `cmd.exe` boundary; POSIX keeps direct shell-less execution. Protocol snapshots also tolerate transient Windows file-locks without deleting the last valid snapshot first.
12
12
 
13
- **Current package version:** `v0.35.12` — use `/glla version` to see the installed version and the command for comparing it with the registry latest. This checkout may contain unreleased changes; the npm registry is authoritative for published versions.
13
+ **Current package version:** `v0.35.13` — use `/glla version` to see the installed version and the command for comparing it with the registry latest. This checkout may contain unreleased changes; the npm registry is authoritative for published versions.
14
14
 
15
15
  ## Why this exists
16
16
 
package/docs/INDEX.md CHANGED
@@ -17,7 +17,7 @@ For shipped docs, the relevant entry points are:
17
17
  task milestone gating; v0.35.8 added main-model preferred-primary
18
18
  failback; v0.35.9 hardened cross-version npm tarball checks; v0.35.10
19
19
  handles multi-entry npm dry-run reports; v0.35.11 accepts both npm report
20
- shapes; v0.35.12 supports npm 12's keyed pack reports.
20
+ shapes; v0.35.12 supports npm 12's keyed pack reports; v0.35.13 fixes stale-API recovery loops.
21
21
  - `../README.md` — what the plugin is, install, quickstart, and the
22
22
  architectural guarantee (drafting + confirm + detached auditor).
23
23
  - `../INSTALL.md` — manual install / symlink setup; the recommended
@@ -484,19 +484,12 @@ function heartbeatTick(): void {
484
484
  if (tryAbsorbHostSuccessor(knownCtx, "heartbeat-self-heal")) return;
485
485
  rememberCtx(knownCtx);
486
486
  // Reuse the same-session recovery gate as ordinary commands. It clears
487
- // the durable interrupted marker and can resume unattended work; the
488
- // fallback below preserves the old probe-only behavior if ownership is
489
- // still ambiguous.
490
- if (!flags.staleTerminalDone && !flags.extensionApiStale) return;
491
- flags.staleTerminalDone = false;
492
- flags.extensionApiStale = false;
493
- flags.zombieStoodDown = false;
494
- flags.sessionHandoffPending = false;
495
- try {
496
- knownCtx.ui.notify("glla: pi recovered after a stale-handle terminal — self-healing in-memory state (no /reload needed).", "info");
497
- } catch {
498
- /* the ledger is the durable record; notify is best-effort */
499
- }
487
+ // the durable interrupted marker and rebinds the owner only after BOTH
488
+ // the context and the captured ExtensionAPI are healthy. If that gate
489
+ // refuses the contact, stay parked: clearing these flags here used to
490
+ // announce a false recovery and immediately retry against the same stale
491
+ // API every heartbeat tick.
492
+ if (flags.staleTerminalDone || flags.extensionApiStale) return;
500
493
  }
501
494
  const ctx = freshCtx();
502
495
  if (!ctx) return;
@@ -1072,7 +1072,11 @@ function isCtxAlive(ctx: ExtensionContext | null | undefined): boolean {
1072
1072
  * (field: hegemon 2026-08-06 — one probe failure parked a live session for
1073
1073
  * 5 hours). */
1074
1074
  function probeExtensionApiStaleRaw(): boolean {
1075
- if (lastCtx && isCtxAlive(lastCtx)) return false;
1075
+ // The ExtensionAPI and ExtensionContext can disagree briefly: a live
1076
+ // context does NOT prove that the captured `pi` can still send. In
1077
+ // particular, a stale send can latch the API while ctx.isIdle() continues
1078
+ // to succeed. Always probe the API itself here or the heartbeat will
1079
+ // repeatedly "recover" and immediately fail the next continuation send.
1076
1080
  if (!extensionApi) return false;
1077
1081
  try {
1078
1082
  extensionApi.getSessionName();
@@ -1083,13 +1087,10 @@ function probeExtensionApiStaleRaw(): boolean {
1083
1087
  }
1084
1088
 
1085
1089
  function probeExtensionApiStale(): boolean {
1086
- if (extensionApiStale) {
1087
- if (lastCtx && isCtxAlive(lastCtx)) {
1088
- extensionApiStale = false;
1089
- return false;
1090
- }
1091
- return true;
1092
- }
1090
+ // A stale ExtensionAPI is terminal for this factory instance. A healthy
1091
+ // context probe must not clear this latch: the next send would still use
1092
+ // the same captured API and throw again.
1093
+ if (extensionApiStale) return true;
1093
1094
  if (probeExtensionApiStaleRaw()) extensionApiStale = true;
1094
1095
  return extensionApiStale;
1095
1096
  }
@@ -1483,8 +1484,11 @@ function selfHealStaleSameSession(ctx: ExtensionContext): boolean {
1483
1484
  if (owner && owner.pid === process.pid && typeof owner.instanceId === "string" && owner.instanceId !== instanceId) {
1484
1485
  return false; // a successor module instance owns this cwd — never re-claim
1485
1486
  }
1486
- // The handle must actually be healthy NOW — the fresh probe on the live context.
1487
- if (!isCtxAlive(ctx)) return false;
1487
+ // Both handles must actually be healthy NOW. A live ExtensionContext does
1488
+ // not prove that the captured ExtensionAPI can send; Pi can leave ctx
1489
+ // probes answering while the API has already latched stale. Reclaim only
1490
+ // when the exact API used by continuation sends also passes its probe.
1491
+ if (!isCtxAlive(ctx) || !extensionApi || probeExtensionApiStaleRaw()) return false;
1488
1492
  const was = extensionApiStale ? "extension_api_stale" : sessionHandoffPending ? "session_handoff_pending" : "stale_terminal_done";
1489
1493
  extensionApiStale = false;
1490
1494
  sessionHandoffPending = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.35.12",
3
+ "version": "0.35.13",
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. A detached extension-less auditor process re-verifies every completion with raw evidence without holding the main pi turn; confirmed drafts, decision pauses and consent gates keep you in charge.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "dracon",