pi-goal-list-loop-audit 0.26.6 → 0.26.8
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/goal-loop-core.ts +19 -2
- package/extensions/loops/goal.ts +44 -14
- package/package.json +1 -1
|
@@ -660,8 +660,16 @@ export function cloneGoal(goal: Goal): Goal {
|
|
|
660
660
|
* setting for unattended restarts). One mechanical predicate; no heuristics.
|
|
661
661
|
*/
|
|
662
662
|
export function shouldAutoResumeOnSessionStart(reason: string | undefined, autoResume: boolean | undefined): boolean {
|
|
663
|
-
|
|
664
|
-
|
|
663
|
+
// v0.26.8: default flipped to ON — keep pushing forward on every session
|
|
664
|
+
// start unless the user explicitly opts out (/glla autoresume=off). The
|
|
665
|
+
// "super stuck" brakes (stall escalation, stale-api terminal, pending-
|
|
666
|
+
// latch watchdog) still stop the machine loudly; a mere process restart
|
|
667
|
+
// is not a reason to hold work. Explicit off preserves the v0.21.0 gate:
|
|
668
|
+
// only sessions with history (resume/reload/fork) auto-resume.
|
|
669
|
+
if (autoResume === false) {
|
|
670
|
+
return reason === "resume" || reason === "reload" || reason === "fork";
|
|
671
|
+
}
|
|
672
|
+
return true;
|
|
665
673
|
}
|
|
666
674
|
|
|
667
675
|
/**
|
|
@@ -931,6 +939,15 @@ export function shouldSuppressHeartbeatForRecentShip(args: {
|
|
|
931
939
|
|
|
932
940
|
/** Best-effort "when did work last ship" for a repo: newest of the HEAD
|
|
933
941
|
* commit time and the .pi-glla state file mtime. Null when unknown. */
|
|
942
|
+
/** v0.26.7: pi's exact stale-runtime error signature — thrown by every
|
|
943
|
+
* runtime-bound method after pi invalidates the extension on session
|
|
944
|
+
* replacement (newSession/fork/switchSession/reload; compaction reaches
|
|
945
|
+
* the same teardown in pi 0.82.x). See dist/core/extensions/loader.js
|
|
946
|
+
* createExtensionRuntime().invalidate. */
|
|
947
|
+
export function isStaleApiError(err: unknown): boolean {
|
|
948
|
+
return err instanceof Error && err.message.includes("stale after session replacement");
|
|
949
|
+
}
|
|
950
|
+
|
|
934
951
|
export function lastShippedAtMs(cwd: string): number | null {
|
|
935
952
|
// v0.26.6: the .pi-glla/active.jsonl MTIME term was REMOVED — the
|
|
936
953
|
// heartbeat's own ledger writes refreshed it every 15s, which made the
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
crossRecommendMode,
|
|
52
52
|
formatListDepth,
|
|
53
53
|
shouldEscalateStall,
|
|
54
|
+
isStaleApiError,
|
|
54
55
|
mergeSettings,
|
|
55
56
|
parseListImport,
|
|
56
57
|
|
|
@@ -177,6 +178,30 @@ const HELD_ON_RESTORE = "held: restored in a fresh session";
|
|
|
177
178
|
// The ExtensionAPI captured in the factory. sendMessage lives on the API,
|
|
178
179
|
// not on ExtensionContext, so continuation sends need it at module scope.
|
|
179
180
|
let extensionApi: ExtensionAPI | null = null;
|
|
181
|
+
// v0.26.7: pi invalidates the extension runtime on session replacement
|
|
182
|
+
// (newSession/fork/switchSession/reload — and the compaction path reaches
|
|
183
|
+
// it via teardownCurrent in pi 0.82.x). Once stale, every sendMessage
|
|
184
|
+
// throws FOREVER in this process — retrying for hours is the hegemon
|
|
185
|
+
// failure shape. Detect the stale signature once and go terminally loud.
|
|
186
|
+
let extensionApiStale = false;
|
|
187
|
+
|
|
188
|
+
/** v0.26.7: a stale api is terminal for this process — pause/stop loudly
|
|
189
|
+
* with restart guidance instead of retrying sends that can never land. */
|
|
190
|
+
function goStaleTerminal(ctx: ExtensionContext, where: string): void {
|
|
191
|
+
if (extensionApiStale) return; // already terminal — don't re-spam
|
|
192
|
+
extensionApiStale = true;
|
|
193
|
+
appendLedger(ctx.cwd, "extension_api_stale", { where, kind: isLoopActive() ? "loop" : "goal" });
|
|
194
|
+
const guidance = "pi invalidated this session's extension handle (session replacement — compaction triggers it in pi 0.82.x). Sends can never land in this process. Restart pi (or reload extensions), then /goal resume / /loop start.";
|
|
195
|
+
if (isLoopActive()) {
|
|
196
|
+
clearLoopTimer();
|
|
197
|
+
state.loop = { ...state.loop!, active: false, stopReason: `extension api stale: ${guidance}` };
|
|
198
|
+
persistState(ctx);
|
|
199
|
+
} else if (state.goal && state.goal.status === "active") {
|
|
200
|
+
updateGoal({ status: "paused", pauseReason: "extension api stale (pi session replacement)", pauseSuggestedAction: guidance }, ctx);
|
|
201
|
+
}
|
|
202
|
+
ctx.ui.notify(`glla: ${guidance}`, "warning");
|
|
203
|
+
notifyExternal(ctx, `glla: extension api stale — restart pi. (${where})`);
|
|
204
|
+
}
|
|
180
205
|
|
|
181
206
|
// The most recent ExtensionContext seen from any event or command handler.
|
|
182
207
|
// pi replaces sessions (newSession/fork/reload) and stale ctx throws on use,
|
|
@@ -486,7 +511,7 @@ function sendContinuation(goalId: string): void {
|
|
|
486
511
|
continuationTimer.unref?.();
|
|
487
512
|
return;
|
|
488
513
|
}
|
|
489
|
-
if (!extensionApi) return;
|
|
514
|
+
if (!extensionApi || extensionApiStale) return;
|
|
490
515
|
try {
|
|
491
516
|
extensionApi.sendMessage({
|
|
492
517
|
customType: GOAL_EVENT_ENTRY,
|
|
@@ -496,7 +521,9 @@ function sendContinuation(goalId: string): void {
|
|
|
496
521
|
appendLedger(ctx.cwd, "goal_continuation_sent", { goalId });
|
|
497
522
|
} catch (err) {
|
|
498
523
|
appendLedger(ctx.cwd, "goal_continuation_send_failed", { goalId, error: err instanceof Error ? err.message : String(err) });
|
|
499
|
-
//
|
|
524
|
+
// v0.26.7: stale runtime = terminal (sends can never land); anything
|
|
525
|
+
// else is transient — next agent_end/session_start reschedules.
|
|
526
|
+
if (isStaleApiError(err)) goStaleTerminal(ctx, "sendContinuation");
|
|
500
527
|
}
|
|
501
528
|
}
|
|
502
529
|
|
|
@@ -1268,7 +1295,7 @@ function isLoopActive(): boolean {
|
|
|
1268
1295
|
|
|
1269
1296
|
/** Run the user's measure command. Orchestrator-side, never agent-side. */
|
|
1270
1297
|
async function runMeasure(ctx: ExtensionContext, cmd: string): Promise<number | null> {
|
|
1271
|
-
if (!extensionApi) return null;
|
|
1298
|
+
if (!extensionApi || extensionApiStale) return null;
|
|
1272
1299
|
try {
|
|
1273
1300
|
const result = await extensionApi.exec("bash", ["-c", cmd], { cwd: ctx.cwd, timeout: MEASURE_TIMEOUT_MS });
|
|
1274
1301
|
const stdout = (result as any)?.stdout ?? "";
|
|
@@ -1392,6 +1419,8 @@ function sendLoopTurn(): void {
|
|
|
1392
1419
|
// stale API — next agent_end reschedules (but if none comes, the
|
|
1393
1420
|
// heartbeat's stall escalation stops the spin — v0.26.1).
|
|
1394
1421
|
appendLedger(ctx.cwd, "loop_turn_send_failed", { error: err instanceof Error ? err.message : String(err) });
|
|
1422
|
+
// v0.26.7: stale runtime is terminal, not transient — go loud now.
|
|
1423
|
+
if (isStaleApiError(err)) goStaleTerminal(ctx, "sendLoopTurn");
|
|
1395
1424
|
}
|
|
1396
1425
|
}
|
|
1397
1426
|
|
|
@@ -3192,7 +3221,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
3192
3221
|
patch.autoResume = true;
|
|
3193
3222
|
changed = true;
|
|
3194
3223
|
} else if (["off", "false", "0", "no", "unset"].includes(value)) {
|
|
3195
|
-
patch.autoResume = undefined
|
|
3224
|
+
patch.autoResume = false; // v0.26.8: explicit off must persist — undefined now means ON
|
|
3196
3225
|
changed = true;
|
|
3197
3226
|
} else {
|
|
3198
3227
|
ctx.ui.notify(`autoresume must be on or off, got: ${value}`, "warning");
|
|
@@ -3300,7 +3329,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
3300
3329
|
saveSettings(scope, ctx.cwd, patch);
|
|
3301
3330
|
const effective = loadSettings(ctx.cwd);
|
|
3302
3331
|
ctx.ui.notify(
|
|
3303
|
-
`Saved to ${scope} config. Effective now: model=${effective.auditorModel ?? "(session model)"} thinking=${effective.auditorThinkingLevel ?? "(session)"} notify=${effective.notifyCmd ?? "(off)"} tokenLimit=${effective.tokenLimit ?? 0}${(effective.tokenLimit ?? 0) > 0 ? "" : " (off)"} autoResume=${effective.autoResume ===
|
|
3332
|
+
`Saved to ${scope} config. Effective now: model=${effective.auditorModel ?? "(session model)"} thinking=${effective.auditorThinkingLevel ?? "(session)"} notify=${effective.notifyCmd ?? "(off)"} tokenLimit=${effective.tokenLimit ?? 0}${(effective.tokenLimit ?? 0) > 0 ? "" : " (off)"} autoResume=${effective.autoResume === false ? "off" : "on (default)"} auditFeedbackChars=${effective.auditFeedbackChars ?? DEFAULT_AUDIT_FEEDBACK_CHARS}${(effective.auditFeedbackChars ?? DEFAULT_AUDIT_FEEDBACK_CHARS) === 0 ? " (full report)" : ""}\n` +
|
|
3304
3333
|
`Note: the auditor runs without extensions — it must be a built-in provider, not an extension-registered one.`,
|
|
3305
3334
|
"info",
|
|
3306
3335
|
);
|
|
@@ -3380,6 +3409,7 @@ function warnOnCommandCollision(ctx: ExtensionContext): void {
|
|
|
3380
3409
|
|
|
3381
3410
|
export default function (pi: ExtensionAPI): void {
|
|
3382
3411
|
extensionApi = pi;
|
|
3412
|
+
extensionApiStale = false; // a fresh factory run means a fresh runtime (reload path)
|
|
3383
3413
|
startHeartbeat();
|
|
3384
3414
|
startUITicker();
|
|
3385
3415
|
// Four top-level commands, that's all (v0.8.0 consolidation):
|
|
@@ -3414,7 +3444,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
3414
3444
|
["thinking=", "auditor thinking level: /glla thinking=high"],
|
|
3415
3445
|
["notify=", "desktop push command: /glla notify='notify-send pi \"$1\"'"],
|
|
3416
3446
|
["tokenlimit=", "per-goal token budget (0 = off): /glla tokenlimit=2000000"],
|
|
3417
|
-
["autoresume=", "on: auto-resume
|
|
3447
|
+
["autoresume=", "on (default): auto-resume goals/loops on every session start; off: hold on fresh sessions"],
|
|
3418
3448
|
["auditcap=", "N: pause goal after N consecutive auditor disapprovals (default 5, 0 = unlimited)"],
|
|
3419
3449
|
["auditfeedbackchars=", "cap on executor-visible disapproval report chars (0 = full report, the default)"],
|
|
3420
3450
|
["aggressivemode=", "on: keep-going defaults — autoResume, cap 10, stuck 10, wedge off, quota auto-retry, cap→TODOs"],
|
|
@@ -3611,12 +3641,12 @@ export default function (pi: ExtensionAPI): void {
|
|
|
3611
3641
|
} catch (err) {
|
|
3612
3642
|
ctx.ui.notify(`glla subagent override sync failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
3613
3643
|
}
|
|
3614
|
-
// Restore gate (v0.21.0
|
|
3615
|
-
//
|
|
3616
|
-
//
|
|
3617
|
-
//
|
|
3618
|
-
//
|
|
3619
|
-
//
|
|
3644
|
+
// Restore gate (v0.21.0, default flipped v0.26.8): auto-resume on EVERY
|
|
3645
|
+
// session start by default — keep pushing forward unless super stuck
|
|
3646
|
+
// (the stall escalation / stale-api / latch brakes still stop loudly).
|
|
3647
|
+
// /glla autoresume=off restores the v0.21.0 gate: fresh sessions
|
|
3648
|
+
// ("startup"/"new", or a pi too old to report a reason) HOLD, only
|
|
3649
|
+
// sessions with history ("resume"/"reload"/"fork") auto-resume.
|
|
3620
3650
|
const autoResume = shouldAutoResumeOnSessionStart(event?.reason, resolveEffectiveAggressiveSettings(loadSettings(ctx.cwd)).autoResume);
|
|
3621
3651
|
// v0.25.0 (contract item 6): aggressiveMode announces every auto-event.
|
|
3622
3652
|
if (
|
|
@@ -3638,7 +3668,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
3638
3668
|
state.loop = { ...l, active: false, stopReason: HELD_ON_RESTORE };
|
|
3639
3669
|
persistState(ctx);
|
|
3640
3670
|
ctx.ui.notify(
|
|
3641
|
-
`Loop held on restore (
|
|
3671
|
+
`Loop held on restore (/glla autoresume=off): ${l.target.slice(0, 60)} — /loop to resume, /glla autoresume=on to auto-resume in this project.`,
|
|
3642
3672
|
"info",
|
|
3643
3673
|
);
|
|
3644
3674
|
}
|
|
@@ -3657,7 +3687,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
3657
3687
|
const resumeHint = `${resumeCmd} to continue${queued > 0 ? ` (+${queued} waiting in the list)` : ""} · /glla autoresume=on to auto-resume in this project`;
|
|
3658
3688
|
updateGoal({
|
|
3659
3689
|
status: "paused",
|
|
3660
|
-
pauseReason: "restored in a fresh session —
|
|
3690
|
+
pauseReason: "restored in a fresh session — held because /glla autoresume=off",
|
|
3661
3691
|
pauseSuggestedAction: resumeHint,
|
|
3662
3692
|
}, ctx);
|
|
3663
3693
|
ctx.ui.notify(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.8",
|
|
4
4
|
"description": "Goal. Loop. Audit. Done. — 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 — only the read tools needed to verify your goal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dracon",
|