pi-goal-list-loop-audit 0.24.6 → 0.24.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/README.md CHANGED
@@ -201,6 +201,7 @@ No external watchdog plugin needed.
201
201
  /glla wedgealert=30 # hung-command alert minutes (default: 30, 0 = off)
202
202
  /glla autoresume=on # held goals/loops auto-resume in fresh sessions (unattended rigs)
203
203
  /glla auditcap=5 # pause the goal after N consecutive auditor disapprovals (default 3, 0 = unlimited)
204
+ /glla auditfeedbackchars=16000 # auditor report returned to the executor (default 800, 0 = full report)
204
205
  /glla autoaccept=on # drafts ACTIVATE without the Confirm dialog (unattended rigs)
205
206
  /glla project tokenlimit=500 # rare per-project override
206
207
  ```
@@ -211,6 +212,13 @@ auditor can't auth it — you're told once (info level) with the fix:
211
212
  `/glla model=provider/id`, set once, rarely touched again. The plugin never
212
213
  picks a model itself. Thinking follows the session too (floor `high`).
213
214
 
215
+ On disapproval, the executor receives up to `auditFeedbackChars` characters
216
+ from the auditor report (default 800, preserving the previous behavior).
217
+ Increase it for multi-item `<evidence>` blocks and raw verification output,
218
+ or set `/glla auditfeedbackchars=0` to return the full report. The complete
219
+ report continues to be stored in audit history and is available through
220
+ `/goal status`.
221
+
214
222
  `autoaccept=on` skips BOTH the Confirm dialog and the drafting interview
215
223
  floor — every `propose_*` draft (goal, list batch, loop, task list)
216
224
  activates the moment the agent proposes it, with a notification and a
@@ -320,4 +328,4 @@ pi install .
320
328
 
321
329
  ## License
322
330
 
323
- MIT
331
+ MIT
@@ -287,6 +287,17 @@ export function mergeSettings<T extends Record<string, unknown>>(base: T, ...lay
287
287
  return out as T;
288
288
  }
289
289
 
290
+ /** Backward-compatible default for executor-visible auditor feedback. */
291
+ export const DEFAULT_AUDIT_FEEDBACK_CHARS = 800;
292
+
293
+ /**
294
+ * Bound the auditor report returned to the executor after disapproval.
295
+ * A limit of 0 explicitly means "show the full report".
296
+ */
297
+ export function auditFeedbackExcerpt(output: string, maxChars: number): string {
298
+ return maxChars === 0 ? output : output.slice(0, maxChars);
299
+ }
300
+
290
301
  export interface ListItem {
291
302
  id: string;
292
303
  objective: string;
@@ -101,7 +101,12 @@ export function buildStatusText(state: State, audit?: AuditDisplayProgress | nul
101
101
  return `glla: ${paint(theme, pauseIsError(g) ? "error" : "warning", label)}`;
102
102
  }
103
103
  if (g.status === "active") {
104
- const queue = state.list?.length ? ` · list ${state.list.length}` : "";
104
+ // v0.24.7: list policy gets its own wording a queue item is not a goal.
105
+ // Before: "glla: list ● 3m 19s · list 29" (policy label AND queue counter
106
+ // both said "list"). After: "glla: list ● 3m 19s · 29 queued". Goal
107
+ // policy keeps the bare "list N" suffix (no duplication there).
108
+ const n = state.list?.length ?? 0;
109
+ const queue = n === 0 ? "" : g.policy === "list" ? ` · ${n} queued` : ` · list ${n}`;
105
110
  const tasks = g.taskList ? ` ${countDone(g)}/${countTotal(g)} tasks ·` : "";
106
111
  return `glla: ${g.policy} ${paint(theme, "success", "●")}${tasks} ${fmtElapsed(now - Date.parse(g.createdAt))}${queue}`;
107
112
  }
@@ -158,12 +163,16 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
158
163
  ? paint(theme, "accent", "⟡")
159
164
  : paint(theme, "success", "●");
160
165
  const head = `${icon} ${truncate(g.objective.replace(/\s+/g, " "), budgetFor(width, 3, 64))}`;
166
+ // v0.24.7: a list item is named as such and points at /list — before,
167
+ // the widget called it "active" and hinted "/goal status", reading as if
168
+ // queue work were a standalone goal.
169
+ const isList = g.policy === "list";
161
170
  const statusWord = g.status === "active" ? paint(theme, "success", "active") : g.status;
162
171
  // Token segment only when a budget is set (v0.22.0): the guard is opt-in,
163
172
  // and "0/0 tok" carried no information when off.
164
173
  const tokenLimit = g.usage?.tokensLimit ?? 0;
165
174
  const tokens = tokenLimit > 0 ? ` · ${paint(theme, "dim", `${fmtTokens(g.usage?.tokensUsed ?? 0)}/${fmtTokens(tokenLimit)} tok`)}` : "";
166
- const lines = [head, `├─ ${statusWord} · ${fmtElapsed(now - Date.parse(g.createdAt))}${tokens}`];
175
+ const lines = [head, `├─ ${isList ? "list item · " : ""}${statusWord} · ${fmtElapsed(now - Date.parse(g.createdAt))}${tokens}`];
167
176
  if (g.status === "auditing") {
168
177
  lines.push(`├─ auditor: ${audit?.label ?? "running"}${audit?.currentTool ? ` · ${truncate(audit.currentTool, 30)}` : ""}`);
169
178
  if (audit?.elapsedMs) lines.push(`└─ ${paint(theme, "dim", `${fmtElapsed(audit.elapsedMs)} in isolated session`)}`);
@@ -178,7 +187,10 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
178
187
  const next = nextPending(g);
179
188
  if (next) lines.push(`├─ next: ${truncate(next, budgetFor(width, 9, 56))}`);
180
189
  const queue = state.list?.length ?? 0;
181
- lines.push(`└─ ${paint(theme, "dim", `${queue > 0 ? `list ${queue} · ` : ""}/goal status · /glla`)}`);
190
+ const footer = isList
191
+ ? `${queue > 0 ? `${queue} queued · ` : ""}/list · /glla`
192
+ : `${queue > 0 ? `list ${queue} · ` : ""}/goal status · /glla`;
193
+ lines.push(`└─ ${paint(theme, "dim", footer)}`);
182
194
  return lines;
183
195
  }
184
196
 
@@ -30,6 +30,8 @@ import {
30
30
  archivedGoalPath,
31
31
  buildTaskList,
32
32
  buildTaskSummary,
33
+ auditFeedbackExcerpt,
34
+ DEFAULT_AUDIT_FEEDBACK_CHARS,
33
35
  DEFAULT_TOKEN_LIMIT,
34
36
  mergeSettings,
35
37
  parseListImport,
@@ -595,6 +597,8 @@ async function cmdStatus(ctx: ExtensionContext): Promise<void> {
595
597
  const lines = [
596
598
  `[${g.id}] ${statusLabel(g.status)}`,
597
599
  `Objective: ${g.objective}`,
600
+ // v0.24.7: name WHERE the work came from — a queue item is not a goal.
601
+ ...(g.policy === "list" ? [`Source: /list queue (${listQueue().length} waiting) — /list to manage`] : []),
598
602
  `Auto-continue: ${g.autoContinue ? "on" : "off"}`,
599
603
  `Iteration: ${iterationCounter}`,
600
604
  `Tokens: ${(g.usage?.tokensUsed ?? 0).toLocaleString()}${(g.usage?.tokensLimit ?? 0) > 0 ? ` / ${(g.usage!.tokensLimit).toLocaleString()}` : " (no cap — /glla tokenlimit=<n> to set)"}`,
@@ -1664,7 +1668,19 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1664
1668
  // goal the auditor can NEVER approve used to re-continue forever.
1665
1669
  // auditCap consecutive disapprovals → pause + notify, bounded and
1666
1670
  // surfaced like every other stop in this stack.
1667
- const auditCap = loadSettings(ctx.cwd).auditCap ?? 3;
1671
+ const auditCap = settings.auditCap ?? 3;
1672
+ const configuredFeedbackChars = settings.auditFeedbackChars;
1673
+ const auditFeedbackChars = Number.isInteger(configuredFeedbackChars) && configuredFeedbackChars! >= 0
1674
+ ? configuredFeedbackChars!
1675
+ : DEFAULT_AUDIT_FEEDBACK_CHARS;
1676
+ const auditFeedback = auditFeedbackExcerpt(result.output, auditFeedbackChars);
1677
+ const auditFeedbackIsFull = auditFeedbackChars === 0 || result.output.length <= auditFeedbackChars;
1678
+ const auditFeedbackLabel = auditFeedbackIsFull
1679
+ ? "full report"
1680
+ : `first ${auditFeedbackChars} chars`;
1681
+ const auditFeedbackTruncationHint = auditFeedbackIsFull
1682
+ ? ""
1683
+ : `\n\nReport truncated at the configured limit. /goal status shows the full report; change future feedback with /glla auditfeedbackchars=N (0 = full report).`;
1668
1684
  const trailingDisapprovals = countTrailingDisapprovals(history);
1669
1685
  if (auditCap > 0 && trailingDisapprovals >= auditCap) {
1670
1686
  updateGoal({
@@ -1679,7 +1695,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1679
1695
  return {
1680
1696
  content: [{
1681
1697
  type: "text",
1682
- text: `The auditor has now disapproved ${trailingDisapprovals} times in a row (cap ${auditCap}). The goal is PAUSED — continuing to re-attempt without addressing the pattern wastes tokens. Latest report (first 800 chars):\n${result.output.slice(0, 800)}\n\nDo not call complete_goal again. Summarize the repeated objections for the user and ask how to proceed (/goal status shows all reports; /goal resume resumes).`,
1698
+ text: `The auditor has now disapproved ${trailingDisapprovals} times in a row (cap ${auditCap}). The goal is PAUSED — continuing to re-attempt without addressing the pattern wastes tokens. Latest report (${auditFeedbackLabel}):\n${auditFeedback}\n\nDo not call complete_goal again. Summarize the repeated objections for the user and ask how to proceed (/goal status shows all reports; /goal resume resumes).`,
1683
1699
  }],
1684
1700
  details: {},
1685
1701
  };
@@ -1694,7 +1710,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1694
1710
  return {
1695
1711
  content: [{
1696
1712
  type: "text",
1697
- text: `Auditor disapproved. Report (first 800 chars):\n${result.output.slice(0, 800)}${noContractHint}`,
1713
+ text: `Auditor disapproved. Report (${auditFeedbackLabel}):\n${auditFeedback}${auditFeedbackTruncationHint}${noContractHint}`,
1698
1714
  }],
1699
1715
  details: {},
1700
1716
  };
@@ -2265,6 +2281,9 @@ interface Settings {
2265
2281
  autoResume?: boolean;
2266
2282
  /** v0.24.2: pause the goal after N consecutive auditor disapprovals (0 = unlimited). Default 3. */
2267
2283
  auditCap?: number;
2284
+ /** Maximum auditor-report characters returned to the executor after a
2285
+ * disapproval (0 = full report). Default 800. */
2286
+ auditFeedbackChars?: number;
2268
2287
  /** on → propose_* drafts activate WITHOUT the Confirm dialog and the
2269
2288
  * interview floor is skipped — the seed carries the intent (unattended
2270
2289
  * rigs). Default off: nothing activates before the user confirms. */
@@ -2291,6 +2310,7 @@ const DEFAULT_SETTINGS: Settings = {
2291
2310
  // v0.24.6: subagents inherit the session model by default — one quota
2292
2311
  // pool, no surprise 403s from a pinned default agent's provider.
2293
2312
  subagentModelStrategy: "inherit-parent",
2313
+ auditFeedbackChars: DEFAULT_AUDIT_FEEDBACK_CHARS,
2294
2314
  };
2295
2315
 
2296
2316
  // Two-tier config (v0.7.0): GLOBAL is the normal home — you set things once
@@ -2328,7 +2348,7 @@ function settingsProvenance(cwd: string): Record<keyof Settings, { value: unknow
2328
2348
  const glob = readSettingsFile(globalSettingsPath());
2329
2349
  const effective = loadSettings(cwd);
2330
2350
  const out: Record<string, { value: unknown; source: "project" | "global" | "default" }> = {};
2331
- const keys: Array<keyof Settings> = ["auditorModel", "auditorThinkingLevel", "notifyCmd", "tokenLimit", "wedgeAlertMinutes", "autoResume", "autoAcceptDrafts", "auditCap"];
2351
+ const keys: Array<keyof Settings> = ["auditorModel", "auditorThinkingLevel", "notifyCmd", "tokenLimit", "wedgeAlertMinutes", "autoResume", "autoAcceptDrafts", "auditCap", "auditFeedbackChars", "subagentModelStrategy", "subagentModelOverrides"];
2332
2352
  for (const k of keys) {
2333
2353
  if ((proj as Record<string, unknown>)[k] !== undefined) out[k] = { value: (proj as any)[k], source: "project" };
2334
2354
  else if ((glob as Record<string, unknown>)[k] !== undefined) out[k] = { value: (glob as any)[k], source: "global" };
@@ -2430,6 +2450,7 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
2430
2450
  `Wedge alert minutes — ${show("wedgeAlertMinutes", `(${WEDGE_ALERT_DEFAULT_MINUTES}m default)`)}`,
2431
2451
  `Subagent model strategy — ${show("subagentModelStrategy", "(inherit-parent)")}`,
2432
2452
  `Subagent Explore model pin — ${loadSettings(ctx.cwd).subagentModelOverrides?.Explore ?? "(follows strategy)"}`,
2453
+ `Audit feedback characters — ${show("auditFeedbackChars", `(${DEFAULT_AUDIT_FEEDBACK_CHARS} default)`)}`,
2433
2454
  "Done",
2434
2455
  ],
2435
2456
  );
@@ -2483,6 +2504,15 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
2483
2504
  saveSettings("global", ctx.cwd, { subagentModelOverrides: Object.keys(next).length > 0 ? next : undefined });
2484
2505
  ctx.ui.notify("Explore model pin saved — applies to NEW pi sessions.", "info");
2485
2506
  }
2507
+ } else if (choice.startsWith("Audit feedback")) {
2508
+ const v = await ctx.ui.input("Auditor feedback returned to the executor (characters)", `non-negative integer; 0 = full report, empty = default ${DEFAULT_AUDIT_FEEDBACK_CHARS}`);
2509
+ if (v !== undefined) {
2510
+ const raw = v.trim();
2511
+ const n = Number(raw);
2512
+ if (/^\d+$/.test(raw) && Number.isSafeInteger(n)) saveSettings("global", ctx.cwd, { auditFeedbackChars: n });
2513
+ else if (!v.trim()) saveSettings("global", ctx.cwd, { auditFeedbackChars: undefined });
2514
+ else ctx.ui.notify(`Not a non-negative integer: ${v}`, "warning");
2515
+ }
2486
2516
  }
2487
2517
  } catch {
2488
2518
  return;
@@ -2498,6 +2528,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
2498
2528
  // /glla notify='cmd $1' write to GLOBAL config
2499
2529
  // /glla tokenlimit=2000000 write to GLOBAL config
2500
2530
  // /glla wedgealert=30 hung-command alert minutes (0=off, unset=30)
2531
+ // /glla auditfeedbackchars=800 executor-visible disapproval report chars (0=full)
2501
2532
  // /glla project model=... write to PROJECT override (rare)
2502
2533
  // /glla model=unset remove key (from global; project model=unset for project)
2503
2534
  const trimmed = args.trim();
@@ -2522,6 +2553,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
2522
2553
  fmt("autoResume", "autoResume"),
2523
2554
  fmt("autoAcceptDrafts", "autoAccept"),
2524
2555
  fmt("auditCap", "auditCap"),
2556
+ fmt("auditFeedbackChars", "auditFeedbackChars"),
2525
2557
  `\nglobal: ${globalSettingsPath()}`,
2526
2558
  `project: ${projectSettingsPath(ctx.cwd)}`,
2527
2559
  `Set with: /glla key=value (global) · /glla project key=value (project override)`,
@@ -2607,6 +2639,19 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
2607
2639
  ctx.ui.notify(`auditcap must be a non-negative integer (0 = unlimited), got: ${value}`, "warning");
2608
2640
  }
2609
2641
  }
2642
+ } else if (key === "auditfeedbackchars") {
2643
+ if (["unset", "default"].includes(value)) {
2644
+ patch.auditFeedbackChars = undefined;
2645
+ changed = true;
2646
+ } else {
2647
+ const n = Number(value);
2648
+ if (/^\d+$/.test(value) && Number.isSafeInteger(n)) {
2649
+ patch.auditFeedbackChars = n;
2650
+ changed = true;
2651
+ } else {
2652
+ ctx.ui.notify(`auditfeedbackchars must be a non-negative integer (0 = full report), got: ${value}`, "warning");
2653
+ }
2654
+ }
2610
2655
  } else if (key === "thinking" || key === "auditorthinkinglevel") {
2611
2656
  if (["off", "minimal", "low", "medium", "high", "xhigh"].includes(value)) {
2612
2657
  patch.auditorThinkingLevel = value as Settings["auditorThinkingLevel"];
@@ -2617,13 +2662,13 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
2617
2662
  }
2618
2663
  }
2619
2664
  if (!changed) {
2620
- ctx.ui.notify("Nothing changed. Use key=value (model, thinking, notify, tokenlimit, autoresume), optionally prefixed with 'project'.", "info");
2665
+ ctx.ui.notify("Nothing changed. Use key=value (model, thinking, notify, tokenlimit, autoresume, auditcap, auditfeedbackchars), optionally prefixed with 'project'.", "info");
2621
2666
  return;
2622
2667
  }
2623
2668
  saveSettings(scope, ctx.cwd, patch);
2624
2669
  const effective = loadSettings(ctx.cwd);
2625
2670
  ctx.ui.notify(
2626
- `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 === true ? "on" : "off"}\n` +
2671
+ `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 === true ? "on" : "off"} auditFeedbackChars=${effective.auditFeedbackChars ?? DEFAULT_AUDIT_FEEDBACK_CHARS}${(effective.auditFeedbackChars ?? DEFAULT_AUDIT_FEEDBACK_CHARS) === 0 ? " (full report)" : ""}\n` +
2627
2672
  `Note: the auditor runs without extensions — it must be a built-in provider, not an extension-registered one.`,
2628
2673
  "info",
2629
2674
  );
@@ -2739,6 +2784,7 @@ export default function (pi: ExtensionAPI): void {
2739
2784
  ["tokenlimit=", "per-goal token budget (0 = off): /glla tokenlimit=2000000"],
2740
2785
  ["autoresume=", "on: auto-resume held goals/loops in fresh sessions"],
2741
2786
  ["auditcap=", "N: pause goal after N consecutive auditor disapprovals (default 3, 0 = unlimited)"],
2787
+ ["auditfeedbackchars=", `executor-visible disapproval report characters (default ${DEFAULT_AUDIT_FEEDBACK_CHARS}, 0 = full report)`],
2742
2788
  ["autoaccept=", "on: drafts activate without the Confirm dialog (unattended rigs)"],
2743
2789
  ["project", "write a project override: /glla project key=value"],
2744
2790
  ]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.24.6",
3
+ "version": "0.24.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",