pi-goal-list-loop-audit 0.29.3 → 0.29.5
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-settings.ts +15 -0
- package/extensions/loops/goal.ts +45 -31
- package/package.json +1 -1
|
@@ -152,6 +152,21 @@ export function loadSettings(cwd: string): Settings {
|
|
|
152
152
|
) as unknown as Settings;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/**
|
|
156
|
+
* v0.29.5: autoResume is GLOBAL-only (user directive 2026-07-30: "we are
|
|
157
|
+
* not supporting project level setting for it now, just global"). Launch-
|
|
158
|
+
* time restore reads this, never the project file — a stale autoResume
|
|
159
|
+
* key in a project's settings.json is ignored (junk-runner field case: a
|
|
160
|
+
* project-local opt-in from the unattended-audit era kept auto-firing the
|
|
161
|
+
* list at every bare `pi` launch after the global default flipped off).
|
|
162
|
+
*/
|
|
163
|
+
export function loadGlobalSettings(): Settings {
|
|
164
|
+
return mergeSettings(
|
|
165
|
+
DEFAULT_SETTINGS as unknown as Record<string, unknown>,
|
|
166
|
+
readSettingsFile(globalSettingsPath()) as Record<string, unknown>,
|
|
167
|
+
) as unknown as Settings;
|
|
168
|
+
}
|
|
169
|
+
|
|
155
170
|
/** Every provenance-tracked key (the /glla headless display + UI). */
|
|
156
171
|
export const SETTINGS_KEYS: Array<keyof Settings> = [
|
|
157
172
|
"auditorModel",
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -106,6 +106,7 @@ import {
|
|
|
106
106
|
DEFAULT_SETTINGS,
|
|
107
107
|
SETTINGS_KEYS,
|
|
108
108
|
globalSettingsPath,
|
|
109
|
+
loadGlobalSettings,
|
|
109
110
|
loadSettings,
|
|
110
111
|
projectSettingsPath,
|
|
111
112
|
saveSettings,
|
|
@@ -706,6 +707,10 @@ function heartbeatTick(): void {
|
|
|
706
707
|
ctx.ui.notify(msg, "warning");
|
|
707
708
|
notifyExternal(ctx, msg);
|
|
708
709
|
}
|
|
710
|
+
// v0.29.5: user-abort stand-down — the chain stays DOWN until the
|
|
711
|
+
// user resumes. Without this guard the 60s heartbeat re-fired the
|
|
712
|
+
// continuation and defeated the 0.29.4 stand-down within a minute.
|
|
713
|
+
if (abortedStandDown) return;
|
|
709
714
|
if (!fire) return;
|
|
710
715
|
// v0.26.6: the 0.25.0 "recent ship (<5m)" suppression was REMOVED. It fed
|
|
711
716
|
// lastShippedAtMs, which read the state-file MTIME — and the heartbeat's
|
|
@@ -747,6 +752,10 @@ let consecutiveErrorIterations = 0;
|
|
|
747
752
|
// v0.28.5 (E8): user aborts are NOT provider errors — separate counter,
|
|
748
753
|
// separate brake message, and no auto-resume (aborting is user intent).
|
|
749
754
|
let consecutiveAbortIterations = 0;
|
|
755
|
+
// v0.29.5: set when a user abort stands the chain down (0.29.4) — the
|
|
756
|
+
// heartbeat refire + post-compaction refire must NOT resurrect it; only
|
|
757
|
+
// an explicit schedule (resume/activate/next turn) clears it.
|
|
758
|
+
let abortedStandDown = false;
|
|
750
759
|
let consecutiveNoToolIterations = 0;
|
|
751
760
|
|
|
752
761
|
// =================================================================
|
|
@@ -779,6 +788,7 @@ function freshCtx(): ExtensionContext | null {
|
|
|
779
788
|
}
|
|
780
789
|
|
|
781
790
|
function scheduleContinuation(ctx: ExtensionContext, force = false, delayMs?: number): void {
|
|
791
|
+
abortedStandDown = false; // v0.29.5: any explicit schedule ends the stand-down
|
|
782
792
|
if (!isActionableGoal()) return;
|
|
783
793
|
rememberCtx(ctx);
|
|
784
794
|
const goalId = state.goal!.id;
|
|
@@ -1223,7 +1233,7 @@ function fireReviewer(
|
|
|
1223
1233
|
manual: opts.manual,
|
|
1224
1234
|
ledgerEntries,
|
|
1225
1235
|
sources,
|
|
1226
|
-
enqueueListItems: (objectives) => enqueueItems(ctx, objectives, "reviewer", { autoActivate:
|
|
1236
|
+
enqueueListItems: (objectives) => enqueueItems(ctx, objectives, "reviewer", { autoActivate: loadGlobalSettings().autoResume === true }),
|
|
1227
1237
|
proposeGoal: (objective, reason) => {
|
|
1228
1238
|
try {
|
|
1229
1239
|
extensionApi?.sendUserMessage(
|
|
@@ -3383,13 +3393,11 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
3383
3393
|
persistState(liveCtx);
|
|
3384
3394
|
appendLedger(liveCtx.cwd, "list_added", { id: item.id, objective: item.objective, drafted: true });
|
|
3385
3395
|
if (!state.goal || state.goal.status === "complete" || state.goal.status === "aborted") {
|
|
3386
|
-
// v0.
|
|
3387
|
-
//
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
return { content: [{ type: "text", text: "Draft accepted and added to the list, but NOT started (the user's autoResume setting is off — auto-accepted drafts queue, they don't auto-start). Do NOT begin work. Tell the user: /list next starts it." }], details: {} };
|
|
3392
|
-
}
|
|
3396
|
+
// v0.29.4: an auto-accepted draft STARTS — autoAcceptDrafts is the
|
|
3397
|
+
// pre-consent (the user asked for the draft in-session). The
|
|
3398
|
+
// 0.28.28 autoResume hold is lifted: that setting now gates ONLY
|
|
3399
|
+
// launch-time restore. The 0.29.1 zombie-twin guard already
|
|
3400
|
+
// refused duplicates of just-completed work upstream.
|
|
3393
3401
|
activateNextListItem(liveCtx);
|
|
3394
3402
|
return { content: [{ type: "text", text: "Confirmed and activated (list was empty). Begin work now." }], details: {} };
|
|
3395
3403
|
}
|
|
@@ -3397,20 +3405,11 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
3397
3405
|
}
|
|
3398
3406
|
const goal = createGoal(full, liveCtx);
|
|
3399
3407
|
setGoal(goal, liveCtx, autoAccept ? "draft-autoaccepted" : "draft-confirmed");
|
|
3400
|
-
// v0.
|
|
3401
|
-
//
|
|
3402
|
-
//
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
status: "paused",
|
|
3406
|
-
pauseKind: "blocked",
|
|
3407
|
-
pauseReason: "auto-accepted draft — held for the user's go-ahead (autoResume off)",
|
|
3408
|
-
pauseSuggestedAction: "/goal resume to start · /goal cancel to drop · /glla autoresume=on starts auto-accepted drafts automatically",
|
|
3409
|
-
}, liveCtx);
|
|
3410
|
-
appendLedger(liveCtx.cwd, "draft_held", { goalId: goal.id, reason: "autoaccept-autoresume-off" });
|
|
3411
|
-
liveCtx.ui.notify(`Draft auto-accepted and HELD (autoResume off): ${goal.objective.slice(0, 80)} — /goal resume to start, /goal cancel to drop.`, "info");
|
|
3412
|
-
return { content: [{ type: "text", text: "Goal accepted but HELD (the user's autoResume setting is off — auto-accepted drafts do not auto-start). Do NOT begin work. Tell the user: /goal resume starts it, /goal cancel drops it." }], details: {} };
|
|
3413
|
-
}
|
|
3408
|
+
// v0.29.4: auto-accepted drafts START (autoAcceptDrafts is the
|
|
3409
|
+
// pre-consent — the user asked for the draft in-session). autoResume
|
|
3410
|
+
// no longer gates draft starts; it gates ONLY launch-time restore of
|
|
3411
|
+
// persisted state ("load it but not auto start it"). Zombie twins of
|
|
3412
|
+
// just-completed work are refused upstream (0.29.1).
|
|
3414
3413
|
iterationCounter = 0;
|
|
3415
3414
|
consecutiveErrorIterations = 0;
|
|
3416
3415
|
consecutiveAbortIterations = 0;
|
|
@@ -5071,7 +5070,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5071
5070
|
const c = freshCtx();
|
|
5072
5071
|
if (!c) return;
|
|
5073
5072
|
try {
|
|
5074
|
-
if (c.isIdle() && !c.hasPendingMessages() && continuationTimer === null && loopTimer === null && isSupervising()) {
|
|
5073
|
+
if (c.isIdle() && !c.hasPendingMessages() && continuationTimer === null && loopTimer === null && isSupervising() && !abortedStandDown) {
|
|
5075
5074
|
appendLedger(c.cwd, "compaction_refire", {});
|
|
5076
5075
|
if (isLoopActive()) scheduleLoopTick(c);
|
|
5077
5076
|
else scheduleContinuation(c, true);
|
|
@@ -5196,12 +5195,13 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5196
5195
|
// Restore gate (v0.26.9 tri-state): a human LOADING a session
|
|
5197
5196
|
// ("startup"/"new"/"resume", or no reason) HOLDS — the popup shows what
|
|
5198
5197
|
// is waiting and nothing starts until they resume explicitly. In-session
|
|
5199
|
-
// machinery ("reload"/"fork") auto-resumes. /glla autoresume=on opts
|
|
5200
|
-
//
|
|
5201
|
-
//
|
|
5202
|
-
//
|
|
5203
|
-
//
|
|
5204
|
-
|
|
5198
|
+
// machinery ("reload"/"fork") auto-resumes. /glla autoresume=on opts
|
|
5199
|
+
// into auto-resume everywhere (unattended rigs); autoresume=off never
|
|
5200
|
+
// auto-resumes. v0.29.5: the setting is GLOBAL-only — project-level
|
|
5201
|
+
// autoResume keys are ignored. Once running, the chain auto-continues
|
|
5202
|
+
// forever unless a super-stuck brake (stall escalation / stale-api /
|
|
5203
|
+
// latch) stops it loudly.
|
|
5204
|
+
const autoResumeSetting = resolveEffectiveAggressiveSettings(loadGlobalSettings()).autoResume;
|
|
5205
5205
|
const autoResume = shouldAutoResumeOnSessionStart(event?.reason, autoResumeSetting);
|
|
5206
5206
|
// v0.25.0 (contract item 6): aggressiveMode announces every auto-event.
|
|
5207
5207
|
if (
|
|
@@ -5223,7 +5223,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5223
5223
|
state.loop = { ...l, active: false, stopReason: HELD_ON_RESTORE };
|
|
5224
5224
|
persistState(ctx);
|
|
5225
5225
|
ctx.ui.notify(
|
|
5226
|
-
`Loop held on restore: ${l.target.slice(0, 60)} — /loop resume to continue, /glla autoresume=on to auto-resume on session load
|
|
5226
|
+
`Loop held on restore: ${l.target.slice(0, 60)} — /loop resume to continue, /glla autoresume=on to auto-resume on session load (global setting).`,
|
|
5227
5227
|
"info",
|
|
5228
5228
|
);
|
|
5229
5229
|
}
|
|
@@ -5250,7 +5250,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5250
5250
|
// v0.22.7: name WHAT is held — a list head resumes through /list.
|
|
5251
5251
|
const isListItem = state.goal.policy === "list";
|
|
5252
5252
|
const resumeCmd = isListItem ? "/list resume" : "/goal resume";
|
|
5253
|
-
const resumeHint = `${resumeCmd} to continue${queued > 0 ? ` (+${queued} waiting in the list)` : ""} · /glla autoresume=on to auto-resume
|
|
5253
|
+
const resumeHint = `${resumeCmd} to continue${queued > 0 ? ` (+${queued} waiting in the list)` : ""} · /glla autoresume=on to auto-resume on load (global setting)`;
|
|
5254
5254
|
updateGoal({
|
|
5255
5255
|
status: "paused",
|
|
5256
5256
|
pauseKind: "blocked",
|
|
@@ -5367,6 +5367,12 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5367
5367
|
// "unproductive turns" pause). pi's own retry owns the backoff;
|
|
5368
5368
|
// the nudge counter neither increments nor resets on these turns.
|
|
5369
5369
|
appendLedger(ctx.cwd, "stall_nudge_exempt_error", { nudgesSoFar: heartbeatNudges });
|
|
5370
|
+
} else if (lastA?.stopReason === "aborted") {
|
|
5371
|
+
// v0.29.4: user aborts are not model unproductivity either — the
|
|
5372
|
+
// user pressed Esc. Counting the interrupt tripped the stall brake
|
|
5373
|
+
// on the USER's action (pully field case: Esc-spam → STALL WARNING
|
|
5374
|
+
// 1/3, 2/3 → a bogus "stalled" pause). Neither increment nor reset.
|
|
5375
|
+
appendLedger(ctx.cwd, "stall_nudge_exempt_aborted", { nudgesSoFar: heartbeatNudges });
|
|
5370
5376
|
} else {
|
|
5371
5377
|
const s = loadSettings(ctx.cwd);
|
|
5372
5378
|
const shortWordsThr = s.stallShortWords ?? DEFAULT_STALL_SHORT_WORDS;
|
|
@@ -5529,6 +5535,14 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5529
5535
|
appendLedger(ctx.cwd, "goal_paused", { reason: "5 consecutive aborts (user interrupted)" });
|
|
5530
5536
|
return;
|
|
5531
5537
|
}
|
|
5538
|
+
// v0.29.4: an abort stands the chain DOWN — no auto re-fire. The old
|
|
5539
|
+
// fall-through re-fired the continuation immediately, so every Esc
|
|
5540
|
+
// was answered by another turn under the user's hands ("it auto
|
|
5541
|
+
// triggered and I kept spamming esc on it" — pully, 2026-07-30).
|
|
5542
|
+
ctx.ui.notify(`${goalNoun()} standing down — turn aborted by user (not counted toward stalls). /goal resume to continue, /goal cancel to stop.`, "info");
|
|
5543
|
+
abortedStandDown = true; // v0.29.5: heartbeat/compaction refires must not resurrect the chain
|
|
5544
|
+
appendLedger(ctx.cwd, "abort_stand_down", { consecutiveAborts: consecutiveAbortIterations });
|
|
5545
|
+
return;
|
|
5532
5546
|
} else {
|
|
5533
5547
|
consecutiveErrorIterations = 0;
|
|
5534
5548
|
consecutiveAbortIterations = 0;
|
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.5",
|
|
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",
|