pi-session-mcp 0.8.0 → 0.8.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.
@@ -117,19 +117,26 @@ Skill findings per operation:
117
117
 
118
118
  - `--dry-run`: `skill_absent` (absent), `skill_equivalent` (unchanged),
119
119
  `skill_linked` (unchanged; a link to exactly the effective skill),
120
- `skill_update_planned` (planned), `skill_divergent` (divergent),
120
+ `skill_update_planned` (planned), `skill_recovery_planned` (planned; the
121
+ ownership record is pending for an update that `--apply` would settle),
122
+ `skill_divergent` (divergent), `skill_legacy_unverified` (divergent),
121
123
  `skill_source_invalid` (failed).
122
124
  - `--apply`: `skill_installed` (ok), `skill_already_equivalent` (unchanged),
123
- `skill_linked` (unchanged),
125
+ `skill_recovered` (ok; `--apply` settled an interrupted update and needed no
126
+ further update), `skill_linked` (unchanged),
124
127
  `skill_updated` (ok; setup replaced its own unmodified copy after a package
125
- update or a changed override), `skill_divergent`, `skill_source_invalid`,
128
+ update or a changed override), `skill_divergent`, `skill_legacy_unverified`,
129
+ `skill_recovery_retained` (divergent), `skill_source_invalid`,
126
130
  `skill_install_failed` (failed).
127
131
  - `--verify`: `skill_verified` (ok), `skill_missing` and
128
132
  `skill_not_equivalent` (failed).
129
133
  - `--rollback` and `--remove`: `skill_removed` (ok), `skill_kept_shared`
130
134
  (unchanged; the other Claude Code `user` or `local` registration still uses
131
135
  the shared directory), `skill_not_owned` (unchanged), `skill_absent` (ok) and
132
- `skill_divergent` (a modified copy stays in place).
136
+ `skill_divergent` (a modified copy stays in place), `skill_legacy_unverified`
137
+ (a record of 0.8.0 that no trusted reference proves; copy and record stay) and
138
+ `skill_recovery_retained` (a modified or foreign intermediate directory of an
139
+ interrupted update stays).
133
140
 
134
141
  Setup never replaces or deletes a modified installed copy, a skill of the same
135
142
  name that it did not install, or a link or non-directory at the skill entry; it
@@ -155,9 +162,14 @@ records the fingerprint of the shipped skill in
155
162
  `.pi-session-mcp-bases.json` there. The finding target is
156
163
  `skills/override/<name>`; the codes are `skill_customize_planned` (planned),
157
164
  `skill_customized` (ok), `skill_override_current` (unchanged),
158
- `skill_override_outdated` (planned), `skill_base_updated` (ok),
159
- `skill_not_shipped`, `skill_bases_corrupt`, `skill_override_invalid` and
160
- `skill_customize_failed` (failed). An existing override is never overwritten.
165
+ `skill_override_outdated` (planned), `skill_base_migration_planned` (planned),
166
+ `skill_base_unverified` (planned), `skill_base_migrated` (ok),
167
+ `skill_base_updated` (ok), `skill_not_shipped`, `skill_bases_corrupt`,
168
+ `skill_override_invalid`, `skill_customize_lock_busy`,
169
+ `skill_customize_lock_unavailable` and `skill_customize_failed` (failed). An
170
+ existing override is never overwritten. `--apply` runs under an exclusive lock
171
+ on the override directory (`.pi-session-mcp-bases.lock`, 30 s wait); the dry-run
172
+ takes no lock and writes nothing.
161
173
  After `skill_override_outdated`, `--apply` moves only the recorded base, which
