pi-goal-list-loop-audit 0.38.52 → 0.38.54

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,64 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.38.54 — Opt-in context-checkpoint projection (2026-09-14)
4
+
5
+ The per-turn `context`-hook projection rewrote history on every turn, busting
6
+ the provider prefix-cache (upstream issue #53). Projection is now opt-in:
7
+ the hook leaves the transcript append-only by default so the cache holds
8
+ across turns, while the fresh continuation prompt still carries live durable
9
+ state every turn.
10
+
11
+ ### Added
12
+
13
+ - **`contextCheckpointProjection` setting (default off):** explicit opt-in
14
+ restores the legacy per-turn splice of a bounded continuation checkpoint.
15
+ Carried in `SETTINGS_KEYS` with junk-normalization to unset, a row in the
16
+ `/glla` settings Other tab with an on/off editor, and a `docs/SETTINGS.md`
17
+ entry (release-gate docs coverage holds).
18
+
19
+ ### Fixed
20
+
21
+ - **Prompt-cache continuity:** with projection off (the default) the hook
22
+ returns no message rewrite and records no projection event, preserving
23
+ cache-prefix reuse turn over turn.
24
+
25
+ ### Tests
26
+
27
+ - Both hook paths pinned (default-off passthrough + opt-in splice), junk
28
+ normalization, menu row off/default, editor round-trip, and docs coverage.
29
+ Full suite 2163 pass / 2 skip / 0 fail; `tsc` clean; `git diff --check`
30
+ clean.
31
+
32
+ ## 0.38.53 — Consent-safe GLLA delegation skill and list-draft staging (2026-09-13)
33
+
34
+ Added the packaged `glla-delegate` skill for normal-chat goal/list delegation.
35
+ It documents the consent ladder, main-session ownership, auto-activation warning,
36
+ and tool-first usage without adding new runtime powers. Its quoted frontmatter
37
+ is validated through Pi's public loader and the packed-file release gate.
38
+
39
+ ### Added
40
+
41
+ - **Consent-safe delegation guidance:** explicit queue requests may use
42
+ `list_add`; discovered follow-ups are offered first; durable goals use
43
+ `propose_goal_draft`; speculative queueing, unowned activation, and agent
44
+ `/loop` control remain prohibited.
45
+ - **Pasted-list policy:** supplied multi-line, bullet, numbered, and checklist
46
+ structure is preserved without an exact-versus-refined meta-question. Only a
47
+ genuinely ambiguous individual item remains eligible for clarification, with
48
+ one-record seeds retaining the normal draft path.
49
+ - **Release/package coverage:** `skills/glla-delegate/SKILL.md` is allowlisted,
50
+ declared in `pi.skills`, required by the packed-artifact smoke, and loaded
51
+ with zero diagnostics in the release contract.
52
+
53
+ ### Fixed
54
+
55
+ - **Draft staging:** interview and stale-entry gates remain ahead of Confirm;
56
+ confirmation remains ahead of conflict handling and enqueue.
57
+ - **Paused list carryover:** a confirmed multi-item draft now routes its first
58
+ promotion through the carryover-aware activation choke point, archiving the
59
+ paused predecessor before starting the list head instead of leaving the batch
60
+ stranded behind it.
61
+
3
62
  ## 0.38.52 — Second Gemini-gap: gate-inventory table + per-finding Test Results (2026-09-12)
4
63
 
5
64
  Surveyed five more Antigravity report screenshots
package/INSTALL.md CHANGED
@@ -31,11 +31,11 @@ GLLA loads into new pi sessions. If pi is already open, reload that session:
31
31
 
32
32
  ## Updating
33
33
 
34
- The status line always shows the running version (`glla: … · v0.38.48`).
34
+ The status line always shows the running version (`glla: … · vX.Y.Z`).
35
35
  When the npm registry is ahead, it also nudges:
36
36
 
37
37
  ```text
38
- glla: … · v0.38.47 · update v0.38.48 available
38
+ glla: … · vX.Y.Z · update vA.B.C available
39
39
  ```
40
40
 
41
41
  The nudge comes from a daily sidecar check (`.pi-glla/update-check.json`),
@@ -117,10 +117,16 @@ waiting for a decision.
117
117
  ```text
118
118
  /list "refactor the cache. Done when: tests pass"
119
119
  /list plan.md
120
- /list
120
+ /list # interview + Confirm for a context draft
121
+ /list show # show active and waiting items
122
+ /list add <text...> # queue one item directly (no interview)
123
+ /list import <file> # import a file: one Confirm for the whole batch
121
124
  /list start
122
125
  /list next
123
126
  /list resume
127
+ /list remove <n>
128
+ /list clear
129
+ /list cancel
124
130
 
125
131
  /loop
126
132
  /loop start # one clear recent target, metricless
package/README.md CHANGED
@@ -157,7 +157,8 @@ quietly inventing an unbounded backlog.
157
157
  ```text
158
158
  /list "fix the cache. Done when: tests pass"
159
159
  /list plan.md # import a checklist or plan file
160
- /list # show active and waiting items
160
+ /list # interview + Confirm for a context draft
161
+ /list show # show active and waiting items
161
162
  /list start # activate the queued head, or draft one clear recent request
162
163
  /list next # explicitly skip/activate the next item
163
164
  /list next <n> # explicitly choose a specific item
@@ -165,11 +166,22 @@ quietly inventing an unbounded backlog.
165
166
  /list remove <n>
166
167
  /list clear
167
168
  /list cancel # stop the active item and drop waiting items
169
+ /list add <text...> # queue one item directly (no interview)
170
+ /list import <file> # import a file: one Confirm for the whole batch
168
171
  ```
169
172
 
173
+ A pasted multi-line, bulleted, numbered, or checklist-style list keeps its
174
+ item wording and boundaries; GLLA does not ask for an “exact or refined” choice.
175
+ Only a genuinely ambiguous individual item needs clarification. The direct
176
+ `/list add` path skips the interview but not consent: pasted text and files
177
+ still get one Confirm for the whole batch before anything is queued. The packaged
178
+ `skills/glla-delegate/SKILL.md` records the safe normal-chat delegation path:
179
+ explicit queue requests may use `list_add`, while discovered follow-ups are
180
+ offered first and durable goals remain Confirm-gated.
181
+
170
182
  Order is the default, not the law. Automatic advance normally uses the head of
171
183
  the queue, while `/list next <n>` or the agent's `list_activate` tool can choose
172
- another item. Numbering always matches `/list` output. After a list item is
184
+ another item. Numbering always matches `/list show` output. After a list item is
173
185
  approved and archived, the next queued item starts automatically; no manual
174
186
  `/list next` is needed between items.
175
187
 
@@ -305,9 +317,9 @@ GLLA is the supervisor. These companions add capabilities around it:
305
317
  count + `/glla agents`. If you see the same run stacked 3× or panels
306
318
  swapping order, set pi-subagents `inlineToolDisplay: "summary"` (one
307
319
  stable row per run) and pin `fleetViewPlacement`; GLLA's own richness is
308
- the `subagentDisplayRichness` setting (`/glla` → Subagents): `rich`
309
- (default: worker rows + task linkage), `compact` (count line), `quiet`
310
- (hung/aborting workers only). Upstream triple-render report:
320
+ the `subagentDisplayRichness` setting (`/glla` → Subagents): `quiet`
321
+ (default: troubled workers + the count line; HUNG is never silent),
322
+ `compact` (count line), `rich` (all worker rows). Upstream triple-render report:
311
323
  nicobailon/pi-subagents#1931.
312
324
 
313
325
  Install (or keep pinned):
package/docs/INDEX.md CHANGED
@@ -16,7 +16,7 @@ Policy contracts and recent changes live in the `audit/` directory of the
16
16
  failback; v0.35.9 hardened cross-version npm tarball checks; v0.35.10
17
17
  handles multi-entry npm dry-run reports; v0.35.11 accepts both npm report
18
18
  shapes; v0.35.12 supports npm 12's keyed pack reports; v0.35.13 fixes stale-API recovery loops.
19
- v0.35.14–v0.38.52 continue through the supervisor freeze (`/glla pause`),
19
+ v0.35.14–v0.38.54 continue through the supervisor freeze (`/glla pause`),
20
20
  load hold, auditor picker parity, Windows launch fix, zombie-watchdog
21
21
  subagent carve-out, due-wait backstop, the `/glla agents` visibility panel,
22
22
  durable state-root selection, blank-until-resume auditor context, frozen
@@ -25,18 +25,20 @@ Policy contracts and recent changes live in the `audit/` directory of the
25
25
  extensions, bounded zero-stream retry containment, crash-safe persistence,
26
26
  packed-artifact release verification, and the 2026-09-07 display/lifecycle/
27
27
  settings audit pass (abort-latch send guards, ownership compare-and-swap,
28
- auditor inherit/clear parity) and the v0.38.25 post-objective summary
29
- (canonical approval render, persist + replay, audit-goal counts line), the v0.38.27 selective port of PRs #45/#46 (`/loop pause` soft-hold, widget subtask count), the v0.38.28 provenance-row closure fix, and the v0.38.29 compact active-card recovery/judgment projection; see CHANGELOG.md for the full trail.
28
+ auditor inherit/clear parity), the v0.38.25 post-objective summary
29
+ (canonical approval render, persist + replay, audit-goal counts line), the v0.38.27 selective port of PRs #45/#46 (`/loop pause` soft-hold, widget subtask count), the v0.38.28 provenance-row closure fix, the v0.38.29 compact active-card recovery/judgment projection, and the v0.38.53 consent-safe `glla-delegate`
30
+ skill/list-drafting release, and the v0.38.54 opt-in context-checkpoint
31
+ projection (prompt-cache continuity, issue #53); see CHANGELOG.md for the full trail.
30
32
  - `../README.md`: what the plugin is, install, quickstart, and the
31
33
  architectural guarantee (drafting + confirm + detached auditor).
32
- - `../INSTALL.md`: source install / local development setup; the recommended
33
- companion plugins and the `auditor reads / writes are path-checked`
34
- note.
34
+ - `../INSTALL.md`: source install / local development setup and the
35
+ recommended companion plugins.
35
36
 
36
37
  ## Entry points
37
38
  - `../README.md`: what the plugin is, install, quickstart
38
39
  - `SETTINGS.md`: canonical settings reference (files, precedence, every key)
39
40
  - `../INSTALL.md`: source install / local development setup
41
+ - `../skills/glla-delegate/SKILL.md`: normal-chat goal/list delegation and consent ladder
40
42
  - `../CHANGELOG.md`: user-facing changelog; the first versioned heading is
41
43
  the current released package version (use `/glla version` to compare with
42
44
  the registry); post-release work may appear in `Unreleased` above it.
package/docs/SETTINGS.md CHANGED
@@ -73,6 +73,7 @@ copies are ignored (the recovery runtime reads the global file):
73
73
  | `subagentModelStrategy` | `"inherit-parent"` | Default subagent model policy for new sessions. |
74
74
  | `subagentModelOverrides` | unset | Per-agent-type model pin; always wins over strategy. |
75
75
  | `subagentFallbacks` | unset | Per-role fallback chains (first eligible ref wins). |
76
+ | `subagentDisplayRichness` | `"quiet"` | Ambient worker UI: `"quiet"` (default, troubled workers + count line) / `"compact"` / `"rich"`. |
76
77
  | `aggressiveMode` | `true` | Keep-going defaults (`false` = pause-first policy). |
77
78
  | `stuckMaxInterventions` | `5` (10 aggressive) | Consecutive stuck interventions before a loop stops. |
78
79
  | `subagentHangEscalationMinutes` | `30` | Confirmed no-progress minutes before child-specific abort (`0` = warn only). |
@@ -82,4 +83,5 @@ copies are ignored (the recovery runtime reads the global file):
82
83
  | `stallSimilarityThreshold` | `0.6` | Trigram similarity above this (tool-less) is a nudge. |
83
84
  | `postaudit` | unset | Post-completion audit config (same shape as legacy `reviewer`). |
84
85
  | `toolOverrides` | unset | Per-tool allow/hide/per-tool-config overrides. |
86
+ | `contextCheckpointProjection` | `false` (off) | Per-turn `context`-hook splice of a bounded continuation checkpoint. Off (default) leaves the transcript append-only so the provider prefix-cache holds; the fresh continuation prompt still carries live state. On restores the legacy projection (busts the cache). |
85
87
  | `reviewer` | legacy | Deprecated alias for `postaudit`; migrated on load, `postaudit` wins. |
@@ -292,6 +292,10 @@ export interface RichTerminalParts {
292
292
  durationLine: string | null;
293
293
  findingLines: string[];
294
294
  tableLines: string[];
295
+ /** Audit 2026-09-13: auto-collapsed green verification (chat only) —
296
+ * one PASS line standing in for the table section; null when the full
297
+ * table renders or when there is nothing to verify. */
298
+ verificationLine: string | null;
295
299
  nextLines: string[];
296
300
  }
297
301
 
@@ -383,6 +387,19 @@ export function buildDurationLine(goal: Goal, now = Date.now()): string | null {
383
387
  return segs.length > 0 ? `\u2014 ${segs.join(" \u00b7 ")}` : null;
384
388
  }
385
389
 
390
+ /** Audit 2026-09-13 (terminal findings-first): commit SHAs read as
391
+ * machine receipts in chat. Hex tokens of 7+ chars carrying at least one
392
+ * digit are hashes in practice (02871aa6, full SHAs); pure-letter tokens,
393
+ * versions (v9.9.9 splits to v9/9/9), counts, and file:line evidence never
394
+ * match. The archive keeps the full text — strip chat-side only. */
395
+ export function stripCommitHashes(value: string): string {
396
+ return value
397
+ .replace(/\b[0-9a-fA-F]{7,64}\b/g, (token) => (/[0-9]/.test(token) ? "\u2026" : token))
398
+ .replace(/[ \t]{2,}/g, " ")
399
+ .replace(/\s+([,.;:])/g, "$1")
400
+ .trim();
401
+ }
402
+
386
403
  /** Partition informing details into findings / Tests / next buckets. */
387
404
  export function partitionRichDetails(details: string[]): { findings: string[]; tests: string[]; next: string[] } {
388
405
  const findings: string[] = [];
@@ -425,9 +442,19 @@ export function buildRichTerminalParts(args: {
425
442
  durationLine?: string | null;
426
443
  groups?: FindingGroup[];
427
444
  gates?: GateRow[];
445
+ /** Audit 2026-09-13: "auto" collapses the verification table to one
446
+ * PASS line when every row is green (chat default-off); "full" keeps
447
+ * the table (archive + mechanical fallback). Default "full" so the
448
+ * v0.38.52 byte-identical fallback survives untouched. */
449
+ verification?: "full" | "auto";
450
+ /** Audit 2026-09-13: strip commit hashes chat-side (archive keeps them). */
451
+ chatSafe?: boolean;
428
452
  }): RichTerminalParts {
429
453
  const { findings, tests, next } = partitionRichDetails(args.details);
430
- const headline = requestEchoHeadline("Done", args.objective, args.outcome);
454
+ const chatSafe = args.chatSafe === true;
455
+ const safe = (text: string): string => (chatSafe ? stripCommitHashes(text) : text);
456
+ const outcome = safe(args.outcome);
457
+ const headline = requestEchoHeadline("Done", args.objective, outcome);
431
458
  const budgeted = takeBudgetedGroups(args.groups ?? []);
432
459
  const useTable = budgeted.length >= RICH_TABLE_GROUP_THRESHOLD;
433
460
  const findingLines: string[] = [];
@@ -436,34 +463,37 @@ export function buildRichTerminalParts(args: {
436
463
  for (const group of budgeted) {
437
464
  group.findings.forEach((finding, fi) => {
438
465
  const { text, evidence } = extractEvidenceTokens(finding);
439
- const { lead, body } = leadBody(text);
466
+ const { lead, body } = leadBody(safe(text));
440
467
  // v0.38.52: test proof rides the Evidence cell (tables have no
441
468
  // sub-bullets); cells stay pipe-escaped and budgeted.
442
469
  const proof = group.tests?.[fi]?.trim();
443
- const evidenceCell = [evidence.join(", ") || "\u2014", ...(proof ? [`Tests: ${proof}`] : [])].join(" \u00b7 ");
470
+ const evidenceCell = [evidence.join(", ") || "\u2014", ...(proof ? [`Tests: ${safe(proof)}`] : [])].join(" \u00b7 ");
444
471
  findingLines.push(
445
- `| ${escapeTableCell(clipSummaryValue(group.title, RICH_OBJECTIVE_ECHO_CHARS))} | ${escapeTableCell(`**${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`)} | ${escapeTableCell(clipSummaryValue(evidenceCell, RICH_VALUE_BUDGET))} |`,
472
+ `| ${escapeTableCell(clipSummaryValue(safe(group.title), RICH_OBJECTIVE_ECHO_CHARS))} | ${escapeTableCell(`**${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`)} | ${escapeTableCell(clipSummaryValue(evidenceCell, RICH_VALUE_BUDGET))} |`,
446
473
  );
447
474
  });
448
475
  }
449
476
  } else if (budgeted.length > 0) {
450
477
  budgeted.forEach((group, i) => {
451
- findingLines.push(`#### ${i + 1}. ${clipSummaryValue(group.title, RICH_OBJECTIVE_ECHO_CHARS)}`);
478
+ findingLines.push(`#### ${i + 1}. ${clipSummaryValue(safe(group.title), RICH_OBJECTIVE_ECHO_CHARS)}`);
452
479
  group.findings.forEach((finding, fi) => {
453
- const { lead, body } = leadBody(finding);
480
+ const { lead, body } = leadBody(safe(finding));
454
481
  findingLines.push(`- **${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`);
455
482
  // v0.38.52: per-finding test proof (shot C) — absent stays absent.
456
483
  const proof = group.tests?.[fi]?.trim();
457
- if (proof) findingLines.push(` - Test Results: ${clipSummaryValue(proof, RICH_VALUE_BUDGET)}`);
484
+ if (proof) findingLines.push(` - Test Results: ${clipSummaryValue(safe(proof), RICH_VALUE_BUDGET)}`);
458
485
  });
459
486
  });
