pi-session-mcp 0.8.1 → 0.8.3

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.
@@ -53,7 +53,7 @@ Changes that keep the public MCP contract (tools, fields, states, error codes) s
53
53
 
54
54
  ## Revisit triggers
55
55
 
56
- - **E (isolation research):** coding profiles are used on non-disposable repositories or with untrusted content, at the latest before a local writer runs on real repositories.
56
+ - **E (isolation research):** coding profiles are used on non-disposable repositories or with untrusted content, at the latest before a local writer runs on real repositories. This trigger fired on 2026-10-15: coding profiles had written on non-disposable repositories. Sprint 4 authorizes the isolation decision as the one next investment, an options comparison in ADR 0008, not an implementation; see [#79](https://github.com/plagor44-ux/pi-session-mcp/issues/79) and the [Sprint 4 decision](../sprints/2026-10-15-s4-decision.md).
57
57
  - **C (bounded wait):** clients routinely need more than about five polls per turn, or client tool-call limits or approval prompts make polling costly.
58
58
  - **D (persistence research):** a client or server restart loses useful work in practice.
59
59
  - **F (remote architecture):** a concrete remote or mobile use case that SSH or existing remote-development workflows cannot satisfy.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Validated dependency baseline (2026-10-09)
4
4
 
5
- - [`@earendil-works/pi-coding-agent` 1.1.0](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) supplies the documented root exports used by this server, including `ModelRuntime` and `createAgentSession`. Since 0.99 the prompt preflight hook reports a dispatch disposition (`started`, `queued`, `handled`) and is not called on rejection; only `started` admits a public turn (`admitsTurn` in `src/sdk-pi-adapter.ts`). Model-visible tools are declared on transcript system messages rather than in the provider context. `test/sdk-contract.test.ts` pins both assumptions provider-free.
5
+ - [`@earendil-works/pi-coding-agent` 1.2.0](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) supplies the documented root exports used by this server, including `ModelRuntime` and `createAgentSession`. Since 0.99 the prompt preflight hook reports a dispatch disposition (`started`, `queued`, `handled`) and is not called on rejection; only `started` admits a public turn (`admitsTurn` in `src/sdk-pi-adapter.ts`). Model-visible tools are declared on transcript system messages rather than in the provider context. `test/sdk-contract.test.ts` pins both assumptions provider-free.
6
6
  - [`@modelcontextprotocol/server` 2.3.1](https://www.npmjs.com/package/@modelcontextprotocol/server) supplies `McpServer`, structured tool output, and the stdio entry point.
7
7
  - [Zod 4.6.5](https://zod.dev/) validates strict configuration, tool input, and structured output.
8
8
 
@@ -128,9 +128,20 @@ trash, delete the trash entry by entry, owned record. `--apply`, `--remove` and
128
128
  transaction, and only when the slot is empty, a link, or one of setup's two
129
129
  copies:
130
130
 
131
- - A trash directory is deleted after a structural recheck: a real directory,
132
- regular files and directories only, no hidden entries, within the tree
133
- limits. It may be partial, because it was verified before the rename.
131
+ - A trash directory is deleted only when its current contents are the
132
+ complete, unchanged replaced copy: its version 2 tree fingerprint equals the
133
+ replaced fingerprint in the pending record. A partial trash tree (for
134
+ example after an interruption during the entry-by-entry deletion, including
135
+ an empty one), a modified, added-to, foreign or otherwise unprovable tree,
136
+ and any trash when the record names no usable replaced fingerprint are
137
+ retained. A directory name, the transaction id or a structural check alone
138
+ never proves ownership of the contents. This deliberately replaces the
139
+ earlier automatic deletion of partial trash trees; continuing a partial
140
+ deletion would need additional durable evidence, and no journal is added
141
+ for it. The fingerprint covers the paths and contents of regular files, not
142
+ empty directories or file modes, so an empty directory or a mode change
143
+ inside an otherwise complete trash does not prevent its deletion; no file
144
+ content is lost that way.
134
145
  - The backup must equal the replaced copy; the staging directory must equal
135
146
  the target or be an interrupted write of it. A partially written staging
136
147
  directory counts only when every file is a prefix-consistent part of the
@@ -138,10 +149,22 @@ copies:
138
149
  - `--apply` with an empty slot finishes forward with a complete staging
139
150
  directory, else moves the verified backup back. `--remove` and `--rollback`
140
151
  delete the verified intermediates and leave the slot to the removal.
141
- - Anything modified or foreign stays and is reported as
142
- `skill_recovery_retained` (divergent). A failed update leaves its staging
143
- directory until the next run settles it, and an unhealthy ownership fence
144
- retains intermediates the same way.
152
+ - Anything modified or foreign, the trash directory included, stays and is
153
+ reported as `skill_recovery_retained` (divergent). The pending record and
154
+ the slot then stay unchanged, and nothing is reported as settled. A
155
+ retained trash stops recovery before any other intermediate is touched; a
156
+ retained backup or staging directory can follow the deletion of a proven
157
+ own trash. A failed update leaves its staging directory until the
158
+ next run settles it, and an unhealthy ownership fence retains intermediates
159
+ the same way.
160
+ - Operator settlement of a retained trash directory: inspect the
161
+ `.pi-session-mcp-<skill>-<transactionId>.trash` directory next to the slot,
162
+ move any own content elsewhere, delete that directory, and rerun the same
163
+ setup command.
164
+ - For a pending version 2 transaction, `--dry-run` reports
165
+ `skill_recovery_retained` (divergent, exit 1) whenever `--apply` would
166
+ retain an intermediate, using the same read-only classification as the
167
+ recovery, and otherwise `skill_recovery_planned`. A dry-run never writes.
145
168
  - Random-name directories of 0.8.0 updates are never searched for or deleted;
146
169
  an operator may remove them by hand.
147
170
  - `--dry-run` and `--verify` do not report leftover intermediates.
@@ -264,29 +287,53 @@ skill name only if it is valid, else `invalid`.
264
287
 
265
288
  Production verification requires the offline Doctor, exact public client-CLI registration inspection, and a directly launched bounded MCP handshake. The verifier calls `pi_capabilities_get` with `{}` only; session start/prompt/turn and provider/model operations are forbidden. Its child process is terminated and awaited before success is reported.
266
289
 
290
+ Verify evaluates one target in this order, and the first failure ends that
291
+ target's verification:
292
+
293
+ 1. An unknown client: `client_unsupported`.
294
+ 2. Ownership: `ownership_unavailable`.
295
+ 3. Release binding: `release_binding_mismatch`.
296
+ 4. Doctor, under its own code. A failed Doctor ends the target's verify before
297
+ the registration is inspected and before any handshake.
298
+ 5. Inspection. A client the inspection reports as unsupported or not
299
+ discoverable, for example because its CLI is not on `PATH`, gives status
300
+ `unsupported` and code `unsupported`. An inspection that throws gives
301
+ `inspect_failed`. An absent registration, a divergent one, and an
302
+ equivalent one with another fingerprint give `registration_not_equivalent`.
303
+ 6. Only a truly equivalent registration runs the bounded handshake, which
304
+ reports `mcp_verified` or `mcp_<status>`.
305
+
306
+ None of the failure cases mutates a registration or starts the handshake, and
307
+ all exit with `1`. There is no separate client-unavailable code; the
308
+ dry-run already separates these states the same way.
309
+
267
310
  ## Compatibility and reporting
268
311
 
269
312
  Codex and Claude Code are separate adapters with explicit supported scopes and documented capability fixtures. Unknown output fails closed with stable codes. Absolute paths are passed only to client commands where required and are never included in results, logs, or ownership metadata. Client command diagnostics must be reduced to stable codes before crossing this boundary.
270
313
 
271
- Human and JSON output derive from one `schemaVersion: 1` result. Exit code `0` is success, `1` is an operational or reconciliation failure, `64` is command-line misuse, and interrupted setup returns `130` for `SIGINT` or `143` for `SIGTERM` only after the guardian cleanup path has completed or retained the fence. Automatic replacement of a divergent registration is deliberately unsupported.
314
+ Human and JSON output derive from one `schemaVersion: 1` result; this version is the setup report's own, not the package version or the MCP tool contract (see [Versions](tool-contracts.md#versions)). Exit code `0` is success, `1` is an operational or reconciliation failure, `64` is command-line misuse, and interrupted setup returns `130` for `SIGINT` or `143` for `SIGTERM` only after the guardian cleanup path has completed or retained the fence. Automatic replacement of a divergent registration is deliberately unsupported.
272
315
 
273
316
  Stable finding codes are grouped by responsibility:
274
317
 
275
318
  - input/prerequisites: `target_invalid`, `config_required`, `usage_invalid`,
276
319
  `platform_unsupported`, `operation_interrupted`,
277
320
  `doctor_failed`, `release_unavailable`, `client_unsupported`, `inspect_failed`;
278
- - planning/reconciliation: `absent`, `equivalent`, `divergent`,
321
+ - planning/reconciliation: `absent`, `equivalent`, `divergent`, `unsupported`,
279
322
  `already_equivalent`, `divergence_requires_replacement`,
280
323
  `registration_fingerprint_mismatch`, `release_binding_mismatch`;
281
324
  - ownership/apply: `applied`, `pending_recovered`, `ownership_unavailable`,
282
325
  `apply_failed_recovery_pending`;
283
- - verification: `registration_not_equivalent`, `mcp_verified`,
284
- `mcp_unsupported`, `mcp_timeout`, `mcp_failed`;
326
+ - verification: `registration_not_equivalent` (absent, divergent or
327
+ differently fingerprinted registration), `mcp_verified`, `mcp_unsupported`,
328
+ `mcp_timeout`, `mcp_failed`; verify also reports `unsupported` (client not
329
+ discoverable) and `inspect_failed` (inspection threw), which belong to the
330
+ other groups;
285
331
  - rollback/removal: `rolled_back`, `removed`, `ownership_diverged`,
286
332
  `cleanup_failed`;
287
333
  - controller skill, dry-run: `skill_absent`, `skill_equivalent`, `skill_linked`,
288
- `skill_update_planned`, `skill_recovery_planned`, `skill_divergent`,
289
- `skill_legacy_unverified`, `skill_source_invalid`;
334
+ `skill_update_planned`, `skill_recovery_planned`,
335
+ `skill_recovery_retained`, `skill_divergent`, `skill_legacy_unverified`,
336
+ `skill_source_invalid`;
290
337
  - controller skill, apply: `skill_installed`, `skill_already_equivalent`, `skill_recovered`, `skill_linked`,
291
338
  `skill_updated`, `skill_divergent`, `skill_legacy_unverified`,
292
339
  `skill_recovery_retained`, `skill_source_invalid`,
@@ -90,6 +90,25 @@ bounded direct MCP `initialize`, `tools/list`, and
90
90
  `pi_capabilities_get({})` check. It never starts a session or invokes
91
91
  a provider.
92
92
 
93
+ ### Verify diagnostics
94
+
95
+ Verify checks one target in a fixed order and stops that target at the first
96
+ failure. After an unknown client (`client_unsupported`), ownership
97
+ (`ownership_unavailable`) and release binding (`release_binding_mismatch`),
98
+ Doctor runs; if it fails, verify reports its code and neither inspects the
99
+ registration nor runs the handshake. Then the client's registration is
100
+ inspected:
101
+
102
+ | Result | Status | Code | What it means |
103
+ | --- | --- | --- | --- |
104
+ | Client unsupported or not discoverable | `unsupported` | `unsupported` | Setup could not run or recognize the client CLI. Usually the CLI is not on `PATH`, for example in a non-interactive SSH shell. Put the client's directory on `PATH` and rerun; the registration itself may be fine. `--dry-run` reports the same state |
105
+ | Inspection failed | `failed` | `inspect_failed` | Inspecting the registration threw an error. Rerun; if it persists, run the client's own `mcp` listing |
106
+ | Registration absent, divergent, or equivalent with another fingerprint | `failed` | `registration_not_equivalent` | The registration is missing or differs from the one setup would create. Run `--dry-run` for the exact state |
107
+ | Registration equivalent | `ok` or `failed` | `mcp_verified` or `mcp_<status>` | Only now does the bounded MCP handshake run |
108
+
109
+ None of the failure rows changes a registration or starts the handshake, and
110
+ each exits with `1`.
111
+
93
112
  ### Controller skill
94
113
 
95
114
  Besides the registration, `--apply` installs the controller skill
@@ -81,9 +81,13 @@ run.
81
81
 
82
82
  npm resolves the package's transitive dependencies at install time within the
83
83
  ranges that the Pi SDK and the MCP SDK declare, because npm does not ship a
84
- lockfile inside a package. Direct dependencies are pinned exactly. If you need
85
- the exact dependency tree that the maintainers tested, install from a release
86
- tag instead.
84
+ lockfile inside a package. Direct dependencies are pinned exactly, and so are
85
+ the SDK's internal `@earendil-works` packages (`pi-ai`, `pi-mcp`, `pi-tui`,
86
+ `pi-codemode`, `pi-agent-core` and `pi-telemetry`, the last one declared by
87
+ `pi-ai`), so that the SDK's caret ranges resolve to the tested versions.
88
+ CI smoke-tests an install of the packed package without a lockfile on every
89
+ change, and a nightly job does the same for the published `latest` version. If you need the exact dependency tree that the maintainers
90
+ tested, install from a release tag instead.
87
91
 
88
92
  ### From a release tag
89
93
 
package/docs/releasing.md CHANGED
@@ -16,19 +16,41 @@ does not authorize any of them.
16
16
  - A change to the public contract needs a minor release and its own decision.
17
17
 
18
18
  The public contract is the set of eight tools with their fields, states and
19
- error codes, as defined in the [tool contracts](tool-contracts.md).
19
+ error codes, as defined in the [tool contracts](tool-contracts.md). How the
20
+ package version, the MCP tool contract and the setup report's `schemaVersion`
21
+ differ is defined under [Versions](tool-contracts.md#versions).
22
+
23
+ Wherever this document asks for the Level 1 checks, the test step is
24
+ `NODE_OPTIONS=--unhandled-rejections=strict npm test`. CI uses the same
25
+ setting, so an unhandled promise rejection fails the run locally and in CI.
20
26
 
21
27
  A release is distributed twice: as the GitHub release with its source archive,
22
28
  and as the npm package `pi-session-mcp`. The npm package contains the built
23
29
  files, the executables, the operator documentation and the example
24
30
  configuration. It contains no implementation plans (`docs/superpowers/`), and
25
- no lockfile, because npm does not ship lockfiles inside packages.
31
+ no lockfile, because npm does not ship lockfiles inside packages. A consumer
32
+ install therefore resolves every transitive range afresh, which is why the Pi
33
+ SDK's own packages are pinned directly (see [Update the Pi SDK](#update-the-pi-sdk)).
26
34
 
27
35
  ## Update the Pi SDK
28
36
 
29
- The Pi SDK is pinned exactly. A nightly canary
30
- (`.github/workflows/pi-sdk-canary.yml`) checks the unchanged sources against the
31
- newest published `@earendil-works/pi-coding-agent`: typecheck plus every test
37
+ The Pi SDK is pinned exactly, together with the SDK's internal
38
+ `@earendil-works` packages: `pi-ai`, `pi-mcp`, `pi-tui`, `pi-codemode` and
39
+ `pi-agent-core`, which the SDK declares, and `pi-telemetry`, which `pi-ai`
40
+ declares. They are declared with caret ranges, and npm does not ship our
41
+ lockfile inside the package, so a consumer install resolves those ranges fresh.
42
+ A newer family member can then break the pinned SDK, as in
43
+ [#78](https://github.com/plagor44-ux/pi-session-mcp/issues/78), where
44
+ `pi-codemode` 1.2.0 removed an export that `pi-coding-agent` 1.1.0 imports. We
45
+ do not import the family members; they are direct, exactly pinned dependencies
46
+ only so that a fresh install resolves the SDK's ranges to the tested versions.
47
+ The SDK and the six packages therefore always move together, each to the
48
+ version the canary resolves from the declared ranges.
49
+
50
+ A nightly canary (`.github/workflows/pi-sdk-canary.yml`) checks the unchanged
51
+ sources against the newest published `@earendil-works/pi-coding-agent` together
52
+ with the family versions that candidate declares, transitively (a package that
53
+ another member declares, such as `pi-telemetry`, is followed too): typecheck plus every test
32
54
  that touches the SDK, provider-free. It reports each incompatible version once
33
55
  in an open issue labeled `pi-sdk-canary`. A green canary is a signal that an
34
56
  update is possible. It is not an update and not an acceptance.
@@ -42,7 +64,8 @@ update is possible. It is not an update and not an acceptance.
42
64
  ```
43
65
 
44
66
  Exit status `0` means compatible, `1` incompatible, and `2` invalid input or
45
- a changed manifest.
67
+ a changed manifest. The script prints the candidate and the family versions it
68
+ resolved from the declared ranges.
46
69
 
47
70
  2. Read the upstream changelog between the pinned and the target version. It
48
71
  ships in the package as
@@ -53,23 +76,32 @@ update is possible. It is not an update and not an acceptance.
53
76
  `src/session-tools.ts`. That function is not a root export, so no test
54
77
  notices when the SDK reserves another name.
55
78
 
56
- 3. Move the pin:
79
+ 3. Move the pins, the SDK and its six family packages in one command:
57
80
 
58
81
  ```bash
59
- npm install --save-exact @earendil-works/pi-coding-agent@<version>
82
+ npm install --save-exact \
83
+ @earendil-works/pi-coding-agent@<version> @earendil-works/pi-ai@<ai> \
84
+ @earendil-works/pi-mcp@<mcp> @earendil-works/pi-tui@<tui> \
85
+ @earendil-works/pi-codemode@<codemode> @earendil-works/pi-agent-core@<core> \
86
+ @earendil-works/pi-telemetry@<telemetry>
60
87
  ```
61
88
 
62
- `package.json` must change in exactly one line and keep an exact version
63
- without a range prefix. Review the lockfile diff: only the SDK's own packages
64
- and their dependencies should move, and earlier lockfile-only patches must
65
- survive.
89
+ Use the versions the canary reported. `package.json` must change in exactly
90
+ seven lines, one per package, and keep an exact version without a range
91
+ prefix. Check with `npm ls` that exactly one copy of each family package
92
+ exists.
93
+ Review the lockfile diff: only the SDK family and their dependencies should
94
+ move, and earlier lockfile-only patches must survive.
66
95
 
67
96
  4. Update the documents that name the pinned version,
68
97
  [architecture](architecture.md) and [tool contracts](tool-contracts.md), and
69
98
  add a changelog entry. Record upstream changes that affect operators, for
70
99
  example a renamed provider key.
71
100
 
72
- 5. Run the [Level 1 checks](acceptance.md#level-1-repository-checks).
101
+ 5. Run the [Level 1 checks](acceptance.md#level-1-repository-checks). Before
102
+ the release, the packed-tarball check under
103
+ [Prepare a release](#prepare-a-release) shows whether a consumer install of
104
+ the new pins works.
73
105
 
74
106
  Three guards protect the SDK boundary. `test/sdk-contract.test.ts` pins the prompt
75
107
  dispositions, the rejection path and the exact tools declared to the model for
@@ -166,7 +198,29 @@ Steps:
166
198
  5. Run the working-tree
167
199
  [Level 2 acceptance](acceptance.md#tooling) on the release candidate with
168
200
  `scripts/setup-acceptance.mjs`, unless the release notes state its absence.
169
- 6. Record the pre-release acceptance in the status section of
201
+ 6. Run the packed-tarball Level 2 acceptance on the same candidate. Pack the
202
+ built package, install the tarball into a fresh prefix and point the harness
203
+ at the installed package:
204
+
205
+ ```bash
206
+ npm pack
207
+ PREFIX="$(mktemp -d "$HOME/.cache/pi-session-mcp-pack.XXXXXX")"
208
+ npm install -g --prefix "$PREFIX" ./pi-session-mcp-<x.y.z>.tgz
209
+ node scripts/setup-acceptance.mjs \
210
+ --setup "$PREFIX/bin/pi-session-mcp-setup" \
211
+ --build-dir "$PREFIX/lib/node_modules/pi-session-mcp" \
212
+ --label packed-<x.y.z> --out level2-result.json
213
+ rm -rf "$PREFIX" ./pi-session-mcp-<x.y.z>.tgz
214
+ ```
215
+
216
+ This is the only acceptance-level pre-release check that resolves
217
+ dependencies like a consumer: the working tree and the CI build use our
218
+ lockfile, which a published package does not carry (#78). The CI install
219
+ smoke also resolves fresh, but it is an early warning, not acceptance. Always
220
+ use a fresh prefix from `mktemp -d`, never a fixed or reused one, and never
221
+ install without `--prefix`. Remove the prefix and the tarball afterwards; the
222
+ tarball is not committed.
223
+ 7. Record the pre-release acceptance in the status section of
170
224
  [Acceptance](acceptance.md) and push it to the change pull request. Name the
171
225
  release in the pull request's title and description.
172
226