pi-goal-list-loop-audit 0.28.16 → 0.28.17
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.
|
@@ -12,7 +12,17 @@
|
|
|
12
12
|
|
|
13
13
|
import type { Goal, State } from "./goal-loop-core.js";
|
|
14
14
|
import { isPersistenceDegraded, lastPersistenceFailure } from "./goal-loop-core.js";
|
|
15
|
-
import type
|
|
15
|
+
import { HELD_ON_RESTORE, type LoopState } from "./goal-loop-forever.js";
|
|
16
|
+
|
|
17
|
+
/** v0.28.17: a loop parked by the session-restore gate (was active when the
|
|
18
|
+
* last session ended). Held loops must stay VISIBLE — before, only
|
|
19
|
+
* state.loop?.active rendered anything and a reload made the loop vanish
|
|
20
|
+
* from the always-on UI (user report 2026-07-29: "loops are the most
|
|
21
|
+
* immature"). Stopped loops (any other stopReason) stay invisible. */
|
|
22
|
+
function heldLoop(state: State): LoopState | undefined {
|
|
23
|
+
const l = state.loop;
|
|
24
|
+
return l && !l.active && l.stopReason === HELD_ON_RESTORE ? l : undefined;
|
|
25
|
+
}
|
|
16
26
|
|
|
17
27
|
// ---- formatters ----
|
|
18
28
|
|
|
@@ -128,20 +138,26 @@ export function buildStatusText(state: State, audit?: AuditDisplayProgress | nul
|
|
|
128
138
|
return `glla: loop ${arrow} iter ${l.iteration}/${l.maxIterations > 0 ? l.maxIterations : "∞"} · best ${l.bestValue ?? "n/a"} · ${stall}${stallSuffix}`;
|
|
129
139
|
}
|
|
130
140
|
const g = state.goal;
|
|
131
|
-
|
|
141
|
+
const held = heldLoop(state);
|
|
142
|
+
// v0.28.17: a held loop rides every goal state as a compact suffix.
|
|
143
|
+
const heldSuffix = held ? paint(theme, "warning", " · loop⏸held") : "";
|
|
144
|
+
if (!g) {
|
|
145
|
+
if (held) return `glla: loop ${paint(theme, "warning", "⏸ held")} · iter ${held.iteration} — /loop to resume`;
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
132
148
|
if (g.status === "auditing") {
|
|
133
149
|
const tool = audit?.currentTool ? ` · ${audit.currentTool}` : "";
|
|
134
|
-
return `glla: ${paint(theme, "accent", "auditing…")}${tool}`;
|
|
150
|
+
return `glla: ${paint(theme, "accent", "auditing…")}${tool}${heldSuffix}`;
|
|
135
151
|
}
|
|
136
152
|
if (g.status === "paused") {
|
|
137
153
|
const label = `${g.policy} paused ⏸ ${truncate(g.pauseReason ?? "", 40)}`;
|
|
138
|
-
return `glla: ${paint(theme, pauseIsError(g) ? "error" : "warning", label)}`;
|
|
154
|
+
return `glla: ${paint(theme, pauseIsError(g) ? "error" : "warning", label)}${heldSuffix}`;
|
|
139
155
|
}
|
|
140
156
|
if (g.status === "active") {
|
|
141
157
|
// v0.28.1 (S1/S2): a stale-handle interrupt keeps the goal ACTIVE (the
|
|
142
158
|
// next fresh session auto-resumes it) — say so instead of looking healthy.
|
|
143
159
|
if (g.interruptedAt) {
|
|
144
|
-
return `glla: ${g.policy} ${paint(theme, "error", "⚠ interrupted — stale handle · auto-resumes on pi restart")}`;
|
|
160
|
+
return `glla: ${g.policy} ${paint(theme, "error", "⚠ interrupted — stale handle · auto-resumes on pi restart")}${heldSuffix}`;
|
|
145
161
|
}
|
|
146
162
|
// v0.24.7: list policy gets its own wording — a queue item is not a goal.
|
|
147
163
|
// v0.28.11 (U10): goal policy joins it — "list 29" read as a command
|
|
@@ -150,9 +166,11 @@ export function buildStatusText(state: State, audit?: AuditDisplayProgress | nul
|
|
|
150
166
|
const n = state.list?.length ?? 0;
|
|
151
167
|
const queue = n === 0 ? "" : ` · ${n} queued`;
|
|
152
168
|
const tasks = g.taskList ? ` ${countDone(g)}/${countTotal(g)} tasks ·` : "";
|
|
153
|
-
return `glla: ${g.policy} ${paint(theme, "success", "●")}${tasks} ${fmtElapsed(now - Date.parse(g.createdAt))}${queue}`;
|
|
169
|
+
return `glla: ${g.policy} ${paint(theme, "success", "●")}${tasks} ${fmtElapsed(now - Date.parse(g.createdAt))}${queue}${heldSuffix}`;
|
|
154
170
|
}
|
|
155
|
-
|
|
171
|
+
// complete/aborted → clear — but a held loop still shows.
|
|
172
|
+
if (held) return `glla: loop ${paint(theme, "warning", "⏸ held")} · iter ${held.iteration} — /loop to resume`;
|
|
173
|
+
return undefined;
|
|
156
174
|
}
|
|
157
175
|
|
|
158
176
|
function countDone(g: Goal): number {
|
|
@@ -199,9 +217,27 @@ export function buildWidgetLines(state: State, audit?: AuditDisplayProgress | nu
|
|
|
199
217
|
function buildWidgetLinesInner(state: State, audit?: AuditDisplayProgress | null, now = Date.now(), theme?: DisplayTheme, width?: number, extras?: { stalls?: number }): string[] | undefined {
|
|
200
218
|
if (state.loop?.active) return loopLines(state.loop, now, theme, width, extras);
|
|
201
219
|
const g = state.goal;
|
|
202
|
-
|
|
203
|
-
if (g.status === "complete" || g.status === "aborted")
|
|
204
|
-
|
|
220
|
+
const held = heldLoop(state);
|
|
221
|
+
if (!g || g.status === "complete" || g.status === "aborted") {
|
|
222
|
+
// v0.28.17: no visible goal — the held loop gets its own card.
|
|
223
|
+
return held ? heldLoopLines(held, now, theme, width) : undefined;
|
|
224
|
+
}
|
|
225
|
+
const lines = goalLines(g, state, audit, now, theme, width);
|
|
226
|
+
// v0.28.17: a held loop rides the goal card as a trailing line.
|
|
227
|
+
if (held) {
|
|
228
|
+
lines.push(`${paint(theme, "warning", "⏸")} ${truncate(held.target, budgetFor(width, 3, 64))}`);
|
|
229
|
+
lines.push(`└─ ${paint(theme, "dim", `loop held · iter ${held.iteration} — /loop to resume`)}`);
|
|
230
|
+
}
|
|
231
|
+
return lines;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** v0.28.17: standalone card for a restore-held loop (no goal visible). */
|
|
235
|
+
function heldLoopLines(l: LoopState, now: number, theme?: DisplayTheme, width?: number): string[] {
|
|
236
|
+
return [
|
|
237
|
+
`${paint(theme, "warning", "⏸")} ${truncate(l.target, budgetFor(width, 3, 64))}`,
|
|
238
|
+
`├─ loop held · iter ${l.iteration} · ${fmtElapsed(now - Date.parse(l.startedAt))} so far`,
|
|
239
|
+
`└─ ${paint(theme, "dim", "held by the session-restore gate — /loop to resume, /loop stop to drop")}`,
|
|
240
|
+
];
|
|
205
241
|
}
|
|
206
242
|
|
|
207
243
|
// Branch lines sit flush-left (pi-tasks convention): pi's widget renderer
|
|
@@ -31,6 +31,13 @@ export interface LoopRefinement {
|
|
|
31
31
|
newMeasureCmd: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** v0.28.17: stopReason marking a loop parked by the session-restore gate
|
|
35
|
+
* (it was active when the last session ended; the fresh session holds it
|
|
36
|
+
* until the user resumes with /loop). Exported so the display layer can
|
|
37
|
+
* recognize held loops — they must stay VISIBLE in the status/widget,
|
|
38
|
+
* unlike stopped loops which are genuinely gone. */
|
|
39
|
+
export const HELD_ON_RESTORE = "held: restored in a fresh session";
|
|
40
|
+
|
|
34
41
|
export interface LoopState {
|
|
35
42
|
target: string;
|
|
36
43
|
/** v0.23.0: optional — a metricless "spec loop" (measure=none) has no
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -161,6 +161,7 @@ import {
|
|
|
161
161
|
LOOP_DEFAULTS,
|
|
162
162
|
resolveSpecFiles,
|
|
163
163
|
respecTarget,
|
|
164
|
+
HELD_ON_RESTORE,
|
|
164
165
|
type LoopState,
|
|
165
166
|
} from "../goal-loop-forever.js";
|
|
166
167
|
import {
|
|
@@ -184,8 +185,8 @@ import {
|
|
|
184
185
|
// =================================================================
|
|
185
186
|
|
|
186
187
|
const GOAL_EVENT_ENTRY = "goal-event";
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
// HELD_ON_RESTORE (stopReason marker for a restore-held loop) lives in
|
|
189
|
+
// goal-loop-forever.js since v0.28.17 — the display layer imports it too.
|
|
189
190
|
|
|
190
191
|
// =================================================================
|
|
191
192
|
// Module-level state (one per session)
|
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.17",
|
|
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",
|