162
174
  acknowledges a change you reviewed. Details: [Pi skills](skills.md#customize-and-add-skills).
163
175
 
@@ -43,7 +43,7 @@ Doctor reads extension manifests from Pi's user install location only; it never
43
43
 
44
44
  The skill checks run after the extension checks. They look only at file availability: Doctor never reads a skill's frontmatter, never parses or executes it, and the subject carries the catalog alias, never a path. Frontmatter, skill-name rules, name collisions and explicit-only flags (`disable-model-invocation`) are checked only at session start by the Pi SDK contract, so a clean Doctor run does not promise that a skill loads. Unknown or duplicate aliases and two selected aliases with one path are configuration errors and surface as `config_invalid`. See [Pi skills](skills.md).
45
45
 
46
- `skill_override_outdated` concerns the operator's skill overrides, not a profile. It is `ok` for a recorded base that still matches the shipped skill, and a warning (exit status 1, never blocking `setup`) when the shipped skill changed after the operator ran `pi-session-mcp-setup --customize-skill <name>`; its remediation names `pi-session-mcp-setup --apply --customize-skill <name>` to acknowledge a reviewed change. Only skills with a recorded base are compared, and a recorded name that is no longer shipped is skipped. A corrupt bases file yields a warning with the subject `skill:bases`. Doctor reads the bases file and the shipped skill files only; nothing is executed and the subject is a validated skill name, never a path. The check sorts after `skill_duplicate`.
46
+ `skill_override_outdated` concerns the operator's skill overrides, not a profile. It is `ok` for a recorded base that still matches the shipped skill, and a warning (exit status 1, never blocking `setup`) when the shipped skill changed after the operator ran `pi-session-mcp-setup --customize-skill <name>`; its remediation names `pi-session-mcp-setup --apply --customize-skill <name>` to acknowledge a reviewed change. Only skills with a recorded base are compared, and a recorded name that is no longer shipped is skipped. A recorded base of 0.8.0 (a version 1 fingerprint) is compared through the version 2 value of the released 0.8.0 skill it names; a version 1 base that names no released skill cannot be confirmed and yields a warning that says so, with the same remediation. A corrupt bases file yields a warning with the subject `skill:bases`. Doctor reads the bases file and the shipped skill files only; nothing is executed and the subject is a validated skill name, never a path. The check sorts after `skill_duplicate`.
47
47
 
48
48
  The Doctor reuses `loadConfig`; it does not duplicate alias, profile, thinking
49
49
  level, or read-only-default rules. Workspace values are used only for local
@@ -20,7 +20,7 @@ An installation consists of these locations:
20
20
  | --- | --- | --- |
21
21
  | Package | the npm global prefix, or a dedicated directory per release such as `~/.local/share/pi-session-mcp/releases/<tag>` | The built server, `setup` and Doctor. |
22
22
  | Configuration | a file outside the package, for example `~/.config/pi-session-mcp/pi-session-mcp.json` | Operator-owned, mode `0600`, never committed. |
23
- | Skill overrides (optional) | the `skills/` directory next to the configuration file, for example `~/.config/pi-session-mcp/skills/` | Your own or customized skills, plus `.pi-session-mcp-bases.json`. Created by you or by `setup --customize-skill`. See [Pi skills](#pi-skills). |
23
+ | Skill overrides (optional) | the `skills/` directory next to the configuration file, for example `~/.config/pi-session-mcp/skills/` | Your own or customized skills, plus `.pi-session-mcp-bases.json` and its lock file `.pi-session-mcp-bases.lock`. Created by you or by `setup --customize-skill`. See [Pi skills](#pi-skills). |
24
24
  | Controller skill | `~/.agents/skills/pi-session-orchestration` (Codex), `~/.claude/skills/pi-session-orchestration` or `<project>/.claude/skills/pi-session-orchestration` (Claude Code) | Written by `setup --apply`; see [Register a client](#4-register-a-client). |
25
25
  | Setup ownership state | `~/.local/state/pi-session-mcp/ownership.json` | Written by `setup` only. Path-free and secret-free. |
26
26
 
@@ -487,7 +487,8 @@ choose skills.
487
487
  create `<override dir>/<new-name>/SKILL.md` and reference it as
488
488
  `builtin:<new-name>`; no command is needed. After a package update, Doctor
489
489
  warns with `skill_override_outdated` when a shipped skill changed since you
490
- copied it.
490
+ copied it. `--apply --customize-skill` takes a kernel lock on the override
491
+ directory, so concurrent runs cannot lose a recorded base.
491
492
  - A `builtin:` name with no override and no shipped skill makes Doctor report
492
493
  `skill_missing` and the session start fail with `skill_load_failed`.
493
494
  - An extension allowlist alone makes none of a package's skills available. If a
@@ -513,6 +514,11 @@ the new text. A package update that changes a shipped skill does not touch an
513
514
  override of yours; Doctor warns with `skill_override_outdated` so that you can
514
515
  review the change.
515
516
 
517
+ After an upgrade from 0.8.0, run `setup` as a dry-run and then with `--apply`
518
+ for your targets. Copies and records of 0.8.0 are proven before setup acts on
519
+ them; one that cannot be proven is reported as `skill_legacy_unverified` and
520
+ stays untouched. See [Upgrade from 0.8.0](skills.md#upgrade-from-080).
521
+
516
522
  ### npm installation
517
523
 
518
524
  An npm upgrade replaces the package in place. Close the client sessions that
@@ -263,6 +263,17 @@ and step 6 leaves no session to dispose.
263
263
  - **UI and flags.** No UI context is bound; extension UI calls return the
264
264
  SDK's no-op results. `extensionFlagValues` is empty, so extension flags keep
265
265
  their defaults.
266
+ - **Turn liveness.** The `updatedAt` of a running turn follows the public SDK
267
+ events of the prompt: assistant message start, update and end, and tool
268
+ execution start, update and end. An extension provider with its own agent
269
+ loop, such as `pi-cursor-sdk`, therefore advances `updatedAt` whenever it
270
+ publishes streaming updates. Its internal steps that publish no public
271
+ event stay invisible, so an unchanged `updatedAt` is no proof of a hang, and
272
+ streaming is no evidence of finished or useful work. Only the event type and
273
+ the message role are inspected; no delta, thinking block, tool argument or
274
+ partial result is stored or projected, and the answer still comes only from
275
+ the final assistant message. See [Tool
276
+ contracts](tool-contracts.md).
266
277
 
267
278
  ### Lifecycle and close
268
279
 
package/docs/releasing.md CHANGED
@@ -131,9 +131,15 @@ Patch transitive advisories in the lockfile only:
131
131
 
132
132
  ## Prepare a release
133
133
 
134
+ A release travels in the pull request of the change it ships. That pull
135
+ request also carries the version, the changelog heading and the pre-release
136
+ acceptance record; there is no separate release pull request. Records that can
137
+ only be made after publishing go into the pull request of the next change.
138
+
134
139
  Preconditions:
135
140
 
136
- - `main` contains every change of the release, and Level 1 is green on `main`.
141
+ - The change pull request contains every change of the release, and Level 1
142
+ is green on its head.
137
143
  - `npm audit` reports 0 vulnerabilities.
138
144
  - The public contract is unchanged, or a minor release has been decided.
139
145
  - The acceptance that this release requires is recorded, or its absence is
@@ -141,8 +147,9 @@ Preconditions:
141
147
 
142
148
  Steps:
143
149
 
144
- 1. Create a release branch from the current `main`. Never reuse a release branch
145
- that predates later merges: its lockfile would undo later dependency patches.
150
+ 1. Bring the change branch up to date with `main` if `main` moved. Never reuse
151
+ a branch that predates later merges without that: its lockfile would undo
152
+ later dependency patches.
146
153
  2. Set the version:
147
154
 
148
155
  ```bash
@@ -159,11 +166,13 @@ Steps:
159
166
  5. Run the working-tree
160
167
  [Level 2 acceptance](acceptance.md#tooling) on the release candidate with
161
168
  `scripts/setup-acceptance.mjs`, unless the release notes state its absence.
162
- 6. Open one pull request named `chore: prepare v<x.y.z> release`.
169
+ 6. Record the pre-release acceptance in the status section of
170
+ [Acceptance](acceptance.md) and push it to the change pull request. Name the
171
+ release in the pull request's title and description.
163
172
 
164
173
  ## Publish a release
165
174
 
166
- After the release pull request is merged and CI is green on the merge commit:
175
+ After the change pull request is merged and CI is green on the merge commit:
167
176
 
168
177
  ```bash
169
178
  git switch main
@@ -217,9 +226,9 @@ After publishing:
217
226
 
218
227
  1. Run the published-release
219
228
  [Level 2 acceptance](acceptance.md#working-tree-and-published-release-variants)
220
- from a fresh clone of the tag and from the published npm package, and
221
- record it. `scripts/setup-acceptance.mjs` runs both
222
- ([Tooling](acceptance.md#tooling)).
229
+ from a fresh clone of the tag and from the published npm package.
230
+ `scripts/setup-acceptance.mjs` runs both ([Tooling](acceptance.md#tooling)).
223
231
  2. Upgrade the installation as described in
224
232
  [Installation](installation.md#upgrade).
225
- 3. Update the status section in [Acceptance](acceptance.md).
233
+ 3. Record both in the status section of [Acceptance](acceptance.md) with the
234
+ pull request of the next change, not in a pull request of their own.
package/docs/skills.md CHANGED
@@ -12,8 +12,9 @@ The package ships three skills and keeps them in a skill library together with
12
12
  the operator's own overrides. `pi-session-mcp-setup --apply` also installs the
13
13
  controller skill into Claude Code and Codex. Nothing needs to be linked by hand.
14
14
  Sections: [Configuration](#configuration), [The skill library](#the-skill-library),
15
- [Customize and add skills](#customize-and-add-skills) and [Install the
16
- orchestration skill into Codex and Claude Code](#install-the-orchestration-skill-into-codex-and-claude-code).
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)
17
+ and [Upgrade from 0.8.0](#upgrade-from-080).
17
18
 
18
19
  ## Configuration
19
20
 
@@ -175,7 +176,9 @@ override is picked up, and start new sessions.
175
176
  usage errors (exit 64).
176
177
  - It requires `PI_SESSION_MCP_CONFIG` (otherwise `config_required`) and the
177
178
  same platform as the rest of `setup`: Linux or macOS, otherwise
178
- `platform_unsupported`. It takes no ownership lock.
179
+ `platform_unsupported`. It takes no ownership lock. `--apply` takes the
180
+ [override lock](#concurrent-runs-and-the-override-lock); the dry-run takes no
181
+ lock.
179
182
  - It never overwrites an existing override and never edits your files.
180
183
  - Only regular files are copied: at most 32 files, 1 MiB and 8 directory
181
184
  levels, no links, and entries whose name starts with `.` are skipped.
@@ -184,16 +187,57 @@ The finding target is `{ client: "skills", scope: "override", alias: <name> }`:
184
187
 
185
188
  | Code | Status | Meaning |
186
189
  | --- | --- | --- |
187
- | `skill_customize_planned` | planned | Dry-run: no override exists; `--apply` would copy the shipped skill. |
188
- | `skill_customized` | ok | The override was created and its base recorded. |
189
- | `skill_override_current` | unchanged | The override exists and its recorded base equals the current shipped skill. |
190
- | `skill_override_outdated` | planned | The shipped skill changed since the copy, or no base is recorded for the existing override. |
191
- | `skill_base_updated` | ok | `--apply` after `skill_override_outdated`: only the recorded base moved. |
190
+ | `skill_customize_planned` | planned | Dry-run: no override exists, or an existing override equals the shipped skill but has no recorded base; `--apply` would copy the shipped skill if it is absent and record the base. |
191
+ | `skill_customized` | ok | The override was created and its base recorded, or an existing override that equals the shipped skill got its missing base. The second case completes a customization whose bases commit failed. |
192
+ | `skill_override_current` | unchanged | The override exists and its recorded base equals the current shipped skill. Of two concurrent applies for the same skill, the later one reports this. |
193
+ | `skill_override_outdated` | planned | The shipped skill changed since the copy, or no base is recorded for an existing override that differs from the shipped skill, or a 0.8.0 base names a released shipped skill that is not the current one. |
194
+ | `skill_base_migration_planned` | planned | Dry-run: a 0.8.0 base names the current shipped skill; `--apply` would record it as a version 2 base. |
195
+ | `skill_base_migrated` | ok | `--apply` after `skill_base_migration_planned`: the base is recorded as version 2. |
196
+ | `skill_base_unverified` | planned | Dry-run: a 0.8.0 base names no released shipped skill, so it cannot be confirmed. `--apply` records the current shipped skill as the base after you reviewed it. |
197
+ | `skill_base_updated` | ok | `--apply` after `skill_override_outdated` or `skill_base_unverified`: only the recorded base moved. |
192
198
  | `skill_not_shipped` | failed | The name is not valid or the package ships no such skill. |
193
199
  | `skill_bases_corrupt` | failed | The bases file is not valid. Repair or remove it. |
194
200
  | `skill_override_invalid` | failed | The override entry is not a directory, or its tree is unusable (no `SKILL.md`, a link, too large). |
201
+ | `skill_customize_lock_busy` | failed | Another run held the override lock for the whole wait of 30 seconds. Run the command again. |
202
+ | `skill_customize_lock_unavailable` | failed | The lock cannot be taken: the platform, the filesystem or the `flock` binary is not supported. |
195
203
  | `skill_customize_failed` | failed | Any other failure while copying or recording. |
196
204
 
205
+ ### Concurrent runs and the override lock
206
+
207
+ `--apply --customize-skill` is one transaction per override directory. It takes
208
+ an exclusive kernel lock on `<override dir>/.pi-session-mcp-bases.lock` (mode
209
+ `0600`, never deleted), reads and validates the bases file under the lock,
210
+ copies the shipped skill if no override exists, merges the base, and replaces
211
+ the bases file atomically: an fsynced temporary file, a rename and a directory
212
+ fsync. The lock is released on every path. Two runs for different skills
213
+ therefore cannot lose each other's base. If the commit of the bases file
214
+ fails, the previous file stays; a retry then completes the customization
215
+ without overwriting your files (`skill_customized` for an override that equals
216
+ the shipped skill and has no base).
217
+
218
+ - The lock wait is bounded at 30 seconds, then the result is
219
+ `skill_customize_lock_busy`. A lock that cannot be taken at all is
220
+ `skill_customize_lock_unavailable`.
221
+ - On Linux the lock needs the trusted util-linux `flock` at `/usr/bin/flock` or
222
+ `/bin/flock`; on macOS it uses `O_EXLOCK`. Both need a supported local
223
+ filesystem.
224
+ - A holder that is killed releases the lock through the kernel. It may leave a
225
+ stale hidden temporary bases file `.pi-session-mcp-bases.json.<hex>.tmp` or
226
+ hidden temporary copy directory `.<skill>.<hex>.tmp` in the override
227
+ directory. Both are harmless: they are hidden and never resolved as skills.
228
+ Nothing removes them automatically; you may delete them while no
229
+ customization runs.
230
+ - The dry-run takes no lock and writes nothing: no directory, lock file or
231
+ temporary file.
232
+ - Doctor stays offline, read-only and lock-free.
233
+
234
+ The bases file is `{"schemaVersion":2,"bases":{"<name>":"<fingerprint>"}}`. A
235
+ value is a `v2:` fingerprint, or a legacy 64-hex value that could not be
236
+ migrated. A version 1 file (0.8.0) is still read. Every write produces version
237
+ 2 and migrates each legacy value that names a released 0.8.0 shipped skill; any
238
+ other legacy value is kept as it is. See [Upgrade from
239
+ 0.8.0](#upgrade-from-080).
240
+
197
241
  ### Follow a changed shipped skill
198
242
 
199
243
  After a package update, `skill_override_outdated` (from the command above, or
@@ -246,16 +290,27 @@ status `failed` (exit 1), so it is not overlooked.
246
290
 
247
291
  | Operation | Code (status) |
248
292
  | --- | --- |
249
- | `--dry-run` | `skill_absent` (absent), `skill_equivalent` (unchanged), `skill_linked` (unchanged), `skill_update_planned` (planned), `skill_divergent` (divergent), `skill_source_invalid` (failed) |
250
- | `--apply` | `skill_installed` (ok), `skill_already_equivalent` (unchanged), `skill_linked` (unchanged), `skill_updated` (ok), `skill_divergent` (divergent), `skill_source_invalid` (failed), `skill_install_failed` (failed) |
293
+ | `--dry-run` | `skill_absent` (absent), `skill_equivalent` (unchanged), `skill_linked` (unchanged), `skill_update_planned` (planned), `skill_recovery_planned` (planned), `skill_divergent` (divergent), `skill_legacy_unverified` (divergent), `skill_source_invalid` (failed) |
294
+ | `--apply` | `skill_installed` (ok), `skill_already_equivalent` (unchanged), `skill_recovered` (ok), `skill_linked` (unchanged), `skill_updated` (ok), `skill_divergent` (divergent), `skill_legacy_unverified` (divergent), `skill_recovery_retained` (divergent), `skill_source_invalid` (failed), `skill_install_failed` (failed) |
251
295
  | `--verify` | `skill_verified` (ok), `skill_missing` (failed), `skill_not_equivalent` (failed) |
252
- | `--rollback`, `--remove` | `skill_removed` (ok), `skill_kept_shared` (unchanged), `skill_not_owned` (unchanged), `skill_absent` (ok), `skill_divergent` (divergent) |
296
+ | `--rollback`, `--remove` | `skill_removed` (ok), `skill_kept_shared` (unchanged), `skill_not_owned` (unchanged), `skill_absent` (ok), `skill_divergent` (divergent), `skill_legacy_unverified` (divergent), `skill_recovery_retained` (divergent) |
253
297
 
254
298
  - `--apply` installs the skill only when the registration finding is `ok` or
255
299
  `unchanged`; `--rollback` and `--remove` handle it only after the
256
300
  registration was removed.
257
301
  - `skill_updated` means that setup replaced its own unmodified copy after a
258
302
  package update or a changed override.
303
+ - `skill_recovered` means that `--apply` settled an interrupted update of
304
+ setup's own copy (it renamed an intermediate into place or deleted one, or
305
+ finished the pending ownership record) and needed no further update.
306
+ If the settled copy still needs the new version, `--apply` continues and
307
+ reports `skill_installed` or `skill_updated`.
308
+ - `skill_recovery_planned` means that the ownership record is pending for an
309
+ update that `--apply` would settle. The dry-run does not write or delete
310
+ anything and does not look at the intermediate directories, so `--apply` can
311
+ still find a modified or foreign intermediate, keep it and report
312
+ `skill_recovery_retained`. A divergent or
313
+ foreign copy keeps `skill_divergent`, and a 0.8.0 record keeps its own code.
259
314
  - `skill_kept_shared` means that the other Claude Code registration (`user` or
260
315
  `local`) still uses the shared directory. The last one removes the skill.
261
316
  - A copy that already equals the effective skill but was not created by `setup`
@@ -285,10 +340,8 @@ leaves the link alone (`skill_not_owned`). If you replace setup's own copy with
285
340
  such a link, setup forgets its copy on the next `--apply` or `--remove`. A link
286
341
  counts only while the effective skill itself is valid.
287
342
 
288
- If an update is interrupted, for example by a full disk or a signal, setup still
289
- recognizes the old copy as its own: the next `--apply` finishes the update and
290
- `--remove` deletes it. A project copy belongs to its project: setup records an
291
- opaque project identity, so a `--remove` in another project never deletes it.
343
+ A project copy belongs to its project: setup records an opaque project
344
+ identity, so a `--remove` in another project never deletes it.
292
345
 
293
346
  Links in the path to the entry are handled by place. In your home directory,
294
347
  the client's skill directory may be your own link, for example into a dotfiles
@@ -296,6 +349,38 @@ checkout, and is followed. Inside a project, which may be untrusted, any link
296
349
  below the project root makes the entry divergent, so setup never writes outside
297
350
  the project.
298
351
 
352
+ ### Interrupted updates
353
+
354
+ An update first writes a pending ownership record, then works in intermediate
355
+ directories in the parent of the skill directory. They are named after the
356
+ record's transaction id: `.pi-session-mcp-<skill>-<transactionId>.staging`,
357
+ `.backup` and `.trash`. The order is: pending record, staging directory, rename
358
+ of the old copy to the backup, install, verification, rename of the backup to
359
+ the trash, deletion of the trash entry by entry, owned record.
360
+
361
+ If an update is interrupted, for example by a full disk or a signal, setup
362
+ still recognizes the old copy as its own. `--apply`, `--remove` and
363
+ `--rollback` settle a pending transaction first:
364
+
365
+ - `--apply` finishes forward when a complete staging directory is left, or
366
+ moves the verified backup back, and then continues.
367
+ - `--remove` and `--rollback` delete the verified intermediates and then remove
368
+ the copy as usual.
369
+ - A partial trash directory is deleted after a structural recheck (real
370
+ directories and regular files only, no hidden entries, within the tree
371
+ limits).
372
+ - A partially written staging directory is deleted only when every file in it
373
+ is a prefix-consistent part of the current library tree, and that tree is the
374
+ record's target.
375
+ - Anything modified or foreign stays and is reported as
376
+ `skill_recovery_retained` (divergent). Inspect the directory and delete it
377
+ yourself if it is not yours.
378
+
379
+ A failed update leaves its staging directory until the next run settles it.
380
+ The directories of updates by 0.8.0 had random names. Setup never searches for
381
+ or deletes them; you may delete them by hand. `--dry-run` and `--verify` do not
382
+ report leftover intermediates.
383
+
299
384
  ### Customize the controller skill
300
385
 
301
386
  Never edit the installed copy: it becomes divergent. Instead:
@@ -395,7 +480,8 @@ A fourth check concerns overrides, not profiles:
395
480
 
396
481
  | ID | Severity | Subject | Condition |
397
482
  | --- | --- | --- | --- |
398
- | `skill_override_outdated` | warning | `skill:<name>` | The shipped skill's current fingerprint differs from the base recorded in `<override dir>/.pi-session-mcp-bases.json`. |
483
+ | `skill_override_outdated` | warning | `skill:<name>` | The shipped skill's current fingerprint differs from the base recorded in `<override dir>/.pi-session-mcp-bases.json`. A 0.8.0 base counts as the released shipped skill that it names. |
484
+ | `skill_override_outdated` | warning | `skill:<name>` | The recorded base is a 0.8.0 value that names no released shipped skill. The remediation says that the base predates version 2 fingerprints and cannot be confirmed: review the shipped skill, then run `pi-session-mcp-setup --apply --customize-skill <name>`. |
399
485
  | `skill_override_outdated` | warning | `skill:bases` | The bases file is corrupt. |
400
486
 
401
487
  It is `ok` for a recorded base that still matches. The remediation for a
@@ -465,6 +551,49 @@ Pi lists each available skill to the model with the absolute path of its
465
551
  its answer. Capabilities, errors and diagnostics stay path-free, but completed
466
552
  `assistantText` is model output and is not sanitized.
467
553
 
554
+ ## Upgrade from 0.8.0
555
+
556
+ ### Skill tree fingerprints
557
+
558
+ A fingerprint identifies a skill tree by content. Version 2, stored as
559
+ `v2:<64 hex>`, is a SHA-256 over a domain and version tag, the file count, and
560
+ for each file in path byte order the length-prefixed UTF-8 path and the
561
+ length-prefixed content. The size and file type limits are unchanged. Version 1
562
+ (0.8.0) hashed `path NUL content NUL` for each file. Content may contain NUL
563
+ bytes, so two different trees could share a version 1 value.
564
+
565
+ A version 1 value never proves identity alone. It counts only when a trusted
566
+ reference has that version 1 value and is byte-identical, by its version 2
567
+ value, to the tree in question. The trusted references are the shipped skills of
568
+ the 0.8.0 release (a fixed table in the code) and, for setup's installed copies,
569
+ the current library tree.
570
+
571
+ ### What to do after the upgrade
572
+
573
+ 1. Run `pi-session-mcp-setup` as a dry-run for your targets, then with
574
+ `--apply`. An unchanged copy of the 0.8.0 controller skill is proven against
575
+ the 0.8.0 table, its ownership record is migrated, and the copy is updated to
576
+ the new shipped skill. A copy that cannot be proven stays untouched, and
577
+ dry-run, apply, remove and rollback report `skill_legacy_unverified` for it.
578
+ Inspect the copy. If it is unchanged setup content that you no longer need,
579
+ delete it yourself and run the operation again: an absent slot clears the
580
+ record. Otherwise keep it.
581
+ 2. For customized skills, Doctor resolves 0.8.0 bases against the 0.8.0 table.
582
+ The next `--apply --customize-skill` for any skill rewrites the bases file as
583
+ version 2.
584
+
585
+ Ownership records of skill copies hold `v2:` values in `fingerprint` and
586
+ `replaces`. Registration records are unchanged.
587
+
588
+ ### Downgrade
589
+
590
+ 0.8.0 rejects the new formats, so it never acts on them. It treats a version 2
591
+ bases file as corrupt (`skill_bases_corrupt`, and the Doctor warning with the
592
+ subject `skill:bases`). It validates the ownership file as a whole and treats a
593
+ skill record with a `v2:` value as invalid, so it rejects the entire file as
594
+ corrupt, registration records included. Before you run 0.8.0 again, repair or
595
+ remove those files.
596
+
468
597
  ## Migration: profiles that relied on package skills
469
598
 
470
599
  With Pi SDK 1.1.0, an allowlisted extension package's bundled skills reached
@@ -29,10 +29,10 @@ Pi Session MCP trusts the local process that launches and speaks MCP over inheri
29
29
  - **Skill installation into clients:** `setup --apply` writes the controller skill `pi-session-orchestration` into the clients' skill directories, in the home directory (`~/.agents/skills`, `~/.claude/skills`) and, for Claude Code `project`, in the project that setup runs in (`<project>/.claude/skills`). What is written there becomes instructions for the controlling agent, so the copy is as trusted as the package or the override it comes from. Mitigations:
30
30
  - Copies come only from the package's `skills/` directory or from the operator's override directory next to the configuration file. No tool argument, client prompt or workspace names a source.
31
31
  - The override directory and its `.pi-session-mcp-bases.json` are operator-trusted configuration next to the configuration file, never a workspace. Keep them out of every workspace and writable only by the operator; a `coding` session that can write there could change what setup installs next.
32
- - Setup reads a bounded tree of regular files (at most 32 files and 1 MiB, no links or special files) and installs it as a real copy, never as a link. The copy is staged and renamed into place; only the exact old tree that setup itself moved is deleted afterwards.
33
- - Setup never replaces or deletes a modified installed copy, hidden operator entries inside it, a foreign skill of the same name or a linked or non-directory entry; it reports `skill_divergent`. A link to exactly the effective skill is accepted as the operator's own (`skill_linked`) and never written through or deleted. Ownership records hold a content fingerprint (and, during an update, the replaced fingerprint), no path; a project record carries an unsalted, truncated SHA-256 of the project's real path as an opaque identity, which someone who can read the state file could test against guessed paths.
32
+ - Setup reads a bounded tree of regular files (at most 32 files and 1 MiB, no links or special files) and installs it as a real copy, never as a link. The copy is staged and renamed into place. An update renames the old tree to a backup named after the pending record's transaction id and deletes it only after verifying that it is the exact old tree; leftover intermediates are deleted only by their exact names and only after verification, and anything modified or foreign stays (`skill_recovery_retained`).
33
+ - Setup never replaces or deletes a modified installed copy, hidden operator entries inside it, a foreign skill of the same name or a linked or non-directory entry; it reports `skill_divergent`. A link to exactly the effective skill is accepted as the operator's own (`skill_linked`) and never written through or deleted. Ownership records hold a version 2 content fingerprint (and, during an update, the replaced fingerprint), no path. A bare version 1 value from 0.8.0 names a copy only when a trusted reference (a released 0.8.0 shipped tree or the current library tree) has that value and the copy's exact bytes; otherwise setup reports `skill_legacy_unverified` and changes nothing; a project record carries an unsalted, truncated SHA-256 of the project's real path as an opaque identity, which someone who can read the state file could test against guessed paths.
34
34
  - A project may be untrusted. Inside a project setup never follows a link: any link below the project root makes the slot divergent, so a repository cannot redirect the write outside itself. In the home directory the client's skills directory may be the operator's own link (for example into a dotfiles checkout) and is followed; the skill entry itself is never followed.
35
- - `--customize-skill` writes only below the override directory, never overwrites an existing override and never edits an operator's file. It moves the recorded base only on an explicit `--apply`, which acknowledges a reviewed change to the shipped skill.
35
+ - `--customize-skill` writes only below the override directory, never overwrites an existing override and never edits an operator's file. It moves the recorded base only on an explicit `--apply`, which acknowledges a reviewed change to the shipped skill. `--apply` runs under an exclusive kernel lock on `<override dir>/.pi-session-mcp-bases.lock` and replaces the bases file atomically, so concurrent runs cannot lose a base; the dry-run takes no lock and writes nothing.
36
36
  - Worker skills are not installed into clients. Reports name the skill and a stable code, never a path or content.
37
37
  - **False verification:** Verify combines Doctor and exact registration
38
38
  equality with a bounded direct MCP initialize, `tools/list`, and
@@ -22,7 +22,7 @@ There are exactly eight tools. No `pi_turn_wait` tool or live provider/model inv
22
22
  ```json
23
23
  {
24
24
  "ok": true,
25
- "server": { "name": "pi-session-mcp", "version": "0.8.0" },
25
+ "server": { "name": "pi-session-mcp", "version": "0.8.1" },
26
26
  "configuration": {
27
27
  "fingerprint": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
28
28
  "reloadPolicy": "restart-required"
@@ -90,7 +90,7 @@ Invalid execution-profile configuration prevents startup, so it cannot be an MCP
90
90
 
91
91
  `pi_session_prompt` returns `{session,turn}` only after successful Pi preflight. Preflight is bounded at 5,000 ms; a missing callback, hanging prompt acceptance, or timeout returns `prompt_timeout` / `Pi prompt preflight timed out` and creates no public turn. Late settlement may only release the unpublished operation's quarantine; it cannot publish a turn. `pi_session_abort` waits at most 5,000 ms per caller for one shared SDK abort attempt and returns `abort_timeout` / `Pi abort timed out` without claiming the turn was aborted. Rejection/failure remain `prompt_rejected`/`abort_failed`. Close allows at most 5,000 ms for abort cleanup, then performs best-effort disposal and removes the public registry entry; it remains idempotent through the retained tombstone. Registry shutdown rejects new starts, allows 12,000 ms, and disposes a handle from a late-resolving start instead of admitting it. Process shutdown, started by `SIGINT`, `SIGTERM`, or stdin EOF, allows 15,000 ms, attempts transport cleanup even after registry failure/timeout, then explicitly exits the process. These boundaries do not guarantee graceful cancellation of a provider request. `pi_turn_get` returns a `TurnView` with `turnId`, `sessionId`, state, timestamps, and `completedAt` for terminal turns. Completed views add normalized final `assistantText` and `truncated`; failed views contain only `{code:"turn_failed",message:"Pi turn failed"}`. Final assistant text is capped at 64 KiB UTF-8 bytes, each active session retains at most 20 turns, and close removes its turn data.
92
92
 
93
- While a turn runs, `updatedAt` is the controller's last-activity stamp: it advances after each completed tool execution and each completed assistant message, then freezes at the terminal transition. It is a liveness lower bound only. A stamp that has not moved can still be a working turn (a long model response, thinking, or a first token), so it never justifies a claim that a turn is hung; it is a wall-clock ISO-8601 millisecond stamp for ordering, not a progress count, duration, or step total, and activity within one millisecond simply reuses the stamp. It is never derived from event payloads.
93
+ While a turn runs, `updatedAt` is the time of the last observed public SDK activity of that turn: assistant message start, streaming updates and end, and tool execution start, update and end. It freezes at the terminal transition. The `updatedAt` of the session does not follow this activity: it moves when a turn starts and at its terminal transition. It is a liveness lower bound only. Streaming is activity, not evidence of finished or useful work. Work without a public event, such as the internal steps of an extension's own agent loop or a long tool call without updates, stays invisible, so a stamp that has not moved can still be a working turn (a long model response, thinking, a first token, or a long tool call) and never justifies a claim that a turn is hung. The stamp is a wall-clock ISO-8601 millisecond value for ordering, not a progress count, duration, or step total, and activity within one millisecond simply reuses the stamp. Only the type of an event and the role of its message are inspected: no delta, thinking block, tool argument, or partial result is stored or projected, and the answer still comes only from the final assistant message.
94
94
 
95
95
  Context usage stays out of the boundary: no token, usage, context-window, or cost figure is projected for a running or terminal turn, so approaching context exhaustion remains invisible in the turn view and in `pi_session_get`. This change adds no progress counters, tool names, arguments, results, or transcripts; see *Failure causes* below for failure-cause classification, which is not part of the turn view and is decided in *Failure causes* below.
96
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-session-mcp",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Local stdio MCP server for controlled Pi coding-agent sessions",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -11,7 +11,7 @@ You control Pi worker sessions through the pi-session-mcp tools. You choose only
11
11
 
12
12
  - Call `pi_capabilities_get` before anything else in a run. Use only the workspace and profile aliases it lists. Never invent an alias or pass a path.
13
13
  - Each profile lists `permissionProfile` (`read-only` or `coding`), `extensions` and `skills`. `skills` names the configured skill aliases. That is configuration intent only. It does not prove that a session loaded the skill or that the model will read it.
14
- - Capabilities do not prove that a provider or model is reachable. The first `pi_session_start` is the real check.
14
+ - Capabilities do not prove that a provider or model is reachable. A successful `pi_session_start` shows that the session was created with the selected provider, model and thinking level, and that local authentication, the extensions, external MCP servers and selected skills passed their start checks. It does not prove that the provider accepts requests: credentials, quota and network show only when a turn runs.
15
15
 
16
16
  ## 2. Scope each task
17
17
 
@@ -56,7 +56,7 @@ Keep an internal ledger with one row per task: task, workspace alias, profile al
56
56
 
57
57
  ## 7. Interpret progress and results
58
58
 
59
- - `updatedAt` on a running turn is a last-activity stamp. A stamp that has not moved is not proof of a hang: the model may be thinking or writing a long answer. Judge by your time budget, not by `updatedAt` alone.
59
+ - `updatedAt` on a running turn is the time of the last observed public SDK activity: assistant message start, streaming updates and end, and tool execution start, update and end. Streaming is activity, not evidence of finished or useful work. Work without a public event stays invisible, for example inside an extension's own agent loop or in a long tool call without updates. A stamp that has not moved is therefore no proof of a hang. Judge by your time budget and by the workspace.
60
60
  - `completed` is a technical state: the turn ended and returned text. It does not mean the task succeeded. Read `assistantText` and check `truncated`.
61
61
  - Never accept a worker report as proof. A worker saying the tests pass is a claim, not evidence.
62
62
 
@@ -77,8 +77,9 @@ You integrate. Workers never integrate each other's changes. Resolve conflicts b
77
77
  - `failed` means `turn_failed`. The error carries no cause. Check the result in the workspace, then retry once in a fresh session with a narrower prompt, or take the task over yourself.
78
78
  - `aborted` means the turn stopped. Look at what the workspace contains before reusing it.
79
79
  - `pi_session_abort` returns `abort_failed` if the abort was rejected. The turn may still be running. Poll it.
80
- - `abort_timeout` is not a successful abort. The turn may still be running or may finish normally. Poll `pi_turn_get` until it is terminal. If it stays `running`, close the session with `pi_session_close` and treat the workspace as unverified until you inspect it.
81
- - On `pi_session_start` errors such as `unknown_execution_profile`, `skill_load_failed` or the `extension_*` and `external_mcp_*` codes, do not retry blindly. The cause is configuration. Report it to the operator.
80
+ - `abort_timeout` is not a successful abort. The turn may still be running or may finish normally. Decide an observation and cleanup budget before you abort, for example a fixed number of polling rounds within a few minutes. Poll `pi_turn_get` until the turn is terminal or the budget is spent, then close the session with `pi_session_close`. If the close fails or does not finish, stop using that workspace, treat it as unverified and report it. Never wait without a bound.
81
+ - On `pi_session_start` errors, do not retry blindly and do not change the profile. These codes need the operator; a retry cannot fix them: `unknown_execution_profile`, `unknown_workspace`, `skill_load_failed`, `extension_not_installed`, `extension_version_mismatch`, `extension_tool_conflict`, `external_mcp_no_tools`, `external_mcp_grant_tool_missing` and `external_mcp_activation_mismatch`.
82
+ - `external_mcp_unavailable` (an external MCP server that cannot connect, times out or breaks the protocol) and `extension_load_failed` (an invalid package, but also a start timeout) can be transient. Retry at most once after a pause, then report.
82
83
 
83
84
  ## 10. Close every session
84
85