460
487
  } else {
461
488
  findings.slice(0, RICH_FINDINGS_CAP).forEach((detail, i) => {
462
- const { lead, body } = leadBody(detail);
489
+ const { lead, body } = leadBody(safe(detail));
463
490
  findingLines.push(`${i + 1}. **${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`);
464
491
  });
465
492
  }
466
493
  const tableRows: string[] = [];
494
+ // Audit 2026-09-13: parallel check/status facts drive the auto collapse
495
+ // (row strings alone cannot carry the verdict once escaped/clipped).
496
+ const rowFacts: Array<{ check: string; status: string }> = [];
467
497
  const gates = (args.gates ?? []).slice(0, 10);
468
498
  if (gates.length > 0) {
469
499
  // v0.38.52 (shots B/D): the agent inventory supersedes the mechanical
@@ -472,28 +502,42 @@ export function buildRichTerminalParts(args: {
472
502
  // zero failures ("Clean exit 0"-style notes honestly stay REPORTED).
473
503
  for (const row of gates) {
474
504
  const derived = testsRowStatus(row.notes ?? "");
475
- tableRows.push(`| ${escapeTableCell(clipSummaryValue(row.gate, RICH_OBJECTIVE_ECHO_CHARS))} | ${escapeTableCell(clipSummaryValue(row.scope?.trim() || "\u2014", RICH_VALUE_BUDGET))} | ${derived} | ${escapeTableCell(clipSummaryValue(row.notes?.trim() || "\u2014", RICH_VALUE_BUDGET))} |`);
505
+ rowFacts.push({ check: row.gate, status: derived });
506
+ tableRows.push(`| ${escapeTableCell(clipSummaryValue(safe(row.gate), RICH_OBJECTIVE_ECHO_CHARS))} | ${escapeTableCell(clipSummaryValue(safe(row.scope?.trim() || "\u2014"), RICH_VALUE_BUDGET))} | ${derived} | ${escapeTableCell(clipSummaryValue(safe(row.notes?.trim() || "\u2014"), RICH_VALUE_BUDGET))} |`);
476
507
  }
477
508
  } else {
478
509
  for (const detail of tests.slice(0, 2)) {
479
510
  const { body } = leadBody(detail);
480
- tableRows.push(`| Tests | ${testsRowStatus(body)} | ${escapeTableCell(clipSummaryValue(body, RICH_VALUE_BUDGET))} |`);
511
+ const status = testsRowStatus(body);
512
+ rowFacts.push({ check: "Tests", status });
513
+ tableRows.push(`| Tests | ${status} | ${escapeTableCell(clipSummaryValue(safe(body), RICH_VALUE_BUDGET))} |`);
481
514
  }
482
515
  }
483
516
  const auditStatus = auditRowStatus(args.auditHistory);
484
517
  if (auditStatus !== "NO VERDICT") {
485
- const auditBody = args.countsLine.replace(/^\u2014\s*/, "").replace(/\.\s*$/, "");
518
+ rowFacts.push({ check: "Audit", status: auditStatus });
519
+ const auditBody = safe(args.countsLine.replace(/^\u2014\s*/, "").replace(/\.\s*$/, ""));
486
520
  if (gates.length > 0) {
487
521
  tableRows.push(`| Audit | ${escapeTableCell(clipSummaryValue(auditBody, RICH_VALUE_BUDGET))} | ${auditStatus} | ${escapeTableCell(clipSummaryValue(auditBody, RICH_VALUE_BUDGET))} |`);
488
522
  } else {
489
523
  tableRows.push(`| Audit | ${auditStatus} | ${escapeTableCell(clipSummaryValue(auditBody, RICH_VALUE_BUDGET))} |`);
490
524
  }
491
525
  }
492
- const tableLines = tableRows.length > 0
526
+ // Audit 2026-09-13: auto-off — every row green collapses the table to
527
+ // one PASS line (findings keep the space); anything else renders full.
528
+ // REPORTED is not green: an unclaimed status stays visible, never hidden.
529
+ let tableLines = tableRows.length > 0
493
530
  ? [(gates.length > 0 ? "| Quality Gate | Scope | Status | Notes |" : "| Check | Status | Details |"), (gates.length > 0 ? "| --- | --- | --- | --- |" : "| --- | --- | --- |"), ...tableRows]
494
531
  : [];
532
+ let verificationLine: string | null = null;
533
+ if (args.verification === "auto" && tableRows.length > 0
534
+ && rowFacts.every((fact) => /^(PASS|APPROVED)/.test(fact.status))) {
535
+ const middle = clipSummaryValue(rowFacts.map((fact) => `${clipSummaryValue(safe(fact.check), RICH_OBJECTIVE_ECHO_CHARS)} ${fact.status}`).join(" \u00b7 "), 300);
536
+ verificationLine = `\u2014 Verification passed (${middle}); details in the archive record.`;
537
+ tableLines = [];
538
+ }
495
539
  const nextLines = next.slice(0, RICH_NEXT_CAP).map((detail) => {
496
- const { lead, body } = leadBody(detail);
540
+ const { lead, body } = leadBody(safe(detail));
497
541
  return `- **${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`;
498
542
  });
499
543
  return {
@@ -501,12 +545,15 @@ export function buildRichTerminalParts(args: {
501
545
  durationLine: args.durationLine ?? null,
502
546
  findingLines,
503
547
  tableLines,
548
+ verificationLine,
504
549
  nextLines,
505
550
  };
506
551
  }
507
552
 
508
553
  /** Compose parts + headline + section headers into markdown lines.
509
- * v0.38.50: the duration line rides directly under the headline. */
554
+ * v0.38.50: the duration line rides directly under the headline.
555
+ * Audit 2026-09-13: findings-first order is pinned — findings, then the
556
+ * verification table (or its auto-collapsed PASS line), then Next. */
510
557
  export function composeRichTerminalLines(parts: RichTerminalParts, opts?: { headline?: string }): string[] {
511
558
  const lines = [opts?.headline ?? parts.headline, ""];
512
559
  if (parts.durationLine) lines.push(parts.durationLine, "");
@@ -515,6 +562,8 @@ export function composeRichTerminalLines(parts: RichTerminalParts, opts?: { head
515
562
  }
516
563
  if (parts.tableLines.length > 0) {
517
564
  lines.push("### Verification Summary", ...parts.tableLines, "");
565
+ } else if (parts.verificationLine) {
566
+ lines.push(parts.verificationLine, "");
518
567
  }
519
568
  if (parts.nextLines.length > 0) {
520
569
  lines.push("### Next", ...parts.nextLines, "");
@@ -546,6 +595,9 @@ export function buildRichArchiveSection(goal: Goal, status: Status, archivePath:
546
595
  durationLine: buildDurationLine(goal),
547
596
  groups: findingGroups,
548
597
  gates: gateRows,
598
+ // Audit 2026-09-13: the archive is the full-detail surface — the
599
+ // verification table never collapses here and hashes are preserved.
600
+ verification: "full",
549
601
  });
550
602
  return [
551
603
  ...composeRichTerminalLines(parts, {
@@ -718,8 +770,9 @@ export function buildTerminalApprovalRender(input: TerminalApprovalRenderInput):
718
770
  const recordBullet = trailerBullet(input.record);
719
771
  // Rich voice: headline + Key Findings + Verification table + Next,
720
772
  // closed by the pinned trailer (approval/counts/record, record last).
721
- // The transcript mirrors chat without the record pointer and extras —
722
- // the record lives in chat and the archive, matching the old contract.
773
+ // Audit 2026-09-13: chat is findings-first — verification auto-collapses
774
+ // to one PASS line when green, hashes stay archive-only. The transcript
775
+ // mirrors chat; the archive keeps the full table and full text.
723
776
  const richParts = buildRichTerminalParts({
724
777
  outcome: richBrief.outcome,
725
778
  details: withoutStaleNext(richDetails),
@@ -729,6 +782,8 @@ export function buildTerminalApprovalRender(input: TerminalApprovalRenderInput):
729
782
  durationLine: buildDurationLine(input.goal),
730
783
  groups: input.findingGroups,
731
784
  gates: input.gateRows,
785
+ verification: "auto",
786
+ chatSafe: true,
732
787
  });
733
788
  const chatBody = composeRichTerminalLines(richParts);
734
789
  const transcriptBody = composeRichTerminalLines(richParts);
@@ -270,6 +270,13 @@ export interface Settings {
270
270
  /** Per-tool configuration knobs (extensible). */
271
271
  perToolConfig?: Record<string, Record<string, unknown>>;
272
272
  };
273
+ /** v0.38.54: on → the per-turn `context` hook replaces stale GLLA
274
+ * continuation payloads with a bounded checkpoint (legacy behaviour).
275
+ * Default off (unset): the hook leaves the transcript alone so the
276
+ * provider prefix-cache stays valid across turns; the fresh
277
+ * continuation prompt still carries live durable state every turn.
278
+ * Explicit opt-in restores the per-turn splice. */
279
+ contextCheckpointProjection?: boolean;
273
280
  }
274
281
 
275
282
  /** These settings describe global provider-recovery policy, not a project
@@ -532,6 +539,7 @@ export function normalizeLoadedSettings(settings: Settings): Settings {
532
539
  if (typeof settings.aggressiveMode !== "boolean") delete settings.aggressiveMode;
533
540
  if (typeof settings.autoResume !== "boolean") delete settings.autoResume;
534
541
  if (typeof settings.autoAcceptDrafts !== "boolean") delete settings.autoAcceptDrafts;
542
+ if (typeof settings.contextCheckpointProjection !== "boolean") delete settings.contextCheckpointProjection;
535
543
  if (typeof settings.notifyCmd !== "string" || settings.notifyCmd.trim().length === 0) {
536
544
  delete settings.notifyCmd;
537
545
  }
@@ -643,6 +651,7 @@ export const SETTINGS_KEYS: Array<keyof Settings> = [
643
651
  "subagentModelStrategy",
644
652
  "subagentModelOverrides",
645
653
  "subagentFallbacks",
654
+ "subagentDisplayRichness",
646
655
  "aggressiveMode",
647
656
  "stuckMaxInterventions",
648
657
  "subagentHangEscalationMinutes",
@@ -652,6 +661,7 @@ export const SETTINGS_KEYS: Array<keyof Settings> = [
652
661
  "stallSimilarityThreshold",
653
662
  "postaudit",
654
663
  "toolOverrides",
664
+ "contextCheckpointProjection",
655
665
  "reviewer", // v0.33.1: legacy alias — menu saves can still write it (when postaudit is unset) and load-migration consolidates it into postaudit on the next read; provenance must know it exists or reviewer-sourced values report "unknown"
656
666
  ];
657
667
 
@@ -2846,8 +2846,12 @@ export function registerGoalRuntime(pi: ExtensionAPI): void {
2846
2846
  // may coexist with an active loop. Project whenever either surface is
2847
2847
  // active, while passing both states so a paused goal is not mistaken for
2848
2848
  // the loop's current objective.
2849
+ // v0.38.54: projection is opt-in (default off). Rewriting history
2850
+ // busts the provider prefix-cache on every turn, while the fresh
2851
+ // continuation prompt already carries live durable state. Only splice
2852
+ // the bounded checkpoint when explicitly enabled.
2849
2853
  const activeLoop = state.loop?.active === true ? state.loop : null;
2850
- const checkpointProjection = state.goal || activeLoop
2854
+ const checkpointProjection = (state.goal || activeLoop) && loadSettings(ctx.cwd).contextCheckpointProjection === true
2851
2855
  ? projectBoundedGllaContext(
2852
2856
  hygiene.messages,
2853
2857
  buildAuthoritativeContextCheckpoint({
@@ -860,7 +860,7 @@ async function startDrafting(ctx: ExtensionContext, target: "goal" | "list" | "l
860
860
  // concatenation produced "…regular draft.Goal drafting — deep planning: …".
861
861
  const seededHint = (planNote ? `${planNote} ` : "") + (
862
862
  target === "list"
863
- ? `${label}: free-text is valid without a "Done when:" clause — the agent will turn it into short list items and grill for concrete per-item contracts (nothing activates until you confirm). To skip drafting, include a per-item "Done when:" clause.`
863
+ ? `${label}: free-text is valid without a "Done when:" clause — the agent will turn it into short list items and grill for concrete per-item contracts (nothing activates until you confirm). If the seed is already a pasted multi-line, bulleted, numbered, or checklist-style list, use its wording and structure as supplied; do not ask whether it should be exact or refined. To skip drafting, include a per-item "Done when:" clause.`
864
864
  : target === "loop"
865
865
  ? `${label}: a loop target needs a metric and a direction — the agent will help you design them first (nothing activates until you confirm). Skip the interview entirely: /loop start "<target>" (bare = infinite metricless) or /loop start "<target>" measure="<cmd>" direction=min|max [window=5] [max=50] [time=h] [tokens=n] [branch=1].`
866
866
  : `${label}: the objective has no "Done when:" clause — the agent will grill you about it first (nothing activates until you confirm). Skip the interview entirely: /goal start <objective>.`);
@@ -877,7 +877,10 @@ async function startDrafting(ctx: ExtensionContext, target: "goal" | "list" | "l
877
877
  "for the big version. When the user has many items to enqueue at once ('queue these 50 audits'), propose them ALL AT " +
878
878
  "ONCE with the items[] parameter — one Confirm for the whole batch, never 50 separate proposals. Each items[] entry " +
879
879
  "is still a SHORT task — never an aggregate wrapper ('land all N findings' with a '≥N commits' contract is the " +
880
- "canonical anti-pattern: the auto-committer squashes, the count fails, the auditor disapproves finished work).]",
880
+ "canonical anti-pattern: the auto-committer squashes, the count fails, the auditor disapproves finished work). " +
881
+ "For a pasted multi-line, bulleted, numbered, or checklist-style seed, preserve the supplied wording and structure, " +
882
+ "strip only import syntax and empty headings/lines, and propose items[] directly — do not ask whether it should be exact " +
883
+ "or refined; clarify only a genuinely ambiguous individual item.]",
881
884
  );
882
885
  }
883
886
  // v0.14.0: the LLM grills (its strength — v0.13.0's canned questionnaire
@@ -1027,6 +1030,9 @@ function currentToolContext(execCtx: unknown): ExtensionContext | null {
1027
1030
  defineGoalRuntimeGlobal("listQueue", { get: () => listQueue });
1028
1031
  defineGoalRuntimeGlobal("groupOpenChildren", { get: () => groupOpenChildren });
1029
1032
  defineGoalRuntimeGlobal("activateNextListItem", { get: () => activateNextListItem });
1033
+ // Audit 2026-09-13: list_activate must screen content before the conflict
1034
+ // can archive, so the repair helper joins the runtime-global bridge.
1035
+ defineGoalRuntimeGlobal("queueRepairAheadOfListItem", { get: () => queueRepairAheadOfListItem });
1030
1036
  defineGoalRuntimeGlobal("startDrafting", { get: () => startDrafting });
1031
1037
  defineGoalRuntimeGlobal("restoreDrafterModel", { get: () => restoreDrafterModel });
1032
1038
  defineGoalRuntimeGlobal("handleDrafterModelFailure", { get: () => handleDrafterModelFailure });
@@ -219,6 +219,7 @@ export const GOAL_RUNTIME_GLOBAL_NAMES = [
219
219
  "listQueue",
220
220
  "groupOpenChildren",
221
221
  "activateNextListItem",
222
+ "queueRepairAheadOfListItem",
222
223
  "startDrafting",
223
224
  "healGoalPolicy",
224
225
  "notifyExternal",
@@ -590,6 +591,7 @@ declare global {
590
591
  var listQueue: GoalRuntimeGlobals["listQueue"];
591
592
  var groupOpenChildren: GoalRuntimeGlobals["groupOpenChildren"];
592
593
  var activateNextListItem: GoalRuntimeGlobals["activateNextListItem"];
594
+ var queueRepairAheadOfListItem: GoalRuntimeGlobals["queueRepairAheadOfListItem"];
593
595
  var startDrafting: GoalRuntimeGlobals["startDrafting"];
594
596
  var healGoalPolicy: GoalRuntimeGlobals["healGoalPolicy"];
595
597
  var notifyExternal: GoalRuntimeGlobals["notifyExternal"];
@@ -1036,6 +1036,17 @@ export async function handleSettingChoice(id: string, ctx: ExtensionContext): Pr
1036
1036
  }
1037
1037
  return;
1038
1038
  }
1039
+ case "contextCheckpointProjection": {
1040
+ const v = await ctx.ui.select("Context checkpoint projection — off keeps the transcript append-only so the provider prefix-cache holds across turns; on restores the legacy per-turn splice of a bounded continuation checkpoint", [
1041
+ "off — leave the transcript alone; the continuation prompt carries live state (default)",
1042
+ "on — splice a bounded checkpoint every turn (legacy; busts the prompt cache)",
1043
+ ]);
1044
+ if (v) {
1045
+ saveSettings("global", ctx.cwd, { contextCheckpointProjection: v.startsWith("on") ? true : undefined });
1046
+ ctx.ui.notify(v.startsWith("on") ? "Context checkpoint projection ON — legacy per-turn splice; expect higher prompt-cache miss rates." : "Context checkpoint projection OFF — transcript stays append-only; the continuation prompt carries live state.", "info");
1047
+ }
1048
+ return;
1049
+ }
1039
1050
  case "mainAgent": {
1040
1051
  ctx.ui.notify("The current main agent and thinking level are controlled by pi's regular model/thinking selectors. The fallback chain is configured in this tab.", "info");
1041
1052
  return;
@@ -2440,17 +2440,26 @@ function registerAgentTools(pi: any): void {
2440
2440
  // v0.23.7: show ALL items in full — the user approves the whole
2441
2441
  // batch; hidden items would be approved blind.
2442
2442
  const preview = p.items.map((t, i) => ` ${i + 1}. ${t}`).join("\n");
2443
- const batchActivates = !state.goal || state.goal.status === "complete" || state.goal.status === "aborted";
2443
+ const startsFromEmpty = !state.goal || state.goal.status === "complete" || state.goal.status === "aborted";
2444
+ const replacesPaused = state.goal?.status === "paused";
2445
+ const batchActivates = startsFromEmpty || replacesPaused;
2446
+ const activationNote = startsFromEmpty
2447
+ ? listQueue().length > 0
2448
+ ? "\n\n(Confirming starts the current list head immediately.)"
2449
+ : "\n\n(List is empty — confirming ACTIVATES the first proposed item immediately as the active goal.)"
2450
+ : replacesPaused
2451
+ ? "\n\n(A paused objective is held — confirming resolves that carryover and ACTIVATES the current list head. Reject if you only wanted to queue these items.)"
2452
+ : "";
2444
2453
  let batchConfirmed = false;
2445
2454
  if (autoAccept) {
2446
2455
  batchConfirmed = true;
2447
- liveCtx.ui.notify(`List batch auto-accepted (Auto-accept drafts = on in /glla settings): ${p.items.length} items${batchActivates ? " — item 1 ACTIVATES now" : ""}.`, "info");
2456
+ liveCtx.ui.notify(`List batch auto-accepted (Auto-accept drafts = on in /glla settings): ${p.items.length} items${batchActivates ? " — the list head ACTIVATES now" : ""}.`, "info");
2448
2457
  appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "batch", count: p.items.length });
2449
2458
  } else {
2450
2459
  const c = await confirmDraft(
2451
2460
  liveCtx,
2452
2461
  "Confirm list batch",
2453
- `${p.items.length} items:\n${preview}${batchActivates ? "\n\n(List is empty — confirming ACTIVATES item 1 immediately as the active goal.)" : ""}`,
2462
+ `${p.items.length} items:\n${preview}${activationNote}`,
2454
2463
  );
2455
2464
  const afterConfirm = freshCtxForGeneration(draftGeneration);
2456
2465
  if (!afterConfirm) {
@@ -2487,12 +2496,42 @@ function registerAgentTools(pi: any): void {
2487
2496
  };
2488
2497
  }
2489
2498
  }
2499
+ // Audit 2026-09-13: zero-yield pre-scan BEFORE the conflict can
2500
+ // archive — the batch conflict sits ahead of enqueueItems' own
2501
+ // duplicate filter, so a replace-then-zero-yield run would archive
2502
+ // the paused record and gain nothing. Pinned order is preserved:
2503
+ // this is a read-only scan, not an enqueue mutation.
2504
+ const completedBeforeConflict = recentlyCompletedObjectives(liveCtx.cwd);
2505
+ const freshCount = p.items.filter((t) => {
2506
+ const text = parseListItemDeclaration(t).objective.trim();
2507
+ return text.length > 0 && !completedBeforeConflict.has(normalizeObjective(text));
2508
+ }).length;
2509
+ if (freshCount === 0) {
2510
+ draftingTarget = null;
2511
+ await ((globalThis as any).restoreDrafterModel?.() ?? Promise.resolve());
2512
+ return { content: [{ type: "text", text: "No new list items were queued — every item duplicates work COMPLETED in the last 24h. No conflict was started and the current objective is unchanged." }], details: {} };
2513
+ }
2490
2514
  draftingTarget = null;
2491
2515
  await ((globalThis as any).restoreDrafterModel?.() ?? Promise.resolve());
2492
- const wasIdle = !state.goal || state.goal.status === "complete" || state.goal.status === "aborted";
2516
+ // enqueueItems owns the empty/terminal-slot auto-start. A paused
2517
+ // objective is different: preserve the one-confirm batch, then route
2518
+ // promotion through the same carryover-aware choke point so the
2519
+ // paused record is archived before the new list head starts.
2520
+ const slotWasEmpty = !state.goal || state.goal.status === "complete" || state.goal.status === "aborted";
2521
+ const pausedBeforeEnqueue = state.goal?.status === "paused";
2493
2522
  const n = enqueueItems(liveCtx, p.items, "drafted batch");
2494
- if (wasIdle) {
2495
- return { content: [{ type: "text", text: `${n} items confirmed; first activated (list was empty). Begin work now.` }], details: {} };
2523
+ if (n === 0) {
2524
+ return { content: [{ type: "text", text: "No new list items were queued — the proposed batch was empty after duplicate/persistence checks." }], details: {} };
2525
+ }
2526
+ if (pausedBeforeEnqueue && state.goal?.status === "paused") {
2527
+ const activated = activateNextListItem(liveCtx);
2528
+ if (activated) {
2529
+ return { content: [{ type: "text", text: `${n} items confirmed; the list head activated after paused carryover resolution. Begin work now.` }], details: {} };
2530
+ }
2531
+ return { content: [{ type: "text", text: `${n} items confirmed and queued (${listQueue().length} waiting), but activation was held. The paused objective remains recoverable; retry list activation after fixing the reported issue.` }], details: {} };
2532
+ }
2533
+ if (slotWasEmpty && state.goal?.status === "active" && state.goal.policy === "list") {
2534
+ return { content: [{ type: "text", text: `${n} items confirmed; the first list item activated. Begin work now.` }], details: {} };
2496
2535
  }
2497
2536
  return { content: [{ type: "text", text: `${n} items confirmed and added to the list (${listQueue().length} waiting).` }], details: {} };
2498
2537
  }
@@ -2509,7 +2548,9 @@ function registerAgentTools(pi: any): void {
2509
2548
  const activationNote = isListDraft
2510
2549
  ? willActivate
2511
2550
  ? "\n\n(List is empty — confirming ACTIVATES this immediately as the active goal. Reject if you only wanted to add it, not start it.)"
2512
- : "\n\n(Goes into the list, waiting behind the active goal.)"
2551
+ : state.goal?.status === "paused"
2552
+ ? "\n\n(A paused objective is held — confirming queues this item, archives that paused carryover, and ACTIVATES the current list head. Reject if you only wanted to queue this item.)"
2553
+ : "\n\n(Goes into the list, waiting behind the active goal.)"
2513
2554
  : "";
2514
2555
  let confirmed = false;
2515
2556
  if (autoAccept) {
@@ -2582,9 +2623,25 @@ function registerAgentTools(pi: any): void {
2582
2623
  }
2583
2624
  hydrateListQueueFromDisk(liveCtx);
2584
2625
  const extracted = parseListItemDeclaration(full);
2626
+ // Audit 2026-09-13: resolve Subtask-of parent bindings like
2627
+ // enqueueItems — the inline path used to insert children flat.
2628
+ let singleParentId: string | undefined;
2629
+ if (extracted.parentObjective) {
2630
+ if (!extracted.objective) {
2631
+ appendLedger(liveCtx.cwd, "list_subtask_refused", { source: "drafted single", reason: "empty-child" });
2632
+ return { content: [{ type: "text", text: "The draft is a Subtask-of marker with no child text — nothing was queued. Restate the subtask, then propose again." }], details: {} };
2633
+ }
2634
+ const singleParent = listQueue().find((c: ListItem) => !c.parentId && normalizeObjective(c.objective) === normalizeObjective(extracted.parentObjective!));
2635
+ if (!singleParent) {
2636
+ appendLedger(liveCtx.cwd, "list_subtask_refused", { source: "drafted single", declaration: extracted.parentObjective.slice(0, 200) });
2637
+ return { content: [{ type: "text", text: `The Subtask-of declaration did not match any top-level queue item ("${extracted.parentObjective}") — the draft was NOT queued. Fix the parent wording so it names the queued item exactly, then propose again.` }], details: {} };
2638
+ }
2639
+ singleParentId = singleParent.id;
2640
+ }
2585
2641
  const item = assignQueueOrder([{
2586
2642
  id: newGoalId(),
2587
2643
  objective: extracted.objective,
2644
+ ...(singleParentId ? { parentId: singleParentId } : {}),
2588
2645
  ...(extracted.agentRole ? { agentRole: extracted.agentRole } : {}),
2589
2646
  verificationContract: extracted.verificationContract || undefined,
2590
2647
  ...(extracted.parallelSafe === undefined ? {} : { parallelSafe: extracted.parallelSafe }),
@@ -2601,6 +2658,11 @@ function registerAgentTools(pi: any): void {
2601
2658
  replaceState({ ...state, list: [...listQueue(), item] });
2602
2659
  persistState(liveCtx);
2603
2660
  appendLedger(liveCtx.cwd, "list_added", { id: item.id, objective: item.objective, drafted: true });
2661
+ // Audit 2026-09-13: port the v0.38.53 batch fix — a paused holder
2662
+ // never resolves, so queueing behind it strands the item under a
2663
+ // false "activates on completion" promise. Route through the same
2664
+ // carryover-aware choke point instead.
2665
+ const pausedBeforeEnqueue = state.goal?.status === "paused";
2604
2666
  if (!state.goal || state.goal.status === "complete" || state.goal.status === "aborted") {
2605
2667
  // v0.29.4: an auto-accepted draft STARTS — autoAcceptDrafts is the
2606
2668
  // pre-consent (the user asked for the draft in-session). The
@@ -2610,6 +2672,10 @@ function registerAgentTools(pi: any): void {
2610
2672
  activateNextListItem(liveCtx);
2611
2673
  return { content: [{ type: "text", text: "Confirmed and activated (list was empty). Begin work now." }], details: {} };
2612
2674
  }
2675
+ if (pausedBeforeEnqueue && state.goal?.status === "paused") {
2676
+ const activated = activateNextListItem(liveCtx);
2677
+ return { content: [{ type: "text", text: activated ? `Confirmed and activated after paused carryover resolution (${listQueue().length} still waiting). Begin work now.` : `Confirmed and queued (${listQueue().length} waiting), but activation was held. The paused objective remains recoverable; retry list activation after fixing the reported issue.` }], details: {} };
2678
+ }
2613
2679
  return { content: [{ type: "text", text: `Confirmed and added to the list (${listQueue().length} waiting). It activates when the current goal completes.` }], details: {} };
2614
2680
  }
2615
2681
  const goalConflict = await resolveDraftActivationConflict(liveCtx, "goal", p.objective.trim());
@@ -2902,12 +2968,29 @@ function registerAgentTools(pi: any): void {
2902
2968
  const clean = p.items.map((t) => t.trim()).filter((t) => t.length > 0);
2903
2969
  const wasIdle = !state.goal || state.goal.status === "complete" || state.goal.status === "aborted";
2904
2970
  const n = enqueueItems(liveCtx, clean, "agent list_add");
2971
+ // Audit 2026-09-13: report post-enqueue truth, not the stale
2972
+ // pre-enqueue snapshot — n===0 (duplicates/persist-fail) and held
2973
+ // auto-activation both used to render as a false "now active".
2974
+ if (n === 0) {
2975
+ return { content: [{ type: "text", text: "No new list items were queued — the batch was empty after duplicate/persistence checks. Nothing was activated." }], details: {} };
2976
+ }
2977
+ if (wasIdle) {
2978
+ const active = state.goal?.status === "active" && state.goal.policy === "list";
2979
+ return {
2980
+ content: [{
2981
+ type: "text",
2982
+ text: active
2983
+ ? `${n} item(s) added; the first is now active. Work it normally and call complete_goal when done — the next item activates automatically.`
2984
+ : `${n} item(s) added but activation was held. The list holds ${listQueue().length} waiting item(s); run /list next when ready.`,
2985
+ }],
2986
+ details: {},
2987
+ };
2988
+ }
2989
+ const holder = state.goal?.status === "paused" ? "paused objective" : "active goal";
2905
2990
  return {
2906
2991
  content: [{
2907
2992
  type: "text",
2908
- text: wasIdle
2909
- ? `${n} item(s) added; the first is now active. Work it normally and call complete_goal when done — the next item activates automatically.`
2910
- : `${n} item(s) queued (${listQueue().length} waiting behind the active goal).`,
2993
+ text: `${n} item(s) queued (${listQueue().length} waiting behind the ${holder}).`,
2911
2994
  }],
2912
2995
  details: {},
2913
2996
  };
@@ -2917,7 +3000,7 @@ function registerAgentTools(pi: any): void {
2917
3000
  pi.registerTool(defineTool({
2918
3001
  name: "list_activate",
2919
3002
  label: "Activate list item",
2920
- description: "Activate a specific item from the /list queue by position (1-based). Order is the default, not the law: use this when a different item should be worked next (e.g. you want to research item 5 while item 1 waits). Aborts the currently active goal if one is running.",
3003
+ description: "Activate a specific item from the /list queue by position (1-based). Order is the default, not the law: use this when a different item should be worked next (e.g. you want to research item 5 while item 1 waits). If a live objective is running you choose update / replace / cancel first (replace archives it); a paused objective is handled as carryover, not a live conflict.",
2921
3004
  parameters: Type.Object({
2922
3005
  n: Type.Number({ description: "1-based position in the queue (1 = head)" }),
2923
3006
  }),
@@ -2937,6 +3020,17 @@ function registerAgentTools(pi: any): void {
2937
3020
  const targetItem = position.item;
2938
3021
  const rawIndex = position.flatIndex + 1;
2939
3022
  if (targetItem) {
3023
+ // Audit 2026-09-13: screen content BEFORE the conflict can archive —
3024
+ // a suspicious pick queues its repair instead, with the current
3025
+ // objective preserved (mirrors the queue's own activation guard).
3026
+ const preAssessment = assessSuspiciousObjective(targetItem.objective, targetItem.verificationContract);
3027
+ if (preAssessment.suspicious) {
3028
+ queueRepairAheadOfListItem(liveCtx, targetItem, preAssessment);
3029
+ return {
3030
+ content: [{ type: "text", text: "The requested item failed the suspicious-content screen, so a safe repair item was queued in its place; the current objective was preserved and no replacement was started." }],
3031
+ details: {},
3032
+ };
3033
+ }
2940
3034
  const incomingWholeList = targetItem.objective + (targetItem.verificationContract ? `\nDone when:\n${targetItem.verificationContract}` : "");
2941
3035
  const conflict = await resolveDraftActivationConflict(liveCtx, "list", incomingWholeList);
2942
3036
  if (conflict !== "proceed") {
@@ -824,7 +824,9 @@ function refreshUI(ctx: ExtensionContext, force = false): void {
824
824
  const rows = agents as AgentsPanelRow[];
825
825
  // v0.38.23: the head lifesign reads the same rows the worker
826
826
  // rows render — one snapshot, two projections, never diverging.
827
- const extras = assembleAgentsExtras(rows, settings.subagentDisplayRichness ?? "rich", now, theme);
827
+ // Audit 2026-09-13: quiet is the canonical default (2026-09-07
828
+ // exceptions-only decision, menu label, DEFAULT_SETTINGS).
829
+ const extras = assembleAgentsExtras(rows, settings.subagentDisplayRichness ?? "quiet", now, theme);
828
830
  return extras ? { agents: extras, agentRows: rows } : { agentRows: rows };
829
831
  } catch { return {}; }
830
832
  })(),
@@ -644,6 +644,15 @@ export function buildSettingsRows(
644
644
  description:
645
645
  "post-completion follow-up enqueuer: mode, triggers, cascade, caps (postaudit / reviewer)",
646
646
  },
647
+ {
648
+ id: "contextCheckpointProjection",
649
+ section: "other",
650
+ label: "Context checkpoint projection",
651
+ valueText: show("contextCheckpointProjection", "off"),
652
+ sourceText: src("contextCheckpointProjection"),
653
+ description:
654
+ "off: leave the transcript alone so the provider prefix-cache holds (default) · on: legacy per-turn splice of a bounded continuation checkpoint",
655
+ },
647
656
  );
648
657
 
649
658
  return rows;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.38.52",
3
+ "version": "0.38.54",
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",
@@ -29,6 +29,7 @@
29
29
  },
30
30
  "files": [
31
31
  "extensions",
32
+ "skills",
32
33
  "prompts",
33
34
  "schemas",
34
35
  "scripts",
@@ -45,6 +46,9 @@
45
46
  "pi": {
46
47
  "extensions": [
47
48
  "extensions/loops/goal.ts"
49
+ ],
50
+ "skills": [
51
+ "skills/glla-delegate"
48
52
  ]
49
53
  },
50
54
  "scripts": {
@@ -1,4 +1,4 @@
1
- // pi-goal-list-loop-audit — v0.1.0
1
+ // pi-goal-list-loop-audit
2
2
  // prompts/goal-loop-continuation.md
3
3
  //
4
4
  // This file is exported as a raw string. We don't use string-concat in TS for
@@ -55,7 +55,7 @@ When a goal, list item, or pending task explicitly says `Agent: Designer`, `Role
55
55
 
56
56
  ## Available tools
57
57
 
58
- You have `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`, the `subagent` tool, and the goal toolkit (`propose_task_list`, `complete_task`, `update_task_status`, `update_task_batch`, `record_goal_judgment`, `pause_goal`, `complete_goal`), plus the list tools (`list_add`, `list_status`, `list_activate`) — when the user asks to queue more work ("add these to my list", "queue these 10 things"), call `list_add` with the items; when unsure what is running or waiting, call `list_status`.
58
+ You have `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`, the `subagent` tool, and the goal toolkit (`propose_task_list`, `complete_task`, `update_task_status`, `update_task_batch`, `record_goal_judgment`, `pause_goal`, `complete_goal`), plus the list tools (`list_add`, `list_status`, `list_activate`) — when the user asks to queue more work ("add these to my list", "queue these 10 things"), call `list_add` with the items; when unsure what is running or waiting, call `list_status`. A pasted multi-line, bulleted, numbered, or checklist-style list keeps its item wording and boundaries — never ask "exact or refined"; clarify only genuinely ambiguous individual items. Never `list_activate` an item the user has not selected or clearly authorized. Drafts stay Confirm-gated (`propose_goal_draft`); never auto-activate a speculative seed.
59
59
 
60
60
  If the objective decomposes into milestones and no task list exists yet, call `propose_task_list` early — the user confirms it, then you track progress with `complete_task` / `update_task_status` / `update_task_batch` as you go (not as a post-hoc checklist at the end). For several status changes at once, prefer one `update_task_batch` call: the whole batch validates first, milestones verify, then all statuses persist in one write. Limits: 20 tasks, 5 subtasks per task.
61
61
 
@@ -68,9 +68,20 @@ While drafting, watch the seed's shape and recommend the right mode:
68
68
 
69
69
  `[GOAL DRAFTING]`
70
70
 
71
- The user invoked `/goal` with no objective. Your job is to turn their vague
71
+ The user invoked `/goal` (or `/list` for a list draft) with no objective. Your job is to turn their vague
72
72
  request into a **confirmed goal contract**. Do NOT start substantive work yet.
73
73
 
74
+ ## Pasted list seeds
75
+
76
+ When this prompt is being used for `/list` and the user supplies a multi-line,
77
+ bulleted, numbered, or checklist-style seed, use its structure and wording as
78
+ provided. Strip only import syntax and empty headings/lines, then propose the
79
+ resulting items together through `items[]`. Do not ask whether the user wants
80
+ the list exact or refined: there is no such choice. Clarify only a genuinely
81
+ ambiguous individual item; never turn a complete pasted list into a
82
+ meta-choice. A direct user request to add such a list may use the normal
83
+ bulk-enqueue path instead of drafting.
84
+
74
85
  ## Protocol
75
86
 
76
87
  1. If the request is vague, ask ONE focused question at a time. Offer a
@@ -49,6 +49,16 @@ confirms.
49
49
  earn its place — decisions, constraints, and checkable outcomes only.
50
50
  Do not pad with boilerplate the user did not ask for.
51
51
 
52
+ ## Pasted list seeds
53
+
54
+ When plan mode is used for `/list` and the user supplies a multi-line,
55
+ bulleted, numbered, or checklist-style seed, preserve its item boundaries and
56
+ wording as supplied. Normalize only import syntax and empty headings/lines;
57
+ do not ask whether the list should be exact or refined. Clarify only a
58
+ genuinely ambiguous individual item, never turn a complete pasted list into a
59
+ meta-choice. Research may deepen contracts and verification, but it must not
60
+ rewrite a clear pasted list merely to make it sound more polished.
61
+
52
62
  ## Hard rules (unchanged from regular drafting)
53
63
 
54
64
  - Do NOT start implementing during planning. Research is read-only.
@@ -48,7 +48,21 @@ try {
48
48
  "extensions/goal-loop-auditor-process.ts",
49
49
  "scripts/goal-auditor-launch.mjs",
50
50
  "scripts/goal-auditor-worker.mjs",
51
+ // Audit 2026-09-13: every runtime-loaded file rides the gate, not just
52
+ // the entry points — a files-narrowing that drops a prompt, the schema,
53
+ // or a spawned worker must fail here, not at draft time in the field.
54
+ "scripts/goal-compactor-worker.mjs",
55
+ "scripts/durable-wait.mjs",
51
56
  "scripts/release-pack-smoke.mjs",
57
+ "prompts/goal-loop-continuation.md",
58
+ "prompts/goal-loop-draft.md",
59
+ "prompts/goal-loop-forever-draft.md",
60
+ "prompts/goal-loop-forever-metricless.md",
61
+ "prompts/goal-loop-forever.md",
62
+ "prompts/goal-loop-plan-loop.md",
63
+ "prompts/goal-loop-plan.md",
64
+ "schemas/goal.schema.json",
65
+ "skills/glla-delegate/SKILL.md",
52
66
  ];
53
67
  const listing = run("tar", ["-tzf", tarball]);
54
68
  for (const file of required) {
@@ -94,6 +108,20 @@ try {
94
108
  const auditor = await jiti.import(path.join(installedPackage, "extensions/goal-loop-auditor-process.ts"));
95
109
  if (typeof auditor.resolveWorkerCommand !== "function") throw new Error("packed auditor process did not expose its worker command resolver");
96
110
  if (auditor.resolveWorkerCommand("/usr/bin/node") !== "/usr/bin/node") throw new Error("packed auditor resolver returned an unexpected command");
111
+ // Audit 2026-09-13: presence is not loadability — run the packed skill
112
+ // through Pi's own loader against the INSTALLED tree (the source-tree
113
+ // check in release-contract.test.ts cannot catch tarball-only defects).
114
+ const piCoding = await jiti.import("@earendil-works/pi-coding-agent");
115
+ if (typeof piCoding.loadSkills !== "function") throw new Error("packed gate could not resolve Pi loadSkills");
116
+ const skillProbe = piCoding.loadSkills({
117
+ cwd: installedPackage,
118
+ agentDir: installedPackage,
119
+ skillPaths: ["skills/glla-delegate"],
120
+ includeDefaults: false,
121
+ });
122
+ if (skillProbe.diagnostics.length > 0) throw new Error(`packed skill has loader diagnostics: ${JSON.stringify(skillProbe.diagnostics).slice(0, 300)}`);
123
+ if (!skillProbe.skills.some((skill) => skill.name === "glla-delegate")) throw new Error("packed skill not discoverable by Pi loadSkills");
124
+ console.log("OK: packed glla-delegate skill loads with zero diagnostics");
97
125
  console.log(`OK: installed and imported ${packageName}@${packageJson.version} from its packed tarball`);
98
126
  } finally {
99
127
  fs.rmSync(workspace, { recursive: true, force: true });
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: glla-delegate
3
+ description: "Use GLLA's audited goals and list queue from normal chat: queue explicit work, offer follow-ups before adding them, or draft a confirmed goal. Includes safe handling for pasted list-like input."
4
+ ---
5
+
6
+ # GLLA delegation
7
+
8
+ GLLA is not a scratch task list. A queued item becomes an audited goal, and
9
+ when the queue is idle its first item may activate immediately. Use these
10
+ tools in the main session only:
11
+
12
+ - `list_status` is read-only and safe for inspection.
13
+ - If the user explicitly asks to queue, add, backlog, or list work, call
14
+ `list_add` with the requested items. Do not turn a direct queue request
15
+ into an unnecessary interview.
16
+ - If you discover a useful follow-up while doing other work, finish the
17
+ current request and offer to queue it first. Queue it without another
18
+ question only when the user has given standing permission such as
19
+ “queue follow-ups as you find them.”
20
+ - If one durable, multi-hour objective is warranted, interview only what is
21
+ genuinely unknown, then call `propose_goal_draft`; the user's Confirm
22
+ dialog is the activation gate. Never activate a speculative raw seed.
23
+ (Unless Auto-accept drafts is on in /glla settings, which skips the
24
+ Confirm — never promise a dialog that setting suppresses.)
25
+ - Never call `list_activate` for an item the user has not selected or clearly
26
+ authorized. Never control `/loop` bounds, metrics, or stopping on the
27
+ user's behalf.
28
+
29
+ ## Pasted list-like input
30
+
31
+ When the user supplies a multi-line, bulleted, numbered, or checklist-style
32
+ list and asks to use or add it, use the structure and wording as supplied.
33
+ Strip only import syntax and empty headings/lines, then pass the resulting
34
+ items in one `list_add` call. Do **not** ask whether the user wants the list
35
+ “exact” or “refined”; there is no such choice. Clarify only a genuinely
36
+ ambiguous individual item, never turn a complete pasted list into a
37
+ meta-choice.
38
+
39
+ If a `/list` drafting session is already active, do not bypass its Confirm
40
+ gate with `list_add`: pass the complete pasted set once as `items[]` to
41
+ `propose_goal_draft` instead. This keeps one confirmation for the whole
42
+ batch and prevents partial activation.
43
+
44
+ Use the tools, not slash-command text, when acting. Worker and subagent
45
+ sessions do not own GLLA state; respect a foreign-session refusal instead of
46
+ routing around it.