pi-session-mcp 0.12.0 → 0.13.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/.pi-session-mcp-cli/client-adapters/mcp-verifier.js +8 -3
- package/.pi-session-mcp-cli/config.js +15 -1
- package/.pi-session-mcp-cli/doctor.js +44 -1
- package/.pi-session-mcp-cli/execution-modes.js +13 -3
- package/.pi-session-mcp-cli/execution-profile.js +1 -0
- package/.pi-session-mcp-cli/sandbox-shell.js +254 -0
- package/.pi-session-mcp-cli/session-tools.js +6 -2
- package/.pi-session-mcp-cli/writer-boundary.js +27 -0
- package/.pi-session-mcp-cli/writer-lifecycle.js +6 -4
- package/.pi-session-mcp-cli/writer-limits.js +19 -0
- package/.pi-session-mcp-cli/writer-state.js +5 -2
- package/CHANGELOG.md +96 -0
- package/README.md +2 -1
- package/dist/capabilities.d.ts +1 -0
- package/dist/capabilities.js +3 -1
- package/dist/client-adapters/mcp-verifier.js +8 -3
- package/dist/config.d.ts +1 -0
- package/dist/config.js +15 -1
- package/dist/confined-tools.d.ts +29 -0
- package/dist/confined-tools.js +118 -0
- package/dist/doctor.d.ts +5 -1
- package/dist/doctor.js +44 -1
- package/dist/execution-modes.d.ts +15 -1
- package/dist/execution-modes.js +13 -3
- package/dist/execution-profile.d.ts +4 -0
- package/dist/execution-profile.js +1 -0
- package/dist/main.js +6 -2
- package/dist/pi-adapter.d.ts +1 -1
- package/dist/pi-adapter.js +1 -0
- package/dist/sandbox-shell.d.ts +71 -0
- package/dist/sandbox-shell.js +254 -0
- package/dist/sdk-pi-adapter.d.ts +17 -1
- package/dist/sdk-pi-adapter.js +64 -10
- package/dist/session-tools.d.ts +4 -2
- package/dist/session-tools.js +6 -2
- package/dist/setup-cli.js +40 -6
- package/dist/setup-modes.d.ts +3 -0
- package/dist/setup-modes.js +18 -1
- package/dist/setup-writers.js +37 -6
- package/dist/tool-schemas.d.ts +4 -0
- package/dist/tool-schemas.js +1 -0
- package/dist/writer-boundary.d.ts +2 -0
- package/dist/writer-boundary.js +27 -0
- package/dist/writer-lifecycle.d.ts +1 -0
- package/dist/writer-lifecycle.js +6 -4
- package/dist/writer-limits.d.ts +13 -0
- package/dist/writer-limits.js +19 -0
- package/dist/writer-relay.js +1 -1
- package/dist/writer-state.d.ts +11 -0
- package/dist/writer-state.js +5 -2
- package/docs/acceptance.md +266 -0
- package/docs/adr/0013-hardened-read-only-profiles.md +182 -0
- package/docs/doctor-design.md +2 -0
- package/docs/execution-modes.md +59 -3
- package/docs/installation.md +34 -1
- package/docs/isolation-setup.md +31 -2
- package/docs/roadmap.md +8 -1
- package/docs/session-mcp.md +13 -0
- package/docs/threat-model.md +50 -0
- package/docs/tool-contracts.md +30 -3
- package/package.json +1 -1
- package/skills/pi-session-orchestration/references/start-errors.md +1 -1
package/docs/acceptance.md
CHANGED
|
@@ -2457,6 +2457,272 @@ Not established for `0.11.6`:
|
|
|
2457
2457
|
wrapper's periodic credential copy (#136);
|
|
2458
2458
|
- the VS Code wake; macOS delivery.
|
|
2459
2459
|
|
|
2460
|
+
### v0.13.0 pre-release acceptance
|
|
2461
|
+
|
|
2462
|
+
`v0.13.0` was accepted on 2026-10-28 from 19:30 to 19:36 UTC on its release
|
|
2463
|
+
candidate `f7442b3`, on Linux (`x64`) with Node.js `26.12.1`, npm `12.2.1`,
|
|
2464
|
+
Codex CLI `0.162.1` and Claude Code `2.1.312`. Level 2 passed in the working
|
|
2465
|
+
tree and for the packed tarball, each with delivery off and with delivery on.
|
|
2466
|
+
Level 1 passed except `npm audit`. Later commits on the release pull request
|
|
2467
|
+
change only this record.
|
|
2468
|
+
|
|
2469
|
+
The minor release ships ADR 0013 (#169) and the writer settings changes #162,
|
|
2470
|
+
#163, #165 and #166. It changes the MCP contract (read-only confinement,
|
|
2471
|
+
profile field and capabilities field `shell`, start error
|
|
2472
|
+
`sandbox_unavailable`); the setup report schema is unchanged.
|
|
2473
|
+
|
|
2474
|
+
- Level 1 in a clean worktree of the candidate after `npm ci`: every command
|
|
2475
|
+
exited with `0`, except `npm audit`; `git status --short` printed nothing.
|
|
2476
|
+
- The test run, with `NODE_OPTIONS=--unhandled-rejections=strict`, reported
|
|
2477
|
+
125 files, 3936 passed and 14 skipped, and no unhandled error.
|
|
2478
|
+
- `npm audit` and `npm audit --omit=dev` exited with `1` and name only the
|
|
2479
|
+
known `undici` finding through `@earendil-works/pi-coding-agent` 1.4.0
|
|
2480
|
+
([#120](https://github.com/plagor44-ux/pi-session-mcp/issues/120)).
|
|
2481
|
+
- `npm pack` listed 916 files (tarball shasum
|
|
2482
|
+
`1259d57d2d59099ad72e796609ecab3d581e9b06`, identical in two packs). A scan
|
|
2483
|
+
of the packed files for private names, hosts and paths found none; its only
|
|
2484
|
+
matches for `/home/` are the already redacted
|
|
2485
|
+
`<repo>/eval/results/.../home/.pi/...` paths of disposable evaluation homes
|
|
2486
|
+
in six `docs/evaluation/extension-functions-2026-10-17/cursor/` evidence
|
|
2487
|
+
files, which `0.12.1` shipped unchanged.
|
|
2488
|
+
- Doctor reported `packageVersion` `0.13.0`. It exited with `2` because no
|
|
2489
|
+
configuration was set (`config_missing`).
|
|
2490
|
+
- Level 2 in the working tree: 160 of 160 checks passed with delivery off and
|
|
2491
|
+
228 of 228 with `--events spool`.
|
|
2492
|
+
- Packed-tarball Level 2 from a fresh prefix: 160 of 160 with delivery off and
|
|
2493
|
+
228 of 228 with `--events spool`. The install smoke from another fresh prefix
|
|
2494
|
+
reported `smoke_passed` with version `0.13.0`.
|
|
2495
|
+
- In every Level 2 run the real client registrations were unchanged, no process
|
|
2496
|
+
remained, the leak check passed and the disposable directories were removed.
|
|
2497
|
+
- Podman-gated writer tests on the candidate: 7 of 7 passed.
|
|
2498
|
+
- Bubblewrap-gated sandbox tests (`test/sandbox-shell.bwrap.test.ts`, bubblewrap
|
|
2499
|
+
`0.13.1`): 12 of 12 passed, none skipped.
|
|
2500
|
+
- Review: #169 had an independent review of its full diff (same model family as
|
|
2501
|
+
the author) that found no confinement or sandbox escape; its follow-ups were
|
|
2502
|
+
fixed before the merge of #170.
|
|
2503
|
+
|
|
2504
|
+
Not established for `0.13.0`: a live session with `shell: "sandboxed"` against
|
|
2505
|
+
a real provider, client-target `--verify` through the installed package, which
|
|
2506
|
+
follows the upgrade; Level 3.
|
|
2507
|
+
|
|
2508
|
+
### v0.12.1 post-release evidence
|
|
2509
|
+
|
|
2510
|
+
Recorded on 2026-10-28 for the published release `0.12.1`.
|
|
2511
|
+
|
|
2512
|
+
Tag binding: a fresh clone of the annotated tag `v0.12.1` (tag object
|
|
2513
|
+
`eecd6dbf0c220ded997c697f281f6b984d9ebf4e`) peels to the merge commit
|
|
2514
|
+
`91799f2e84b514110f77c24986d59e72c5ed98e5` of #161, and
|
|
2515
|
+
`git describe --tags --exact-match` reports `v0.12.1`. CI on the merge commit
|
|
2516
|
+
and on the tag passed on the first attempt.
|
|
2517
|
+
|
|
2518
|
+
- Level 1 on the tag clone after `npm ci`, from 14:43 to 14:47 UTC with
|
|
2519
|
+
Node.js `26.12.1` and npm `12.2.1`: typecheck, `build:cli`, the test run
|
|
2520
|
+
(120 files, 3708 passed, 14 skipped, with
|
|
2521
|
+
`NODE_OPTIONS=--unhandled-rejections=strict`), `build` and
|
|
2522
|
+
`git diff --check` exited with `0`, and the status stayed clean.
|
|
2523
|
+
- `pi-session-mcp@0.12.1` on npm: 907 files, shasum
|
|
2524
|
+
`58dc7235e0d54d4290d92934d877223e22eec03d`, `gitHead` `cc25693` (the head of
|
|
2525
|
+
#161, whose tree equals the merge commit). `npm pack` of the tag clone gives
|
|
2526
|
+
the same shasum and file count.
|
|
2527
|
+
- Published npm package, Level 2 from a fresh prefix: 160 of 160 with delivery
|
|
2528
|
+
off and 228 of 228 with `--events spool`; real registrations and built files
|
|
2529
|
+
unchanged, no process left, leak check passed. Clients: Codex CLI `0.162.1`
|
|
2530
|
+
and Claude Code `2.1.312`. The install smoke on the published package
|
|
2531
|
+
reported `smoke_passed` with version `0.12.1`.
|
|
2532
|
+
- The GitHub release `v0.12.1` was published the same day.
|
|
2533
|
+
|
|
2534
|
+
Workstation, upgraded from `0.12.0` to `0.12.1` by the release session: Doctor
|
|
2535
|
+
reported no error, client-target `--verify` passed for `claude-code:user` and
|
|
2536
|
+
`codex:user` (`mcp_verified`, `skill_verified`, and `hook_verified` for the
|
|
2537
|
+
wake hook), the open point of the pre-release record, and
|
|
2538
|
+
`--writer <name> --verify` passed for all 18 writers (`mcp_verified`).
|
|
2539
|
+
|
|
2540
|
+
Operator upgrade of the second machine (Linux) from `0.11.8` to `0.12.1` on
|
|
2541
|
+
2026-10-28 from 14:50 UTC, following
|
|
2542
|
+
[Installation](installation.md#container-writers), with Node.js `24.22.0`:
|
|
2543
|
+
|
|
2544
|
+
- No pi-session-mcp server process and no writer container ran at the start.
|
|
2545
|
+
- Both client targets were removed with `0.11.8`, then
|
|
2546
|
+
`npm install -g pi-session-mcp@0.12.1`. Doctor reported every writer
|
|
2547
|
+
`writer_not_ready`, each container workspace `workspace_base_unset` and each
|
|
2548
|
+
writer `writer_git_identity_missing`.
|
|
2549
|
+
- Four container repositories with 14 writers were migrated by repeating their
|
|
2550
|
+
provisioning commands with unchanged settings, each first as a dry run
|
|
2551
|
+
(`planned`), then with `--apply`: 2, 4, 4 (operator image, `--cache go`) and
|
|
2552
|
+
4 (operator image) writers. Every writer reported `writer_accepted`.
|
|
2553
|
+
- `--finish --apply` of all 14 writers reported `writer_reset`; no work was
|
|
2554
|
+
collected. Doctor then reported no writer or base finding.
|
|
2555
|
+
- `--apply` and `--verify` of both client targets passed (`mcp_verified`), and
|
|
2556
|
+
`--writer <name> --verify` passed for all 14 writers. No writer registration
|
|
2557
|
+
and no container remained.
|
|
2558
|
+
|
|
2559
|
+
Live checks of #154 and #151 on the workstation, 15:02 to 15:04 UTC, with the
|
|
2560
|
+
coding profile `code-deepseek` through the one host registration and wake
|
|
2561
|
+
events from the host spool:
|
|
2562
|
+
|
|
2563
|
+
- Settings change: a writer whose settings differ is refused with
|
|
2564
|
+
`writer_settings_changed`. Both repositories' writers were collected
|
|
2565
|
+
(`nothing_to_collect`), removed with `--writer <name> --remove` and
|
|
2566
|
+
provisioned again with `--tags reachable` (2 writers) and `--cache uv`
|
|
2567
|
+
(4 writers, operator image with uv `0.13.6`); every writer reported
|
|
2568
|
+
`writer_accepted`.
|
|
2569
|
+
- Tags (#154): the writer with `--tags reachable` held 14 tags, the tags of
|
|
2570
|
+
the operator checkout merged into `main`. In the session,
|
|
2571
|
+
`git describe --tags` reported `v0.12.1` and
|
|
2572
|
+
`git show v0.12.0:package.json` read the file at that tag. The writer without
|
|
2573
|
+
the setting held no tags.
|
|
2574
|
+
- Identity (#154): an empty commit in the session, without `GIT_AUTHOR_*` or
|
|
2575
|
+
`GIT_COMMITTER_*` and without `--author`, recorded the operator's name as
|
|
2576
|
+
author and committer. `git config user.name` inside the writer failed with
|
|
2577
|
+
exit `4` because `.git/config` is mounted read-only. The collected ref showed
|
|
2578
|
+
the same identity. The test ref was removed afterwards.
|
|
2579
|
+
- uv cache (#151): three sessions on the same writer, with `--finish --apply`
|
|
2580
|
+
between the second and the third. In the first, `UV_CACHE_DIR` was
|
|
2581
|
+
`/pi/uv/cache` and empty; `uv sync --frozen` took 1.9 s and left 95,576 KiB
|
|
2582
|
+
in 1,648 files. The second and third sessions found the same size and file
|
|
2583
|
+
count before they ran anything, and `uv sync --frozen --offline` after
|
|
2584
|
+
removing `.venv` took 0.07 s and 0.08 s.
|
|
2585
|
+
|
|
2586
|
+
Findings from these checks:
|
|
2587
|
+
|
|
2588
|
+
- Reset and finish keep Git-ignored files in the clone, as designed: `.venv`
|
|
2589
|
+
from the first session was still present in the third, after the finish.
|
|
2590
|
+
The opt-in `--clean all` removes them
|
|
2591
|
+
([#162](https://github.com/plagor44-ux/pi-session-mcp/issues/162)); it is
|
|
2592
|
+
not part of the released `0.12.1`.
|
|
2593
|
+
- A provisioning command with changed settings reported `mode_applied` and
|
|
2594
|
+
recorded the new mode although every writer failed with
|
|
2595
|
+
`writer_settings_changed`; Doctor reported no mismatch
|
|
2596
|
+
([#163](https://github.com/plagor44-ux/pi-session-mcp/issues/163), open).
|
|
2597
|
+
|
|
2598
|
+
Not established for `0.12.1`: a busy pool (`writer_unavailable`) and a `base`
|
|
2599
|
+
fix round in live use; Level 3; the macOS upgrade.
|
|
2600
|
+
|
|
2601
|
+
### v0.12.1 pre-release acceptance
|
|
2602
|
+
|
|
2603
|
+
`v0.12.1` was accepted on 2026-10-28 from 13:12 to 13:17 UTC on its release
|
|
2604
|
+
candidate `c6b472d`, on Linux (`x64`) with Node.js `26.12.1`, npm `12.2.1`,
|
|
2605
|
+
Codex CLI `0.162.1` and Claude Code `2.1.312`. Level 2 passed in the working
|
|
2606
|
+
tree and for the packed tarball, each with delivery off and with delivery on.
|
|
2607
|
+
Level 1 passed except `npm audit`. Later commits on the release pull request
|
|
2608
|
+
change only this record.
|
|
2609
|
+
|
|
2610
|
+
The patch ships #160 on top of 0.12.0. It does not change the MCP contract or
|
|
2611
|
+
the setup report schema.
|
|
2612
|
+
|
|
2613
|
+
- Level 1 in a clean worktree of the candidate after `npm ci`: every command
|
|
2614
|
+
exited with `0`, except `npm audit`; `git status --short` printed nothing.
|
|
2615
|
+
- The test run, with `NODE_OPTIONS=--unhandled-rejections=strict`, reported
|
|
2616
|
+
120 files, 3708 passed and 14 skipped, and no unhandled error. It ran on a
|
|
2617
|
+
host with a real writer lease directory, which the Doctor mode-order test
|
|
2618
|
+
no longer reads.
|
|
2619
|
+
- `npm audit` exited with `1` and names only the known `undici` finding
|
|
2620
|
+
through `@earendil-works/pi-coding-agent` 1.4.0
|
|
2621
|
+
([#120](https://github.com/plagor44-ux/pi-session-mcp/issues/120)).
|
|
2622
|
+
- `npm pack` listed 907 files (tarball shasum
|
|
2623
|
+
`f206e0eb0818342fb9e6a36c2c147fb690c515ce`); a scan of the packed files for
|
|
2624
|
+
private names, hosts and paths found none.
|
|
2625
|
+
- Doctor reported `packageVersion` `0.12.1`. It exited with `2` because no
|
|
2626
|
+
configuration was set (`config_missing`).
|
|
2627
|
+
- Level 2 in the working tree: 160 of 160 checks passed with delivery off and
|
|
2628
|
+
228 of 228 with `--events spool`.
|
|
2629
|
+
- Packed-tarball Level 2 from a fresh prefix: 160 of 160 with delivery off and
|
|
2630
|
+
228 of 228 with `--events spool`. The install smoke reported `smoke_passed`
|
|
2631
|
+
with version `0.12.1`.
|
|
2632
|
+
- In every Level 2 run the real client registrations and the built files were
|
|
2633
|
+
unchanged, no process remained, and the leak check passed.
|
|
2634
|
+
- Podman-gated writer tests on the candidate: 4 of 4 passed.
|
|
2635
|
+
- The candidate's setup verifier against its own server with the operator's
|
|
2636
|
+
real configuration, which has five container workspaces, reported
|
|
2637
|
+
`verified`, with initialize, `tools/list` (eight tools) and
|
|
2638
|
+
`pi_capabilities_get`. The same check with the installed 0.12.0 failed at
|
|
2639
|
+
`pi_capabilities_get`.
|
|
2640
|
+
- Review: `review-glm` (another model family than the author) on the fix,
|
|
2641
|
+
verdict `NON_BLOCKING_ONLY`; the shared writer bound and the real-server test
|
|
2642
|
+
with container workspaces were added.
|
|
2643
|
+
|
|
2644
|
+
Not established for `0.12.1`: client-target `--verify` through the installed
|
|
2645
|
+
package, which follows the upgrade; Level 3.
|
|
2646
|
+
|
|
2647
|
+
### v0.12.0 post-release evidence
|
|
2648
|
+
|
|
2649
|
+
Recorded on 2026-10-28 for the published release `0.12.0`.
|
|
2650
|
+
|
|
2651
|
+
Tag binding: a fresh clone of the annotated tag `v0.12.0` (tag object
|
|
2652
|
+
`bb77dfd8a4e49849d5e9c7fd305fb00baf443044`) peels to the merge commit
|
|
2653
|
+
`dfbef8c5efce38eacc8704dbaf93c534cdad0ef8` of #159, and
|
|
2654
|
+
`git describe --tags --exact-match` reports `v0.12.0`. CI on the merge commit
|
|
2655
|
+
passed on Linux, macOS 15, macOS 26 and `verify` after one rerun: the first
|
|
2656
|
+
macOS 26 run hit a timeout in `test/extension-ablation-deps.test.ts`
|
|
2657
|
+
(5044 ms against 5000 ms), a git-heavy evaluation test that the release did not
|
|
2658
|
+
touch. CI on the tag passed on the first attempt.
|
|
2659
|
+
|
|
2660
|
+
- Level 1 on the tag clone after `npm ci`: typecheck, `build:cli`, the test
|
|
2661
|
+
run (120 files, 3693 passed, 14 skipped, with
|
|
2662
|
+
`NODE_OPTIONS=--unhandled-rejections=strict`), `build` and
|
|
2663
|
+
`git diff --check` exited with `0`, and the status stayed clean. Doctor
|
|
2664
|
+
reported `packageVersion` `0.12.0`.
|
|
2665
|
+
- `pi-session-mcp@0.12.0` on npm: 907 files, shasum
|
|
2666
|
+
`4699b05ef849e88c6d56f5ddae6b06984799cea6`, `gitHead` `1fb8a7e` (the head of
|
|
2667
|
+
#159, whose tree equals the merge commit). A local `npm pack` of the merge
|
|
2668
|
+
commit gives the same shasum. A scan of the published files for private
|
|
2669
|
+
names, hosts and paths found none.
|
|
2670
|
+
- Published npm package, Level 2 from a fresh prefix: 160 of 160 with delivery
|
|
2671
|
+
off and 228 of 228 with `--events spool`; the install smoke reported
|
|
2672
|
+
`smoke_passed` with version `0.12.0`. Clients: Codex CLI `0.162.1` and Claude
|
|
2673
|
+
Code `2.1.312`.
|
|
2674
|
+
- The GitHub release `v0.12.0` was published the same day.
|
|
2675
|
+
|
|
2676
|
+
Operator upgrade of the workstation from `0.11.8` to `0.12.0` on 2026-10-28,
|
|
2677
|
+
following [Installation](installation.md#container-writers):
|
|
2678
|
+
|
|
2679
|
+
- Coding sessions were closed and the two pi-session-mcp writer servers were
|
|
2680
|
+
disabled in the controller; one idle controller of another repository still
|
|
2681
|
+
had four writer containers running.
|
|
2682
|
+
- Both host client targets (`claude-code:user`, `codex:user`) were removed with
|
|
2683
|
+
`0.11.8`, then `npm install -g pi-session-mcp@0.12.0`. Doctor reported
|
|
2684
|
+
`packageVersion` `0.12.0` and, as expected, every writer `writer_not_ready`
|
|
2685
|
+
and `writer_not_migrated`.
|
|
2686
|
+
- Five container repositories with 18 writers were migrated by repeating their
|
|
2687
|
+
provisioning commands with unchanged settings, each first as a dry run
|
|
2688
|
+
(`planned`), then with `--apply`: pi-session-mcp (2 writers), node-app (4),
|
|
2689
|
+
go-app (4, operator image, `--cache go`), pynode-app (4, operator image,
|
|
2690
|
+
base `dev`) and py-app (4, operator image). Every writer reported
|
|
2691
|
+
`writer_accepted` and is `launch: "relay"`, `ready`, verdict `pass`. Each
|
|
2692
|
+
repository recorded its shared default base (`main`, and `dev` for
|
|
2693
|
+
pynode-app). All 36 owned writer registrations and their hook entries were
|
|
2694
|
+
removed; the fence removed the four running legacy containers; no writer
|
|
2695
|
+
container remained.
|
|
2696
|
+
- `--apply` of both client targets passed, but `--verify` failed with
|
|
2697
|
+
`mcp_failed` on both:
|
|
2698
|
+
[#160](https://github.com/plagor44-ux/pi-session-mcp/issues/160). A direct
|
|
2699
|
+
handshake with the installed server returned the eight tools and the
|
|
2700
|
+
capabilities, including `writers` and `defaultBase` for each container
|
|
2701
|
+
workspace; the 0.12.0 verifier rejected them. The fix, run with the real
|
|
2702
|
+
configuration against the candidate's own server, reported `verified`.
|
|
2703
|
+
- `--writer <name> --verify` passed for all 18 writers over the relay path
|
|
2704
|
+
(`mcp_verified`).
|
|
2705
|
+
- Live relayed coding session through the one host registration, after
|
|
2706
|
+
reconnecting `pi-session-mcp`: `pi_capabilities_get` listed the container
|
|
2707
|
+
workspaces with `writers` and `defaultBase`; `pi_session_start` with profile
|
|
2708
|
+
`code` on the pi-session-mcp workspace leased `pi-session-mcp-w1` at
|
|
2709
|
+
`refs/heads/main` and returned `writer` and `base`. The worker implemented
|
|
2710
|
+
the #160 fix in two turns. Both turns woke the controller through the wake
|
|
2711
|
+
hook from the host spool, without polling. Close, collect and finish
|
|
2712
|
+
succeeded without a reconnect. A read-only `review-glm` session on the host
|
|
2713
|
+
checkout reviewed the commit (`NON_BLOCKING_ONLY`).
|
|
2714
|
+
- The first turn could not commit: the migrated clones had no commit identity
|
|
2715
|
+
(Doctor `writer_git_identity_missing` on all 18 writers), because migration
|
|
2716
|
+
keeps the clone and only provisioning and reset copy the identity. After
|
|
2717
|
+
`--finish` of the used writer and `--reset --apply` of the other 17, Doctor
|
|
2718
|
+
reported no writer finding, and all 18 writers stayed `relay`, `ready` and
|
|
2719
|
+
accepted. The upgrade runbook now names this step.
|
|
2720
|
+
|
|
2721
|
+
Not established for `0.12.0`: the upgrade of the second machine; the live
|
|
2722
|
+
checks of #151 (`--cache uv`) and #154 (`--tags reachable`), which need a
|
|
2723
|
+
settings change of an existing writer; a busy-pool (`writer_unavailable`) and a
|
|
2724
|
+
`base` fix round in live use; Level 3.
|
|
2725
|
+
|
|
2460
2726
|
### v0.12.0 pre-release acceptance
|
|
2461
2727
|
|
|
2462
2728
|
`v0.12.0` was accepted on 2026-10-28 from 11:40 to 11:45 UTC on its release
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# ADR 0013: Hardened read-only profiles
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted (2026-10-28). Amends
|
|
4
|
+
[ADR 0006](0006-pi-extension-allowlist.md) and
|
|
5
|
+
[ADR 0010](0010-read-only-extension-tools.md). Issue #169.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Read-only profiles have `read`, `grep`, `find` and `ls` and no shell. Review
|
|
10
|
+
workers cannot run `git log`, `git blame`, a test file or a short script, and
|
|
11
|
+
read more files instead.
|
|
12
|
+
|
|
13
|
+
Read-only sessions run in the host server process. The SDK's read tools accept
|
|
14
|
+
absolute paths, `..`, `~` and symlinks, so a read-only worker can read every
|
|
15
|
+
file the operator can read, including Pi credentials outside the workspace.
|
|
16
|
+
Today only the absence of a network path keeps that content local. A granted
|
|
17
|
+
research tool with free-form input (search, documentation lookup) would give a
|
|
18
|
+
prompt-injected worker a channel to send it out.
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
### 1. Workspace-confined read tools for every read-only profile
|
|
23
|
+
|
|
24
|
+
`read`, `grep`, `find` and `ls` of **every** read-only profile accept only
|
|
25
|
+
paths inside the allowed read roots:
|
|
26
|
+
|
|
27
|
+
- the session's workspace root, and
|
|
28
|
+
- the directory of each selected skill file, because the model loads skills
|
|
29
|
+
with `read` and they live outside the workspace. If that directory equals or
|
|
30
|
+
contains the operator's home directory, the configuration file's directory
|
|
31
|
+
or the Pi agent directory (`~/.pi`), only the skill file itself is a root,
|
|
32
|
+
so a catalog entry such as `./SKILL.md` next to the configuration does not
|
|
33
|
+
expose the configuration.
|
|
34
|
+
|
|
35
|
+
The server replaces the SDK's built-in definitions with definitions built from
|
|
36
|
+
the root exports `createReadToolDefinition`, `createGrepToolDefinition`,
|
|
37
|
+
`createFindToolDefinition` and `createLsToolDefinition`, registered under the
|
|
38
|
+
same names as SDK custom tools. Two layers check each call:
|
|
39
|
+
|
|
40
|
+
1. Before the SDK tool runs, the `path` argument is resolved against the
|
|
41
|
+
session directory and canonicalized with `realpath` (for a path that does
|
|
42
|
+
not exist yet, the nearest existing ancestor plus the remaining segments).
|
|
43
|
+
It must equal an allowed root or lie below one. Arguments the SDK would
|
|
44
|
+
rewrite before resolving (a leading `@` or `~`, a `file:` URL, Unicode
|
|
45
|
+
spaces, NUL) are refused, so this check and the SDK always resolve the
|
|
46
|
+
same path.
|
|
47
|
+
2. The `*Operations` hooks of `read`, `grep` and `ls` check the absolute path
|
|
48
|
+
the SDK hands them again. This also covers `read`'s file-name variants and
|
|
49
|
+
symlinks met during reading.
|
|
50
|
+
|
|
51
|
+
`grep` and `find` start `rg` and `fd` on the checked path. Neither follows
|
|
52
|
+
symlinks during traversal by default, so a symlink below the workspace does not
|
|
53
|
+
lead out of it. A refusal is an ordinary tool error,
|
|
54
|
+
`Path is outside the allowed read roots`, without the path.
|
|
55
|
+
|
|
56
|
+
At admission the server checks with `getToolDefinition` that each of these
|
|
57
|
+
names resolves to the server's own definition; otherwise the start fails with
|
|
58
|
+
`pi_session_creation_failed`.
|
|
59
|
+
|
|
60
|
+
The confinement is always on; there is no configuration field. Reading outside
|
|
61
|
+
the workspace has no review use, and a per-profile switch would invite the
|
|
62
|
+
unconfined variant for exactly the profiles that get external tools.
|
|
63
|
+
|
|
64
|
+
### 2. Sandboxed shell, opt-in per profile
|
|
65
|
+
|
|
66
|
+
The optional profile field `shell` takes the single value `"sandboxed"`. It is
|
|
67
|
+
allowed only on `read-only` profiles, not together with
|
|
68
|
+
`grantedExtensionTools` (granted extension tools may write, so the shell's
|
|
69
|
+
read-only guarantee would not hold), and only on Linux; any other combination
|
|
70
|
+
fails config validation with `invalid_execution_profile_config`.
|
|
71
|
+
|
|
72
|
+
Such a profile also gets `bash`, built with `createBashToolDefinition(cwd,
|
|
73
|
+
{ operations, exposeSessionEnvironment: false })` and a custom
|
|
74
|
+
`BashOperations.exec`. Each command runs as
|
|
75
|
+
`/usr/bin/bwrap <fixed arguments> -- /usr/bin/bash -c <command>`:
|
|
76
|
+
|
|
77
|
+
- `--unshare-all` (no network), `--unshare-user` (which bubblewrap requires
|
|
78
|
+
for `--disable-userns`), `--disable-userns`, `--die-with-parent`,
|
|
79
|
+
`--new-session`, `--clearenv` with
|
|
80
|
+
`PATH=/usr/local/bin:/usr/bin:/bin` and `HOME=/tmp/home`;
|
|
81
|
+
- `/usr` and `/etc` read-only; `/bin`, `/lib`, `/lib64` and `/sbin` recreated
|
|
82
|
+
as the host's merged-`/usr` symlinks or bound read-only; `--dev /dev`,
|
|
83
|
+
`--proc /proc`, a tmpfs `/tmp` limited to 512 MiB; no other host path, so the operator's home
|
|
84
|
+
is not visible;
|
|
85
|
+
- the workspace at its host path as an overlay with a temporary upper layer
|
|
86
|
+
(`--overlay-src <workspace> --tmp-overlay <workspace>`): writes succeed
|
|
87
|
+
inside the sandbox and vanish when the command ends;
|
|
88
|
+
- for a linked worktree (a `.git` file instead of a directory), the main
|
|
89
|
+
repository's Git directory as a second temporary overlay at its host path,
|
|
90
|
+
so `git log` and `git blame` work. The server accepts the pointer only if
|
|
91
|
+
`gitdir:` names `<common>/worktrees/<name>`, that directory's `gitdir` file
|
|
92
|
+
points back to the workspace's `.git`, its `commondir` resolves to
|
|
93
|
+
`<common>`, and `<common>` holds `HEAD`, `objects` and `refs`. Otherwise
|
|
94
|
+
nothing extra is bound and Git commands fail inside the sandbox. The read
|
|
95
|
+
tools stay confined to the workspace either way.
|
|
96
|
+
|
|
97
|
+
A command without a `timeout` argument gets a default of 600 seconds; on
|
|
98
|
+
timeout or abort the server kills bubblewrap, and the PID namespace takes every
|
|
99
|
+
process of the sandbox with it.
|
|
100
|
+
|
|
101
|
+
The bubblewrap path is fixed and must be a trusted system binary (root-owned,
|
|
102
|
+
not group- or world-writable); there is no `PATH` lookup. The tool's own
|
|
103
|
+
environment, the controller's environment and `PI_*` session variables never
|
|
104
|
+
reach the command.
|
|
105
|
+
|
|
106
|
+
Fail closed: before a session with `shell: "sandboxed"` starts, the server runs
|
|
107
|
+
`/usr/bin/true` through the same sandbox for the session's workspace, bounded
|
|
108
|
+
and cancelled with the start request. If the binary is untrusted or the probe
|
|
109
|
+
fails, `pi_session_start` fails with the new code `sandbox_unavailable`, class
|
|
110
|
+
`needs_operator`. A command whose sandbox cannot start fails as that command;
|
|
111
|
+
there is never an unsandboxed fallback.
|
|
112
|
+
|
|
113
|
+
### 3. Research tools
|
|
114
|
+
|
|
115
|
+
There is no new mechanism. Operators grant them through the existing
|
|
116
|
+
per-profile `mcpServers` with `readOnly: true`. The documentation advises
|
|
117
|
+
query-style tools over free URL fetching. Now that every read-only profile is
|
|
118
|
+
confined, granting research tools to read-only profiles no longer exposes files
|
|
119
|
+
outside the workspace and the skill directories.
|
|
120
|
+
|
|
121
|
+
### Doctor and capabilities
|
|
122
|
+
|
|
123
|
+
Doctor stays offline. When any profile has `shell: "sandboxed"`, it probes
|
|
124
|
+
each configured workspace with the session's fixed arguments, including a
|
|
125
|
+
linked worktree's main Git directory, and reports `sandbox_unavailable` as an
|
|
126
|
+
error when the trusted bubblewrap binary is missing or a probe fails, for
|
|
127
|
+
example because the installed version lacks the overlay options, unprivileged
|
|
128
|
+
user namespaces are disabled or restricted by AppArmor (see
|
|
129
|
+
[ADR 0008](0008-coding-profile-isolation.md)), or the workspace's filesystem
|
|
130
|
+
cannot be an overlay lower layer. For each
|
|
131
|
+
workspace with an unusable gitfile pointer, it warns with
|
|
132
|
+
`workspace_git_unavailable` when any profile has a sandboxed shell.
|
|
133
|
+
|
|
134
|
+
Capabilities expose `shell` (`"none"` or `"sandboxed"`) for every profile,
|
|
135
|
+
directly after `grantedExtensionTools`. This is configured intent only.
|
|
136
|
+
|
|
137
|
+
## Amendments
|
|
138
|
+
|
|
139
|
+
- ADR 0006: read-only profiles have `read`, `grep`, `find` and `ls`, and
|
|
140
|
+
additionally `bash` with `shell: "sandboxed"`. The read tools are
|
|
141
|
+
workspace-confined.
|
|
142
|
+
- ADR 0010: the active tools of a read-only profile are the above, the granted
|
|
143
|
+
MCP tools and the granted extension tools. A profile with granted extension
|
|
144
|
+
tools cannot have a sandboxed shell.
|
|
145
|
+
|
|
146
|
+
## Contract change and versioning
|
|
147
|
+
|
|
148
|
+
The capabilities field `shell`, the start error `sandbox_unavailable` and the
|
|
149
|
+
confinement of existing read-only profiles change the public contract and need
|
|
150
|
+
minor release **0.13.0** under
|
|
151
|
+
[ADR 0003](0003-post-mvp-maintenance.md#versioning). The confinement breaks
|
|
152
|
+
read-only workflows that read files outside the workspace. This decision does
|
|
153
|
+
not itself bump the version or authorize publication.
|
|
154
|
+
|
|
155
|
+
## Consequences
|
|
156
|
+
|
|
157
|
+
- Read-only workers can run Git, tests and scripts without touching the host
|
|
158
|
+
tree or the network.
|
|
159
|
+
- The sandbox shows only `/usr`, `/etc` and the workspace. Toolchains under the
|
|
160
|
+
home directory or `/opt` (nvm, `~/.local`) are not available; Node.js and Git
|
|
161
|
+
from the distribution are.
|
|
162
|
+
- Lock-dependent tests that need a supported local filesystem can fail on the
|
|
163
|
+
overlay (seen with `test/setup-modes.test.ts` in the #169 probe).
|
|
164
|
+
- The sandbox has no memory, process-count or overlay upper-layer limits.
|
|
165
|
+
A prompt-injected worker can exhaust memory or processes of the operator's
|
|
166
|
+
user until the command times out, as a host-mode coding session already
|
|
167
|
+
can. Limits through a cgroup scope are a possible follow-up.
|
|
168
|
+
- When `bash` truncates long output, the SDK stores the full output in the
|
|
169
|
+
host's temporary directory and names that file. Confined `read` refuses it
|
|
170
|
+
and the sandbox's `/tmp` is a fresh tmpfs, so only the truncated output is
|
|
171
|
+
available; workers should narrow their commands instead.
|
|
172
|
+
- `ls` omits entries that are symlinks pointing outside the read roots,
|
|
173
|
+
without a notice.
|
|
174
|
+
- The confinement checks a path when the call starts. A concurrent writer in
|
|
175
|
+
the same workspace, for example a host-mode coding session, could swap a
|
|
176
|
+
checked directory for a symlink before `rg` or `fd` opens it. Read-only
|
|
177
|
+
workers cannot create such symlinks themselves.
|
|
178
|
+
- Extension hooks and granted extension tools keep running in the server
|
|
179
|
+
process with the operator's rights (ADR 0006/0010); confinement covers the
|
|
180
|
+
built-in tools only.
|
|
181
|
+
- macOS, a VM backend and network allowlists inside the sandbox are out of
|
|
182
|
+
scope. Coding profiles are unchanged (ADR 0008/0009).
|
package/docs/doctor-design.md
CHANGED
|
@@ -52,6 +52,8 @@ and owners are:
|
|
|
52
52
|
| `skill_unreadable` | A selected skill path is not a regular file, is larger than 256 KiB, or cannot be read | `<profile>:<skill-alias>` |
|
|
53
53
|
| `skill_duplicate` | Two selected skill aliases of one profile resolve to the same canonical file | `<profile>:<skill-alias>` |
|
|
54
54
|
| `skill_override_outdated` | Warning. The current fingerprint of a shipped skill differs from the base recorded in the skill bases file next to the configuration file, or the bases file is corrupt | `skill:<name>`, or `skill:bases` for a corrupt file |
|
|
55
|
+
| `sandbox_unavailable` | Only when some profile has `shell: "sandboxed"` ([ADR 0013](adr/0013-hardened-read-only-profiles.md)). Error: `/usr/bin/bwrap` is missing or not a trusted system binary (root-owned, not group- or world-writable), or a probe run of `/usr/bin/true` through the session's fixed sandbox arguments for a configured workspace (its real path, plus the main Git directory of a linked worktree) fails or exceeds 5 seconds, for example because the installed version lacks the overlay options, unprivileged user namespaces are disabled or restricted by AppArmor, or the workspace's filesystem cannot be an overlay lower layer. Reported once per workspace that exists as a directory, however many profiles select the shell | workspace alias |
|
|
56
|
+
| `workspace_git_unavailable` | Only when some profile has `shell: "sandboxed"`. Warning: a workspace's `.git` is a regular file starting with `gitdir:` that is not a usable linked-worktree pointer (`<common>/worktrees/<name>` with a matching back-pointer and `commondir`, and `HEAD`, `objects` and `refs` in `<common>`), so Git commands fail inside the sandbox; the read tools are unaffected. `ok` for a usable pointer; absent for a workspace without a gitfile | workspace alias |
|
|
55
57
|
| `event_spool_root` | Only when `events.delivery` is `spool`. Warning: no absolute `XDG_RUNTIME_DIR` is set in Doctor's environment; a server started that way runs with delivery inactive. Error: the `pi-session-mcp` entry in the runtime directory, or its `events` or `claims` entry, cannot be inspected, is not a directory (a link counts), belongs to another user, or has group or other permission bits. An absent entry is `ok`: the server creates it | `event spool` |
|
|
56
58
|
|
|
57
59
|
`workspace_gitfile` is advice, not a boundary. A linked Git worktree shares `hooks/` and `config` with its main repository, so a coding worker could plant code that runs in the operator's later Git commands. A `.git` gitfile alone does not prove a linked worktree, however: submodules and `git init --separate-git-dir` use gitfiles too, and the content of the three is indistinguishable without following the pointer. Doctor therefore never follows, resolves, stats or reads the pointer target and never runs Git. It calls `lstat` on `<workspace>/.git` and, for a regular file of at most 4 KiB, reads at most that many bytes (without following a symbolic link) and checks for a `gitdir:` prefix followed by a non-blank value. Every such file warns, including submodule and separate-git-dir gitfiles, with a fixed path-free remediation that says "possible linked worktree or Git metadata stored outside the workspace" and recommends a separate full clone for coding workers. The check runs only when a coding profile exists and emits one entry per workspace alias: a warning, or `ok` when no gitfile pointer was found. A `.git` directory, an absent `.git`, an empty, oversized or malformed file, a symbolic link named `.git` (not followed, so nothing is known about its target), and any `lstat` or read error (for example `EACCES`) produce `ok`, never a failure and never a crash; an unreadable `.git` therefore means "not detected", not "verified clean". The warning cannot mask an error: the exit code stays 2 whenever any error exists.
|
package/docs/execution-modes.md
CHANGED
|
@@ -86,7 +86,11 @@ clone.
|
|
|
86
86
|
| `--image <value>` | `node` (default, built-in digest pin) or an existing local 64-character lowercase hex image ID; no arbitrary tags |
|
|
87
87
|
| `--network <value>` | `pasta` (default, not limited) or `none` |
|
|
88
88
|
| `--tags <value>` | `none` (default), or `reachable` for the tags reachable from the selected base commit |
|
|
89
|
-
| `--
|
|
89
|
+
| `--clean <untracked\|all>` | `untracked` (default) keeps ignored files; `all` also removes ignored files on reset and finish |
|
|
90
|
+
| `--cpus <n>` | Hard CPU cap, > 0 through 1024, at most two decimals; `none` removes it; unlimited unless set |
|
|
91
|
+
| `--cpu-shares <n>` | Relative CPU weight under contention, integer 2–262144; `none` removes it; Podman default 1024 |
|
|
92
|
+
| `--memory <size>` | Hard memory cap, 1–9999999 plus lowercase `m` or `g`; `none` removes it; unlimited unless set |
|
|
93
|
+
| `--cache <value>` | `none` (default), `go` for the writer's Go module/build cache, `uv` for its private uv and npm cache, or `npm` for its private npm-only cache |
|
|
90
94
|
| `--base <ref>` | Named local or remote branch for provisioning; also supported by writer reset; not HEAD or a raw commit hash |
|
|
91
95
|
| `--client codex` | No writer effect; coding uses the host registration for either client |
|
|
92
96
|
| `--discard` | Explicitly allow loss of uncollected surplus writers on mode/count changes |
|
|
@@ -194,7 +198,7 @@ unexported work. Do not blindly retry or bypass a refusal by changing profiles.
|
|
|
194
198
|
| Writer operation | Behavior |
|
|
195
199
|
| --- | --- |
|
|
196
200
|
| `--collect --apply` | Bundle export; retain clone |
|
|
197
|
-
| `--reset --apply [--base <ref>]` | Reset to current source base branch, delete extra branches/stash and clean untracked files
|
|
201
|
+
| `--reset --apply [--base <ref>]` | Reset to current source base branch, delete extra branches/stash and clean untracked files, and ignored files with `--clean all`; falls back to recreation if post-reset verification fails |
|
|
198
202
|
| `--recreate --apply` | Rebuild clone and agent copy from the workspace default base, retaining image/settings |
|
|
199
203
|
| `--finish --apply` | Collect, then reset persistent / recreate ephemeral on the workspace default base |
|
|
200
204
|
| `--remove` | Explicit mutation: unregister owned registrations before removing writer state |
|
|
@@ -255,6 +259,9 @@ listed below; dynamic planned and MCP verification outcomes are listed explicitl
|
|
|
255
259
|
| `usage_invalid` | Invalid writer name or argument combination |
|
|
256
260
|
| `doctor_failed` | Relevant blocking Doctor checks failed; repository steps and mutating writer operations also report each blocking check as a separate failed finding with its Doctor check id and target `client: "doctor"`, `scope: "check"`, and as alias the profile or workspace alias that leads the check's subject (the part before `:`, and for a writer name its alias), or `"invalid"` for a global check or when that is not a valid alias |
|
|
257
261
|
| `writer_planned` | Writer would be retained/provisioned |
|
|
262
|
+
| `writer_limits_update_planned` | Existing writer limits would be updated without reprovisioning |
|
|
263
|
+
| `writer_limits_updated` | Existing writer limits updated under its fence |
|
|
264
|
+
| `cgroup_controller_unavailable` | Requested CPU or memory controller is unavailable; mode was not written |
|
|
258
265
|
| `writer_remove_planned` | Surplus writer would be removed |
|
|
259
266
|
| `writer_collect_planned` | Collection planned only |
|
|
260
267
|
| `writer_reset_planned` | Reset planned only |
|
|
@@ -262,7 +269,7 @@ listed below; dynamic planned and MCP verification outcomes are listed explicitl
|
|
|
262
269
|
| `writer_finish_planned` | Finish planned only |
|
|
263
270
|
| `writer_provisioned` | Clone/config/wrapper prepared, not yet accepted |
|
|
264
271
|
| `writer_unchanged` | Provisioned settings already match |
|
|
265
|
-
| `writer_settings_changed` | Existing writer settings or its rendered configuration differ from the current ones; no silent replacement, no acceptance and no registration change ([Isolation setup](isolation-setup.md)) |
|
|
272
|
+
| `writer_settings_changed` | Existing writer settings or its rendered configuration differ from the current ones; no silent replacement, no acceptance and no registration change, and an apply is refused before the mode is written ([Isolation setup](isolation-setup.md)) |
|
|
266
273
|
| `registration_divergent` | Writer registration is foreign or differs from its ownership record; it is retained and the writer stays in legacy launch mode |
|
|
267
274
|
| `writer_reconcile_failed` | Unexpected reconciliation failure |
|
|
268
275
|
| `writer_operation_failed` | Unclassified lifecycle/orchestrator failure |
|
|
@@ -375,6 +382,43 @@ No other uv or npm variables are set, so download and network policy stay the
|
|
|
375
382
|
image's. The directory is per writer: it is never shared between writers and
|
|
376
383
|
never a host cache mount. Setup does not install uv or npm for you.
|
|
377
384
|
|
|
385
|
+
### Resource limits
|
|
386
|
+
|
|
387
|
+
Container writers are unlimited by default. Optional `--cpus <n>` sets a hard
|
|
388
|
+
CPU cap (greater than 0, at most 1024, with at most two decimals).
|
|
389
|
+
`--cpu-shares <n>` sets relative CPU weight under contention (integer 2–262144;
|
|
390
|
+
Podman's default is 1024). The weight only counts against sibling cgroups:
|
|
391
|
+
rootless writers run in the operator's user slice, so `--cpu-shares` makes a
|
|
392
|
+
writer yield to the operator's other containers and processes, not to system
|
|
393
|
+
services such as rootful Docker CI runners in `system.slice`. To protect such
|
|
394
|
+
a service, use the `--cpus` hard cap.
|
|
395
|
+
`--memory <size>` sets a hard memory cap: 1–9999999 followed by lowercase `m`
|
|
396
|
+
or `g`, for example `--memory 512m`.
|
|
397
|
+
|
|
398
|
+
These are fairness measures, not a security boundary. Setup checks that the
|
|
399
|
+
Podman host exposes the `cpu` controller for either CPU option and the
|
|
400
|
+
`memory` controller for memory; missing controllers refuse the mode change
|
|
401
|
+
with `cgroup_controller_unavailable`, including dry-runs. Limits apply to the
|
|
402
|
+
serving container, not throwaway clone/maintenance containers. They update
|
|
403
|
+
existing writer state under its lease without reprovisioning or changing
|
|
404
|
+
boundary acceptance, and take effect at the writer's next launch. Acceptance
|
|
405
|
+
and `--writer <name> --verify` run the serving container under the same
|
|
406
|
+
limits, so a very small cap can make them time out. Unlike the other
|
|
407
|
+
settings, a later mode request that omits a limit flag keeps the recorded
|
|
408
|
+
limit, because limits are outside the settings drift check and would
|
|
409
|
+
otherwise vanish without an error, for example in an upgrade's repeated
|
|
410
|
+
provisioning command. Pass `--cpus none`, `--cpu-shares none` or
|
|
411
|
+
`--memory none` to remove a limit.
|
|
412
|
+
|
|
413
|
+
### npm cache (opt-in)
|
|
414
|
+
|
|
415
|
+
For a Node writer, pass `--cache npm` to mount a private per-writer directory
|
|
416
|
+
at `/pi/npm` with `npm_config_cache=/pi/npm/cache`. This records `cache: "npm"`;
|
|
417
|
+
no other npm variables are set and setup does not install npm. The directory
|
|
418
|
+
is never shared between writers or mounted from a host package cache. It
|
|
419
|
+
survives reset, finish and recreate, and is removed with the writer. Repeat
|
|
420
|
+
`--cache npm` on later container mode requests to retain it.
|
|
421
|
+
|
|
378
422
|
### Base tags (opt-in)
|
|
379
423
|
|
|
380
424
|
Container writers default to `tags: "none"`. Pass `--tags reachable` to bundle
|
|
@@ -384,5 +428,17 @@ importing the selected base tags; tags created by workers are never collected
|
|
|
384
428
|
and do not change the collected-work digest. Changing the tag setting requires
|
|
385
429
|
reprovisioning for settings drift, but does not change boundary acceptance.
|
|
386
430
|
|
|
431
|
+
### Ignored files (opt-in)
|
|
432
|
+
|
|
433
|
+
Container writers default to `clean: "untracked"`, keeping ignored files such as
|
|
434
|
+
`.venv`, `node_modules`, `.env` and build output across reset and a persistent
|
|
435
|
+
writer's finish (an ephemeral writer's finish recreates the clone). Pass
|
|
436
|
+
`--clean all` on a container mode request to remove them with `git clean -ffdx`.
|
|
437
|
+
Repeat this flag on later mode requests to retain it. Per-writer caches at
|
|
438
|
+
`/pi/go`, `/pi/uv` and `/pi/npm` live outside the clone and stay untouched.
|
|
439
|
+
Changing the setting requires reprovisioning for settings drift: collect work,
|
|
440
|
+
run `--writer <name> --remove`, then run the provisioning command again with
|
|
441
|
+
the desired `--clean` value. It does not change boundary acceptance.
|
|
442
|
+
|
|
387
443
|
See [Isolation setup](isolation-setup.md) for boundary prerequisites and what
|
|
388
444
|
setup automates, and the historical manual acceptance evidence linked there.
|
package/docs/installation.md
CHANGED
|
@@ -58,6 +58,20 @@ the same arguments.
|
|
|
58
58
|
`~/.local/state/pi-session-mcp` on the same filesystem type as `/`, which is
|
|
59
59
|
APFS. A home directory on HFS+, exFAT, a network volume or any other
|
|
60
60
|
filesystem fails closed.
|
|
61
|
+
- Optional, Linux only, for the sandboxed shell of a read-only profile
|
|
62
|
+
(`"shell": "sandboxed"`, [ADR 0013](adr/0013-hardened-read-only-profiles.md)):
|
|
63
|
+
[bubblewrap](https://github.com/containers/bubblewrap) at `/usr/bin/bwrap`,
|
|
64
|
+
root-owned and not group- or world-writable, with the options
|
|
65
|
+
`--overlay-src`, `--tmp-overlay` and `--disable-userns`. It runs unprivileged and
|
|
66
|
+
needs unprivileged user namespaces (kernel setting, and on some distributions an
|
|
67
|
+
AppArmor profile that allows the user namespace for `bwrap`; see
|
|
68
|
+
[ADR 0008](adr/0008-coding-profile-isolation.md)). Doctor probes every
|
|
69
|
+
configured workspace and reports `sandbox_unavailable` as an error per
|
|
70
|
+
workspace when it does not work (the workspace's filesystem must be able to be
|
|
71
|
+
an overlay lower layer). The sandbox shows only
|
|
72
|
+
`/usr`, `/etc` and the workspace: toolchains installed under the home
|
|
73
|
+
directory or `/opt` (nvm, `~/.local`) are not available, Node.js and Git from
|
|
74
|
+
the distribution are.
|
|
61
75
|
- Codex CLI or Claude Code. `setup` does not pin client versions. It accepts a
|
|
62
76
|
client whose public `mcp` command output matches the expected contract and
|
|
63
77
|
reports `unsupported` otherwise. The contract was last recorded with Codex CLI
|
|
@@ -169,6 +183,15 @@ Rules:
|
|
|
169
183
|
`azure`; a profile that names the old key must be changed.
|
|
170
184
|
- A profile may grant tools from local stdio MCP servers through `mcpServers`.
|
|
171
185
|
See [External MCP tools in embedded sessions](session-mcp.md).
|
|
186
|
+
- A `read-only` profile may add `"shell": "sandboxed"` to get a `bash` tool
|
|
187
|
+
that runs every command in a bubblewrap sandbox without network, with the
|
|
188
|
+
workspace as a temporary overlay (see the requirements above). It is Linux
|
|
189
|
+
only and cannot be combined with `grantedExtensionTools`; the start fails
|
|
190
|
+
with `sandbox_unavailable` when the sandbox does not work. Without the field
|
|
191
|
+
the profile has no shell.
|
|
192
|
+
- The `read`, `grep`, `find` and `ls` tools of every `read-only` profile accept
|
|
193
|
+
only paths inside the workspace and the directories of the profile's selected
|
|
194
|
+
skill files; there is no field to change that.
|
|
172
195
|
- A profile may load Pi extensions that Pi has installed, through
|
|
173
196
|
`extensions`: a list of at most 8 entries of the form
|
|
174
197
|
`npm:<name>@<exact version>`, each package once. Ranges, tags, `git:`
|
|
@@ -747,7 +770,8 @@ repository step between the install and the client targets:
|
|
|
747
770
|
more; both clients code through the one host `pi-session-mcp` registration.
|
|
748
771
|
Use the same
|
|
749
772
|
settings as before: a writer whose settings or coding profiles changed is
|
|
750
|
-
reported as `writer_settings_changed
|
|
773
|
+
reported as `writer_settings_changed`, the apply is refused before the
|
|
774
|
+
mode is written and the writer is left as it is, and one whose
|
|
751
775
|
coding profile selects an overridden or unshipped `builtin:` skill as
|
|
752
776
|
`container_profile_unsupported`. Such a writer stays `writer_not_ready`
|
|
753
777
|
until you change the profile or remove the override, or collect it,
|
|
@@ -756,6 +780,15 @@ repository step between the install and the client targets:
|
|
|
756
780
|
3. `--apply` and `--verify` the client targets, then `--writer <name> --verify`
|
|
757
781
|
for each writer. Reconnect the host `pi-session-mcp` server after migration
|
|
758
782
|
and client setup. Later reset, finish and recreate need no reconnect.
|
|
783
|
+
On 0.12.0, client-target verify fails with `mcp_failed` once a container
|
|
784
|
+
workspace exists
|
|
785
|
+
([#160](https://github.com/plagor44-ux/pi-session-mcp/issues/160)); 0.12.1
|
|
786
|
+
fixes it.
|
|
787
|
+
4. Migration keeps each clone, so a writer migrated from a release before
|
|
788
|
+
0.12.0 has no commit identity yet, and Doctor reports
|
|
789
|
+
`writer_git_identity_missing`. Collect any work, then
|
|
790
|
+
`--writer <name> --reset --apply` (or `--finish --apply`) each writer;
|
|
791
|
+
reset copies the operator checkout's identity into the clone.
|
|
759
792
|
|
|
760
793
|
The repository step does not count another repository's writers that are not
|
|
761
794
|
ready yet, so the repositories can be accepted one after the other. Releases
|