pi-goal-list-loop-audit 0.38.51 → 0.38.53
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 +63 -0
- package/README.md +9 -1
- package/docs/DESIGN-long-running-supervision.md +4 -1
- package/docs/INDEX.md +4 -2
- package/extensions/completion-summary.ts +55 -15
- package/extensions/goal-loop-core.ts +74 -1
- package/extensions/loops/goal-auditor-hooks.ts +3 -1
- package/extensions/loops/goal-list-queue.ts +5 -2
- package/extensions/loops/goal-orchestrator.ts +4 -2
- package/extensions/loops/goal-tools.ts +54 -9
- package/package.json +5 -1
- package/prompts/goal-loop-continuation.md +1 -1
- package/prompts/goal-loop-draft.md +11 -0
- package/prompts/goal-loop-plan.md +10 -0
- package/scripts/release-pack-smoke.mjs +1 -0
- package/skills/glla-delegate/SKILL.md +44 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.38.53 — Consent-safe GLLA delegation skill and list-draft staging (2026-09-13)
|
|
4
|
+
|
|
5
|
+
Added the packaged `glla-delegate` skill for normal-chat goal/list delegation.
|
|
6
|
+
It documents the consent ladder, main-session ownership, auto-activation warning,
|
|
7
|
+
and tool-first usage without adding new runtime powers. Its quoted frontmatter
|
|
8
|
+
is validated through Pi's public loader and the packed-file release gate.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Consent-safe delegation guidance:** explicit queue requests may use
|
|
13
|
+
`list_add`; discovered follow-ups are offered first; durable goals use
|
|
14
|
+
`propose_goal_draft`; speculative queueing, unowned activation, and agent
|
|
15
|
+
`/loop` control remain prohibited.
|
|
16
|
+
- **Pasted-list policy:** supplied multi-line, bullet, numbered, and checklist
|
|
17
|
+
structure is preserved without an exact-versus-refined meta-question. Only a
|
|
18
|
+
genuinely ambiguous individual item remains eligible for clarification, with
|
|
19
|
+
one-record seeds retaining the normal draft path.
|
|
20
|
+
- **Release/package coverage:** `skills/glla-delegate/SKILL.md` is allowlisted,
|
|
21
|
+
declared in `pi.skills`, required by the packed-artifact smoke, and loaded
|
|
22
|
+
with zero diagnostics in the release contract.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **Draft staging:** interview and stale-entry gates remain ahead of Confirm;
|
|
27
|
+
confirmation remains ahead of conflict handling and enqueue.
|
|
28
|
+
- **Paused list carryover:** a confirmed multi-item draft now routes its first
|
|
29
|
+
promotion through the carryover-aware activation choke point, archiving the
|
|
30
|
+
paused predecessor before starting the list head instead of leaving the batch
|
|
31
|
+
stranded behind it.
|
|
32
|
+
|
|
33
|
+
## 0.38.52 — Second Gemini-gap: gate-inventory table + per-finding Test Results (2026-09-12)
|
|
34
|
+
|
|
35
|
+
Surveyed five more Antigravity report screenshots
|
|
36
|
+
(audit/SECOND-GEMINI-SUMMARY-GAP-2026-09-12.md) and closed the second gap
|
|
37
|
+
in the terminal render only (chat + transcript + archive human layer;
|
|
38
|
+
progress cards, status, widget, external notifies unchanged).
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **Agent-supplied gate inventory:** optional `complete_goal gateRows`
|
|
43
|
+
(one row per quality gate with scope and outcome notes, sanitized at
|
|
44
|
+
the claim boundary, max 10). The Verification Summary widens to
|
|
45
|
+
Quality Gate | Scope | Status | Notes and supersedes the mechanical
|
|
46
|
+
Tests rows. Statuses are mechanically derived, never claimed: PASS
|
|
47
|
+
only when the notes say pass with zero failures, FAIL on nonzero
|
|
48
|
+
failures, REPORTED otherwise — so bare-exit notes like `Clean exit 0`
|
|
49
|
+
honestly stay REPORTED. Absent inventory keeps the 3-col mechanical
|
|
50
|
+
table byte-identical.
|
|
51
|
+
- **Per-finding test proof:** optional parallel `tests` array on each
|
|
52
|
+
`findingGroups` entry (`tests[i]` proves `findings[i]`, clipped to
|
|
53
|
+
findings, sanitized at the claim boundary). Nested area sections render
|
|
54
|
+
each as a `Test Results:` sub-bullet; the 4+-group table rides the
|
|
55
|
+
proof in the Evidence cell (`Evidence · Tests:`). Groups without tests
|
|
56
|
+
render exactly as v0.38.50.
|
|
57
|
+
|
|
58
|
+
### Preserved
|
|
59
|
+
|
|
60
|
+
- Chat close stays archive-only: no commit hash, emoji markers, or
|
|
61
|
+
artifact links in chat (survey rejects). The verbatim six-label
|
|
62
|
+
machine record, auditor-verified-before-posting, and the honesty
|
|
63
|
+
rules (PASS only on pass, NO VERDICT never renders, pipe escaping)
|
|
64
|
+
are unchanged.
|
|
65
|
+
|
|
3
66
|
## 0.38.51 — Bare /list drafts from context like bare /goal (2026-09-12)
|
|
4
67
|
|
|
5
68
|
User note: empty `/list` could not start anything while empty `/goal`
|
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
|
|
@@ -167,6 +168,13 @@ quietly inventing an unbounded backlog.
|
|
|
167
168
|
/list cancel # stop the active item and drop waiting items
|
|
168
169
|
```
|
|
169
170
|
|
|
171
|
+
A pasted multi-line, bulleted, numbered, or checklist-style list keeps its
|
|
172
|
+
item wording and boundaries; GLLA does not ask for an “exact or refined” choice.
|
|
173
|
+
Only a genuinely ambiguous individual item needs clarification. The packaged
|
|
174
|
+
`skills/glla-delegate/SKILL.md` records the safe normal-chat delegation path:
|
|
175
|
+
explicit queue requests may use `list_add`, while discovered follow-ups are
|
|
176
|
+
offered first and durable goals remain Confirm-gated.
|
|
177
|
+
|
|
170
178
|
Order is the default, not the law. Automatic advance normally uses the head of
|
|
171
179
|
the queue, while `/list next <n>` or the agent's `list_activate` tool can choose
|
|
172
180
|
another item. Numbering always matches `/list` output. After a list item is
|
|
@@ -114,7 +114,10 @@ The full six-label recap lives verbatim in the archive (`## Completion summary`)
|
|
|
114
114
|
and the status/history surfaces. The human layer in chat, transcript, and the
|
|
115
115
|
archive's `## Terminal summary` renders the same facts as rich sections
|
|
116
116
|
(`## Done: <objective> — <outcome>`, duration line, Key Findings grouped by area
|
|
117
|
-
or tabulated at 4+ groups
|
|
117
|
+
or tabulated at 4+ groups with per-finding `Test Results:` proof lines,
|
|
118
|
+
Verification Summary table — widened to Quality Gate | Scope | Status | Notes
|
|
119
|
+
with derived statuses when the agent supplies a gate inventory — Next, capped
|
|
120
|
+
and clipped
|
|
118
121
|
to fixed budgets). Width-bound and external surfaces (status line, widget card,
|
|
119
122
|
external notifies) use the compact single-line projection of all six labels.
|
|
120
123
|
Every terminal goal notification, including version-bearing already-shipped claims,
|
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.53 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,7 +25,8 @@ 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)
|
|
28
|
+
auditor inherit/clear parity), the v0.38.53 consent-safe `glla-delegate`
|
|
29
|
+
skill/list-drafting release, and the v0.38.25 post-objective summary
|
|
29
30
|
(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.
|
|
30
31
|
- `../README.md`: what the plugin is, install, quickstart, and the
|
|
31
32
|
architectural guarantee (drafting + confirm + detached auditor).
|
|
@@ -37,6 +38,7 @@ Policy contracts and recent changes live in the `audit/` directory of the
|
|
|
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.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FindingGroup, Goal, Status } from "./goal-loop-core.js";
|
|
1
|
+
import type { FindingGroup, GateRow, Goal, Status } from "./goal-loop-core.js";
|
|
2
2
|
import { fmtElapsed, truncateCells } from "./goal-loop-display.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -347,7 +347,8 @@ export function extractEvidenceTokens(text: string): { text: string; evidence: s
|
|
|
347
347
|
/**
|
|
348
348
|
* v0.38.50: enforce the total-findings budget across groups in order —
|
|
349
349
|
* groups that go empty are dropped so the table/nesting never shows a
|
|
350
|
-
* bare area header.
|
|
350
|
+
* bare area header. v0.38.52: parallel `tests` ride along clipped to the
|
|
351
|
+
* surviving findings so tests[i] still proves findings[i].
|
|
351
352
|
*/
|
|
352
353
|
export function takeBudgetedGroups(groups: FindingGroup[]): FindingGroup[] {
|
|
353
354
|
const out: FindingGroup[] = [];
|
|
@@ -356,7 +357,9 @@ export function takeBudgetedGroups(groups: FindingGroup[]): FindingGroup[] {
|
|
|
356
357
|
if (remaining <= 0) break;
|
|
357
358
|
const findings = group.findings.slice(0, remaining);
|
|
358
359
|
if (findings.length === 0) continue;
|
|
359
|
-
|
|
360
|
+
// v0.38.52: keep the parallel test lines aligned with the survivors.
|
|
361
|
+
const aligned = group.tests?.slice(0, findings.length);
|
|
362
|
+
out.push(aligned && aligned.length > 0 ? { title: group.title, findings, tests: aligned } : { title: group.title, findings });
|
|
360
363
|
remaining -= findings.length;
|
|
361
364
|
}
|
|
362
365
|
return out;
|
|
@@ -407,7 +410,12 @@ export function requestEchoHeadline(kind: "Done" | "Aborted", objective: string
|
|
|
407
410
|
* v0.38.50: agent-structured groups render as `#### n. Area` subsections
|
|
408
411
|
* with nested evidence bullets; at RICH_TABLE_GROUP_THRESHOLD groups the
|
|
409
412
|
* same facts render as an Area | Finding | Evidence table instead.
|
|
410
|
-
* Without groups the flat six-label projection stays the fallback.
|
|
413
|
+
* Without groups the flat six-label projection stays the fallback.
|
|
414
|
+
* v0.38.52: optional per-finding `tests` render as a `Test Results:`
|
|
415
|
+
* sub-bullet (nested) or ride the Evidence cell (table); optional agent
|
|
416
|
+
* `gates` widen the Verification table to Gate | Scope | Status | Notes
|
|
417
|
+
* with mechanically derived statuses, superseding the mechanical Tests
|
|
418
|
+
* rows. Without gates the 3-col mechanical table is byte-identical. */
|
|
411
419
|
export function buildRichTerminalParts(args: {
|
|
412
420
|
outcome: string;
|
|
413
421
|
details: string[];
|
|
@@ -416,6 +424,7 @@ export function buildRichTerminalParts(args: {
|
|
|
416
424
|
objective?: string;
|
|
417
425
|
durationLine?: string | null;
|
|
418
426
|
groups?: FindingGroup[];
|
|
427
|
+
gates?: GateRow[];
|
|
419
428
|
}): RichTerminalParts {
|
|
420
429
|
const { findings, tests, next } = partitionRichDetails(args.details);
|
|
421
430
|
const headline = requestEchoHeadline("Done", args.objective, args.outcome);
|
|
@@ -425,21 +434,28 @@ export function buildRichTerminalParts(args: {
|
|
|
425
434
|
if (useTable) {
|
|
426
435
|
findingLines.push("| Area | Finding | Evidence |", "| --- | --- | --- |");
|
|
427
436
|
for (const group of budgeted) {
|
|
428
|
-
|
|
437
|
+
group.findings.forEach((finding, fi) => {
|
|
429
438
|
const { text, evidence } = extractEvidenceTokens(finding);
|
|
430
439
|
const { lead, body } = leadBody(text);
|
|
440
|
+
// v0.38.52: test proof rides the Evidence cell (tables have no
|
|
441
|
+
// sub-bullets); cells stay pipe-escaped and budgeted.
|
|
442
|
+
const proof = group.tests?.[fi]?.trim();
|
|
443
|
+
const evidenceCell = [evidence.join(", ") || "\u2014", ...(proof ? [`Tests: ${proof}`] : [])].join(" \u00b7 ");
|
|
431
444
|
findingLines.push(
|
|
432
|
-
`| ${escapeTableCell(clipSummaryValue(group.title, RICH_OBJECTIVE_ECHO_CHARS))} | ${escapeTableCell(`**${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`)} | ${escapeTableCell(
|
|
445
|
+
`| ${escapeTableCell(clipSummaryValue(group.title, RICH_OBJECTIVE_ECHO_CHARS))} | ${escapeTableCell(`**${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`)} | ${escapeTableCell(clipSummaryValue(evidenceCell, RICH_VALUE_BUDGET))} |`,
|
|
433
446
|
);
|
|
434
|
-
}
|
|
447
|
+
});
|
|
435
448
|
}
|
|
436
449
|
} else if (budgeted.length > 0) {
|
|
437
450
|
budgeted.forEach((group, i) => {
|
|
438
451
|
findingLines.push(`#### ${i + 1}. ${clipSummaryValue(group.title, RICH_OBJECTIVE_ECHO_CHARS)}`);
|
|
439
|
-
|
|
452
|
+
group.findings.forEach((finding, fi) => {
|
|
440
453
|
const { lead, body } = leadBody(finding);
|
|
441
454
|
findingLines.push(`- **${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`);
|
|
442
|
-
|
|
455
|
+
// v0.38.52: per-finding test proof (shot C) — absent stays absent.
|
|
456
|
+
const proof = group.tests?.[fi]?.trim();
|
|
457
|
+
if (proof) findingLines.push(` - Test Results: ${clipSummaryValue(proof, RICH_VALUE_BUDGET)}`);
|
|
458
|
+
});
|
|
443
459
|
});
|
|
444
460
|
} else {
|
|
445
461
|
findings.slice(0, RICH_FINDINGS_CAP).forEach((detail, i) => {
|
|
@@ -448,17 +464,33 @@ export function buildRichTerminalParts(args: {
|
|
|
448
464
|
});
|
|
449
465
|
}
|
|
450
466
|
const tableRows: string[] = [];
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
467
|
+
const gates = (args.gates ?? []).slice(0, 10);
|
|
468
|
+
if (gates.length > 0) {
|
|
469
|
+
// v0.38.52 (shots B/D): the agent inventory supersedes the mechanical
|
|
470
|
+
// Tests rows — one curated gate list, never a duplicated one. Status
|
|
471
|
+
// is derived, never claimed: PASS only when the notes say pass with
|
|
472
|
+
// zero failures ("Clean exit 0"-style notes honestly stay REPORTED).
|
|
473
|
+
for (const row of gates) {
|
|
474
|
+
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))} |`);
|
|
476
|
+
}
|
|
477
|
+
} else {
|
|
478
|
+
for (const detail of tests.slice(0, 2)) {
|
|
479
|
+
const { body } = leadBody(detail);
|
|
480
|
+
tableRows.push(`| Tests | ${testsRowStatus(body)} | ${escapeTableCell(clipSummaryValue(body, RICH_VALUE_BUDGET))} |`);
|
|
481
|
+
}
|
|
454
482
|
}
|
|
455
483
|
const auditStatus = auditRowStatus(args.auditHistory);
|
|
456
484
|
if (auditStatus !== "NO VERDICT") {
|
|
457
485
|
const auditBody = args.countsLine.replace(/^\u2014\s*/, "").replace(/\.\s*$/, "");
|
|
458
|
-
|
|
486
|
+
if (gates.length > 0) {
|
|
487
|
+
tableRows.push(`| Audit | ${escapeTableCell(clipSummaryValue(auditBody, RICH_VALUE_BUDGET))} | ${auditStatus} | ${escapeTableCell(clipSummaryValue(auditBody, RICH_VALUE_BUDGET))} |`);
|
|
488
|
+
} else {
|
|
489
|
+
tableRows.push(`| Audit | ${auditStatus} | ${escapeTableCell(clipSummaryValue(auditBody, RICH_VALUE_BUDGET))} |`);
|
|
490
|
+
}
|
|
459
491
|
}
|
|
460
492
|
const tableLines = tableRows.length > 0
|
|
461
|
-
? ["| Check | Status | Details |", "| --- | --- | --- |", ...tableRows]
|
|
493
|
+
? [(gates.length > 0 ? "| Quality Gate | Scope | Status | Notes |" : "| Check | Status | Details |"), (gates.length > 0 ? "| --- | --- | --- | --- |" : "| --- | --- | --- |"), ...tableRows]
|
|
462
494
|
: [];
|
|
463
495
|
const nextLines = next.slice(0, RICH_NEXT_CAP).map((detail) => {
|
|
464
496
|
const { lead, body } = leadBody(detail);
|
|
@@ -495,7 +527,7 @@ export function composeRichTerminalLines(parts: RichTerminalParts, opts?: { head
|
|
|
495
527
|
* raw six-label recap stays verbatim in `## Completion summary` as the
|
|
496
528
|
* machine layer. Headline follows terminal status; aborted records never
|
|
497
529
|
* wear a `Done` headline. */
|
|
498
|
-
export function buildRichArchiveSection(goal: Goal, status: Status, archivePath: string, findingGroups?: FindingGroup[]): string[] {
|
|
530
|
+
export function buildRichArchiveSection(goal: Goal, status: Status, archivePath: string, findingGroups?: FindingGroup[], gateRows?: GateRow[]): string[] {
|
|
499
531
|
const facts: CompletionSummaryFacts = { goal, status, archivePath };
|
|
500
532
|
const summary = resolveCompletionSummary(facts, goal.completionSummary).summary;
|
|
501
533
|
const brief = humanCompletionBrief(summary, 140, RICH_VALUE_BUDGET);
|
|
@@ -513,6 +545,7 @@ export function buildRichArchiveSection(goal: Goal, status: Status, archivePath:
|
|
|
513
545
|
objective: goal.objective,
|
|
514
546
|
durationLine: buildDurationLine(goal),
|
|
515
547
|
groups: findingGroups,
|
|
548
|
+
gates: gateRows,
|
|
516
549
|
});
|
|
517
550
|
return [
|
|
518
551
|
...composeRichTerminalLines(parts, {
|
|
@@ -611,6 +644,12 @@ export interface TerminalApprovalRenderInput {
|
|
|
611
644
|
* sanitized at claim time). Absent keeps the flat six-label fallback.
|
|
612
645
|
*/
|
|
613
646
|
findingGroups?: FindingGroup[];
|
|
647
|
+
/**
|
|
648
|
+
* v0.38.52: agent-supplied verification gate rows (complete_goal
|
|
649
|
+
* gateRows, sanitized at claim time). Absent keeps the mechanical
|
|
650
|
+
* 3-col verification table.
|
|
651
|
+
*/
|
|
652
|
+
gateRows?: GateRow[];
|
|
614
653
|
}
|
|
615
654
|
|
|
616
655
|
export interface TerminalApprovalRender {
|
|
@@ -689,6 +728,7 @@ export function buildTerminalApprovalRender(input: TerminalApprovalRenderInput):
|
|
|
689
728
|
objective: input.goal.objective,
|
|
690
729
|
durationLine: buildDurationLine(input.goal),
|
|
691
730
|
groups: input.findingGroups,
|
|
731
|
+
gates: input.gateRows,
|
|
692
732
|
});
|
|
693
733
|
const chatBody = composeRichTerminalLines(richParts);
|
|
694
734
|
const transcriptBody = composeRichTerminalLines(richParts);
|
|
@@ -282,12 +282,21 @@ export type AuditorRecoveryFailureClass = "transport" | "timeout" | "no-verdict"
|
|
|
282
282
|
export interface FindingGroup {
|
|
283
283
|
title: string;
|
|
284
284
|
findings: string[];
|
|
285
|
+
/**
|
|
286
|
+
* v0.38.52 (second Gemini-gap survey, shot C): optional per-finding
|
|
287
|
+
* test-result lines, aligned by index with `findings` (tests[i] proves
|
|
288
|
+
* findings[i]). Absent/short entries simply omit the sub-line — old
|
|
289
|
+
* claims without `tests` render byte-identical to v0.38.50.
|
|
290
|
+
*/
|
|
291
|
+
tests?: string[];
|
|
285
292
|
}
|
|
286
293
|
|
|
287
294
|
export const MAX_FINDING_GROUPS = 6;
|
|
288
295
|
export const MAX_GROUP_FINDINGS = 6;
|
|
289
296
|
export const MAX_GROUP_TITLE_CHARS = 120;
|
|
290
297
|
export const MAX_GROUP_FINDING_CHARS = 500;
|
|
298
|
+
/** v0.38.52: per-finding test-result lines share the finding budget. */
|
|
299
|
+
export const MAX_GROUP_TESTS_CHARS = 500;
|
|
291
300
|
|
|
292
301
|
/**
|
|
293
302
|
* Bound agent-supplied finding groups at the trust boundary. Drops
|
|
@@ -313,11 +322,64 @@ export function sanitizeFindingGroups(value: unknown): FindingGroup[] | undefine
|
|
|
313
322
|
if (text) findings.push(text);
|
|
314
323
|
}
|
|
315
324
|
if (findings.length === 0) continue;
|
|
316
|
-
|
|
325
|
+
// v0.38.52: optional test-result lines ride parallel to findings.
|
|
326
|
+
// Non-string entries drop; the array is clipped to the finding count
|
|
327
|
+
// so tests[i] always proves findings[i] after budgeting.
|
|
328
|
+
let tests: string[] | undefined;
|
|
329
|
+
if (Array.isArray(raw.tests)) {
|
|
330
|
+
const parsed: string[] = [];
|
|
331
|
+
for (const entry of raw.tests) {
|
|
332
|
+
if (typeof entry !== "string") continue;
|
|
333
|
+
const text = entry.trim().slice(0, MAX_GROUP_TESTS_CHARS);
|
|
334
|
+
if (text) parsed.push(text);
|
|
335
|
+
}
|
|
336
|
+
if (parsed.length > 0) tests = parsed.slice(0, findings.length);
|
|
337
|
+
}
|
|
338
|
+
groups.push(tests ? { title, findings, tests } : { title, findings });
|
|
317
339
|
}
|
|
318
340
|
return groups.length > 0 ? groups : undefined;
|
|
319
341
|
}
|
|
320
342
|
|
|
343
|
+
/**
|
|
344
|
+
* v0.38.52 (second Gemini-gap survey, shots B/D): one agent-supplied
|
|
345
|
+
* verification gate row for the widened terminal table. Presentation
|
|
346
|
+
* only — the six-label completionSummary stays the audited substance.
|
|
347
|
+
* Status is NEVER agent-controlled: the renderer derives PASS/FAIL/
|
|
348
|
+
* REPORTED mechanically from `notes` via testsRowStatus, so a gate can
|
|
349
|
+
* only claim PASS when its notes say so with zero failures.
|
|
350
|
+
*/
|
|
351
|
+
export interface GateRow {
|
|
352
|
+
gate: string;
|
|
353
|
+
scope?: string;
|
|
354
|
+
notes?: string;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export const MAX_GATE_ROWS = 10;
|
|
358
|
+
export const MAX_GATE_GATE_CHARS = 120;
|
|
359
|
+
export const MAX_GATE_SCOPE_CHARS = 200;
|
|
360
|
+
export const MAX_GATE_NOTES_CHARS = 400;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Bound agent-supplied gate rows at the trust boundary. Drops blank
|
|
364
|
+
* gates; clips cells. Returns undefined when nothing valid survives —
|
|
365
|
+
* the mechanical 3-col table stays the fallback.
|
|
366
|
+
*/
|
|
367
|
+
export function sanitizeGateRows(value: unknown): GateRow[] | undefined {
|
|
368
|
+
if (!Array.isArray(value)) return undefined;
|
|
369
|
+
const rows: GateRow[] = [];
|
|
370
|
+
for (const entry of value) {
|
|
371
|
+
if (rows.length >= MAX_GATE_ROWS) break;
|
|
372
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
373
|
+
const raw = entry as Record<string, unknown>;
|
|
374
|
+
const gate = typeof raw.gate === "string" ? raw.gate.trim().slice(0, MAX_GATE_GATE_CHARS) : "";
|
|
375
|
+
if (!gate) continue;
|
|
376
|
+
const scope = typeof raw.scope === "string" ? raw.scope.trim().slice(0, MAX_GATE_SCOPE_CHARS) : "";
|
|
377
|
+
const notes = typeof raw.notes === "string" ? raw.notes.trim().slice(0, MAX_GATE_NOTES_CHARS) : "";
|
|
378
|
+
rows.push({ gate, ...(scope ? { scope } : {}), ...(notes ? { notes } : {}) });
|
|
379
|
+
}
|
|
380
|
+
return rows.length > 0 ? rows : undefined;
|
|
381
|
+
}
|
|
382
|
+
|
|
321
383
|
export interface PendingCompletion {
|
|
322
384
|
completionSummary?: string;
|
|
323
385
|
verificationSummary?: string;
|
|
@@ -352,6 +414,12 @@ export interface PendingCompletion {
|
|
|
352
414
|
* Absent means the flat six-label render stays the fallback.
|
|
353
415
|
*/
|
|
354
416
|
findingGroups?: FindingGroup[];
|
|
417
|
+
/**
|
|
418
|
+
* v0.38.52: agent-supplied verification gate rows for the widened
|
|
419
|
+
* terminal table (complete_goal gateRows, sanitized at claim time).
|
|
420
|
+
* Absent means the mechanical 3-col table stays the fallback.
|
|
421
|
+
*/
|
|
422
|
+
gateRows?: GateRow[];
|
|
355
423
|
/**
|
|
356
424
|
* Durable one-shot recovery fence. A parked claim may receive one
|
|
357
425
|
* automatic retry after a validated healthy lifecycle/recovery event;
|
|
@@ -2213,6 +2281,10 @@ function normalizePendingCompletion(value: unknown): PendingCompletion {
|
|
|
2213
2281
|
// garbage at the boundary degrades to absent (flat fallback), never
|
|
2214
2282
|
// to a half-structured projection.
|
|
2215
2283
|
const sanitizedGroups = sanitizeFindingGroups(raw.findingGroups);
|
|
2284
|
+
// v0.38.52: agent-supplied gate rows survive reloads sanitized; garbage
|
|
2285
|
+
// at the boundary degrades to absent (mechanical fallback), never to a
|
|
2286
|
+
// half-structured projection.
|
|
2287
|
+
const sanitizedGates = sanitizeGateRows(raw.gateRows);
|
|
2216
2288
|
return {
|
|
2217
2289
|
...canonicalOrUnknown,
|
|
2218
2290
|
...(auditorCandidateRefs !== undefined ? { auditorCandidateRefs } : {}),
|
|
@@ -2226,6 +2298,7 @@ function normalizePendingCompletion(value: unknown): PendingCompletion {
|
|
|
2226
2298
|
...(auditorFailureAt ? { auditorFailureAt } : {}),
|
|
2227
2299
|
...(timeoutEscalation !== undefined ? { timeoutEscalation } : {}),
|
|
2228
2300
|
...(sanitizedGroups ? { findingGroups: sanitizedGroups } : {}),
|
|
2301
|
+
...(sanitizedGates ? { gateRows: sanitizedGates } : {}),
|
|
2229
2302
|
...(phase === "running" || phase === "recovery-pending" || phase === "retry-waiting" ? { phase } : {}),
|
|
2230
2303
|
...(typeof raw.retryAttempts === "number"
|
|
2231
2304
|
? { retryAttempts: raw.retryAttempts }
|
|
@@ -1442,13 +1442,15 @@ async function retryStoredCompletionAudit(origin: CompletionAuditOrigin = "provi
|
|
|
1442
1442
|
// v0.38.37: the deliberate non-do the agent claimed, if any.
|
|
1443
1443
|
...(claim.leftOut ? { leftOut: claim.leftOut } : {}),
|
|
1444
1444
|
// v0.38.50: agent-structured finding groups ride the audited claim.
|
|
1445
|
+
// v0.38.52: same for the gate inventory.
|
|
1445
1446
|
...(claim.findingGroups ? { findingGroups: claim.findingGroups } : {}),
|
|
1447
|
+
...(claim.gateRows ? { gateRows: claim.gateRows } : {}),
|
|
1446
1448
|
extras: inspectionSessionPath
|
|
1447
1449
|
? [`Auditor session kept for review: pi --session ${inspectionSessionPath} (or pi --fork ${inspectionSessionPath}).`]
|
|
1448
1450
|
: [],
|
|
1449
1451
|
});
|
|
1450
1452
|
const approvalObjective = state.goal.objective;
|
|
1451
|
-
const archived = archiveCurrentGoal(liveCtx, "complete", `auditor ${result.model} approved (${origin})`, {}, { findingGroups: claim.findingGroups });
|
|
1453
|
+
const archived = archiveCurrentGoal(liveCtx, "complete", `auditor ${result.model} approved (${origin})`, {}, { findingGroups: claim.findingGroups, gateRows: claim.gateRows });
|
|
1452
1454
|
if (!archived) {
|
|
1453
1455
|
// archiveCurrentGoal already preserved the live record and warned the
|
|
1454
1456
|
// user. Keep the approved claim recoverable, but never emit a terminal
|
|
@@ -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
|
|
@@ -30,6 +30,7 @@ import { state, replaceState, persistStateLine } from "../goal-state.js";
|
|
|
30
30
|
|
|
31
31
|
import {
|
|
32
32
|
type FindingGroup,
|
|
33
|
+
type GateRow,
|
|
33
34
|
type Goal,
|
|
34
35
|
type Policy,
|
|
35
36
|
type State,
|
|
@@ -1102,7 +1103,8 @@ function archiveCurrentGoal(
|
|
|
1102
1103
|
patch: Partial<Pick<Goal, "auditHistory" | "completionSummary" | "pendingTasks">> = {},
|
|
1103
1104
|
// v0.38.50: agent-structured finding groups ride the audited claim into
|
|
1104
1105
|
// the archive rich section only — abort/wip paths omit it (flat fallback).
|
|
1105
|
-
|
|
1106
|
+
// v0.38.52: same for the gate inventory.
|
|
1107
|
+
opts?: { findingGroups?: FindingGroup[]; gateRows?: GateRow[] },
|
|
1106
1108
|
): boolean {
|
|
1107
1109
|
if (!state.goal) return false;
|
|
1108
1110
|
if (status !== "complete" && status !== "aborted") return false;
|
|
@@ -1151,7 +1153,7 @@ function archiveCurrentGoal(
|
|
|
1151
1153
|
// it carries the same rich markdown as chat over the verbatim
|
|
1152
1154
|
// six-label machine record. Built from the terminal goal AFTER the
|
|
1153
1155
|
// summary fence resolves, so chat and archive cannot disagree.
|
|
1154
|
-
const richSection = buildRichArchiveSection(terminalGoal, status, archivePath, opts?.findingGroups);
|
|
1156
|
+
const richSection = buildRichArchiveSection(terminalGoal, status, archivePath, opts?.findingGroups, opts?.gateRows);
|
|
1155
1157
|
const richMd = `${md}\n## Terminal summary\n\n${richSection.join("\n")}\n`;
|
|
1156
1158
|
// An existing same-id archive is an immutable fence. Check it before
|
|
1157
1159
|
// publishing an intent so an unrelated/sentinel winner can never make the
|
|
@@ -134,6 +134,7 @@ isGoalRevisionCurrent,
|
|
|
134
134
|
buildDurableChoiceRecord,
|
|
135
135
|
normalizeDurableDeferRecommendationInput,
|
|
136
136
|
sanitizeFindingGroups,
|
|
137
|
+
sanitizeGateRows,
|
|
137
138
|
} from "../goal-loop-core.js";
|
|
138
139
|
import {
|
|
139
140
|
applyValidatedBatch,
|
|
@@ -540,6 +541,7 @@ function registerAgentTools(pi: any): void {
|
|
|
540
541
|
findingGroups: Type.Optional(Type.Array(Type.Object({
|
|
541
542
|
title: Type.String({ maxLength: 120, description: "Work-area name (e.g. a subsystem, screen, or phase)" }),
|
|
542
543
|
findings: Type.Array(Type.String({ maxLength: 500 }), { maxItems: 6, description: "Findings in this area as `Lead: body with path:line evidence` (max 6 per area)" }),
|
|
544
|
+
tests: Type.Optional(Type.Array(Type.String({ maxLength: 500 }), { maxItems: 6, description: "v0.38.52: optional per-finding test-result lines, aligned by index with findings (tests[i] proves findings[i]); each renders as a `Test Results:` sub-line" })),
|
|
543
545
|
}), {
|
|
544
546
|
maxItems: 6,
|
|
545
547
|
description:
|
|
@@ -549,6 +551,18 @@ function registerAgentTools(pi: any): void {
|
|
|
549
551
|
"Presentation only — the six-label completionSummary stays the audited substance, and file:line tokens are never invented. " +
|
|
550
552
|
"Omit for single-area work — the flat six-label render stays the fallback.",
|
|
551
553
|
})),
|
|
554
|
+
gateRows: Type.Optional(Type.Array(Type.Object({
|
|
555
|
+
gate: Type.String({ maxLength: 120, description: "Gate name (e.g. Unit Tests, Typecheck, E2E, Production Build)" }),
|
|
556
|
+
scope: Type.Optional(Type.String({ maxLength: 200, description: "What the gate covered (e.g. suites, tiers, invariants)" })),
|
|
557
|
+
notes: Type.Optional(Type.String({ maxLength: 400, description: "Outcome notes with counts (e.g. 743 passed, 0 failed). Status is DERIVED from these notes — PASS only when they say pass with zero failures, never claimed." })),
|
|
558
|
+
}), {
|
|
559
|
+
maxItems: 10,
|
|
560
|
+
description:
|
|
561
|
+
"v0.38.52: optional verification gate inventory for the terminal summary. " +
|
|
562
|
+
"Widens the Verification table to Quality Gate | Scope | Status | Notes and supersedes the mechanical Tests rows. " +
|
|
563
|
+
"Presentation only — the six-label completionSummary stays the audited substance, and counts are never invented. " +
|
|
564
|
+
"Omit when there is no gate inventory — the mechanical 3-col table stays the fallback.",
|
|
565
|
+
})),
|
|
552
566
|
}),
|
|
553
567
|
async execute(_id, params, signal, _onUpdate, execCtx) {
|
|
554
568
|
const foreign0 = foreignToolGuard(execCtx);
|
|
@@ -572,7 +586,7 @@ function registerAgentTools(pi: any): void {
|
|
|
572
586
|
}
|
|
573
587
|
return { content: [{ type: "text", text: `No active goal — it is ${state.goal.status}.` }], details: {} };
|
|
574
588
|
}
|
|
575
|
-
const p = params as { completionSummary?: string; verificationSummary?: string; newObjective?: string; leftOut?: string; findingGroups?: unknown };
|
|
589
|
+
const p = params as { completionSummary?: string; verificationSummary?: string; newObjective?: string; leftOut?: string; findingGroups?: unknown; gateRows?: unknown };
|
|
576
590
|
if (state.goal.repairTarget) {
|
|
577
591
|
return {
|
|
578
592
|
content: [{ type: "text", text: `This repair card cannot be completed yet. Redraft the original target as a confirmed task list with propose_task_list (include objective: ${state.goal.repairTarget.objective.slice(0, 180)}), then continue the real work.` }],
|
|
@@ -787,7 +801,9 @@ function registerAgentTools(pi: any): void {
|
|
|
787
801
|
}
|
|
788
802
|
// v0.38.50: agent-structured finding groups ride the same claim —
|
|
789
803
|
// sanitized once at the boundary, rendered only after approval.
|
|
804
|
+
// v0.38.52: same for the agent-supplied gate inventory.
|
|
790
805
|
const sanitizedGroups = sanitizeFindingGroups(p.findingGroups);
|
|
806
|
+
const sanitizedGates = sanitizeGateRows(p.gateRows);
|
|
791
807
|
const completionClaim = beginCompletionAudit(ctx, {
|
|
792
808
|
completionSummary: finalSummary,
|
|
793
809
|
verificationSummary: p.verificationSummary,
|
|
@@ -795,6 +811,7 @@ function registerAgentTools(pi: any): void {
|
|
|
795
811
|
// the terminal render — the only source the summary may cite.
|
|
796
812
|
...(p.leftOut?.trim() ? { leftOut: p.leftOut.trim().slice(0, 500) } : {}),
|
|
797
813
|
...(sanitizedGroups ? { findingGroups: sanitizedGroups } : {}),
|
|
814
|
+
...(sanitizedGates ? { gateRows: sanitizedGates } : {}),
|
|
798
815
|
at: nowIso(),
|
|
799
816
|
}, "complete-goal");
|
|
800
817
|
if (!completionClaim) {
|
|
@@ -1252,6 +1269,7 @@ function registerAgentTools(pi: any): void {
|
|
|
1252
1269
|
// v0.38.50: same for agent-structured finding groups.
|
|
1253
1270
|
const escLeftOut = state.goal.pendingCompletion?.leftOut;
|
|
1254
1271
|
const escFindingGroups = state.goal.pendingCompletion?.findingGroups;
|
|
1272
|
+
const escGateRows = state.goal.pendingCompletion?.gateRows;
|
|
1255
1273
|
updateGoal({ status: "active", auditHistory: history, pendingCompletion: undefined, pauseReason: "audit aborted by user (Esc)" }, ctx);
|
|
1256
1274
|
const abortConfirmCtx = freshCtxForGeneration(auditGeneration);
|
|
1257
1275
|
if (!abortConfirmCtx) return staleToolResult();
|
|
@@ -1294,8 +1312,9 @@ function registerAgentTools(pi: any): void {
|
|
|
1294
1312
|
// captured from it above.
|
|
1295
1313
|
...(escLeftOut ? { leftOut: escLeftOut } : {}),
|
|
1296
1314
|
...(escFindingGroups ? { findingGroups: escFindingGroups } : {}),
|
|
1315
|
+
...(escGateRows ? { gateRows: escGateRows } : {}),
|
|
1297
1316
|
});
|
|
1298
|
-
if (!archiveCurrentGoal(ctx, "complete", terminalReason, {}, { findingGroups: escFindingGroups })) {
|
|
1317
|
+
if (!archiveCurrentGoal(ctx, "complete", terminalReason, {}, { findingGroups: escFindingGroups, gateRows: escGateRows })) {
|
|
1299
1318
|
return {
|
|
1300
1319
|
content: [{ type: "text", text: "The audit was aborted, but the terminal archive could not be persisted. The goal remains active; fix persistence and retry." }],
|
|
1301
1320
|
details: {},
|
|
@@ -1351,14 +1370,16 @@ function registerAgentTools(pi: any): void {
|
|
|
1351
1370
|
// v0.38.37: the deliberate non-do rides the durable claim.
|
|
1352
1371
|
...(durableCompletionClaim.leftOut ? { leftOut: durableCompletionClaim.leftOut } : {}),
|
|
1353
1372
|
// v0.38.50: agent-structured finding groups ride the same claim.
|
|
1373
|
+
// v0.38.52: same for the gate inventory.
|
|
1354
1374
|
...(durableCompletionClaim.findingGroups ? { findingGroups: durableCompletionClaim.findingGroups } : {}),
|
|
1375
|
+
...(durableCompletionClaim.gateRows ? { gateRows: durableCompletionClaim.gateRows } : {}),
|
|
1355
1376
|
extras: inspectionSessionPath
|
|
1356
1377
|
? [`Auditor session kept for review: pi --session ${inspectionSessionPath} (or pi --fork ${inspectionSessionPath}).`]
|
|
1357
1378
|
: [],
|
|
1358
1379
|
});
|
|
1359
1380
|
const manualObjective = state.goal.objective;
|
|
1360
1381
|
const manualGoalId = state.goal.id;
|
|
1361
|
-
const archived = archiveCurrentGoal(ctx, "complete", terminalReason, {}, { findingGroups: durableCompletionClaim.findingGroups });
|
|
1382
|
+
const archived = archiveCurrentGoal(ctx, "complete", terminalReason, {}, { findingGroups: durableCompletionClaim.findingGroups, gateRows: durableCompletionClaim.gateRows });
|
|
1362
1383
|
if (!archived) {
|
|
1363
1384
|
// The archive helper preserves the live objective and emits the
|
|
1364
1385
|
// persistence warning. Stop here: an approved verdict is not a
|
|
@@ -2419,17 +2440,26 @@ function registerAgentTools(pi: any): void {
|
|
|
2419
2440
|
// v0.23.7: show ALL items in full — the user approves the whole
|
|
2420
2441
|
// batch; hidden items would be approved blind.
|
|
2421
2442
|
const preview = p.items.map((t, i) => ` ${i + 1}. ${t}`).join("\n");
|
|
2422
|
-
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
|
+
: "";
|
|
2423
2453
|
let batchConfirmed = false;
|
|
2424
2454
|
if (autoAccept) {
|
|
2425
2455
|
batchConfirmed = true;
|
|
2426
|
-
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");
|
|
2427
2457
|
appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "batch", count: p.items.length });
|
|
2428
2458
|
} else {
|
|
2429
2459
|
const c = await confirmDraft(
|
|
2430
2460
|
liveCtx,
|
|
2431
2461
|
"Confirm list batch",
|
|
2432
|
-
`${p.items.length} items:\n${preview}${
|
|
2462
|
+
`${p.items.length} items:\n${preview}${activationNote}`,
|
|
2433
2463
|
);
|
|
2434
2464
|
const afterConfirm = freshCtxForGeneration(draftGeneration);
|
|
2435
2465
|
if (!afterConfirm) {
|
|
@@ -2468,10 +2498,25 @@ function registerAgentTools(pi: any): void {
|
|
|
2468
2498
|
}
|
|
2469
2499
|
draftingTarget = null;
|
|
2470
2500
|
await ((globalThis as any).restoreDrafterModel?.() ?? Promise.resolve());
|
|
2471
|
-
|
|
2501
|
+
// enqueueItems owns the empty/terminal-slot auto-start. A paused
|
|
2502
|
+
// objective is different: preserve the one-confirm batch, then route
|
|
2503
|
+
// promotion through the same carryover-aware choke point so the
|
|
2504
|
+
// paused record is archived before the new list head starts.
|
|
2505
|
+
const slotWasEmpty = !state.goal || state.goal.status === "complete" || state.goal.status === "aborted";
|
|
2506
|
+
const pausedBeforeEnqueue = state.goal?.status === "paused";
|
|
2472
2507
|
const n = enqueueItems(liveCtx, p.items, "drafted batch");
|
|
2473
|
-
if (
|
|
2474
|
-
return { content: [{ type: "text", text:
|
|
2508
|
+
if (n === 0) {
|
|
2509
|
+
return { content: [{ type: "text", text: "No new list items were queued — the proposed batch was empty after duplicate/persistence checks." }], details: {} };
|
|
2510
|
+
}
|
|
2511
|
+
if (pausedBeforeEnqueue && state.goal?.status === "paused") {
|
|
2512
|
+
const activated = activateNextListItem(liveCtx);
|
|
2513
|
+
if (activated) {
|
|
2514
|
+
return { content: [{ type: "text", text: `${n} items confirmed; the list head activated after paused carryover resolution. Begin work now.` }], details: {} };
|
|
2515
|
+
}
|
|
2516
|
+
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: {} };
|
|
2517
|
+
}
|
|
2518
|
+
if (slotWasEmpty && state.goal?.status === "active" && state.goal.policy === "list") {
|
|
2519
|
+
return { content: [{ type: "text", text: `${n} items confirmed; the first list item activated. Begin work now.` }], details: {} };
|
|
2475
2520
|
}
|
|
2476
2521
|
return { content: [{ type: "text", text: `${n} items confirmed and added to the list (${listQueue().length} waiting).` }], details: {} };
|
|
2477
2522
|
}
|
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.53",
|
|
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": {
|
|
@@ -205,4 +205,4 @@ The orchestrator's backstop is the stall watchdog: three consecutive turns with
|
|
|
205
205
|
|
|
206
206
|
`complete_goal` submits a nonterminal claim, not a completion verdict. While the detached audit is pending, do not say the goal is done/complete/approved or present a final success summary. Do not wait or poll. If a response is needed, give one brief pending-status sentence. GLLA posts the concrete outcome-first summary into chat after verified approval and durable archive, without needing another user prompt. Do not duplicate that summary. A rejected claim remains unfinished and repair work continues.
|
|
207
207
|
|
|
208
|
-
The agent-written completionSummary is user-facing evidence, not a step log: open with the outcome in plain words, then one verifiable result per label with its proof inline (test counts, file paths, commit refs) — never raw run stats. Keep each label value to one or two short clauses so it survives the renderer's clause-boundary clipping (rich sections budget ~200 chars per value); cite human-readable proof (counts, versions, repo-relative paths, PR numbers) and never machine paths (/var/tmp logs, tarballs — those live in the archive only). Cut values at clause boundaries, never mid-word; write Next: as one concrete follow-up action or none (audit-self-referential lines like "verdict decides" are stripped at render); pass what was deliberately left out as the complete_goal leftOut parameter — it renders as the closing bullet, and is omitted when absent. When the work spans distinct areas (subsystems, screens, phases), group the findings with the complete_goal findingGroups parameter — one group per area, each finding as `Lead: body` with repo-relative `path:line` evidence tokens (never invented paths); 4+ groups render as an Area | Finding | Evidence table, fewer as nested area sections. Single-area work omits findingGroups — the flat six-label render stays the fallback.
|
|
208
|
+
The agent-written completionSummary is user-facing evidence, not a step log: open with the outcome in plain words, then one verifiable result per label with its proof inline (test counts, file paths, commit refs) — never raw run stats. Keep each label value to one or two short clauses so it survives the renderer's clause-boundary clipping (rich sections budget ~200 chars per value); cite human-readable proof (counts, versions, repo-relative paths, PR numbers) and never machine paths (/var/tmp logs, tarballs — those live in the archive only). Cut values at clause boundaries, never mid-word; write Next: as one concrete follow-up action or none (audit-self-referential lines like "verdict decides" are stripped at render); pass what was deliberately left out as the complete_goal leftOut parameter — it renders as the closing bullet, and is omitted when absent. When the work spans distinct areas (subsystems, screens, phases), group the findings with the complete_goal findingGroups parameter — one group per area, each finding as `Lead: body` with repo-relative `path:line` evidence tokens (never invented paths); 4+ groups render as an Area | Finding | Evidence table, fewer as nested area sections. Add per-finding test proof with the parallel `tests` array (tests[i] proves findings[i]) — each renders as a `Test Results:` sub-line. When the work ran named quality gates (unit, typecheck, E2E, build, invariants), pass the complete_goal gateRows inventory — one row per gate with scope and outcome notes; the Verification table widens to Quality Gate | Scope | Status | Notes with mechanically derived statuses (PASS only when the notes say pass with zero failures) and supersedes the mechanical Tests rows. Single-area work omits findingGroups — the flat six-label render stays the fallback; work with no gate inventory omits gateRows — the mechanical 3-col table stays the fallback.
|
|
@@ -71,6 +71,17 @@ While drafting, watch the seed's shape and recommend the right mode:
|
|
|
71
71
|
The user invoked `/goal` 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.
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
- Never call `list_activate` for an item the user has not selected or clearly
|
|
24
|
+
authorized. Never control `/loop` bounds, metrics, or stopping on the
|
|
25
|
+
user's behalf.
|
|
26
|
+
|
|
27
|
+
## Pasted list-like input
|
|
28
|
+
|
|
29
|
+
When the user supplies a multi-line, bulleted, numbered, or checklist-style
|
|
30
|
+
list and asks to use or add it, use the structure and wording as supplied.
|
|
31
|
+
Strip only import syntax and empty headings/lines, then pass the resulting
|
|
32
|
+
items in one `list_add` call. Do **not** ask whether the user wants the list
|
|
33
|
+
“exact” or “refined”; there is no such choice. Clarify only a genuinely
|
|
34
|
+
ambiguous individual item, never turn a complete pasted list into a
|
|
35
|
+
meta-choice.
|
|
36
|
+
|
|
37
|
+
If a `/list` drafting session is already active, do not bypass its Confirm
|
|
38
|
+
gate with `list_add`: pass the complete pasted set once as `items[]` to
|
|
39
|
+
`propose_goal_draft` instead. This keeps one confirmation for the whole
|
|
40
|
+
batch and prevents partial activation.
|
|
41
|
+
|
|
42
|
+
Use the tools, not slash-command text, when acting. Worker and subagent
|
|
43
|
+
sessions do not own GLLA state; respect a foreign-session refusal instead of
|
|
44
|
+
routing around it.
|