pi-goal-list-loop-audit 0.38.98 → 0.38.104
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 +149 -0
- package/INSTALL.md +6 -5
- package/docs/DESIGN-long-running-supervision.md +3 -3
- package/docs/DESIGN.md +8 -4
- package/docs/INDEX.md +1 -1
- package/docs/SETTINGS.md +8 -0
- package/extensions/action-reminder.ts +265 -0
- package/extensions/audit-lifecycle.ts +450 -0
- package/extensions/compaction-failure.ts +67 -0
- package/extensions/compaction-input.ts +299 -44
- package/extensions/completion-summary.ts +203 -110
- package/extensions/faulty-objective-recovery.ts +15 -1
- package/extensions/finding-lead.ts +181 -0
- package/extensions/goal-agents-panel.ts +2 -3
- package/extensions/goal-commands.ts +200 -12
- package/extensions/goal-compactor.ts +49 -3
- package/extensions/goal-continuation.ts +154 -11
- package/extensions/goal-heartbeat.ts +180 -15
- package/extensions/goal-loop-auditor-process.ts +433 -24
- package/extensions/goal-loop-auditor.ts +62 -3
- package/extensions/goal-loop-core.ts +520 -25
- package/extensions/goal-loop-display.ts +165 -31
- package/extensions/goal-loop-shield.ts +162 -17
- package/extensions/goal-loop-subagents.ts +25 -9
- package/extensions/goal-loop.ts +105 -49
- package/extensions/goal-recovery.ts +23 -4
- package/extensions/goal-settings.ts +69 -2
- package/extensions/loops/goal-activation.ts +222 -36
- package/extensions/loops/goal-auditor-hooks.ts +477 -99
- package/extensions/loops/goal-orchestrator.ts +5 -6
- package/extensions/loops/goal-settings-ui.ts +78 -0
- package/extensions/loops/goal-tools.ts +321 -80
- package/extensions/loops/goal-ui.ts +28 -4
- package/extensions/loops/goal.ts +9 -3
- package/extensions/main-model-recovery.ts +99 -1
- package/extensions/prompt-layers.ts +30 -1
- package/extensions/quota-retry.ts +58 -3
- package/extensions/settings-menu.ts +43 -2
- package/media/glla-thumb.png +0 -0
- package/media/glla-thumb.svg +117 -0
- package/package.json +3 -3
- package/prompts/goal-loop-continuation.md +22 -9
- package/schemas/goal.schema.json +50 -3
- package/scripts/auditor-stderr-diagnostic.d.mts +8 -0
- package/scripts/auditor-stderr-diagnostic.mjs +39 -0
- package/scripts/goal-auditor-worker.mjs +189 -13
- package/scripts/run-tests.d.mts +8 -0
- package/scripts/run-tests.mjs +131 -7
- package/scripts/smoke.sh +1 -1
- package/scripts/verify-compaction-live.mjs +578 -182
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,154 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
Untagged work since 0.38.104. It stays under this heading until the release
|
|
6
|
+
commit renames it (docs/RELEASING.md); these are descriptive milestones, not
|
|
7
|
+
invented version headers.
|
|
8
|
+
|
|
9
|
+
## 0.38.104 — prose stops grinding the audit (2026-09-29)
|
|
10
|
+
|
|
11
|
+
### The audit cap covers every round that failed to settle the goal
|
|
12
|
+
|
|
13
|
+
- The regression shield (auditor approved, evidence contract unmet) and an
|
|
14
|
+
aggressive `IMPOSIBLE(partial)` verdict both re-activate the goal without
|
|
15
|
+
disapproving it, so the v0.38.107 hard cap — which counts `disapproved`
|
|
16
|
+
rounds — returned 0 for both and let them cycle forever. They now pause on
|
|
17
|
+
the same `auditCapHard`, offering accept / resume / tweak / cancel.
|
|
18
|
+
- `countTrailingUnsettledRounds` is the shared primitive: every trailing round
|
|
19
|
+
that failed to *settle* the goal, across all verdict classes. An approval and
|
|
20
|
+
an infra error break the streak; mechanical pre-audit gate rows stay
|
|
21
|
+
transparent.
|
|
22
|
+
- An alternating primary/fallback auditor ladder no longer defeats the cap by
|
|
23
|
+
re-baselining the comparable streak to 1 every round. The guard that detects
|
|
24
|
+
this (`trailingStreakGraderStable`) had no production caller; it is wired
|
|
25
|
+
now, and the pause reason says which count it used.
|
|
26
|
+
|
|
27
|
+
### Honesty fixes found by the same pass
|
|
28
|
+
|
|
29
|
+
- `/goal timeline` counted objections with a raw `superseded` filter while the
|
|
30
|
+
runtime reads them through a migrate-on-read backfill, so it could tell the
|
|
31
|
+
user to re-submit work the loop had already accepted.
|
|
32
|
+
- The continuation self-heal budget could never be refilled: the turn-start
|
|
33
|
+
reset was gated on a live timer, but exhaustion nulls the timer without
|
|
34
|
+
clearing the counter — so the next stuck episode reported an already-spent
|
|
35
|
+
budget with zero re-probes.
|
|
36
|
+
- A blank subtask title reached `taskList` persistence and made the goal's own
|
|
37
|
+
state file fail the published schema. Rejected at the boundary now.
|
|
38
|
+
- `INSTALL.md` said the auditor runs extension-less by default; the mirror is
|
|
39
|
+
on by default, as `README.md` correctly said.
|
|
40
|
+
- The published schema now types the five completion-claim fields the terminal
|
|
41
|
+
render actually writes, plus the task deferral stamp, and drops
|
|
42
|
+
`wallDeadlineAt` — a field no GLLA code has ever written.
|
|
43
|
+
|
|
44
|
+
### Provider walls: honest classification and bounded redaction
|
|
45
|
+
|
|
46
|
+
- An empty provider response is a transient glitch, not a quota wall, and a
|
|
47
|
+
real `Retry-After` reset outranks the eager first-retry path instead of being
|
|
48
|
+
shortened to 5s.
|
|
49
|
+
- The numeric `5xx` match is anchored, and the redaction cascade is bounded by
|
|
50
|
+
a fraction of the report's lines so sanitising cannot destroy the evidence.
|
|
51
|
+
- The between-tasks compaction threshold is a named token budget
|
|
52
|
+
(`runProactiveCompactionIfDue`), not an "emergency" that could fire on any
|
|
53
|
+
threshold.
|
|
54
|
+
|
|
55
|
+
### Durable state: writes that either land or say they did not
|
|
56
|
+
|
|
57
|
+
- A retried continuation dispatch is built as a copy and adopted only after
|
|
58
|
+
`persistDispatchRecord` succeeds, so an unwritable `.pi-glla` can no longer
|
|
59
|
+
re-send an identical payload from a durable `retryCount = 0`.
|
|
60
|
+
- The subagent RPC registry releases its listeners instead of retaining every
|
|
61
|
+
rebound session's bus for the life of the process.
|
|
62
|
+
- A finished `result.json` classifies its job dir as dead even when the parent
|
|
63
|
+
PID was reused, so a proven-finished dir is reaped instead of leaking.
|
|
64
|
+
- `mainModelRetryMinutes`, `auditFeedbackChars` and the thinking-level pins are
|
|
65
|
+
normalized at load, so no surface can display a value the runtime ignores.
|
|
66
|
+
- Summary and widget clipping is code-point safe, and `complete_goal`'s
|
|
67
|
+
`newObjective` path goes through `updateGoal` with an explicit revision.
|
|
68
|
+
|
|
69
|
+
### A park is not terminal
|
|
70
|
+
|
|
71
|
+
- The bounded-horizon and identical-failure auditor parks re-probe themselves
|
|
72
|
+
instead of dead-ending the goal.
|
|
73
|
+
- An unacknowledged continuation turn start self-heals on a doubling,
|
|
74
|
+
probe-bounded cadence; the loop's live event log is no longer tracked.
|
|
75
|
+
|
|
76
|
+
### The treadmill ceiling reaches the path the field actually takes
|
|
77
|
+
|
|
78
|
+
- The disapproval hard cap (0.38.103) was wired only into `complete_goal`'s
|
|
79
|
+
inline settlement. Real audits settle through the detached driver minutes or
|
|
80
|
+
hours later, which had no cap — under aggressive mode (the default) every hit
|
|
81
|
+
became a TODO and the goal ground forever (hellhunter 12 rounds, junk-runner
|
|
82
|
+
9, zero cap pauses). The detached path now pauses on the same
|
|
83
|
+
comparable-disapproval streak. See `audit/STUCK-AT-LAST-PART-2026-09-28.md`.
|
|
84
|
+
|
|
85
|
+
### Tests: observable, hang-proof, orphan-free
|
|
86
|
+
|
|
87
|
+
- `scripts/run-tests.mjs` wraps the serialized suite with a progress
|
|
88
|
+
heartbeat, a bounded stall watchdog, and a teardown that takes the suite
|
|
89
|
+
down with it instead of leaving a runaway. The suite child is spawned
|
|
90
|
+
detached, so the documented process-group kill is real rather than an
|
|
91
|
+
ESRCH-swallowed no-op; `test:all` and both CI jobs run through the wrapper
|
|
92
|
+
and are bounded by `timeout-minutes`.
|
|
93
|
+
|
|
94
|
+
### Prose stops grinding the audit (field bugs 2026-09-29)
|
|
95
|
+
|
|
96
|
+
- A verification contract with list structure now defines its checkable items
|
|
97
|
+
by the list: a wrapped prose preamble is commentary, not criteria. The
|
|
98
|
+
dracon-log contract's parenthetical preamble had split into "items" no
|
|
99
|
+
evidence could reference, so the shield blocked two genuine approvals
|
|
100
|
+
forever. Shapeless contracts keep the every-line behavior.
|
|
101
|
+
- Mechanical extraction no longer gates on prohibited commands: backticked
|
|
102
|
+
commands under negation ("no longer uses `bun run dev`") are skipped, and
|
|
103
|
+
never-exiting server-mode commands (`bun run dev`, `npm run serve`,
|
|
104
|
+
`*server.py`, …) are rejected at extraction instead of burning the round's
|
|
105
|
+
time budget. The AI auditor judges those items instead.
|
|
106
|
+
- The detached worker's stderr capture accumulates instead of last-chunk-wins
|
|
107
|
+
and strips control characters, so a trailing BEL flush can no longer
|
|
108
|
+
overwrite the real failure — the 90+ field errors recorded as `error: BEL`
|
|
109
|
+
will carry their true cause next time
|
|
110
|
+
(`scripts/auditor-stderr-diagnostic.mjs`).
|
|
111
|
+
|
|
112
|
+
### Audit pass 2026-09-28 (findings + fixes in this section's range)
|
|
113
|
+
|
|
114
|
+
- An approved audit no longer falls through the settlement driver into the
|
|
115
|
+
residual-failure tail, which wrote a false "infrastructure error" warning and
|
|
116
|
+
a contradictory `approved: false` evidence record after a successful
|
|
117
|
+
completion.
|
|
118
|
+
- `/goal verify` checks for an owed settlement before writing its manual claim,
|
|
119
|
+
so an approved-but-unarchived goal keeps its approval instead of being
|
|
120
|
+
re-audited from scratch.
|
|
121
|
+
- A branch-changed loop park names the loop surface and is resumable, so the
|
|
122
|
+
loop's iteration, best value and history stay reachable.
|
|
123
|
+
- The settings menu no longer promises a 24h recovery ceiling the default
|
|
124
|
+
aggressive mode removes, and renders the legal retention value `0` as a value
|
|
125
|
+
instead of "?".
|
|
126
|
+
|
|
127
|
+
## 0.38.103 — the treadmill gets a ceiling: inconclusive gates and a hard disapproval cap (2026-09-27)
|
|
128
|
+
|
|
129
|
+
- **A mechanical gate the host kills is no longer a disapproval.** Timeout/kill/output-limit/abort outcomes now classify `inconclusive` and enter the durable infra-retry plan instead of the rework loop — a killed gate is evidence of nothing about the work. Gate budgets also scale with host load (1× at load ≤ cpus, capped 2× past it; `mechanicalLoadScale`, default on, global-only).
|
|
130
|
+
- **Consecutive disapprovals hit a hard ceiling in every mode.** `auditCapHard` (default 8: soft cap 5 + three grace rounds, `0` = unlimited) pauses with a decision even under aggressiveMode keep-going. Cap pauses now offer **Accept with follow-ups** first: archives complete with residuals while the reviewer mines the disapproval reports into `/list` items (`(/goal accept)` in the decision picker).
|
|
131
|
+
- **Required fixes carry severity.** The brief mandates leading `[HIGH]`/`[MED]`/`[LOW]` tags per item plus a severity-honesty rule; each disapproval records a severity census on its objections ledger event. Advisory this release — automatic severity gating follows once field label distribution is measured.
|
|
132
|
+
|
|
133
|
+
## 0.38.102 — audits converge, and the card stops burying its own gist (2026-09-27)
|
|
134
|
+
|
|
135
|
+
- **The audit brief now bounds what may block approval and defines a reachable bar.** `CHANGE SCOPE` separates blocking defects (in the changed paths, an unverified contract item, a regression this change causes) from non-blocking ones (pre-existing debt outside the change, style, anything untied to a changed path); it also states that the auditor's own prior findings are inputs to re-check, not new targets — that line breaks the self-feeding loop where each round grew the 148KB findings ledger the next round then audited. `WHAT APPROVED MEANS` gives the checkable bar. `REWORK ROUND` reframes round N as “are the PRIOR objections closed?”, with a new in-scope defect still blocking. Measured 612 attempts fleet-wide: the loop was non-terminating (8/8, 13/13, and 20× consecutive disapprovals at 63h elapsed), not slow. (audit/ENDLESS-AUDIT-CONVERGENCE-2026-09-27.md)
|
|
136
|
+
- **An over-budget tool no longer discards the whole audit.** A verification step that blew the 5-minute per-tool budget used to kill the worker, throwing away up to 64 completed tool calls; the goal then re-audited from zero and blew the same budget on the same loaded host. The worker now cancels that tool, resumes with a brief carrying the completed calls, and the parent holds its hard kill for `TOOL_CANCEL_GRACE_MS`. An approving verdict from a verification-incomplete attempt is refused as retryable infrastructure — never laundered into a disapproval, which would send the agent into rework for a load problem. (audit/NON-DESTRUCTIVE-AUDIT-TOOL-TIMEOUT-2026-09-26.md)
|
|
137
|
+
- **A title-like goal objective no longer livelocks.** `# MONSTER MINECRAFT — a first-person voxel survival game` tripped a bare markdown-heading heuristic, so it was paused as suspicious and the queued repair never rewrote the objective; every resume re-tripped it. A heading is now suspicious only when it is a bare report-section label.
|
|
138
|
+
- **A torn `progress.json` read no longer fails a healthy audit.** It is telemetry — the verdict arrives in `result.json` — so an unreadable snapshot is now tolerated on a time bound instead of costing the attempt.
|
|
139
|
+
- **The terminal card stopped burying its own gist.** A `leftOut` field carrying seven scope decisions rendered as one ~900-character bullet; `Remaining` and `Next` now expand an inline `(1) … (2) …` enumeration into an indented list. In `What Changed` the outcome is the bullet and the evidence rides indented beneath it, so the reader sees what changed before reading why.
|
|
140
|
+
- **New store thumbnail**: a mission-control radar with one verified target among several, replacing the generic checkmark. `aggressiveMode` semantics and the `auditCap` gate are deliberately unchanged.
|
|
141
|
+
|
|
142
|
+
## 0.38.100 — the audit scopes itself to the recorded change set (2026-09-25)
|
|
143
|
+
|
|
144
|
+
- Execution now records the touched paths behind the `fileWrites` counter (`telemetry.files`, first-seen order, deduped, capped at 100 with an overflow count), and the detached-audit brief opens with that change set: verify THESE first instead of exploring unboundedly. The list scopes the START of the audit, never its boundary — subagent writes, shell redirections, and deletions bypass capture, so the brief says so and the auditor still follows the evidence outward. The recorded-facts fallback summary names the paths too (bounded), replacing "changed paths were not captured" for tracked goals.
|
|
145
|
+
|
|
146
|
+
## 0.38.99 — the detached audit has one legible lifecycle and one settlement (2026-09-25)
|
|
147
|
+
|
|
148
|
+
- The isolated completion audit now names its state instead of guessing: **starting** (claim durable, worker not reporting yet), **running** with its last durable activity, **settling** (approved, archive owed), **approved** (settlement durable), and **recovery-needed** (parked, with a real `/goal resume` action). The widget, status line, goal markdown, and restart recovery all read the same projection, so a claim that a crash left mid-settlement no longer renders as “awaiting completion review” (audit/DETACHED-AUDIT-LIFECYCLE-2026-09-25.md).
|
|
149
|
+
- An unresolved audit can no longer look finished. The approval path persists the verdict on the claim **before** any archive is attempted, keeps that approved claim when the archive fails (so the settlement stays re-drivable), and gates every terminal surface on a durable verdict plus a landed archive. A cold session that finds an approved-but-unarchived claim finishes it without re-running the auditor.
|
|
150
|
+
- Last activity is durable now: the detached worker's real event stamps a throttled heartbeat on the claim, so after a restart a silent attempt is reported as no-progress instead of looking live — the in-process watchdog does not exist in a fresh session.
|
|
151
|
+
|
|
3
152
|
## 0.38.98 — audited lifecycle hardening and change-first completion (2026-09-24)
|
|
4
153
|
|
|
5
154
|
- Fresh audit repairs make goal/list/loop state durable across persistence failures, session replacement, compaction failure, repair promotion, and branch-mode completion. Confirmed loop refinements now apply atomically instead of mutating live state before the spec is safely written.
|
package/INSTALL.md
CHANGED
|
@@ -193,11 +193,12 @@ re-fired behind your back.
|
|
|
193
193
|
|
|
194
194
|
## Auditor model requirement
|
|
195
195
|
|
|
196
|
-
The completion auditor runs in a detached fresh pi RPC process
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
196
|
+
The completion auditor runs in a detached fresh pi RPC process. By default it
|
|
197
|
+
mirrors your session's extension packages (so an extension-provided model
|
|
198
|
+
works in the auditor too), but it loads no skills, prompt templates, themes, or
|
|
199
|
+
context files. If you would rather the auditor run against only built-in pi
|
|
200
|
+
providers, set **Auditor mirror session extensions** to off in `/glla` and then
|
|
201
|
+
select a compatible model under the Auditor settings.
|
|
201
202
|
|
|
202
203
|
The worker resolves `pi` from `PATH` and inherits normal provider configuration.
|
|
203
204
|
If required, point it at a specific binary:
|
|
@@ -125,14 +125,14 @@ and the status/history surfaces. The human layer follows two different purposes:
|
|
|
125
125
|
Verification Summary table (Quality Gate | Command | Scope | Status | Notes
|
|
126
126
|
when supplied), and Final Repository State.
|
|
127
127
|
|
|
128
|
-
Both surfaces retain the independent
|
|
128
|
+
Both surfaces retain the independent completion review and the full six-label
|
|
129
129
|
machine recap. A lone approval may be promoted to the chat headline because
|
|
130
|
-
it is a durable
|
|
130
|
+
it is a durable review fact, not because tests passed. Width-bound and
|
|
131
131
|
external surfaces use the compact single-line projection of all six labels.
|
|
132
132
|
Every terminal goal notification, including version-bearing already-shipped
|
|
133
133
|
claims and explicit cancellation, carries the change-first human view or the
|
|
134
134
|
compact projection; loop notifications do the same. The executor recap and
|
|
135
|
-
independent
|
|
135
|
+
independent completion review stay separate: an approval is not manufactured from
|
|
136
136
|
the presence of a summary.
|
|
137
137
|
|
|
138
138
|
Metric-loop stops use the same six-label contract in their durable loop state
|
package/docs/DESIGN.md
CHANGED
|
@@ -499,20 +499,24 @@ This is a **clean break** by decision of the user. We do not interop with `pi-go
|
|
|
499
499
|
> **Superseded by consolidation (v0.8.0).** The planned per-loop files below
|
|
500
500
|
> never shipped: loops 1+2 live together in `extensions/loops/goal.ts`
|
|
501
501
|
> (one state machine, one loop driver), loop 3's helpers in
|
|
502
|
-
> `extensions/
|
|
503
|
-
> drafting inline in `goal.ts` + `prompts/`. Kept for history.
|
|
502
|
+
> `extensions/goal-loop-forever.ts`, rendering in `goal-loop-display.ts`,
|
|
503
|
+
> drafting inline in `goal.ts` + `prompts/`. Kept for history. v0.38.108: the
|
|
504
|
+
> "Lines" column below is a snapshot of that plan, not a claim about today's
|
|
505
|
+
> tree — where the plan and the shipped path disagree, the shipped path is
|
|
506
|
+
> `extensions/goal-loop-forever.ts` and the auditor prompt is built in
|
|
507
|
+
> `extensions/goal-loop-auditor.ts` (there is no prompt file).
|
|
504
508
|
|
|
505
509
|
| File | Purpose | Lines |
|
|
506
510
|
|---|---|---|
|
|
507
511
|
| `extensions/loops/goal.ts` | Loops 1+2 (single goal + list of goals) | shipped |
|
|
508
|
-
| `extensions/loops/forever.ts` | Loop 3 (metric loop helpers)
|
|
512
|
+
| `extensions/loops/forever.ts` | Loop 3 (metric loop helpers) — plan name; shipped as `extensions/goal-loop-forever.ts` | planned |
|
|
509
513
|
| `extensions/goal-loop-core.ts` | Shared state machine, types, JSONL | shipped |
|
|
510
514
|
| `extensions/goal-loop-auditor.ts` | Auditor prompt + compatibility helper | shipped |
|
|
511
515
|
| `extensions/goal-loop-auditor-process.ts` | Detached worker protocol, IPC, and shield revalidation | shipped |
|
|
512
516
|
| `scripts/goal-auditor-worker.mjs` | Extension-less RPC auditor child | shipped |
|
|
513
517
|
| `extensions/goal-loop-display.ts` | Status line + /goal status rendering | shipped |
|
|
514
518
|
| `prompts/goal-loop-continuation.md` | Templated continuation prompt | ~80 |
|
|
515
|
-
| `prompts/goal-loop-auditor.md` | Templated auditor prompt |
|
|
519
|
+
| `prompts/goal-loop-auditor.md` | Templated auditor prompt — never shipped; the prompt is built in `extensions/goal-loop-auditor.ts` | planned |
|
|
516
520
|
| `prompts/goal-loop-draft.md` | Templated drafting prompt | v0.2.0 |
|
|
517
521
|
| `schemas/goal.schema.json` | JSON Schema for goal state | ~50 |
|
|
518
522
|
|
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.104 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
|
@@ -32,6 +32,11 @@ copies are ignored (the recovery runtime reads the global file):
|
|
|
32
32
|
`auditorModelFallbacks`, `auditorToolTimeoutMs`, `auditorStallMs`,
|
|
33
33
|
`auditJobRetentionMs`, `auditSpotCheckRate`, `auditorInspection`.
|
|
34
34
|
|
|
35
|
+
This list is `GLOBAL_ONLY_KEYS` in `extensions/goal-settings.ts`, and a
|
|
36
|
+
project-scope write to any of them is stripped on read. Every other key —
|
|
37
|
+
including `auditCapHard` and `mechanicalLoadScale` — is project-settable and
|
|
38
|
+
a project file wins over the global one.
|
|
39
|
+
|
|
35
40
|
## Keys
|
|
36
41
|
|
|
37
42
|
| Key | Default | Meaning |
|
|
@@ -69,12 +74,15 @@ copies are ignored (the recovery runtime reads the global file):
|
|
|
69
74
|
| `carryover` | `"pause"` | Stale carryover on new activation: `"pause"` / `"clear"` / `"resume"`. |
|
|
70
75
|
| `autoAcceptDrafts` | `false` | Drafts activate without the Confirm dialog (unattended rigs). |
|
|
71
76
|
| `auditCap` | `5` (10 aggressive) | Pause after N consecutive auditor disapprovals (`0` = unlimited). |
|
|
77
|
+
| `auditCapHard` | `8` | Hard ceiling on consecutive disapprovals — pauses with a decision even in aggressive mode (`0` = unlimited). Project-settable. |
|
|
78
|
+
| `mechanicalLoadScale` | `true` | Scale mechanical gate budgets with host load up to 2× (`false` = fixed budgets). Project-settable. |
|
|
72
79
|
| `auditFeedbackChars` | `0` (full) | Max auditor-report chars returned after disapproval (`0` = full). |
|
|
73
80
|
| `auditorSilent` | `true` | Auditor report renders final-only, no word-by-word HUD. |
|
|
74
81
|
| `auditorProgressSignals` | `true` | Silent audits show phase label + byte counter. |
|
|
75
82
|
| `hourlyRetryProbe` | `true` | Extra blind retry at :00:30 every hour while parked. Global-only. |
|
|
76
83
|
| `subagentModelStrategy` | `"inherit-parent"` | Default subagent model policy for new sessions. |
|
|
77
84
|
| `subagentModelOverrides` | unset | Per-agent-type model pin; always wins over strategy. |
|
|
85
|
+
| `subagentThinkingOverrides` | unset | Per-agent-type thinking pin (`off`–`max`); unset per type = session inherit. |
|
|
78
86
|
| `subagentFallbacks` | unset | Per-role fallback chains (first eligible ref wins). |
|
|
79
87
|
| `subagentDisplayRichness` | `"quiet"` | Ambient worker UI: `"quiet"` (default, troubled workers + count line) / `"compact"` / `"rich"`. |
|
|
80
88
|
| `aggressiveMode` | `true` | Keep-going defaults (`false` = pause-first policy). |
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Box, Text } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
export const ACTION_REMINDER_CUSTOM_TYPE = "glla-action-reminder";
|
|
5
|
+
|
|
6
|
+
export type ActionReminderKind = "decision" | "blocked" | "error" | "wait" | "standby";
|
|
7
|
+
|
|
8
|
+
export type ActionReminderParkState = "stopped" | "automatic" | "redirect";
|
|
9
|
+
|
|
10
|
+
export interface ActionReminderDetails {
|
|
11
|
+
kind: ActionReminderKind;
|
|
12
|
+
reason: string;
|
|
13
|
+
action: string;
|
|
14
|
+
resumeCommand: string;
|
|
15
|
+
resumeAt?: string;
|
|
16
|
+
diagnostic?: string;
|
|
17
|
+
parkState: ActionReminderParkState;
|
|
18
|
+
/** The persisted goal is safe in every pause state. `parkState` separately
|
|
19
|
+
* distinguishes a stopped turn, automatic wait, or redirect continuation. */
|
|
20
|
+
safelyParked: true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ActionReminderPresentation {
|
|
24
|
+
heading: string;
|
|
25
|
+
why: string;
|
|
26
|
+
next: string;
|
|
27
|
+
resumeCommand?: string;
|
|
28
|
+
resumeAt?: string;
|
|
29
|
+
parkState: ActionReminderParkState;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ActionReminderCopy {
|
|
33
|
+
content: string;
|
|
34
|
+
details: ActionReminderDetails;
|
|
35
|
+
presentation: ActionReminderPresentation;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PauseAbortMarker {
|
|
39
|
+
ownerSession: unknown;
|
|
40
|
+
goalId: string;
|
|
41
|
+
kind: ActionReminderKind;
|
|
42
|
+
reason: string;
|
|
43
|
+
action?: string;
|
|
44
|
+
resumeCommand: string;
|
|
45
|
+
resumeAt?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let pauseAbortMarker: PauseAbortMarker | null = null;
|
|
49
|
+
let pauseTurnStartedAt = 0;
|
|
50
|
+
let pauseTurnGeneration = 0;
|
|
51
|
+
let pauseMarkerGeneration: number | null = null;
|
|
52
|
+
|
|
53
|
+
/** Bind the exact pause state and host owner to the turn pause_goal is about
|
|
54
|
+
* to abort. A session/goal mismatch expires the marker instead of letting it
|
|
55
|
+
* rewrite some later abort. */
|
|
56
|
+
export function markPauseAbort(marker: PauseAbortMarker): void {
|
|
57
|
+
pauseAbortMarker = { ...marker };
|
|
58
|
+
// Keep the public marker contract focused on the pause payload. A monotonic
|
|
59
|
+
// generation captured at agent_start is the fence: consuming requires the
|
|
60
|
+
// marker and its owner/goal to still belong to this exact turn.
|
|
61
|
+
pauseMarkerGeneration = pauseTurnStartedAt === 0 ? null : pauseTurnGeneration;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function markActionReminderTurnStart(): void {
|
|
65
|
+
pauseTurnStartedAt = Date.now();
|
|
66
|
+
pauseTurnGeneration++;
|
|
67
|
+
pauseAbortMarker = null;
|
|
68
|
+
pauseMarkerGeneration = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function clearPauseAbort(): void {
|
|
72
|
+
pauseAbortMarker = null;
|
|
73
|
+
pauseTurnStartedAt = 0;
|
|
74
|
+
pauseMarkerGeneration = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Consume a pending pause marker only when this exact owner, goal, and turn
|
|
78
|
+
* still match. `aborted` distinguishes the expected assistant abort from any
|
|
79
|
+
* other message_end that happens to arrive first. */
|
|
80
|
+
export function consumePauseAbort(input: {
|
|
81
|
+
ownerSession: unknown;
|
|
82
|
+
goalId?: string;
|
|
83
|
+
aborted: boolean;
|
|
84
|
+
}): PauseAbortMarker | null {
|
|
85
|
+
const marker = pauseAbortMarker;
|
|
86
|
+
if (!marker) return null;
|
|
87
|
+
if (
|
|
88
|
+
!input.aborted
|
|
89
|
+
|| marker.ownerSession !== input.ownerSession
|
|
90
|
+
|| marker.goalId !== input.goalId
|
|
91
|
+
|| pauseTurnStartedAt === 0
|
|
92
|
+
|| pauseMarkerGeneration === null
|
|
93
|
+
|| pauseMarkerGeneration !== pauseTurnGeneration
|
|
94
|
+
) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
pauseAbortMarker = null;
|
|
98
|
+
pauseMarkerGeneration = null;
|
|
99
|
+
return marker;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function buildAbortedAssistantNotice(input: {
|
|
103
|
+
kind: ActionReminderKind;
|
|
104
|
+
reason: string;
|
|
105
|
+
action?: string;
|
|
106
|
+
resumeCommand: string;
|
|
107
|
+
resumeAt?: string;
|
|
108
|
+
}): string {
|
|
109
|
+
const reminder = buildActionReminder(input);
|
|
110
|
+
return `GLLA paused this turn safely — ${reminder.presentation.why} ${reminder.presentation.next}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Build the durable, user-facing explanation for a GLLA pause. Pi's core
|
|
114
|
+
* abort renderer may still print its generic transport line; this message is
|
|
115
|
+
* the GLLA-owned card that tells the user what happened and what to do next. */
|
|
116
|
+
export function buildActionReminder(input: {
|
|
117
|
+
kind: ActionReminderKind;
|
|
118
|
+
reason: string;
|
|
119
|
+
action?: string;
|
|
120
|
+
resumeCommand: string;
|
|
121
|
+
resumeAt?: string;
|
|
122
|
+
diagnostic?: string;
|
|
123
|
+
parkState?: ActionReminderParkState;
|
|
124
|
+
}): ActionReminderCopy {
|
|
125
|
+
const reason = input.reason.trim() || "The current turn cannot continue safely.";
|
|
126
|
+
const suppliedAction = input.action?.trim();
|
|
127
|
+
const action = suppliedAction || input.resumeCommand;
|
|
128
|
+
const parkState = input.parkState ?? (
|
|
129
|
+
input.kind === "wait" || input.kind === "standby" ? "automatic" : "stopped"
|
|
130
|
+
);
|
|
131
|
+
const presentation = buildPausePresentation({
|
|
132
|
+
kind: input.kind,
|
|
133
|
+
reason,
|
|
134
|
+
action: input.kind === "decision" && !suppliedAction ? "" : action,
|
|
135
|
+
resumeCommand: input.resumeCommand,
|
|
136
|
+
resumeAt: input.resumeAt,
|
|
137
|
+
parkState,
|
|
138
|
+
});
|
|
139
|
+
const diagnostic = input.diagnostic?.trim();
|
|
140
|
+
const content = [
|
|
141
|
+
presentation.heading,
|
|
142
|
+
`Why: ${presentation.why}`,
|
|
143
|
+
parkState === "redirect"
|
|
144
|
+
? "The saved goal is held while the current turn handles the new request."
|
|
145
|
+
: parkState === "automatic"
|
|
146
|
+
? "Your saved work is intact; no manual action is needed."
|
|
147
|
+
: "Your saved work is intact; this turn stopped before more work could be lost.",
|
|
148
|
+
presentation.next,
|
|
149
|
+
...(diagnostic ? [`Diagnostic: ${diagnostic}`] : []),
|
|
150
|
+
].join("\n");
|
|
151
|
+
return {
|
|
152
|
+
content,
|
|
153
|
+
details: {
|
|
154
|
+
kind: input.kind,
|
|
155
|
+
reason,
|
|
156
|
+
action,
|
|
157
|
+
resumeCommand: input.resumeCommand,
|
|
158
|
+
resumeAt: input.resumeAt,
|
|
159
|
+
diagnostic,
|
|
160
|
+
parkState,
|
|
161
|
+
safelyParked: true,
|
|
162
|
+
},
|
|
163
|
+
presentation,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Normalize all reminder states into one hierarchy used by both transcript
|
|
168
|
+
* copy and the TUI renderer. Raw transport/provider diagnostics stay outside
|
|
169
|
+
* this model; callers may append them only after the actionable lines. */
|
|
170
|
+
export function buildPausePresentation(input: {
|
|
171
|
+
kind: ActionReminderKind;
|
|
172
|
+
reason: string;
|
|
173
|
+
action: string;
|
|
174
|
+
resumeCommand: string;
|
|
175
|
+
resumeAt?: string;
|
|
176
|
+
parkState: ActionReminderParkState;
|
|
177
|
+
}): ActionReminderPresentation {
|
|
178
|
+
const why = input.reason.trim() || "The current turn cannot continue safely.";
|
|
179
|
+
if (input.parkState === "redirect") {
|
|
180
|
+
return {
|
|
181
|
+
heading: "Goal safely parked — handling your new request now",
|
|
182
|
+
why,
|
|
183
|
+
next: `Next: ${input.action || input.resumeCommand}`,
|
|
184
|
+
resumeCommand: input.resumeCommand,
|
|
185
|
+
parkState: input.parkState,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (input.kind === "standby") {
|
|
189
|
+
return {
|
|
190
|
+
heading: "Waiting — work is safely parked",
|
|
191
|
+
why,
|
|
192
|
+
next: "No action is needed; the background completion will wake this work automatically.",
|
|
193
|
+
parkState: input.parkState,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (input.kind === "wait") {
|
|
197
|
+
return {
|
|
198
|
+
heading: "Waiting — work is safely parked",
|
|
199
|
+
why,
|
|
200
|
+
next: input.resumeAt
|
|
201
|
+
? `The wait resumes automatically at ${input.resumeAt}.`
|
|
202
|
+
: "The wait resumes automatically when its condition clears.",
|
|
203
|
+
parkState: input.parkState,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (input.kind === "decision") {
|
|
207
|
+
return {
|
|
208
|
+
heading: "Decision needed — work is safely parked",
|
|
209
|
+
why,
|
|
210
|
+
next: input.action
|
|
211
|
+
? `Next: ${input.action}`
|
|
212
|
+
: "Choose an option in the decision card, then resume.",
|
|
213
|
+
resumeCommand: input.resumeCommand,
|
|
214
|
+
parkState: input.parkState,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
heading: "Action needed — work is safely parked",
|
|
219
|
+
why,
|
|
220
|
+
next: `Next: ${input.action || input.resumeCommand}`,
|
|
221
|
+
resumeCommand: input.resumeCommand,
|
|
222
|
+
parkState: input.parkState,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function registerActionReminderRenderer(pi: ExtensionAPI): void {
|
|
227
|
+
pi.registerMessageRenderer(ACTION_REMINDER_CUSTOM_TYPE, (message, { outputPad }, theme) => {
|
|
228
|
+
const details = message.details as Partial<ActionReminderDetails> | undefined;
|
|
229
|
+
const diagnostics = details?.diagnostic?.trim();
|
|
230
|
+
const kind = details?.kind ?? "blocked";
|
|
231
|
+
const color = kind === "error" ? "error" : kind === "decision" ? "accent" : "warning";
|
|
232
|
+
const parkState = details?.parkState ?? (kind === "wait" || kind === "standby" ? "automatic" : "stopped");
|
|
233
|
+
const presentation = buildPausePresentation({
|
|
234
|
+
kind,
|
|
235
|
+
reason: details?.reason ?? "",
|
|
236
|
+
action: details?.action ?? details?.resumeCommand ?? "",
|
|
237
|
+
resumeCommand: details?.resumeCommand ?? "",
|
|
238
|
+
resumeAt: details?.resumeAt,
|
|
239
|
+
parkState,
|
|
240
|
+
});
|
|
241
|
+
const heading = parkState === "redirect"
|
|
242
|
+
? `↪ GLLA ${presentation.heading}`
|
|
243
|
+
: kind === "wait" || kind === "standby"
|
|
244
|
+
? `⏳ GLLA ${presentation.heading}`
|
|
245
|
+
: `⏸ GLLA ${presentation.heading}`;
|
|
246
|
+
const box = new Box(outputPad, 1, (t) => theme.bg("customMessageBg", t));
|
|
247
|
+
box.addChild(new Text(theme.fg(color, heading), 0, 0));
|
|
248
|
+
box.addChild(new Text(theme.fg("dim", `Why: ${presentation.why}`), 0, 0));
|
|
249
|
+
if (presentation.next) box.addChild(new Text(theme.fg("accent", presentation.next), 0, 0));
|
|
250
|
+
if (presentation.parkState === "redirect") {
|
|
251
|
+
box.addChild(new Text(theme.fg("dim", "The saved goal is held while the current turn handles the new request."), 0, 0));
|
|
252
|
+
} else if (presentation.parkState === "automatic") {
|
|
253
|
+
box.addChild(new Text(theme.fg("dim", "Your saved work is intact; no manual action is needed."), 0, 0));
|
|
254
|
+
} else {
|
|
255
|
+
box.addChild(new Text(theme.fg("dim", "Your saved work is intact; this turn stopped before more work could be lost."), 0, 0));
|
|
256
|
+
}
|
|
257
|
+
if (presentation.resumeCommand && presentation.parkState !== "automatic") {
|
|
258
|
+
box.addChild(new Text(theme.fg("dim", `Resume: ${presentation.resumeCommand}`), 0, 0));
|
|
259
|
+
}
|
|
260
|
+
if (diagnostics) {
|
|
261
|
+
box.addChild(new Text(theme.fg("dim", `Diagnostic: ${diagnostics}`), 0, 0));
|
|
262
|
+
}
|
|
263
|
+
return box;
|
|
264
|
+
});
|
|
265
|
+
}
|