pi-session-mcp 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.pi-session-mcp-cli/config.js +11 -1
  2. package/.pi-session-mcp-cli/doctor-cli.js +5 -1
  3. package/.pi-session-mcp-cli/doctor.js +22 -1
  4. package/.pi-session-mcp-cli/execution-profile.js +1 -0
  5. package/.pi-session-mcp-cli/extension-spec.js +21 -0
  6. package/.pi-session-mcp-cli/pi-extensions.js +67 -0
  7. package/CHANGELOG.md +54 -0
  8. package/CONTRIBUTING.md +1 -1
  9. package/README.md +2 -2
  10. package/SECURITY.md +1 -1
  11. package/dist/capabilities.d.ts +1 -0
  12. package/dist/capabilities.js +2 -1
  13. package/dist/client-adapters/mcp-verifier.js +5 -2
  14. package/dist/config.d.ts +1 -0
  15. package/dist/config.js +11 -1
  16. package/dist/doctor-cli.js +5 -1
  17. package/dist/doctor.d.ts +4 -1
  18. package/dist/doctor.js +22 -1
  19. package/dist/execution-profile.d.ts +3 -0
  20. package/dist/execution-profile.js +1 -0
  21. package/dist/extension-spec.d.ts +15 -0
  22. package/dist/extension-spec.js +21 -0
  23. package/dist/main.js +6 -2
  24. package/dist/pi-adapter.d.ts +1 -1
  25. package/dist/pi-adapter.js +4 -0
  26. package/dist/pi-extensions.d.ts +17 -0
  27. package/dist/pi-extensions.js +67 -0
  28. package/dist/sdk-pi-adapter.d.ts +24 -6
  29. package/dist/sdk-pi-adapter.js +253 -86
  30. package/dist/server.js +1 -0
  31. package/dist/session-tools.d.ts +9 -0
  32. package/dist/session-tools.js +25 -0
  33. package/dist/setup-command-guardian.js +29 -73
  34. package/dist/setup-main.js +2 -1
  35. package/dist/setup-ownership.js +18 -28
  36. package/dist/setup-platform.d.ts +57 -0
  37. package/dist/setup-platform.js +195 -0
  38. package/dist/setup-process.d.ts +6 -3
  39. package/dist/setup-process.js +85 -93
  40. package/dist/setup.js +2 -1
  41. package/dist/stdout-guard-install.d.ts +5 -0
  42. package/dist/stdout-guard-install.js +6 -0
  43. package/dist/stdout-guard.d.ts +20 -0
  44. package/dist/stdout-guard.js +72 -0
  45. package/docs/acceptance.md +245 -0
  46. package/docs/adr/0005-macos-setup-platform.md +84 -0
  47. package/docs/adr/0006-pi-extension-allowlist.md +58 -0
  48. package/docs/architecture.md +1 -1
  49. package/docs/client-setup-design.md +21 -9
  50. package/docs/client-setup.md +56 -10
  51. package/docs/doctor-design.md +9 -2
  52. package/docs/installation.md +129 -4
  53. package/docs/macos-setup-design.md +386 -0
  54. package/docs/pi-extensions-design.md +421 -0
  55. package/docs/releasing.md +1 -1
  56. package/docs/superpowers/plans/2026-10-10-macos-setup.md +1725 -0
  57. package/docs/superpowers/plans/2026-10-11-pi-extensions.md +2450 -0
  58. package/docs/threat-model.md +54 -18
  59. package/docs/tool-contracts.md +8 -3
  60. package/package.json +1 -1
