pi-goal-list-loop-audit 0.38.34 → 0.38.36

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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.38.36 — human-voice terminal summaries (2026-09-08)
4
+
5
+ ### Fixed
6
+
7
+ - **Summaries read as machine receipts (field 20260908_223522/223523):** `clipSummaryValue` now cuts at clause boundaries and strips stranded punctuation (`…append/replace Apply…`, never `…playlist auto-add,…`); the counts line is verdict-only (`— audit: auditor approved (1 verdict).`, raw run stats stay in the archive); the AUDIT PENDING notice states facts instead of agent imperatives. Agent-written summaries get Codex-shape guidance in the continuation prompt (outcome-first, verifiable bullets, clause cuts). Researched from real Codex transcripts + agy binary strings; user chose Codex-style chat shape.
8
+ - **Stale pins updated deliberately:** `— run:` telemetry assertions, `/detached auditor queued/` wordings, continuation-payload byte fixtures (+344/payload, growth exactly linear).
9
+
10
+ ### Verification
11
+
12
+ - Full `release:check`: 2038 pass / 2 skip / 0 fail across 204 files; `tsc --noEmit` clean.
13
+ - Evidence: `audit/HUMAN-VOICE-SUMMARY-2026-09-08.md`.
14
+
15
+ ## 0.38.35 — closed card + smart summary (2026-09-08)
16
+
17
+ ### Fixed
18
+
19
+ - **Card no longer reads cut off (field 2026-09-08 220808):** the head objective now cuts at the last clause boundary inside the budget instead of mid-word (`truncateObjective`; character cut remains the fallback); the steady-state `model: primary … · inherited from session` row is dropped — it restated pi's own status line two rows below; and every card closes — the final `├─`/`│` row becomes `└─` whatever built it (active action row, lone orphan worker row). Provenance still renders on any news (pin, fallbacks, skips, failover); `/goal status` keeps the full chain.
20
+ - **Stale card tests updated deliberately, purpose preserved:** the v0.33.0 slim-card `├─` tail, the v0.38.28 lone-row close, the loop-kind test's incidental model assertion, the narrow-width truncation fixture (now on a pinned row), and the worker-before-footer invariant (detail never lands *after* a footer).
21
+
22
+ ### Verification
23
+
24
+ - Full `release:check`: 2037 pass / 2 skip / 0 fail across 204 files; `tsc --noEmit` clean.
25
+ - Evidence: `audit/UI-CARD-CLOSED-2026-09-08.md`.
26
+
3
27
  ## 0.38.34 — completion communication: fair replay rotation + stale-test refresh (2026-09-08)
4
28
 
5
29
  ### Fixed
package/docs/INDEX.md CHANGED
@@ -18,7 +18,7 @@ For shipped docs, the relevant entry points are:
18
18
  failback; v0.35.9 hardened cross-version npm tarball checks; v0.35.10
19
19
  handles multi-entry npm dry-run reports; v0.35.11 accepts both npm report
20
20
  shapes; v0.35.12 supports npm 12's keyed pack reports; v0.35.13 fixes stale-API recovery loops.
21
- v0.35.14–v0.38.34 continue through the supervisor freeze (`/glla pause`),
21
+ v0.35.14–v0.38.36 continue through the supervisor freeze (`/glla pause`),
22
22
  load hold, auditor picker parity, Windows launch fix, zombie-watchdog
23
23
  subagent carve-out, due-wait backstop, the `/glla agents` visibility panel,
24
24
  durable state-root selection, blank-until-resume auditor context, frozen
