gentle-pi 3.5.0 → 3.6.0
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/README.md +50 -41
- package/assets/orchestrator-delegation.md +2 -0
- package/bin/gentle-shell.mjs +1068 -16
- package/docs/gentle-shell.md +1 -1
- package/docs/readme-reference.md +108 -19
- package/extensions/gentle-ai.ts +27 -48
- package/lib/gentle-shell-launcher.ts +734 -37
- package/lib/inprocess-reviewer.ts +54 -6
- package/lib/native-review-cli.ts +12 -0
- package/package.json +1 -1
- package/runtime/gentle-shell-launcher.mjs +732 -35
- package/runtime/native-review-cli.mjs +12 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/install-tui-mode-setting.mjs +21 -2
- package/scripts/verify-package-files.mjs +2 -2
- package/tests/agents-rpc-publisher.test.ts +66 -0
- package/tests/gentle-agents.test.ts +46 -0
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +54 -49
- package/tests/gentle-ai.test.ts +147 -2
- package/tests/gentle-shell-bin.test.ts +2389 -6
- package/tests/gentle-shell-launcher.test.ts +1053 -10
- package/tests/inprocess-reviewer.test.ts +179 -0
- package/tests/install-tui-mode-setting.test.ts +22 -4
- package/tests/native-review-capability-contract.test.ts +28 -1
- package/tests/odd-runtime-delegation-gate.test.ts +18 -197
- package/tests/package-manifest.test.ts +6 -6
- package/tests/runtime-harness.mjs +1 -2
- package/lib/odd-runtime-delegation-gate.ts +0 -88
package/docs/gentle-shell.md
CHANGED
|
@@ -15,7 +15,7 @@ The [v2.6.0 release](https://github.com/Gentleman-Programming/gentle-pi/releases
|
|
|
15
15
|
- The Agents List and Details views preserve the orchestrator/session hierarchy and completion, abort, and lost-exit history. Parent-child queries and notifications have an explicit handoff path, while model, effort, and usage stay observable per task.
|
|
16
16
|
- Named `/gentle:profiles` atomically route the orchestrator separately from packaged and review roles; see the [technical reference](readme-reference.md#agent-model-profiles) for the profile model.
|
|
17
17
|
|
|
18
|
-
The source checkout currently prepares `gentle-pi` `3.
|
|
18
|
+
The source checkout currently prepares `gentle-pi` `3.6.0` with a package-local Gentle AI `v3.6.1` pin; this is not a claim that `3.6.0` is published.
|
|
19
19
|
|
|
20
20
|
## Shell interactions and runtime behavior
|
|
21
21
|
|
package/docs/readme-reference.md
CHANGED
|
@@ -24,7 +24,7 @@ ODD is the predefined workflow: it runs by default on every request, without the
|
|
|
24
24
|
- **One feature document:** `odd/tasks/<feature-name>.md` holds objective, problem, why, scope, constraints, actionable checklist with stable IDs and acceptance criteria, verification evidence, progress, and next step. Project-scoped Engram topic `odd/<feature-name>/tasks` mirrors the full document and repository-relative locator. Keep concise rationale for meaningful accepted changes here, not a separate plan or exhaustive journal. Accepted user, review, or verification changes update intent and tasks together; preserve valid completed work, add new tasks or reopen invalidated items with reasons. Findings alone do not authorize expansion or acceptance. Routine corrections stay with their tasks; checkoffs require observed proof.
|
|
25
25
|
- **Recovery:** write local progress first and read back both copies; writes are not atomic. Unavailable Engram leaves an explicit pending mirror, not invented success or a block on unrelated safe work. Before implementation or resume, the parent reads full feature memory and the actual task file, reconciles code and evidence, and preserves conflicting versions. Pass the locator and relevant context; workers read the document before edits. The existing Todo UI is a projection, not another authority.
|
|
26
26
|
- **Task size:** about 400 authored changed lines (additions plus deletions) is advisory only, not a cap, acceptance criterion, automatic stop, forced split, or RDD trigger. Keep coherent behavior with tests and docs, explain natural overages, and continue under existing PR policy. Forward this instruction to workers; never remove whitespace, comments, or tests, minify, invent abstractions, or split artificially for cosmetic savings.
|
|
27
|
-
- **
|
|
27
|
+
- **Delegation boundary:** the parent delegates implementation touching two or more non-trivial files; a second direct path alone is not a runtime refusal. The runtime cannot infer whether an edit is mechanical from write history. Validate consequential premises before building, reuse relevant sibling findings, run focused checks while iterating, then the applicable full suite at closure. This is effort guidance, not a hard token or line budget.
|
|
28
28
|
- **Research:** optional research addresses a named uncertainty. Establish problem, intended outcome, constraints, and current evidence; inspect code and adapt depth to consequence, not fixed questionnaires or rounds. The parent asks one focused product question only when needed, then waits; workers return gaps. Use available authorized documentation/web tools, prefer primary sources, and attribute claims to URLs/code locations. Distinguish facts, assumptions, contradictions, freshness, and gaps; return a recommendation, tradeoffs, open questions, and implementation implications. Forward these instructions to an existing fresh general worker, not a specialized agent or `sdd-research`. Unavailable evidence pauses only unsafe dependent decisions. Research stays read-only with no new persistence/readiness machinery; a brief proposal is needed only for a real decision.
|
|
29
29
|
- **Assumptions:** at most one scoped independent read-only challenge for a high-consequence unproven premise, including a small security-critical change. Deterministic failures need fixes, not debate. Native RDD claims stay with its refuter.
|
|
30
30
|
- **TDD:** resolve on/off from existing project/session configuration or explicit user choice; retain source and exact runner in the feature document when present and forward all three on every implementation delegation, refreshing on resume. Test presence does not enable TDD. Enabled requires observed RED before implementation → GREEN → REFACTOR; disabled still requires ordinary functional checks. Unknown/conflicting mode or a missing runner needs only the clarification affecting the next action, never invented precedence, commands, or `sdd-init`.
|
|
@@ -106,7 +106,7 @@ This is guidance through existing tools, not a new CLI, phase, state engine, or
|
|
|
106
106
|
| **Skill creation workflow** | Provides the `gentle-ai-skill-creator`/`gentle-ai-skill-improver` skills, `/skill-creation` prompt, and packaged style guide for LLM-first skills. |
|
|
107
107
|
| **Delivery skills** | Includes issue-first PRs, chained PRs, work-unit commits, cognitive docs, comment writing, and Judgment Day review. |
|
|
108
108
|
| **Bounded native review** | Freezes one candidate, dispatches only controller-selected lenses, and records native authority. Review outcomes are informational; delivery follows ordinary repository policy. |
|
|
109
|
-
| **Verified native runtime** | The current source checkout provisions the exact package-local Gentle AI v3.
|
|
109
|
+
| **Verified native runtime** | The current source checkout provisions the exact package-local Gentle AI v3.6.1 runtime: signed, SHA-256-pinned release archives on Darwin/Linux and a Go SumDB-verified source build on Windows x64/arm64. It validates package-local integrity and rejects PATH, global, sibling, symlink, and mode fallbacks. |
|
|
110
110
|
| **Runtime safety** | Blocks destructive shell commands, asks for confirmation for sensitive operations, and blocks direct read/write/edit access to sensitive paths. |
|
|
111
111
|
|
|
112
112
|
## Native pointer regions
|
|
@@ -135,15 +135,33 @@ Callers own keyboard policy, theme state, and business actions.
|
|
|
135
135
|
|
|
136
136
|
## Install
|
|
137
137
|
|
|
138
|
+
Two paths reach the same package. Path A stays standalone; Path B installs into an existing pi.
|
|
139
|
+
|
|
140
|
+
### Path A: standalone `gentle-shell` (recommended, no pi changes)
|
|
141
|
+
|
|
138
142
|
```bash
|
|
139
|
-
|
|
143
|
+
npm i -g gentle-pi
|
|
144
|
+
|
|
145
|
+
# Own home, never touches your pi install
|
|
146
|
+
gentle-shell
|
|
147
|
+
|
|
148
|
+
# Reuse your pi sign-ins, models and chats instead
|
|
149
|
+
gentle-shell --link
|
|
140
150
|
```
|
|
141
151
|
|
|
142
|
-
|
|
152
|
+
`gentle-shell` alone starts in its own home, `~/.gentle-shell/agent`. `gentle-shell --link` reuses `~/.pi/agent` as-is. Run `gentle-shell home link` to make `--link` the default. Full flags, env vars, and modes: [gentle-shell launcher](#gentle-shell-launcher).
|
|
153
|
+
|
|
154
|
+
### Path B: inside an existing pi
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pi install npm:gentle-pi@3.5.1
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The stable release is [`v3.5.1`](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v3.5.1). Restart Pi after installation, then run `gentle-ai sync`. That published release pairs with Gentle AI `v2.8.0` and provider contract `1.2.0`; capabilities `v2.5` are retained. The command above installs that exact published version.
|
|
143
161
|
|
|
144
162
|
### Source checkout
|
|
145
163
|
|
|
146
|
-
This checkout prepares `gentle-pi` `3.
|
|
164
|
+
This checkout prepares `gentle-pi` `3.6.0`; it is source state, not a published release. Its package-local native runtime pin is Gentle AI `v3.6.1`, distinct from the published `v3.5.1` pairing.
|
|
147
165
|
|
|
148
166
|
The native SDD status consumer accepts both the pinned producer's legacy
|
|
149
167
|
`apply`/`verify`/`remediate`/`archive` instruction record and the classical
|
|
@@ -154,20 +172,20 @@ Unknown or incomplete instruction records still fail closed.
|
|
|
154
172
|
The Pi runtime now uses native status exclusively for SDD and retires standalone
|
|
155
173
|
sync. The full chain follows completed apply to archive, where applicable delta
|
|
156
174
|
specs are composed; verification remains explicitly invokable. With the current
|
|
157
|
-
3.
|
|
175
|
+
3.6.1 pin, native still requires verification and its emitted evidence requirements;
|
|
158
176
|
a plain practical PASS report does not satisfy that legacy native gate. Pi forwards
|
|
159
177
|
those exact instructions without overriding readiness or inventing legacy evidence.
|
|
160
178
|
Classical direct-archive behavior is compatibility-tested with an identified
|
|
161
179
|
upstream development build, not presented as a published fix or version bump.
|
|
162
180
|
The complete classical flow awaits a compatible published native version; this
|
|
163
181
|
change does not bump the pin. Ordinary attempt governance and research/planning simplification remain separate
|
|
164
|
-
work under [SDD parity #1051](https://github.com/Gentleman-Programming/gentle-
|
|
182
|
+
work under [SDD parity #1051](https://github.com/Gentleman-Programming/gentle-shell/issues/1051).
|
|
165
183
|
|
|
166
184
|
### Pi compatibility
|
|
167
185
|
|
|
168
186
|
The current package requires Pi 0.85.1 or newer (development tests pin 0.85.1). Use the latest Pi release; gentle-pi does not update your installed Pi automatically. Children, including any `GENTLE_PI_AGENTS_PI` override, must emit `agent_settled`: `agent_end` records a run's output but is not completion because retries or queued continuations may follow.
|
|
169
187
|
|
|
170
|
-
The [`v2.6.0` release](https://github.com/Gentleman-Programming/gentle-
|
|
188
|
+
The [`v2.6.0` release](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v2.6.0) added persistent registered worktrees and grouped `/gentle:changes` views; fuller workspace interaction details are in the [Gentle Shell reference](gentle-shell.md). It also adds named atomic `/gentle:profiles`, parent-confirmed native SDD preflight transport, native review intended-untracked selection and provider continuations, and opt-in custom ask responses. Pi recognizes its global Git-managed package path; subsystems install with explicit recovery guidance when npm lifecycle work was skipped. Windows keeps child consoles hidden and fixes ownership mode; Gentle Todo keeps the next pending task visible when collapsed.
|
|
171
189
|
|
|
172
190
|
### Install-time fullscreen
|
|
173
191
|
|
|
@@ -179,18 +197,30 @@ Malformed/nonobject JSON, symlink/nonregular settings, unsafe paths, or a busy s
|
|
|
179
197
|
|
|
180
198
|
### RDD history and opt-in
|
|
181
199
|
|
|
182
|
-
Native RDD was introduced in `gentle-pi` `v0.15.0` on 2026-07-10 with bounded review transactions. The current stable release, [`
|
|
200
|
+
Native RDD was introduced in `gentle-pi` `v0.15.0` on 2026-07-10 with bounded review transactions. The current stable release, [`v3.5.1`](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v3.5.1), includes native RDD:
|
|
183
201
|
|
|
184
202
|
```bash
|
|
185
203
|
# Stable release
|
|
186
|
-
pi install npm:gentle-pi@
|
|
204
|
+
pi install npm:gentle-pi@3.5.1
|
|
187
205
|
```
|
|
188
206
|
|
|
189
207
|
RDD remains opt-in. Enable it only through an explicit user decision with `/gentle:review-mode enable`; `status` lets you inspect the mode without changing it.
|
|
190
208
|
|
|
191
|
-
The source checkout's RDD integration installs Gentle AI only into its private `.gentle-ai/` directory. Darwin and Linux use pinned release assets with asset and executable SHA-256 verification (signed archives for source pin `v3.
|
|
209
|
+
The source checkout's RDD integration installs Gentle AI only into its private `.gentle-ai/` directory. Darwin and Linux use pinned release assets with asset and executable SHA-256 verification (signed archives for source pin `v3.6.1`; raw prerelease binaries only under a prerelease pin). Windows x64 and arm64 build the exact `v3.6.1` source tag with a local Go 1.25.10+ toolchain, a sealed Go environment, `GOTOOLCHAIN=local`, and `GOSUMDB=sum.golang.org`; it does not download Go automatically. Windows provenance is Go-toolchain plus SumDB evidence and postinstall tamper detection, **not** Authenticode or protection against a malicious joint binary-and-manifest replacement. Package-private locks coordinate cooperative concurrent or crashed installers; their tombstones fail closed. A malicious same-user process with write access to package-private `node_modules` is outside that protocol because it can already replace package code, binary, or manifest, and portable Node has no pathname-delete CAS. It never uses `PATH` or a global `gentle-ai` installation. For development or offline installs only, set `GENTLE_PI_SKIP_GENTLE_AI_INSTALL=1`; native review operations then fail closed with an actionable `package-local-binary-missing` error. To recover explicitly, if `GENTLE_PI_SKIP_GENTLE_AI_INSTALL` is set, remove or unset it before changing to the installed `gentle-pi` package directory. Then run `node scripts/install-gentle-ai.mjs`. This invokes the package-owned installer without relying on a global binary or npm configuration change. A missing binary can result from skipped lifecycle scripts, but does not prove that lifecycle scripts were disabled.
|
|
192
210
|
|
|
193
|
-
Recommended companion packages:
|
|
211
|
+
Recommended companion packages, into the standalone `gentle-shell` home:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
gentle-shell install npm:pi-intercom
|
|
215
|
+
gentle-shell install npm:gentle-engram
|
|
216
|
+
gentle-shell install npm:pi-web-access
|
|
217
|
+
gentle-shell install npm:pi-lens
|
|
218
|
+
gentle-shell install npm:@juicesharp/rpiv-ask-user-question
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`--link` before the subcommand (for example `gentle-shell --link install npm:pi-intercom`) targets `~/.pi/agent` instead of the isolated home.
|
|
222
|
+
|
|
223
|
+
Or, when `gentle-pi` is installed inside an existing pi:
|
|
194
224
|
|
|
195
225
|
```bash
|
|
196
226
|
pi install npm:pi-intercom
|
|
@@ -232,6 +262,7 @@ An orphan branch with commits and no parent has no branch point to name as `base
|
|
|
232
262
|
```bash
|
|
233
263
|
gentle-shell [options] [-- pi-args...]
|
|
234
264
|
gentle-shell home [link|isolated|<path>]
|
|
265
|
+
gentle-shell [home selectors] setup [--dry-run]
|
|
235
266
|
```
|
|
236
267
|
|
|
237
268
|
### Flags
|
|
@@ -241,6 +272,7 @@ gentle-shell home [link|isolated|<path>]
|
|
|
241
272
|
| `--link` | Home is `PI_CODING_AGENT_DIR` or `~/.pi/agent`. Reuses your existing pi sign-ins, models, and chats; never writes to its `settings.json`. |
|
|
242
273
|
| `--isolated` | Home is `GENTLE_SHELL_HOME` or `~/.gentle-shell/agent`. No credential seeding. Default when nothing else is configured. |
|
|
243
274
|
| `--home <path>` | Home is the given directory. |
|
|
275
|
+
| `--package-root <dir>` | Force this directory as the gentle-pi package to load, taking over from any conflicting package the target `settings.json` already declares (see "Loading the package" below). |
|
|
244
276
|
| `--help`, `-h` | Print usage (flags, commands, env vars) and exit 0. |
|
|
245
277
|
| `--version` | Print `gentle-shell <version>`, `pi <version>`, and `home <mode> <dir>`, then exit 0. |
|
|
246
278
|
| `--` | Everything after is forwarded to pi verbatim, even text that looks like a `gentle-shell` flag. |
|
|
@@ -255,6 +287,30 @@ gentle-shell home [link|isolated|<path>]
|
|
|
255
287
|
|
|
256
288
|
`gentle-shell install npm:<pkg>`, `gentle-shell remove ...`, `gentle-shell list`, `gentle-shell update ...`, `gentle-shell config`, and `gentle-shell auth ...` run pi's own commands against the resolved home — the `--isolated` home by default, or your own pi home with `--link`. A launcher flag before the subcommand (`--link`, `--isolated`, `--home <path>`) still selects which home the subcommand runs against. Running `gentle-shell install npm:gentle-pi` inside the isolated home is unnecessary: the launcher already loads the Gentle Shell package itself (see "Loading the package" below).
|
|
257
289
|
|
|
290
|
+
`gentle-shell update` and `gentle-shell list` follow that same home selection, so they inspect and update packages in whichever home the effective flag or persisted `home` config points to.
|
|
291
|
+
|
|
292
|
+
### `setup` subcommand
|
|
293
|
+
|
|
294
|
+
`gentle-shell setup` provisions the resolved home — the same home selection as any other invocation, `--isolated` by default, or `--link`/`--home <path>` when given before `setup` — with the same companion packages a regular `gentle-ai install --agent pi` installs into a Pi agent home: `npm:gentle-pi`, `npm:gentle-engram`, `npm:pi-mcp-adapter`, `npm:@juicesharp/rpiv-ask-user-question`, `npm:pi-web-access`, `npm:pi-btw`, plus running `pi-engram init`. It never touches `~/.pi/agent` unless you pass `--link`.
|
|
295
|
+
|
|
296
|
+
It resolves the home and the pi runtime exactly as a normal run does (including the isolated/`--home` bootstrap and the pi version gate), then runs the package-local pinned gentle-ai binary — `gentleAiBinaryPath()` from `lib/gentle-ai-binary.ts`, never a `gentle-ai` found on `PATH` — as:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
<package>/.gentle-ai/v<version>/gentle-ai install --agent pi --scope global [--dry-run]
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
with `PI_CODING_AGENT_DIR` and `GENTLE_PI_AGENT_HOME` set to the resolved home, and the resolved pi runtime's directory prepended to `PATH`, so gentle-ai's own preflight finds `pi` even when it is bundled or given through `GENTLE_SHELL_PI`. `--dry-run` is forwarded to gentle-ai unchanged. Output streams straight through (`stdio: "inherit"`), and `gentle-shell setup` exits with gentle-ai's own exit code. If the package-local gentle-ai binary is missing, `setup` installs it itself (by running its own `scripts/install-gentle-ai.mjs` postinstall) before giving up — the postinstall never runs when `npm install`'s lifecycle scripts were disabled (for example under `ignore-scripts=true`) — unless `GENTLE_PI_SKIP_GENTLE_AI_INSTALL=1`, in which case it exits 1 with the same actionable message it always did.
|
|
303
|
+
|
|
304
|
+
Requires the package-local gentle-ai pin at v3.6.0 or newer — the pin that adds `PI_CODING_AGENT_DIR` support to `gentle-ai install`. `setup` enforces this before spawning anything: an older pinned gentle-ai ignores that variable and would silently install into `~/.pi/agent` instead of the target home, so `setup` exits 1 with `gentle-shell: setup needs the package-local gentle-ai v3.6.0 or newer (pinned: <version>); this build cannot provision a home without touching ~/.pi/agent` instead of spawning it.
|
|
305
|
+
|
|
306
|
+
gentle-ai's managed Pi stack always declares `npm:gentle-pi` itself in the home's `settings.json` as part of that install. Once gentle-ai exits 0, `setup` removes it again immediately: this launcher always loads its own gentle-pi (its own package root, or a take-over — see "Loading the package" below), never the one gentle-ai's stack just installed, so leaving that declaration in place would let the home drift onto whatever gentle-pi npm last installed — or, for a developer running from a source checkout, onto the published npm package — instead of the running launcher's own copy. `setup` also removes `npm:@juicesharp/rpiv-ask-user-question` the same way, if gentle-ai declared it: that package conflicts with gentle-pi's own first-party `ask_user_question` tool, and Pi refuses to load two providers for the same tool name (tracked upstream as gentle-ai #4820). `--dry-run` only reports both pending removals instead of running them. A home only ever keeps a `npm:gentle-pi` declaration — and so only ever stops getting the launcher's own injection (see "Loading the package" below) — when something puts it back after `setup` runs: a hand-edited `settings.json`, or `gentle-shell install npm:gentle-pi` run manually; in that case the home behaves like a regular Pi agent home with gentle-pi installed, and `gentle-shell update npm:gentle-pi` updates it like any other package.
|
|
307
|
+
|
|
308
|
+
**Known limitation**: gentle-ai always writes its persona file to the shared `~/.pi/gentle-ai/persona.json` without honoring `PI_CODING_AGENT_DIR`, so the persona is shared across every home `gentle-shell setup` provisions, not per-home. `setup` keeps that file byte-identical across the run regardless — snapshotting it before spawning gentle-ai and restoring it afterward, in manual, `--dry-run`, and automatic first-run modes alike — because the pinned gentle-ai still writes the Pi persona outside `PI_CODING_AGENT_DIR`. gentle-ai also records the running binary's managed-asset digest in the shared `~/.gentle-ai/state.json` (`managed_asset_digest`), again regardless of `PI_CODING_AGENT_DIR`, so a `setup` run otherwise leaves the user's own (unrelated, on-`PATH`) gentle-ai reporting its managed assets as outdated and demanding `gentle-ai sync`. `setup` restores just that one field afterward, the same way and in the same modes — but never the whole file, since `state.json` also carries fields (like the installed-agents list) the pinned gentle-ai is meant to update, and it never creates or deletes `state.json` itself.
|
|
309
|
+
|
|
310
|
+
**Test/development only**: `GENTLE_SHELL_GENTLE_AI_BIN` overrides which gentle-ai executable `setup` runs, bypassing the pinned package-local resolution. `GENTLE_SHELL_GENTLE_AI_PIN` overrides the pin version `setup` (and automatic first-run provisioning, below) checks against `MIN_SETUP_GENTLE_AI_VERSION` (3.6.0), independent of `GENTLE_SHELL_GENTLE_AI_BIN`. `GENTLE_SHELL_GENTLE_AI_INSTALLER` overrides the script path `setup` runs to self-heal a missing package-local binary, instead of the real `scripts/install-gentle-ai.mjs`. `GENTLE_SHELL_CONFIG` overrides the launcher config.json path (normally `<homedir>/.gentle-shell/config.json`), read and written by the `home` subcommand and by automatic first-run provisioning's marker. `GENTLE_SHELL_AUTO_SETUP_TIMEOUT_MS` overrides automatic first-run provisioning's 15-minute per-child timeout ceiling (manual `setup` never has one). All five exist for the test suite and for exercising a different gentle-ai build/pin/installer/config/timeout; end users never need them.
|
|
311
|
+
|
|
312
|
+
**Where the plugin list comes from**: the companion list above is not maintained in gentle-shell itself — it is the managed Pi stack of the pinned package-local gentle-ai (gentle-ai's own managed sources, plus whatever it has already retired). Over time, third-party plugins in that stack get replaced by native Gentle Shell features — already done for `rpiv-todo` and `npm:@juicesharp/rpiv-ask-user-question` — so a gentle-ai release retires a plugin, gentle-pi bumps its pinned gentle-ai version, and the next `gentle-shell` launch sees the pin change (see "First run in an isolated or custom home" below), re-runs the setup flow, and gentle-ai prunes the retired package from the home. `gentle-shell`'s own post-install removal of `npm:@juicesharp/rpiv-ask-user-question` above is a stopgap for homes provisioned before that gentle-ai retirement ships.
|
|
313
|
+
|
|
258
314
|
### pi runtime resolution
|
|
259
315
|
|
|
260
316
|
1. `GENTLE_SHELL_PI` — path to a pi executable, when set to a non-empty value.
|
|
@@ -271,14 +327,47 @@ If none resolve, `gentle-shell` exits 1 naming all three options. Once a runtime
|
|
|
271
327
|
| `GENTLE_SHELL_HOME` | Overrides the isolated home directory (default `~/.gentle-shell/agent`). |
|
|
272
328
|
| `PI_CODING_AGENT_DIR` | Read to resolve the `--link` home; also set on the pi child process to the effective home. |
|
|
273
329
|
| `GENTLE_PI_AGENT_HOME` | Set on the pi child process to the effective home; gentle-pi's own home resolution reads it back. |
|
|
330
|
+
| `GENTLE_SHELL_NO_AUTO_SETUP` | Set to `1` to skip automatic first-run provisioning (see "First run in an isolated or custom home" below). |
|
|
274
331
|
|
|
275
332
|
### Loading the package
|
|
276
333
|
|
|
277
|
-
Unless the target home's `settings.json` already
|
|
334
|
+
Unless the target home's `settings.json` already declares gentle-pi, every invocation injects `-e <package root> --theme <root>/themes --skill <root>/skills --prompt-template <root>/prompts` ahead of the forwarded arguments, so the Gentle Shell extensions, themes, skills, and prompt templates load without a separate `pi install`. Every mode — `--link`, `--isolated`, and `--home <path>` — consults the home's own `settings.json` for a declaration; an isolated or `--home` home only ever carries one by running `gentle-shell setup` (see above), which installs `npm:gentle-pi` into it, or by hand-editing `settings.json`. A home without any declaration always gets the plain injection — except when the forwarded arguments start with one of pi's own subcommands (`install`, `remove`, `uninstall`, `update`, `list`, `config`, `auth`): pi dispatches those on `argv[0]` before it parses any flags, so the injection — and any take-over below — is skipped entirely and pi sees the bare subcommand, e.g. `gentle-shell install npm:x` runs exactly `pi install npm:x`. A subcommand never triggers a take-over, even against a home whose settings declare a conflicting gentle-pi; see "Managing packages" above.
|
|
335
|
+
|
|
336
|
+
A declaration is recognized either as `npm:gentle-pi[@version]` in the `packages` array, or as a local path package (string or `{"source": "..."}` entry, relative or absolute) whose own `package.json` names it `"gentle-pi"` — the shape produced when gentle-pi is developed from a checkout and referenced by path in `settings.json` instead of installed via `pi install npm:gentle-pi`.
|
|
337
|
+
|
|
338
|
+
- **A pi subcommand as the first forwarded argument**: no injection and no take-over at all, regardless of any declaration — pi must see the bare subcommand as `argv[0]`.
|
|
339
|
+
- **npm declaration matching this launcher's own install**: no injection — pi already loads gentle-pi from the declared package.
|
|
340
|
+
- **No declaration at all, or a path declaration that resolves (after `realpath`) to this launcher's own package root**: the same plain injection as above.
|
|
341
|
+
- **A declaration that resolves to a *different* gentle-pi** (a different checkout declared by path, for example) **— take-over**: `gentle-shell` prints `taking over gentle-pi from <declared source> for this run (settings unchanged; its skills, prompts, and themes still load alongside this launcher's)` to stderr, then runs pi with `--no-extensions` followed by an explicit `-e <dir>` for every *other* package already in settings (npm entries resolve to `<agent dir>/npm/node_modules/<name>`; path entries resolve relative to the settings file), then loose extension entries for `<agent dir>/extensions` and the project-local `<cwd>/.pi/extensions` (each candidate directory only consulted when it already exists), and finally its own `-e <package root> --theme ... --skill ... --prompt-template ...`. `settings.json` itself is never modified, and every `-e` path — including the launcher's own package root — is injected at most once even if it would otherwise repeat.
|
|
342
|
+
|
|
343
|
+
A declared *other* package whose resolved directory does not actually exist (a hand-edited `settings.json`, a failed or interrupted `pi install`, or an npm store laid out anywhere other than `<agent dir>/npm/node_modules`) is skipped with a stderr warning naming the source and the resolved path, instead of being handed to pi as an unresolvable `-e` that would fail the whole launch with "Cannot find module".
|
|
344
|
+
|
|
345
|
+
`--no-extensions` disables pi's normal directory-discovery pass, and pi's `-e` flag hands a path straight to its module loader with no discovery of its own — passing a loose extensions directory as-is via `-e <dir>` fails with "Cannot find module" unless that directory is itself a self-contained extension. So each loose candidate directory is resolved before injection: a directory that is itself a self-contained extension (a `package.json` declaring a `pi.extensions` manifest) is passed through as a single `-e <dir>`; otherwise its direct `*.ts`/`*.js`/`*.mjs` files — including a root-level `index.ts`/`index.js`, which is just another loose file — and any `<subdir>/index.ts`/`index.js` are discovered individually — mirroring pi's own directory scan — and each is injected as its own `-e <file>`. Hidden entries (dotfiles) and `*.d.ts` declaration files are skipped, since neither was ever a runnable extension.
|
|
346
|
+
|
|
347
|
+
A git-sourced other package is skipped with a stderr warning, since its install directory cannot be derived without pi's own package manager; an object entry with `extensions` or `autoload` filters is still included but warned about, because the take-over cannot honor those filters for extension discovery — that package's skills, prompts, and themes still load normally through settings discovery, which `--no-extensions` does not affect.
|
|
348
|
+
|
|
349
|
+
**Known limitation**: the take-over never removes the original declaration from `settings.json`, so its skills, prompt templates, and themes are still discovered alongside this launcher's own — only its extensions are replaced by `--no-extensions` plus the injected `-e` flags above.
|
|
350
|
+
- **`--package-root <dir>`**: forces a take-over using `<dir>` as the package root, even when settings already declare a matching `npm:gentle-pi`, or when there is no declaration at all. Use it to test a different gentle-pi checkout against a home whose settings already point at another one. Has no effect when the forwarded arguments start with a pi subcommand, since a subcommand skips the take-over entirely. This *forcing* behavior — a take-over with no matching declaration required — is only ever reached for `--link`: with `--isolated` or `--home <path>`, `--package-root` still changes which directory is injected, but on its own it goes through the same plain injection as "no declaration at all" above — no `--no-extensions`, and no other-package or loose-extension re-injection. A settings.json declaration in an isolated or `--home` home (one `gentle-shell setup` installed, or a hand-edited path entry) still triggers its own take-over there exactly as it would for `--link`, independent of `--package-root`. When that declaration is present and the home is not `--link`, `--package-root` has no effect at all — `gentle-shell` prints one stderr warning naming both the home and the ignored directory instead of silently dropping the flag. `--package-root` must also name an existing directory; a missing or non-directory path fails fast with a clear error instead of launching pi with unresolvable flags.
|
|
351
|
+
|
|
352
|
+
This take-over exists because two gentle-pi copies loaded at once — the declared one plus this launcher's own injection — register the same tools and extensions twice, which pi reports as tool conflicts (for example `Tool ask_user_choice conflicts with ...`).
|
|
278
353
|
|
|
279
354
|
### First run in an isolated or custom home
|
|
280
355
|
|
|
281
|
-
The first time `gentle-shell` resolves to an isolated or `--home <path>` home that does not already exist, it creates the directory, writes `"tuiMode": "fullscreen"` into its `settings.json`, and prints one hint to stderr pointing at `--link`. A `--link` home is never bootstrapped this way — it is assumed to already exist as your pi agent home. Later runs against the same home skip
|
|
356
|
+
The first time `gentle-shell` resolves to an isolated or `--home <path>` home that does not already exist, it creates the directory, writes `"tuiMode": "fullscreen"` and, unless the home's `settings.json` already declares one, `"theme": "Gentleman-Cute"` into its `settings.json`, writes a small ownership marker file at `<home>/.gentle-shell-home` (a one-line JSON object naming the `gentle-pi` version that created it), and prints one hint to stderr pointing at `--link`. A `--link` home is never bootstrapped this way — it is assumed to already exist as your pi agent home. Later runs against the same home skip the write and the hint. The default theme is also re-applied after automatic or manual setup if gentle-ai's own managed install wrote a different theme into a home that had none before that run; a home (or `--link`) that already declares its own theme is never touched.
|
|
357
|
+
|
|
358
|
+
Right after that bootstrap, and on every later launch, a plain `gentle-shell` against an isolated or `--home <path>` home (never `--link`, and never a pi subcommand like `gentle-shell install/remove/list/...`) also runs the same flow as `gentle-shell setup` automatically before starting pi, so you never have to know `setup` exists. It runs when the home has never been provisioned, was provisioned with a gentle-ai pin different from the package-local pin this `gentle-pi` ships — for example after upgrading `gentle-pi` to a version pinned to a newer gentle-ai — or was provisioned by a different `gentle-pi` version than the one now running — for example after `npm i -g gentle-pi` upgrades the launcher itself, so the home re-syncs to match it. A completed run is recorded as `provisioned: {"<realpath of the home>": {"gentleAi": "<pin>", "gentlePi": "<running gentle-pi version>", "at": "<ISO timestamp>"}}` in the launcher's config.json (`~/.gentle-shell/config.json` by default, or `GENTLE_SHELL_CONFIG` when overridden — see "Environment variables" above), preserving every other key already in that file (including the persisted `home` mode and any other home's marker). A marker written before this `gentlePi` field existed always counts as needing provisioning too, so the very next launch backfills it.
|
|
359
|
+
|
|
360
|
+
Every child process the flow spawns (the gentle-ai installer self-heal, the package-local gentle-ai binary, and the `pi remove` post-install cleanup) has its stdout routed to this launcher's own stderr, together with the flow's own notices, so a headless consumer's stdout — `gentle-shell --mode rpc` or `gentle-shell -p "..."` — stays exactly what it always was: pi's own output, nothing else. The first time it runs in a home you see `gentle-shell: first run in <home>: installing the Gentle AI companion packages (one time; set GENTLE_SHELL_NO_AUTO_SETUP=1 to skip)`; on a gentle-ai pin change you see `gentle-shell: gentle-ai pin changed (<old> -> <new>): updating <home>`; on a gentle-pi version change you see `gentle-shell: gentle-pi changed (<old> -> <new>): updating <home>`; when both changed at once, one line names both.
|
|
361
|
+
|
|
362
|
+
Automatic provisioning only ever touches a home `gentle-shell` itself owns: the dedicated isolated home, a `--home <path>` (or persisted `home <path>`) that is new or already empty, one carrying the `.gentle-shell-home` ownership marker the bootstrap above wrote (so a `--home` directory whose *first* auto-provision attempt failed — leaving only the bootstrapped `settings.json` and marker behind — is still retried on the next launch instead of being mistaken for a foreign, pre-existing directory), or one this same config marker already recorded as provisioned before (so a later gentle-ai/gentle-pi re-sync still runs). A `--home` that already has content and neither marker — for example pointing at an existing, unrelated directory — is left alone, with one stderr hint (`` gentle-shell: <dir> already has content and was not set up by gentle-shell; run `gentle-shell <home flags> setup` to provision it ``) instead of a silent skip; pointing it at pi's own default agent home is refused the same way even when that directory is empty. `gentle-shell setup` run manually still works against any home — that is explicit intent, not automatic provisioning.
|
|
363
|
+
|
|
364
|
+
A failed flow (a non-zero gentle-ai or pi exit, a pin gate refusal, a self-heal that still can't find the binary, or a gentle-ai/`pi remove` child that runs past its timeout ceiling — 15 minutes by default — and gets killed) never blocks the launch: `gentle-shell` prints `` gentle-shell: automatic setup failed (exit <n>); starting anyway and retrying next run. Run `gentle-shell <home flags> setup` to see the full output. ``, followed by the underlying reason on the next line when one is known (a timeout's reason names the *effective* ceiling that fired — `` timed out after 15 minutes `` by default, or in seconds for a shorter override), writes no marker, and starts pi with today's plain injection (the home has no `npm:gentle-pi` declaration to skip it for). The next launch against the same home retries automatically. Manual `setup` never has that ceiling. A spawned child dying by a signal on its own — a crash, an OOM kill, an external `kill`, anything gentle-shell itself did not ask for — is just another failure reported the same way; pi still launches. Only an interrupt actually reaching `gentle-shell` itself (Ctrl-C, or SIGTERM/SIGHUP delivered to the launcher process) is different: it forwards that signal to whichever child is running, kills it, and exits `gentle-shell` itself immediately with the matching signal exit code, without starting pi — you asked the process to stop, not to fall back. This launcher-interrupt tracking covers the package-local gentle-ai install and each `pi remove` cleanup step (both driven through the same async spawn helper); the installer self-heal step that recovers a missing package-local gentle-ai binary runs synchronously and does not carry the same tracking — an interrupt reaching the launcher during that narrow step falls back to Node's default signal handling instead. An otherwise-unexpected failure anywhere in the flow itself (for example an unwritable config.json directory) is also never fatal: it is reported the same way and the launch continues.
|
|
365
|
+
|
|
366
|
+
Concurrent first runs against the same home are serialized with an exclusive lock file at `<home>/.gentle-shell-setup.lock`: a second `gentle-shell` process started while the first is still provisioning skips auto-provisioning for that run instead of racing gentle-ai's own installer, with one stderr notice. A lock older than 15 minutes is treated as stale — left over from a run that crashed or was killed before it could clean up — and is removed (after re-confirming it is still stale right before removal, so a lock a concurrent process just refreshed is never deleted out from under it) so provisioning can proceed. The lock is always removed once the flow finishes, successfully or not.
|
|
367
|
+
|
|
368
|
+
Set `GENTLE_SHELL_NO_AUTO_SETUP=1` to skip automatic provisioning entirely and keep today's plain-injection behavior on every launch; `gentle-shell setup` (see above) still works as a manual, explicit step. `--link` is never auto-provisioned — it reuses your existing pi agent home as-is, credentials included.
|
|
369
|
+
|
|
370
|
+
**Known limitation**: like `gentle-shell setup`, automatic provisioning never copies credentials into the home it provisions — a freshly auto-provisioned isolated or `--home` home still needs its own `/login` (or equivalent) inside pi.
|
|
282
371
|
|
|
283
372
|
### Windows shims
|
|
284
373
|
|
|
@@ -375,7 +464,7 @@ Reconciliation is intentionally narrow: native code may quarantine only the boun
|
|
|
375
464
|
|
|
376
465
|
Native lifecycle status remains informational. VALIDATE does not authorize delivery; commit, push, PR, and release commands follow ordinary repository policy. Recovery grants no new budget, and legacy graph bundle export/import is retired.
|
|
377
466
|
|
|
378
|
-
This is the post-U8 boundary, not the final architecture. [Issue #191](https://github.com/Gentleman-Programming/gentle-
|
|
467
|
+
This is the post-U8 boundary, not the final architecture. [Issue #191](https://github.com/Gentleman-Programming/gentle-shell/issues/191) is the immediate final unit in this same delivery: extract the remaining Pi command-projection and lifecycle-gate surface from `review-transaction.ts`, repoint runtime enforcement, then delete only dependencies proven unreachable without weakening graph-v1 Judgment Day. The branch-wide High-tier 4R runs after that extraction, before the single size-exception PR.
|
|
379
468
|
|
|
380
469
|
### Review Lens Selection (architecture reference)
|
|
381
470
|
|
|
@@ -430,13 +519,13 @@ flowchart TD
|
|
|
430
519
|
|
|
431
520
|
VALIDATE is informational. Commit, push, PR, and release commands follow ordinary repository policy; RDD never authorizes, rewrites, consumes review state for, or blocks them. Dangerous-command safety and destructive-review consent remain independent.
|
|
432
521
|
|
|
433
|
-
For the source checkout, native contract pairing is exact: this adapter resolves only the integrity-verified package-local Gentle AI v3.
|
|
522
|
+
For the source checkout, native contract pairing is exact: this adapter resolves only the integrity-verified package-local Gentle AI v3.6.1 executable, independently hashes it, then negotiates `gentle-ai.review-integration/v2` outside the repository. Capabilities are cached by that executable digest. Every START, target status, FINALIZE, validate, and BIND-SDD request passes the same contract identifier. Negotiated envelopes decode exactly against the vendored schemas; `recover` routes only the provider-selected `action_disposition`, and optional additions require a future compatible schema/minor that the provider explicitly advertises and the consumer negotiates.
|
|
434
523
|
|
|
435
524
|
Contract `/v2` replaces the Base64 `candidate_diff` reviewer transport of `/v1` with immutable `base_tree`/`candidate_tree` plus an ordered `changed_path_manifest` and never an inline patch. `gentle-pi` negotiates `/v2` only, with no dual-lane fallback; the cutover landed as one atomic commit against gentle-ai v2.2.2 (tracked by the `migrate-review-integration-v2` change), and the `/v1` schemas stay packaged because the `/v2` schemas `$ref` into their fragments. This provider contract version is unrelated to Pi's own internal "compact-v2" review-authority naming used below — the shared digit is coincidental, not a version pairing.
|
|
436
525
|
|
|
437
526
|
Target status owns `current_target`, `unrelated`, `ambiguous`, and `corrupted` applicability and returns one native action. Pi does not reconstruct ordinary authority from provider-private files or choose a lineage from repository-wide history. Restart recovery rebuilds only the derived candidate view from the native Git/content projection, including intended-untracked paths, symlinks, and immutable gitlink identities. Native failure envelopes retain their exact mutation outcome, replayability, required inputs, request digest, and next action. After an unknown or lost mutating result, Pi calls target status before any replay decision and returns only the provider-declared action.
|
|
438
527
|
|
|
439
|
-
Once the source checkout's pinned gentle-ai runtime (currently v3.
|
|
528
|
+
Once the source checkout's pinned gentle-ai runtime (currently v3.6.1) has written review authority, rollback MUST preserve every native store and receipt and MUST NOT run a downgraded binary against that repository. Disable the Pi route or roll forward to a compatible authority-aware release instead; deleting authority data or reinstalling an older binary is not a rollback path.
|
|
440
529
|
|
|
441
530
|
### FINALIZE wrapper input
|
|
442
531
|
|
|
@@ -1138,10 +1227,10 @@ tag="v${version}"
|
|
|
1138
1227
|
git fetch --no-tags origin "refs/tags/${tag}"
|
|
1139
1228
|
test "$(git rev-parse 'FETCH_HEAD^{commit}')" = "$(git rev-parse "${tag}^{commit}")"
|
|
1140
1229
|
gh workflow run publish.yml \
|
|
1141
|
-
--repo Gentleman-Programming/gentle-
|
|
1230
|
+
--repo Gentleman-Programming/gentle-shell \
|
|
1142
1231
|
--ref main \
|
|
1143
1232
|
-f tag="${tag}"
|
|
1144
|
-
gh run watch <run-id> --repo Gentleman-Programming/gentle-
|
|
1233
|
+
gh run watch <run-id> --repo Gentleman-Programming/gentle-shell --exit-status
|
|
1145
1234
|
npm view gentle-pi@<version> version --registry=https://registry.npmjs.org/
|
|
1146
1235
|
npm dist-tag ls gentle-pi --registry=https://registry.npmjs.org/
|
|
1147
1236
|
```
|
package/extensions/gentle-ai.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { consumeReviewMutation, pendingReviewMutation, recordReviewMutation } from "../lib/review-reminder-receipt.ts";
|
|
2
2
|
import { resolveSessionWorktree } from "../lib/session-worktree-registry.ts";
|
|
3
|
-
import { OddRuntimeDelegationGate } from "../lib/odd-runtime-delegation-gate.ts";
|
|
4
3
|
import { resolveResearchCapabilities, renderResearchCapabilities } from "../lib/sdd-research-capabilities.ts";
|
|
5
4
|
import { declareReviewRelayHandshake } from "../lib/review-relay-contract.ts";
|
|
6
5
|
import { execFileSync } from "node:child_process";
|
|
@@ -8,12 +7,10 @@ import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
|
8
7
|
import {
|
|
9
8
|
existsSync,
|
|
10
9
|
lstatSync,
|
|
11
|
-
mkdtempSync,
|
|
12
10
|
mkdirSync,
|
|
13
11
|
readdirSync,
|
|
14
12
|
readFileSync,
|
|
15
13
|
realpathSync,
|
|
16
|
-
rmSync,
|
|
17
14
|
writeFileSync,
|
|
18
15
|
} from "node:fs";
|
|
19
16
|
import {
|
|
@@ -23,7 +20,7 @@ import {
|
|
|
23
20
|
readdir,
|
|
24
21
|
writeFile,
|
|
25
22
|
} from "node:fs/promises";
|
|
26
|
-
import { homedir
|
|
23
|
+
import { homedir } from "node:os";
|
|
27
24
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
28
25
|
import { fileURLToPath } from "node:url";
|
|
29
26
|
import type {
|
|
@@ -197,7 +194,7 @@ import {
|
|
|
197
194
|
nativeReviewLegacyQuarantineAuthorization,
|
|
198
195
|
nativeReviewReconcileAuthorization,
|
|
199
196
|
nativeReviewRecoverAuthorization,
|
|
200
|
-
|
|
197
|
+
|
|
201
198
|
NativeReviewCliError,
|
|
202
199
|
nativeUntrackedSelection,
|
|
203
200
|
NativeReviewConsentBindingError,
|
|
@@ -2439,24 +2436,6 @@ function builtinAgentDirs(cwd: string): string[] {
|
|
|
2439
2436
|
];
|
|
2440
2437
|
}
|
|
2441
2438
|
|
|
2442
|
-
function listBuiltinAgentNames(cwd: string): Set<string> {
|
|
2443
|
-
return new Set(
|
|
2444
|
-
builtinAgentDirs(cwd).flatMap((dir) =>
|
|
2445
|
-
listAgentsFromDir(dir, "builtin").map((agent) => agent.name),
|
|
2446
|
-
),
|
|
2447
|
-
);
|
|
2448
|
-
}
|
|
2449
|
-
|
|
2450
|
-
async function listBuiltinAgentNamesAsync(cwd: string): Promise<Set<string>> {
|
|
2451
|
-
const names = new Set<string>();
|
|
2452
|
-
for (const dir of builtinAgentDirs(cwd)) {
|
|
2453
|
-
for (const agent of await listAgentsFromDirAsync(dir, "builtin")) {
|
|
2454
|
-
names.add(agent.name);
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
return names;
|
|
2458
|
-
}
|
|
2459
|
-
|
|
2460
2439
|
function listDiscoverableAgents(cwd: string): AgentEntry[] {
|
|
2461
2440
|
const builtinDirs = builtinAgentDirs(cwd);
|
|
2462
2441
|
const agents = [
|
|
@@ -2837,7 +2816,20 @@ function describeModelConfig(cwd: string, config: AgentModelConfig): string[] {
|
|
|
2837
2816
|
}
|
|
2838
2817
|
|
|
2839
2818
|
async function getPiModelOptions(ctx: ExtensionContext): Promise<string[]> {
|
|
2840
|
-
const
|
|
2819
|
+
const registry = ctx.modelRegistry;
|
|
2820
|
+
if (!registry) {
|
|
2821
|
+
return [...MODEL_CONTROL_OPTIONS];
|
|
2822
|
+
}
|
|
2823
|
+
let raw: unknown;
|
|
2824
|
+
try {
|
|
2825
|
+
raw = await registry.getAvailable();
|
|
2826
|
+
} catch {
|
|
2827
|
+
return [...MODEL_CONTROL_OPTIONS];
|
|
2828
|
+
}
|
|
2829
|
+
if (!Array.isArray(raw)) {
|
|
2830
|
+
return [...MODEL_CONTROL_OPTIONS];
|
|
2831
|
+
}
|
|
2832
|
+
const models = raw as { provider: string; id: string }[];
|
|
2841
2833
|
const modelIds = models
|
|
2842
2834
|
.map((model) => normalizeModelId(`${model.provider}/${model.id}`))
|
|
2843
2835
|
.filter((model): model is string => model !== undefined)
|
|
@@ -4164,7 +4156,14 @@ async function switchLiveOrchestrator(ctx: ExtensionContext, live: LiveSession,
|
|
|
4164
4156
|
const reference = parseOrchestratorModelRef(entry.model);
|
|
4165
4157
|
if (reference === undefined) return "";
|
|
4166
4158
|
const label = `${reference.provider}/${reference.model}`;
|
|
4167
|
-
const
|
|
4159
|
+
const registry = ctx.modelRegistry;
|
|
4160
|
+
if (!registry) {
|
|
4161
|
+
if (ctx.hasUI && ctx.ui.notify) {
|
|
4162
|
+
ctx.ui.notify("Model registry unavailable; this session keeps its current model.", "warning");
|
|
4163
|
+
}
|
|
4164
|
+
return `\nModel registry unavailable; this session keeps its current model.`;
|
|
4165
|
+
}
|
|
4166
|
+
const model = registry.find(reference.provider, reference.model);
|
|
4168
4167
|
if (model === undefined) return `\n${label} is not in the model catalog; this session keeps its current model.`;
|
|
4169
4168
|
let switched = false;
|
|
4170
4169
|
try {
|
|
@@ -5145,14 +5144,6 @@ function isReviewTransition(value: string): value is ReviewTransition {
|
|
|
5145
5144
|
return Object.values(REVIEW_TRANSITION).some((transition) => transition === value);
|
|
5146
5145
|
}
|
|
5147
5146
|
|
|
5148
|
-
function isGraphV1JudgmentDayLineage(cwd: string, lineageId: string): boolean {
|
|
5149
|
-
try {
|
|
5150
|
-
return ReviewTransactionStore.forRepository(cwd).read(lineageId).mode === REVIEW_MODE.JUDGMENT_DAY;
|
|
5151
|
-
} catch {
|
|
5152
|
-
return false;
|
|
5153
|
-
}
|
|
5154
|
-
}
|
|
5155
|
-
|
|
5156
5147
|
interface NativeStartPreAuthorityRejection {
|
|
5157
5148
|
lineage_created: false;
|
|
5158
5149
|
mutation_performed: false;
|
|
@@ -8699,6 +8690,9 @@ export const __testing = {
|
|
|
8699
8690
|
readSddChangeFlag,
|
|
8700
8691
|
resetTelemetryTriggerGuardForTesting,
|
|
8701
8692
|
createGentleAiExtension: createGentleAiExtensionForTesting,
|
|
8693
|
+
getPiModelOptions,
|
|
8694
|
+
MODEL_CONTROL_OPTIONS,
|
|
8695
|
+
switchLiveOrchestrator,
|
|
8702
8696
|
};
|
|
8703
8697
|
|
|
8704
8698
|
export interface GentleAiRuntimeDependencies {
|
|
@@ -8759,11 +8753,6 @@ function createGentleAiExtensionForTesting(
|
|
|
8759
8753
|
const candidateViews = dependencies.candidateViews === undefined ? new CandidateViewRegistry() : dependencies.candidateViews;
|
|
8760
8754
|
const herdrLifecycle = createHerdrConfirmationLifecycle(pi.events);
|
|
8761
8755
|
const permissionEnvironment = dependencies.processEnv ?? process.env;
|
|
8762
|
-
const oddDelegationGate = new OddRuntimeDelegationGate();
|
|
8763
|
-
const oddSessionId = (ctx: ExtensionContext): string => {
|
|
8764
|
-
try { return ctx.sessionManager.getSessionId(); }
|
|
8765
|
-
catch { return ""; }
|
|
8766
|
-
};
|
|
8767
8756
|
|
|
8768
8757
|
const setReviewSessionPermissionStatus = (context: ExtensionContext, active: boolean): void => {
|
|
8769
8758
|
try {
|
|
@@ -9169,10 +9158,6 @@ function createGentleAiExtensionForTesting(
|
|
|
9169
9158
|
const retiredSync = readAgentStartNames(event).includes("sdd-sync") || /\bSDD sync executor\b/i.test(event.systemPrompt ?? "");
|
|
9170
9159
|
const isSddAgent = retiredSync || isSddAgentStartEvent(event);
|
|
9171
9160
|
const isNamedAgent = isNamedAgentStartEvent(event);
|
|
9172
|
-
oddDelegationGate.start(
|
|
9173
|
-
oddSessionId(ctx),
|
|
9174
|
-
!isNamedAgent && !isSddAgent && permissionEnvironment.GENTLE_PI_AGENTS_CHILD !== "1",
|
|
9175
|
-
);
|
|
9176
9161
|
const subagentDepthKey = pendingReviewConsentSessionKey(ctx, pendingReviewConsentFallbackKey);
|
|
9177
9162
|
if (isSddAgent || isNamedAgent) {
|
|
9178
9163
|
processAgentEndSubagentDepth.set(subagentDepthKey, (processAgentEndSubagentDepth.get(subagentDepthKey) ?? 0) + 1);
|
|
@@ -9282,7 +9267,6 @@ function createGentleAiExtensionForTesting(
|
|
|
9282
9267
|
// consent, or chooses a partial candidate. Durable own-mutation receipts
|
|
9283
9268
|
// gate STATUS and consume only the generation captured before that await.
|
|
9284
9269
|
pi.on("agent_end", async (_event, ctx) => {
|
|
9285
|
-
oddDelegationGate.endChild(oddSessionId(ctx));
|
|
9286
9270
|
if (nativeReviewCli?.reviewMode === undefined || nativeReviewCli.targetStatus === undefined) return;
|
|
9287
9271
|
if (ctx.hasUI !== true || !reminderSessionActive) return;
|
|
9288
9272
|
const sessionKey = pendingReviewConsentSessionKey(ctx, pendingReviewConsentFallbackKey);
|
|
@@ -9318,7 +9302,6 @@ function createGentleAiExtensionForTesting(
|
|
|
9318
9302
|
pi.on("tool_result", (event, ctx) => {
|
|
9319
9303
|
if (!reminderSessionActive || event.isError !== false || (event.toolName !== "write" && event.toolName !== "edit")) return;
|
|
9320
9304
|
if (!isRecord(event.input) || typeof event.input.path !== "string" || !event.input.path.trim()) return;
|
|
9321
|
-
oddDelegationGate.recordSuccess(oddSessionId(ctx), event.toolName, event.input, ctx.cwd);
|
|
9322
9305
|
try {
|
|
9323
9306
|
const root = resolveSessionWorktree(event.input.path, ctx.cwd)?.root;
|
|
9324
9307
|
if (root) recordReviewMutation(pi, ctx.sessionManager, root, { source: "direct", toolName: event.toolName, toolCallId: event.toolCallId });
|
|
@@ -9332,10 +9315,6 @@ function createGentleAiExtensionForTesting(
|
|
|
9332
9315
|
event.input,
|
|
9333
9316
|
);
|
|
9334
9317
|
if (sensitivePathDenied) return sensitivePathDenied;
|
|
9335
|
-
const oddDelegationDenied = oddDelegationGate.beforeTool(
|
|
9336
|
-
oddSessionId(ctx), event.toolName, event.input, ctx.cwd, readActiveToolNames(pi),
|
|
9337
|
-
);
|
|
9338
|
-
if (oddDelegationDenied) return oddDelegationDenied;
|
|
9339
9318
|
if (event.toolName === "subagent_run") {
|
|
9340
9319
|
const sddAgent = sddDispatchAgentName(event.input);
|
|
9341
9320
|
if (sddAgent === "invalid") {
|