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 +59 -0
- package/INSTALL.md +9 -3
- package/README.md +17 -5
- package/docs/INDEX.md +8 -6
- package/docs/SETTINGS.md +2 -0
- package/extensions/completion-summary.ts +71 -16
- package/extensions/goal-settings.ts +10 -0
- package/extensions/loops/goal-activation.ts +5 -1
- package/extensions/loops/goal-list-queue.ts +8 -2
- package/extensions/loops/goal-runtime-globals.ts +2 -0
- package/extensions/loops/goal-settings-ui.ts +11 -0
- package/extensions/loops/goal-tools.ts +105 -11
- package/extensions/loops/goal-ui.ts +3 -1
- package/extensions/settings-menu.ts +9 -0
- package/package.json +5 -1
- package/prompts/goal-loop-continuation.md +2 -2
- package/prompts/goal-loop-draft.md +12 -1
- package/prompts/goal-loop-plan.md +10 -0
- package/scripts/release-pack-smoke.mjs +28 -0
- package/skills/glla-delegate/SKILL.md +46 -0
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: … ·
|
|
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: … ·
|
|
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 #
|
|
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): `
|
|
309
|
-
(default:
|
|
310
|
-
(
|
|
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.
|
|
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)
|
|
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,
|
|
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
|
|
33
|
-
companion plugins
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
722
|
-
//
|
|
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
|
|
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 ? " —
|
|
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}${
|
|
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
|
-
|
|
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 (
|
|
2495
|
-
return { content: [{ type: "text", text:
|
|
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
|
-
:
|
|
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:
|
|
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).
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|