pi-goal-list-loop-audit 0.24.7 → 0.24.9
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 +8 -1
- package/extensions/goal-loop-core.ts +17 -0
- package/extensions/loops/goal.ts +50 -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=800 # cap the executor-visible auditor report (default 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,12 @@ 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 the full auditor report by default
|
|
216
|
+
(`auditFeedbackChars=0`, since v0.24.9 — a truncated report loses exactly the
|
|
217
|
+
actionable tail of multi-item `<evidence>` blocks). Set a positive
|
|
218
|
+
`auditFeedbackChars` to cap it. The complete report is always stored in audit
|
|
219
|
+
history and is available through `/goal status` regardless.
|
|
220
|
+
|
|
214
221
|
`autoaccept=on` skips BOTH the Confirm dialog and the drafting interview
|
|
215
222
|
floor — every `propose_*` draft (goal, list batch, loop, task list)
|
|
216
223
|
activates the moment the agent proposes it, with a notification and a
|
|
@@ -320,4 +327,4 @@ pi install .
|
|
|
320
327
|
|
|
321
328
|
## License
|
|
322
329
|
|
|
323
|
-
MIT
|
|
330
|
+
MIT
|
|
@@ -287,6 +287,23 @@ export function mergeSettings<T extends Record<string, unknown>>(base: T, ...lay
|
|
|
287
287
|
return out as T;
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Default for executor-visible auditor feedback. 0 = no cap: the executor
|
|
292
|
+
* gets the FULL disapproval report (v0.24.9 — truncating by default cut
|
|
293
|
+
* exactly the actionable tail of multi-item <evidence> blocks; a few KB of
|
|
294
|
+
* report is negligible next to a wasted re-attempt). Set a positive
|
|
295
|
+
* auditFeedbackChars to cap.
|
|
296
|
+
*/
|
|
297
|
+
export const DEFAULT_AUDIT_FEEDBACK_CHARS = 0;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Bound the auditor report returned to the executor after disapproval.
|
|
301
|
+
* A limit of 0 explicitly means "show the full report".
|
|
302
|
+
*/
|
|
303
|
+
export function auditFeedbackExcerpt(output: string, maxChars: number): string {
|
|
304
|
+
return maxChars === 0 ? output : output.slice(0, maxChars);
|
|
305
|
+
}
|
|
306
|
+
|
|
290
307
|
export interface ListItem {
|
|
291
308
|
id: string;
|
|
292
309
|
objective: string;
|
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,
|
|
@@ -1666,7 +1668,19 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1666
1668
|
// goal the auditor can NEVER approve used to re-continue forever.
|
|
1667
1669
|
// auditCap consecutive disapprovals → pause + notify, bounded and
|
|
1668
1670
|
// surfaced like every other stop in this stack.
|
|
1669
|
-
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).`;
|
|
1670
1684
|
const trailingDisapprovals = countTrailingDisapprovals(history);
|
|
1671
1685
|
if (auditCap > 0 && trailingDisapprovals >= auditCap) {
|
|
1672
1686
|
updateGoal({
|
|
@@ -1681,7 +1695,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1681
1695
|
return {
|
|
1682
1696
|
content: [{
|
|
1683
1697
|
type: "text",
|
|
1684
|
-
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).`,
|
|
1685
1699
|
}],
|
|
1686
1700
|
details: {},
|
|
1687
1701
|
};
|
|
@@ -1696,7 +1710,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1696
1710
|
return {
|
|
1697
1711
|
content: [{
|
|
1698
1712
|
type: "text",
|
|
1699
|
-
text: `Auditor disapproved. Report (
|
|
1713
|
+
text: `Auditor disapproved. Report (${auditFeedbackLabel}):\n${auditFeedback}${auditFeedbackTruncationHint}${noContractHint}`,
|
|
1700
1714
|
}],
|
|
1701
1715
|
details: {},
|
|
1702
1716
|
};
|
|
@@ -2267,6 +2281,9 @@ interface Settings {
|
|
|
2267
2281
|
autoResume?: boolean;
|
|
2268
2282
|
/** v0.24.2: pause the goal after N consecutive auditor disapprovals (0 = unlimited). Default 3. */
|
|
2269
2283
|
auditCap?: number;
|
|
2284
|
+
/** Maximum auditor-report characters returned to the executor after a
|
|
2285
|
+
* disapproval (0 = full report). Default 0 (full report). */
|
|
2286
|
+
auditFeedbackChars?: number;
|
|
2270
2287
|
/** on → propose_* drafts activate WITHOUT the Confirm dialog and the
|
|
2271
2288
|
* interview floor is skipped — the seed carries the intent (unattended
|
|
2272
2289
|
* rigs). Default off: nothing activates before the user confirms. */
|
|
@@ -2293,6 +2310,7 @@ const DEFAULT_SETTINGS: Settings = {
|
|
|
2293
2310
|
// v0.24.6: subagents inherit the session model by default — one quota
|
|
2294
2311
|
// pool, no surprise 403s from a pinned default agent's provider.
|
|
2295
2312
|
subagentModelStrategy: "inherit-parent",
|
|
2313
|
+
auditFeedbackChars: DEFAULT_AUDIT_FEEDBACK_CHARS,
|
|
2296
2314
|
};
|
|
2297
2315
|
|
|
2298
2316
|
// Two-tier config (v0.7.0): GLOBAL is the normal home — you set things once
|
|
@@ -2330,7 +2348,7 @@ function settingsProvenance(cwd: string): Record<keyof Settings, { value: unknow
|
|
|
2330
2348
|
const glob = readSettingsFile(globalSettingsPath());
|
|
2331
2349
|
const effective = loadSettings(cwd);
|
|
2332
2350
|
const out: Record<string, { value: unknown; source: "project" | "global" | "default" }> = {};
|
|
2333
|
-
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"];
|
|
2334
2352
|
for (const k of keys) {
|
|
2335
2353
|
if ((proj as Record<string, unknown>)[k] !== undefined) out[k] = { value: (proj as any)[k], source: "project" };
|
|
2336
2354
|
else if ((glob as Record<string, unknown>)[k] !== undefined) out[k] = { value: (glob as any)[k], source: "global" };
|
|
@@ -2432,6 +2450,7 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
|
|
|
2432
2450
|
`Wedge alert minutes — ${show("wedgeAlertMinutes", `(${WEDGE_ALERT_DEFAULT_MINUTES}m default)`)}`,
|
|
2433
2451
|
`Subagent model strategy — ${show("subagentModelStrategy", "(inherit-parent)")}`,
|
|
2434
2452
|
`Subagent Explore model pin — ${loadSettings(ctx.cwd).subagentModelOverrides?.Explore ?? "(follows strategy)"}`,
|
|
2453
|
+
`Audit feedback characters — ${show("auditFeedbackChars", "(full report)")}`,
|
|
2435
2454
|
"Done",
|
|
2436
2455
|
],
|
|
2437
2456
|
);
|
|
@@ -2485,6 +2504,15 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
|
|
|
2485
2504
|
saveSettings("global", ctx.cwd, { subagentModelOverrides: Object.keys(next).length > 0 ? next : undefined });
|
|
2486
2505
|
ctx.ui.notify("Explore model pin saved — applies to NEW pi sessions.", "info");
|
|
2487
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 cap; 0 or empty = full report (default)");
|
|
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
|
+
}
|
|
2488
2516
|
}
|
|
2489
2517
|
} catch {
|
|
2490
2518
|
return;
|
|
@@ -2500,6 +2528,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2500
2528
|
// /glla notify='cmd $1' write to GLOBAL config
|
|
2501
2529
|
// /glla tokenlimit=2000000 write to GLOBAL config
|
|
2502
2530
|
// /glla wedgealert=30 hung-command alert minutes (0=off, unset=30)
|
|
2531
|
+
// /glla auditfeedbackchars=800 cap executor-visible disapproval report (0=full, the default)
|
|
2503
2532
|
// /glla project model=... write to PROJECT override (rare)
|
|
2504
2533
|
// /glla model=unset remove key (from global; project model=unset for project)
|
|
2505
2534
|
const trimmed = args.trim();
|
|
@@ -2524,6 +2553,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2524
2553
|
fmt("autoResume", "autoResume"),
|
|
2525
2554
|
fmt("autoAcceptDrafts", "autoAccept"),
|
|
2526
2555
|
fmt("auditCap", "auditCap"),
|
|
2556
|
+
fmt("auditFeedbackChars", "auditFeedbackChars"),
|
|
2527
2557
|
`\nglobal: ${globalSettingsPath()}`,
|
|
2528
2558
|
`project: ${projectSettingsPath(ctx.cwd)}`,
|
|
2529
2559
|
`Set with: /glla key=value (global) · /glla project key=value (project override)`,
|
|
@@ -2609,6 +2639,19 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2609
2639
|
ctx.ui.notify(`auditcap must be a non-negative integer (0 = unlimited), got: ${value}`, "warning");
|
|
2610
2640
|
}
|
|
2611
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
|
+
}
|
|
2612
2655
|
} else if (key === "thinking" || key === "auditorthinkinglevel") {
|
|
2613
2656
|
if (["off", "minimal", "low", "medium", "high", "xhigh"].includes(value)) {
|
|
2614
2657
|
patch.auditorThinkingLevel = value as Settings["auditorThinkingLevel"];
|
|
@@ -2619,13 +2662,13 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2619
2662
|
}
|
|
2620
2663
|
}
|
|
2621
2664
|
if (!changed) {
|
|
2622
|
-
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");
|
|
2623
2666
|
return;
|
|
2624
2667
|
}
|
|
2625
2668
|
saveSettings(scope, ctx.cwd, patch);
|
|
2626
2669
|
const effective = loadSettings(ctx.cwd);
|
|
2627
2670
|
ctx.ui.notify(
|
|
2628
|
-
`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` +
|
|
2629
2672
|
`Note: the auditor runs without extensions — it must be a built-in provider, not an extension-registered one.`,
|
|
2630
2673
|
"info",
|
|
2631
2674
|
);
|
|
@@ -2741,6 +2784,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2741
2784
|
["tokenlimit=", "per-goal token budget (0 = off): /glla tokenlimit=2000000"],
|
|
2742
2785
|
["autoresume=", "on: auto-resume held goals/loops in fresh sessions"],
|
|
2743
2786
|
["auditcap=", "N: pause goal after N consecutive auditor disapprovals (default 3, 0 = unlimited)"],
|
|
2787
|
+
["auditfeedbackchars=", "cap on executor-visible disapproval report chars (0 = full report, the default)"],
|
|
2744
2788
|
["autoaccept=", "on: drafts activate without the Confirm dialog (unattended rigs)"],
|
|
2745
2789
|
["project", "write a project override: /glla project key=value"],
|
|
2746
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.9",
|
|
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",
|