pi-goal-list-loop-audit 0.28.6 → 0.28.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.
|
@@ -897,7 +897,12 @@ export interface EffectiveAggressiveSettings {
|
|
|
897
897
|
stuckMaxInterventions: number;
|
|
898
898
|
/** 0 = wedge alerts off. */
|
|
899
899
|
wedgeAlertMinutes: number;
|
|
900
|
-
|
|
900
|
+
/** Tri-state: true = always auto-resume; false = never; undefined =
|
|
901
|
+
* DEFAULT (hold on human session loads, resume on reload/fork).
|
|
902
|
+
* v0.28.7: must stay tri-state here — coercing unset→false broke the
|
|
903
|
+
* restore gate's default branch (the 0.28.3 regression the behavioral
|
|
904
|
+
* harness caught). */
|
|
905
|
+
autoResume: boolean | undefined;
|
|
901
906
|
aggressiveMode: boolean;
|
|
902
907
|
}
|
|
903
908
|
|
|
@@ -918,7 +923,7 @@ export function resolveEffectiveAggressiveSettings(s: {
|
|
|
918
923
|
stuckMaxInterventions:
|
|
919
924
|
s.stuckMaxInterventions ?? (aggressiveMode ? AGGRESSIVE_STUCK_MAX_INTERVENTIONS : BASE_STUCK_MAX_INTERVENTIONS),
|
|
920
925
|
wedgeAlertMinutes: s.wedgeAlertMinutes ?? (aggressiveMode ? 0 : 30),
|
|
921
|
-
autoResume: s.autoResume ?? aggressiveMode,
|
|
926
|
+
autoResume: s.autoResume ?? (aggressiveMode ? true : undefined),
|
|
922
927
|
};
|
|
923
928
|
}
|
|
924
929
|
|
|
@@ -43,6 +43,11 @@ export interface LoopState {
|
|
|
43
43
|
maxIterations: number;
|
|
44
44
|
plateauWindow: number;
|
|
45
45
|
stallCount: number;
|
|
46
|
+
/** v0.28.8 (E5): consecutive iterations where the measure printed NO
|
|
47
|
+
* number. Tracked separately from stallCount — plateau judges movement
|
|
48
|
+
* (a real number that didn't improve); a broken measure says nothing
|
|
49
|
+
* about movement and must stop the loop with its own loud reason. */
|
|
50
|
+
consecutiveNullMeasures?: number;
|
|
46
51
|
bestValue: number | null;
|
|
47
52
|
lastValue: number | null;
|
|
48
53
|
active: boolean;
|
|
@@ -172,14 +177,20 @@ export type LoopTickOutcome =
|
|
|
172
177
|
*/
|
|
173
178
|
export function applyMeasurement(loop: LoopState, value: number | null, at: string): LoopTickOutcome {
|
|
174
179
|
loop.iteration++;
|
|
180
|
+
// improved is judged BEFORE bestValue moves (post-mutation it would read false).
|
|
175
181
|
const improved = value !== null && loop.direction !== undefined && isImprovement(loop.direction, value, loop.bestValue);
|
|
176
182
|
if (value === null) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
loop.
|
|
180
|
-
loop.stallCount = 0;
|
|
183
|
+
// E5: a null measure is NOT a stall — it carries no information about
|
|
184
|
+
// improvement. Plateau stays reserved for real non-improving numbers.
|
|
185
|
+
loop.consecutiveNullMeasures = (loop.consecutiveNullMeasures ?? 0) + 1;
|
|
181
186
|
} else {
|
|
182
|
-
loop.
|
|
187
|
+
loop.consecutiveNullMeasures = 0;
|
|
188
|
+
if (improved) {
|
|
189
|
+
loop.bestValue = value;
|
|
190
|
+
loop.stallCount = 0;
|
|
191
|
+
} else {
|
|
192
|
+
loop.stallCount++;
|
|
193
|
+
}
|
|
183
194
|
}
|
|
184
195
|
loop.lastValue = value;
|
|
185
196
|
loop.history.push({ iteration: loop.iteration, value, improved, at });
|
|
@@ -198,6 +209,14 @@ export function applyMeasurement(loop: LoopState, value: number | null, at: stri
|
|
|
198
209
|
loop.stopReason = `token budget exhausted (${(loop.tokensUsed ?? 0).toLocaleString()} >= ${loop.tokenBudget.toLocaleString()}); best: ${loop.bestValue ?? "n/a"}`;
|
|
199
210
|
return { kind: "stop", reason: loop.stopReason };
|
|
200
211
|
}
|
|
212
|
+
// E5: a broken measure command gets its OWN loud stop — never the
|
|
213
|
+
// misleading "plateau — no improvement" (there was nothing to improve
|
|
214
|
+
// against; the metric itself is dead).
|
|
215
|
+
if ((loop.consecutiveNullMeasures ?? 0) >= loop.plateauWindow) {
|
|
216
|
+
loop.active = false;
|
|
217
|
+
loop.stopReason = `measure command broken — ${loop.consecutiveNullMeasures} consecutive iterations printed no number (cmd: \`${loop.measureCmd ?? "?"}\`). Fix the measure command, or /loop stop.`;
|
|
218
|
+
return { kind: "stop", reason: loop.stopReason };
|
|
219
|
+
}
|
|
201
220
|
if (loop.stallCount >= loop.plateauWindow) {
|
|
202
221
|
loop.active = false;
|
|
203
222
|
loop.stopReason = `plateau — no improvement in ${loop.plateauWindow} consecutive iterations (best: ${loop.bestValue ?? "n/a"})`;
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -224,6 +224,13 @@ function goStaleTerminal(ctx: ExtensionContext, where: string): void {
|
|
|
224
224
|
notifyExternal(ctx, `glla: extension api stale — restart pi. (${where})`);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
/** TEST-ONLY hook (tests/harness): the stale flag is process-terminal in
|
|
228
|
+
* production — only a pi restart clears it — so behavioral tests reset it
|
|
229
|
+
* between stale scenarios. Never called by production code. */
|
|
230
|
+
export function __testOnlyResetStaleFlag(): void {
|
|
231
|
+
extensionApiStale = false;
|
|
232
|
+
}
|
|
233
|
+
|
|
227
234
|
/** v0.28.1 (S3): side-effect-free staleness probe — getSessionName()
|
|
228
235
|
* routes through pi's assertActive() and throws the stale signature iff
|
|
229
236
|
* pi invalidated this factory handle (session replacement). A positive
|
|
@@ -872,8 +879,16 @@ function fireReviewer(
|
|
|
872
879
|
`[REVIEWER FOLLOW-UP — ${reason}. Propose this as a /goal via propose_goal_draft (the user Confirms or rejects): ${objective}]`,
|
|
873
880
|
{ deliverAs: ctx.isIdle() ? "followUp" : "steer" },
|
|
874
881
|
);
|
|
875
|
-
|
|
876
|
-
|
|
882
|
+
return true;
|
|
883
|
+
} catch (err) {
|
|
884
|
+
// v0.28.8 (E4): the phantom-reviewer hole — a swallowed throw used
|
|
885
|
+
// to still count as "proposed" in the report + notify. Now the
|
|
886
|
+
// failure is LOUD and the proposal goes uncounted.
|
|
887
|
+
ctx.ui.notify(
|
|
888
|
+
`Reviewer /goal proposal NOT delivered: ${err instanceof Error ? err.message : String(err)} — the follow-up never reached the session. Restart pi if the session was just replaced.`,
|
|
889
|
+
"warning",
|
|
890
|
+
);
|
|
891
|
+
return false;
|
|
877
892
|
}
|
|
878
893
|
},
|
|
879
894
|
notify: (message, level) => ctx.ui.notify(message, level),
|
|
@@ -2523,11 +2538,12 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2523
2538
|
pi.registerTool(defineTool({
|
|
2524
2539
|
name: "complete_task",
|
|
2525
2540
|
label: "Complete task",
|
|
2526
|
-
description: "Mark a task in the active goal's task list as complete (does not stop the turn).",
|
|
2527
|
-
parameters: Type.Object({
|
|
2541
|
+
description: "Mark a task in the active goal's task list as complete (does not stop the turn).", parameters: Type.Object({
|
|
2528
2542
|
id: Type.String({ description: "Task id to complete" }),
|
|
2529
2543
|
}),
|
|
2530
|
-
async execute(_id, params) {
|
|
2544
|
+
async execute(_id, params, _signal, _onUpdate, execCtx) {
|
|
2545
|
+
const foreign7 = foreignToolGuard(execCtx);
|
|
2546
|
+
if (foreign7) return { content: [{ type: "text", text: foreign7 }], details: {} };
|
|
2531
2547
|
const p = params as { id: string };
|
|
2532
2548
|
if (!state.goal || !state.goal.taskList) {
|
|
2533
2549
|
return { content: [{ type: "text", text: "No task list in this goal." }], details: {} };
|
|
@@ -2555,7 +2571,9 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2555
2571
|
id: Type.String(),
|
|
2556
2572
|
status: Type.Union([Type.Literal("pending"), Type.Literal("in_progress"), Type.Literal("complete")]),
|
|
2557
2573
|
}),
|
|
2558
|
-
async execute(_id, params) {
|
|
2574
|
+
async execute(_id, params, _signal, _onUpdate, execCtx) {
|
|
2575
|
+
const foreign8 = foreignToolGuard(execCtx);
|
|
2576
|
+
if (foreign8) return { content: [{ type: "text", text: foreign8 }], details: {} };
|
|
2559
2577
|
const p = params as { id: string; status: "pending" | "in_progress" | "complete" };
|
|
2560
2578
|
if (!state.goal || !state.goal.taskList) {
|
|
2561
2579
|
return { content: [{ type: "text", text: "No task list in this goal." }], details: {} };
|
|
@@ -3012,6 +3030,8 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
3012
3030
|
})),
|
|
3013
3031
|
}),
|
|
3014
3032
|
async execute(_id, params, _signal, _onUpdate, execCtx) {
|
|
3033
|
+
const foreign9 = foreignToolGuard(execCtx);
|
|
3034
|
+
if (foreign9) return { content: [{ type: "text", text: foreign9 }], details: {} };
|
|
3015
3035
|
if (!state.goal || state.goal.status !== "active") {
|
|
3016
3036
|
return { content: [{ type: "text", text: "No active goal to break down." }], details: {} };
|
|
3017
3037
|
}
|
|
@@ -3179,7 +3199,9 @@ async function promptSettingsMenu(
|
|
|
3179
3199
|
* Same handlers as v0.27.0's if/else chain — only the trigger changed from
|
|
3180
3200
|
* `startsWith(label)` strings to stable ids.
|
|
3181
3201
|
*/
|
|
3182
|
-
|
|
3202
|
+
// v0.28.7 (T4): exported for the behavioral settings-editor tests
|
|
3203
|
+
// (tests/settings-editors.test.ts drives each editor class end-to-end).
|
|
3204
|
+
export async function handleSettingChoice(id: string, ctx: ExtensionContext): Promise<void> {
|
|
3183
3205
|
switch (id) {
|
|
3184
3206
|
case "autoResume": {
|
|
3185
3207
|
const v = await ctx.ui.select("Auto-resume goals/loops on session start", [
|
package/extensions/reviewer.ts
CHANGED
|
@@ -197,7 +197,11 @@ export interface ReviewerDeps {
|
|
|
197
197
|
/** Source texts for finding extraction (archive md, audit reports). */
|
|
198
198
|
sources: Array<{ name: string; text: string }>;
|
|
199
199
|
enqueueListItems: (objectives: string[]) => void;
|
|
200
|
-
|
|
200
|
+
/** Deliver a /goal proposal message to the session. Returns true when the
|
|
201
|
+
* message was actually sent; false when the send failed (the v0.28.8 E4
|
|
202
|
+
* contract — a failed send must NOT count as `proposed`, else the user is
|
|
203
|
+
* told about phantom proposals that never arrived). */
|
|
204
|
+
proposeGoal: (objective: string, reason: string) => boolean;
|
|
201
205
|
notify: (message: string, level: "info" | "warning") => void;
|
|
202
206
|
ledger: (type: string, value: Record<string, unknown>) => void;
|
|
203
207
|
}
|
|
@@ -274,23 +278,29 @@ export function runReviewer(
|
|
|
274
278
|
// v0.27.5 aggressive: also propose the FIRST architectural finding
|
|
275
279
|
// as a relaunch so the queue gets burned through even when the
|
|
276
280
|
// unattended rig can't Confirm.
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
if (
|
|
282
|
+
deps.proposeGoal(
|
|
283
|
+
architectural[0]!.text,
|
|
284
|
+
`aggressive postaudit: relaunching as /goal without Confirm (${architectural.length} architectural findings total)`,
|
|
285
|
+
)
|
|
286
|
+
) {
|
|
287
|
+
proposed += 1;
|
|
288
|
+
}
|
|
281
289
|
enqueued += architectural.length;
|
|
282
|
-
proposed += 1;
|
|
283
290
|
cascadeStep = "aggressive-relaunch";
|
|
284
291
|
} else if (auto) {
|
|
285
292
|
deps.enqueueListItems(architectural.map((f) => f.text));
|
|
286
293
|
enqueued += architectural.length;
|
|
287
294
|
cascadeStep = convertStep;
|
|
288
295
|
} else {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
296
|
+
if (
|
|
297
|
+
deps.proposeGoal(
|
|
298
|
+
architectural.map((f) => f.text).join("; "),
|
|
299
|
+
`reviewer found ${architectural.length} architectural-class finding(s) — needs your Confirm`,
|
|
300
|
+
)
|
|
301
|
+
) {
|
|
302
|
+
proposed += architectural.length;
|
|
303
|
+
}
|
|
294
304
|
cascadeStep = "propose-goal";
|
|
295
305
|
}
|
|
296
306
|
}
|
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.8",
|
|
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",
|