pi-goal-list-loop-audit 0.19.3 → 0.20.1
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.
|
@@ -17,9 +17,11 @@ export function fmtElapsed(ms: number): string {
|
|
|
17
17
|
const s = Math.floor(ms / 1000);
|
|
18
18
|
if (s < 60) return `${s}s`;
|
|
19
19
|
const m = Math.floor(s / 60);
|
|
20
|
-
|
|
20
|
+
// Seconds stay visible up to the hour: the elapsed counter is the
|
|
21
|
+
// liveness signal — minute-only granularity looks frozen on a 1s tick.
|
|
22
|
+
if (m < 60) return `${m}m ${String(s % 60).padStart(2, "0")}s`;
|
|
21
23
|
const h = Math.floor(m / 60);
|
|
22
|
-
return `${h}h${m % 60}m`;
|
|
24
|
+
return `${h}h ${String(m % 60).padStart(2, "0")}m`;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export function fmtTokens(n: number): string {
|
|
@@ -37,6 +39,18 @@ function sinceIso(iso: string): number {
|
|
|
37
39
|
return Number.isFinite(t) ? Date.now() - t : 0;
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
// ---- semantic colors (optional; tests call without a theme → plain strings) ----
|
|
43
|
+
|
|
44
|
+
export type DisplayColor = "accent" | "success" | "warning" | "error" | "muted" | "dim";
|
|
45
|
+
export interface DisplayTheme {
|
|
46
|
+
fg(color: DisplayColor, text: string): string;
|
|
47
|
+
}
|
|
48
|
+
const paint = (theme: DisplayTheme | undefined, color: DisplayColor, text: string): string => (theme ? theme.fg(color, text) : text);
|
|
49
|
+
|
|
50
|
+
/** Pause reasons that mean "something broke", not "waiting on the user". */
|
|
51
|
+
const ERROR_PAUSE = /token limit|stalled|infra|auditor.*fail/i;
|
|
52
|
+
const pauseIsError = (g: Goal): boolean => ERROR_PAUSE.test(g.pauseReason ?? "");
|
|
53
|
+
|
|
40
54
|
// ---- status line (one-liner, always-on) ----
|
|
41
55
|
|
|
42
56
|
export interface AuditDisplayProgress {
|
|
@@ -49,25 +63,28 @@ export interface AuditDisplayProgress {
|
|
|
49
63
|
* One-line status for ctx.ui.setStatus("pi-glla", …).
|
|
50
64
|
* Returns undefined when nothing is being supervised (clears the segment).
|
|
51
65
|
*/
|
|
52
|
-
export function buildStatusText(state: State, audit?: AuditDisplayProgress | null, now = Date.now()): string | undefined {
|
|
66
|
+
export function buildStatusText(state: State, audit?: AuditDisplayProgress | null, now = Date.now(), theme?: DisplayTheme): string | undefined {
|
|
53
67
|
if (state.loop?.active) {
|
|
54
68
|
const l = state.loop;
|
|
55
|
-
const arrow = l.direction === "min" ? "↓" : "↑";
|
|
56
|
-
|
|
69
|
+
const arrow = paint(theme, "accent", l.direction === "min" ? "↓" : "↑");
|
|
70
|
+
const stallText = `stall ${l.stallCount}/${l.plateauWindow}`;
|
|
71
|
+
const stall = l.stallCount >= l.plateauWindow - 1 ? paint(theme, "warning", stallText) : stallText;
|
|
72
|
+
return `glla: loop ${arrow} iter ${l.iteration}/${l.maxIterations} · best ${l.bestValue ?? "n/a"} · ${stall}`;
|
|
57
73
|
}
|
|
58
74
|
const g = state.goal;
|
|
59
75
|
if (!g) return undefined;
|
|
60
76
|
if (g.status === "auditing") {
|
|
61
77
|
const tool = audit?.currentTool ? ` · ${audit.currentTool}` : "";
|
|
62
|
-
return `glla: auditing
|
|
78
|
+
return `glla: ${paint(theme, "accent", "auditing…")}${tool}`;
|
|
63
79
|
}
|
|
64
80
|
if (g.status === "paused") {
|
|
65
|
-
|
|
81
|
+
const label = `paused ⏸ ${truncate(g.pauseReason ?? "", 40)}`;
|
|
82
|
+
return `glla: ${paint(theme, pauseIsError(g) ? "error" : "warning", label)}`;
|
|
66
83
|
}
|
|
67
84
|
if (g.status === "active") {
|
|
68
85
|
const queue = state.list?.length ? ` · list ${state.list.length}` : "";
|
|
69
86
|
const tasks = g.taskList ? ` ${countDone(g)}/${countTotal(g)} tasks ·` : "";
|
|
70
|
-
return `glla: ${g.policy}
|
|
87
|
+
return `glla: ${g.policy} ${paint(theme, "success", "●")}${tasks} ${fmtElapsed(now - Date.parse(g.createdAt))}${queue}`;
|
|
71
88
|
}
|
|
72
89
|
return undefined; // complete/aborted → clear
|
|
73
90
|
}
|
|
@@ -102,52 +119,60 @@ function countTotal(g: Goal): number {
|
|
|
102
119
|
* Widget lines for ctx.ui.setWidget("pi-glla", lines).
|
|
103
120
|
* Returns undefined when nothing is worth showing.
|
|
104
121
|
*/
|
|
105
|
-
export function buildWidgetLines(state: State, audit?: AuditDisplayProgress | null, now = Date.now()): string[] | undefined {
|
|
106
|
-
if (state.loop?.active) return loopLines(state.loop, now);
|
|
122
|
+
export function buildWidgetLines(state: State, audit?: AuditDisplayProgress | null, now = Date.now(), theme?: DisplayTheme): string[] | undefined {
|
|
123
|
+
if (state.loop?.active) return loopLines(state.loop, now, theme);
|
|
107
124
|
const g = state.goal;
|
|
108
125
|
if (!g) return undefined;
|
|
109
126
|
if (g.status === "complete" || g.status === "aborted") return undefined;
|
|
110
|
-
return goalLines(g, state, audit, now);
|
|
127
|
+
return goalLines(g, state, audit, now, theme);
|
|
111
128
|
}
|
|
112
129
|
|
|
113
|
-
|
|
114
|
-
|
|
130
|
+
// Branch lines indent one space (pi-tasks convention): the tree sits under
|
|
131
|
+
// the head glyph, text column consistent across widgets.
|
|
132
|
+
function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | undefined, now: number, theme?: DisplayTheme): string[] {
|
|
133
|
+
const icon =
|
|
134
|
+
g.status === "paused"
|
|
135
|
+
? paint(theme, pauseIsError(g) ? "error" : "warning", "⏸")
|
|
136
|
+
: g.status === "auditing"
|
|
137
|
+
? paint(theme, "accent", "⟡")
|
|
138
|
+
: paint(theme, "success", "◆");
|
|
115
139
|
const head = `${icon} ${truncate(g.objective.replace(/\s+/g, " "), 64)}`;
|
|
116
|
-
const
|
|
140
|
+
const statusWord = g.status === "active" ? paint(theme, "success", "active") : g.status;
|
|
141
|
+
const tokens = paint(theme, "dim", `${fmtTokens(g.usage?.tokensUsed ?? 0)}/${fmtTokens(g.usage?.tokensLimit ?? 10_000_000)} tok`);
|
|
142
|
+
const lines = [head, ` ├─ ${statusWord} · ${fmtElapsed(now - Date.parse(g.createdAt))} · ${tokens}`];
|
|
117
143
|
if (g.status === "auditing") {
|
|
118
|
-
lines.push(
|
|
119
|
-
if (audit?.elapsedMs) lines.push(
|
|
120
|
-
else lines.push(
|
|
144
|
+
lines.push(` ├─ auditor: ${audit?.label ?? "running"}${audit?.currentTool ? ` · ${truncate(audit.currentTool, 30)}` : ""}`);
|
|
145
|
+
if (audit?.elapsedMs) lines.push(` └─ ${paint(theme, "dim", `${fmtElapsed(audit.elapsedMs)} in isolated session`)}`);
|
|
146
|
+
else lines.push(` └─ ${paint(theme, "dim", "isolated session, read-only tools")}`);
|
|
121
147
|
return lines;
|
|
122
148
|
}
|
|
123
149
|
if (g.status === "paused" && g.pauseReason) {
|
|
124
|
-
lines.push(
|
|
125
|
-
if (g.pauseSuggestedAction) lines.push(
|
|
150
|
+
lines.push(` ├─ ${paint(theme, pauseIsError(g) ? "error" : "warning", truncate(g.pauseReason, 60))}`);
|
|
151
|
+
if (g.pauseSuggestedAction) lines.push(` └─ ${paint(theme, "dim", truncate(g.pauseSuggestedAction, 60))}`);
|
|
126
152
|
return lines;
|
|
127
153
|
}
|
|
128
154
|
const next = nextPending(g);
|
|
129
|
-
if (next) lines.push(
|
|
155
|
+
if (next) lines.push(` ├─ next: ${truncate(next, 56)}`);
|
|
130
156
|
const queue = state.list?.length ?? 0;
|
|
131
|
-
lines.push(
|
|
157
|
+
lines.push(` └─ ${paint(theme, "dim", `${queue > 0 ? `list ${queue} · ` : ""}/goal status · /glla`)}`);
|
|
132
158
|
return lines;
|
|
133
159
|
}
|
|
134
160
|
|
|
135
|
-
function loopLines(l: LoopState, now: number): string[] {
|
|
136
|
-
const arrow = l.direction === "min" ? "↓" : "↑";
|
|
161
|
+
function loopLines(l: LoopState, now: number, theme?: DisplayTheme): string[] {
|
|
162
|
+
const arrow = paint(theme, "accent", l.direction === "min" ? "↓" : "↑");
|
|
163
|
+
const best = paint(theme, "success", `${l.bestValue ?? "n/a"}`);
|
|
164
|
+
const stallText = `stall ${l.stallCount}/${l.plateauWindow}`;
|
|
165
|
+
const stall = l.stallCount >= l.plateauWindow - 1 ? paint(theme, "warning", stallText) : stallText;
|
|
137
166
|
const lines = [
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
167
|
+
`${paint(theme, "accent", "◆")} ${truncate(l.target, 64)}`,
|
|
168
|
+
` ├─ loop ${arrow} iter ${l.iteration}/${l.maxIterations} · ${fmtElapsed(now - Date.parse(l.startedAt))}`,
|
|
169
|
+
` ├─ best ${best} · last ${l.lastValue ?? "n/a"} · ${stall}`,
|
|
170
|
+
` └─ ${paint(theme, "dim", truncate(l.measureCmd, 56))}`,
|
|
142
171
|
];
|
|
143
|
-
if (l.branchName) lines.push(`
|
|
172
|
+
if (l.branchName) lines.push(` ⎇ ${paint(theme, "muted", truncate(l.branchName, 50))}`);
|
|
144
173
|
return lines;
|
|
145
174
|
}
|
|
146
175
|
|
|
147
|
-
function statusLabel(s: string): string {
|
|
148
|
-
return s === "active" ? "active" : s;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
176
|
function nextPending(g: Goal): string | undefined {
|
|
152
177
|
const tasks = g.taskList?.tasks ?? [];
|
|
153
178
|
const queue = [...tasks];
|
|
@@ -60,7 +60,7 @@ export interface LoopState {
|
|
|
60
60
|
export function loopBranchName(startedAtIso: string, target: string): string {
|
|
61
61
|
const stamp = startedAtIso.replace(/[^0-9]/g, "").slice(0, 14);
|
|
62
62
|
const slug = target.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 30) || "loop";
|
|
63
|
-
return `pi-
|
|
63
|
+
return `pi-glla-loop/${stamp}-${slug}`;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export const LOOP_DEFAULTS = {
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -145,8 +145,9 @@ let uiTicker: NodeJS.Timeout | null = null;
|
|
|
145
145
|
function refreshUI(ctx: ExtensionContext): void {
|
|
146
146
|
if (!ctx.hasUI) return;
|
|
147
147
|
try {
|
|
148
|
-
ctx.ui.
|
|
149
|
-
ctx.ui.
|
|
148
|
+
const theme = ctx.ui.theme as unknown as import("../goal-loop-display.js").DisplayTheme | undefined;
|
|
149
|
+
ctx.ui.setStatus("pi-glla", buildStatusText(state, latestAuditProgress, Date.now(), theme));
|
|
150
|
+
ctx.ui.setWidget("pi-glla", buildWidgetLines(state, latestAuditProgress, Date.now(), theme));
|
|
150
151
|
} catch {
|
|
151
152
|
// stale ctx — next event refreshes
|
|
152
153
|
}
|
|
@@ -157,7 +158,7 @@ function startUITicker(): void {
|
|
|
157
158
|
uiTicker = setInterval(() => {
|
|
158
159
|
const ctx = freshCtx();
|
|
159
160
|
if (ctx && isSupervising()) refreshUI(ctx);
|
|
160
|
-
},
|
|
161
|
+
}, 1_000);
|
|
161
162
|
uiTicker.unref?.();
|
|
162
163
|
}
|
|
163
164
|
|
|
@@ -1018,7 +1019,7 @@ async function runLoopTick(ctx: ExtensionContext, event?: any): Promise<void> {
|
|
|
1018
1019
|
if (loop.branchName && outcome.kind === "continue") {
|
|
1019
1020
|
if (outcome.improved) {
|
|
1020
1021
|
await runGit(ctx, ["add", "-A"]);
|
|
1021
|
-
const committed = await runGit(ctx, ["commit", "-m", `pi-
|
|
1022
|
+
const committed = await runGit(ctx, ["commit", "-m", `pi-glla-loop: iteration ${loop.iteration} (${loop.direction}=${loop.bestValue})`]);
|
|
1022
1023
|
appendLedger(ctx.cwd, "loop_git", { action: "commit", iteration: loop.iteration, ok: committed.ok });
|
|
1023
1024
|
} else {
|
|
1024
1025
|
const reset = await runGit(ctx, ["reset", "--hard", "HEAD"]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
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",
|