pi-goal-list-loop-audit 0.38.37 → 0.38.39

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.39 — Codex-like terminal summary (2026-09-09)
4
+
5
+ ### Fixed
6
+
7
+ - **Terminal `[goal-event]` summary was not Codex-like (field 20260909_013733):** `•` detail bullets followed by dangling `—` trailer lines, every bullet trailing into `…`, a raw `/var/tmp/…` log path in chat, and no next action. The render is now one voice: uniform `•` bullets (approval/counts/record ride as bullets, record stays last), machine paths (`/tmp/…`, `/var/tmp/…`, `*.tgz`, receipt-only paren groups) stripped from chat bullets (archive keeps the full text), `+` joins the clause-boundary cut set, and `withoutStaleNext` is selective — audit-self-referential Next lines still drop but the first concrete next action survives as the closing bullet ahead of the non-do.
8
+ - **Claim-side guidance:** continuation prompt budgets each label value to ~90 chars, bans machine paths from the recap, and asks for one concrete `Next:` or none.
9
+
10
+ ### Verification
11
+
12
+ - New pins: uniform-voice, machine-path strip, `+`-boundary cut, next-action ordering, record-last; full `release:check` 0 failures; `tsc --noEmit` clean.
13
+ - Evidence: `audit/CODEX-SUMMARY-2026-09-09.md`. Archive format and delivery/replay mechanics unchanged.
14
+
15
+ ## 0.38.38 — elapsed-time duplication cut + PR #47 closed (2026-09-09)
16
+
17
+ ### Fixed
18
+
19
+ - **Elapsed time in two places (field 20260909_002132):** the `setStatus` one-liner no longer carries `total …` on the idle/busy/working branches — other sessions' tab strips showed `glla: [WORKING] total …` under a card already reading it. The card head keeps the timer unconditionally; state + task/queue counts stay on the status line. (`Chrome Bridge`/`(indefinite)` framing is pi core, not GLLA — only our timer text was cut.)
20
+ - **PR #47 triage:** KV-cache checkpoint complaint verified real on main but the strip shape is unsafe as-is (post-compaction blindness, no compensating pointer) — replied per-piece, gave the contributor a merge-based re-sync command, closed without merging per the #45/#46 precedent (live `active.jsonl`, 39-behind divergence, bundled features). Per-task audits stay held pending cost review.
21
+
22
+ ### Verification
23
+
24
+ - New pins: status-without-timer + card-keeps-timer; full `release:check` 0 failures; `tsc --noEmit` clean.
25
+ - Evidence: `audit/ELAPSED-DUPLICATION-PR47-2026-09-09.md`. No open PRs.
26
+
3
27
  ## 0.38.37 — bullets + deliberate non-do (2026-09-09)
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.37 continue through the supervisor freeze (`/glla pause`),
21
+ v0.35.14–v0.38.39 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
@@ -86,7 +86,7 @@ export function isUsefulCompletionSummary(text: string | undefined): boolean {
86
86
  * back to the word break, then to a hard cut only when the head holds
87
87
  * no space past the halfway mark — a long token such as a commit hash
88
88
  * must not eviscerate the whole value. Trailing punctuation is stripped
89
- * before the ellipsis. Short values pass through untouched (no `…`). */
89
+ * `npm version…+ latest` cut inside a `+`-joined list). */
90
90
  export function clipSummaryValue(value: string, limit: number): string {
91
91
  const clean = value.replace(/\s+/g, " ").trim();
92
92
  const capped = Number.isFinite(limit) ? Math.max(8, Math.floor(limit)) : 72;
@@ -95,16 +95,16 @@ export function clipSummaryValue(value: string, limit: number): string {
95
95
  const floor = Math.max(16, Math.floor((capped - 1) * 0.4));
96
96
  let boundary = -1;
97
97
  for (let i = 0; i < head.length; i++) {
98
- if (/[,;:·—–(\[]/.test(head[i]!)) boundary = i;
98
+ if (/[,;:·—–(+[\[]/.test(head[i]!)) boundary = i;
99
99
  }
100
100
  if (boundary >= floor) {
101
- const cut = head.slice(0, boundary).replace(/[,;:·—–(\[\s]+$/u, "");
101
+ const cut = head.slice(0, boundary).replace(/[,;:·—–(+[\[\s]+$/u, "");
102
102
  if (cut.length >= Math.min(floor, 16)) return `${cut}…`;
103
103
  }
104
104
  const space = head.lastIndexOf(" ");
105
105
  const kept = (space > capped / 2 ? head.slice(0, space) : head)
106
106
  .trimEnd()
107
- .replace(/[,;:·—–(\[]$/u, "");
107
+ .replace(/[,;:·—–(+\[]$/u, "");
108
108
  return `${kept}…`;
109
109
  }
110
110
 
@@ -152,6 +152,34 @@ export interface HumanCompletionBrief {
152
152
  details: string[];
153
153
  }
154
154
 
155
+ /** v0.38.39 (field 20260909_013733): absolute machine paths read as a
156
+ * machine receipt in user chat (`Tests: … (/var/tmp/glla-….log…`). The
157
+ * chat brief strips `/tmp/…`, `/var/tmp/…`, and `*.tgz` tokens so bullets
158
+ * carry human-readable proof (counts, versions, repo-relative paths);
159
+ * the durable archive keeps the full text. Falls back to the original
160
+ * when stripping would empty the value. */
161
+ export function chatSafeDetailValue(value: string): string {
162
+ const withoutGroups = value.replace(/\([^()]*\)/g, (group) => {
163
+ // A parenthesized group that carries nothing but stripped tokens and
164
+ // receipt words is machine packaging — drop the whole group so no
165
+ // `( tarball )` husk survives. Groups with real words stay verbatim.
166
+ const inner = group
167
+ .slice(1, -1)
168
+ .replace(/(?:\/var)?\/tmp\/\S+/g, "")
169
+ .replace(/\S+\.tgz\b/g, "")
170
+ .replace(/\btarballs?\b|\blogs?\b/gi, "");
171
+ return /[a-z]/i.test(inner) ? group : "";
172
+ });
173
+ const stripped = withoutGroups
174
+ .replace(/(?:\/var)?\/tmp\/\S+/g, "")
175
+ .replace(/\btarballs?\s+\S+\.tgz\b/gi, "")
176
+ .replace(/\S+\.tgz\b/g, "")
177
+ .replace(/\(\s*\)/g, "")
178
+ .replace(/\s{2,}/g, " ")
179
+ .trim();
180
+ return stripped || value;
181
+ }
182
+
155
183
  /** The human briefing: outcome first in its own words, then only the
156
184
  * labels that carry real content (filler like `Unresolved: none` or
157
185
  * `Changed: not recorded` is dropped, never shown). The durable archive
@@ -170,7 +198,9 @@ export function humanCompletionBrief(
170
198
  const separator = line.indexOf(":");
171
199
  if (separator < 0) continue;
172
200
  const content = briefValueContent(line.slice(separator + 1));
173
- if (content) details.push(`${line.slice(0, separator)}: ${clipSummaryValue(content, valueBudget)}`);
201
+ // v0.38.39: machine paths are archive evidence, not chat evidence —
202
+ // strip before clipping so the budget applies to the human text.
203
+ if (content) details.push(`${line.slice(0, separator)}: ${clipSummaryValue(chatSafeDetailValue(content), valueBudget)}`);
174
204
  }
175
205
  return { outcome, details };
176
206
  }
@@ -178,10 +208,27 @@ export function humanCompletionBrief(
178
208
  /** v0.38.20: the agent's `Next:` recap line goes stale the moment the
179
209
  * verdict lands — reprinting it next to the approval trailer reads as
180
210
  * complete-before-verify (field 2026-09-04: `Next: detached auditor
181
- * verdict decides.` printed above `— auditor … approved.`). Every approval
182
- * surface strips it; the full six-label record stays in the archive. */
211
+ * verdict decides.` printed above `— auditor … approved.`).
212
+ * v0.38.39 (field 20260909_013733): the Codex close wants at most ONE
213
+ * concrete next action, so the filter is now selective instead of total:
214
+ * self-referential audit/process lines (`verdict decides`, `awaiting
215
+ * approval`, …) still drop, but the first concrete action survives as
216
+ * the closing bullet. The full six-label record stays in the archive. */
217
+ const STALE_NEXT_PATTERN = /auditor|verdict|approv|audit|settl|review/i;
183
218
  export function withoutStaleNext(details: string[] | undefined): string[] {
184
- return (details ?? []).filter((d) => !/^\s*Next\s*:/i.test(d));
219
+ const kept: string[] = [];
220
+ let actionKept = false;
221
+ for (const detail of details ?? []) {
222
+ if (!/^\s*Next\s*:/i.test(detail)) {
223
+ kept.push(detail);
224
+ continue;
225
+ }
226
+ if (STALE_NEXT_PATTERN.test(detail)) continue;
227
+ if (actionKept) continue;
228
+ actionKept = true;
229
+ kept.push(detail);
230
+ }
231
+ return kept;
185
232
  }
186
233
 
187
234
  /** v0.38.20: the approval chat notify — outcome first, all bounded
@@ -202,13 +249,24 @@ export function buildApprovalChatLines(notice: {
202
249
  `✓ done — ${notice.outcome}`,
203
250
  // v0.38.37 (audit 2026-09-08): one verifiable-result bullet per
204
251
  // informing detail — the Codex closing shape.
252
+ // v0.38.39: the approval/counts/record trailer rides as bullets too
253
+ // (field 20260909_013733 — the `—` tail read as a second voice); the
254
+ // record pointer stays last.
205
255
  ...withoutStaleNext(notice.details).map((detail) => `• ${detail}`),
206
- notice.approval,
207
- ...(notice.counts ? [notice.counts] : []),
208
- notice.record,
256
+ trailerBullet(notice.approval),
257
+ ...(notice.counts ? [trailerBullet(notice.counts)] : []),
258
+ trailerBullet(notice.record),
209
259
  ];
210
260
  }
211
261
 
262
+ /** v0.38.39 (field 20260909_013733): the trailer rides as bullets too —
263
+ * the Codex close is uniform `•` lines (`• Changed:` … `• record:`),
264
+ * never a `•` block followed by dangling `—` lines. The `— ` sigil is
265
+ * stripped at render; the input strings keep it for non-chat surfaces. */
266
+ function trailerBullet(line: string): string {
267
+ return `• ${line.replace(/^—\s*/, "")}`;
268
+ }
269
+
212
270
  /** v0.38.25: the audit-goal counts line — verdict proof ONLY, built from
213
271
  * durable goal state. Raw run stats (turns/file-writes/bash calls) read as
214
272
  * a machine receipt in user chat (field 2026-09-08 223522/223523); the
@@ -297,7 +355,7 @@ export function buildTerminalApprovalRender(input: TerminalApprovalRenderInput):
297
355
  ...(input.extras ?? []),
298
356
  ],
299
357
  recap,
300
- transcriptLines: [...withoutStaleNext(brief.details).map((detail) => `• ${detail}`), input.approval],
358
+ transcriptLines: [...withoutStaleNext(brief.details).map((detail) => `• ${detail}`), trailerBullet(input.approval)],
301
359
  countsLine,
302
360
  outcome: brief.outcome,
303
361
  approval: input.approval,
@@ -1323,8 +1323,11 @@ function buildStatusTextBase(state: State, audit?: AuditDisplayProgress | null,
1323
1323
  // state + counts only. Freshness tails (`last host activity`,
1324
1324
  // `last stream`) duplicated the card-head `stream {age}` readout —
1325
1325
  // one surface owns liveness so the two can never disagree.
1326
+ // v0.38.38 (field 20260909_002132): the elapsed `total …` duplicated
1327
+ // the card head too (other sessions' tab strips showed
1328
+ // `glla: [WORKING] total …` under a card already reading it) — the
1329
+ // card owns the timer now; the status line keeps state + counts.
1326
1330
  const idleDetails = [
1327
- goalTotalText(g, now),
1328
1331
  (state.list?.length ?? 0) > 0 ? `${state.list!.length} queued` : "",
1329
1332
  ].filter(Boolean);
1330
1333
  return `glla: ${activityStateBadge("IDLE", theme, "warning")}${idleDetails.length > 0 ? ` ${idleDetails.join(" · ")}` : ""}${heldSuffix}`;
@@ -1335,7 +1338,6 @@ function buildStatusTextBase(state: State, audit?: AuditDisplayProgress | null,
1335
1338
  // like progress. Queued work is neither idle nor currently executing.
1336
1339
  if (activity === "busy") {
1337
1340
  const busyDetails = [
1338
- goalTotalText(g, now),
1339
1341
  g.taskList ? `${countDone(g)}/${countTotal(g)} tasks` : "",
1340
1342
  (state.list?.length ?? 0) > 0 ? `${state.list!.length} queued` : "",
1341
1343
  ].filter(Boolean);
@@ -1370,11 +1372,11 @@ function buildStatusTextBase(state: State, audit?: AuditDisplayProgress | null,
1370
1372
  const recovery = state.mainModelRecovery;
1371
1373
  const blockedByRecovery = queued && recovery && recovery.retryAt;
1372
1374
  const recoverySuffix = blockedByRecovery ? ` · parked on provider recovery` : "";
1373
- // Keep the screenshot-proven order: state, elapsed, freshness, then
1374
- // queue/task context. It scans like a compact instrument readout and
1375
- // remains useful when the above-editor card is hidden or scrolled away.
1375
+ // Keep the screenshot-proven order: state, then queue/task context.
1376
+ // It scans like a compact instrument readout and remains useful when
1377
+ // the above-editor card is hidden or scrolled away. Elapsed `total …`
1378
+ // lives on the card head only (v0.38.38, field 20260909_002132).
1376
1379
  const details = [
1377
- goalTotalText(g, now),
1378
1380
  g.taskList ? `${countDone(g)}/${countTotal(g)} tasks` : "",
1379
1381
  // v0.34.124: the QUEUED "why" — an accepted dispatch that pi has not
1380
1382
  // started. (The last-activity age that used to ride here moved to the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.38.37",
3
+ "version": "0.38.39",
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",
@@ -197,4 +197,4 @@ The orchestrator's backstop is the stall watchdog: three consecutive turns with
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
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; pass what was deliberately left out as the complete_goal leftOut parameter — it renders as the closing bullet, and is omitted when absent.
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. Keep each label value to one line of ~90 chars so every chat bullet fits without a trailing …; cite human-readable proof (counts, versions, repo-relative paths, PR numbers) and never machine paths (/var/tmp logs, tarballs — those live in the archive only). Cut values at clause boundaries, never mid-word; write Next: as one concrete follow-up action or none (audit-self-referential lines like "verdict decides" are stripped at render); pass what was deliberately left out as the complete_goal leftOut parameter — it renders as the closing bullet, and is omitted when absent.