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 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 (unattended rigs)
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
 
@@ -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(ctx.cwd).autoAcceptDrafts === true;
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
- try {
2652
- batchConfirmed = await liveCtx.ui.confirm(
2653
- "Confirm list batch",
2654
- `${p.items.length} items:\n${preview}${batchActivates ? "\n\n(List is empty — confirming ACTIVATES item 1 immediately as the active goal.)" : ""}`,
2655
- );
2656
- } catch (err) {
2657
- // v0.28.1 (T1): a stale confirm is NOT a rejection — nothing was
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
- if (isStaleApiError(err)) {
2660
- extensionApiStale = true;
2661
- appendLedger(liveCtx.cwd, "extension_api_stale", { where: "batch confirm" });
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
- try {
2703
- confirmed = await liveCtx.ui.confirm(isListDraft ? "Confirm list item" : "Confirm goal", `${p.objective.trim()}${contractBlock}${activationNote}`);
2704
- } catch (err) {
2705
- // v0.28.1 (T1): a stale confirm is NOT "Draft rejected by the user".
2706
- if (isStaleApiError(err)) {
2707
- extensionApiStale = true;
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(ctx.cwd).autoAcceptDrafts === true;
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
- confirmed = await liveCtx.ui.confirm(
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
- try {
2911
- confirmed = await liveCtx.ui.confirm(
2912
- "Confirm loop spec refinement",
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
- } catch {
2916
- confirmed = false;
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(ctx.cwd).autoAcceptDrafts === true;
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.ui.confirm("Confirm task list", preview);
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.11",
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",