gentle-pi 4.4.0 → 4.5.1

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.
Files changed (47) hide show
  1. package/README.md +2 -2
  2. package/bin/gentle-shell.mjs +37 -4
  3. package/docs/gentle-music.md +28 -4
  4. package/docs/gentle-shell.md +5 -5
  5. package/docs/readme-reference.md +61 -46
  6. package/extensions/gentle-agents.ts +14 -2
  7. package/extensions/gentle-ai.ts +1677 -1240
  8. package/extensions/gentle-music.ts +83 -8
  9. package/extensions/gentle-shell.ts +88 -8
  10. package/lib/command-palette-catalog.ts +1 -2
  11. package/lib/gentle-shell-bundled.ts +18 -8
  12. package/lib/gentle-shell-launcher.ts +2 -0
  13. package/lib/jobs-sidebar-state.ts +22 -3
  14. package/lib/jobs-widget.ts +112 -0
  15. package/lib/session-profile-freeze.ts +2 -2
  16. package/lib/shell-bar.ts +2 -4
  17. package/lib/shell-sidebar-layout.ts +1 -1
  18. package/lib/vim-editor-adapter.ts +3 -2
  19. package/package.json +1 -1
  20. package/runtime/gentle-shell-bundled.mjs +18 -8
  21. package/runtime/gentle-shell-launcher.mjs +2 -0
  22. package/scripts/bundled-install.mjs +209 -13
  23. package/scripts/installer-downloads.mjs +24 -0
  24. package/scripts/installer-preflight.mjs +1 -1
  25. package/scripts/pi-update.mjs +3 -1
  26. package/tests/bundled-install.test.ts +273 -2
  27. package/tests/command-palette.test.ts +3 -3
  28. package/tests/design-canvas-server.test.ts +6 -2
  29. package/tests/fixtures/primary-session-prompt/gentleman.golden.md +1 -1
  30. package/tests/gentle-agents.test.ts +4 -4
  31. package/tests/gentle-ai.test.ts +810 -609
  32. package/tests/gentle-music.test.ts +153 -1
  33. package/tests/gentle-shell-bundled.test.ts +20 -0
  34. package/tests/gentle-shell-launcher.test.ts +1 -0
  35. package/tests/gentle-shell.test.ts +44 -19
  36. package/tests/installer-downloads.test.ts +32 -0
  37. package/tests/installer-server.test.ts +2 -2
  38. package/tests/jobs-widget.test.ts +138 -0
  39. package/tests/model-profiles-preservation.test.ts +2 -2
  40. package/tests/package-manifest.test.ts +2 -2
  41. package/tests/persona-reply-language.test.ts +42 -0
  42. package/tests/pi-update.test.ts +5 -0
  43. package/tests/runtime-harness.mjs +188 -211
  44. package/tests/session-profile-freeze.test.ts +6 -4
  45. package/tests/shell-bar.test.ts +4 -4
  46. package/tests/shell-sidebar-layout.test.ts +17 -0
  47. package/tests/vim-editor-adapter.test.ts +2 -2
package/README.md CHANGED
@@ -381,7 +381,7 @@ The first-party `nan` provider is included; no third-party provider package is r
381
381
  - **Streaming:** Pi streams chat completions through its OpenAI-compatible provider.
382
382
  - **Model discovery:** Model discovery intersects NaN's authenticated `/v1/models` response with a maintained subset of known chat IDs from the [official model documentation](https://nan.builders/docs/models); unknown and non-chat IDs are omitted. A successful response with no known chat IDs stays empty. Documented context, reasoning, and text/image capabilities are preserved with conservative numeric bounds for abbreviated limits; audio input is not advertised by Pi.
383
383
  - **Output caps:** The provider configures `maxTokens` caps rather than claiming undocumented model maxima: 32,768 for GLM 5.3, GLM 5.3 Flash, and MiMo; 16,384 for DeepSeek V4 Flash; 65,536 for Gemma 4 and Qwen 3.6; and 131,000 for Qwen 3.8 Flash (NaN documents 131K). Reasoning shares the output budget.
384
- - **Offline fallback:** Before a successful refresh, all seven documented chat models are available as the offline fallback in `/gentle:models`: `glm5.3`, `deepseek-v4-flash`, `glm5.3-flash`, `qwen3.8-flash`, `mimo-v2.6-flash`, `gemma4`, and `qwen3.6`. This fallback declares documented support, not proof of access for your key. Once refreshed, the successful live key-scoped list remains authoritative (including an empty list), even offline or after a failed refresh. Changing credentials resets the catalog to the full documented fallback until discovery succeeds for the new key.
384
+ - **Offline fallback:** Before a successful refresh, all seven documented chat models are available as the offline fallback in `/gentle:profiles`: `glm5.3`, `deepseek-v4-flash`, `glm5.3-flash`, `qwen3.8-flash`, `mimo-v2.6-flash`, `gemma4`, and `qwen3.6`. This fallback declares documented support, not proof of access for your key. Once refreshed, the successful live key-scoped list remains authoritative (including an empty list), even offline or after a failed refresh. Changing credentials resets the catalog to the full documented fallback until discovery succeeds for the new key.
385
385
  - **Not included:** NaN MCP search and media bridges are not included.
386
386
 
387
387
  Thinking levels follow NaN's [reasoning contract](https://nan.builders/docs/models):
@@ -585,7 +585,7 @@ gentle-shell upgrade --rollback
585
585
  - **Main channel:** On the main channel it rebuilds Gentle AI and Gentle Shell from the latest `main` commits (only what changed) and needs pnpm and Go (a Go older than the minimum is replaced, for the build only, by the pinned Go the installer left in its own folder); switching back to release restores the pinned Gentle AI binary. When the Gentle AI build fails but Gentle Shell moved, the new Gentle Shell is still installed and the command exits with an error asking you to run `gentle-shell upgrade --channel main` again. The retry rebuilds Gentle AI with the new Shell's code, and finishes once the cause of the original error is resolved.
586
586
  - **Your Pi:** With pnpm 11, the updated Gentle Shell may run a newer Pi that pnpm puts next to it (always at least the minimum); your own Pi is never changed.
587
587
  - **Bundled install:** A bundled install (the web installer's own Node, pnpm and Pi) follows the release channel only: it installs the new version beside the current one, verifies it, then switches, keeping the previous version for `--rollback`. On release, once the latest release publishes the bundled distribution, `upgrade` moves a global pnpm or npm installation to the bundled one and asks once before removing the old global `gentle-pi`.
588
- - **`update` vs `upgrade`:** `gentle-shell update` is a different command: it is Pi's own package update. In a bundled install Pi ships pinned with Gentle Shell, so updating Pi itself runs `gentle-shell upgrade` instead.
588
+ - **`update` vs `upgrade`:** `gentle-shell update` is a different command: it is Pi's own package update. In a bundled install Pi ships pinned with Gentle Shell, so updating Pi itself runs `gentle-shell upgrade` instead. `gentle-shell update --latest` (also with `pi`, `self`, `--self` or `--all`) installs the newest published stable Pi with the active Gentle Shell instead, without the release's verified lockfile: it first warns that Gentle Shell features relying on audited Pi internals (such as the Vim editor) may turn off until a Gentle Shell release verifies that Pi, then verifies and switches to it like `upgrade`, keeping the previous version for `gentle-shell upgrade --rollback`. Afterwards `gentle-shell upgrade` never downgrades that Pi: a release of the same Gentle Shell with an older verified Pi changes nothing, and a newer Gentle Shell release upgrades as usual.
589
589
 
590
590
  </details>
591
591
 
@@ -1684,6 +1684,32 @@ async function handleBundledUpgrade(bundled, commandArgs) {
1684
1684
  }
1685
1685
  }
1686
1686
 