@@ -0,0 +1,421 @@
1
+ # Pi extensions design
2
+
3
+ Status: approved by the owner on 2026-10-11 for implementation. Tracking issue:
4
+ [#34](https://github.com/plagor44-ux/pi-session-mcp/issues/34).
5
+
6
+ ## Goal
7
+
8
+ An operator can allowlist Pi extensions per execution profile. A profile that
9
+ names an extension gets an embedded Pi session in which that extension is
10
+ loaded with its full capabilities: providers, tools, hooks and system-prompt
11
+ changes. The first target is `pi-cursor-sdk`, which registers the `cursor`
12
+ provider; other provider extensions follow the same path.
13
+
14
+ Success means:
15
+
16
+ - a `cursor` profile works end to end through `pi_session_start`,
17
+ `pi_session_prompt`, `pi_turn_get` and `pi_session_close`, including two
18
+ parallel `cursor` sessions;
19
+ - Doctor reports a missing or mismatched extension offline, without running
20
+ extension code;
21
+ - profiles without `extensions` keep their session-creation path; their only
22
+ change is that prompts pass `expandPromptTemplates: false`, which has no
23
+ effect without templates, skills or extensions;
24
+ - stdout carries only MCP frames even when an extension writes to it;
25
+ - automated tests need no credentials and no network.
26
+
27
+ ## Non-goals
28
+
29
+ - Extension sources other than npm packages installed by Pi (`git:`, local
30
+ paths, URLs).
31
+ - Installing or updating extensions. Pi owns that (`pi update --extensions`).
32
+ - Version ranges or "latest". A version is pinned exactly.
33
+ - Pi's built-in extensions (`mcp`, `codemode`, `tool-search`, `llama.cpp`).
34
+ pi-session-mcp keeps its own `mcpServers` bridge.
35
+ - Extension CLI flags (for example `--cursor-runtime`) and extension UI. Flags
36
+ keep their defaults; UI calls go to the SDK's no-op UI.
37
+ - Extension slash commands triggered by client prompt text.
38
+ - Process isolation of extension code. Extensions run in the server process.
39
+
40
+ ## Owner decisions
41
+
42
+ | Question | Decision |
43
+ | --- | --- |
44
+ | Capability scope | Full: providers, tools, hooks, system-prompt changes. |
45
+ | Naming and pinning | `npm:<name>@<exact version>`, resolved from Pi's user install; any other installed version fails closed. |
46
+ | Granularity | Per execution profile. |
47
+ | `read-only` profiles | Extensions allowed; the model's tools stay read/grep/find/ls. |
48
+ | Integration approach | Pi SDK session lifecycle API (`createAgentSessionServices`, `createAgentSessionFromServices`, `AgentSessionRuntime`). |
49
+
50
+ ## Current behavior and constraints
51
+
52
+ `createPhase0ResourceLoader` (`src/sdk-pi-adapter.ts`) builds a
53
+ `DefaultResourceLoader` with `noExtensions: true`, no skills, templates or
54
+ themes, and a disabled system prompt. `SdkPiSessionAdapter.create` resolves
55
+ provider, model, authentication and availability against one `ModelRuntime`
56
+ shared by all sessions, and only then builds the resource loader and calls
57
+ `createAgentSession`.
58
+
59
+ Facts about Pi SDK 1.1.0 that shape this design, verified against the
60
+ installed package:
61
+
62
+ 1. An extension's `registerProvider` calls are queued at load time and applied
63
+ to the `ModelRuntime` when the session's extension runner binds its core,
64
+ which happens inside session construction. A provider check that runs before
65
+ the session exists does not see an extension provider.
66
+ `createAgentSessionServices` applies the queued registrations to the
67
+ services' runtime, so the provider is visible before the session exists.
68
+ 2. Provider registrations are global to a `ModelRuntime` instance; the last
69
+ registration for an id wins. `pi-cursor-sdk` fails sibling sessions that
70
+ share a runtime with `CURSOR_PROVIDER_OWNERSHIP_ERROR`. An extension session
71
+ therefore needs its own runtime.
72
+ 3. `createAgentSession` does not emit `session_start`; only
73
+ `session.bindExtensions()` does. `pi-cursor-sdk` refuses to stream until it
74
+ has seen `session_start`.
75
+ 4. `AgentSession.dispose()` does not emit `session_shutdown`;
76
+ `AgentSessionRuntime.dispose()` does. `pi-cursor-sdk` registers cleanup on
77
+ `session_shutdown`.
78
+ 5. `session.prompt(text)` dispatches extension slash commands unless it is
79
+ called with `expandPromptTemplates: false`.
80
+ 6. With an explicit `tools` list, every tool, extension tools included, is
81
+ filtered by the session's allowlist.
82
+ 7. `noExtensions: true` suppresses settings and auto-discovered extensions but
83
+ still loads `additionalExtensionPaths`.
84
+ 8. `DefaultPackageManager.resolve()` runs `npm install` for missing or
85
+ mismatched npm packages, and `getInstalledPath(source, "user")` falls back
86
+ to a synchronous `npm root -g` on a miss and accepts legacy global
87
+ installs; neither is side-effect free.
88
+ 9. `pi-cursor-sdk` and `@cursor/sdk` call `console.log`. In a stdio MCP server
89
+ such output corrupts the protocol stream.
90
+
91
+ The relevant SDK functions are root exports of
92
+ `@earendil-works/pi-coding-agent`: `createAgentSessionServices`,
93
+ `createAgentSessionFromServices`, `createAgentSessionRuntime`,
94
+ `AgentSessionRuntime`, `getAgentDir` and
95
+ `ModelRuntime`. No internal module is imported.
96
+
97
+ ## Design
98
+
99
+ ### Configuration
100
+
101
+ Every execution profile accepts an optional `extensions` array:
102
+
103
+ ```json
104
+ "code-cursor": {
105
+ "default": false,
106
+ "permissionProfile": "coding",
107
+ "provider": "cursor",
108
+ "model": "<cursor-model-id>",
109
+ "thinkingLevel": "high",
110
+ "extensions": ["npm:pi-cursor-sdk@0.5.3"]
111
+ }
112
+ ```
113
+
114
+ Rules, enforced by `configSchema`:
115
+
116
+ - Each entry matches `npm:<name>@<version>`. `<name>` is an npm package name,
117
+ optionally scoped (`@scope/name`), lowercase. `<version>` is an exact semver
118
+ version with optional prerelease and build metadata. Ranges, tags, `git:`,
119
+ paths and URLs are rejected.
120
+ - At most 8 entries per profile. A package name appears at most once per
121
+ profile.
122
+ - The field is allowed on `read-only` and `coding` profiles, including the
123
+ default profile.
124
+ - An invalid entry fails the whole configuration with
125
+ `invalid_execution_profile_config`, like every other schema violation.
126
+
127
+ The parsed profile keeps `extensions` as a frozen array of
128
+ `{ name, version }` pairs plus the original spec string. The configuration
129
+ fingerprint already covers the field because it hashes the file bytes.
130
+
131
+ ### Extension resolution
132
+
133
+ A new module `src/pi-extensions.ts` owns resolution. It is used by the adapter
134
+ and by Doctor.
135
+
136
+ `resolveExtension(spec, dependencies)`:
137
+
138
+ 1. Computes Pi's user install location `<agent dir>/npm/node_modules/<name>`,
139
+ with the agent directory from `getAgentDir()`. It never calls the SDK
140
+ package manager, never spawns npm and never installs. A package installed
141
+ anywhere else counts as not installed.
142
+ 2. No path: `extension_not_installed`.
143
+ 3. Reads `<dir>/package.json`. Unreadable or invalid JSON:
144
+ `extension_load_failed`. A `version` that differs from the pinned version:
145
+ `extension_version_mismatch`.
146
+ 4. Reads `pi.extensions` from the manifest. It must be a non-empty array of
147
+ relative paths. Every entry must resolve, after symlink resolution, to a
148
+ file inside the real package directory. Otherwise: `extension_load_failed`.
149
+ `pi.extensions` entries must be plain relative file paths; globs and
150
+ overrides, which Pi accepts, fail with `extension_load_failed`.
151
+ 5. Returns the real package directory. The SDK receives that directory, not
152
+ the `npm:` source, so it cannot start a temporary install.
153
+
154
+ Dependencies (install-location lookup, file reads, real-path resolution, stat) are
155
+ injectable for tests.
156
+
157
+ ### Session creation
158
+
159
+ Profiles without `extensions` keep today's path unchanged: shared runtime,
160
+ `createPhase0ResourceLoader`, `createAgentSession`.
161
+
162
+ For a profile with `extensions`, `SdkPiSessionAdapter.create` runs:
163
+
164
+ 1. **Resolve.** Resolve every extension as above.
165
+ 2. **Runtime.** Create a fresh `ModelRuntime` for this session through the
166
+ existing `modelRuntimeFactory`. It reads Pi's normal authentication store.
167
+ 3. **Services.** Call `createAgentSessionServices` with that runtime, the
168
+ workspace as `cwd` and `agentDir`, an in-memory untrusted settings manager,
169
+ and resource-loader options identical to `createPhase0ResourceLoader` plus
170
+ `additionalExtensionPaths` set to the resolved package directories. Any
171
+ services diagnostic of type `error`, or an exception, is
172
+ `extension_load_failed`.
173
+ 4. **Checks.** Run the existing provider, model, `checkAuth`, `getAuth` and
174
+ `getAvailable` checks against the services' runtime, with the existing
175
+ error codes and deadlines.
176
+ 5. **External MCP tools.** Connect configured `mcpServers` as today.
177
+ 6. **Session.** Call `createAgentSessionFromServices` with the model, thinking
178
+ level, an in-memory session manager, the tool list (see Tools) and the MCP
179
+ custom tools. The session is wrapped so that it can be disposed through
180
+ `AgentSessionRuntime` (see Lifecycle).
181
+ 7. **Bind.** Call `session.bindExtensions({ mode: "print" })` without a UI
182
+ context, so the SDK's no-op UI applies and `session_start` is emitted. A
183
+ failure is `extension_load_failed`.
184
+ 8. **Postchecks.** Model, thinking level and active tools. For extension
185
+ sessions no active tool may be outside the composed allowlist, but fewer
186
+ active tools are allowed: an extension may deactivate tools, as
187
+ `pi-cursor-sdk` does with its own tool in `session_start`. Sessions without
188
+ extensions keep the exact-equality check as today.
189
+
190
+ Step 3, step 6 and step 7 are each bounded by the existing session-creation
191
+ deadline (`SESSION_CREATION_TIMEOUT_MS`). A failure after step 6 disposes the
192
+ session through the lifecycle path, which emits `session_shutdown`. A session
193
+ that resolves after its deadline is disposed the same way, never admitted.
194
+ Extension code loaded in step 3 cannot be unloaded; a failure between step 3
195
+ and step 6 leaves no session to dispose.
196
+
197
+ ### Tools, hooks and prompts
198
+
199
+ - **`read-only`.** The tool list is `read`, `grep`, `find`, `ls`, exactly as
200
+ today. Extension tools are registered but inactive. An extension that calls
201
+ `setActiveTools` cannot activate a tool outside the list; a contract test
202
+ proves this against the real SDK. If the SDK does not enforce it, the
203
+ adapter adds its own check before the implementation is accepted.
204
+ - **`coding`.** The tool list is the built-in coding tools, the granted MCP
205
+ tools, and every tool registered by the profile's extensions. The adapter
206
+ reads the registered tool names from the services' resource loader after
207
+ step 3.
208
+ - **Name conflicts, both profiles.** An extension tool whose name equals a
209
+ built-in tool (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`), a
210
+ granted MCP tool or a tool of another extension fails with
211
+ `extension_tool_conflict`. This also stops an extension from replacing a
212
+ built-in tool inside a `read-only` profile.
213
+ A name must also match `[A-Za-z0-9_.:][A-Za-z0-9_.:-]*`: the SDK reads `*`
214
+ in an allowlist as a glob and a leading `+` or `-` as a modifier, so any
215
+ other name fails the same way, in both profiles.
216
+ The check runs after load and again after binding, because a `session_start`
217
+ handler can register a tool during `bindExtensions`. If the registered tool
218
+ set then differs from the one the session was created with, the start fails
219
+ with `extension_tool_conflict`. Tools an extension registers later, during a
220
+ turn, are not re-checked; allowlisted extensions are trusted code.
221
+ - **Hook errors while binding.** A hook error reported while binding fails the
222
+ start with `extension_load_failed`. Failure cleanup disposes the session
223
+ runtime, bounded by the creation deadline, concurrently with closing the
224
+ granted MCP connections.
225
+ - **MCP servers registered by extensions.** An extension that registers an MCP
226
+ server while loading or starting fails the start with
227
+ `extension_load_failed`, because the built-in MCP extension is not loaded.
228
+ - **Hooks.** All extension hooks run in both permission profiles, including
229
+ `before_agent_start` system-prompt changes, `tool_call`, `tool_result`,
230
+ `before_provider_request`, `before_provider_headers` and `context`.
231
+ - **Prompts.** Every `session.prompt()` call, for every profile, passes
232
+ `expandPromptTemplates: false`. Client prompt text never dispatches an
233
+ extension command, a skill command or a prompt template.
234
+ - **UI and flags.** No UI context is bound; extension UI calls return the
235
+ SDK's no-op results. `extensionFlagValues` is empty, so extension flags keep
236
+ their defaults.
237
+
238
+ ### Lifecycle and close
239
+
240
+ The handle of an extension session owns the `AgentSessionRuntime` that wraps
241
+ its services and session. `close`, the close cleanup after an abort deadline,
242
+ registry shutdown and late-start disposal call `AgentSessionRuntime.dispose()`,
243
+ which emits `session_shutdown` and disposes the session. Existing deadlines,
244
+ idempotent close and tombstones do not change. MCP connections close
245
+ concurrently with the runtime, as they close concurrently with the session
246
+ today; the handle's disposal completes when both have settled.
247
+
248
+ How the session is wrapped in an `AgentSessionRuntime` uses only the exported
249
+ `createAgentSessionRuntime` and `AgentSessionRuntime` API. The exact wiring is
250
+ fixed in the implementation plan and proven by a contract test that observes
251
+ `session_shutdown`.
252
+
253
+ ### stdout guard
254
+
255
+ A new module `src/stdout-guard.ts` runs first in `src/main.ts`, before any
256
+ other import with side effects:
257
+
258
+ - It captures the original `process.stdout.write`.
259
+ - It builds a write-through `Proxy` over the real stdout: `write` and `end`
260
+ resolve to the original bound methods and every other function is bound to
261
+ the real stream, so queued frames reach stdout synchronously. `main` passes
262
+ `new StdioServerTransport(process.stdin, thatProxy)` to `serveStdio`
263
+ through its `transport` option.
264
+ - It also patches `process.stdout.end` for foreign callers: a foreign `end`
265
+ neither leaks content nor closes the real stdout.
266
+ - It replaces `process.stdout.write` and `console.log`, `console.info` and
267
+ `console.debug`. Their output is dropped. The guard writes at most one
268
+ structured stderr line per second, carrying the bytes and the number of
269
+ writes suppressed since its previous line:
270
+ `{"level":"warning","event":"foreign_stdout_suppressed","bytes":<n>,"writes":<m>}`.
271
+ Pending counts are flushed on shutdown. The content is never written
272
+ anywhere, because it may contain prompts or secrets.
273
+
274
+ The guard is active for every server start, not only with extensions. A child
275
+ process started with inherited stdio writes to file descriptor 1 directly and
276
+ cannot be intercepted in Node.js; this is a documented residual risk.
277
+
278
+ ### Capabilities projection
279
+
280
+ `pi_capabilities_get` adds `extensions` to every execution profile: an array of
281
+ the configured spec strings, empty for profiles without extensions. The field
282
+ is path-free and secret-free. `capabilitiesSchema` and
283
+ `docs/tool-contracts.md` change accordingly.
284
+
285
+ ### Doctor
286
+
287
+ Doctor gains two checks after the workspace checks, one per profile and
288
+ extension, with the subject `<profile>:<package name>`:
289
+
290
+ - `extension_missing` (error) when the resolver finds no installed package;
291
+ - `extension_version_mismatch` (error) when the installed manifest version
292
+ differs, or the manifest is unreadable.
293
+
294
+ Doctor reads manifests only; it never imports extension code. It stays
295
+ offline. `setup --verify` includes Doctor and therefore these checks.
296
+
297
+ ### Diagnostics
298
+
299
+ The adapter's diagnostic sink gains the stages `extension_resolve`,
300
+ `extension_load` and `extension_bind`. A diagnostic carries the profile alias
301
+ and the error code; a resolution failure also carries the failing extension as
302
+ `<name>@<version>`. Disposal stays best effort and reports nothing, as today. It never carries a path, an extension error message, or prompt content.
303
+
304
+ ## Failure semantics
305
+
306
+ New `PiSessionCreationErrorCode` values, all fail closed before a session is
307
+ admitted:
308
+
309
+ | Code | Message | When |
310
+ | --- | --- | --- |
311
+ | `extension_not_installed` | Configured Pi extension is not installed | Pi has no user install of the package. |
312
+ | `extension_version_mismatch` | Configured Pi extension version is not installed | The installed version differs from the pinned version. |
313
+ | `extension_load_failed` | Configured Pi extension could not be loaded | Invalid manifest, entry outside the package, load or bind error, services diagnostic of type `error`. |
314
+ | `extension_tool_conflict` | Configured Pi extension tools conflict | An extension tool name collides with a built-in, MCP or other extension tool. |
315
+
316
+ Existing codes keep their meaning. An extension provider that is unknown after
317
+ loading is `unknown_provider`.
318
+
319
+ Known limit: `pi-cursor-sdk` returns a placeholder key when no Cursor key is
320
+ stored, so `checkAuth` and `getAuth` succeed and the first prompt fails with
321
+ `turn_failed`. Detecting the placeholder would depend on extension internals;
322
+ the limit is documented instead.
323
+
324
+ ## Trust boundary and limits
325
+
326
+ - An allowlisted extension is code the operator trusts. It runs in the server
327
+ process with the user's full rights and may start processes, use the
328
+ network, and read and write files. `pi-cursor-sdk` writes below
329
+ `~/.pi/agent`, `~/.cursor/projects/` and `<workspace>/.pi/`.
330
+ - Pinning the version prevents silent updates. It does not protect against a
331
+ modified installed package directory, which is user-writable code like any
332
+ other.
333
+ - `read-only` limits the model's tool set, not the extension's code.
334
+ - pi-session-mcp keeps its own state in memory only. Extensions may persist
335
+ their own state outside its control. AGENTS.md and `docs/architecture.md`
336
+ state this next to the product boundary.
337
+ - The MCP client remains one trusted local peer. It selects profile aliases
338
+ only and cannot name an extension.
339
+
340
+ ## Testing
341
+
342
+ Automated tests use no credentials and no network.
343
+
344
+ - **Config** (`test/config.test.ts`): valid scoped and unscoped specs;
345
+ rejection of ranges, tags, `git:`, paths, duplicates and more than 8
346
+ entries; the field on read-only and coding profiles.
347
+ - **Resolution** (`test/pi-extensions.test.ts`), with an injected install-location
348
+ lookup and file system: not installed, version mismatch, invalid manifest,
349
+ missing `pi.extensions`, an entry or symlink escaping the package directory,
350
+ and the guarantee that `resolve()` and install methods are never called.
351
+ - **Doctor** (`test/doctor.test.ts`): both new checks, check order, and that
352
+ no extension module is imported.
353
+ - **Adapter** (`test/sdk-pi-adapter.test.ts`), with injected services and
354
+ session factories:
355
+ - step order, and the provider being visible only after services;
356
+ - two sessions getting two runtimes;
357
+ - `bindExtensions` being called;
358
+ - `session_shutdown` on close, on a failure after services, and on a late
359
+ start after the deadline;
360
+ - tool lists for read-only and coding, and `extension_tool_conflict`;
361
+ - `expandPromptTemplates: false` on every prompt;
362
+ - profiles without extensions taking the unchanged path.
363
+ - **stdout guard** (`test/stdout-guard.test.ts`): foreign `write` and
364
+ `console.*` output does not reach stdout, transport writes do, and the
365
+ stderr event carries only a byte count. Regressions cover synchronous
366
+ delivery of every queued transport frame and a foreign `end` that must not
367
+ close stdout.
368
+ - **SDK contract with the real SDK** (`test/sdk-extension-contract.test.ts`):
369
+ a fixture extension in `test/fixtures/pi-extension-fixture`, installed into a
370
+ temporary Pi agent directory, registers:
371
+ - a provider with a canned, network-free stream;
372
+ - a tool and a slash command;
373
+ - a `setActiveTools` attempt;
374
+ - `console.log` and `process.stdout.write` calls;
375
+ - `session_start` and `session_shutdown` hooks;
376
+ - a `before_agent_start` hook that repeats the widening attempt (adding
377
+ `bash`, `write`, `edit` and the fixture tool) on every prompt.
378
+
379
+ The test proves that:
380
+ - the provider resolves and a prompt completes;
381
+ - read-only keeps the fixture tool and the built-in write tools inactive,
382
+ including against the mid-turn `before_agent_start` widening attempt;
383
+ - the slash command is not dispatched;
384
+ - both lifecycle events are observed.
385
+ - **stdio end to end** (`test/process-extension.test.ts`): the built server runs
386
+ with a configuration that uses the fixture extension. Every stdout frame
387
+ parses, and stderr contains `foreign_stdout_suppressed`.
388
+
389
+ ## Documentation and governance
390
+
391
+ - ADR 0006 records the decision and its trust boundary.
392
+ - `docs/installation.md`: the `extensions` rule in the configuration section.
393
+ - `docs/tool-contracts.md`: the capabilities field and the new error codes.
394
+ - `docs/threat-model.md` and `docs/architecture.md`: the trust boundary, the
395
+ stdout guard and its residual risk, and extension-owned persistence.
396
+ - `docs/doctor-design.md`: the two new checks.
397
+ - A new section "Pi extensions" in `docs/installation.md` with the
398
+ `pi-cursor-sdk` example and the placeholder-key limit.
399
+ - AGENTS.md: the product-boundary sentence names extension-owned persistence
400
+ and the allowlist rule.
401
+ - `docs/acceptance.md`: the manual live check below.
402
+ - CHANGELOG.md: an entry under Unreleased.
403
+
404
+ ## Acceptance on real hardware
405
+
406
+ Manual, not in CI, recorded in `docs/acceptance.md`:
407
+
408
+ 1. `pi-cursor-sdk@0.5.3` installed by Pi with a stored Cursor key.
409
+ 2. A `code-cursor` profile: `pi_session_start`, a "PONG" prompt, `pi_turn_get`
410
+ completed, `pi_session_close`.
411
+ 3. Two parallel `cursor` sessions, each completing a prompt.
412
+ 4. After close, no Cursor child process remains, and stderr shows no
413
+ `foreign_stdout_suppressed` storm.
414
+ 5. Record how the Cursor local runtime starts its child processes (inherited or
415
+ piped stdio).
416
+
417
+ ## Delivery
418
+
419
+ One branch (`feat/pi-extensions`) and one pull request for #34, with commits
420
+ grouped by concern. The owner's live configuration gets a `cursor` profile only
421
+ after the acceptance above.
package/docs/releasing.md CHANGED
@@ -12,7 +12,7 @@ does not authorize any of them.
12
12
  [ADR 0003](adr/0003-post-mvp-maintenance.md) sets the rule:
13
13
 
14
14
  - A change that keeps the public MCP contract ships as a patch release
15
- (`0.6.x`). This includes Pi SDK updates.
15
+ (for example `0.7.x`). This includes Pi SDK updates.
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