pi-goal-list-loop-audit 0.38.68 → 0.38.70
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 +28 -0
- package/README.md +7 -7
- package/docs/INDEX.md +1 -1
- package/docs/SETTINGS.md +1 -0
- package/extensions/completion-summary.ts +91 -44
- package/extensions/goal-continuation.ts +44 -3
- package/extensions/goal-loop-core.ts +30 -0
- package/extensions/goal-loop-display.ts +44 -8
- package/extensions/goal-recovery.ts +87 -16
- package/extensions/goal-settings.ts +10 -0
- package/extensions/loops/goal-auditor-hooks.ts +7 -9
- package/extensions/loops/goal-tools.ts +71 -12
- package/extensions/main-model-recovery.ts +44 -5
- package/package.json +2 -2
- package/schemas/goal.schema.json +14 -0
- package/scripts/release-pack-smoke.mjs +56 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.38.70 — Paused action cards lead with the action (2026-09-20)
|
|
4
|
+
|
|
5
|
+
- Paused cards render lifecycle/next, banner, reason, and suggested action before tally, provenance, and judgment, so Pi core tail truncation cuts history instead of the instruction (field 20260920_152744).
|
|
6
|
+
- Judgment rides collapsed on action cards (header + selection; full plaques stay in `/goal status`).
|
|
7
|
+
|
|
8
|
+
## 0.38.69 — About and README refresh (2026-09-20)
|
|
9
|
+
|
|
10
|
+
- Refined npm about line and README intro: tighter mission control wording, no em dashes.
|
|
11
|
+
- README range wording uses plain "to" (0 to 10, 1 to 10).
|
|
12
|
+
|
|
13
|
+
### Antigravity auto-continue port (survey 2026-09-19)
|
|
14
|
+
|
|
15
|
+
- Mid-run interruption budget (`decisionPauseBudget`): decision pauses past
|
|
16
|
+
budget auto-default to the recommended option and ledger
|
|
17
|
+
`decision_budget_auto_default` instead of stalling mid-run.
|
|
18
|
+
- Quota sleep-until-reset: quota-class failures with an explicit upstream
|
|
19
|
+
hint sleep until reset (5h cap; 5s eager first retry preserved); quota
|
|
20
|
+
waits never park at the 24h horizon; background primary probe re-arms at
|
|
21
|
+
reset while fallback serves.
|
|
22
|
+
- Completion-claim evidence-density lint: zero path:line tokens AND zero
|
|
23
|
+
gate rows rides a ledgered NOTE annotation (claim still audits); every
|
|
24
|
+
terminal path already ends with the archive record pointer.
|
|
25
|
+
- Per-repo pitfall registry: `.pi-glla/pitfalls.md` (absent stays absent)
|
|
26
|
+
rides the continuation prompt under REPO PITFALLS, ledgered once per goal.
|
|
27
|
+
- Objection-attached retries: every disapproval extracts durable TODOs via
|
|
28
|
+
`durableObjectionsForDisapproval` (aggressive keeps only cap/no-progress
|
|
29
|
+
behaviors); two pre-existing source-shape pins revised to the new shape.
|
|
30
|
+
|
|
3
31
|
## 0.38.68 — Relentless auto-continue: heat-routed exhaustion, quota retries, truthful recovering (2026-09-19)
|
|
4
32
|
|
|
5
33
|
### Length-exhaustion wedge fixed at the root: heat, not budget (field 162348)
|
package/README.md
CHANGED
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
<img src="media/glla2.png" alt="GLLA mission control" width="960">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
> **Long
|
|
7
|
+
> **Long running, high leverage autonomy for pi.**
|
|
8
8
|
>
|
|
9
9
|
> Give pi a meaningful outcome. GLLA helps it research, plan, execute,
|
|
10
10
|
> recover, and prove the result over hours or days instead of treating one
|
|
11
11
|
> chat turn as the whole job.
|
|
12
12
|
|
|
13
13
|
`pi-goal-list-loop-audit` (GLLA) is mission control for autonomous work in
|
|
14
|
-
[pi](https://github.com/badlogic/pi-mono). It
|
|
14
|
+
[pi](https://github.com/badlogic/pi-mono). It fits work that is too broad,
|
|
15
15
|
too long, or too important to leave to a single uninterrupted prompt:
|
|
16
16
|
repo-wide changes, migrations, audits, research, documentation overhauls,
|
|
17
17
|
large refactors, and continuous improvement.
|
|
18
18
|
|
|
19
|
-
GLLA does not promise that an agent
|
|
20
|
-
agent's work **more effective, durable, recoverable, and
|
|
19
|
+
GLLA does not promise that an agent never makes a mistake. It makes the
|
|
20
|
+
agent's work **more effective, durable, recoverable, and hard to declare
|
|
21
21
|
finished without evidence**:
|
|
22
22
|
|
|
23
23
|
- You state the outcome and what “done” means.
|
|
@@ -30,7 +30,7 @@ finished without evidence**:
|
|
|
30
30
|
- A separate detached auditor checks the saved completion claim before GLLA
|
|
31
31
|
accepts it.
|
|
32
32
|
|
|
33
|
-
The aim is not
|
|
33
|
+
The aim is not "run forever." The aim is **more useful work per unit of
|
|
34
34
|
attention, with event-driven progress instead of guessed-duration waiting, and
|
|
35
35
|
better evidence at the end**. See `docs/DESIGN-long-running-supervision.md` for
|
|
36
36
|
the long-running policy.
|
|
@@ -398,7 +398,7 @@ proof of a quota or billing state.
|
|
|
398
398
|
|
|
399
399
|
- automatic retries are bounded and visible; a BUSY/no-stream turn is parked
|
|
400
400
|
and re-dispatched within the configurable **Zero-stream retries** budget
|
|
401
|
-
(default 3, range 0
|
|
401
|
+
(default 3, range 0 to 10), then requires explicit resume;
|
|
402
402
|
- `/goal resume`, `/list resume`, and `/loop resume` are explicit recovery
|
|
403
403
|
paths;
|
|
404
404
|
- a user abort means stop, not “try again behind my back”;
|
|
@@ -431,7 +431,7 @@ Open `/glla` for the settings table. The most important choices are:
|
|
|
431
431
|
- **Subagent hang escalation:** warning-only at `0`, or one child-specific
|
|
432
432
|
action after a confirmed frozen interval;
|
|
433
433
|
- **Zero-stream retries:** automatic GLLA recovery attempts after a busy,
|
|
434
|
-
stream-silent Pi turn; `0` keeps recovery manual and `1
|
|
434
|
+
stream-silent Pi turn; `0` keeps recovery manual and `1 to 10` bounds repeats;
|
|
435
435
|
- **Audit cap and retry cadence:** bounds for repeated objections and
|
|
436
436
|
infrastructure recovery.
|
|
437
437
|
|
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.70 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
|
package/docs/SETTINGS.md
CHANGED
|
@@ -64,6 +64,7 @@ copies are ignored (the recovery runtime reads the global file):
|
|
|
64
64
|
| `wedgeAlertMinutes` | unset (30, or off while aggressive mode is on — the default) | Busy-but-silent minutes before the wedge alert; `0` = off. The menu shows the effective value. |
|
|
65
65
|
| `autoResume` | `false` | Restored goals/loops/lists auto-resume in fresh sessions. Global-only. |
|
|
66
66
|
| `decisionPopup` | `true` | Decision pauses pop the picker (`false` = widget card only). |
|
|
67
|
+
| `decisionPauseBudget` | unset (unlimited) | Max agent-authored decision pauses per goal before auto-default: the (N+1)-th adopts the recommended option as a logged assumption without pausing. `0` = relentless from the first decision. |
|
|
67
68
|
| `carryover` | `"pause"` | Stale carryover on new activation: `"pause"` / `"clear"` / `"resume"`. |
|
|
68
69
|
| `autoAcceptDrafts` | `false` | Drafts activate without the Confirm dialog (unattended rigs). |
|
|
69
70
|
| `auditCap` | `5` (10 aggressive) | Pause after N consecutive auditor disapprovals (`0` = unlimited). |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import type
|
|
2
|
+
import { sanitizeDisplayText, type FindingGroup, type GateRow, type Goal, type Status } from "./goal-loop-core.js";
|
|
3
3
|
import { fmtElapsed, truncateCells } from "./goal-loop-display.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -180,9 +180,10 @@ export interface HumanCompletionBrief {
|
|
|
180
180
|
* the durable archive keeps the full text. Falls back to the original
|
|
181
181
|
* when stripping would empty the value. */
|
|
182
182
|
export function chatSafeDetailValue(value: string): string {
|
|
183
|
+
const safeValue = sanitizeDisplayText(value);
|
|
183
184
|
// v0.38.45 audit: loop the innermost-group strip to a fixpoint
|
|
184
185
|
// (bounded) — a single pass left nested husks like "(log )" behind.
|
|
185
|
-
let withoutGroups = stripMachineGroups(
|
|
186
|
+
let withoutGroups = stripMachineGroups(safeValue);
|
|
186
187
|
const stripped = withoutGroups
|
|
187
188
|
.replace(/(?:\/var)?\/tmp\/\S+/g, "")
|
|
188
189
|
.replace(/\btarballs?\s+\S+\.tgz\b/gi, "")
|
|
@@ -190,7 +191,7 @@ export function chatSafeDetailValue(value: string): string {
|
|
|
190
191
|
.replace(/\(\s*\)/g, "")
|
|
191
192
|
.replace(/\s{2,}/g, " ")
|
|
192
193
|
.trim();
|
|
193
|
-
return stripped ||
|
|
194
|
+
return stripped || safeValue;
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
/** The human briefing: outcome first in its own words, then only the
|
|
@@ -384,7 +385,7 @@ function stripMachineTokens(line: string): string {
|
|
|
384
385
|
export function structuredSummaryLines(summary: string | undefined, label = "Outcome:"): string[] | null {
|
|
385
386
|
const raw = rawLabelValue(summary ?? "", label);
|
|
386
387
|
if (!raw || !isSectionStructured(raw)) return null;
|
|
387
|
-
const lines = raw.split("\n").map(stripMachineTokens);
|
|
388
|
+
const lines = raw.split("\n").map((line) => stripMachineTokens(sanitizeDisplayText(line)));
|
|
388
389
|
while (lines.length > 0 && !(lines[0] ?? "").trim()) lines.shift();
|
|
389
390
|
while (lines.length > 0 && !(lines[lines.length - 1] ?? "").trim()) lines.pop();
|
|
390
391
|
let text = lines.join("\n");
|
|
@@ -416,7 +417,7 @@ export interface RichTerminalParts {
|
|
|
416
417
|
}
|
|
417
418
|
|
|
418
419
|
function escapeTableCell(value: string): string {
|
|
419
|
-
return value.replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
|
|
420
|
+
return sanitizeDisplayText(value).replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
|
|
420
421
|
}
|
|
421
422
|
|
|
422
423
|
function testsRowStatus(value: string): string {
|
|
@@ -470,9 +471,10 @@ function auditRowStatus(history: Goal["auditHistory"]): string {
|
|
|
470
471
|
|
|
471
472
|
/** Split a stale-filtered `Label: value` detail into a bold lead + body. */
|
|
472
473
|
function leadBody(detail: string): { lead: string; body: string } {
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
return { lead:
|
|
474
|
+
const safeDetail = sanitizeDisplayText(detail);
|
|
475
|
+
const separator = safeDetail.indexOf(":");
|
|
476
|
+
if (separator < 0) return { lead: "Note", body: safeDetail };
|
|
477
|
+
return { lead: safeDetail.slice(0, separator).trim() || "Note", body: safeDetail.slice(separator + 1).trim() };
|
|
476
478
|
}
|
|
477
479
|
|
|
478
480
|
/**
|
|
@@ -487,7 +489,7 @@ const EVIDENCE_TOKEN_PATTERN = /(?<![/~+\w])[\w.+][\w.+/-]*\.[A-Za-z0-9]{1,8}:\d
|
|
|
487
489
|
|
|
488
490
|
export function extractEvidenceTokens(text: string): { text: string; evidence: string[] } {
|
|
489
491
|
const evidence: string[] = [];
|
|
490
|
-
const stripped = text
|
|
492
|
+
const stripped = sanitizeDisplayText(text)
|
|
491
493
|
.replace(EVIDENCE_TOKEN_PATTERN, (match) => {
|
|
492
494
|
if (evidence.length < RICH_EVIDENCE_TOKENS_PER_FINDING && !evidence.includes(match)) evidence.push(match);
|
|
493
495
|
// The token often rides in parentheses — move the wrapper too, so
|
|
@@ -501,6 +503,29 @@ export function extractEvidenceTokens(text: string): { text: string; evidence: s
|
|
|
501
503
|
return { text: stripped, evidence };
|
|
502
504
|
}
|
|
503
505
|
|
|
506
|
+
/**
|
|
507
|
+
* v0.38.69 (Antigravity port): completion-claim evidence-density lint.
|
|
508
|
+
* A walkthrough always ships areas + counts; GLLA richness rode optional
|
|
509
|
+
* agent params, so a flat six-label claim with zero path:line tokens and
|
|
510
|
+
* zero gate rows still passed. Returns a NOTE annotation when the claim
|
|
511
|
+
* carries no verifiable pointer at all (no evidence tokens across the
|
|
512
|
+
* summary and every group finding, and no gate rows) — the claim still
|
|
513
|
+
* audits, but the terminal render falls back to recorded facts. Tokens in
|
|
514
|
+
* finding groups count; gate rows count as density without tokens.
|
|
515
|
+
*/
|
|
516
|
+
export function completionSummaryDensityNote(
|
|
517
|
+
summary: string | undefined,
|
|
518
|
+
groups?: FindingGroup[],
|
|
519
|
+
gates?: GateRow[],
|
|
520
|
+
): string | undefined {
|
|
521
|
+
if (!summary?.trim()) return undefined;
|
|
522
|
+
const pool = [summary, ...(groups ?? []).flatMap((group) => group.findings ?? [])].join("\n");
|
|
523
|
+
const { evidence } = extractEvidenceTokens(pool);
|
|
524
|
+
if (evidence.length > 0) return undefined;
|
|
525
|
+
if ((gates ?? []).length > 0) return undefined;
|
|
526
|
+
return "low evidence density: no path:line evidence tokens and no verification gate rows — add file:line pointers (e.g. extensions/goal-recovery.ts:847) or a gateRows inventory so the terminal render is verifiable";
|
|
527
|
+
}
|
|
528
|
+
|
|
504
529
|
/**
|
|
505
530
|
* v0.38.50: one compact duration line from durable goal state — turns,
|
|
506
531
|
* wall-clock elapsed since creation, and audit count. Only known facts
|
|
@@ -509,7 +534,9 @@ export function extractEvidenceTokens(text: string): { text: string; evidence: s
|
|
|
509
534
|
export function buildDurationLine(goal: Goal, now = Date.now()): string | null {
|
|
510
535
|
const segs: string[] = [];
|
|
511
536
|
const turns = goal.telemetry?.turns;
|
|
512
|
-
|
|
537
|
+
// Field 20260918_172705: a zero turns count means untracked telemetry,
|
|
538
|
+
// not a known fact — omit it while elapsed/audits still render.
|
|
539
|
+
if (typeof turns === "number" && Number.isFinite(turns) && turns > 0) {
|
|
513
540
|
segs.push(`${turns} turn${turns === 1 ? "" : "s"}`);
|
|
514
541
|
}
|
|
515
542
|
const started = Date.parse(goal.createdAt ?? "");
|
|
@@ -519,15 +546,25 @@ export function buildDurationLine(goal: Goal, now = Date.now()): string | null {
|
|
|
519
546
|
return segs.length > 0 ? `\u2014 ${segs.join(" \u00b7 ")}` : null;
|
|
520
547
|
}
|
|
521
548
|
|
|
522
|
-
/** Partition informing details into findings / Tests / next buckets.
|
|
549
|
+
/** Partition informing details into findings / Tests / next buckets.
|
|
550
|
+
* Field 20260918_172705: exact-duplicate lines collapse to one per bucket
|
|
551
|
+
* (repeated claim details rendered as doubled rows). Near-duplicates with
|
|
552
|
+
* different wording still render — that prose belongs to the claim. */
|
|
523
553
|
export function partitionRichDetails(details: string[]): { findings: string[]; tests: string[]; next: string[] } {
|
|
554
|
+
const seen = new Set<string>();
|
|
555
|
+
const push = (bucket: string[], detail: string) => {
|
|
556
|
+
const key = detail.trim();
|
|
557
|
+
if (seen.has(key)) return;
|
|
558
|
+
seen.add(key);
|
|
559
|
+
bucket.push(detail);
|
|
560
|
+
};
|
|
524
561
|
const findings: string[] = [];
|
|
525
562
|
const tests: string[] = [];
|
|
526
563
|
const next: string[] = [];
|
|
527
564
|
for (const detail of details) {
|
|
528
|
-
if (/^\s*Tests\s*:/i.test(detail))
|
|
529
|
-
else if (/^\s*(Next|Unresolved|Left out)\s*:/i.test(detail))
|
|
530
|
-
else
|
|
565
|
+
if (/^\s*Tests\s*:/i.test(detail)) push(tests, detail);
|
|
566
|
+
else if (/^\s*(Next|Unresolved|Left out)\s*:/i.test(detail)) push(next, detail);
|
|
567
|
+
else push(findings, detail);
|
|
531
568
|
}
|
|
532
569
|
return { findings, tests, next };
|
|
533
570
|
}
|
|
@@ -538,15 +575,17 @@ export function partitionRichDetails(details: string[]): { findings: string[]; t
|
|
|
538
575
|
* `## Done — outcome` shape stays, so direct unit callers are unaffected.
|
|
539
576
|
*/
|
|
540
577
|
export function requestEchoHeadline(kind: "Done" | "Aborted", objective: string | undefined, outcome: string): string {
|
|
541
|
-
const
|
|
542
|
-
|
|
578
|
+
const safeObjective = objective ? sanitizeDisplayText(objective) : "";
|
|
579
|
+
const safeOutcome = sanitizeDisplayText(outcome);
|
|
580
|
+
const echo = safeObjective.trim() ? clipSummaryValue(safeObjective.trim(), RICH_OBJECTIVE_ECHO_CHARS) : null;
|
|
581
|
+
return echo ? `## ${kind}: ${echo} \u2014 ${safeOutcome}` : `## ${kind} \u2014 ${safeOutcome}`;
|
|
543
582
|
}
|
|
544
583
|
|
|
545
584
|
/** Chat-only projection: keep explanations and counts, not command/hash receipts.
|
|
546
585
|
* Repository-only findings are filtered separately; useful implementation
|
|
547
586
|
* references in substantive explanations are not themselves bookkeeping. */
|
|
548
587
|
function chatNarrative(value: string): string {
|
|
549
|
-
return stripMachineGroups(chatSafeDetailValue(value)
|
|
588
|
+
return stripMachineGroups(chatSafeDetailValue(sanitizeDisplayText(value))
|
|
550
589
|
.replace(/\b(?:fixed in|commit|HEAD(?: at)?|built from)\s+`?[a-f0-9]{7,64}`?/gi, "")
|
|
551
590
|
.replace(/\b(?=[a-f0-9]*[a-f])(?=[a-f0-9]*\d)[a-f0-9]{7,64}\b/gi, "")
|
|
552
591
|
.replace(/`(?:bun|npm|npx|node|git|tsc)\s+[^`]+`/g, "")
|
|
@@ -620,9 +659,12 @@ export function buildFinalRepoStateLines(cwd: string): string[] | undefined {
|
|
|
620
659
|
return undefined;
|
|
621
660
|
}
|
|
622
661
|
};
|
|
623
|
-
const
|
|
624
|
-
const
|
|
625
|
-
const
|
|
662
|
+
const branchRaw = run(["branch", "--show-current"]);
|
|
663
|
+
const headRaw = run(["log", "-1", "--format=%h %s"]);
|
|
664
|
+
const shortRaw = run(["status", "--short"]);
|
|
665
|
+
const branch = branchRaw === undefined ? undefined : sanitizeDisplayText(branchRaw);
|
|
666
|
+
const head = headRaw === undefined ? undefined : sanitizeDisplayText(headRaw);
|
|
667
|
+
const short = shortRaw === undefined ? undefined : shortRaw.split("\n").map((line) => sanitizeDisplayText(line)).join("\n");
|
|
626
668
|
if (!branch && !head && short === undefined) return undefined;
|
|
627
669
|
const lines = [`Branch ${branch ?? "detached"}${head ? ` @ ${head}` : ""}`];
|
|
628
670
|
if (short === undefined) lines.push("Tree state unreadable");
|
|
@@ -660,7 +702,7 @@ export function buildRichTerminalParts(args: {
|
|
|
660
702
|
chat?: boolean;
|
|
661
703
|
}): RichTerminalParts {
|
|
662
704
|
const { findings, tests, next } = partitionRichDetails(args.details);
|
|
663
|
-
const outcome = args.outcome;
|
|
705
|
+
const outcome = sanitizeDisplayText(args.outcome);
|
|
664
706
|
const kind = args.kind ?? "Done";
|
|
665
707
|
const headline = args.chat ? `## ${kind} — ${outcome}` : requestEchoHeadline(kind, args.objective, outcome);
|
|
666
708
|
const auditStatus = auditRowStatus(args.auditHistory);
|
|
@@ -676,11 +718,11 @@ export function buildRichTerminalParts(args: {
|
|
|
676
718
|
findingLines.push("| Area | Finding | Evidence |", "| --- | --- | --- |");
|
|
677
719
|
for (const group of groups) {
|
|
678
720
|
group.findings.forEach((finding, fi) => {
|
|
679
|
-
const { text, evidence } = extractEvidenceTokens(finding);
|
|
721
|
+
const { text, evidence } = extractEvidenceTokens(sanitizeDisplayText(finding));
|
|
680
722
|
const { lead, body } = leadBody(text);
|
|
681
723
|
// v0.38.52: test proof rides the Evidence cell (tables have no
|
|
682
724
|
// sub-bullets); cells stay pipe-escaped. v0.38.55: unclipped.
|
|
683
|
-
const proof = group.tests?.[fi]
|
|
725
|
+
const proof = group.tests?.[fi] ? sanitizeDisplayText(group.tests[fi]).trim() : "";
|
|
684
726
|
const evidenceCell = [evidence.join(", ") || "\u2014", ...(proof ? [`Tests: ${proof}`] : [])].join(" \u00b7 ");
|
|
685
727
|
findingLines.push(
|
|
686
728
|
`| ${escapeTableCell(group.title)} | ${escapeTableCell(`**${lead}** \u2014 ${body}`)} | ${escapeTableCell(evidenceCell)} |`,
|
|
@@ -689,12 +731,12 @@ export function buildRichTerminalParts(args: {
|
|
|
689
731
|
}
|
|
690
732
|
} else if (groups.length > 0) {
|
|
691
733
|
groups.forEach((group, i) => {
|
|
692
|
-
findingLines.push(`#### ${i + 1}. ${group.title}`);
|
|
734
|
+
findingLines.push(`#### ${i + 1}. ${sanitizeDisplayText(group.title)}`);
|
|
693
735
|
group.findings.forEach((finding, fi) => {
|
|
694
|
-
const { lead, body } = leadBody(args.chat ? chatNarrative(extractEvidenceTokens(finding).text) : finding);
|
|
736
|
+
const { lead, body } = leadBody(args.chat ? chatNarrative(extractEvidenceTokens(finding).text) : sanitizeDisplayText(finding));
|
|
695
737
|
findingLines.push(`- **${lead}** \u2014 ${body}`);
|
|
696
738
|
// v0.38.52: per-finding test proof (shot C) — absent stays absent.
|
|
697
|
-
const proof = group.tests?.[fi]
|
|
739
|
+
const proof = group.tests?.[fi] ? sanitizeDisplayText(group.tests[fi]).trim() : "";
|
|
698
740
|
if (proof) findingLines.push(` - Test Results: ${args.chat ? chatNarrative(proof) : proof}`);
|
|
699
741
|
});
|
|
700
742
|
});
|
|
@@ -716,22 +758,25 @@ export function buildRichTerminalParts(args: {
|
|
|
716
758
|
const showCommand = !args.chat && gates.some((row) => row.command?.trim());
|
|
717
759
|
if (gates.length > 0) {
|
|
718
760
|
for (const row of gates) {
|
|
719
|
-
const
|
|
720
|
-
const
|
|
721
|
-
const
|
|
761
|
+
const safeNotes = row.notes ? sanitizeDisplayText(row.notes).trim() : "";
|
|
762
|
+
const derived = testsRowStatus(safeNotes);
|
|
763
|
+
const notes = args.chat ? chatNarrative(safeNotes) : safeNotes;
|
|
764
|
+
const command = row.command ? sanitizeDisplayText(row.command).trim() : "\u2014";
|
|
765
|
+
const gate = sanitizeDisplayText(row.gate);
|
|
766
|
+
const scope = row.scope ? sanitizeDisplayText(row.scope).trim() : "\u2014";
|
|
722
767
|
tableRows.push(showCommand
|
|
723
|
-
? `| ${escapeTableCell(
|
|
724
|
-
: `| ${escapeTableCell(
|
|
768
|
+
? `| ${escapeTableCell(gate)} | ${escapeTableCell(command)} | ${escapeTableCell(scope)} | ${derived} | ${escapeTableCell(notes || "\u2014")} |`
|
|
769
|
+
: `| ${escapeTableCell(gate)} | ${escapeTableCell(scope)} | ${derived} | ${escapeTableCell(notes || "\u2014")} |`);
|
|
725
770
|
}
|
|
726
771
|
} else {
|
|
727
772
|
for (const detail of tests) {
|
|
728
773
|
const { body } = leadBody(detail);
|
|
729
774
|
const status = testsRowStatus(body);
|
|
730
|
-
tableRows.push(`| Tests | ${status} | ${escapeTableCell(args.chat ? chatNarrative(body) : body)} |`);
|
|
775
|
+
tableRows.push(`| Tests | ${status} | ${escapeTableCell(args.chat ? chatNarrative(body) : sanitizeDisplayText(body))} |`);
|
|
731
776
|
}
|
|
732
777
|
}
|
|
733
778
|
if (!args.chat && auditStatus !== "NO VERDICT") {
|
|
734
|
-
const auditBody = args.countsLine.replace(/^\u2014\s*/, "").replace(/\.\s*$/, "");
|
|
779
|
+
const auditBody = sanitizeDisplayText(args.countsLine).replace(/^\u2014\s*/, "").replace(/\.\s*$/, "");
|
|
735
780
|
// v0.38.55 audit: the Audit row's Scope names the row kind — the old
|
|
736
781
|
// shape duplicated the counts text in Scope and Notes.
|
|
737
782
|
if (gates.length > 0) {
|
|
@@ -763,8 +808,8 @@ export function buildRichTerminalParts(args: {
|
|
|
763
808
|
findingLines,
|
|
764
809
|
tableLines,
|
|
765
810
|
nextLines,
|
|
766
|
-
repoLines: args.chat ? [] : args.repoState ?? [],
|
|
767
|
-
summaryLines: args.summaryLines ?? [],
|
|
811
|
+
repoLines: args.chat ? [] : (args.repoState ?? []).map((line) => sanitizeDisplayText(line)),
|
|
812
|
+
summaryLines: (args.summaryLines ?? []).map((line) => sanitizeDisplayText(line)),
|
|
768
813
|
};
|
|
769
814
|
}
|
|
770
815
|
|
|
@@ -776,28 +821,30 @@ export function buildRichTerminalParts(args: {
|
|
|
776
821
|
* final repository state closes it — findings, verification, Next,
|
|
777
822
|
* repo state, in that order. */
|
|
778
823
|
export function composeRichTerminalLines(parts: RichTerminalParts): string[] {
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
824
|
+
const banner = sanitizeDisplayText(parts.banner ?? parts.headline);
|
|
825
|
+
const headline = sanitizeDisplayText(parts.headline);
|
|
826
|
+
const lines = [banner, ""];
|
|
827
|
+
if (headline !== lines[0]) lines.push(headline, "");
|
|
828
|
+
if (parts.durationLine) lines.push(sanitizeDisplayText(parts.durationLine), "");
|
|
782
829
|
// Structured-long (field 2026-09-16): the full Outcome body rides its
|
|
783
830
|
// own section between the headline and the findings — findings-first
|
|
784
831
|
// order is preserved (findings, verification, Next keep their relative
|
|
785
832
|
// order), the headline echo stays short, and the one-action Next rule
|
|
786
833
|
// is untouched.
|
|
787
834
|
if (parts.summaryLines.length > 0) {
|
|
788
|
-
lines.push("### Summary", ...parts.summaryLines, "");
|
|
835
|
+
lines.push("### Summary", ...parts.summaryLines.map((line) => sanitizeDisplayText(line)), "");
|
|
789
836
|
}
|
|
790
837
|
if (parts.findingLines.length > 0) {
|
|
791
|
-
lines.push("### Key Findings & Remediation", ...parts.findingLines, "");
|
|
838
|
+
lines.push("### Key Findings & Remediation", ...parts.findingLines.map((line) => sanitizeDisplayText(line)), "");
|
|
792
839
|
}
|
|
793
840
|
if (parts.tableLines.length > 0) {
|
|
794
|
-
lines.push("### Verification Summary", ...parts.tableLines, "");
|
|
841
|
+
lines.push("### Verification Summary", ...parts.tableLines.map((line) => sanitizeDisplayText(line)), "");
|
|
795
842
|
}
|
|
796
843
|
if (parts.nextLines.length > 0) {
|
|
797
|
-
lines.push("### Next", ...parts.nextLines, "");
|
|
844
|
+
lines.push("### Next", ...parts.nextLines.map((line) => sanitizeDisplayText(line)), "");
|
|
798
845
|
}
|
|
799
846
|
if (parts.repoLines.length > 0) {
|
|
800
|
-
lines.push("### Final Repository State", ...parts.repoLines.map((line) => `- ${line}`), "");
|
|
847
|
+
lines.push("### Final Repository State", ...parts.repoLines.map((line) => `- ${sanitizeDisplayText(line)}`), "");
|
|
801
848
|
}
|
|
802
849
|
return lines;
|
|
803
850
|
}
|
|
@@ -889,7 +936,7 @@ function stripApprovalModel(line: string): string {
|
|
|
889
936
|
return line.replace(/auditor\s+\S+\s+approved/, "auditor approved");
|
|
890
937
|
}
|
|
891
938
|
function trailerBullet(line: string): string {
|
|
892
|
-
return `• ${line.replace(/^—\s*/, "")}`;
|
|
939
|
+
return `• ${sanitizeDisplayText(line).replace(/^—\s*/, "")}`;
|
|
893
940
|
}
|
|
894
941
|
|
|
895
942
|
/** v0.38.25: the audit-goal counts line — verdict proof ONLY, built from
|
|
@@ -1420,7 +1420,7 @@ export function sendStallEscalation(ctx: ExtensionContext, nudges: number): void
|
|
|
1420
1420
|
if (supervisorPaused(state)) return;
|
|
1421
1421
|
// Audit 2026-09-07 (HIGH): a stall nudge must not resurrect a stood-down
|
|
1422
1422
|
// chain — same abort-latch reasoning as sendContinuation.
|
|
1423
|
-
if (flags.sessionHandoffPending || flags.initialSessionLoadPending || !flags.extensionApi || flags.extensionApiStale || continuationDispatchStoodDown || pendingContinuationDispatch || flags.abortedStandDown) return;
|
|
1423
|
+
if (flags.sessionHandoffPending || flags.initialSessionLoadPending || !flags.extensionApi || flags.extensionApiStale || flags.staleTerminalDone || flags.zombieStoodDown || continuationDispatchStoodDown || pendingContinuationDispatch || flags.abortedStandDown) return;
|
|
1424
1424
|
if (!state.goal || !guardGoalBeforeContinuation(ctx, "stall-escalation")) return;
|
|
1425
1425
|
const remaining = HEARTBEAT_MAX_NUDGES - nudges;
|
|
1426
1426
|
const text = [
|
|
@@ -1464,7 +1464,7 @@ export function sendLengthContinue(ctx: ExtensionContext, consecutive: number):
|
|
|
1464
1464
|
if (supervisorPaused(state)) return;
|
|
1465
1465
|
// Audit 2026-09-07 (HIGH): a length nudge must not resurrect a stood-down
|
|
1466
1466
|
// chain — same abort-latch reasoning as sendContinuation.
|
|
1467
|
-
if (flags.sessionHandoffPending || flags.initialSessionLoadPending || !flags.extensionApi || flags.extensionApiStale || continuationDispatchStoodDown || pendingContinuationDispatch || flags.abortedStandDown) return;
|
|
1467
|
+
if (flags.sessionHandoffPending || flags.initialSessionLoadPending || !flags.extensionApi || flags.extensionApiStale || flags.staleTerminalDone || flags.zombieStoodDown || continuationDispatchStoodDown || pendingContinuationDispatch || flags.abortedStandDown) return;
|
|
1468
1468
|
if (state.goal && !guardGoalBeforeContinuation(ctx, "length-continuation")) return;
|
|
1469
1469
|
// v0.38.66 (PR #55, FOF11): active goals get completion-aware recovery
|
|
1470
1470
|
// text — a truncated turn must offer closure, not another blind work
|
|
@@ -1525,7 +1525,34 @@ export function buildPostCompactResync(briefExcerpt?: string): string {
|
|
|
1525
1525
|
return lines.join("\n") + "\n\n";
|
|
1526
1526
|
}
|
|
1527
1527
|
|
|
1528
|
-
|
|
1528
|
+
/**
|
|
1529
|
+
* v0.38.69 (Antigravity port, 09-04 borrow candidate #1): per-repo pitfall
|
|
1530
|
+
* registry. `.pi-glla/pitfalls.md` holds distilled, answer-agnostic rakes
|
|
1531
|
+
* (never ledger prose — the ledger is forensics, never distilled). Read at
|
|
1532
|
+
* goal start (and re-read when edited mid-goal); absent/blank/unreadable
|
|
1533
|
+
* resolves absent so repos without one render byte-identical prompts.
|
|
1534
|
+
*/
|
|
1535
|
+
export const PITFALLS_BRIEF_MAX_CHARS = 1500;
|
|
1536
|
+
|
|
1537
|
+
export function readPitfallsBrief(cwd: string): string | undefined {
|
|
1538
|
+
let body: string;
|
|
1539
|
+
try {
|
|
1540
|
+
body = fs.readFileSync(path.join(cwd, ".pi-glla", "pitfalls.md"), "utf8");
|
|
1541
|
+
} catch {
|
|
1542
|
+
return undefined;
|
|
1543
|
+
}
|
|
1544
|
+
const trimmed = body.trim();
|
|
1545
|
+
if (!trimmed) return undefined;
|
|
1546
|
+
return trimmed.length > PITFALLS_BRIEF_MAX_CHARS
|
|
1547
|
+
? trimmed.slice(0, PITFALLS_BRIEF_MAX_CHARS)
|
|
1548
|
+
: trimmed;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/** Goals already ledgered for their pitfalls consult this process — the
|
|
1552
|
+
* ledger records the consult once per goal, never once per turn. */
|
|
1553
|
+
const pitfallsLedgeredGoals = new Set<string>();
|
|
1554
|
+
|
|
1555
|
+
export function continuationPrompt(goal: Goal, opts: { includeRestartDetail?: boolean; pitfallsBrief?: string } = {}): string {
|
|
1529
1556
|
// Read the .md file as the template, then substitute {{tokens}}.
|
|
1530
1557
|
// For v0.1.0 we inline-substitute so we don't need fs at runtime.
|
|
1531
1558
|
const next = findNextPendingTask(goal.taskList?.tasks ?? []);
|
|
@@ -1583,6 +1610,20 @@ export function continuationPrompt(goal: Goal, opts: { includeRestartDetail?: bo
|
|
|
1583
1610
|
if (loadSettings(settingsCwd).visionAssist !== false) {
|
|
1584
1611
|
directives.push(VISION_ASSIST_GUIDANCE);
|
|
1585
1612
|
}
|
|
1613
|
+
// v0.38.69 (Antigravity port): the repo pitfall registry rides the
|
|
1614
|
+
// continuation prompt — consulted at goal start, re-read when edited.
|
|
1615
|
+
// Explicit opts win (tests); otherwise the repo file resolves, absent
|
|
1616
|
+
// keeping the prompt byte-identical for repos without one.
|
|
1617
|
+
const pitfallsBrief = opts.pitfallsBrief ?? readPitfallsBrief(settingsCwd);
|
|
1618
|
+
if (pitfallsBrief?.trim()) {
|
|
1619
|
+
directives.push(
|
|
1620
|
+
`## REPO PITFALLS (distilled — consult before acting)\n\nThese rakes already caught this repo. Check your plan against them before the first tool call and before every risky step:\n\n${pitfallsBrief.trim()}`,
|
|
1621
|
+
);
|
|
1622
|
+
if (!pitfallsLedgeredGoals.has(goal.id)) {
|
|
1623
|
+
pitfallsLedgeredGoals.add(goal.id);
|
|
1624
|
+
appendLedger(settingsCwd, "pitfalls_consulted", { goalId: goal.id });
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1586
1627
|
if (effSettings.aggressiveMode && isFullAuditObjective(goal.objective)) {
|
|
1587
1628
|
directives.push(
|
|
1588
1629
|
"## FULL-AUDIT MODE (aggressiveMode + survey objective)\n\nThis objective is a survey, not a single fix. Spawn 3+ `scout` subagents NOW — one per subsystem, in a single message so they run in parallel — synthesize their findings, and call `propose_task_list` with the result. Do not start fixing before the task list exists.",
|
|
@@ -656,6 +656,15 @@ export interface Goal {
|
|
|
656
656
|
pauseOptions?: string[];
|
|
657
657
|
/** v0.28.22: 1-based index into pauseOptions the agent recommends. */
|
|
658
658
|
pauseRecommended?: number;
|
|
659
|
+
/** v0.38.69 (Antigravity port): mid-run decision-pause accounting — every
|
|
660
|
+
* agent-authored kind="decision" pause increments this, in-budget or
|
|
661
|
+
* not, so the interruption budget has a durable counter across reloads. */
|
|
662
|
+
midRunDecisionCount?: number;
|
|
663
|
+
/** v0.38.69 (Antigravity port): decisions auto-resolved to the
|
|
664
|
+
* recommended default when the budget was exhausted — the assumption
|
|
665
|
+
* the agent must carry into its completion recap's Left out. Bounded
|
|
666
|
+
* to the trailing 20. */
|
|
667
|
+
autoDefaultLog?: Array<{ at: string; reason: string; chosen: string; options: string[] }>;
|
|
659
668
|
/** v0.28.22: ISO time a wait-pause becomes resumable (countdown shown). */
|
|
660
669
|
pauseResumeAt?: string;
|
|
661
670
|
/** v0.35.28 (issue #16): set when glla AUTO-resumed a lapsed wait — the
|
|
@@ -1120,6 +1129,11 @@ export interface MainModelRecovery {
|
|
|
1120
1129
|
retryAt?: string;
|
|
1121
1130
|
/** Next preferred-primary health probe while a fallback is serving. */
|
|
1122
1131
|
primaryProbeAt?: string;
|
|
1132
|
+
/** v0.38.69 (Antigravity port): absolute reset of the quota-walled
|
|
1133
|
+
* primary, stamped when the episode failed over with an explicit
|
|
1134
|
+
* upstream reset hint. While a fallback serves, the background primary
|
|
1135
|
+
* probe fires at this reset instead of the generic probe cadence. */
|
|
1136
|
+
primaryResetAt?: string;
|
|
1123
1137
|
/** A preferred-primary switch was accepted and awaits one supervised turn. */
|
|
1124
1138
|
primaryProbeInFlight?: boolean;
|
|
1125
1139
|
/** Number of completed recovery waits; drives the bounded per-attempt exponential cadence. */
|
|
@@ -1228,6 +1242,7 @@ export function sanitizeMainModelRecovery(value: unknown): MainModelRecovery | u
|
|
|
1228
1242
|
...(Array.isArray(raw.recoveryNoticeKeys) ? { recoveryNoticeKeys: raw.recoveryNoticeKeys.filter((key): key is string => typeof key === "string").slice(-16).map((key) => key.slice(0, 300)) } : {}),
|
|
1229
1243
|
...(date(raw.retryAt) ? { retryAt: date(raw.retryAt) } : {}),
|
|
1230
1244
|
...(date(raw.primaryProbeAt) ? { primaryProbeAt: date(raw.primaryProbeAt) } : {}),
|
|
1245
|
+
...(date(raw.primaryResetAt) ? { primaryResetAt: date(raw.primaryResetAt) } : {}),
|
|
1231
1246
|
...(raw.primaryProbeInFlight === true ? { primaryProbeInFlight: true } : {}),
|
|
1232
1247
|
...(date(raw.firstFailureAt) ? { firstFailureAt: date(raw.firstFailureAt) } : {}),
|
|
1233
1248
|
...(date(raw.autoRetryUntil) ? { autoRetryUntil: date(raw.autoRetryUntil) } : {}),
|
|
@@ -3626,6 +3641,21 @@ export function extractPendingTasks(report: string, cap = 5): string[] {
|
|
|
3626
3641
|
return out;
|
|
3627
3642
|
}
|
|
3628
3643
|
|
|
3644
|
+
/**
|
|
3645
|
+
* v0.38.69 (Antigravity port): objection-attached retries. Every
|
|
3646
|
+
* disapproval — aggressive or not — becomes a durable TODO projection:
|
|
3647
|
+
* extracted objection bullets, or a single review-the-report TODO when
|
|
3648
|
+
* nothing extractable survives. Never an empty list: the retry argues the
|
|
3649
|
+
* objection, not generic effort. The aggressive gate keeps only the
|
|
3650
|
+
* cap-keep-going / no-progress-stop behaviors, never the TODOs.
|
|
3651
|
+
*/
|
|
3652
|
+
export function durableObjectionsForDisapproval(report: string, statusCommand = "/goal status"): string[] {
|
|
3653
|
+
const extracted = extractPendingTasks(report, 5);
|
|
3654
|
+
return extracted.length > 0
|
|
3655
|
+
? extracted
|
|
3656
|
+
: [`Review the latest auditor disapproval in ${statusCommand}.`];
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3629
3659
|
/** Contract item 23: is the auditor's IMPOSSIBLE reason about the WHOLE
|
|
3630
3660
|
* goal or only part of it? Default "full" (safe — keeps the pause);
|
|
3631
3661
|
* partial only on explicit subset language. */
|
|
@@ -1112,6 +1112,21 @@ function pausedNextTransition(g: Goal, state: State, now: number): string {
|
|
|
1112
1112
|
}
|
|
1113
1113
|
}
|
|
1114
1114
|
|
|
1115
|
+
/** v0.38.70 (field 20260920_152744): close a paused action card with its
|
|
1116
|
+
* deferred history. The pause body already ended on a `└─` closer; reopen
|
|
1117
|
+
* it to `├─` and hang the tally/provenance/collapsed-judgment rows after
|
|
1118
|
+
* the action, closing on the last history row. Empty history is a no-op,
|
|
1119
|
+
* so cards with nothing deferred keep their exact shape. */
|
|
1120
|
+
function closePausedWithHistory(lines: string[], history: string[], theme?: DisplayTheme): string[] {
|
|
1121
|
+
if (history.length === 0) return lines;
|
|
1122
|
+
const tail = lines[lines.length - 1];
|
|
1123
|
+
if (tail !== undefined) lines[lines.length - 1] = tail.replace(/^└─/, "├─");
|
|
1124
|
+
history.forEach((h, i) => {
|
|
1125
|
+
lines.push(`${i === history.length - 1 ? "└─" : "├─"} ${paint(theme, "dim", h)}`);
|
|
1126
|
+
});
|
|
1127
|
+
return lines;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1115
1130
|
function pausedLifecycleLines(g: Goal, state: State, extras: WidgetExtras | undefined, now: number): [string, string] {
|
|
1116
1131
|
const queued = state.list?.length ?? 0;
|
|
1117
1132
|
return [
|
|
@@ -1932,9 +1947,20 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
1932
1947
|
// v0.38.8: durable verdict tally as a first-class card row — the widget
|
|
1933
1948
|
// is the glance surface, and stored verdicts are the progress evidence
|
|
1934
1949
|
// when no auditor is live. Silent when history is empty.
|
|
1950
|
+
// v0.38.70 (field 20260920_152744): a paused action card must lead with
|
|
1951
|
+
// the action, not history. Pi core truncates the widget tail (~10 rows),
|
|
1952
|
+
// so judgment plaques above the blocked banner pushed the suggested action
|
|
1953
|
+
// below the cut — "... (widget truncated)" with no action visible.
|
|
1954
|
+
// Tally, provenance, and judgment ride AFTER the pause closer instead;
|
|
1955
|
+
// judgment rides collapsed (header + selection; full plaques stay in
|
|
1956
|
+
// /goal status). Interrupted/attention cards keep the existing order.
|
|
1957
|
+
const deferHistory = g.status === "paused" && !!g.pauseReason && !interrupted && !attention;
|
|
1958
|
+
const deferredHistory: string[] = [];
|
|
1935
1959
|
const headTally = formatVerdictTallySegment(auditorVerdictTally(g.auditHistory, now), now);
|
|
1936
1960
|
if (headTally) {
|
|
1937
|
-
|
|
1961
|
+
const tallyRow = `audits: ${truncate(headTally, Math.max(20, (width ?? 80) - 12))}`;
|
|
1962
|
+
if (deferHistory) deferredHistory.push(tallyRow);
|
|
1963
|
+
else lines.push(`├─ ${paint(theme, "dim", tallyRow)}`);
|
|
1938
1964
|
}
|
|
1939
1965
|
if (g.repairTarget) {
|
|
1940
1966
|
lines.push(`├─ ${paint(theme, "warning", `REPLAN REQUIRED · original target: ${truncate(g.repairTarget.objective.replace(/\s+/g, " "), Math.max(30, (width ?? 80) - 28))}`)}`);
|
|
@@ -1967,12 +1993,22 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
1967
1993
|
? []
|
|
1968
1994
|
: modelProvenanceLines(extras?.modelProvenance, width);
|
|
1969
1995
|
const provenanceStart = lines.length;
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
}
|
|
1996
|
+
if (deferHistory) {
|
|
1997
|
+
provenance.forEach((line) => deferredHistory.push(line));
|
|
1998
|
+
} else {
|
|
1999
|
+
provenance.forEach((line, i) => {
|
|
2000
|
+
lines.push(`${i === 0 ? "├─" : "│ "} ${paint(theme, "dim", line)}`);
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
1973
2003
|
if (extras?.durableDeferRecommendation) {
|
|
1974
2004
|
const judgment = buildDurableDeferDecisionLines(extras.durableDeferRecommendation, width);
|
|
1975
|
-
|
|
2005
|
+
// Collapsed on action cards: header + selection only. Plaque bodies
|
|
2006
|
+
// stay in /goal status; the glance card must fit the action.
|
|
2007
|
+
const collapsed = deferHistory && judgment.length > 2
|
|
2008
|
+
? [judgment[0]!, judgment[judgment.length - 1]!]
|
|
2009
|
+
: judgment;
|
|
2010
|
+
if (deferHistory) deferredHistory.push(...collapsed);
|
|
2011
|
+
else judgment.forEach((line) => lines.push(`├─ ${paint(theme, "dim", line)}`));
|
|
1976
2012
|
}
|
|
1977
2013
|
if (interrupted) {
|
|
1978
2014
|
const resumeCmd = isList ? "/list resume" : "/goal resume";
|
|
@@ -2031,7 +2067,7 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
2031
2067
|
lines.push(`├─ ${paint(theme, "dim", lifecycle)}`);
|
|
2032
2068
|
lines.push(`│ ${paint(theme, "dim", transition)}`);
|
|
2033
2069
|
lines.push(`└─ ${paint(theme, "warning", sanitizeProviderDisplayText(g.pauseSuggestedAction ?? `The claim is safe; ${isList ? "/list resume" : "/goal resume"} starts exactly one fresh auditor.`))}`);
|
|
2034
|
-
return lines;
|
|
2070
|
+
return closePausedWithHistory(lines, deferredHistory, theme);
|
|
2035
2071
|
}
|
|
2036
2072
|
const [lifecycle, transition] = pausedLifecycleLines(g, state, extras, now);
|
|
2037
2073
|
lines.push(`├─ ${paint(theme, "dim", lifecycle)}`);
|
|
@@ -2128,7 +2164,7 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
2128
2164
|
// the card here, closing the previous row as the footer.
|
|
2129
2165
|
const tail = lines[lines.length - 1];
|
|
2130
2166
|
if (tail !== undefined) lines[lines.length - 1] = tail.replace(/^├─|^│\s*/, "└─ ");
|
|
2131
|
-
return lines;
|
|
2167
|
+
return closePausedWithHistory(lines, deferredHistory, theme);
|
|
2132
2168
|
}
|
|
2133
2169
|
const spent: string[] = [];
|
|
2134
2170
|
const tokUsed = g.usage?.tokensUsed ?? 0;
|
|
@@ -2148,7 +2184,7 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
2148
2184
|
} else {
|
|
2149
2185
|
lines.push(`└─ ${paint(theme, "dim", truncate(savedLine, budget))}`);
|
|
2150
2186
|
}
|
|
2151
|
-
return lines;
|
|
2187
|
+
return closePausedWithHistory(lines, deferredHistory, theme);
|
|
2152
2188
|
}
|
|
2153
2189
|
if (attention) {
|
|
2154
2190
|
const budget = budgetFor(width, 3, 60);
|