1687
+ // `gentle-shell update --latest` in a bundled install: the newest published
1688
+ // Pi installed with this Gentle Shell beside the active version, without the
1689
+ // release's verified lockfile (after a warning), verified, then `current`
1690
+ // switched. Returns the exit code like handleBundledUpgrade; a failure leaves
1691
+ // `current` as it was.
1692
+ async function handleBundledPiLatest(bundled) {
1693
+ const { prefixLayout, publishedPi, upgradePiLatest } = await import("../scripts/bundled-install.mjs");
1694
+ const layout = prefixLayout({ platform: process.platform, env: process.env, home: homedir(), root: bundled.root });
1695
+ try {
1696
+ await upgradePiLatest({
1697
+ layout,
1698
+ home: homedir(),
1699
+ shell: ownPackageVersion(),
1700
+ platform: process.platform,
1701
+ arch: process.arch,
1702
+ env: process.env,
1703
+ adapters: { latestPi: () => publishedPi(layout, { platform: process.platform, env: process.env }) },
1704
+ out: (line) => process.stdout.write(`${line}\n`),
1705
+ });
1706
+ return 0;
1707
+ } catch (error) {
1708
+ process.stderr.write(`gentle-shell update: ${error.message}${error.stderr ? `\n${error.stderr}` : ""}\n`);
1709
+ return 1;
1710
+ }
1711
+ }
1712
+
1687
1713
  // The bundled npm as the `npmCommand` of a home Gentle Shell owns (the isolated
1688
1714
  // home, or a --home it created or provisioned; never --link or a foreign home),
1689
1715
  // so Pi installs packages with our npm while its tools keep the user's PATH.
