pi-goal-list-loop-audit 0.28.11 → 0.28.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/README.md +1 -1
- package/extensions/loops/goal.ts +77 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -209,7 +209,7 @@ No external watchdog plugin needed.
|
|
|
209
209
|
/glla quotaretryminutes=60 # minutes before auto-retrying a quota-exhausted auditor
|
|
210
210
|
/glla stuckmax=10 # consecutive stuck interventions before a loop stops (default 5)
|
|
211
211
|
/glla auditfeedbackchars=800 # cap the executor-visible auditor report (default 0 = full report)
|
|
212
|
-
/glla autoaccept=on # drafts ACTIVATE without the Confirm dialog
|
|
212
|
+
/glla autoaccept=on # drafts ACTIVATE without the Confirm dialo... (every draft dialog also offers "always auto-accept" inline)unattended rigs)
|
|
213
213
|
/glla project tokenlimit=500 # rare per-project override
|
|
214
214
|
```
|
|
215
215
|
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -259,6 +259,36 @@ function warnIfStaleAtEntry(ctx: ExtensionContext, what: string): boolean {
|
|
|
259
259
|
return true;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
/** v0.28.12: draft-class confirm with the auto-accept escape hatch SURFACED.
|
|
263
|
+
* The polis incident: a user sat through a 14-item batch Confirm having
|
|
264
|
+
* already reviewed every item during drafting, never knowing /glla
|
|
265
|
+
* autoaccept=on existed — the Yes/No dialog never mentioned it. Now every
|
|
266
|
+
* draft dialog is a 3-choice select; the ALWAYS choice persists project
|
|
267
|
+
* autoAcceptDrafts=true and accepts. Returns "stale" when the dialog can't
|
|
268
|
+
* render (session replacement) so call sites keep their NOT-a-rejection
|
|
269
|
+
* handling; falls back to the plain confirm if select is unavailable. */
|
|
270
|
+
type DraftChoice = "yes" | "no" | "stale";
|
|
271
|
+
async function confirmDraft(ctx: ExtensionContext, title: string, body: string): Promise<DraftChoice> {
|
|
272
|
+
const ALWAYS = "Yes — and always auto-accept drafts (sets autoAcceptDrafts for this project)";
|
|
273
|
+
try {
|
|
274
|
+
const choice = await ctx.ui.select(`${title}\n\n${body}`, ["Yes", ALWAYS, "No"]);
|
|
275
|
+
if (choice === ALWAYS) {
|
|
276
|
+
saveSettings("project", ctx.cwd, { autoAcceptDrafts: true });
|
|
277
|
+
appendLedger(ctx.cwd, "draft_autoaccept_enabled", { via: title });
|
|
278
|
+
ctx.ui.notify("Draft auto-accept ON for this project — future draft confirms are skipped. Undo: /glla autoaccept=off.", "info");
|
|
279
|
+
return "yes";
|
|
280
|
+
}
|
|
281
|
+
return choice === "Yes" ? "yes" : "no";
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (isStaleApiError(err)) return "stale";
|
|
284
|
+
try {
|
|
285
|
+
return (await ctx.ui.confirm(title, body)) ? "yes" : "no";
|
|
286
|
+
} catch (err2) {
|
|
287
|
+
return isStaleApiError(err2) ? "stale" : "no";
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
262
292
|
// The most recent ExtensionContext seen from any event or command handler.
|
|
263
293
|
// pi replaces sessions (newSession/fork/reload) and stale ctx throws on use,
|
|
264
294
|
// so timers must never capture a ctx — they read lastCtx at fire time.
|
|
@@ -2615,10 +2645,11 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2615
2645
|
details: {},
|
|
2616
2646
|
};
|
|
2617
2647
|
}
|
|
2648
|
+
const liveCtx = (execCtx as ExtensionContext | undefined) ?? ctx;
|
|
2618
2649
|
// v0.14.0: the interview floor — no Confirm until the user replied.
|
|
2619
2650
|
// v0.23.8: /glla autoaccept=on skips the floor AND the Confirm —
|
|
2620
2651
|
// the seed carries the intent (unattended rigs). Default off.
|
|
2621
|
-
const autoAccept = loadSettings(
|
|
2652
|
+
const autoAccept = loadSettings(liveCtx.cwd).autoAcceptDrafts === true;
|
|
2622
2653
|
if (!autoAccept) {
|
|
2623
2654
|
if (draftingUserReplies === 0) draftingBlockedProposals++;
|
|
2624
2655
|
const block = draftProposalBlock(draftingUserReplies, draftingBlockedProposals);
|
|
@@ -2633,7 +2664,6 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2633
2664
|
details: {},
|
|
2634
2665
|
};
|
|
2635
2666
|
}
|
|
2636
|
-
const liveCtx = (execCtx as ExtensionContext | undefined) ?? ctx;
|
|
2637
2667
|
// v0.28.1 (S3): honest staleness warning before any Confirm attempt.
|
|
2638
2668
|
warnIfStaleAtEntry(liveCtx, "goal drafting");
|
|
2639
2669
|
// Multi-item list draft: one Confirm for the whole batch.
|
|
@@ -2648,21 +2678,19 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2648
2678
|
liveCtx.ui.notify(`List batch auto-accepted (/glla autoaccept=on): ${p.items.length} items${batchActivates ? " — item 1 ACTIVATES now" : ""}.`, "info");
|
|
2649
2679
|
appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "batch", count: p.items.length });
|
|
2650
2680
|
} else {
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
// v0.28.1 (T1): a stale
|
|
2681
|
+
const c = await confirmDraft(
|
|
2682
|
+
liveCtx,
|
|
2683
|
+
"Confirm list batch",
|
|
2684
|
+
`${p.items.length} items:\n${preview}${batchActivates ? "\n\n(List is empty — confirming ACTIVATES item 1 immediately as the active goal.)" : ""}`,
|
|
2685
|
+
);
|
|
2686
|
+
if (c === "stale") {
|
|
2687
|
+
// v0.28.1 (T1): a stale dialog is NOT a rejection — nothing was
|
|
2658
2688
|
// refused; the dialog simply can't render in a doomed process.
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
|
|
2663
|
-
}
|
|
2664
|
-
batchConfirmed = false;
|
|
2689
|
+
extensionApiStale = true;
|
|
2690
|
+
appendLedger(liveCtx.cwd, "extension_api_stale", { where: "batch confirm" });
|
|
2691
|
+
return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
|
|
2665
2692
|
}
|
|
2693
|
+
batchConfirmed = c === "yes";
|
|
2666
2694
|
}
|
|
2667
2695
|
if (!batchConfirmed) {
|
|
2668
2696
|
return {
|
|
@@ -2699,17 +2727,14 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2699
2727
|
liveCtx.ui.notify(`Draft auto-accepted (/glla autoaccept=on)${willActivate ? " — ACTIVATING now" : ""}: ${p.objective.trim().slice(0, 90)}`, "info");
|
|
2700
2728
|
appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: isListDraft ? "list" : "goal", objective: p.objective.trim().slice(0, 200) });
|
|
2701
2729
|
} else {
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
appendLedger(liveCtx.cwd, "extension_api_stale", { where: "draft confirm" });
|
|
2709
|
-
return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
|
|
2710
|
-
}
|
|
2711
|
-
confirmed = false;
|
|
2730
|
+
const c = await confirmDraft(liveCtx, isListDraft ? "Confirm list item" : "Confirm goal", `${p.objective.trim()}${contractBlock}${activationNote}`);
|
|
2731
|
+
if (c === "stale") {
|
|
2732
|
+
// v0.28.1 (T1): a stale dialog is NOT "Draft rejected by the user".
|
|
2733
|
+
extensionApiStale = true;
|
|
2734
|
+
appendLedger(liveCtx.cwd, "extension_api_stale", { where: "draft confirm" });
|
|
2735
|
+
return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
|
|
2712
2736
|
}
|
|
2737
|
+
confirmed = c === "yes";
|
|
2713
2738
|
}
|
|
2714
2739
|
if (!confirmed) {
|
|
2715
2740
|
return {
|
|
@@ -2814,7 +2839,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2814
2839
|
// drafter path was still defaulting to 50 after v0.23.6 flipped the
|
|
2815
2840
|
// CLI default.
|
|
2816
2841
|
const max = p.max !== undefined && Number.isFinite(p.max) && p.max >= 0 ? Math.floor(p.max) : metricless ? 0 : 50;
|
|
2817
|
-
const autoAccept = loadSettings(
|
|
2842
|
+
const autoAccept = loadSettings(liveCtx.cwd).autoAcceptDrafts === true;
|
|
2818
2843
|
let confirmed = false;
|
|
2819
2844
|
if (autoAccept) {
|
|
2820
2845
|
confirmed = true;
|
|
@@ -2822,12 +2847,14 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2822
2847
|
appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "loop", target: p.target.trim().slice(0, 200), metricless });
|
|
2823
2848
|
} else {
|
|
2824
2849
|
try {
|
|
2825
|
-
|
|
2850
|
+
const c = await confirmDraft(
|
|
2851
|
+
liveCtx,
|
|
2826
2852
|
"Confirm loop",
|
|
2827
2853
|
metricless
|
|
2828
2854
|
? `Target: ${p.target.trim()}\n\nMeasure: NONE — metricless spec loop. There is NO plateau stop: the loop ends only at ${max > 0 ? `${max} iterations` : "NO iteration cap"}${typeof p.time === "number" && p.time > 0 ? ` · Time bound: ${p.time}h` : ""}${typeof p.tokens === "number" && p.tokens > 0 ? ` · Token bound: ${p.tokens.toLocaleString()}` : ""} · /loop stop.${p.branch ? "\nbranch mode: scratch branch, every iteration committed (clean tree required)" : ""}\n\nEvery iteration must make ONE real, inspectable change — cosmetic churn is the known failure mode (doorknob-polishing). Start it?`
|
|
2829
2855
|
: `Target: ${p.target.trim()}\n\nMeasure: ${p.measureCmd}\nTest-run output: ${rawOutput.slice(0, 200)}\nParsed number: ${parsed} (${p.direction === "min" ? "lower is better" : "higher is better"})\n\nPlateau stop: ${window} non-improving iterations · Cap: ${max > 0 ? `${max} iterations` : "none (unbounded)"}${typeof p.time === "number" && p.time > 0 ? ` · Time bound: ${p.time}h` : ""}${typeof p.tokens === "number" && p.tokens > 0 ? ` · Token bound: ${p.tokens.toLocaleString()}` : ""}${p.branch ? "\nbranch mode: scratch branch (clean tree required)" : ""}\n\nThe loop never completes — it runs until one of these bounds, plateau, or /loop stop. Start it?`,
|
|
2830
2856
|
);
|
|
2857
|
+
confirmed = c === "yes";
|
|
2831
2858
|
} catch {
|
|
2832
2859
|
confirmed = false;
|
|
2833
2860
|
}
|
|
@@ -2907,13 +2934,20 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2907
2934
|
}
|
|
2908
2935
|
}
|
|
2909
2936
|
let confirmed = false;
|
|
2910
|
-
|
|
2911
|
-
confirmed =
|
|
2912
|
-
|
|
2937
|
+
if (loadSettings(liveCtx.cwd).autoAcceptDrafts === true) {
|
|
2938
|
+
confirmed = true;
|
|
2939
|
+
liveCtx.ui.notify("Loop spec refinement auto-accepted (/glla autoaccept=on).", "info");
|
|
2940
|
+
appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "loop-refine" });
|
|
2941
|
+
} else {
|
|
2942
|
+
try {
|
|
2943
|
+
confirmed = (await confirmDraft(
|
|
2944
|
+
liveCtx,
|
|
2945
|
+
"Confirm loop spec refinement",
|
|
2913
2946
|
`Rationale: ${p.rationale}\n\nTarget:\n old: ${loop.target.slice(0, 120)}\n new: ${newTarget.slice(0, 120)}\n\nMeasure:\n old: ${loop.measureCmd}\n new: ${newMeasure}${newMeasure !== loop.measureCmd ? `\n test-run: ${testOutput.slice(0, 120)} → ${newBaseline}` : ""}\n\nThe loop keeps running against the refined spec (iteration ${loop.iteration} so far). Apply?`,
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2947
|
+
)) === "yes";
|
|
2948
|
+
} catch {
|
|
2949
|
+
confirmed = false;
|
|
2950
|
+
}
|
|
2917
2951
|
}
|
|
2918
2952
|
if (!confirmed) {
|
|
2919
2953
|
return { content: [{ type: "text", text: "Refinement rejected by the user. The loop continues against the current spec — keep improving the metric as defined." }], details: {} };
|
|
@@ -3051,7 +3085,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
3051
3085
|
const subs = (t.subtasks ?? []).map((s, j) => ` ${i + 1}.${j + 1} ${s}`).join("\n");
|
|
3052
3086
|
return `${i + 1}. ${t.title}` + (subs ? `\n${subs}` : "");
|
|
3053
3087
|
}).join("\n");
|
|
3054
|
-
const autoAcceptTasks = loadSettings(
|
|
3088
|
+
const autoAcceptTasks = loadSettings(liveCtx.cwd).autoAcceptDrafts === true;
|
|
3055
3089
|
let confirmed = false;
|
|
3056
3090
|
if (autoAcceptTasks) {
|
|
3057
3091
|
confirmed = true;
|
|
@@ -3059,7 +3093,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
3059
3093
|
appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "tasks", count: p.tasks.length });
|
|
3060
3094
|
} else {
|
|
3061
3095
|
try {
|
|
3062
|
-
confirmed = await liveCtx
|
|
3096
|
+
confirmed = (await confirmDraft(liveCtx, "Confirm task list", preview)) === "yes";
|
|
3063
3097
|
} catch {
|
|
3064
3098
|
confirmed = false;
|
|
3065
3099
|
}
|
|
@@ -4390,6 +4424,14 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4390
4424
|
// them toward the stall brake paused restored goals mid-recovery.
|
|
4391
4425
|
postRestoreGraceTurns--;
|
|
4392
4426
|
appendLedger(ctx.cwd, "post_restore_grace", { remaining: postRestoreGraceTurns });
|
|
4427
|
+
} else if (lastA?.stopReason === "error") {
|
|
4428
|
+
// v0.28.13 (endless-td 429 incident 2026-07-28): provider-error
|
|
4429
|
+
// turns (429 quota exhaustion, 5xx) are NOT model unproductivity —
|
|
4430
|
+
// the model never got a say. Counting them tripped the brake on a
|
|
4431
|
+
// healthy goal mid-CDP-capture (4 MiniMax-M3 429s → wrong
|
|
4432
|
+
// "unproductive turns" pause). pi's own retry owns the backoff;
|
|
4433
|
+
// the nudge counter neither increments nor resets on these turns.
|
|
4434
|
+
appendLedger(ctx.cwd, "stall_nudge_exempt_error", { nudgesSoFar: heartbeatNudges });
|
|
4393
4435
|
} else {
|
|
4394
4436
|
const s = loadSettings(ctx.cwd);
|
|
4395
4437
|
const shortWordsThr = s.stallShortWords ?? DEFAULT_STALL_SHORT_WORDS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.13",
|
|
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",
|