pi-goal-list-loop-audit 0.29.21 → 0.29.23
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/extensions/loops/goal.ts +58 -26
- package/package.json +1 -1
package/extensions/loops/goal.ts
CHANGED
|
@@ -238,26 +238,53 @@ function goStaleTerminal(ctx: ExtensionContext, where: string): void {
|
|
|
238
238
|
updateGoal({ interruptedAt: nowIso(), interruptedReason: `extension api stale (${where})` }, ctx);
|
|
239
239
|
}
|
|
240
240
|
ctx.ui.notify(`glla: ${guidance}`, "warning");
|
|
241
|
-
notifyExternal(ctx, `glla: extension api stale —
|
|
242
|
-
|
|
241
|
+
notifyExternal(ctx, `glla: extension api stale — run /reload, then /glla resume. (${where})`);
|
|
242
|
+
attemptAutoReload(ctx, where);
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
/** v0.29.13: automatic recovery — the zombie handle can't call ctx.reload()
|
|
246
246
|
* (pi walls EVERY runtime method behind assertActive), but fs/child_process
|
|
247
|
-
* are extension-side and still work.
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
|
|
247
|
+
* are extension-side and still work. Inject /reload as keystrokes into our
|
|
248
|
+
* own terminal pane: pi rebuilds the extension runtime in place, the fresh
|
|
249
|
+
* instance loads .pi-glla state and holds (autoresume=on resumes for you).
|
|
250
|
+
* v0.29.22: transport-generalized — tmux OR WezTerm. Field: this rig runs
|
|
251
|
+
* WezTerm (TERM_PROGRAM=WezTerm, WEZTERM_PANE set, no TMUX), so the
|
|
252
|
+
* tmux-only gate failed silently 100% of the time — auto_reload_injected
|
|
253
|
+
* never fired fleet-wide, and every stale handle fell back to the manual
|
|
254
|
+
* warning (user: "stopping and told to reload is common"). v0.29.22 also
|
|
255
|
+
* fires from the entry-probe path (the most common stale discovery).
|
|
256
|
+
* Opt out: autoReloadOnStale=false. Best-effort: the manual warning
|
|
257
|
+
* already fired, so failures cost nothing. */
|
|
258
|
+
function attemptAutoReload(ctx: ExtensionContext, where: string): void {
|
|
253
259
|
try {
|
|
254
260
|
if (loadSettings(ctx.cwd).autoReloadOnStale === false) return;
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
+
const tmuxPane = process.env.TMUX_PANE;
|
|
262
|
+
const wezPane = process.env.WEZTERM_PANE;
|
|
263
|
+
let transport: "tmux" | "wezterm";
|
|
264
|
+
let cmd: string;
|
|
265
|
+
let pane: string;
|
|
266
|
+
if (process.env.TMUX && tmuxPane && /^%\d+$/.test(tmuxPane)) {
|
|
267
|
+
transport = "tmux";
|
|
268
|
+
pane = tmuxPane;
|
|
269
|
+
// v0.29.22: NO leading Escape — a late zombie probe can fire AFTER a
|
|
270
|
+
// fresh instance already resumed and started a turn, and Escape
|
|
271
|
+
// would abort that turn without consent (the consent line). /reload
|
|
272
|
+
// alone lands at a dead prompt and queues harmlessly mid-turn (pi
|
|
273
|
+
// refuses the reload itself if a response is streaming).
|
|
274
|
+
cmd = `tmux send-keys -t ${pane} -l '/reload' && tmux send-keys -t ${pane} Enter`;
|
|
275
|
+
} else if (wezPane && /^\d+$/.test(wezPane)) {
|
|
276
|
+
transport = "wezterm";
|
|
277
|
+
pane = wezPane;
|
|
278
|
+
// wezterm cli send-text --no-paste delivers bytes as keystrokes.
|
|
279
|
+
// /reload + CR only — no Escape (see above). Literal CR byte inside
|
|
280
|
+
// single quotes — no bash-isms (exec uses /bin/sh).
|
|
281
|
+
cmd = `wezterm cli send-text --pane-id ${pane} --no-paste '/reload\r'`;
|
|
282
|
+
} else {
|
|
283
|
+
appendLedger(ctx.cwd, "auto_reload_skipped", { where, reason: "no supported multiplexer (tmux/WezTerm) in env" });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
appendLedger(ctx.cwd, "auto_reload_injected", { where, transport, pane });
|
|
287
|
+
ctx.ui.notify("glla: injecting /reload into this pane — extensions rebuild in place and the fresh instance holds state. /glla resume after (automatic with autoresume=on).", "info");
|
|
261
288
|
exec(cmd, () => { /* best-effort */ });
|
|
262
289
|
} catch {
|
|
263
290
|
/* never let recovery take the warning path down */
|
|
@@ -303,6 +330,11 @@ function warnIfStaleAtEntry(ctx: ExtensionContext, what: string): boolean {
|
|
|
303
330
|
`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.`,
|
|
304
331
|
"warning",
|
|
305
332
|
);
|
|
333
|
+
// v0.29.22: deliberately NO self-heal from the entry probe — it fires
|
|
334
|
+
// when the user is ACTIVELY typing a /glla command, and injected
|
|
335
|
+
// keystrokes would race their input. User-present cases keep the manual
|
|
336
|
+
// warning; the self-heal stays on the autonomous paths (heartbeat
|
|
337
|
+
// probe, send paths) where no one is at the keyboard.
|
|
306
338
|
return true;
|
|
307
339
|
}
|
|
308
340
|
|
|
@@ -601,9 +633,9 @@ function escalateSendRearmStorm(ctx: ExtensionContext, kind: "continuation" | "l
|
|
|
601
633
|
appendLedger(ctx.cwd, "send_rearm_escalated", { kind, afterMinutes: mins, silentMinutes: silent });
|
|
602
634
|
if (kind === "loop" && isLoopActive()) {
|
|
603
635
|
clearLoopTimer();
|
|
604
|
-
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.
|
|
636
|
+
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. Press Escape to cancel the stuck run (pi's own rate-limit retry holds it; pi prints "escape to cancel"), then /loop resume — the loop holds on restore. If still wedged: /reload rebuilds extensions in place, then /loop resume again. Restart pi only if /reload itself fails.` };
|
|
605
637
|
persistState(ctx);
|
|
606
|
-
ctx.ui.notify(`Loop stopped: send-retry storm (${mins}m, session silent ${silent}m).
|
|
638
|
+
ctx.ui.notify(`Loop stopped: send-retry storm (${mins}m, session silent ${silent}m). Escape cancels the stuck run, then /loop resume (the loop holds on restore). /reload if it persists; restart pi only if /reload fails.`, "warning");
|
|
607
639
|
notifyExternal(ctx, "Loop stopped: send-retry storm.");
|
|
608
640
|
return;
|
|
609
641
|
}
|
|
@@ -619,7 +651,7 @@ function escalateSendRearmStorm(ctx: ExtensionContext, kind: "continuation" | "l
|
|
|
619
651
|
// + junk-runner: "complete ending in a pause retry storm"). The audit
|
|
620
652
|
// lifecycle owns its own pauses (quota etc.).
|
|
621
653
|
appendLedger(ctx.cwd, "send_rearm_escalated_suppressed", { reason: "audit-lifecycle" });
|
|
622
|
-
ctx.ui.notify("Send-retry storm during the completion audit — NOT pausing; the auditor's silence is expected. If pi is wedged,
|
|
654
|
+
ctx.ui.notify("Send-retry storm during the completion audit — NOT pausing; the auditor's silence is expected. If pi is wedged, Escape cancels the stuck run; the stored claim survives.", "info");
|
|
623
655
|
return;
|
|
624
656
|
}
|
|
625
657
|
if (state.goal && state.goal.status === "active") {
|
|
@@ -627,9 +659,9 @@ function escalateSendRearmStorm(ctx: ExtensionContext, kind: "continuation" | "l
|
|
|
627
659
|
status: "paused",
|
|
628
660
|
pauseKind: "error",
|
|
629
661
|
pauseReason: `send-retry storm: ${mins}m of re-arms with no session activity for ${silent}m — the session never went idle for the continuation`,
|
|
630
|
-
pauseSuggestedAction: "The session produced no events while the send retried (wedged queue).
|
|
662
|
+
pauseSuggestedAction: "The session produced no events while the send retried (wedged queue — often pi's own rate-limit retry holding the run; pi prints 'escape to cancel'). Press Escape, then /goal resume. If still wedged: /reload rebuilds extensions in place, then /goal resume again. Restart pi only if /reload fails.",
|
|
631
663
|
}, ctx);
|
|
632
|
-
ctx.ui.notify(`${goalNoun()} paused: send-retry storm (${mins}m, session silent ${silent}m).
|
|
664
|
+
ctx.ui.notify(`${goalNoun()} paused: send-retry storm (${mins}m, session silent ${silent}m). Escape cancels the stuck run, then /goal resume. /reload if it persists; restart pi only if /reload fails.`, "warning");
|
|
633
665
|
notifyExternal(ctx, `${goalNoun()} paused: send-retry storm.`);
|
|
634
666
|
}
|
|
635
667
|
}
|
|
@@ -640,9 +672,9 @@ function escalateStallNow(ctx: ExtensionContext, threshold: number): boolean {
|
|
|
640
672
|
appendLedger(ctx.cwd, "stall_escalated", { threshold, kind: isLoopActive() ? "loop" : "goal" });
|
|
641
673
|
if (isLoopActive()) {
|
|
642
674
|
clearLoopTimer();
|
|
643
|
-
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).
|
|
675
|
+
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). Press Escape to cancel any stuck run, then /loop resume — the loop holds on restore. If the handle is stale, /reload rebuilds extensions in place; restart pi only if /reload fails.` };
|
|
644
676
|
persistState(ctx);
|
|
645
|
-
ctx.ui.notify(`Loop stopped: ${threshold} refires produced no turn — the continuation is not landing.
|
|
677
|
+
ctx.ui.notify(`Loop stopped: ${threshold} refires produced no turn — the continuation is not landing. Escape cancels a stuck run, then /loop resume (the loop holds on restore). /reload if stale; restart pi only if /reload fails.`, "warning");
|
|
646
678
|
notifyExternal(ctx, "Loop stopped: stalled (continuation not landing).");
|
|
647
679
|
return true;
|
|
648
680
|
}
|
|
@@ -651,9 +683,9 @@ function escalateStallNow(ctx: ExtensionContext, threshold: number): boolean {
|
|
|
651
683
|
status: "paused",
|
|
652
684
|
pauseKind: "error",
|
|
653
685
|
pauseReason: `stalled: ${threshold} continuation refires landed no turn`,
|
|
654
|
-
pauseSuggestedAction: "The continuation chain is broken in this process (wedged message queue or stale API).
|
|
686
|
+
pauseSuggestedAction: "The continuation chain is broken in this process (wedged message queue or stale API). Press Escape to cancel any stuck run, then /goal resume. If the handle is stale, /reload rebuilds extensions in place — restart pi only if /reload fails.",
|
|
655
687
|
}, ctx);
|
|
656
|
-
ctx.ui.notify(`${goalNoun()} paused: ${threshold} refires produced no turn.
|
|
688
|
+
ctx.ui.notify(`${goalNoun()} paused: ${threshold} refires produced no turn. Escape cancels a stuck run, then /goal resume. /reload if stale; restart pi only if /reload fails.`, "warning");
|
|
657
689
|
notifyExternal(ctx, `${goalNoun()} paused: stalled (continuation not landing).`);
|
|
658
690
|
return true;
|
|
659
691
|
}
|
|
@@ -1360,7 +1392,7 @@ function fireReviewer(
|
|
|
1360
1392
|
// to still count as "proposed" in the report + notify. Now the
|
|
1361
1393
|
// failure is LOUD and the proposal goes uncounted.
|
|
1362
1394
|
ctx.ui.notify(
|
|
1363
|
-
`Postaudit /goal proposal NOT delivered: ${err instanceof Error ? err.message : String(err)} — the follow-up never reached the session.
|
|
1395
|
+
`Postaudit /goal proposal NOT delivered: ${err instanceof Error ? err.message : String(err)} — the follow-up never reached the session. Run /reload if the session was just replaced (extensions rebuild in place), then retry.`,
|
|
1364
1396
|
"warning",
|
|
1365
1397
|
);
|
|
1366
1398
|
return false;
|
|
@@ -1496,7 +1528,7 @@ async function startDrafting(ctx: ExtensionContext, target: "goal" | "list" | "l
|
|
|
1496
1528
|
if (isStaleApiError(err)) {
|
|
1497
1529
|
extensionApiStale = true;
|
|
1498
1530
|
appendLedger(ctx.cwd, "extension_api_stale", { where: "startDrafting seed" });
|
|
1499
|
-
ctx.ui.notify("glla: can't start the drafting interview — this session's extension handle is stale (pi session replacement).
|
|
1531
|
+
ctx.ui.notify("glla: can't start the drafting interview — this session's extension handle is stale (pi session replacement). Run /reload (extensions rebuild in place), then re-run the command. Restart pi only if /reload fails.", "warning");
|
|
1500
1532
|
} else {
|
|
1501
1533
|
ctx.ui.notify(`glla: couldn't start the drafting interview (${err instanceof Error ? err.message : String(err)}) — try again.`, "warning");
|
|
1502
1534
|
}
|
|
@@ -1616,7 +1648,7 @@ async function cmdSet(args: string, ctx: ExtensionContext, skipDraft = false): P
|
|
|
1616
1648
|
// v0.28.1 (S3): the goal is persisted — mark the interrupt so the next
|
|
1617
1649
|
// fresh session LOADS it (held by default since v0.28.21), and tell the truth instead of "starting now".
|
|
1618
1650
|
updateGoal({ interruptedAt: nowIso(), interruptedReason: "created in a stale session" }, ctx);
|
|
1619
|
-
ctx.ui.notify(`Goal saved: ${shortObj(goal.objective)} — safe in .pi-glla/, but this stale process can't send continuations.
|
|
1651
|
+
ctx.ui.notify(`Goal saved: ${shortObj(goal.objective)} — safe in .pi-glla/, but this stale process can't send continuations. Run /reload (extensions rebuild in place, state survives), then /goal resume. Restart pi only if /reload itself fails.`, "warning");
|
|
1620
1652
|
return;
|
|
1621
1653
|
}
|
|
1622
1654
|
ctx.ui.notify(`Goal started: ${shortObj(goal.objective)} — the auditor will verify on completion.`, "info");
|
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.23",
|
|
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",
|