@@ -1933,14 +1959,21 @@ async function main() {
1933
1959
 
1934
1960
  // S5: in a bundled install Pi updates only with Gentle Shell, so Pi's own
1935
1961
  // update becomes `gentle-shell upgrade` (run when interactive, named
1936
- // otherwise); package updates still reach Pi.
1962
+ // otherwise), or with `--latest` the newest published Pi without the
1963
+ // release's lockfile (run even when not interactive: it was asked for);
1964
+ // package updates still reach Pi.
1937
1965
  let passthrough = args.passthrough;
1938
1966
  if (bundled && args.piSubcommand === "update") {
1939
1967
  const plan = bundledUpdatePlan(args.passthrough);
1940
1968
  if (plan.self) {
1941
- const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true;
1942
- for (const line of bundledUpdateNotice({ piVersion: versionCheck.version, interactive })) process.stdout.write(`${line}\n`);
1943
- const code = interactive ? await handleBundledUpgrade(bundled, []) : undefined;
1969
+ let code;
1970
+ if (plan.latest) {
1971
+ code = await handleBundledPiLatest(bundled);
1972
+ } else {
1973
+ const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true;
1974
+ for (const line of bundledUpdateNotice({ piVersion: versionCheck.version, interactive })) process.stdout.write(`${line}\n`);
1975
+ code = interactive ? await handleBundledUpgrade(bundled, []) : undefined;
1976
+ }
1944
1977
  const next = bundledUpdateNext(plan, code);
1945
1978
  if ("exitCode" in next) process.exit(next.exitCode);
1946
1979
  if (next.note) process.stderr.write(`${next.note}\n`);
@@ -42,20 +42,44 @@ When you quit Pi after using `/gentle:music`, Gentle Shell runs `gentle-stream c
42
42
 
43
43
  ## Installation
44
44
 
45
- Gentle Shell looks for gentle-stream in this order:
45
+ gentle-stream runs on macOS and Linux, including Gentle Shell inside WSL. It needs POSIX features native Windows lacks (file locks, a Unix control socket, process groups), so on native Windows `/gentle:music` only says it is not available yet: it never offers, installs, or runs the player.
46
+
47
+ On macOS and Linux, Gentle Shell looks for gentle-stream in this order:
46
48
 
47
49
  1. `gentle-stream` on `PATH`.
48
- 2. `uvx`, running gentle-stream straight from its repository (the first run downloads it).
49
- 3. `uv`, through `uv tool run`.
50
+ 2. In a bundled Gentle Shell, the gentle-stream it installed inside its own folder (see below).
51
+ 3. `uvx`, running gentle-stream straight from its repository (the first run downloads it).
52
+ 4. `uv`, through `uv tool run`.
50
53
 
51
- With none of them, `/gentle:music` explains how to install and does nothing else. Install [uv](https://docs.astral.sh/uv/getting-started/installation/), or install gentle-stream permanently:
54
+ With none of them, a pnpm or npm install of Gentle Shell explains how to install and does nothing else. Install [uv](https://docs.astral.sh/uv/getting-started/installation/), or install gentle-stream permanently:
52
55
 
53
56
  ```bash
54
57
  uv tool install 'gentle-stream[samples] @ git+https://github.com/Gentleman-Programming/gentle-stream'
55
58
  ```
56
59
 
60
+ ### Installed by a bundled Gentle Shell
61
+
62
+ On macOS and Linux, a bundled Gentle Shell (the one the web installer sets up) offers to install the player for you the first time `/gentle:music` finds none:
63
+
64
+ > Install the gentle-stream music player inside Gentle Shell? It downloads uv, Python 3.13 and the player (about 150 MB) into the Gentle Shell folder.
65
+
66
+ Decline and you get the manual hint above; nothing is downloaded. Accept and Gentle Shell shows its progress, installs the player, and starts the music. Later runs use it directly, with no question.
67
+
68
+ Everything stays inside the Gentle Shell folder (`~/.gentle-shell`). Nothing is added to your `PATH`, and your own Python, uv, and uv settings are never used or changed:
69
+
70
+ | What | Where | Size on disk |
71
+ | :-- | :-- | :-- |
72
+ | [uv](https://github.com/astral-sh/uv) 0.13.6, the release archive pinned by sha256 and checked before use | `runtime/uv-0.13.6/` | about 30 MB |
73
+ | Python 3.13, managed by that uv | `music/python/` | about 70 MB |
74
+ | gentle-stream, from a pinned commit archive of its repository (git is not needed) | `music/tools/`, run from `music/bin/gentle-stream` | about 110 MB |
75
+
76
+ uv's download cache (`music/cache/`) is emptied after the install. Removing the bundled install removes all of it.
77
+
78
+ The player is installed with its `samples` extra (pyfluidsynth). If that extra cannot be installed, Gentle Shell says so and installs the player without it. Either way, the sampled instruments need the FluidSynth library from your system (for example `brew install fluid-synth` or `apt install libfluidsynth3`) and the soundfont `gentle-stream setup-samples` downloads; without them gentle-stream plays its synthesized instruments.
79
+
57
80
  ## Limits
58
81
 
82
+ - Native Windows is not supported yet (see [Installation](#installation)); use WSL.
59
83
  - The player needs the interactive terminal. In RPC, JSON, and print modes, `/gentle:music` shows a notice and does nothing.
60
84
  - There is no default shortcut.
61
85
  - gentle-stream starts its own `gentle-shell` process to interpret moods; Gentle Shell does not pass its own launcher path.
@@ -10,7 +10,7 @@ Source map: [shell extension](../extensions/gentle-shell.ts), [shell bar](../lib
10
10
 
11
11
  Run `gentle-shell update` to update the Pi this launcher runs. It also works when that Pi is older than the required minimum version, which every other command refuses. Shell verifies npm/pnpm global listing JSON and canonical package roots before installing exactly one supplying package. This supports pnpm v11 linked stores without relying on `PNPM_HOME` containment. When the launcher runs its adjacent Pi peer, the supplier is the listed `gentle-pi` package itself: Shell is reinstalled at its current version so the package manager re-resolves the peer, because updating a separate global Pi never changes the peer a Shell install runs. A main-channel Shell (`<version>-main.<sha12>`) is never published, so it is reinstalled from the tarball `gentle-shell upgrade --channel main` packed under `<config home>/main/packages/` (an absolute `GENTLE_PI_CONFIG_HOME`, else `~/.pi/gentle-ai`; a relative override is refused); when that tarball is missing, `update` installs nothing and names the rebuild path (`gentle-shell upgrade --channel release`, then `--channel main`), because `upgrade --channel main` alone is a no-op when already at the latest main. A Pi found on `PATH` or through `GENTLE_SHELL_PI` is updated through its own listed global package; a package-manager shim is followed to the single `cli.js` it names. Pi and Shell are never combined into a comma install.
12
12
 
13
- Unknown or ambiguous ownership, npm source/link packages, and pnpm groups shared with other top-level packages refuse without installation changes. Separate groups in one aggregated listing are allowed. After the install, Shell reads the supplied Pi version back from a fresh listing and reports whether it changed. pnpm installs only releases older than its `minimumReleaseAge` setting (pnpm 11 defaults to 4320 minutes), so a very recent Pi release is picked up once it is old enough; Shell reports that rule instead of overriding it. Package managers run from the system temporary directory so a project's `packageManager` pin never changes which pnpm answers. Use the supplying package manager for source checkouts or unsupported layouts. `gentle-shell update --extensions-only` remains an extension-only Pi command; prefix-bundled Pi still updates with `gentle-shell upgrade`. See [managing packages](readme-reference.md#managing-packages) for the ownership contract.
13
+ Unknown or ambiguous ownership, npm source/link packages, and pnpm groups shared with other top-level packages refuse without installation changes. Separate groups in one aggregated listing are allowed. After the install, Shell reads the supplied Pi version back from a fresh listing and reports whether it changed. pnpm installs only releases older than its `minimumReleaseAge` setting (pnpm 11 defaults to 4320 minutes), so a very recent Pi release is picked up once it is old enough; Shell reports that rule instead of overriding it. Package managers run from the system temporary directory so a project's `packageManager` pin never changes which pnpm answers. Use the supplying package manager for source checkouts or unsupported layouts. `gentle-shell update --extensions-only` remains an extension-only Pi command; prefix-bundled Pi still updates with `gentle-shell upgrade`, or with `gentle-shell update --latest` to the newest published Pi without the release's verified lockfile (after a warning that features relying on audited Pi internals, such as the Vim editor, may turn off; `upgrade` never downgrades it afterwards). See [managing packages](readme-reference.md#managing-packages) for the ownership contract.
14
14
 
15
15
  When the install fails, `update` names the package manager and the exact command it ran, then one sanitized line of that manager's own output. That line is the last error line (`Error:` or an `ERR_` code) in its stdout or stderr, stderr's when both have one, because npm prints its error on stderr while pnpm 11 prints `ERR_PNPM_*` on stdout. A warning on stderr never hides that error. Without any error line, it is the last line of stderr, else of stdout. Terminal escapes and control characters are removed, your home directory becomes `~`, and the line is at most 300 characters. For example:
16
16
 
@@ -45,7 +45,7 @@ At 140 columns or wider, fullscreen defaults to a live header over a transcript-
45
45
  ```
46
46
 
47
47
  - The header carries live session data: session identity on the left (brand, cwd, branch, dirty count, model · effort · profile) and the two live counters right-aligned (the context gauge and session cost). The effort reads `auto` for NaN models that manage their reasoning depth themselves (DeepSeek V4 Flash, Qwen 3.8 Flash, MiMo), with a one-time notice explaining that the selected level does not change the depth. It never shows the working/thinking state or extension statuses — those stay in the prompt indicator and the Status surface. When the terminal is too narrow for everything, segments give way in a fixed order — profile, then effort, then the whole cwd/branch/dirty group — before the counters are touched; below that, only the brand survives, and below that the header renders nothing.
48
- - The right rail scrolls **Status → Changes → Agents → TODO**. Cards cache their rendered content until their own digest or an explicit invalidation changes. `neon` keeps the rounded border and accent title; `float` uses a tone background, a full-height `▎` accent, one-column transparent margins, painted padding above and below, and a blank painted separator between heading and body. Warning/error/success panels retain their tone colors.
48
+ - The right rail scrolls **Status → Jobs → Agents → TODO**. Cards cache their rendered content until their own digest or an explicit invalidation changes. `neon` keeps the rounded border and accent title; `float` uses a tone background, a full-height `▎` accent, one-column transparent margins, painted padding above and below, and a blank painted separator between heading and body. Warning/error/success panels retain their tone colors.
49
49
  - The Status card carries Project (cwd, branch, session name, active profile), Changes, and Integrations (other extensions' statuses); its live digest detects changes without requiring an event. Model, effort, context, cost, and the per-model usage table live in the header instead — the header ticks every frame, so duplicating them in a card would just make that card repaint every frame too.
50
50
  - Gentle Agents follows TODO: while the fullscreen rail is showing, the Agents card moves into it and lists every shown task; without the rail (narrow fullscreen or regular mode) it stays above the editor. Its card has fixed right-aligned columns for `model · effort`, tokens, cost, and elapsed, each sized to the widest value among the shown tasks — so the numbers line up vertically even when one row's values are much shorter than another's. A queued task fills only the elapsed column with the word `queued`, leaving the other columns blank rather than overwriting the row.
51
51
  - The sidebar reuses its last frame until something it paints changes, so silent frames stay cheap; a per-section cache means one card's changing digest (or the header's) never forces an unrelated card to redraw.
@@ -245,7 +245,7 @@ Gentle Shell ships its own interactive tools instead of depending on third-party
245
245
 
246
246
  ### Gentle Agents
247
247
 
248
- The current package requires Pi 1.2.0 or newer and Node >=22.19.0. Development tests resolve Pi through the open `>=1.0.0` development range. The private Vim editor adapter admits only the audited Pi `0.99.1`, `0.99.2`, `1.0.0`, `1.1.0`, and `1.2.0` releases; any other release keeps ordinary prompt editing until its editor is audited. 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.
248
+ The current package requires Pi 1.2.0 or newer and Node >=22.19.0. Development tests resolve Pi through the open `>=1.0.0` development range. The private Vim editor adapter admits only the audited Pi `0.99.1`, `0.99.2`, `1.0.0`, `1.1.0`, `1.2.0`, `1.3.0`, and `1.4.0` releases; any other release keeps ordinary prompt editing until its editor is audited. 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.
249
249
 
250
250
  The `subagent_*` tools and the agents card replace the third-party subagents package (remove `npm:pi-subagents-j0k3r` from your pi packages; while it is still installed the tools stay unregistered and a warning says so at startup). Agent definitions and settings are the ones you already have: markdown agents in `~/.pi/agent/agents/`, `~/.pi/agent/subagents/`, `<cwd>/.pi/agents/`, `<cwd>/.pi/subagents/` (project beats global, `subagents/` beats `agents/`), and `subagents.json` at the global and project level (`default_model`, `default_effort`, `default_mode`, `model_profiles`, `stall_timeout_ms`, `tool_stall_timeout_ms`, `max_concurrency`, `history_max_tasks`).
251
251
 
@@ -308,7 +308,7 @@ until curl -sf localhost:3000/health; do sleep 1; done # server is up
308
308
  - While the orchestrator has background work running (a subagent task or a background job), a `bash` call that only sleeps (`sleep 10`, `sleep 2m`, or several such sleeps joined by `&&`/`;`) is refused with a message to end the turn, because each completion already arrives as its own message. Wait conditions such as `until ...; do sleep 1; done`, sleeps mixed with real commands, `bash_background`, and `monitor` are never refused, and `sleep` is unrestricted when nothing runs in the background.
309
309
  - Jobs live in memory, belong to the session that started them, and are stopped when the session shuts down. At most 25 run at once.
310
310
  - `monitor` is a background job whose every output line is an event, delivered while the command keeps running — for example `tail -f app.log | grep --line-buffered ERROR`, or a loop that prints each CI check as it finishes. Lines within 200 ms share one notice of at most 20 lines (the rest are counted, and stay in the log); notices waiting for a busy agent coalesce. `timeout_seconds` is mandatory (1–1800): the monitor is stopped when it expires and reports its event count. One that prints more than 120 lines within 60 seconds is stopped as a flood, so filter its output. When the command exits, a final notice reports the exit code and event count. It goes through the same command guards as `bash`.
311
- - `/gentle:jobs` opens a full-terminal overlay with this session's jobs beside the selected job's command, status, output file, and live output tail; `↑`/`↓` or `j`/`k` move, `s` stops the selected running job, `q` closes (narrow terminals: `Tab` toggles details, `Escape` goes back). The footer shows `⧗ N jobs` while any run.
311
+ - `/gentle:jobs` opens a full-terminal overlay with this session's jobs beside the selected job's command, status, output file, and live output tail; `↑`/`↓` or `j`/`k` move, `s` stops the selected running job, `q` closes (narrow terminals: `Tab` toggles details, `Escape` goes back). The footer shows `⧗ N jobs` while any run. In fullscreen at 140 columns or wider, running jobs also get their own collapsible sidebar card (`⧗ Jobs`), one row per job with its elapsed time, newest first; it paints nothing only while nothing is current — no running jobs and no finished result still inside its 60-second tail, the Status card never repeats it, and `ctrl+shift+j` (or clicking the card's header) collapses it; `GENTLE_PI_JOBS_KEY` remaps the shortcut, with an empty value or `off` disabling it.
312
312
 
313
313
  ### Gentle Todo
314
314
 
@@ -397,7 +397,7 @@ The canvas server binds `127.0.0.1` on a random port and needs a random per-serv
397
397
 
398
398
  ### Gentle Music
399
399
 
400
- `/gentle:music [mood]` hands the terminal to the [gentle-stream](https://github.com/Gentleman-Programming/gentle-stream) player, like Pi's external editor, passing the mood and the session model. In the player, `b` returns to Pi with the music playing and `q` returns and stops it. `/gentle:music next`, `pause`, `play`, `stop`, and `status` control the music without leaving Pi. Quitting Pi after using the command stops the music. gentle-stream runs from `PATH`, or through `uvx`/`uv` when it is not installed. See [Gentle Music](gentle-music.md).
400
+ `/gentle:music [mood]` hands the terminal to the [gentle-stream](https://github.com/Gentleman-Programming/gentle-stream) player, like Pi's external editor, passing the mood and the session model. In the player, `b` returns to Pi with the music playing and `q` returns and stops it. `/gentle:music next`, `pause`, `play`, `stop`, and `status` control the music without leaving Pi. Quitting Pi after using the command stops the music. gentle-stream runs from `PATH`, or through `uvx`/`uv` when it is not installed. On native Windows the player is not available yet (it needs POSIX; WSL works), so the command only says so. A bundled Gentle Shell on macOS or Linux without any of them offers, with your consent, to install the player inside its own folder: a pinned, checksum-verified uv 0.13.6 in `runtime/`, and the uv-managed Python 3.13 and gentle-stream in `music/` (about 150 MB to download, about 210 MB on disk). A pnpm or npm install keeps the manual install hint. See [Gentle Music](gentle-music.md).
401
401
 
402
402
  ### Bridge providers
403
403
 
@@ -222,7 +222,7 @@ This checkout declares `gentle-pi` `4.0.0` with a package-local Gentle AI `v4.3.
222
222
 
223
223
  ### Pi compatibility
224
224
 
225
- The current package requires Pi 1.2.0 or newer and Node >=22.19.0. Development tests resolve Pi through the open `>=1.0.0` development range. The private Vim editor adapter admits only the audited Pi `0.99.1`, `0.99.2`, `1.0.0`, `1.1.0`, and `1.2.0` releases; any other release keeps ordinary prompt editing until its editor is audited. 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.
225
+ The current package requires Pi 1.2.0 or newer and Node >=22.19.0. Development tests resolve Pi through the open `>=1.0.0` development range. The private Vim editor adapter admits only the audited Pi `0.99.1`, `0.99.2`, `1.0.0`, `1.1.0`, `1.2.0`, `1.3.0`, and `1.4.0` releases; any other release keeps ordinary prompt editing until its editor is audited. 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.
226
226
 
227
227
  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`, 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.
228
228
 
@@ -304,6 +304,7 @@ gentle-shell home [link|isolated|<path>]
304
304
  gentle-shell [home selectors] setup [--dry-run]
305
305
  gentle-shell upgrade [--channel release|main]
306
306
  gentle-shell upgrade --rollback
307
+ gentle-shell update [pi|self|--self|--all] --latest
307
308
  gentle-shell self-uninstall [--dry-run] [--yes] [--include-shared]
308
309
  ```
309
310
 
@@ -382,7 +383,7 @@ Tradeoff: `--link` shares one credential store with pi; `import-pi` duplicates i
382
383
 
383
384
  After a successful install, Shell reads the supplied Pi version back from a fresh global listing and reports it, naming the previous version when it changed. An unchanged pnpm result also names pnpm's `minimumReleaseAge` setting: pnpm installs only releases older than that age (pnpm 11 defaults to 4320 minutes), so a very recent Pi release is picked up once it is old enough; Shell reports the rule rather than overriding it. Both the listing and the install run from the system temporary directory, so a project's `packageManager` pin in the current directory never changes which pnpm answers. When the version cannot be read back, the output says so and points at `gentle-shell --version`.
384
385
 
385
- Prefix-bundled installations keep their existing `gentle-shell upgrade` route for Pi. `update --all` updates the verified owner before forwarding extension updates; an ownership refusal stops without mutation. Shell's `upgrade` and `self-uninstall` ownership behavior is unchanged.
386
+ Prefix-bundled installations keep their existing `gentle-shell upgrade` route for Pi, or `gentle-shell update --latest` for the newest published Pi without the release's verified lockfile (see [`upgrade`](#upgrade-subcommand-and-channels)). Outside a bundled install `--latest` changes nothing: `update` already installs the latest Pi. `update --all` updates the verified owner before forwarding extension updates; an ownership refusal stops without mutation. Shell's `upgrade` and `self-uninstall` ownership behavior is unchanged.
386
387
 
387
388
  ### `setup` subcommand
388
389
 
@@ -555,6 +556,7 @@ every `update` form that updates Pi itself explains that and runs
555
556
  `gentle-shell upgrade` (in a terminal) or names it. Those forms are no target,
556
557
  `--self`, `self` or `pi`, `--all`, and `self` or `pi` with `--extensions`. The
557
558
  extension part of an "all" form, and every package update, still reaches Pi.
559
+ `gentle-shell update --latest` (also with `pi`, `self`, `--self` or `--all`) installs the newest published stable Pi with the active Gentle Shell instead, without the release's verified lockfile: it first warns that Gentle Shell features relying on audited Pi internals (such as the Vim editor) may turn off until a Gentle Shell release verifies that Pi, then verifies and switches to it like `upgrade`, keeping the previous version for `gentle-shell upgrade --rollback`. Afterwards `gentle-shell upgrade` never downgrades that Pi: a release of the same Gentle Shell with an older verified Pi changes nothing, and a newer Gentle Shell release upgrades as usual. When the active Pi is already newer than the published `latest` (the dist-tag moved back), `--latest` changes nothing. Outside a bundled install, `gentle-shell update --latest` is the ordinary managed Pi update, which already installs the newest published Pi.
558
560
  In a bundled install Pi and the agent's commands keep your own environment;
559
561
  Pi installs packages with Gentle Shell's npm through the `npmCommand` setting
560
562
  of its own home. Details: [Bundled install](bundled-install.md#launcher-in-a-bundled-install).
@@ -657,8 +659,7 @@ Synthetic tests prove the observer's behavior, not the cause of issue #213. A ne
657
659
  ```text
658
660
  /gentle:status Check package assets and global model config.
659
661
  /gentle:doctor Run read-only diagnostics for assets, config, tools, guards, and Herdr setup.
660
- /gentle:models Assign global agent model/effort routing and choose a profile orchestrator.
661
- /gentle:profiles Create, switch, and manage global agent-model profiles.
662
+ /gentle:profiles Open the models screen on this repository's profile. Subcommands: export <name>, import, publish <name>, export-routing, restore-routing.
662
663
  /gentle:persona Switch between gentleman and neutral persona modes.
663
664
  /gentle:background-subagents Show or set the managed background-subagents policy, with its deciding source.
664
665
  /gentle:review-mode Show or set the receipt-driven development mode (status|enable|disable).
@@ -987,7 +988,9 @@ Delegation contract:
987
988
  | Persona | Behavior |
988
989
  | ----------- | ------------------------------------------------------------------------------------------------------------- |
989
990
  | `gentleman` | Senior architect, teacher, direct technical feedback, Rioplatense Spanish/voseo when the user writes Spanish. |
990
- | `neutral` | Same discipline, warmer professional language, no regional expression. |
991
+ | `neutral` | Same discipline, warmer professional language, no regional expression, no voseo in any verb form. |
992
+
993
+ Both modes reply in the language of the user's latest message and never default to Spanish, so an English message gets an English reply even after context compaction.
991
994
 
992
995
  Saved globally at:
993
996
 
@@ -1006,15 +1009,41 @@ A project can still override the global default with:
1006
1009
  ## Model and effort assignment
1007
1010
 
1008
1011
  ```text
1009
- /gentle:models
1012
+ /gentle:profiles
1013
+ ```
1014
+
1015
+ `/gentle:profiles` opens the models screen on the profile this repository resolves: one table, with your profiles as tabs across the top. It replaces the retired `/gentle:models`, which opened the same screen.
1016
+
1017
+ ```text
1018
+ ╭──────────────────────────────────────────────────────────────────────────────╮
1019
+ │ current ◉★ ▾ before-nan-202609… nan GPT-Max + New │
1020
+ │ ─━━━━━━━━━━━━━━───────────────────────────────────────────────────────────── │
1021
+ │ current Used in this session. Default for new sessions. │
1022
+ │ │
1023
+ │ ▸ Orchestrator openai-codex/gpt-6.1-sol max │
1024
+ │ ────────────────────────────────────────────────────────────── │
1025
+ │ Agent Model ▾ Effort ▾ │
1026
+ │ gentle-ai-explore nan/deepseek-v4-flash high │
1027
+ │ gentle-ai-worker nan/glm5.3-flash high │
1028
+ │ │
1029
+ │ [ Use in this session ] [ Make default ] [ Save ] Saved │
1030
+ ╰──────────────────────────────────────────────────────────────────────────────╯
1010
1031
  ```
1011
1032
 
1012
- The modal discovers:
1033
+ - **Tabs are profiles.** Click a tab or press `←` `→` to show another one; the screen redraws in place. Symbols show where a profile is in effect: `◉` this session, `★` the default for new sessions, `⌂` this repository. Leaving a profile with unsaved changes asks first: **Save and switch**, **Discard**, or **Stay**.
1034
+ - **The table is that profile's routing.** The orchestrator is the first row; agents follow. Click a model or press `enter` to choose from a searchable list (the current one is marked `✓`; **Custom model id…** takes any `provider/model`). Click an effort or press `e` to pick from a single row of levels: only the levels that row's model supports, as its provider's catalog declares them (every level when the row inherits its model or the model is not in the catalog). Pi lands an unsupported level on the nearest supported one at launch, and the table says where: `max → high`. Click `Model ▾` or `Effort ▾`, or move the cursor to that header, to change every agent at once; the orchestrator is never part of that.
1035
+ - **Three buttons decide where it applies.** **Use in this session** (`u`) binds what you see to this Pi session only and switches its orchestrator; nothing shared is written. **Make default** (`d`) makes it the default for new sessions through the guarded global apply (diff confirmation, pin honesty, rollback). **Save** (`s` or `ctrl+s`) writes what you see into the profile. Use and Make default save pending edits first and say so.
1036
+ - **The profile menu**, `▾` on the active tab (`m`), holds **Rename**, **Duplicate**, **Use in this repo** (the clone pin; it reads **Stop using in this repo** when it already applies), and **Delete**. **+ New** (`n`) starts a profile as a copy of the current one, from what is running now, or empty.
1037
+ - Problems the screen will not hide appear under the profile name with `⚠`: a pin file that is not a valid pin or names a profile that does not exist (either way it is ignored), and routing keys no agent matches (ignored at launch), with the agent name it probably meant when there is one.
1038
+ - Edited values are highlighted until saved, and the footer counts them (`● 2 unsaved`). `esc` closes; with unsaved edits the first press warns and the second closes without saving. Every action reports on the line under the footer.
1039
+ - Everything clickable highlights under the pointer, and a click runs one action. Keys: `←→` profile, `↑↓` agent, `enter` model, `e` effort, `s` save, `u` use, `d` default, `n` new, `m` the profile menu, `esc` close.
1040
+
1041
+ The editor discovers:
1013
1042
 
1014
1043
  - project agents in `.pi/subagents/`, `.pi/agents/`, and `.agents/`;
1015
1044
  - user agents in `~/.pi/agent/subagents/`, `~/.pi/agent/agents/`, and `~/.agents/`.
1016
1045
 
1017
- When applying routing, project agents write runtime profiles to `.pi/subagents.json`; global and built-in agents write profiles to `~/.pi/agent/subagents.json`.
1046
+ When **Make default** applies a profile, project agents write runtime profiles to `.pi/subagents.json`; global and built-in agents write profiles to `~/.pi/agent/subagents.json`.
1018
1047
 
1019
1048
  Recommended model/effort shape:
1020
1049
 
@@ -1031,15 +1060,11 @@ Saved globally at:
1031
1060
  ~/.pi/gentle-ai/models.json
1032
1061
  ```
1033
1062
 
1034
- Existing project-local `.pi/gentle-ai/models.json` files are still read as a legacy fallback when no global model config exists, but `/gentle:models` writes the shared global config.
1035
-
1036
- Startup and reload materialize saved non-empty routing, but never treat a saved empty agent entry (`{}`) as permission to delete an existing `subagents.json` model profile or agent frontmatter. This also preserves previously materialized routes and routes whose agent definition is missing. To clear routing, explicitly save an inherit choice in `/gentle:models` or confirm a profile replacement in `/gentle:profiles`. Activation needs no ownership sidecar or lock.
1063
+ Global routing is owned by profiles: edit a profile's routing on the models screen and apply it with **Make default**. Applying writes the shared global config at `~/.pi/gentle-ai/models.json`; existing project-local `.pi/gentle-ai/models.json` files are still read as a legacy fallback when no global model config exists.
1037
1064
 
1038
- Inside `/gentle:models`, press `x` to export the saved routing to `~/.pi/gentle-ai/models.export.json`, or `r` to restore from that file after confirmation. Export uses a versioned envelope and restore writes the normal `models.json` shape before applying routing to agents.
1065
+ Startup and reload materialize saved non-empty routing, but never treat a saved empty agent entry (`{}`) as permission to delete an existing `subagents.json` model profile or agent frontmatter. This also preserves previously materialized routes and routes whose agent definition is missing. To clear routing, make a profile whose entries are inherit the default (or confirm a profile replacement) on the models screen. Activation needs no ownership sidecar or lock.
1039
1066
 
1040
- The `orchestrator` row selects the model and thinking effort that `u` will store in the current profile. Its initial choice is the target profile's saved entry, or **leave untouched** if that profile has no orchestrator key. Without an existing target profile, it uses the live session model and effort, falling back to `settings.json` when no live model exists; changing the row never switches the live session. Choose **leave untouched** in its model picker, or press `i` on the row, to store no `orchestrator` key—even when replacing a profile that previously carried one. Select a model before editing effort. **Set all agents** never changes this row.
1041
-
1042
- Press `u` to save global agent routing like `ctrl+s`, then snapshot that routing and the explicit orchestrator choice into the current profile. `ctrl+s` saves only agent routing: it neither updates a profile nor applies the chosen orchestrator. Both keys leave orchestrator defaults and the live model/effort unchanged; applying a profile with no orchestrator key preserves both. Custom-model, export and restore reopens retain the choice; exports/restores remain saved-agent-routing operations. `/gentle:profiles` `s` remains unchanged: it captures the live session model/effort when available, the `settings.json` defaults otherwise. The panel names the profile `u` targets: the profile this repository pins when a pin wins, otherwise the globally active profile. When no profiles store exists yet, `u` seeds it with a `current` profile the way `/gentle:profiles` does on first open; when the store exists but nothing is active and nothing is pinned, the global save still happens and the panel points you to `/gentle:profiles`.
1067
+ `/gentle:profiles export-routing` writes the saved routing to `~/.pi/gentle-ai/models.export.json`, and `/gentle:profiles restore-routing` restores from that file after confirmation. They carry `-routing` because they move the global `models.json` routing, not a profile: `/gentle:profiles export <name>` and `import` move one profile through `profiles.export.json`. Export uses a versioned envelope and restore writes the normal `models.json` shape before applying routing to agents.
1043
1068
 
1044
1069
  Config shape (per agent):
1045
1070
 
@@ -1063,32 +1088,23 @@ Legacy string entries are still accepted and treated as `model`-only config.
1063
1088
  /gentle:profiles
1064
1089
  ```
1065
1090
 
1066
- Profiles are named, switchable snapshots of the global agent-model routing from `/gentle:models`. The panel fills the terminal, shows the profile list on the left, and a detail pane comparing the selected profile's routing with the currently effective routing, one line per agent in shared columns. Keys:
1091
+ Profiles are named, switchable snapshots of the agent-model routing, including the orchestrator. They are the tabs of the models screen described above; everything about choosing, editing, and applying them happens there.
1092
+
1093
+ The file operations are subcommands:
1067
1094
 
1068
- | Key | Action |
1069
- | ------- | ---------------------------------------------------------------------- |
1070
- | `enter` | Bind the selected profile to this parent session. The panel shows it as `name (session)`; launches from this session resolve the binding ahead of pins and the global default, and a profile with an `orchestrator` entry switches the session you are in to that model right away. Nothing is written: no global routing, no pins, no materialized stores. |
1071
- | `a` | Set as global default (the pre-binding `enter` semantics): writes `models.json`, replaces the routing of every agent, sets the orchestrator when the profile defines one. Inside a pinned repository it stays repository-scoped instead; see **Per-repository pins** below. |
1072
- | `c` | Create a new, empty profile. |
1073
- | `s` | Snapshot the current routing into the selected profile, including the orchestrator the session actually runs (live model and thinking level); falls back to the `settings.json` defaults when no live model exists. Live routing is unchanged. |
1074
- | `d` | Duplicate the selected profile. |
1075
- | `r` | Rename the selected profile (keeps it active if it was active). |
1076
- | `x` | Delete the selected profile (refuses the active profile). |
1077
- | `e` | Export the selected profile to `~/.pi/gentle-ai/profiles.export.json`. |
1078
- | `i` | Import a profile from `~/.pi/gentle-ai/profiles.export.json`. |
1079
- | `p` | Pin the selected profile to this repository: writes the clone-local pin, so this repository's subagent launches use that profile no matter which profile is globally active. Pressing it again on the pinned profile removes the pin. |
1080
- | `P` | Publish or remove the shared repository declaration at `<worktree-root>/.pi/gentle-ai/profile.json`, so the whole team starts from that profile in this repository. |
1081
- | `j`/`k`, wheel | Scroll the detail pane one line at a time (agents-view style). |
1082
- | `pgup`/`pgdn`, `ctrl+j`/`ctrl+k` | Scroll the detail pane by a page. |
1083
- | `esc` | Close. |
1095
+ | Command | Action |
1096
+ | ------------------------------------ | ------ |
1097
+ | `/gentle:profiles export <name>` | Export a profile to `~/.pi/gentle-ai/profiles.export.json`. |
1098
+ | `/gentle:profiles import` | Import the profile in `~/.pi/gentle-ai/profiles.export.json`, asking before it replaces one with the same name. |
1099
+ | `/gentle:profiles publish <name>` | Publish the shared repository declaration at `<worktree-root>/.pi/gentle-ai/profile.json` so the whole team starts from that profile; run it again to remove the declaration. |
1084
1100
 
1085
- Selecting a profile with Enter does not write `profiles.json`, `models.json`, Pi's global `settings.json`, agent frontmatter, `subagents.json`, or repository pins. Other open sessions keep their own bindings and live orchestrators. The session binding overrides pins and global routing as a complete snapshot, including reviewer routing; omitted roles do not fall back to another profile. The binding lasts in the current process (including `/reload`); resuming in another process requires selecting it again. Re-selecting re-copies the profile definition from `profiles.json`; later edits to `subagents.json` or agent frontmatter never reach a bound session, in that session or through a re-selection.
1101
+ The binding made by **Use in this session** writes no shared routing: not `models.json`, Pi's global `settings.json`, agent frontmatter, `subagents.json`, or repository pins. When the table has unsaved edits, they are saved to the profile in `profiles.json` first, exactly as **Save** would, and the screen says so; with no pending edits nothing is written at all. Other open sessions keep their own bindings and live orchestrators. The session binding overrides pins and global routing as a complete snapshot, including reviewer routing; omitted roles do not fall back to another profile. The binding lasts in the current process (including `/reload`); resuming in another process requires selecting it again. Re-selecting re-copies the profile definition from `profiles.json`; later edits to `subagents.json` or agent frontmatter never reach a bound session, in that session or through a re-selection.
1086
1102
 
1087
- Applying a profile with `a` writes `~/.pi/gentle-ai/models.json`, then reconciles agent frontmatter and `subagents.json` the same way `/gentle:models` does. A profile is a complete snapshot: every discoverable agent it omits returns to inherit, so routing materialized by a previous profile, by `/gentle:models`, or by a migration never survives a switch silently. The active profile is persisted so `/gentle:profiles` reopens with the applied profile marked.
1103
+ Outside a pinned repository, **Make default** writes `~/.pi/gentle-ai/models.json`, then reconciles agent frontmatter and `subagents.json` with the shared materialization rules. When a valid pin wins, it only re-pins this clone to the profile and writes none of `models.json`, materialized routing, `settings.json`, or the active marker (see **Per-repository pins**). A profile is a complete snapshot: every discoverable agent it omits returns to inherit, so routing materialized by a previous profile, by an earlier apply, or by a migration never survives a switch silently. The active profile is persisted so `/gentle:profiles` reopens with the applied profile marked.
1088
1104
 
1089
- A profile also carries the orchestrator under the reserved routing key `orchestrator`. Applying a profile with `a` when it defines one writes `defaultProvider`, `defaultModel`, and `defaultThinkingLevel` to Pi's global `settings.json` (preserving every other key; an unreadable `settings.json` aborts that part and is reported instead of being overwritten) and switches the session you are in to that model and thinking level right away, so the orchestrator answers with the profile's model from the next turn. Binding with `enter` skips the settings write and switches only the session you are in: same live move, nothing persisted. When the model is not in Pi's catalog or its provider has no authentication, `a` still records the default for new sessions and the apply note says this session kept its current model, while `enter` simply reports the refusal. Applying or binding a profile without an `orchestrator` entry never moves the orchestrator, and `s` snapshots the orchestrator the session actually runs together with the routing. `orchestrator` is reserved: it is not a subagent name, is never written to `subagents.json`, and is not counted as a role.
1105
+ A profile also carries the orchestrator under the reserved routing key `orchestrator`. Outside a pinned repository, **Make default** on a profile that defines one writes `defaultProvider`, `defaultModel`, and `defaultThinkingLevel` to Pi's global `settings.json` (preserving every other key; an unreadable `settings.json` aborts that part and is reported instead of being overwritten) and switches the session you are in to that model and thinking level right away, so the orchestrator answers with the profile's model from the next turn. **Use in this session** skips the settings write and switches only the session you are in: same live move, nothing persisted. When the model is not in Pi's catalog or its provider has no authentication, **Make default** still records the default for new sessions and says this session kept its current model, while **Use in this session** simply reports the refusal. Applying or binding a profile without an `orchestrator` entry never moves the orchestrator, and **+ New › From what's running now** captures the orchestrator the session actually runs together with the routing. `orchestrator` is reserved: it is not a subagent name, is never written to `subagents.json`, and is not counted as a role.
1090
1106
 
1091
- The panel's current routing, the `current` seed, and `s` all read the routing in effect: `models.json` where it has an entry, and otherwise the `subagents.json` model profile or frontmatter routing the runtime actually resolves for that agent. A sparse `models.json` therefore never hides routing that is still live. When `profiles.json` is missing, the command seeds one profile named `current` captured from that effective routing, marked active only when it has routing entries. Profiles or routing entries dropped by normalization are named in a warning instead of being lost silently.
1107
+ The `current` seed and **From what's running now** read the routing in effect. A valid local pin or repository declaration wins first: inside a pinned repository they read the pinned profile's routing. **From what's running now** also prefers this session's binding when it has one. Otherwise they read `models.json` where it has an entry, and the `subagents.json` model profile or frontmatter routing the runtime actually resolves for agents it omits. A sparse `models.json` therefore never hides routing that is still live. When `profiles.json` is missing, the command seeds one profile named `current` captured from that effective routing, marked active only when it has routing entries. Profiles or routing entries dropped by normalization are named in a warning instead of being lost silently.
1092
1108
 
1093
1109
  Saved globally at:
1094
1110
 
@@ -1121,7 +1137,7 @@ Store shape:
1121
1137
 
1122
1138
  The `profiles` values use the same per-agent shape as `models.json`. Profile names are slugs of 1-64 ASCII characters (letters, numbers, `.`, `_`, `-`, starting with a letter or number); names outside ASCII are rejected, as are the reserved object keys `__proto__`, `constructor`, and `prototype`. A rename or duplicate onto an existing name is refused, renaming the active profile keeps it active, and deleting the active profile is refused. Export and import use a single-profile envelope (`kind: "gentle-pi.agent_model_profile"`, `version: 1`) at `~/.pi/gentle-ai/profiles.export.json`.
1123
1139
 
1124
- The store is replaced atomically through a sibling temp file and a rename, so an interrupted write cannot leave truncated JSON behind. Explicit global apply with `a` writes `profiles.json` first and then materializes routing; if materialization fails, the previous active marker and the previous routing are restored, and anything that could not be restored is named in the warning.
1140
+ The store is replaced atomically through a sibling temp file and a rename, so an interrupted write cannot leave truncated JSON behind. **Make default** writes `profiles.json` first and then materializes routing; if materialization fails, the previous active marker is restored, and the previous routing is restored when a profile was active before. With no previously active profile there is no routing to restore: `models.json` keeps the new routing and the warning says so. Anything that could not be restored is named in the warning.
1125
1141
 
1126
1142
  ### Per-repository pins
1127
1143
 
@@ -1131,8 +1147,8 @@ Two pin layers exist, and both hold only a profile name:
1131
1147
 
1132
1148
  | Layer | Path | Written by | Git impact |
1133
1149
  | ----- | ---- | ---------- | ---------- |
1134
- | Local pin | `<git-common-dir>/gentle-ai/profile-pin.json` | `p` | Invisible to git; every worktree of the clone shares it. |
1135
- | Repository declaration | `<worktree-root>/.pi/gentle-ai/profile.json` | `P` | An ordinary repository file; commit it to share the pin with the team. |
1150
+ | Local pin | `<git-common-dir>/gentle-ai/profile-pin.json` | Profile menu `▾` › **Use in this repo** | Invisible to git; every worktree of the clone shares it. |
1151
+ | Repository declaration | `<worktree-root>/.pi/gentle-ai/profile.json` | `/gentle:profiles publish <name>` | An ordinary repository file; commit it to share the pin with the team. |
1136
1152
 
1137
1153
  Both use the same shape, and both are a separate artifact from `profiles.json`:
1138
1154
 
@@ -1152,9 +1168,9 @@ or stale pins fall through to the next valid layer. A session shows the profile
1152
1168
  in `follow` mode, appears within about two seconds while the UI session is active. The indicator is
1153
1169
  omitted if no valid profile remains.
1154
1170
 
1155
- For a given working directory the winner is the local pin, then the repository declaration, then the globally active profile. A session resolves these layers once, when it starts, and keeps the result. `p` and `P` are toggles: pressing one on the profile that already holds that layer removes it, and either key pressed outside a Git worktree writes nothing and says so.
1171
+ For a given working directory the winner is the local pin, then the repository declaration, then the globally active profile. A session resolves these layers once, when it starts, and keeps the result. Both are toggles: using one on the profile that already holds that layer removes it (the menu then reads **Stop using in this repo**), and either one outside a Git worktree writes nothing and says so.
1156
1172
 
1157
- In a pinned repository the pinned profile governs subagent launches: the agents it names take its model and effort, and the agents it omits return to inherit (their own definition, then the default model). The globally active profile and writes made through `/gentle:models` do not reach those launches, which `/gentle:models` reports when it runs inside a pinned repository. `a` follows the same boundary: inside a pinned repository it re-pins that repository instead of writing the global routing, so a global apply can never move another repository's routing. `enter` writes nothing at all: it binds the profile to the current session, which outranks the pin for this session's launches. The panel states which layer won, names the file that holds it, and marks the profile with `(pinned)`.
1173
+ In a pinned repository the pinned profile governs subagent launches: the agents it names take its model and effort, and the agents it omits return to inherit (their own definition, then the default model). The default profile does not reach those launches, which `/gentle:profiles` reports when it runs inside a pinned repository. **Make default** follows the same boundary: when a valid pin wins it re-pins this clone (the clone-local pin) instead of writing the global routing; a malformed pin or one naming a missing profile is ignored, and Make default applies globally as it would outside a pinned repository, so it can never move another repository's routing. A committed repository declaration is never rewritten; when one still names another profile, the clone-local pin takes precedence and the screen says so. **Use in this session** writes no pin or shared routing (only pending table edits, saved to the profile first): it binds the profile to the current session, which outranks the pin for this session's launches. The tab of the pinned profile carries `⌂`.
1158
1174
 
1159
1175
  To share a pin, commit the repository declaration. When `.pi/` is ignored, Git cannot re-include a nested file until its parent directories are visible. The panel therefore prints these ordered root `.gitignore` rules, which keep unrelated `.pi` content ignored while making only the declaration committable:
1160
1176
 
@@ -1166,7 +1182,7 @@ To share a pin, commit the repository declaration. When `.pi/` is ignored, Git c
1166
1182
  !.pi/gentle-ai/profile.json
1167
1183
  ```
1168
1184
 
1169
- Renaming the profile that is this repository's local pin rewrites the local pin; a repository declaration is never rewritten behind a commit, and the panel says to press `P` again when it still names the old profile. Deleting a profile is refused while it is the global active profile, this repository's local pin, or this repository's repository declaration. Pins held by other repositories cannot be enumerated from here and are not checked.
1185
+ Renaming the profile that is this repository's local pin rewrites the local pin; a repository declaration is never rewritten behind a commit, and the rename note says to run `/gentle:profiles publish <name>` again when it still names the old profile. Deleting a profile is refused while it is the global active profile, this repository's local pin, or this repository's repository declaration. Pins held by other repositories cannot be enumerated from here and are not checked.
1170
1186
 
1171
1187
  A pin that cannot be honored never blocks work and is never destroyed by a read. Running outside a Git worktree, an unreadable or unparseable pin file, and a pin naming a profile the global store does not have are reported in the panel, and the repository falls back to the globally active profile. A missing pin layer is the ordinary no-pin state and stays silent.
1172
1188
 
@@ -1189,7 +1205,7 @@ A session that never selected a profile resolves the shared defaults once, at st
1189
1205
  **Drift notice.** On each subagent launch, a session with a frozen profile compares it with the current defaults of its own directory, without applying them. When they differ, it shows one line:
1190
1206
 
1191
1207
  ```text
1192
- el Gentleman: the default profile changed to "local" (local), this session keeps "frontier" (local). Press Enter on a profile in /gentle:profiles to adopt it.
1208
+ el Gentleman: the default profile changed to "local" (local), this session keeps "frontier" (local). Open /gentle:profiles and choose Use in this session to adopt one.
1193
1209
  ```
1194
1210
 
1195
1211
  The notice appears once per distinct change: further launches stay quiet until the defaults change again. The same profile reached through another layer is not a change; an edit of the profile's content is. Sessions with a profile selected in the session, `follow` sessions, and subagent children never show it.
@@ -1210,8 +1226,7 @@ The notice appears once per distinct change: further launches stay quiet until t
1210
1226
  | -------------------------------- | ------------------------------------------------------------------- |
1211
1227
  | `/gentle:status` | Shows package assets and global model config status. |
1212
1228
  | `/gentle:doctor` | Runs read-only diagnostics for assets, model/persona config, memory tools, safety guards, and (inside Herdr) the task-summary sidebar setup. |
1213
- | `/gentle:models` | Opens global model + effort assignment UI. Press `x` to export, `r` to restore saved routing, and `u` to save routing and capture the session in the current profile. |
1214
- | `/gentle:profiles` | Opens global agent-model profiles: apply live, create, snapshot, duplicate, rename, delete, export, and import. |
1229
+ | `/gentle:profiles` | Opens the models screen on this repository's profile. `export <name>`, `import`, and `publish <name>` handle profile files and the repository declaration; `export-routing` and `restore-routing` move the saved global routing through `models.export.json`. |
1215
1230
  | `/gentle:commands` | Opens the command palette (default `alt+k`): a curated, grouped menu (Configuration, Session, Diagnostics, Skills) of registered Gentle commands; search and run by label. |
1216
1231
  | `/gentle:persona` | Switches global persona mode, with project override support. |
1217
1232
  | `/gentle:background-subagents` | Shows or sets the managed background-subagents policy (`status\|enable\|disable`), naming the source that decided it. |
@@ -1334,7 +1349,7 @@ The frame labels INSERT, NORMAL, VISUAL (characterwise), or VISUAL LINE (linewis
1334
1349
 
1335
1350
  **Deliberate `/` divergence from Claude Code:** NORMAL `/` hands off to **Pi's native slash commands and skills**, enters INSERT, and inserts `/` at the existing cursor. Pi offers slash completion only at the start of the first line; elsewhere it inserts a literal slash without moving or replacing the draft. There is **no reverse prompt-history search**. Pi's explicit history shortcuts still work, transferring to INSERT first. Unknown NORMAL printable input, encoded text and bracketed paste do not silently insert; application shortcuts can transfer to INSERT before acting.
1336
1351
 
1337
- This is a bounded command subset, not full Claude Code/Vim parity. The private editor adapter supports only the audited Pi coding-agent/TUI `0.99.1`, `0.99.2`, `1.0.0`, `1.1.0`, and `1.2.0` package pairs. The two 0.99 releases have byte-identical editor and undo-stack sources. Pi 1.0.0, 1.1.0 and 1.2.0 are separately audited against the touched state, paste/history, cursor/layout, autocomplete and undo-snapshot contracts. Pi 1.2.0's paired APC fake-cursor markers remain intact so the host can suppress software cursors when hardware cursors are visible; selection inversion and empty-prompt hints support both these markers and legacy SGR cursors. Current bundled and unbundled 1.2.0 tests prove identity, edit/undo, paste, selection, wrapping/scroll and autocomplete behavior, not old/new byte identity. Fabricated metadata tests preserve exact-version admission coverage for the older audited releases. Version metadata must come from a canonical candidate host package root whose actual `CustomEditor` and `Editor` classes match the loaded classes, never from the extension's local metadata or CLI path alone. Unknown versions, mismatched prototypes, or invalid layouts fail closed: a single compatibility warning is shown and the prompt continues with ordinary editing instead of silently entering inert NORMAL mode. Operations that would cross a registered collapsed paste marker, or encounter duplicate occurrences of a registered marker ID, are rejected without editing it. Visual highlighting relies on Pi's render layout and may be omitted if its geometry cannot be validated. No live-terminal proof of every layout or complete parity is claimed.
1352
+ This is a bounded command subset, not full Claude Code/Vim parity. The private editor adapter supports only the audited Pi coding-agent/TUI `0.99.1`, `0.99.2`, `1.0.0`, `1.1.0`, `1.2.0`, `1.3.0`, and `1.4.0` package pairs. The two 0.99 releases have byte-identical editor and undo-stack sources. Pi 1.0.0, 1.1.0 and 1.2.0 are separately audited against the touched state, paste/history, cursor/layout, autocomplete and undo-snapshot contracts. Pi 1.2.0's paired APC fake-cursor markers remain intact so the host can suppress software cursors when hardware cursors are visible; selection inversion and empty-prompt hints support both these markers and legacy SGR cursors. Current bundled and unbundled 1.2.0 tests prove identity, edit/undo, paste, selection, wrapping/scroll and autocomplete behavior, not old/new byte identity. Fabricated metadata tests preserve exact-version admission coverage for the older audited releases. Version metadata must come from a canonical candidate host package root whose actual `CustomEditor` and `Editor` classes match the loaded classes, never from the extension's local metadata or CLI path alone. Unknown versions, mismatched prototypes, or invalid layouts fail closed: a single compatibility warning is shown and the prompt continues with ordinary editing instead of silently entering inert NORMAL mode. Operations that would cross a registered collapsed paste marker, or encounter duplicate occurrences of a registered marker ID, are rejected without editing it. Visual highlighting relies on Pi's render layout and may be omitted if its geometry cannot be validated. No live-terminal proof of every layout or complete parity is claimed.
1338
1353
 
1339
1354
  Startup banner settings remain global in `banner.json` under `GENTLE_PI_CONFIG_HOME` (default `~/.pi/gentle-ai`). Existing `showRose` and `showTextLogo` opt-outs independently control the main startup artwork; both default to enabled. Changes apply on the next session or `/reload`. Color presets are `pink` (default), `cyan`, `yellow`, and `green`. The static sidebar heading is independent of these preferences and follows the active theme.
1340
1355
 
@@ -19,6 +19,7 @@ import { createJobRegistry, isStandaloneSleep, type JobExecOperations, type JobR
19
19
  import { JOB_GLYPH, JOB_NOTICE_TYPE, jobDetails, jobNoticeText, monitorEventsText, monitorStoppedText, registerBackgroundJobTools } from "../lib/background-jobs-tools.ts";
20
20
  import { createMonitorController, mergeMonitorBatches, type MonitorNotice } from "../lib/background-monitor.ts";
21
21
  import { JobsView } from "../lib/jobs-view.ts";
22
+ import { JOBS_FINISHED_TTL_MS } from "../lib/jobs-widget.ts";
22
23
  import { JOBS_SIDEBAR_EVENT, JOBS_STATUS_KEY } from "../lib/jobs-sidebar-state.ts";
23
24
  import { Text, type Component, type TUI } from "@earendil-works/pi-tui";
24
25
  import { invalidateSidebar } from "../lib/shell-sidebar-layout.ts";
@@ -1231,11 +1232,22 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
1231
1232
  // Structured descriptions feed Status; native hosts keep the compact count.
1232
1233
  const refreshJobStatus = () => {
1233
1234
  const sessionId = activeSessionId();
1234
- const running = jobs.list(sessionId ?? "").filter((job) => job.status === "running")
1235
+ const sessionJobs = jobs.list(sessionId ?? "");
1236
+ const running = sessionJobs.filter((job) => job.status === "running")
1235
1237
  .sort((a, b) => b.startedAt - a.startedAt);
1238
+ // Finished tails ride the same snapshot so the sidebar card can linger
1239
+ // them briefly, like the Agents card's results; failed jobs ride too,
1240
+ // painting the ✗ glyph like a nonzero exit.
1241
+ const finished = sessionJobs.filter((job) => (job.status === "exited" || job.status === "stopped" || job.status === "failed") && job.endedAt !== undefined && deps.now() - job.endedAt < JOBS_FINISHED_TTL_MS)
1242
+ .sort((a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0)).slice(0, 3);
1236
1243
  try { parentCtx?.ui.setStatus(JOBS_STATUS_KEY, running.length === 0 ? undefined : `${JOB_GLYPH} ${running.length} job${running.length === 1 ? "" : "s"}`); } catch { /* Status is cosmetic. */ }
1237
1244
  if (sessionId) {
1238
- try { pi.events.emit(JOBS_SIDEBAR_EVENT, { sessionId, snapshot: { jobs: running.map(({ id, label, startedAt }) => ({ id, label, startedAt })) } }); } catch { /* Display only. */ }
1245
+ try {
1246
+ pi.events.emit(JOBS_SIDEBAR_EVENT, { sessionId, snapshot: { jobs: [
1247
+ ...running.map(({ id, label, startedAt }) => ({ id, label, startedAt })),
1248
+ ...finished.map(({ id, label, startedAt, status, endedAt, exitCode }) => ({ id, label, startedAt, status, endedAt, exitCode })),
1249
+ ] } });
1250
+ } catch { /* Display only. */ }
1239
1251
  }
1240
1252
  };
1241
1253