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 +9 -1
- package/extensions/goal-loop-core.ts +11 -0
- package/extensions/goal-loop-display.ts +15 -3
- package/extensions/loops/goal.ts +52 -6
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -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 =
|
|
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 (
|
|
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 (
|
|
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.
|
|
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",
|