pi-goal-list-loop-audit 0.28.19 → 0.28.21
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 -1
- package/extensions/goal-loop-core.ts +10 -10
- package/extensions/loops/goal.ts +31 -7
- package/extensions/settings-menu.ts +38 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -203,7 +203,7 @@ No external watchdog plugin needed.
|
|
|
203
203
|
/glla tokenlimit=10000000 # per-goal token budget (default: off) → GLOBAL
|
|
204
204
|
/glla tokenlimit=0 # explicitly no cap (the default)
|
|
205
205
|
/glla wedgealert=30 # hung-command alert minutes (default: 30, 0 = off)
|
|
206
|
-
/glla autoresume=on # auto-resume goals/loops on session
|
|
206
|
+
/glla autoresume=on # auto-resume goals/loops on ANY session start (default: load HELD, never auto-start — explicit /goal resume, /list resume, or /loop; off: never)
|
|
207
207
|
/glla auditcap=5 # pause the goal after N consecutive auditor disapprovals (default 5, 0 = unlimited)
|
|
208
208
|
/glla aggressivemode=on # keep-going defaults: autoResume, cap 10, stuck 10, wedge off, quota auto-retry, cap→TODOs
|
|
209
209
|
/glla quotaretryminutes=60 # minutes before auto-retrying a quota-exhausted auditor
|
|
@@ -728,21 +728,21 @@ export function cloneGoal(goal: Goal): Goal {
|
|
|
728
728
|
* setting for unattended restarts). One mechanical predicate; no heuristics.
|
|
729
729
|
*/
|
|
730
730
|
export function shouldAutoResumeOnSessionStart(reason: string | undefined, autoResume: boolean | undefined): boolean {
|
|
731
|
-
// v0.
|
|
732
|
-
//
|
|
731
|
+
// v0.28.21: the DEFAULT flipped to hold-everything (user directive:
|
|
732
|
+
// "load it on session load but not auto start it"). Tri-state:
|
|
733
|
+
// true → auto-resume on EVERY session start (unattended rigs;
|
|
734
|
+
// /glla autoresume=on — this is the ONLY auto-resume path).
|
|
733
735
|
// false → never auto-resume; always hold for an explicit resume.
|
|
734
|
-
// undefined → DEFAULT:
|
|
735
|
-
// "resume",
|
|
736
|
-
//
|
|
737
|
-
//
|
|
738
|
-
// extension reload or session fork never strands work.
|
|
736
|
+
// undefined → DEFAULT: never auto-resume either — whatever the reason
|
|
737
|
+
// ("startup"/"new"/"resume"/"reload"/"fork"/none), the
|
|
738
|
+
// item is LOADED (visible, state intact) but HELD until an
|
|
739
|
+
// explicit /goal resume, /list resume, or /loop.
|
|
739
740
|
// Mid-session continuation (agent_end chains, heartbeat refires,
|
|
740
741
|
// post-compaction, list/loop transitions) is not gated here at all — it
|
|
741
742
|
// auto-continues forever unless a super-stuck brake (stall escalation,
|
|
742
743
|
// stale-api terminal, pending-latch watchdog) stops it loudly.
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
return reason === "reload" || reason === "fork";
|
|
744
|
+
void reason; // retained for the signature; no reason auto-resumes by default anymore
|
|
745
|
+
return autoResume === true;
|
|
746
746
|
}
|
|
747
747
|
|
|
748
748
|
/**
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -1194,9 +1194,9 @@ async function cmdSet(args: string, ctx: ExtensionContext, skipDraft = false): P
|
|
|
1194
1194
|
consecutiveNoToolIterations = 0;
|
|
1195
1195
|
if (staleEntry) {
|
|
1196
1196
|
// v0.28.1 (S3): the goal is persisted — mark the interrupt so the next
|
|
1197
|
-
// fresh session
|
|
1197
|
+
// fresh session LOADS it (held by default since v0.28.21), and tell the truth instead of "starting now".
|
|
1198
1198
|
updateGoal({ interruptedAt: nowIso(), interruptedReason: "created in a stale session" }, ctx);
|
|
1199
|
-
ctx.ui.notify(`Goal saved: ${shortObj(goal.objective)} — safe in .pi-glla/, but this stale process can't send continuations. Restart pi
|
|
1199
|
+
ctx.ui.notify(`Goal saved: ${shortObj(goal.objective)} — safe in .pi-glla/, but this stale process can't send continuations. Restart pi, then /goal resume (v0.28.21: session loads no longer auto-start by default). (id: ${goal.id})`, "warning");
|
|
1200
1200
|
return;
|
|
1201
1201
|
}
|
|
1202
1202
|
ctx.ui.notify(`Goal started: ${shortObj(goal.objective)} — the auditor will verify on completion. (id: ${goal.id})`, "info");
|
|
@@ -1239,6 +1239,13 @@ async function cmdPause(ctx: ExtensionContext): Promise<void> {
|
|
|
1239
1239
|
|
|
1240
1240
|
async function cmdResume(ctx: ExtensionContext): Promise<void> {
|
|
1241
1241
|
if (!state.goal || state.goal.status !== "paused") return;
|
|
1242
|
+
// v0.28.21: one-active-thing — the LAST unguarded activation path. A
|
|
1243
|
+
// paused goal/list-item must not resume over a live loop (covers
|
|
1244
|
+
// /goal resume AND /list resume, which routes here).
|
|
1245
|
+
if (isLoopActive()) {
|
|
1246
|
+
ctx.ui.notify("A loop is active — one active thing at a time. /loop stop it first, then resume the goal.", "warning");
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1242
1249
|
// v0.28.1 (S1/S3): resuming in a stale session used to flip status to
|
|
1243
1250
|
// active, claim "Resumed goal", then re-pause on the stale send failure
|
|
1244
1251
|
// (or zombie — S1). Now: persist the resume (the next fresh session
|
|
@@ -4460,12 +4467,13 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4460
4467
|
);
|
|
4461
4468
|
}
|
|
4462
4469
|
} else if (state.goal && state.goal.status === "active" && state.goal.autoContinue) {
|
|
4463
|
-
// v0.28.3 (S2 completed): an infra interrupt outranks the DEFAULT
|
|
4464
|
-
// hold — the goal never chose to stop; pi killed its handle. With
|
|
4465
|
-
// autoresume unset, an interrupted goal auto-resumes even on a human
|
|
4466
|
-
// session load; explicit /glla autoresume=off still holds.
|
|
4467
4470
|
const wasInterrupted = !!state.goal.interruptedAt;
|
|
4468
|
-
|
|
4471
|
+
// v0.28.21: the 0.28.3 interrupted-goal exemption is SUPERSEDED —
|
|
4472
|
+
// the default is now hold-everything on session load (user directive:
|
|
4473
|
+
// "load it but not auto start it"). Interrupted goals hold like
|
|
4474
|
+
// everything else; autoresume=on (unattended rigs) still auto-resumes
|
|
4475
|
+
// them, and the marker is cleared only on that promised auto-resume.
|
|
4476
|
+
if (autoResume) {
|
|
4469
4477
|
// v0.28.1 (S2): clear the stale-handle interrupt marker — this IS
|
|
4470
4478
|
// the auto-resume the marker promised.
|
|
4471
4479
|
if (wasInterrupted) updateGoal({ interruptedAt: undefined, interruptedReason: undefined }, ctx);
|
|
@@ -4506,6 +4514,22 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4506
4514
|
ctx.ui.notify(`List has ${listQueue().length} item(s) waiting — /list next to activate the head.`, "info");
|
|
4507
4515
|
}
|
|
4508
4516
|
}
|
|
4517
|
+
// v0.28.21: enforce one-active-thing at the restore boundary for DIRTY
|
|
4518
|
+
// legacy states — pre-guard versions could persist an active goal AND
|
|
4519
|
+
// an active/held loop; the chain above handles the loop first, and the
|
|
4520
|
+
// goal would otherwise stay active and fire on agent_end. Pause it:
|
|
4521
|
+
// at most one thing owns the active slot, and nothing auto-starts.
|
|
4522
|
+
if (state.loop && state.goal && state.goal.status === "active") {
|
|
4523
|
+
updateGoal({
|
|
4524
|
+
status: "paused",
|
|
4525
|
+
pauseReason: "held on session load — the loop owns the active slot (one active thing at a time)",
|
|
4526
|
+
pauseSuggestedAction: "/loop to work the loop, or /loop stop then /goal resume to work the goal",
|
|
4527
|
+
}, ctx);
|
|
4528
|
+
ctx.ui.notify(
|
|
4529
|
+
`Goal [${state.goal.id}] held — a loop also exists; one active thing at a time. /loop to resume the loop, or /loop stop then /goal resume.`,
|
|
4530
|
+
"info",
|
|
4531
|
+
);
|
|
4532
|
+
}
|
|
4509
4533
|
// Always paint on session load (v0.22.1): the branches above only reach
|
|
4510
4534
|
// refreshUI via persistState, so a goal that was ALREADY paused (or any
|
|
4511
4535
|
// state that doesn't mutate on load) rendered nothing — "can't tell if
|
|
@@ -70,7 +70,7 @@ export interface SettingsRow {
|
|
|
70
70
|
label: string;
|
|
71
71
|
/** VALUE column — current effective value, e.g. `true` / `(off)` / `60`. */
|
|
72
72
|
valueText: string;
|
|
73
|
-
/** SOURCE column — provenance tag
|
|
73
|
+
/** SOURCE column — provenance tag: `project` / `global` / `default` / `runtime` (v0.28.20: bare — brackets were chrome). */
|
|
74
74
|
sourceText: string;
|
|
75
75
|
/** DESCRIPTION column — one-line explanation; truncated with ellipsis when narrow. */
|
|
76
76
|
description: string;
|
|
@@ -116,7 +116,7 @@ export function buildSettingsRows(
|
|
|
116
116
|
const p = provFor(k);
|
|
117
117
|
return p.value === undefined ? fallback : String(p.value);
|
|
118
118
|
};
|
|
119
|
-
const src = (k: keyof Settings): string =>
|
|
119
|
+
const src = (k: keyof Settings): string => provFor(k).source;
|
|
120
120
|
|
|
121
121
|
const rows: SettingsRow[] = [];
|
|
122
122
|
|
|
@@ -135,7 +135,7 @@ export function buildSettingsRows(
|
|
|
135
135
|
id: "autoAcceptDrafts",
|
|
136
136
|
section: "keep-going",
|
|
137
137
|
label: "Auto-accept drafts",
|
|
138
|
-
valueText: show("autoAcceptDrafts", "
|
|
138
|
+
valueText: show("autoAcceptDrafts", "off"),
|
|
139
139
|
sourceText: src("autoAcceptDrafts"),
|
|
140
140
|
description: "on: goal/loop drafts activate without the Confirm dialog (unattended rigs)",
|
|
141
141
|
},
|
|
@@ -143,7 +143,7 @@ export function buildSettingsRows(
|
|
|
143
143
|
id: "aggressiveMode",
|
|
144
144
|
section: "keep-going",
|
|
145
145
|
label: "Aggressive mode",
|
|
146
|
-
valueText: show("aggressiveMode", "
|
|
146
|
+
valueText: show("aggressiveMode", "off"),
|
|
147
147
|
sourceText: src("aggressiveMode"),
|
|
148
148
|
description:
|
|
149
149
|
"flips DEFAULTS toward keep-going (autoResume, cap 10, stuck 10, wedge off, quota auto-retry, cap→TODOs); explicit per-key settings still win",
|
|
@@ -156,7 +156,7 @@ export function buildSettingsRows(
|
|
|
156
156
|
id: "auditorModel",
|
|
157
157
|
section: "auditor",
|
|
158
158
|
label: "Auditor model",
|
|
159
|
-
valueText: show("auditorModel", "
|
|
159
|
+
valueText: show("auditorModel", "pi session model"),
|
|
160
160
|
sourceText: src("auditorModel"),
|
|
161
161
|
description: "provider/model override for the isolated auditor",
|
|
162
162
|
},
|
|
@@ -164,7 +164,7 @@ export function buildSettingsRows(
|
|
|
164
164
|
id: "auditorThinkingLevel",
|
|
165
165
|
section: "auditor",
|
|
166
166
|
label: "Auditor thinking",
|
|
167
|
-
valueText: show("auditorThinkingLevel", "
|
|
167
|
+
valueText: show("auditorThinkingLevel", "session, floor high"),
|
|
168
168
|
sourceText: src("auditorThinkingLevel"),
|
|
169
169
|
description: "thinking level for the auditor session",
|
|
170
170
|
},
|
|
@@ -172,7 +172,7 @@ export function buildSettingsRows(
|
|
|
172
172
|
id: "auditCap",
|
|
173
173
|
section: "auditor",
|
|
174
174
|
label: "Audit cap",
|
|
175
|
-
valueText: show("auditCap",
|
|
175
|
+
valueText: show("auditCap", `${defaults.auditCap}`),
|
|
176
176
|
sourceText: src("auditCap"),
|
|
177
177
|
description: "pause the goal after N consecutive disapprovals (0 = unlimited)",
|
|
178
178
|
},
|
|
@@ -182,7 +182,7 @@ export function buildSettingsRows(
|
|
|
182
182
|
label: "Audit feedback chars",
|
|
183
183
|
valueText: show(
|
|
184
184
|
"auditFeedbackChars",
|
|
185
|
-
DEFAULT_AUDIT_FEEDBACK_CHARS === 0 ? "
|
|
185
|
+
DEFAULT_AUDIT_FEEDBACK_CHARS === 0 ? "full report" : `${DEFAULT_AUDIT_FEEDBACK_CHARS}`,
|
|
186
186
|
),
|
|
187
187
|
sourceText: src("auditFeedbackChars"),
|
|
188
188
|
description: "cap the executor-visible disapproval report (0 = full report)",
|
|
@@ -191,7 +191,7 @@ export function buildSettingsRows(
|
|
|
191
191
|
id: "quotaRetryMinutes",
|
|
192
192
|
section: "auditor",
|
|
193
193
|
label: "Quota retry minutes",
|
|
194
|
-
valueText: show("quotaRetryMinutes",
|
|
194
|
+
valueText: show("quotaRetryMinutes", `${DEFAULT_QUOTA_RETRY_MINUTES}`),
|
|
195
195
|
sourceText: src("quotaRetryMinutes"),
|
|
196
196
|
description: "auto-retry a quota-exhausted auditor after N minutes",
|
|
197
197
|
},
|
|
@@ -203,7 +203,7 @@ export function buildSettingsRows(
|
|
|
203
203
|
id: "wedgeAlertMinutes",
|
|
204
204
|
section: "stall-brakes",
|
|
205
205
|
label: "Wedge alert minutes",
|
|
206
|
-
valueText: show("wedgeAlertMinutes",
|
|
206
|
+
valueText: show("wedgeAlertMinutes", `${WEDGE_ALERT_DEFAULT_MINUTES}`),
|
|
207
207
|
sourceText: src("wedgeAlertMinutes"),
|
|
208
208
|
description: "hung-command alert while the session is busy (0 = off)",
|
|
209
209
|
},
|
|
@@ -211,7 +211,7 @@ export function buildSettingsRows(
|
|
|
211
211
|
id: "stuckMaxInterventions",
|
|
212
212
|
section: "stall-brakes",
|
|
213
213
|
label: "Stuck max interventions",
|
|
214
|
-
valueText: show("stuckMaxInterventions",
|
|
214
|
+
valueText: show("stuckMaxInterventions", `${defaults.stuckMaxInterventions}`),
|
|
215
215
|
sourceText: src("stuckMaxInterventions"),
|
|
216
216
|
description: "consecutive stuck interventions before a loop stops",
|
|
217
217
|
},
|
|
@@ -219,7 +219,7 @@ export function buildSettingsRows(
|
|
|
219
219
|
id: "stallEscalationRefires",
|
|
220
220
|
section: "stall-brakes",
|
|
221
221
|
label: "Stall escalation refires",
|
|
222
|
-
valueText: show("stallEscalationRefires",
|
|
222
|
+
valueText: show("stallEscalationRefires", `${DEFAULT_STALL_ESCALATION_REFIRES}`),
|
|
223
223
|
sourceText: src("stallEscalationRefires"),
|
|
224
224
|
description:
|
|
225
225
|
"heartbeat refires with no turn before the goal pauses / loop stops (0 = never)",
|
|
@@ -228,7 +228,7 @@ export function buildSettingsRows(
|
|
|
228
228
|
id: "stallShortWords",
|
|
229
229
|
section: "stall-brakes",
|
|
230
230
|
label: "Stall short words",
|
|
231
|
-
valueText: show("stallShortWords",
|
|
231
|
+
valueText: show("stallShortWords", `${DEFAULT_STALL_SHORT_WORDS}`),
|
|
232
232
|
sourceText: src("stallShortWords"),
|
|
233
233
|
description: "turns with no tools AND fewer words than this count as a nudge",
|
|
234
234
|
},
|
|
@@ -236,7 +236,7 @@ export function buildSettingsRows(
|
|
|
236
236
|
id: "stallSimilarityThreshold",
|
|
237
237
|
section: "stall-brakes",
|
|
238
238
|
label: "Stall similarity threshold",
|
|
239
|
-
valueText: show("stallSimilarityThreshold",
|
|
239
|
+
valueText: show("stallSimilarityThreshold", `${DEFAULT_STALL_SIM_THRESHOLD}`),
|
|
240
240
|
sourceText: src("stallSimilarityThreshold"),
|
|
241
241
|
description:
|
|
242
242
|
"no-tool turns whose text is > this similar to the prior turn count as a nudge (0–1)",
|
|
@@ -249,7 +249,7 @@ export function buildSettingsRows(
|
|
|
249
249
|
id: "subagentModelStrategy",
|
|
250
250
|
section: "subagents",
|
|
251
251
|
label: "Subagent model strategy",
|
|
252
|
-
valueText: show("subagentModelStrategy", "
|
|
252
|
+
valueText: show("subagentModelStrategy", "inherit-parent"),
|
|
253
253
|
sourceText: src("subagentModelStrategy"),
|
|
254
254
|
description:
|
|
255
255
|
"inherit-parent shares your session model + quota pool; agent-default uses the upstream pi-subagents default agents",
|
|
@@ -258,45 +258,52 @@ export function buildSettingsRows(
|
|
|
258
258
|
id: "subagentModelOverrides.Explore",
|
|
259
259
|
section: "subagents",
|
|
260
260
|
label: "Subagent Explore pin",
|
|
261
|
-
valueText: settings.subagentModelOverrides?.Explore ?? "
|
|
261
|
+
valueText: settings.subagentModelOverrides?.Explore ?? "follows strategy",
|
|
262
262
|
sourceText:
|
|
263
263
|
settings.subagentModelOverrides?.Explore !== undefined
|
|
264
264
|
? src("subagentModelOverrides")
|
|
265
|
-
: "
|
|
265
|
+
: "default",
|
|
266
266
|
description: "provider/model pin; always wins over strategy",
|
|
267
267
|
},
|
|
268
268
|
{
|
|
269
269
|
id: "subagentModelOverrides.Plan",
|
|
270
270
|
section: "subagents",
|
|
271
271
|
label: "Subagent Plan pin",
|
|
272
|
-
valueText: settings.subagentModelOverrides?.Plan ?? "
|
|
272
|
+
valueText: settings.subagentModelOverrides?.Plan ?? "follows strategy",
|
|
273
273
|
sourceText:
|
|
274
274
|
settings.subagentModelOverrides?.Plan !== undefined
|
|
275
275
|
? src("subagentModelOverrides")
|
|
276
|
-
: "
|
|
276
|
+
: "default",
|
|
277
277
|
description: "provider/model pin; always wins over strategy",
|
|
278
278
|
},
|
|
279
279
|
{
|
|
280
280
|
id: "subagentModelOverrides.general-purpose",
|
|
281
281
|
section: "subagents",
|
|
282
282
|
label: "Subagent general-purpose pin",
|
|
283
|
-
valueText: settings.subagentModelOverrides?.["general-purpose"] ?? "
|
|
283
|
+
valueText: settings.subagentModelOverrides?.["general-purpose"] ?? "follows strategy",
|
|
284
284
|
sourceText:
|
|
285
285
|
settings.subagentModelOverrides?.["general-purpose"] !== undefined
|
|
286
286
|
? src("subagentModelOverrides")
|
|
287
|
-
: "
|
|
287
|
+
: "default",
|
|
288
288
|
description: "provider/model pin; always wins over strategy",
|
|
289
289
|
},
|
|
290
290
|
{
|
|
291
291
|
id: "subagentResolved",
|
|
292
292
|
section: "subagents",
|
|
293
293
|
label: "Effective resolution",
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
294
|
+
// v0.28.20: compact — strip the parenthesized qualifier (the
|
|
295
|
+
// DESCRIPTION column carries semantics) and dedupe identical
|
|
296
|
+
// resolutions; the old 3-part parenthesized composite never fit.
|
|
297
|
+
valueText: (() => {
|
|
298
|
+
const strip = (r: string) => r.replace(/ \([^)]*\)$/, "").replace(/^\((.*)\)$/, "$1");
|
|
299
|
+
const parts = [
|
|
300
|
+
resolveEffectiveSubagentModel("Explore", settings, subagent.sessionModel),
|
|
301
|
+
resolveEffectiveSubagentModel("Plan", settings, subagent.sessionModel),
|
|
302
|
+
resolveEffectiveSubagentModel("general-purpose", settings, subagent.sessionModel),
|
|
303
|
+
].map(strip);
|
|
304
|
+
return parts.every((p) => p === parts[0]) ? parts[0]! : parts.join(" · ");
|
|
305
|
+
})(),
|
|
306
|
+
sourceText: "runtime",
|
|
300
307
|
description: "effective Explore / Plan / general-purpose model given current settings",
|
|
301
308
|
},
|
|
302
309
|
);
|
|
@@ -307,7 +314,7 @@ export function buildSettingsRows(
|
|
|
307
314
|
id: "notifyCmd",
|
|
308
315
|
section: "other",
|
|
309
316
|
label: "Notify command",
|
|
310
|
-
valueText: show("notifyCmd", "
|
|
317
|
+
valueText: show("notifyCmd", "off"),
|
|
311
318
|
sourceText: src("notifyCmd"),
|
|
312
319
|
description: "desktop push command; the event message is passed as $1",
|
|
313
320
|
},
|
|
@@ -315,16 +322,16 @@ export function buildSettingsRows(
|
|
|
315
322
|
id: "tokenLimit",
|
|
316
323
|
section: "other",
|
|
317
324
|
label: "Token limit per goal",
|
|
318
|
-
valueText: show("tokenLimit", "
|
|
325
|
+
valueText: show("tokenLimit", "off"),
|
|
319
326
|
sourceText: src("tokenLimit"),
|
|
320
327
|
description: "per-goal token budget; pause when exceeded (0 = off)",
|
|
321
328
|
},
|
|
322
329
|
{
|
|
323
330
|
id: "postaudit",
|
|
324
331
|
section: "other",
|
|
325
|
-
label: "Postaudit
|
|
332
|
+
label: "Postaudit",
|
|
326
333
|
valueText: "open sub-menu",
|
|
327
|
-
sourceText: "
|
|
334
|
+
sourceText: "—",
|
|
328
335
|
description:
|
|
329
336
|
"post-completion follow-up enqueuer: mode, triggers, cascade, caps (postaudit / reviewer)",
|
|
330
337
|
},
|
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.21",
|
|
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",
|