pi-goal-list-loop-audit 0.29.10 → 0.29.12
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.
|
@@ -1045,10 +1045,15 @@ export function shouldSuppressHeartbeatForRecentShip(args: {
|
|
|
1045
1045
|
/** Best-effort "when did work last ship" for a repo: newest of the HEAD
|
|
1046
1046
|
* commit time and the .pi-glla state file mtime. Null when unknown. */
|
|
1047
1047
|
/** v0.26.7: pi's exact stale-runtime error signature — thrown by every
|
|
1048
|
-
* runtime-bound method after pi invalidates the extension
|
|
1049
|
-
*
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
1048
|
+
* runtime-bound method after pi invalidates the extension. v0.29.12 source
|
|
1049
|
+
* audit (pi 0.83.0 dist): the ONLY invalidate() caller is AgentSession
|
|
1050
|
+
* .dispose(), reachable solely via session replacement (newSession/
|
|
1051
|
+
* switchSession/fork/quit). COMPACTION NEVER DISPOSES — manual, auto, and
|
|
1052
|
+
* overflow compaction all rebuild context in place. Field note 2026-07-30:
|
|
1053
|
+
* 10 stale events in 4 days in endless-td, repeatedly ~3 min after
|
|
1054
|
+
* compactions with NO new session file and zombie command handlers still
|
|
1055
|
+
* answering — a replacement that disposed but never re-ran factories (or
|
|
1056
|
+
* a same-file switchSession); the disposing path is unidentified pi-side. */
|
|
1052
1057
|
export function isStaleApiError(err: unknown): boolean {
|
|
1053
1058
|
return err instanceof Error && err.message.includes("stale after session replacement");
|
|
1054
1059
|
}
|
|
@@ -173,10 +173,11 @@ export function buildStatusText(state: State, audit?: AuditDisplayProgress | nul
|
|
|
173
173
|
return `glla: ${paint(theme, pauseIsError(g) ? "error" : "warning", label)}${heldSuffix}`;
|
|
174
174
|
}
|
|
175
175
|
if (g.status === "active") {
|
|
176
|
-
// v0.28.1 (S1/S2): a stale-handle interrupt keeps the goal ACTIVE
|
|
177
|
-
//
|
|
176
|
+
// v0.28.1 (S1/S2): a stale-handle interrupt keeps the goal ACTIVE.
|
|
177
|
+
// v0.29.11: the fresh session HOLDS it (hold-everything restore gate;
|
|
178
|
+
// autoresume=on resumes for you) — name the verb, don't promise auto.
|
|
178
179
|
if (g.interruptedAt) {
|
|
179
|
-
return `glla: ${g.policy} ${paint(theme, "error", "⚠ interrupted — stale handle ·
|
|
180
|
+
return `glla: ${g.policy} ${paint(theme, "error", "⚠ interrupted — stale handle · /reload → /glla resume")}${heldSuffix}`;
|
|
180
181
|
}
|
|
181
182
|
// v0.24.7: list policy gets its own wording — a queue item is not a goal.
|
|
182
183
|
// v0.28.11 (U10): goal policy joins it — "list 29" read as a command
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -219,7 +219,7 @@ function goStaleTerminal(ctx: ExtensionContext, where: string): void {
|
|
|
219
219
|
if (extensionApiStale) return; // already terminal — don't re-spam
|
|
220
220
|
extensionApiStale = true;
|
|
221
221
|
appendLedger(ctx.cwd, "extension_api_stale", { where, kind: isLoopActive() ? "loop" : "goal" });
|
|
222
|
-
const guidance = "pi invalidated this session's extension handle (session replacement —
|
|
222
|
+
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.";
|
|
223
223
|
if (isLoopActive()) {
|
|
224
224
|
clearLoopTimer();
|
|
225
225
|
state.loop = { ...state.loop!, active: false, stopReason: `extension api stale: ${guidance}` };
|
|
@@ -260,7 +260,7 @@ function warnIfStaleAtEntry(ctx: ExtensionContext, what: string): boolean {
|
|
|
260
260
|
if (!probeExtensionApiStale()) return false;
|
|
261
261
|
appendLedger(ctx.cwd, "extension_api_stale", { where: `entry probe (${what})` });
|
|
262
262
|
ctx.ui.notify(
|
|
263
|
-
`glla: this session's extension handle is stale (pi session replacement) — ${what} can't send continuations in this process. State is safe in .pi-glla/ —
|
|
263
|
+
`glla: this session's extension handle is stale (pi session replacement) — ${what} can't send continuations in this process. State is safe in .pi-glla/ — run /reload (extensions rebuild in place), then /glla resume. Restart pi only if /reload fails.`,
|
|
264
264
|
"warning",
|
|
265
265
|
);
|
|
266
266
|
return true;
|
|
@@ -544,9 +544,9 @@ function escalateSendRearmStorm(ctx: ExtensionContext, kind: "continuation" | "l
|
|
|
544
544
|
appendLedger(ctx.cwd, "send_rearm_escalated", { kind, afterMinutes: mins, silentMinutes: silent });
|
|
545
545
|
if (kind === "loop" && isLoopActive()) {
|
|
546
546
|
clearLoopTimer();
|
|
547
|
-
state.loop = { ...state.loop!, active: false, stopReason: `send-retry storm: ${mins}m of re-arms with no session activity for ${silent}m — the session is wedged. Restart pi, then /loop
|
|
547
|
+
state.loop = { ...state.loop!, active: false, stopReason: `send-retry storm: ${mins}m of re-arms with no session activity for ${silent}m — the session is wedged. Restart pi, then /loop resume (the loop holds on restore).` };
|
|
548
548
|
persistState(ctx);
|
|
549
|
-
ctx.ui.notify(`Loop stopped: send-retry storm (${mins}m, session silent ${silent}m). Restart pi
|
|
549
|
+
ctx.ui.notify(`Loop stopped: send-retry storm (${mins}m, session silent ${silent}m). Restart pi, then /loop resume (the loop holds on restore).`, "warning");
|
|
550
550
|
notifyExternal(ctx, "Loop stopped: send-retry storm.");
|
|
551
551
|
return;
|
|
552
552
|
}
|
|
@@ -583,9 +583,9 @@ function escalateStallNow(ctx: ExtensionContext, threshold: number): boolean {
|
|
|
583
583
|
appendLedger(ctx.cwd, "stall_escalated", { threshold, kind: isLoopActive() ? "loop" : "goal" });
|
|
584
584
|
if (isLoopActive()) {
|
|
585
585
|
clearLoopTimer();
|
|
586
|
-
state.loop = { ...state.loop!, active: false, stopReason: `stalled: ${threshold} continuation refires landed no turn — the session is not continuing (wedged message queue or stale API). Restart pi, then /loop
|
|
586
|
+
state.loop = { ...state.loop!, active: false, stopReason: `stalled: ${threshold} continuation refires landed no turn — the session is not continuing (wedged message queue or stale API). Restart pi, then /loop resume (the loop holds on restore).` };
|
|
587
587
|
persistState(ctx);
|
|
588
|
-
ctx.ui.notify(`Loop stopped: ${threshold} refires produced no turn — the continuation is not landing. Restart pi
|
|
588
|
+
ctx.ui.notify(`Loop stopped: ${threshold} refires produced no turn — the continuation is not landing. Restart pi, then /loop resume (the loop holds on restore).`, "warning");
|
|
589
589
|
notifyExternal(ctx, "Loop stopped: stalled (continuation not landing).");
|
|
590
590
|
return true;
|
|
591
591
|
}
|
|
@@ -619,12 +619,16 @@ function heartbeatTick(): void {
|
|
|
619
619
|
// machinery below stays quiet for 3 minutes while the replaced session
|
|
620
620
|
// settles (latch watchdog, wedge alert, refire counting all resume after).
|
|
621
621
|
if (Date.now() < compactionGraceUntil) return;
|
|
622
|
-
// v0.
|
|
623
|
-
//
|
|
624
|
-
//
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
|
|
622
|
+
// v0.29.11: PROBE the handle before the stall machinery runs — a
|
|
623
|
+
// session-replaced handle can never land a refire, so detect it on the
|
|
624
|
+
// first tick after replacement and go terminal immediately, instead of
|
|
625
|
+
// burning refires into the void until a send happens to throw (field:
|
|
626
|
+
// polis stall 3/5, endless-td stall 1/5 before the warning fired).
|
|
627
|
+
// v0.28.27: once terminal, ALL stall machinery stays quiet from here
|
|
628
|
+
// on: refiring into a dead process is misleading, and worse, the stall
|
|
629
|
+
// escalation would PAUSE the goal — silently cancelling the
|
|
630
|
+
// interruptedAt → hold-on-restart promise the footer shows.
|
|
631
|
+
if (probeExtensionApiStale()) { goStaleTerminal(ctx, "heartbeat probe"); return; }
|
|
628
632
|
// v0.29.1: stranded-audit recovery. A goal left in "auditing" with NO
|
|
629
633
|
// in-flight audit means the auditor's result never landed (wedged queue
|
|
630
634
|
// ate the tool result; compaction/restart mid-audit). Field-observed in
|
|
@@ -4499,6 +4503,10 @@ async function cmdGllaWipe(ctx: ExtensionContext): Promise<void> {
|
|
|
4499
4503
|
* genuinely differ per type (tweak/finish/next/decide/refine) stay typed.
|
|
4500
4504
|
*/
|
|
4501
4505
|
async function cmdGllaResume(ctx: ExtensionContext): Promise<void> {
|
|
4506
|
+
// v0.29.12: a zombie instance (handle dead after session replacement)
|
|
4507
|
+
// used to answer "Nothing to resume" — the resume path must name the
|
|
4508
|
+
// real recovery (/reload rebuilds extensions in place), not mislead.
|
|
4509
|
+
if (warnIfStaleAtEntry(ctx, "/glla resume")) return;
|
|
4502
4510
|
const g = state.goal;
|
|
4503
4511
|
const goalResumable = g && g.status === "paused";
|
|
4504
4512
|
const loopResumable = state.loop && !state.loop.active && state.loop.stopReason === HELD_ON_RESTORE;
|
|
@@ -5328,6 +5336,16 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5328
5336
|
) {
|
|
5329
5337
|
ctx.ui.notify("Auto-resume fired (event: session start). Continue working.", "info");
|
|
5330
5338
|
}
|
|
5339
|
+
// v0.29.11: loops stopped by the stale-handle terminal or the stall
|
|
5340
|
+
// escalation told the user "restart pi, then /loop start" — but a
|
|
5341
|
+
// fresh start discards iteration/best/history. Hold them on load like
|
|
5342
|
+
// any restore-held loop: /loop resume continues from the saved state.
|
|
5343
|
+
if (state.loop && !state.loop.active &&
|
|
5344
|
+
(state.loop.stopReason?.startsWith("extension api stale") || state.loop.stopReason?.startsWith("stalled:") || state.loop.stopReason?.startsWith("send-retry storm:"))) {
|
|
5345
|
+
appendLedger(ctx.cwd, "loop_held_for_resume", { was: (state.loop.stopReason ?? "").slice(0, 40) });
|
|
5346
|
+
state.loop = { ...state.loop, stopReason: HELD_ON_RESTORE };
|
|
5347
|
+
persistState(ctx);
|
|
5348
|
+
}
|
|
5331
5349
|
// v0.29.10: reseed live/held audit loops stuck on the degenerate
|
|
5332
5350
|
// pre-discovery baseline-0 — best can never go below 0, so every
|
|
5333
5351
|
// iteration stalls and the prompt cries REGRESSED on real progress
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.12",
|
|
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",
|