pi-session-mcp 0.13.1 → 0.14.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 (52) hide show
  1. package/.pi-session-mcp-cli/config.js +21 -3
  2. package/.pi-session-mcp-cli/doctor.js +6 -2
  3. package/.pi-session-mcp-cli/skill-library.js +1 -1
  4. package/.pi-session-mcp-cli/writer-boundary.js +2 -1
  5. package/.pi-session-mcp-cli/writer-lifecycle.js +18 -4
  6. package/CHANGELOG.md +87 -0
  7. package/README.md +1 -1
  8. package/dist/capabilities.d.ts +13 -0
  9. package/dist/capabilities.js +17 -13
  10. package/dist/config.d.ts +3 -1
  11. package/dist/config.js +21 -3
  12. package/dist/doctor.d.ts +1 -1
  13. package/dist/doctor.js +6 -2
  14. package/dist/model-catalog.d.ts +40 -0
  15. package/dist/model-catalog.js +38 -0
  16. package/dist/profile-probe.d.ts +28 -0
  17. package/dist/profile-probe.js +164 -0
  18. package/dist/sdk-pi-adapter.d.ts +1 -1
  19. package/dist/sdk-pi-adapter.js +25 -6
  20. package/dist/setup-cli.d.ts +7 -0
  21. package/dist/setup-cli.js +36 -0
  22. package/dist/setup-main.js +10 -3
  23. package/dist/setup-probe-main.d.ts +14 -0
  24. package/dist/setup-probe-main.js +76 -0
  25. package/dist/setup-probe-result.d.ts +39 -0
  26. package/dist/setup-probe-result.js +62 -0
  27. package/dist/setup-writers-common.js +1 -1
  28. package/dist/skill-library.js +1 -1
  29. package/dist/writer-boundary.d.ts +1 -1
  30. package/dist/writer-boundary.js +2 -1
  31. package/dist/writer-guard.js +1 -1
  32. package/dist/writer-lifecycle.d.ts +1 -0
  33. package/dist/writer-lifecycle.js +18 -4
  34. package/dist/writer-relay.js +12 -1
  35. package/docs/acceptance.md +313 -1
  36. package/docs/adr/0012-leased-writer-pool.md +10 -6
  37. package/docs/adr/0013-hardened-read-only-profiles.md +30 -0
  38. package/docs/adr/0014-profile-probe-and-model-catalog.md +89 -0
  39. package/docs/client-setup.md +18 -12
  40. package/docs/doctor-design.md +1 -0
  41. package/docs/execution-modes.md +25 -3
  42. package/docs/installation.md +87 -0
  43. package/docs/isolation-setup.md +8 -2
  44. package/docs/roadmap.md +20 -1
  45. package/docs/skills.md +42 -25
  46. package/docs/sprints/2026-10-24-s8-writer-migration.md +5 -3
  47. package/docs/sprints/2026-10-29-s12-audit-fixes.md +172 -0
  48. package/docs/threat-model.md +10 -1
  49. package/docs/tool-contracts.md +1 -1
  50. package/package.json +1 -1
  51. package/skills/pi-session-orchestration/SKILL.md +1 -1
  52. package/skills/pi-session-profiles/SKILL.md +124 -0
@@ -182,10 +182,24 @@ pi-session-mcp-setup --writer my-project-w1 --finish --apply
182
182
 
183
183
  Collection exports changed branches via a bundle into the operator's checkout as
184
184
  `refs/remotes/writer-<name>/<branch>`. Uncommitted/untracked work is captured as a
185
- synthetic `<branch>-wip` commit. These refs are **untrusted until reviewed**;
185
+ synthetic `<branch>-wip` commit. A clean detached HEAD whose commit no branch in
186
+ the clone reaches is exported as `<base>-detached` (for example `main-detached`,
187
+ imported as `refs/remotes/writer-<name>/main-detached`); a dirty detached HEAD
188
+ keeps the `<base>-wip` export. A `<base>-detached` or `<branch>-wip` name that
189
+ an exported branch already takes, or that conflicts with an exported branch's
190
+ path (such as a branch `main-detached/topic`), is refused and makes collection
191
+ incomplete (`writer_collect_incomplete`); the other branches are still
192
+ exported. Clone-side Git ignores replace refs. These refs are **untrusted until reviewed**;
186
193
  setup does not merge or cherry-pick them. Collection does not change the clone.
187
- Only a complete export records a digest authorizing later destructive cleanup;
188
- new work invalidates that authorization. Stashes are not exported as branches:
194
+ Only a complete export records a digest authorizing later destructive cleanup,
195
+ and the digest is recorded only when the clone's HEAD commit and every branch
196
+ commit exist in the operator checkout; otherwise collection is incomplete
197
+ (`writer_collect_incomplete`) and no digest is recorded. This includes the base
198
+ commit: if the operator checkout no longer has it (for example after a deleted
199
+ base branch and garbage collection), collect and finish fail this way even on a
200
+ pristine writer, without refused names; fetch the base again or resolve with
201
+ `--reset --discard --apply`. New work invalidates
202
+ that authorization. Stashes are not exported as branches:
189
203
  resolve any needed stash inside the boundary before collection.
190
204
 
191
205
  `--finish` collects, then resets a persistent writer or recreates an ephemeral
@@ -234,6 +248,14 @@ reset updates admission without a server restart. A missing/unusable runtime or
234
248
  stale/unready writer launches a refusal MCP server: all eight tools return the
235
249
  sanitized refusal, with no host fallback. See [Tool contracts](tool-contracts.md).
236
250
 
