pi-goal-list-loop-audit 0.22.1 → 0.22.3

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.
@@ -208,7 +208,15 @@ export async function runGoalCompletionAuditor(args: {
208
208
  cwd: ctx.cwd,
209
209
  model,
210
210
  thinkingLevel,
211
- modelRegistry: ctx.modelRegistry,
211
+ // Pass the PARENT's ModelRuntime (v0.22.2). createAgentSession has no
212
+ // "modelRegistry" option — passing the facade was silently ignored and
213
+ // a FRESH runtime was built from auth.json/models.json, which has no
214
+ // extension-registered providers. Streaming a model from such a
215
+ // provider (e.g. one with a custom streamSimple wrapper) then failed
216
+ // inside the stream and the auditor produced zero output. The facade
217
+ // keeps the runtime in a TS-private field; reach it defensively and
218
+ // fall back to the default (fresh runtime) if pi ever reshapes it.
219
+ modelRuntime: (ctx.modelRegistry as any)?.runtime,
212
220
  resourceLoader: makeAuditorResourceLoader(),
213
221
  sessionManager: SessionManager.inMemory(ctx.cwd),
214
222
  // Compaction ENABLED (v0.4.0, closes pi-goal-x flaw #3: context exhaustion
@@ -256,6 +264,13 @@ export async function runGoalCompletionAuditor(args: {
256
264
  if (event.type === "message_end") {
257
265
  const message = event.message as any;
258
266
  if (message?.role !== "assistant") return;
267
+ // Stream failures surface as an assistant message with stopReason
268
+ // "error" + errorMessage — NOT as an "error" event (v0.22.2: this
269
+ // is why "unknown provider"/driver failures looked like a silent
270
+ // empty report).
271
+ if (message.stopReason === "error" && typeof message.errorMessage === "string" && message.errorMessage.trim()) {
272
+ streamError = message.errorMessage.slice(0, 300);
273
+ }
259
274
  for (const part of message.content ?? []) {
260
275
  if (part.type === "text" && typeof part.text === "string") outputParts.push(part.text);
261
276
  }
@@ -37,6 +37,18 @@ export function truncate(s: string, max: number): string {
37
37
  return s.length <= max ? s : s.slice(0, Math.max(0, max - 1)) + "…";
38
38
  }
39
39
 
40
+ /**
41
+ * Width-aware truncation budget (v0.22.2). The hardcoded caps are FLOORS for
42
+ * narrow terminals; when the terminal is wider, lines may use the available
43
+ * width instead of being cut at a fixed ~60 chars (pi-tasks truncates at
44
+ * tui.terminal.columns — match that behavior). `prefixCols` is the visible
45
+ * width of the static prefix on the line (branch glyph + pi's 1-col gutter).
46
+ */
47
+ function budgetFor(width: number | undefined, prefixCols: number, floor: number): number {
48
+ if (!width || width <= 0) return floor;
49
+ return Math.max(floor, width - 1 - prefixCols);
50
+ }
51
+
40
52
  function sinceIso(iso: string): number {
41
53
  const t = Date.parse(iso);
42
54
  return Number.isFinite(t) ? Date.now() - t : 0;
@@ -122,17 +134,17 @@ function countTotal(g: Goal): number {
122
134
  * Widget lines for ctx.ui.setWidget("pi-glla", lines).
123
135
  * Returns undefined when nothing is worth showing.
124
136
  */
125
- export function buildWidgetLines(state: State, audit?: AuditDisplayProgress | null, now = Date.now(), theme?: DisplayTheme): string[] | undefined {
126
- if (state.loop?.active) return loopLines(state.loop, now, theme);
137
+ export function buildWidgetLines(state: State, audit?: AuditDisplayProgress | null, now = Date.now(), theme?: DisplayTheme, width?: number): string[] | undefined {
138
+ if (state.loop?.active) return loopLines(state.loop, now, theme, width);
127
139
  const g = state.goal;
128
140
  if (!g) return undefined;
129
141
  if (g.status === "complete" || g.status === "aborted") return undefined;
130
- return goalLines(g, state, audit, now, theme);
142
+ return goalLines(g, state, audit, now, theme, width);
131
143
  }
132
144
 
133
145
  // Branch lines sit flush-left (pi-tasks convention): pi's widget renderer
134
146
  // adds its own one-space gutter, so any indent here doubles up.
135
- function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | undefined, now: number, theme?: DisplayTheme): string[] {
147
+ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | undefined, now: number, theme?: DisplayTheme, width?: number): string[] {
136
148
  // Head glyph is ● (not ◆): U+25C6 renders as a color-emoji diamond in some
137
149
  // terminal fonts and ignores ANSI color; ● takes the paint everywhere.
138
150
  const icon =
@@ -141,7 +153,7 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
141
153
  : g.status === "auditing"
142
154
  ? paint(theme, "accent", "⟡")
143
155
  : paint(theme, "success", "●");
144
- const head = `${icon} ${truncate(g.objective.replace(/\s+/g, " "), 64)}`;
156
+ const head = `${icon} ${truncate(g.objective.replace(/\s+/g, " "), budgetFor(width, 3, 64))}`;
145
157
  const statusWord = g.status === "active" ? paint(theme, "success", "active") : g.status;
146
158
  // Token segment only when a budget is set (v0.22.0): the guard is opt-in,
147
159
  // and "0/0 tok" carried no information when off.
@@ -155,29 +167,29 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
155
167
  return lines;
156
168
  }
157
169
  if (g.status === "paused" && g.pauseReason) {
158
- lines.push(`├─ ${paint(theme, pauseIsError(g) ? "error" : "warning", truncate(g.pauseReason, 60))}`);
159
- if (g.pauseSuggestedAction) lines.push(`└─ ${paint(theme, "dim", truncate(g.pauseSuggestedAction, 60))}`);
170
+ lines.push(`├─ ${paint(theme, pauseIsError(g) ? "error" : "warning", truncate(g.pauseReason, budgetFor(width, 3, 60)))}`);
171
+ if (g.pauseSuggestedAction) lines.push(`└─ ${paint(theme, "dim", truncate(g.pauseSuggestedAction, budgetFor(width, 3, 60)))}`);
160
172
  return lines;
161
173
  }
162
174
  const next = nextPending(g);
163
- if (next) lines.push(`├─ next: ${truncate(next, 56)}`);
175
+ if (next) lines.push(`├─ next: ${truncate(next, budgetFor(width, 9, 56))}`);
164
176
  const queue = state.list?.length ?? 0;
165
177
  lines.push(`└─ ${paint(theme, "dim", `${queue > 0 ? `list ${queue} · ` : ""}/goal status · /glla`)}`);
166
178
  return lines;
167
179
  }
168
180
 
169
- function loopLines(l: LoopState, now: number, theme?: DisplayTheme): string[] {
181
+ function loopLines(l: LoopState, now: number, theme?: DisplayTheme, width?: number): string[] {
170
182
  const arrow = paint(theme, "accent", l.direction === "min" ? "↓" : "↑");
171
183
  const best = paint(theme, "success", `${l.bestValue ?? "n/a"}`);
172
184
  const stallText = `stall ${l.stallCount}/${l.plateauWindow}`;
173
185
  const stall = l.stallCount >= l.plateauWindow - 1 ? paint(theme, "warning", stallText) : stallText;
174
186
  const lines = [
175
- `${paint(theme, "accent", "●")} ${truncate(l.target, 64)}`,
187
+ `${paint(theme, "accent", "●")} ${truncate(l.target, budgetFor(width, 3, 64))}`,
176
188
  `├─ loop ${arrow} iter ${l.iteration}/${l.maxIterations} · ${fmtElapsed(now - Date.parse(l.startedAt))}`,
177
189
  `├─ best ${best} · last ${l.lastValue ?? "n/a"} · ${stall}`,
178
- `└─ ${paint(theme, "dim", truncate(l.measureCmd, 56))}`,
190
+ `└─ ${paint(theme, "dim", truncate(l.measureCmd, budgetFor(width, 3, 56)))}`,
179
191
  ];
180
- if (l.branchName) lines.push(`⎇ ${paint(theme, "muted", truncate(l.branchName, 50))}`);
192
+ if (l.branchName) lines.push(`⎇ ${paint(theme, "muted", truncate(l.branchName, budgetFor(width, 3, 50)))}`);
181
193
  return lines;
182
194
  }
183
195
 
@@ -144,8 +144,11 @@ function refreshUI(ctx: ExtensionContext): void {
144
144
  if (!ctx.hasUI) return;
145
145
  try {
146
146
  const theme = ctx.ui.theme as unknown as import("../goal-loop-display.js").DisplayTheme | undefined;
147
+ // Terminal width for truncation budgets: on wide terminals the widget
148
+ // uses the room instead of cutting at fixed ~60-char floors.
149
+ const width = process.stdout.columns || 80;
147
150
  ctx.ui.setStatus("pi-glla", buildStatusText(state, latestAuditProgress, Date.now(), theme));
148
- ctx.ui.setWidget("pi-glla", buildWidgetLines(state, latestAuditProgress, Date.now(), theme));
151
+ ctx.ui.setWidget("pi-glla", buildWidgetLines(state, latestAuditProgress, Date.now(), theme, width));
149
152
  } catch {
150
153
  // stale ctx — next event refreshes
151
154
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.22.1",
3
+ "version": "0.22.3",
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",
@@ -55,7 +55,7 @@
55
55
  "devDependencies": {
56
56
  "@earendil-works/pi-agent-core": "^0.74.0",
57
57
  "@earendil-works/pi-ai": "^0.74.0",
58
- "@earendil-works/pi-coding-agent": "^0.74.0",
58
+ "@earendil-works/pi-coding-agent": "^0.81.1",
59
59
  "@earendil-works/pi-tui": "^0.74.0",
60
60
  "typebox": "^1.0.58",
61
61
  "typescript": "^5.9.3"