@@ -78,18 +78,33 @@ export function isUsefulCompletionSummary(text: string | undefined): boolean {
78
78
  * Missing labels are retained as `not recorded` so a compact notification
79
79
  * cannot accidentally imply evidence that the durable recap did not contain.
80
80
  */
81
- /** Cut a summary value at a word boundary, never mid-word (the
82
- * Screenshot_20260903_204003/204005 complaint: `0 o…`, `qu…`, `belo…`).
83
- * Falls back to a hard cut only when the head holds no space past the
84
- * halfway mark — a long token such as a commit hash must not eviscerate
85
- * the whole value. Short values pass through untouched (no `…`). */
81
+ /** Cut a summary value at a clause boundary, never mid-word (field
82
+ * complaints 2026-09-03 `0 o…` and 2026-09-08 `playlist auto-add,…`:
83
+ * a word-boundary cut that strands dangling punctuation still reads as
84
+ * clipping, not summarizing). Prefer the last clause boundary
85
+ * (`, ; : · — – ( [`) past a floor so the cut reads intentional; fall
86
+ * back to the word break, then to a hard cut only when the head holds
87
+ * no space past the halfway mark — a long token such as a commit hash
88
+ * must not eviscerate the whole value. Trailing punctuation is stripped
89
+ * before the ellipsis. Short values pass through untouched (no `…`). */
86
90
  export function clipSummaryValue(value: string, limit: number): string {
87
91
  const clean = value.replace(/\s+/g, " ").trim();
88
92
  const capped = Number.isFinite(limit) ? Math.max(8, Math.floor(limit)) : 72;
89
93
  if (clean.length <= capped) return clean;
90
94
  const head = clean.slice(0, capped - 1);
95
+ const floor = Math.max(16, Math.floor((capped - 1) * 0.4));
96
+ let boundary = -1;
97
+ for (let i = 0; i < head.length; i++) {
98
+ if (/[,;:·—–(\[]/.test(head[i]!)) boundary = i;
99
+ }
100
+ if (boundary >= floor) {
101
+ const cut = head.slice(0, boundary).replace(/[,;:·—–(\[\s]+$/u, "");
102
+ if (cut.length >= Math.min(floor, 16)) return `${cut}…`;
103
+ }
91
104
  const space = head.lastIndexOf(" ");
92
- const kept = (space > capped / 2 ? head.slice(0, space) : head).trimEnd();
105
+ const kept = (space > capped / 2 ? head.slice(0, space) : head)
106
+ .trimEnd()
107
+ .replace(/[,;:·—–(\[]$/u, "");
93
108
  return `${kept}…`;
94
109
  }
95
110
 
@@ -192,15 +207,12 @@ export function buildApprovalChatLines(notice: {
192
207
  ];
193
208
  }
194
209
 
195
- /** v0.38.25: the audit-goal counts line. Compact execution + audit proof
196
- * built ONLY from durable goal state — turns/file-writes/bash from
197
- * telemetry, verdict counts from auditHistory. Absent facts are named as
198
- * absent, never invented (the recorded-facts honesty rule). */
210
+ /** v0.38.25: the audit-goal counts line — verdict proof ONLY, built from
211
+ * durable goal state. Raw run stats (turns/file-writes/bash calls) read as
212
+ * a machine receipt in user chat (field 2026-09-08 223522/223523); the
213
+ * full execution evidence stays in the six-label archive record. Absent
214
+ * facts are named as absent, never invented (recorded-facts honesty). */
199
215
  export function buildAuditCountsLine(goal: Goal, auditNote?: string): string {
200
- const telemetry = goal.telemetry;
201
- const run = telemetry
202
- ? `${telemetry.turns} turns · ${telemetry.fileWrites} file writes · ${telemetry.bashCalls} bash calls`
203
- : "no execution telemetry was recorded";
204
216
  const history = goal.auditHistory ?? [];
205
217
  const latest = history.length > 0 ? history[history.length - 1] : undefined;
206
218
  const audit = auditNote ?? (latest === undefined
@@ -209,7 +221,7 @@ export function buildAuditCountsLine(goal: Goal, auditNote?: string): string {
209
221
  const verdict = latest.approved ? "approved" : latest.impossible ? "impossible" : latest.disapproved ? "disapproved" : "no verdict";
210
222
  return `auditor ${verdict} (${history.length} verdict${history.length === 1 ? "" : "s"})`;
211
223
  })());
212
- return `— run: ${run} · ${audit}.`;
224
+ return `— audit: ${audit}.`;
213
225
  }
214
226
 
215
227
  export interface TerminalApprovalRenderInput {
@@ -83,6 +83,35 @@ export function truncateCells(s: string, max: number): string {
83
83
  return `${out}…`;
84
84
  }
85
85
 
86
+ /** Clause-aware objective shortening for the card head. A raw character cut
87
+ * ends objectives mid-word ("…restyle the …") which reads as a clipped
88
+ * card rather than a summary (field 2026-09-08 220808). When the text
89
+ * overruns, prefer cutting at the last clause boundary (: ; · — – ( [)
90
+ * inside the budget so the head reads as an intentional summary; fall back
91
+ * to the character cut when no boundary clears the floor. Display-only. */
92
+ export function truncateObjective(s: string, max: number): string {
93
+ const clean = compactDisplayText(s);
94
+ if (tuiVisibleWidth(clean) <= max) return clean;
95
+ if (max <= 1) return truncateCells(clean, max);
96
+ const budget = max - 1;
97
+ const floor = Math.max(16, Math.floor(budget * 0.4));
98
+ let out = "";
99
+ let w = 0;
100
+ let boundaryLen = -1;
101
+ for (const ch of clean) {
102
+ const cw = tuiVisibleWidth(ch);
103
+ if (w + cw > budget) break;
104
+ out += ch;
105
+ w += cw;
106
+ if (/[:;·—–(\[]/.test(ch)) boundaryLen = out.length;
107
+ }
108
+ if (boundaryLen > 0) {
109
+ const cut = [...out].slice(0, boundaryLen).join("").replace(/[:;·—–(\[\s]+$/u, "");
110
+ if (tuiVisibleWidth(cut) >= Math.min(floor, 16)) return `${cut}…`;
111
+ }
112
+ return `${out}…`;
113
+ }
114
+
86
115
  function displayPauseReason(reason: string): string {
87
116
  return compactDisplayText(sanitizeProviderDisplayText(reason));
88
117
  }
@@ -353,26 +382,34 @@ function compactMainModelRecoveryLine(recovery: MainModelRecovery | undefined, c
353
382
 
354
383
  function modelProvenanceLines(provenance: ModelProvenanceDisplay | undefined, width?: number): string[] {
355
384
  if (!provenance) return [];
385
+ const primary = typeof provenance.primary === "string" ? provenance.primary.trim() : "";
386
+ const fallbacks = uniqueModelRefs(provenance.fallbackRefs);
387
+ const skipped = uniqueModelRefs(provenance.skippedForbiddenRefs);
388
+ const handledTurn = typeof provenance.handledTurn === "string" ? provenance.handledTurn.trim() : "";
389
+ const handledTurnNews = !!handledTurn && handledTurn.toLowerCase() !== (primary || "").toLowerCase();
390
+ const handledAudit = typeof provenance.handledAudit === "string" ? provenance.handledAudit.trim() : "";
391
+ // The card sits just above pi's own status line, which already names the
392
+ // session model. A lone `model: primary X · inherited from session` row
393
+ // restates that indicator for zero new information while deepening the
394
+ // tree (field 2026-09-08 220808). Show the block only when it carries
395
+ // news: a pinned selection, fallbacks, skips, or a failover that
396
+ // handled work. `/goal status` keeps the full chain regardless.
397
+ if (primary && provenance.primarySource !== "pinned" && fallbacks.length === 0 && skipped.length === 0 && !handledTurnNews && !handledAudit) return [];
356
398
  const budget = budgetFor(width, 3, 60);
357
399
  const lines: string[] = [];
358
- const primary = typeof provenance.primary === "string" ? provenance.primary.trim() : "";
359
400
  if (primary) {
360
401
  const source = provenance.primarySource === "pinned" ? "pinned" : "inherited from session";
361
402
  lines.push(`model: primary ${truncate(primary, budget)} · ${source}`);
362
403
  }
363
- const fallbacks = uniqueModelRefs(provenance.fallbackRefs);
364
404
  if (fallbacks.length > 0) lines.push(`fallbacks: ${truncate(fallbacks.join(" → "), budget)}`);
365
- const skipped = uniqueModelRefs(provenance.skippedForbiddenRefs);
366
405
  if (skipped.length > 0) lines.push(`skipped forbidden: ${truncate(skipped.join(", "), budget)}`);
367
- const handledTurn = typeof provenance.handledTurn === "string" ? provenance.handledTurn.trim() : "";
368
406
  // A normal turn is handled by the configured primary model. Repeating the
369
407
  // same ref immediately below it adds no information and, when it is the
370
408
  // last row, leaves a misleading continuation glyph in the card. Keep the
371
409
  // row only when a recovery/failover actually handled the turn.
372
- if (handledTurn && handledTurn.toLowerCase() !== primary.toLowerCase()) {
410
+ if (handledTurnNews) {
373
411
  lines.push(`handled turn: ${truncate(handledTurn, budget)}`);
374
412
  }
375
- const handledAudit = typeof provenance.handledAudit === "string" ? provenance.handledAudit.trim() : "";
376
413
  if (handledAudit) {
377
414
  const source = provenance.handledAuditSource?.trim() ? modelSourceLabel(provenance.handledAuditSource) : "";
378
415
  const via = source ? ` · via ${truncate(source, 24)}` : "";
@@ -1450,15 +1487,18 @@ export function buildWidgetLines(state: State, audit?: AuditDisplayProgress | nu
1450
1487
  withAgents = orphanHead ? [orphanHead, ...agentLines] : [...agentLines];
1451
1488
  }
1452
1489
  }
1453
- // Keep the final tree row visually closed when the compact judgment block
1454
- // ends without a queue footer. Without this, its last `├─` reads like a
1455
- // missing continuation and makes an already dense card feel unfinished.
1456
- if (withAgents && inner && detailedAgents.length === 0 && extras?.durableDeferRecommendation && withAgents.length > 0) {
1490
+ // The card always reads as a finished block: a final `├─`/`│` row
1491
+ // promises continuation rows that never come (field 2026-09-08 220808 —
1492
+ // the active card ended on its action row, reading as cut off). Close
1493
+ // the tail whatever built it; rows that already close (`└─`, the head,
1494
+ // the spacer) are untouched. Tree prefixes are literal — paint wraps
1495
+ // content, never the glyph — so this holds themed too.
1496
+ if (withAgents && withAgents.length > 0) {
1457
1497
  const tailIndex = withAgents.length - 1;
1458
1498
  const tail = withAgents[tailIndex]!;
1459
- if (tail.startsWith("├─ ") || tail.startsWith("│ ")) {
1460
- withAgents[tailIndex] = `└─ ${tail.slice(3)}`;
1461
- }
1499
+ const m = tail.match(/^(├─ |│ |│ )/);
1500
+ const prefix = m?.[1] ?? "";
1501
+ if (prefix) withAgents[tailIndex] = `└─ ${tail.slice(prefix.length)}`;
1462
1502
  }
1463
1503
  // v0.28.6 (E1): a persistence failure outranks everything — first line,
1464
1504
  // on every render, until a write lands again.
@@ -1663,7 +1703,7 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
1663
1703
  const headIcon = headLive
1664
1704
  ? paint(theme, headLive.band === "fresh" ? "success" : headLive.band === "aging" ? "warning" : "error", headLive.breath)
1665
1705
  : icon;
1666
- const head = `${headIcon} ${truncate(displayObjective(g.objective), objBudget)} ${paint(theme, "dim", "·")} ${segsText}`;
1706
+ const head = `${headIcon} ${truncateObjective(displayObjective(g.objective), objBudget)} ${paint(theme, "dim", "·")} ${segsText}`;
1667
1707
  const lines = [head];
1668
1708
  // v0.38.8: durable verdict tally as a first-class card row — the widget
1669
1709
  // is the glance surface, and stored verdicts are the progress evidence
@@ -1876,7 +1876,7 @@ function registerAgentTools(pi: any): void {
1876
1876
  if (notifyFailure) current.ui.notify(`Completion auditor worker failed to settle (infrastructure, not a verdict). The stored claim is safe; ${activeGoalSurfaceCommand("resume")} retries it.`, "warning");
1877
1877
  });
1878
1878
  return {
1879
- content: [{ type: "text", text: `AUDIT PENDING — nonterminal. Completion claim persisted; detached auditor queued (model: ${via ?? "setting"}). The goal is not approved or complete. Do not claim completion or give a final success summary. Do not wait or poll: GLLA will post the concrete final summary after verified approval and durable archive; rejection resumes work.` }],
1879
+ content: [{ type: "text", text: `AUDIT PENDING — nonterminal. The completion claim is stored and a detached auditor is settling it (model: ${via ?? "setting"}). The goal is not approved or complete yet. The final summary posts here after verified approval and durable archive; rejection resumes work automatically. Ending this turn now is correct — no waiting or polling needed.` }],
1880
1880
  details: { status: "audit-pending", terminal: false },
1881
1881
  // End this tool batch, not the goal; the detached worker owns settlement.
1882
1882
  terminate: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.38.34",
3
+ "version": "0.38.36",
4
4
  "description": "Mission control for autonomous pi: interview-drafted goals, an audited task queue, and forever-loops (metric, spec, project-audit) that run for hours. A detached extension-less auditor process re-verifies every completion with raw evidence without holding the main pi turn; confirmed drafts, decision pauses and consent gates keep you in charge.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "dracon",
@@ -196,3 +196,5 @@ The orchestrator's backstop is the stall watchdog: three consecutive turns with
196
196
  ### Completion communication
197
197
 
198
198
  `complete_goal` submits a nonterminal claim, not a completion verdict. While the detached audit is pending, do not say the goal is done/complete/approved or present a final success summary. Do not wait or poll. If a response is needed, give one brief pending-status sentence. GLLA posts the concrete outcome-first summary into chat after verified approval and durable archive, without needing another user prompt. Do not duplicate that summary. A rejected claim remains unfinished and repair work continues.
199
+
200
+ The agent-written completionSummary is user-facing evidence, not a step log: open with the outcome in plain words, then one verifiable result per label with its proof inline (test counts, file paths, commit refs) — never raw run stats. Cut values at clause boundaries, never mid-word; state what was deliberately left out when it matters.