251
+ Before the relay starts an inner session it compares every public capability
252
+ field of the writer's profile with the host profile: `permissionProfile`,
253
+ `provider`, `model`, `thinkingLevel` and `shell` exactly, and `skills`,
254
+ `extensions` and `grantedExtensionTools` as sets. A mismatch or a missing
255
+ entry counts as `settingsChanged`, starts no inner session and moves on to the
256
+ next writer; if no writer is admitted, the start fails with
257
+ `writer_unavailable` and reason `none_admitted`.
258
+
237
259
  ## Reports and finding codes
238
260
 
239
261
  Reports use sanitized targets `modes/workspace/<alias>` or `writer/<alias>/<name>`
@@ -89,6 +89,89 @@ local user's existing Pi authentication and never prints it. Automated writers
89
89
  keep selected provider credentials in their private boundary agent copy; see
90
90
  [Execution modes](execution-modes.md#writer-storage-launch-and-readiness).
91
91
 
92
+ ## Check profiles: model catalog and probe
93
+
94
+ List the providers and model ids Pi knows locally, with authentication presence
95
+ only (never credential values):
96
+
97
+ ```bash
98
+ pi-session-mcp-setup --list-models
99
+ pi-session-mcp-setup --list-models --json
100
+ ```
101
+
102
+ This is offline and read-only: no provider request, credential refresh, model
103
+ network access or setup lock. It requires no `PI_SESSION_MCP_CONFIG`.
104
+ Providers registered only by Pi extensions are not listed; the report includes
105
+ that note. Invalid catalog ids are omitted and counted in `omittedIds`.
106
+ Catalog creation failures report `catalog_unavailable`.
107
+
108
+ With `PI_SESSION_MCP_CONFIG` already set to the configuration to check:
109
+
110
+ ```bash
111
+ pi-session-mcp-setup --probe-profile review,code --json
112
+ pi-session-mcp-setup --probe-profile review,code --apply --json
113
+ ```
114
+
115
+ The first command is a dry run: Doctor runs offline, followed by profile,
116
+ provider, model and authentication-presence checks. Only `--apply` sends a
117
+ provider turn: one fixed PONG request per profile that passes the offline
118
+ checks, sequentially. Duplicate aliases are checked once in request order.
119
+ Point the variable at a candidate configuration to check it before going live;
120
+ the commands do not write configuration or registrations. The running MCP
121
+ server still needs a restart or reconnect to read changes.
122
+
123
+ The Doctor gate blocks global configuration/build/version errors and errors
124
+ whose subject is a requested profile. Unrelated errors and all warnings are
125
+ listed by check id in `doctor.other`, without blocking. A blocked request
126
+ reports `doctor_failed`; missing or unusable configuration reports
127
+ `config_required` or `config_unusable`. Unexpected operational failures report
128
+ `operation_failed`, and catalog creation failures report `catalog_unavailable`.
129
+ No raw exception text is printed.
130
+
131
+ Each applied probe starts the normal adapter with the resolved profile in a
132
+ private empty temporary directory (mode 0700), not a real repository. This
133
+ directory is created internally, never supplied as an argument or workspace
134
+ alias, and removed after cleanup. Start and turn deadlines are 60 and 120
135
+ seconds; session cleanup is bounded to 10 seconds. Interrupts abort and dispose
136
+ the current session and skip the remaining profiles. Reports never include
137
+ paths, credential values, environment values, the prompt or reply text.
138
+ Foreign stdout from profiles/extensions is suppressed so JSON remains intact;
139
+ sanitized structured diagnostics go to stderr. No turn event spool is used.
140
+
141
+ | Code | Status | Meaning |
142
+ | --- | --- | --- |
143
+ | `probe_planned` | planned | Dry run: offline checks passed |
144
+ | `probe_profile_unknown` | failed | Alias not in the configuration |
145
+ | `probe_provider_unknown` | failed | Provider not in the catalog (offline) |
146
+ | `probe_model_unknown` | failed | Model not in the provider's catalog (offline) |
147
+ | `probe_auth_missing` | failed | Provider has no configured authentication (offline) |
148
+ | `probe_passed` | ok | Session started, reply was `PONG` |
149
+ | `probe_unexpected_reply` | failed | Turn completed with another reply |
150
+ | `probe_turn_failed` | failed | Turn failed or was aborted |
151
+ | `probe_start_failed` | failed | Session creation refused; carries the existing sanitized `PiSessionCreationErrorCode` |
152
+ | `probe_timeout` | failed | Start or turn deadline passed; the session was aborted and disposed |
153
+
154
+ A profile that fails an offline check is not started; the other profiles are
155
+ still checked. Apply findings include `durationMs`. `providerTurn` records
156
+ whether a provider turn was accepted, including turns that failed or timed out;
157
+ a `false` finding did not spend a provider turn. Provider usage and monetary
158
+ cost remain `unknown`.
159
+
160
+ Exit codes: `0` for a successful catalog, a clean dry run (`planned`) or an
161
+ apply where every profile passed; `1` for a failed check or refusal; `64` for
162
+ invalid usage; `130` for SIGINT and `143` for SIGTERM during a probe (both
163
+ report `operation_interrupted`). Both operations run on Linux and macOS and
164
+ need no setup lock. Probe requests cannot be combined with `--target`,
165
+ `--repo`, `--writer`, `--customize-skill`, `--list-models`, `--verify`,
166
+ `--rollback` or `--remove`. `--list-models` is standalone apart from `--json` and the default `--dry-run`.
167
+ Doctor and `--verify` remain provider-free.
168
+
169
+ **Container repositories:** the probe proves the profile on the host, with
170
+ the host's Pi agent directory. Writer acceptance stays with Doctor's
171
+ `container_profile_refused` check and writer provisioning; a host probe does
172
+ not prove a container writer. See [Execution modes](execution-modes.md) and
173
+ [ADR 0014](adr/0014-profile-probe-and-model-catalog.md).
174
+
92
175
  ## 1. Install
93
176
 
94
177
  ### From npm
@@ -701,6 +784,10 @@ choose skills.
701
784
  directory, so concurrent runs cannot lose a recorded base.
702
785
  - A `builtin:` name with no override and no shipped skill makes Doctor report
703
786
  `skill_missing` and the session start fail with `skill_load_failed`.
787
+ - The controller skills `pi-session-orchestration` and `pi-session-profiles`
788
+ are not worker skills: a `builtin:` catalog entry for either makes the
789
+ configuration invalid, and Doctor reports `skill_controller_selected` for
790
+ `configuration:skillCatalog.<alias>`.
704
791
  - An extension allowlist alone makes none of a package's skills available. If a
705
792
  profile relied on them, add a catalog entry with the absolute path of the
706
793
  package's `SKILL.md` and select it; see the migration section of [Pi
@@ -225,12 +225,18 @@ Git metadata can redirect reads to worker-controlled configuration. Setup checks
225
225
  clone layout and runs clone-side Git only in throwaway no-network containers.
226
226
  Collect with `--writer <name> --collect --apply`, then review
227
227
  `refs/remotes/writer-<name>/<branch>` in your own checkout, with dirty work at
228
- `<branch>-wip`. Imported refs are untrusted until reviewed.
228
+ `<branch>-wip` and a clean detached HEAD at `<base>-detached` (for example
229
+ `main-detached`). Imported refs are untrusted until reviewed.
229
230
 
230
231
  After taking over the result and closing its coding session, run
231
232
  `pi-session-mcp-setup --writer <name> --finish --apply` before another task.
232
233
  It collects then resets (persistent) or recreates (ephemeral). If any branch name
233
- was refused, collection is incomplete and finish stops. Rename inside the writer
234
+ was refused, collection is incomplete and finish stops; a `<base>-detached` or
235
+ `<branch>-wip` name that an exported branch takes or whose path it conflicts
236
+ with is refused the same way. The release digest is
237
+ recorded only when the clone's HEAD commit and every branch commit exist in the
238
+ operator checkout, otherwise collection is `writer_collect_incomplete` and no
239
+ digest is recorded. Rename inside the writer
234
240
  or have the operator explicitly resolve loss with `--reset --discard --apply`.
235
241
  Finish returns persistent and ephemeral writers to the workspace default base.
236
242
  No reconnect is needed after finish, reset or recreate: each session starts a
package/docs/roadmap.md CHANGED
@@ -23,7 +23,8 @@ mean they are implemented or that their gates passed.
23
23
  workspace-confined read tools for every read-only profile and the opt-in
24
24
  bubblewrap shell (`shell: "sandboxed"`), released in minor release 0.13.0
25
25
  together with #162, #163, #165 and #166.
26
- - 2026-10-29: the post-0.13.0 review findings in one pull request, unreleased:
26
+ - 2026-10-29: the post-0.13.0 review findings in one pull request, released in
27
+ patch release 0.13.1:
27
28
  stale waiter-lock reclamation in the wake hook
28
29
  ([#179](https://github.com/plagor44-ux/pi-session-mcp/issues/179)), per-writer
29
30
  reconcile failures ([#180](https://github.com/plagor44-ux/pi-session-mcp/issues/180))
@@ -31,6 +32,24 @@ mean they are implemented or that their gates passed.
31
32
  ([#181](https://github.com/plagor44-ux/pi-session-mcp/issues/181)). #120 still
32
33
  waits for a Pi SDK release: 1.4.0 is the newest and still pins `undici` 8.10.2,
33
34
  which `npm audit` now reports with eight advisories.
35
+ - 2026-10-29: agent-driven execution-profile setup
36
+ ([#139](https://github.com/plagor44-ux/pi-session-mcp/issues/139)), released in
37
+ minor release 0.14.0 together with the refusal of controller skills as worker
38
+ skills ([#188](https://github.com/plagor44-ux/pi-session-mcp/issues/188)):
39
+ the offline model catalog `pi-session-mcp-setup --list-models`, the opt-in
40
+ provider probe `--probe-profile <alias> --apply` (one fixed PONG turn per
41
+ profile), and the shipped controller skill `pi-session-profiles`, with which a
42
+ controlling agent creates, changes or retires profiles without a guided
43
+ dialog. Recorded in [ADR 0014](adr/0014-profile-probe-and-model-catalog.md).
44
+ - 2026-10-30: [Sprint 12](sprints/2026-10-29-s12-audit-fixes.md) fixes three
45
+ audit findings on `9d9cf8c` in one pull request, released in patch release
46
+ 0.14.1: read-only context files confined to the workspace
47
+ ([#192](https://github.com/plagor44-ux/pi-session-mcp/issues/192)), detached
48
+ `HEAD` collection and the release-digest gate
49
+ ([#193](https://github.com/plagor44-ux/pi-session-mcp/issues/193)) and the
50
+ full writer profile comparison in the relay
51
+ ([#194](https://github.com/plagor44-ux/pi-session-mcp/issues/194)). Levels 1
52
+ to 3 passed for the candidate. #120 still waits for a Pi SDK release.
34
53
 
35
54
  The explicit 2026-10-17 user request adds a bounded development-only
36
55
  [token-footprint measurement](sprints/2026-10-17-token-footprint.md), following
package/docs/skills.md CHANGED
@@ -8,12 +8,12 @@ its system prompt. The model may read the file when a task matches the skill's
8
8
  description. Pi Session MCP lets the operator register skill files in a catalog
9
9
  and select them per execution profile. MCP callers never choose skills.
10
10
 
11
- The package ships three skills and keeps them in a skill library together with
12
- the operator's own overrides. `pi-session-mcp-setup --apply` also installs the
13
- controller skill into Claude Code and Codex. Nothing needs to be linked by hand.
11
+ The package ships four skills and keeps them in a skill library together with
12
+ the operator's own overrides. `pi-session-mcp-setup --apply` also installs both
13
+ controller skills into Claude Code and Codex. Nothing needs to be linked by hand.
14
14
  Sections: [Configuration](#configuration), [The skill library](#the-skill-library),
15
15
  [Customize and add skills](#customize-and-add-skills), [Install the
16
- orchestration skill into Codex and Claude Code](#install-the-orchestration-skill-into-codex-and-claude-code)
16
+ controller skills into Codex and Claude Code](#install-the-controller-skills-into-codex-and-claude-code)
17
17
  and [Upgrade from 0.8.0](#upgrade-from-080).
18
18
 
19
19
  ## Configuration
@@ -96,6 +96,11 @@ Doctor reports `config_invalid`. Stderr shows the stable startup code
96
96
  for example two aliases for `builtin:pi-session-review`;
97
97
  - a catalog value that violates the rules above (including a malformed
98
98
  `builtin:` name), or more than 64 entries;
99
+ - a catalog value `builtin:pi-session-orchestration` or
100
+ `builtin:pi-session-profiles`, selected or not: the catalog serves Pi
101
+ workers, and the controller skills are for the controlling agent only. Doctor
102
+ reports `skill_controller_selected` for `configuration:skillCatalog.<alias>`
103
+ instead of `config_invalid`;
99
104
  - more than 16 selected aliases.
100
105
 
101
106
  ## The skill library
@@ -143,7 +148,7 @@ The shipped example configuration registers the two worker skills with
143
148
 
144
149
  ### Shipped skills
145
150
 
146
- The package ships three skills under `skills/`. Each has one canonical copy and
151
+ The package ships four skills under `skills/`. Each has one canonical copy and
147
152
  frontmatter with only `name` and `description`.
148
153
 
149
154
  | Skill | For | Use |
@@ -151,6 +156,7 @@ frontmatter with only `name` and `description`.
151
156
  | `pi-session-implementation` | A Pi worker | One bounded implementation task and a fixed final report. Register it as `builtin:pi-session-implementation` and select it on coding profiles. |
152
157
  | `pi-session-review` | A Pi worker | A review of a fixed target with findings and evidence. Register it as `builtin:pi-session-review` and select it on read-only profiles. |
153
158
  | `pi-session-orchestration` | The controlling Claude Code or Codex agent | How to run Pi worker sessions through this server. `setup --apply` installs it into the clients. It is not a Pi worker skill: do not select it in a profile. |
159
+ | `pi-session-profiles` | The controlling agent | Create, change, tune, rename or retire execution profiles and change container repository profile selections. Uses the offline model catalog and a bounded profile probe, preserves writer work, and reports evidence. `setup --apply` installs it into clients; do not select it in a Pi worker profile. |
154
160
 
155
161
  The worker skills ask for concise final reports: status, actual changed paths,
156
162
  checks with exit codes, failures, blockers, risks and repository-relative
@@ -287,18 +293,19 @@ equals a shipped skill replaces that skill instead of adding one.
287
293
  Where `setup` is unsupported, create an override by copying a shipped skill
288
294
  directory by hand. Doctor does not track a copy that has no recorded base.
289
295
 
290
- ## Install the orchestration skill into Codex and Claude Code
296
+ ## Install the controller skills into Codex and Claude Code
291
297
 
292
- The orchestration skill is for the controlling agent, not for a Pi worker.
293
- `pi-session-mcp-setup --apply --target <target>` installs it after the
294
- registration, as a real copy of the effective skill: your override if you have
295
- one, else the shipped skill. Clients discover it without further steps.
298
+ `pi-session-orchestration` and `pi-session-profiles` are for the controlling
299
+ agent, not for a Pi worker. `pi-session-mcp-setup --apply --target <target>`
300
+ installs both after the registration, as real copies of the effective skills:
301
+ your per-skill override if present, else the shipped skill. Clients discover
302
+ them without further steps.
296
303
 
297
- | Target | Skill directory |
304
+ | Target | Skill directories |
298
305
  | --- | --- |
299
- | `codex:user:pi-session-mcp` | `~/.agents/skills/pi-session-orchestration` |
300
- | `claude-code:user:pi-session-mcp` and `claude-code:local:pi-session-mcp` | `~/.claude/skills/pi-session-orchestration` (one shared directory) |
301
- | `claude-code:project:pi-session-mcp` | `<project>/.claude/skills/pi-session-orchestration`, where `<project>` is the directory in which `setup` runs |
306
+ | `codex:user:pi-session-mcp` | `~/.agents/skills/pi-session-orchestration` and `~/.agents/skills/pi-session-profiles` |
307
+ | `claude-code:user:pi-session-mcp` and `claude-code:local:pi-session-mcp` | `~/.claude/skills/pi-session-orchestration` and `~/.claude/skills/pi-session-profiles` (shared by both scopes) |
308
+ | `claude-code:project:pi-session-mcp` | `<project>/.claude/skills/pi-session-orchestration` and `<project>/.claude/skills/pi-session-profiles`, where `<project>` is the directory in which `setup` runs |
302
309
 
303
310
  Claude Code reads personal skills in `~/.claude/skills/<name>/SKILL.md` and
304
311
  project skills in `.claude/skills/<name>/SKILL.md`
@@ -306,8 +313,9 @@ project skills in `.claude/skills/<name>/SKILL.md`
306
313
  skills in `~/.agents/skills`
307
314
  ([Codex skills](https://learn.chatgpt.com/docs/build-skills)).
308
315
 
309
- Every `setup` result has a second finding per target, with the same client and
310
- scope and the alias `pi-session-orchestration`. A skill problem never blocks
316
+ Every eligible target has two skill findings in addition to its registration
317
+ finding, with the same client and scope and aliases `pi-session-orchestration`
318
+ and `pi-session-profiles`. A skill problem never blocks
311
319
  the registration, but a `divergent` or `failed` skill finding makes the overall
312
320
  status `failed` (exit 1), so it is not overlooked.
313
321
 
@@ -432,7 +440,7 @@ The directories of updates by 0.8.0 had random names. Setup never searches for
432
440
  or deletes them; you may delete them by hand. `--dry-run` and `--verify` do not
433
441
  report leftover intermediates.
434
442
 
435
- ### Customize the controller skill
443
+ ### Customize the controller skills
436
444
 
437
445
  Never edit the installed copy: it becomes divergent. Instead:
438
446
 
@@ -441,13 +449,18 @@ Never edit the installed copy: it becomes divergent. Instead:
441
449
  3. `pi-session-mcp-setup --apply --target <target>` again. The unmodified
442
450
  installed copy is replaced (`skill_updated`).
443
451
 
452
+ The same steps work for `pi-session-profiles`: use
453
+ `pi-session-mcp-setup --customize-skill pi-session-profiles` to plan the copy,
454
+ then add `--apply`, edit its override and reapply your client targets. Each
455
+ skill has independent ownership, update, verification and removal behavior.
456
+
444
457
  To recover after editing the installed copy, move your edits into the override,
445
458
  delete the installed copy, and run `--apply` again.
446
459
 
447
460
  ### Scope of what setup installs
448
461
 
449
- Setup installs only `pi-session-orchestration` into clients. The worker skills
450
- are for Pi profiles and are used through `builtin:`. Controller skills of your
462
+ Setup installs only `pi-session-orchestration` and `pi-session-profiles` into
463
+ clients. The worker skills are for Pi profiles and are used through `builtin:`. Controller skills of your
451
464
  own belong in the clients' native skill directories; setup does not manage
452
465
  them.
453
466
 
@@ -457,8 +470,9 @@ Where `setup` does not run, copy the directory; do not link it. Copy
457
470
  `skills/pi-session-orchestration` from the installed package (in an npm
458
471
  installation `"$(npm root -g)/pi-session-mcp/skills/pi-session-orchestration"`)
459
472
  to `~/.claude/skills/` or `.claude/skills/` of a project for Claude Code, or to
460
- `~/.agents/skills/` for Codex. Repeat the copy after a package update; nothing
461
- keeps a manual copy current. Do not replace an existing skill of the same name
473
+ `~/.agents/skills/` for Codex. Also copy `skills/pi-session-profiles` from the
474
+ same package root into the same client skill directory. Repeat both copies
475
+ after a package update; nothing keeps a manual copy current. Do not replace an existing skill of the same name
462
476
  without checking it first.
463
477
 
464
478
  Neither client needs a skill to call the Pi Session MCP tools; the skill only
@@ -471,8 +485,11 @@ setup](client-setup.md#run-codex-without-prompts) describes.
471
485
 
472
486
  Pi workers never discover skills on their own in Pi Session MCP. Register the
473
487
  worker skills with `builtin:` and select them on profiles, as described above.
474
- Do not select the orchestration skill for a Pi worker: a worker has only the
475
- tools of its profile and is not a controller of other Pi sessions.
488
+ Neither controller skill can be selected for a Pi worker: a worker has only the
489
+ tools of its profile and is not the controlling agent, so the configuration
490
+ rejects `builtin:pi-session-orchestration` and `builtin:pi-session-profiles` as
491
+ catalog values. A catalog entry with a plain path to a copy of a controller
492
+ skill is not detected; do not add one.
476
493
 
477
494
  ## What happens at session start
478
495
 
@@ -583,8 +600,8 @@ to.
583
600
  Skills are operator-managed prompt content. The override directory and its
584
601
  bases file are operator-trusted configuration next to the configuration file,
585
602
  never a workspace; keep them out of every workspace and writable only by you.
586
- Setup copies the effective orchestration skill into the clients' skill
587
- directories, where it becomes instructions for the controlling agent, so review
603
+ Setup copies the effective controller skills into the clients' skill
604
+ directories, where they become instructions for the controlling agent, so review
588
605
  an override like code. In a `coding` profile a skill's
589
606
  instructions are executed with that profile's tools, so review a skill like
590
607
  code. A skill grants no rights: the tools of the permission profile stay the
@@ -4,9 +4,11 @@ Planning basis: main `47b8d26` (Sprint 7 merged), installed release 0.11.2.
4
4
  Tracking: [#79](https://github.com/plagor44-ux/pi-session-mcp/issues/79),
5
5
  prerequisite [#121](https://github.com/plagor44-ux/pi-session-mcp/issues/121).
6
6
 
7
- Status: **planned**. The maintainer made the choices on 2026-10-25; see
8
- [Decisions](#decisions-2026-10-25). Nothing below has been changed on any
9
- machine yet.
7
+ Status: **in progress** — steps 1–4 are done (pilot `pi-session-mcp`,
8
+ `node-app`, the operator images, go-app) and steps 5–7 are open; see
9
+ [Progress (2026-10-27)](#progress-2026-10-27) and the v0.11.6 and v0.11.8
10
+ post-release evidence in [Acceptance](../acceptance.md). The maintainer made
11
+ the choices on 2026-10-25; see [Decisions](#decisions-2026-10-25).
10
12
 
11
13
  ## Mandate
12
14
 
@@ -0,0 +1,172 @@
1
+ # Sprint 12: audit findings F1–F3
2
+
3
+ Planning basis: main `9d9cf8ca49e6e34dc16be6846b9324ad26ad92b9` (merge of PR
4
+ #191), published 0.14.0, 2026-10-29. Entry: an external audit of that commit
5
+ reported three defects with reproductions. This file plans the work and records
6
+ it as it lands; it claims nothing that its progress log does not state with
7
+ evidence.
8
+
9
+ Branch: `fix/s12-audit-findings`, one pull request, no release.
10
+
11
+ ## Preflight (2026-10-29)
12
+
13
+ - main and `origin/main` at `9d9cf8c`; open issues #79, #120, #144; no open
14
+ pull requests; clean operator checkout; no other worktree in use.
15
+ - Doctor: no errors; only the known `workspace_gitfile` and
16
+ `workspace_mode_unset` warnings of the host worktree aliases.
17
+ - Level 1 on `9d9cf8c` in a clean detached worktree on the workstation:
18
+ `npm ci`, `npm run typecheck`, `npm run build:cli`,
19
+ `NODE_OPTIONS=--unhandled-rejections=strict npm test` (527 files, 4005
20
+ passed, 14 skipped, 0 failed, no unhandled error) and `npm run build` all exit
21
+ 0. The audit's cloud run of the same commit reported 70 failed tests and four
22
+ unhandled errors; they do not reproduce here, so they are not attributed to
23
+ the product. The audit environment is not available to separate them further.
24
+ - Both `pi-session-mcp` writers were at `baseline` on an older main and were
25
+ reset without `--discard` to `9d9cf8c` (`writer_reset`).
26
+
27
+ ## Findings, re-checked on `9d9cf8c`
28
+
29
+ | Id | Issue | Reproduction by the controller | Result |
30
+ | --- | --- | --- | --- |
31
+ | F1 | [#192](https://github.com/plagor44-ux/pi-session-mcp/issues/192) | Synthetic workspace whose `AGENTS.md` links to an outside marker file; `PI_CODING_AGENT_DIR` isolated; faux provider | Confined `read` refuses; `getAgentsFiles()` contains the marker; the real `SdkPiSessionAdapter` sends it as `project_instructions` to the faux provider. Confirmed. |
32
+ | F2 | [#193](https://github.com/plagor44-ux/pi-session-mcp/issues/193) | Real Git; boundary scripts run by an injected runner | Detached `HEAD` commit, clean tree: remove before collect `writer_uncollected`; collect `nothing_to_collect`, 0 refs, digest stored; commit absent from the operator checkout; finish `writer_reset`; remove `writer_removed`. Feature-branch counterproof exports the commit. Confirmed; not yet a Podman result. |
33
+ | F3 | [#194](https://github.com/plagor44-ux/pi-session-mcp/issues/194) | Real relay over stdio, fake writer server fixture | Identical profiles admitted; model drift refused with `settingsChanged`; host `skills: ["pi-session-implementation"]` against inner `skills: []` admitted. Confirmed. |
34
+
35
+ ## Decisions taken at planning
36
+
37
+ - F1: for read-only profiles every resource loader keeps only context files
38
+ whose real path lies inside the workspace's real path. Context files of
39
+ ancestor directories above the workspace, including a main repository above a
40
+ linked worktree, are no longer loaded for read-only profiles: they are outside
41
+ the read roots of ADR 0013. Coding profiles keep the SDK's behaviour.
42
+ - F2: smallest fix inside the existing collection contract; the export name for
43
+ a detached `HEAD` is a documented behaviour addition, no new public code.
44
+ - F3: the relay compares every public capability field a writer reports, with
45
+ set semantics for skills, extensions and granted extension tools.
46
+ - #120: still waiting for an SDK release (see below); no override, no
47
+ `npm audit fix --force`.
48
+ - No release on this branch.
49
+
50
+ ## Roles and routing
51
+
52
+ `pi_capabilities_get` and `--list-models` on 2026-10-29 settle which requested
53
+ models exist as authenticated profiles:
54
+
55
+ | Requested | Profile used | Status |
56
+ | --- | --- | --- |
57
+ | GPT 6.1 Sol | `code` (openai `gpt-6.1-sol`) | available |
58
+ | GPT 6.1 Terra | none | unavailable: the catalog lists no `gpt-6.1-terra`; not substituted with another version |
59
+ | DeepSeek | `code-deepseek` (`deepseek-flash`) | available |
60
+ | GLM 5.3 | `review-glm`, `code-glm` (`zai-glm-5-3`) | available |
61
+ | Mistral Large 4 | `code-mistral` (`mistral-large-4`) | available |
62
+ | Sonnet 5.5 | no profile; provider `anthropic` authenticated | added only in an expendable test configuration (acceptance item D) |
63
+ | Grok 4.7 | `review-grok` (xai `grok-4.7`) | available: the R2 turn completed |
64
+
65
+ agy (Antigravity CLI, Gemini) plans the work packages, dependencies and review
66
+ routing; Pi workers implement and review; the controller integrates, verifies
67
+ and is the only editor of this file, the roadmap, `docs/acceptance.md` and the
68
+ CHANGELOG.
69
+
70
+ ## Tasks
71
+
72
+ | Id | Task | Route | Change area |
73
+ | --- | --- | --- | --- |
74
+ | P1 | Work packages, dependencies, review routing | agy | none |
75
+ | P2 | Independent regression test plan | `review-glm` | none |
76
+ | T1 | F1, #192 | `pi-session-mcp` writer, `code` | `src/sdk-pi-adapter.ts`, `src/confined-tools.ts`, tests |
77
+ | T2 | F2, #193 | `pi-session-mcp` writer, `code-mistral` (agy's fallback for the unavailable Terra) | `src/writer-lifecycle.ts`, tests |
78
+ | T3 | F3, #194 | `pi-session-mcp` writer, `code-deepseek` | `src/writer-relay.ts`, `src/capabilities.ts`, tests, relay fixture |
79
+ | R1 | Security review of T1 and T3 | Sonnet 5.5, read-only | none |
80
+ | R2 | Adversarial review of T2 and the Git lifecycle checks | `review-grok` | none |
81
+ | D1 | Documentation and issue reconciliation (#120, ADR 0014 status, Sprint 8 status, #79, #144) | `pi-session-mcp` writer, `code-mistral`; controller as editor | `docs/`, `skills/pi-session-orchestration/SKILL.md` |
82
+ | A | Live acceptance A–E | controller | evaluation record |
83
+
84
+ ## Progress log
85
+
86
+ Recorded as work lands on the branch.
87
+
88
+ - 2026-10-29: issues #192, #193 and #194 filed; this plan written before any
89
+ implementation.
90
+ - P1: agy (`gemini-3.8-flash-high`, plan mode) proposed the packages,
91
+ `code-mistral` as the F2 author in place of Terra, Sonnet for R1 and Grok
92
+ with a fallback for R2, and at most three review rounds. Adopted.
93
+ - P2: `review-glm` wrote the regression test plan; its edge cases (absent
94
+ against empty set fields, real-Git collection, a Podman case) went into the
95
+ task prompts.
96
+ - T3 (`code-deepseek`, writer w2): collected as
97
+ `writer-pi-session-mcp-w2/fix/f3-relay-profile-drift`; the controller ran the
98
+ new tests against the old source (7 fail) and the fix (all pass); integrated
99
+ as `230e60e`, writer finished (`writer_reset`).
100
+ - T1 (`code`, writer w1): collected as
101
+ `writer-pi-session-mcp-w1/fix/f1-context-files`; new tests against the old
102
+ source 9 of 12 fail, with the fix 148 of 148 pass; integrated as `a0a7d87`,
103
+ writer finished. The container-writer suite showed only the eight known
104
+ lock and hook timeouts (`override-lock`, `skill-customize-concurrency`,
105
+ `wake-hook`).
106
+ - R1 (Sonnet 5.5, read-only, through the test configuration of item D):
107
+ `NON_BLOCKING_ONLY`. Fixed by the controller: kept context files are read
108
+ again from their verified real path (`79e8a7a`), the set comparison is
109
+ symmetric with a duplicated-entry test (`3e14279`); the shadowing case is
110
+ covered by a test and documented. Not changed: synchronous `realpathSync` in
111
+ the loader override (the SDK hook is synchronous) and the trust in the
112
+ writer's own capability report, which is the boundary the relay already
113
+ assumes.
114
+ - T2 (`code-mistral`, writer w2): collected as
115
+ `writer-pi-session-mcp-w2/fix/f2-detached-collect`; the new real-Git tests
116
+ against the old source 3 of 8 fail, with the fix 8 of 8 pass, and the F2
117
+ reproduction now gets `writer_collected`; integrated as `236ca28`, writer
118
+ finished. The controller added the Podman case (`6ee7add`): with real
119
+ rootless Podman and the pinned Node image it passes, against the old source
120
+ it fails.
121
+ - D1 (`code-mistral`, writer w1): collected as
122
+ `writer-pi-session-mcp-w1/docs/s12-reconcile`; split by issue into
123
+ `f5a9712`, `58e0e7b`, `17845ff` and `ebe3c52`. Controller edits: the
124
+ shadowing and re-read rule in the ADR 0013 amendment, and the relay wording
125
+ (a mismatch counts as `settingsChanged`; `writer_unavailable` with
126
+ `none_admitted` only when no writer is admitted). Writer finished.
127
+ - R2 (`review-grok`, Grok 4.7, read-only): `NON_BLOCKING_ONLY`. Verified by
128
+ the controller and fixed in `1dfe4da`: a derived `-wip` or `-detached` name
129
+ that conflicts with an exported branch path aborted the export under
130
+ `set -eu` (reproduced with a branch `main-detached/topic`); a worker-made
131
+ `git replace --graft` made an unexported detached commit look reachable
132
+ (Grok's variant with a plain replacement did not reproduce, the graft did);
133
+ the reachability gate now also runs on `nothing_to_collect`. Recorded as
134
+ limits, not changed: an index-only staged change that the worktree reverted
135
+ (pre-existing), a base name of 192 to 200 characters (fails closed), and a
136
+ runner that reports a fetch it did not do (outside the threat model).
137
+ - Final integration review (Sonnet 5.5, read-only, on `896e29d`):
138
+ `NON_BLOCKING_ONLY`. Fixed: the in-boundary guard probe now ignores replace
139
+ refs too (`9426682`, with a test that fails without it), the BOM pattern is an
140
+ escape (`5d9f15b`), and execution modes names the pruned-base case of the
141
+ digest gate. Not changed: `inspectWork.head` stays optional because required
142
+ would break type-checked mocks outside this change.
143
+ - Acceptance: see
144
+ [Sprint 12 candidate acceptance](../acceptance.md#sprint-12-candidate-acceptance-2026-10-30).
145
+ Level 1 green on `9426682`; Level 2 green on the packed `896e29d`; live items
146
+ B, C and D passed on expendable configurations and were rolled back; the
147
+ Level 3 ten-task cycle 2 passed (10 of 10) after cycle 1 failed on fixture and
148
+ harness design (5 of 10, kept unchanged).
149
+
150
+ ## Results
151
+
152
+ | Finding | Status | Commits | Evidence |
153
+ | --- | --- | --- | --- |
154
+ | F1 #192 | fixed | `a0a7d87`, `79e8a7a`, `5d9f15b`; docs `f5a9712` | `test/read-only-context-files.test.ts` (fails on `9d9cf8c`), faux-provider context check, R1 and final review |
155
+ | F2 #193 | fixed | `236ca28`, `6ee7add`, `1dfe4da`, `9426682`; docs `58e0e7b`, `896e29d` | real-Git collect tests, Podman test, guard test (each fails on the source before), R2 and final review |
156
+ | F3 #194 | fixed | `230e60e`, `3e14279`; docs `17845ff` | `test/writer-relay.test.ts` drift tests (fail on `9d9cf8c`), live refusal with `settingsChanged`, R1 and final review |
157
+
158
+ Remaining risk: #120 is unchanged. `npm audit --omit=dev` on 2026-10-29 still
159
+ reports `undici` 8.10.2 (high) through `@earendil-works/pi-coding-agent` 1.4.0
160
+ (moderate), eight advisories fixed in `undici` >= 8.12.2; the only offered fix
161
+ is a semver-major downgrade. No override and no `npm audit fix --force`.
162
+
163
+ Kept open: #79 (Sprint 8 steps 5 to 7 are not done), #120 and #144 (no macOS
164
+ claim). The sandbox resource limits of ADR 0013 are the follow-up issue
165
+ [#195](https://github.com/plagor44-ux/pi-session-mcp/issues/195), not part of
166
+ this sprint.
167
+
168
+ Cleanup: all controller Pi sessions closed; both `pi-session-mcp` writers
169
+ finished (`writer_reset`); the test writer `s12-probe-w1` removed; expendable
170
+ configurations, the synthetic repository, the pack prefix and the
171
+ controller's worktrees removed.
172
+
@@ -16,7 +16,16 @@ Pi Session MCP trusts the local process that launches and speaks MCP over inheri
16
16
  (arguments the SDK would rewrite, such as a leading `@` or `~`, a `file:`
17
17
  URL, Unicode spaces or NUL, are refused so both resolve the same path), and
18
18
  the `*Operations` hooks of `read`, `grep` and `ls` check the absolute path
19
- again. `rg` and `fd` do not follow symlinks during traversal. A profile with
19
+ again. `rg` and `fd` do not follow symlinks during traversal. For read-only
20
+ profiles the resource loader likewise keeps only Pi context files
21
+ (`AGENTS.override.md`, `AGENTS.md`, `AGENTS.MD`, `CLAUDE.md`, `CLAUDE.MD`)
22
+ whose real path lies inside the workspace's real path, on every reload and
23
+ before the first provider request: a symlinked context file that resolves
24
+ outside the workspace, a dangling one and the context files of ancestor
25
+ directories above the workspace (including a main repository above a linked
26
+ worktree) are not loaded, while the linked worktree's own file inside the
27
+ workspace is. Coding profiles keep the SDK behaviour (the working directory
28
+ and its ancestors). A profile with
20
29
  `shell: "sandboxed"` also gets `bash`, run as `/usr/bin/bwrap` (fixed path,
21
30
  root-owned and not group- or world-writable, no `PATH` lookup) with
22
31
  `--unshare-all`, `--disable-userns`, a cleared environment, no network, `/usr`
@@ -40,7 +40,7 @@ records name the package version and the build they ran on, see
40
40
  ```json
41
41
  {
42
42
  "ok": true,
43
- "server": { "name": "pi-session-mcp", "version": "0.13.1" },
43
+ "server": { "name": "pi-session-mcp", "version": "0.14.1" },
44
44
  "configuration": {
45
45
  "fingerprint": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
46
46
  "reloadPolicy": "restart-required"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-session-mcp",
3
- "version": "0.13.1",
3
+ "version": "0.14.1",
4
4
  "description": "Local stdio MCP server for controlled Pi coding-agent sessions",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -122,7 +122,7 @@ You integrate. Workers never integrate each other's changes. Resolve conflicts b
122
122
 
123
123
  After taking over the result and closing its coding session, before the next task:
124
124
 
125
- 1. Run `pi-session-mcp-setup --writer <name> --collect --apply` from the operator environment. Review the collected refs in the operator's checkout: `refs/remotes/writer-<name>/<branch>`, with uncommitted work as `<branch>-wip`. They are untrusted until reviewed. Integrate only verified work yourself.
125
+ 1. Run `pi-session-mcp-setup --writer <name> --collect --apply` from the operator environment. Review the collected refs in the operator's checkout: `refs/remotes/writer-<name>/<branch>`, with uncommitted work as `<branch>-wip` and a clean detached HEAD as `<base>-detached`. They are untrusted until reviewed. Integrate only verified work yourself.
126
126
  2. Run `pi-session-mcp-setup --writer <name> --finish --apply`. It collects again, then resets a persistent writer or recreates an ephemeral writer. Do not reuse it until this succeeds. A refused branch name makes collection incomplete and finish stops: ask the operator to resolve it by renaming inside the boundary or explicitly discarding with `--reset --discard --apply`. Finish returns it to the workspace default base. No reconnect is needed after reset, finish or recreate. A setup operation on a writer held by a session fails with `writer_busy` after a short wait; close the session and allow launcher cleanup to finish.
127
127
  3. A coding start needs the writer's clone at its baseline, even after a complete collection; otherwise it fails with the controller-action code for uncollected work in [references/start-errors.md](references/start-errors.md). To let a fresh session continue from collected work, for example after a failed turn or for a review fix round, create a local branch at the collected ref and run `pi-session-mcp-setup --writer <name> --reset --base <branch> --apply`, then call `pi_session_start` on `pi-session-mcp` with the coding profile and `base: "<branch>"`. Read `writer` from that result for collection. Default starts cannot use a writer moved to the fix branch.
128
128
  4. Never run Git inside the writer's clone as the host user, including direct fetch from that clone. Use setup's bundle collection and inspect the imported refs in your own checkout. Never discard unreviewed work just to admit the next task.