fix(pi): close four mode-list gaps in the pi run mode

Review follow-ups on #282. All four are the same failure shape: a list that
enumerates run modes, missed by the sweep that added 'pi'.

1. Cron ignored pi's project-trust clamp. The PR widened CronJobBaseSchema's
   agentType to accept 'pi' but not the matching clamp beside gemini's, so a
   non-granted multi-user owner's cron pi job spawned bare `pi` (pi's own
   defaultProjectTrust, an interactive prompt they can answer "yes" to, which
   loads and EXECUTES repo-local .pi/extensions TypeScript) while the same
   user's UI/API launch was forced to --no-approve. The clamp is now a pure
   exported helper, clampCronExternalCliConfigs(), so both it and gemini's
   previously untested materialization are pinned.

2. POST /api/sessions/:id/interactive auto-enabled the Ralph tracker for pi:
   its denylist covered opencode/codex/gemini/antigravity only. The tracker is
   never fed for an external CLI (_processExpensiveParsers returns early), so a
   pi session reported ralphEnabled and Ralph UI state no sibling backend shows.

3. REMOTE_CLI_BIN had no pi entry, so buildRemoteCliVersionProbeCommand()
   returned null and Session.cliVersion stayed blank for every remote-SSH pi
   session, even though the PR wired the remote launch command and the
   per-mode override schema field.

4. The desktop home rail's badge map had no pi entry, and its lookup falls back
   to '', which is what claude renders. A pi session read as Claude there while
   the tab strip and phone overview badged it correctly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-08-13 17:23:27 +02:00
parent c5b59633d8
commit f4dcfbe6ca
8 changed files with 105 additions and 12 deletions
+2 -1
View File
@@ -8,9 +8,10 @@ Add Pi (pi.dev) as a sixth CLI run mode (#206).
- **New resolver** `src/utils/pi-cli-resolver.ts`. Unlike the sibling resolvers it sanity-probes `pi --version` and requires semver-shaped output, because `pi` is a short generic name that a stray binary on `$PATH` can shadow; the rejected path is logged. `GET /api/pi/status` returns `{ available, path, version }` so a misresolution is diagnosable.
- **`PiConfig`** maps to `--model` (accepts `provider/id` and a `:thinking` suffix), `--provider`, `--thinking`, `--session`/`-c`, and the tri-state `--approve` / `--no-approve`. Every value is regex-allowlisted and dropped on failure. `--api-key` is deliberately never wired: it would put a provider secret on the spawn command line.
- **No bypass flag.** Pi has no permission prompts and no sandbox, so there is no `--dangerously-skip-permissions` analog. Its privilege-shaped knob is `approveProjectTrust`, which makes pi load and execute repo-local `.pi/extensions` TypeScript and install missing project packages. `clampExternalCliBypassForOwner()` therefore puts pi in the **materialize** branch: a non-granted multi-user owner gets `--no-approve` even when no config was sent, because pi's own default is an interactive prompt the session user could answer themselves. That helper had no test coverage at all; it now does, for all four CLIs.
- **No bypass flag.** Pi has no permission prompts and no sandbox, so there is no `--dangerously-skip-permissions` analog. Its privilege-shaped knob is `approveProjectTrust`, which makes pi load and execute repo-local `.pi/extensions` TypeScript and install missing project packages. `clampExternalCliBypassForOwner()` therefore puts pi in the **materialize** branch: a non-granted multi-user owner gets `--no-approve` even when no config was sent, because pi's own default is an interactive prompt the session user could answer themselves. The same materialization applies to cron-fired jobs (`clampCronExternalCliConfigs`), which carry no per-CLI config and would otherwise launch on pi's own default. Both helpers had no test coverage at all; they now do, for every CLI.
- **Env allowlist gains only the `PI_*` prefix.** Pi's ~34 provider key vars share no prefix and `ALLOWED_ENV_PREFIXES` is one global list with no mode context, so admitting them would widen the allowlist for every mode at once. Users authenticate via pi's `/login` or the server process's own environment.
- **Pi stays out of `isAltScreenStripMode()`.** Its default TUI renders into the main screen with terminal-owned scrollback, and since 0.84.0 the user can flip to a fullscreen TUI at runtime via `/settings` — verified to switch the pane into the alt screen, which the strip would have corrupted.
- **Docker**: pi installs in its own `--ignore-scripts` step so that flag cannot affect the other four CLIs, and its credentials are seeded per-file (`auth.json`, `settings.json`, `trust.json`, `models.json`, `models-store.json`) rather than whole-dir, since `~/.pi/agent` also holds sessions, extensions and installed package trees.
- **Local echo**: pi lands on the buffer overlay. Verified that codex's per-keystroke starvation does not reproduce — pi's slash picker re-filters on the whole composer content, so a one-shot flush behaves identically to per-keystroke typing.
- **Mode-list parity**: pi is excluded from the Ralph tracker auto-enable on `POST /api/sessions/:id/interactive` (like every other external CLI, whose output the tracker never parses), carries a `REMOTE_CLI_BIN` entry so a remote-SSH pi session reports its CLI version, and gets its own badge in the desktop home rail instead of rendering like Claude.
- Installer detection, docs (`docs/pi-integration.md`), READMEs, and the architecture invariants are updated. Tests: `test/pi-mode.test.ts` and `test/routes/external-cli-bypass-clamp.test.ts`, plus extensions to the run-mode, mobile-overview, render-index-html, system-routes and local-echo suites.