pi-goal-list-loop-audit 0.28.25 → 0.28.27
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 -0
- package/extensions/goal-loop-core.ts +11 -2
- package/extensions/loops/goal.ts +182 -0
- package/package.json +1 -1
- package/schemas/goal.schema.json +1 -0
package/README.md
CHANGED
|
@@ -39,6 +39,7 @@ Five top-level commands — `/goal`, `/list`, `/loop`, `/glla`, `/review`:
|
|
|
39
39
|
/goal resume # resume
|
|
40
40
|
/goal cancel # abort
|
|
41
41
|
/goal decide # re-open the decision picker (v0.28.23)
|
|
42
|
+
/goal audit # run the isolated auditor on the current goal now — no agent turn (v0.28.27)
|
|
42
43
|
/goal tweak "<new objective>" # edit in place (Confirm dialog)
|
|
43
44
|
/goal archive # archived goals, newest first
|
|
44
45
|
/glla # settings UI table · /glla key=value · /glla stats · /glla audits [N|full] · /glla postaudit · /glla autoaccept=on
|
|
@@ -176,6 +176,15 @@ export interface Goal {
|
|
|
176
176
|
* At 3 the goal pauses loudly — a broken auditor model must not spin a
|
|
177
177
|
* silent retry-forever loop. Cleared on any real auditor run. */
|
|
178
178
|
auditInfraStreak?: number;
|
|
179
|
+
/** v0.28.26: the completion claim captured when an audit attempt is
|
|
180
|
+
* quota-blocked. The quota retry re-runs the AUDITOR directly with this
|
|
181
|
+
* stored claim instead of re-engaging the agent — re-engaging produced a
|
|
182
|
+
* hallucinated-closure repetition loop in the field (π-games 2026-07-29:
|
|
183
|
+
* the agent concluded the goal was closed, stopped calling complete_goal,
|
|
184
|
+
* and repeated the same essay until the stall brake fired). Cleared when
|
|
185
|
+
* the retry resolves. Only consumed while paused with an "auditor quota:"
|
|
186
|
+
* reason, so a stale value is unreachable by construction. */
|
|
187
|
+
pendingCompletion?: { completionSummary?: string; verificationSummary?: string; at: string };
|
|
179
188
|
/** v0.25.0 (contract item 22): auditor objections extracted as TODOs when
|
|
180
189
|
* aggressiveMode keeps the goal active past the disapproval cap. Rendered
|
|
181
190
|
* into every continuation prompt until the next audit clears them. */
|
|
@@ -203,9 +212,9 @@ export interface Goal {
|
|
|
203
212
|
export type GoalRoute =
|
|
204
213
|
| { kind: "draft" }
|
|
205
214
|
| { kind: "set"; text: string }
|
|
206
|
-
| { kind: "sub"; name: "status" | "pause" | "resume" | "cancel" | "decide" | "tweak" | "archive" | "start"; rest: string };
|
|
215
|
+
| { kind: "sub"; name: "status" | "pause" | "resume" | "cancel" | "decide" | "audit" | "tweak" | "archive" | "start"; rest: string };
|
|
207
216
|
|
|
208
|
-
const GOAL_EXACT_SUBS = new Set(["status", "pause", "resume", "cancel", "decide"]);
|
|
217
|
+
const GOAL_EXACT_SUBS = new Set(["status", "pause", "resume", "cancel", "decide", "audit"]);
|
|
209
218
|
const GOAL_ARG_SUBS = new Set(["tweak", "archive", "start"]);
|
|
210
219
|
|
|
211
220
|
export function routeGoalArgs(raw: string): GoalRoute {
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -568,6 +568,12 @@ function heartbeatTick(): void {
|
|
|
568
568
|
// machinery below stays quiet for 3 minutes while the replaced session
|
|
569
569
|
// settles (latch watchdog, wedge alert, refire counting all resume after).
|
|
570
570
|
if (Date.now() < compactionGraceUntil) return;
|
|
571
|
+
// v0.28.27: a stale (session-replaced) handle can never land a send —
|
|
572
|
+
// the terminal warning already fired once. ALL stall machinery stays
|
|
573
|
+
// quiet from here on: refiring into a dead process is misleading, and
|
|
574
|
+
// worse, the stall escalation would PAUSE the goal — silently cancelling
|
|
575
|
+
// the interruptedAt → auto-resume-on-restart promise the footer shows.
|
|
576
|
+
if (extensionApiStale) return;
|
|
571
577
|
// v0.26.5: pending-latch watchdog — a queued continuation whose turn
|
|
572
578
|
// trigger was dropped (field-observed post-compaction: continuation
|
|
573
579
|
// ACCEPTED at compact+0s, then 22 minutes of silence). The stuck latch
|
|
@@ -947,6 +953,148 @@ function archiveCurrentGoal(ctx: ExtensionContext, status: Status, stopReason?:
|
|
|
947
953
|
}
|
|
948
954
|
}
|
|
949
955
|
|
|
956
|
+
/**
|
|
957
|
+
* v0.28.26: quota-window retry for a STORED completion claim. The auditor
|
|
958
|
+
* was quota-blocked at complete_goal time; the claim (completionSummary +
|
|
959
|
+
* verificationSummary) was persisted on the goal, and when the quota window
|
|
960
|
+
* elapses we re-run the AUDITOR directly — no agent turn. Re-engaging the
|
|
961
|
+
* agent to re-submit an unchanged claim produced a hallucinated-closure
|
|
962
|
+
* repetition loop in the field (π-games: the model concluded the goal was
|
|
963
|
+
* closed, repeated the same essay 4×+, stormed continuations, compacted 14×
|
|
964
|
+
* in 35 minutes, and burned the stall brake).
|
|
965
|
+
*
|
|
966
|
+
* Outcomes: approved → close + cascade (archiveCurrentGoal handles list
|
|
967
|
+
* advance + reviewer); quota again → re-pause with a fresh scheduled retry
|
|
968
|
+
* (claim preserved); anything else (disapproved, impossible, non-quota
|
|
969
|
+
* infra) → hand back to the agent: resume active + continuation, verdict
|
|
970
|
+
* durable in auditHistory.
|
|
971
|
+
*/
|
|
972
|
+
async function retryStoredCompletionAudit(ctx: ExtensionContext, origin: "quota-retry" | "manual" = "quota-retry"): Promise<void> {
|
|
973
|
+
const goal = state.goal;
|
|
974
|
+
if (!goal?.pendingCompletion) return;
|
|
975
|
+
if (completionAuditInFlight) return;
|
|
976
|
+
const liveCtx = freshCtx() ?? ctx;
|
|
977
|
+
const claim = goal.pendingCompletion;
|
|
978
|
+
updateGoal({ status: "auditing" }, liveCtx);
|
|
979
|
+
appendLedger(liveCtx.cwd, "goal_resumed", { via: origin === "manual" ? "manual-audit" : "quota-retry-direct-audit" });
|
|
980
|
+
liveCtx.ui.notify(origin === "manual"
|
|
981
|
+
? "Manual /goal audit — running the isolated auditor now (no agent turn needed)."
|
|
982
|
+
: "Auditor quota window elapsed — retrying the audit with your stored completion claim (no agent turn needed).", "info");
|
|
983
|
+
const settings = loadSettings(liveCtx.cwd);
|
|
984
|
+
const { model: auditorModel, error: modelError, via } = resolveAuditorModel(liveCtx, settings.auditorModel);
|
|
985
|
+
if (modelError) liveCtx.ui.notify(`Auditor model issue: ${modelError}`, "warning");
|
|
986
|
+
latestAuditProgress = { label: "quota-retry", lastEventAt: Date.now() };
|
|
987
|
+
completionAuditInFlight = true;
|
|
988
|
+
const auditStartMs = Date.now();
|
|
989
|
+
let result: Awaited<ReturnType<typeof runGoalCompletionAuditor>>;
|
|
990
|
+
try {
|
|
991
|
+
({ result } = await runWithInfraRetry(
|
|
992
|
+
() =>
|
|
993
|
+
runGoalCompletionAuditor({
|
|
994
|
+
ctx: liveCtx,
|
|
995
|
+
goal: state.goal!,
|
|
996
|
+
completionSummary: claim.completionSummary,
|
|
997
|
+
verificationSummary: claim.verificationSummary,
|
|
998
|
+
model: auditorModel,
|
|
999
|
+
thinkingLevel: settings.auditorThinkingLevel ?? getSessionThinkingLevel(),
|
|
1000
|
+
onProgress: (progress) => {
|
|
1001
|
+
latestAuditProgress = { currentTool: progress.currentTool, label: progress.label, elapsedMs: progress.elapsedMs, lastEventAt: Date.now() };
|
|
1002
|
+
refreshUI(liveCtx);
|
|
1003
|
+
},
|
|
1004
|
+
}),
|
|
1005
|
+
{ onRetry: (err) => appendLedger(liveCtx.cwd, "audit_infra_retry", { goalId: state.goal?.id, error: err.slice(0, 200) }) },
|
|
1006
|
+
));
|
|
1007
|
+
} finally {
|
|
1008
|
+
completionAuditInFlight = false;
|
|
1009
|
+
latestAuditProgress = null;
|
|
1010
|
+
}
|
|
1011
|
+
if (!state.goal) return; // aborted mid-audit
|
|
1012
|
+
|
|
1013
|
+
// Record the run in history (same compact shape as the tool path).
|
|
1014
|
+
const auditorRan = result.output.trim().length > 0;
|
|
1015
|
+
const history = state.goal.auditHistory ?? [];
|
|
1016
|
+
if (auditorRan) {
|
|
1017
|
+
result.output = stripThinkBlocks(result.output);
|
|
1018
|
+
history.push({
|
|
1019
|
+
at: nowIso(),
|
|
1020
|
+
approved: result.approved,
|
|
1021
|
+
disapproved: result.disapproved,
|
|
1022
|
+
impossible: result.impossible,
|
|
1023
|
+
impossibleReason: result.impossibleReason,
|
|
1024
|
+
model: result.model,
|
|
1025
|
+
thinkingLevel: result.thinkingLevel,
|
|
1026
|
+
report: result.output,
|
|
1027
|
+
error: result.error,
|
|
1028
|
+
regressionShieldPassed: result.regressionShieldPassed,
|
|
1029
|
+
regressionShieldMissing: result.regressionShieldMissing,
|
|
1030
|
+
durationMs: Date.now() - auditStartMs,
|
|
1031
|
+
} as any);
|
|
1032
|
+
if (history.length > 20) history.splice(0, history.length - 20);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (result.approved) {
|
|
1036
|
+
updateGoal({ auditHistory: history, pendingCompletion: undefined }, liveCtx);
|
|
1037
|
+
const objective = state.goal.objective;
|
|
1038
|
+
archiveCurrentGoal(liveCtx, "complete", `auditor ${result.model} approved (${origin})`);
|
|
1039
|
+
liveCtx.ui.notify(`Goal complete — auditor ${result.model} approved${origin === "manual" ? " on /goal audit" : " on the quota retry"}.`, "info");
|
|
1040
|
+
notifyExternal(liveCtx, `Goal complete (auditor approved, ${origin}): ${objective.slice(0, 120)}`);
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
if (result.error && !result.disapproved && isQuotaError(result.error)) {
|
|
1045
|
+
// Still quota'd — re-pause with a fresh window, claim preserved.
|
|
1046
|
+
const settingsNow = loadSettings(liveCtx.cwd);
|
|
1047
|
+
const defaultSec = (settingsNow.quotaRetryMinutes ?? DEFAULT_QUOTA_RETRY_MINUTES) * 60;
|
|
1048
|
+
const quota = parseQuotaError(result.error, defaultSec);
|
|
1049
|
+
const retryMin = Math.max(1, Math.round(quota.retryAfterSec / 60));
|
|
1050
|
+
updateGoal({
|
|
1051
|
+
status: "paused",
|
|
1052
|
+
auditHistory: history,
|
|
1053
|
+
pauseKind: "wait",
|
|
1054
|
+
pauseResumeAt: new Date(Date.now() + quota.retryAfterSec * 1000).toISOString(),
|
|
1055
|
+
pauseReason: `auditor quota: ${result.error}`,
|
|
1056
|
+
pauseSuggestedAction: `Quota auto-retry in ${retryMin}m — or /goal resume to retry now`,
|
|
1057
|
+
}, liveCtx);
|
|
1058
|
+
appendLedger(liveCtx.cwd, "goal_paused", { reason: `auditor quota: retry in ${quota.retryAfterSec}s (stored-claim retry)` });
|
|
1059
|
+
liveCtx.ui.notify(`Auditor still quota-limited — next auto-retry in ${retryMin}m (your completion claim is stored; no action needed).`, "warning");
|
|
1060
|
+
scheduleQuotaRetry(liveCtx, quota.retryAfterSec, result.error, () => {
|
|
1061
|
+
if (state.goal && state.goal.status === "paused" && (state.goal.pauseReason ?? "").startsWith("auditor quota:") && state.goal.pendingCompletion) {
|
|
1062
|
+
void retryStoredCompletionAudit(liveCtx, origin);
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Any other outcome — disapproved, impossible, non-quota infra — belongs
|
|
1069
|
+
// to the agent: resume and let the continuation drive the next step. The
|
|
1070
|
+
// verdict is durable in auditHistory + /goal status.
|
|
1071
|
+
updateGoal({
|
|
1072
|
+
status: "active",
|
|
1073
|
+
auditHistory: history,
|
|
1074
|
+
pendingCompletion: undefined,
|
|
1075
|
+
pauseReason: result.disapproved
|
|
1076
|
+
? `auditor disapproved on quota-retry — see /goal status`
|
|
1077
|
+
: result.impossible
|
|
1078
|
+
? `auditor verdict: IMPOSSIBLE on quota-retry — ${(result.impossibleReason ?? "").slice(0, 120)}`
|
|
1079
|
+
: `auditor infrastructure error on quota-retry: ${(result.error ?? "").slice(0, 120)}`,
|
|
1080
|
+
}, liveCtx);
|
|
1081
|
+
liveCtx.ui.notify(
|
|
1082
|
+
result.disapproved
|
|
1083
|
+
? `Auditor (${origin}) DISAPPROVED — resuming; the report is in /goal status.`
|
|
1084
|
+
: result.impossible
|
|
1085
|
+
? `Auditor (${origin}): goal IMPOSSIBLE — ${(result.impossibleReason ?? "").slice(0, 100)}. Resuming; consider /goal tweak.`
|
|
1086
|
+
: `Auditor (${origin}) hit an infrastructure error — resuming; re-call complete_goal when ready.`,
|
|
1087
|
+
"warning",
|
|
1088
|
+
);
|
|
1089
|
+
appendLedger(liveCtx.cwd, "quota_retry_audit_verdict", {
|
|
1090
|
+
approved: false,
|
|
1091
|
+
disapproved: result.disapproved,
|
|
1092
|
+
impossible: result.impossible,
|
|
1093
|
+
error: result.error?.slice(0, 160),
|
|
1094
|
+
});
|
|
1095
|
+
scheduleContinuation(liveCtx, true);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
950
1098
|
/**
|
|
951
1099
|
* v0.26.0: bind the reviewer to the live session. Sources for finding
|
|
952
1100
|
* extraction: the archived goal markdown + its audit reports + the
|
|
@@ -1169,6 +1317,30 @@ async function cmdGoal(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
1169
1317
|
if (!shown) ctx.ui.notify("No pending decision — the goal isn't paused on a choice (or no UI).", "info");
|
|
1170
1318
|
return;
|
|
1171
1319
|
}
|
|
1320
|
+
// v0.28.27: /goal audit — run the isolated auditor on the current goal
|
|
1321
|
+
// RIGHT NOW, without engaging the agent. The user's "the work looks
|
|
1322
|
+
// done — just verify it" handle (and the manual counterpart of the
|
|
1323
|
+
// v0.28.26 stored-claim quota retry). Seeds a synthesized claim so a
|
|
1324
|
+
// quota block falls into the same pendingCompletion retry machinery.
|
|
1325
|
+
if (route.name === "audit") {
|
|
1326
|
+
if (!state.goal) {
|
|
1327
|
+
ctx.ui.notify("No active goal — /goal audit needs a goal to verify.", "warning");
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
if (completionAuditInFlight) {
|
|
1331
|
+
ctx.ui.notify("An audit is already running…", "info");
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
updateGoal({
|
|
1335
|
+
pendingCompletion: {
|
|
1336
|
+
completionSummary: "Manual audit requested by the user via /goal audit (no agent completion claim). Verify the objective against the repo directly.",
|
|
1337
|
+
at: nowIso(),
|
|
1338
|
+
},
|
|
1339
|
+
}, ctx);
|
|
1340
|
+
appendLedger(ctx.cwd, "manual_audit_requested", { goalId: state.goal.id });
|
|
1341
|
+
void retryStoredCompletionAudit(ctx, "manual");
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1172
1344
|
if (route.name === "tweak") return cmdTweak(route.rest, ctx);
|
|
1173
1345
|
if (route.name === "archive") return cmdGoals(ctx);
|
|
1174
1346
|
// v0.16.0: /goal start <objective> — explicit skip-draft. Activates
|
|
@@ -2574,6 +2746,9 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2574
2746
|
status: "paused",
|
|
2575
2747
|
auditHistory: history,
|
|
2576
2748
|
auditInfraStreak: undefined, // quota reached the auditor — infra streak broken
|
|
2749
|
+
// v0.28.26: store the claim — the quota retry re-runs the
|
|
2750
|
+
// auditor DIRECTLY with it (no agent turn to confuse).
|
|
2751
|
+
pendingCompletion: { completionSummary: p.completionSummary, verificationSummary: p.verificationSummary, at: nowIso() },
|
|
2577
2752
|
pauseKind: "wait",
|
|
2578
2753
|
pauseResumeAt: new Date(Date.now() + quota.retryAfterSec * 1000).toISOString(),
|
|
2579
2754
|
pauseReason: `auditor quota: ${result.error}`,
|
|
@@ -2584,6 +2759,13 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2584
2759
|
// Re-check: only auto-resume if STILL paused for the quota
|
|
2585
2760
|
// reason (a user /goal pause during the window is not stomped).
|
|
2586
2761
|
if (state.goal && state.goal.status === "paused" && (state.goal.pauseReason ?? "").startsWith("auditor quota:")) {
|
|
2762
|
+
// v0.28.26: a stored claim retries the AUDITOR directly — the
|
|
2763
|
+
// agent is not needed to re-submit an unchanged claim, and
|
|
2764
|
+
// re-engaging it produced hallucinated-closure loops.
|
|
2765
|
+
if (state.goal.pendingCompletion) {
|
|
2766
|
+
void retryStoredCompletionAudit(ctx);
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2587
2769
|
updateGoal({ status: "active" }, ctx);
|
|
2588
2770
|
appendLedger(ctx.cwd, "goal_resumed", { via: "quota-retry" });
|
|
2589
2771
|
if (resolveEffectiveAggressiveSettings(loadSettings(ctx.cwd)).aggressiveMode) {
|
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.27",
|
|
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",
|
package/schemas/goal.schema.json
CHANGED
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"interruptedAt": { "type": "string" },
|
|
54
54
|
"interruptedReason": { "type": "string" },
|
|
55
55
|
"auditInfraStreak": { "type": "number" },
|
|
56
|
+
"pendingCompletion": { "type": "object" },
|
|
56
57
|
"activePath": { "type": "string" },
|
|
57
58
|
"archivedPath": { "type": "string" },
|
|
58
59
|
"usage": {
|