pi-ptc-subagents 1.5.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,9 +5,152 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [2.0.0] - 2026-10-10
9
9
 
10
- (nothing yet)
10
+ ### Added
11
+
12
+ - **`ptc_run_code` and `ptc_workflow` reach the line where pi's `codemode` orchestrates.** A
13
+ session on the `subagents` surface used to hold `ptc_subagent` and the lifecycle face with no
14
+ program behind them: pi's `codemode` was composing tool calls, and the one capability its
15
+ QuickJS sandbox cannot provide — spawning a process — was unreachable, because `pi.dispatch`,
16
+ background tasks and the frozen six-name environment all live inside the PTC worker. Both tools
17
+ are now registered there at `codemode` reach, which makes them **callable from a `codemode`
18
+ script without being declared to the model**: `AgentSession._isDeclarable` admits `direct` and
19
+ `model-only` only, so the request pi sends still carries exactly one orchestrator and it is
20
+ pi's. Verified end to end on a real pi 1.1.0 session — a `codemode` script enumerates all six
21
+ `ptc_*` tools in `ALL_TOOLS` and successfully calls `await tools.ptc_run_code({ code: … })`,
22
+ while the payload carries only the subagent face
23
+ ([ADR-0025](./docs/adr/0025-extension-surface-is-a-setting.md) §3 as amended).
24
+
25
+ **A pi older than 0.99.0 is unaffected.** `ToolExposure` does not exist there — `grep -r
26
+ exposure` over `pi-coding-agent@0.86.1`'s `dist/` returns nothing, and so does
27
+ `AgentSession._getCallableTools` — so the field is ignored and the tools are declared to the
28
+ model, which is correct: such a pi ships no `codemode`, resolves to `full` anyway, and has
29
+ nothing that could call a `codemode`-reach tool. Measured across 0.86.1 / 0.87.1 / 0.99.0 /
30
+ 0.99.1 / 0.99.2 / 1.0.0 / 1.0.4 / 1.1.0.
31
+
32
+ **Naming either tool in `--tools` or `defaultTools` overrides this.** A tool named there is
33
+ recorded in pi's `toolsAdded`, which the prompt projection filters only against `hidden` and
34
+ never against `codemode` — so it is declared to the model regardless of its exposure. That is
35
+ a user's explicit choice and is left alone; it is recorded here because the exposure field does
36
+ not survive it.
37
+
38
+ ### Changed
39
+
40
+ - **Surface detection asks pi for the loadout instead of reconstructing it.** The third question —
41
+ will `codemode` be **active**, i.e. callable by the model — used to be answered by parsing
42
+ `--tools` / `--exclude-tools` / `--no-tools` / `--no-builtin-tools` out of `process.argv`,
43
+ merging `defaultTools` across the user-scope and project-scope settings files, and replaying pi's
44
+ precedence over the result. That is deleted. At `session_start` — the first moment pi's runtime is
45
+ bound — `getActiveTools()` answers directly, and pi has already applied both the command line and
46
+ its project-trust decision to what it returns. Registration moves to the same event, where pi
47
+ adds a tool from it and refreshes its own registry; the command-line reader, the `defaultTools`
48
+ merge, the precedence replay and the differential test that held them honest all go with it
49
+ ([ADR-0035](./docs/adr/0035-ask-pi-for-the-loadout.md)).
50
+
51
+ **What this fixes.** A project's `defaultTools` was read off disk with no trust check, while pi
52
+ drops project settings for any project it has not been told to trust. An untrusted project could
53
+ therefore name `codemode` and put the session on the `subagents` surface — whose
54
+ `ptc_run_code` and `ptc_workflow` are registered at `codemode` reach, so with no `codemode`
55
+ running they are reachable from nowhere (#131). A trusted project that configures `codemode` that
56
+ way keeps the `subagents` surface, exactly as before.
57
+
58
+ **What does not change.** The MCP auto-enable evidence stays a file-side read, because pi
59
+ activates `codemode` from inside its own `session_start` handler and extension order is not ours
60
+ to choose — measured across pi 0.86.1 → 1.1.0, a read from ours is too early to see it, and the
61
+ existing cross-check catches it when it lands. The **codemode switch** axis also stays as it is;
62
+ it may also be collapsible into the live registry, but that loses the distinction between "not
63
+ shipped" and "not loaded", and it is a separate decision. Behaviour is otherwise unchanged on
64
+ every pi this package supports, verified from 0.86.1 upward under both trust decisions.
65
+
66
+ - **BREAKING: the `surfaceMode` setting is removed, and the surface is detected only.** Which
67
+ model-facing tools this package registers is now a pure function of what pi is — three probes
68
+ (does this pi ship `codemode`, will pi load it, can the model call it), with `full` as the
69
+ fallback for every case a probe cannot answer
70
+ ([ADR-0034](./docs/adr/0034-surface-is-detected-not-set.md)).
71
+
72
+ **What is gone:**
73
+
74
+ - the `surfaceMode` key in `~/.pi/agent/ptc.json` (`off` / `subagents` / `full`);
75
+ - the `off` value — a session that wants none of this package's tools no longer has a way to say
76
+ so here;
77
+ - the `/ptc surface [off|subagents|full]` subcommand. **`/ptc off` is unaffected** — it turns PTC
78
+ _mode_ off, which was never the same thing as the extension surface;
79
+ - the `surfaceModeConflict` notice about a pinned value disagreeing with detection.
80
+
81
+ **Migrating: use pi, not this package.** Run `pi config` and disable this package's extensions
82
+ there — measured on pi 1.1.0 against this package's own `dist/index.js`, a `packages` entry whose
83
+ `extensions` is `[]` or `["!dist/index.js"]` is not loaded at all, while `["+dist/index.js"]` and
84
+ an omitted key are. This is strictly better than any value of a key this package reads, because
85
+ pi honours it **without loading the extension**: none of this package runs, rather than running
86
+ and then deciding to register nothing. In a project-level `.pi/settings.json` write
87
+ `["!dist/index.js"]` rather than `[]` — pi resolves the two scopes with two different functions
88
+ (`package-manager.js:1850`), where an empty project delta means "no change".
89
+
90
+ **Expect one warning on upgrade if you had the key set.** A `ptc.json` carrying `surfaceMode` is
91
+ reported at session start and the key is **not** honoured — deliberately, including for `"off"`,
92
+ which is why this package comes back after being switched off. The warning names the file, quotes
93
+ the value, and points at `pi config`; deleting the key silences it.
94
+
95
+ - **ADR-0025 decision 3 is amended.** `subagents` does register the programming pair now. What
96
+ the decision protected — the model being offered one orchestration surface, not two — is
97
+ unchanged and is now enforced by exposure rather than by an absent tool. `CONTEXT.md`'s
98
+ _orchestration surface_ and _subagent surface_ entries and the README's surface list say so
99
+ too.
100
+
101
+ - **The `codemode` activation probe now reads every CLI flag that can remove the tool.** It reads
102
+ `--exclude-tools` / `-xt` and `--no-tools` / `-nt` in addition to `--tools` / `-t`, and it applies
103
+ them in pi's own order: `-t` decides the list and beats `-nt`, while `-xt` vetoes whatever list
104
+ won. That last one was a real misreading — pi builds the active set as
105
+ `(tools ?? configured).filter(name => !excluded.has(name))` (`sdk.js:148`), so **exclusion beats
106
+ inclusion**, and `-t codemode -xt codemode` was previously read as `active`. The direction was the
107
+ harmful one: under `subagents`, `ptc_run_code` / `ptc_workflow` are at `codemode` reach, so an
108
+ orchestrator believed to be present but absent is an orchestrator nobody can call.
109
+ `--no-builtin-tools` is deliberately still not read: it maps to `noTools: "builtin"` and
110
+ `codemode` is not one of pi's built-in tools, so reading it would be a guess about a flag that
111
+ cannot move it.
112
+
113
+ [2.0.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v1.6.0...v2.0.0
114
+
115
+ ## [1.6.0] - 2026-10-08
116
+
117
+ ### Fixed
118
+
119
+ - **On pi 1.0, a session with MCP servers was offered two orchestration tools at once.** pi's MCP
120
+ extension activates pi's own `codemode` tool by itself — MCP tools default to `codemode`
121
+ exposure so that they are reachable only from scripts — and it does that with a
122
+ `pi.setActiveTools` call that no settings file records. This package's surface detection
123
+ ([ADR-0025](./docs/adr/0025-extension-surface-is-a-setting.md)) asked whether `codemode` would
124
+ be _active_ by mirroring pi's loadout resolution alone ([ADR-0029](./docs/adr/0029-surface-follows-codemode-activation.md)),
125
+ so on exactly the sessions pi 1.0 is built around it answered _not active_, registered
126
+ `ptc_run_code` + `ptc_workflow` as well, and handed the model two ways to compose tools. Detection
127
+ now reads pi's MCP configuration (`<agentDir>/mcp.json`, then `<cwd>/.pi/mcp.json`) as a second
128
+ evidence source and unions the two, so those sessions resolve `subagents`: `codemode` for
129
+ orchestration, `ptc_subagent` and the task lifecycle for fan-out.
130
+ [ADR-0033](./docs/adr/0033-mcp-auto-enable-evidence.md) records the mirror, what it deliberately
131
+ does not mirror, and the one under-report path that stays open (a server registered through
132
+ `pi.registerMcpServer()`, which no file records — the drift notice below is what covers it).
133
+
134
+ ### Added
135
+
136
+ - **A session start warning for an active `codemode` the probes did not predict.** Once per
137
+ session, and only on a detected surface: if nothing in `settings.json` or a readable `mcp.json`
138
+ said codemode would be active, the surface defaulted to `full`, and pi's real tool loadout
139
+ contains `codemode` anyway, the session is now told it is carrying two orchestration surfaces and
140
+ which setting picks one. Checked at session start and again on the first turn, because
141
+ pi's MCP extension may activate codemode after this package's own `session_start` runs. It is
142
+ `TUI`-only, like every other notice in this package.
143
+ - **A named report for an `mcp.json` this package could not read.** A file that is absent is
144
+ silent, matching pi; a file that exists and cannot be parsed or read is named at session start
145
+ with its path, and when both files are broken both are named in the one line.
146
+
147
+ ### Changed
148
+
149
+ - **`CONTEXT.md` gains a `codemode activation` entry** defining the question surface detection asks
150
+ and its two evidence classes, so "is codemode active" no longer has to be reconstructed from an
151
+ ADR.
152
+ - ADR-0029 carries an amendment: its under-reporting bound still holds, but the premise it rested
153
+ on — that the loadout is the only way pi activates codemode — did not.
11
154
 
12
155
  ## [1.5.1] - 2026-10-08
13
156
 
package/README.md CHANGED
@@ -225,37 +225,40 @@ the session. The mode's rationale and rejected alternatives are in [ADR-0010](./
225
225
  { "defaultMode": false }
226
226
  ```
227
227
 
228
- **Choosing the surface.** `defaultMode` decides whether the session _enters_ PTC mode; `surfaceMode`
229
- decides which model-facing tools this package registers at all ([ADR-0025](./docs/adr/0025-extension-surface-is-a-setting.md)):
228
+ **Choosing the surface.** `defaultMode` decides whether the session _enters_ PTC mode. Which
229
+ model-facing tools this package registers is **detected, not configured** — there is no setting for
230
+ it ([ADR-0034](./docs/adr/0034-surface-is-detected-not-set.md)):
230
231
 
231
- ```
232
- /ptc surface # report the current surface and where it came from
233
- /ptc surface subagents # write the key and reload so it applies now
234
- ```
235
-
236
- ```jsonc
237
- // ~/.pi/agent/ptc.json
238
- { "surfaceMode": "subagents" }
239
- ```
240
-
241
- - `off` — a stock pi session: no tool, no `/ptc` command, no briefing.
242
232
  - `subagents` — `ptc_subagent` plus the three `ptc_task_*` tools, with pi's own `codemode`
243
- doing the orchestration; warns at startup when `codemode` is not in the active tool set.
244
- - `full` — today's set: `ptc_run_code` / `ptc_workflow` plus the three `ptc_task_*` tools.
245
-
246
- The command exists because the alternative is editing JSON by hand and starting a new session: the
247
- surface is read once in the extension factory and pi has no way to unregister a tool, so a change
248
- cannot apply in place. `/ptc surface` writes the key and then performs pi's own `/reload`, which
249
- re-runs every extension factory ([ADR-0030](./docs/adr/0030-surface-switch-reloads.md)). Two things
250
- it will not do silently: a malformed `ptc.json` is reported and left exactly as it was, and an
251
- unchanged value does not reload — a reload replaces every extension instance in the session, so
252
- retyping the value you already have costs you in-flight state for nothing. Switching to
253
- `subagents` or `off` also ends a running PTC mode, which the command says before it does it.
254
-
255
- **The default is detected, and it follows three questions, not one**
233
+ doing the orchestration; warns at startup when `codemode` is not in the active tool set and
234
+ `ptc_run_code` is not declared to the model either (which on a pi below 0.99.0 it is, since
235
+ `exposure` does not exist there and the pair falls back to being model-visible).
236
+ `ptc_run_code` / `ptc_workflow` are registered here too, but at `codemode` reach: a `codemode`
237
+ script can call them, and the model is not shown them. That is what puts `pi.dispatch` and
238
+ background tasks underneath `codemode`, whose sandbox cannot spawn a process itself.
239
+ - `full` — the rest: `ptc_run_code` / `ptc_workflow` plus the three `ptc_task_*` tools. Also the
240
+ answer to every "the probe could not tell" case.
241
+
242
+ **To keep this package out of your sessions, use pi, not this package.** Run `pi config` and
243
+ disable this package's extensions there. Measured on pi 1.1.0 against this package's own
244
+ `dist/index.js`: a `packages` entry whose `extensions` is `[]` or `["!dist/index.js"]` is not
245
+ loaded at all, while `["+dist/index.js"]` and an omitted key are. pi does this **without loading
246
+ the extension**, which no value of a key this package reads could achieve — reading the key
247
+ requires having run the code that reads it.
248
+
249
+ > In the **project-level** `.pi/settings.json`, write `"extensions": ["!dist/index.js"]` rather than
250
+ > `"extensions": []`. The two settings files are resolved by two different functions in pi
251
+ > (`package-manager.js:1850`): the personal-level path reads `[]` as "load nothing", the
252
+ > project-level path reads it as an empty delta, which means "no change".
253
+
254
+ **If you had `surfaceMode` in `ptc.json`, it is no longer read** — including `"off"`, so upgrading
255
+ brings this package back. At session start you get one `warning` naming the file and pointing here.
256
+ Delete the key to silence it.
257
+
258
+ **The surface is detected, and it follows three questions, not one**
256
259
  ([ADR-0026](./docs/adr/0026-surface-default-is-detected.md),
257
260
  [ADR-0027](./docs/adr/0027-codemode-switch-decides-surface.md),
258
- [ADR-0029](./docs/adr/0029-surface-follows-codemode-activation.md)). With no `surfaceMode` key:
261
+ [ADR-0029](./docs/adr/0029-surface-follows-codemode-activation.md)):
259
262
 
260
263
  | does this pi ship `codemode`? | will pi load it? | can the model call it? | surface |
261
264
  | ----------------------------- | ---------------------------------------------- | --------------------------------------------- | ----------- |
@@ -264,15 +267,15 @@ retyping the value you already have costs you in-flight state for nothing. Switc
264
267
  | yes | no (`-builtin:codemode`, or `--no-extensions`) | — | `full` |
265
268
  | no | — | — | `full` |
266
269
 
267
- The third column is the one that decides most sessions, and it is why the default is `full` on a pi
270
+ The third column is the one that decides most sessions, and it is why the answer is `full` on a pi
268
271
  that has never been configured. pi ships `codemode` and loads it by default, but registers it
269
272
  **inactive** (`defaultActive: false`) — it joins the model's tool list only when a loadout names it.
270
273
  Handing orchestration to a tool the model cannot call is the failure this avoids, so `subagents` is
271
274
  chosen only on positive evidence.
272
275
 
273
- Setting the key always wins. A probe that cannot answer falls back to `full` — the safe direction,
274
- since `subagents` as a failure mode would take away the orchestration tool the session was relying
275
- on.
276
+ A probe that cannot answer falls back to `full` — the safe direction, since `subagents` as a
277
+ failure mode would take away the orchestration tool the session was relying on. There is no key
278
+ that overrides this, by design ([ADR-0034](./docs/adr/0034-surface-is-detected-not-set.md)).
276
279
 
277
280
  > **To use the `subagents` surface, put `codemode` in your tool list.** Without that you get
278
281
  > `ptc_run_code` / `ptc_workflow` and no startup warning, which is the correct answer for a session
@@ -284,54 +287,45 @@ on.
284
287
  > ```
285
288
  >
286
289
  > or per launch, `pi --tools read,bash,edit,write,codemode`. A list made only of modifiers starts
287
- > from pi's four defaults, so `{ "defaultTools": ["+codemode"] }` means the same thing. If you would
288
- > rather pin the surface regardless of what pi is doing, set `{ "surfaceMode": "subagents" }` —
289
- > and note that pinning it does not activate `codemode`, so a pinned `subagents` on a session that
290
- > never configured it still warns, by design ([ADR-0025](./docs/adr/0025-extension-surface-is-a-setting.md)
291
- > decision 4).
290
+ > from pi's four defaults, so `{ "defaultTools": ["+codemode"] }` means the same thing.
292
291
 
293
292
  > Turning pi's `codemode` **off** — `"extensions": ["-builtin:codemode"]`, or launching with
294
293
  > `--no-extensions` — brings the PTC surfaces back on its own. Before ADR-0027 it did not: the
295
294
  > detection asked only whether the extension directory exists, so a pi told not to load it still
296
295
  > counted as an orchestrator and you got `ptc_subagent` with nothing to compose with. The switch is
297
296
  > read from the same three places pi reads it — the command line, `<cwd>/.pi/settings.json`, and
298
- > `<agentDir>/settings.json` — in the same order, and the activation probe reads the first two plus
299
- > `--tools`.
300
-
301
- No file, or a value outside that set, falls back to the detected default and says so at startup
302
- rather than half-applying: which tools exist is not something to change on a guess.
303
-
304
- **A detection you cannot see is the failure this design has**, so the result is reported. With no
305
- `surfaceMode` key, the outcome is issued through the TUI notification channel at session start —
306
- how the probe came out and which surface the default therefore is — but only when the probe could
307
- not answer. A pi that ships `codemode`, loads it, and has it in the tool list is the expected case
308
- and says nothing. A second notice is issued when the probe and pi's own tool registry disagree, which
309
- is the case the probe structurally cannot see: it walks the filesystem, so under
310
- `--exclude-tools codemode` it answers `present` for a tool this session does not have (and the
311
- mirror: a restructured `dist` answers `not-found` for one pi plainly registers). A third notice
312
- covers ADR-0027: a settings file that could not be read, and an explicit `surfaceMode` that
313
- disagrees with the table — the pinned value still wins, and the notice only says so. What is **not**
314
- established is that either line actually paints in a real pi TUI: a pty capture at review time
315
- showed neither the notice nor a control marker, and a TUI quits on stdin EOF before a toast
316
- renders, so that is an unmeasured end to end rather than a broken one. No test in this repository
317
- observes a notice through a real TUI. If you are relying on the notice rather than on your own
318
- `surfaceMode` key, verify it once.
297
+ > `<agentDir>/settings.json` — in the same order. The activation probe does **not** read the
298
+ > settings at all: it asks pi for its own tool loadout at session start, by which time pi has
299
+ > already applied the command line _and_ its project-trust decision. That matters for a project
300
+ > you have not approved — its `defaultTools` is not evidence of anything, because pi is not
301
+ > reading it either ([ADR-0035](./docs/adr/0035-ask-pi-for-the-loadout.md)).
302
+
303
+ **A detection you cannot see is the failure this design has**, so the result is reported. The
304
+ outcome is issued through the TUI notification channel at session start — how the probe came out
305
+ and which surface therefore is — but only when the probe could not answer. A pi that ships
306
+ `codemode`, loads it, and has it in the tool list is the expected case and says nothing. A second
307
+ notice is issued when the probe and pi's own tool registry disagree, which is the case the probe
308
+ structurally cannot see: it walks the filesystem, so under `--exclude-tools codemode` it answers
309
+ `present` for a tool this session does not have (and the mirror: a restructured `dist` answers
310
+ `not-found` for one pi plainly registers). A third notice covers ADR-0027: a settings file that
311
+ could not be read. What is **not** established is that either line actually paints in a real pi
312
+ TUI: a pty capture at review time showed neither the notice nor a control marker, and a TUI quits
313
+ on stdin EOF before a toast renders, so that is an unmeasured end to end rather than a broken one.
314
+ No test in this repository observes a notice through a real TUI.
319
315
 
320
316
  **On a `--print` session, none of it prints.** `ui.notify` is the TUI channel; measured across
321
317
  three `--print` runs that each emit one of these notices, stdout and stderr received **0 bytes**
322
318
  each. That makes this page the only channel on which a `--print` user learns why they got the
323
- surface they got — ADR-0025's decision-4 warning shares the gap, and there the answer is the same
324
- one line of JSON: set `surfaceMode` yourself and the detection no longer matters.
319
+ surface they got.
325
320
 
326
321
  **Where it does not run.** Print / JSON / RPC sessions are left exactly as launched, and so is a
327
322
  session started with an explicit tool restriction (`--tools`, `--exclude-tools`,
328
323
  `--no-builtin-tools`, `--no-extensions`) — the extension does not override what you asked for.
329
324
  `--no-extensions` does, however, change the **detected surface**: pi's own `codemode` is a built-in
330
- extension, so turning extensions off means it will not load, and ADR-0027's table resolves the
331
- default to `full` — you keep `ptc_run_code` / `ptc_workflow` rather than a `ptc_subagent` with
332
- nothing to compose with. If you would rather pin the surface regardless, set `"surfaceMode"` in
333
- `ptc.json`. If another extension changes the tool set while the mode is on, the mode yields and
334
- tells you.
325
+ extension, so turning extensions off means it will not load, and ADR-0027's table resolves to
326
+ `full` — you keep `ptc_run_code` / `ptc_workflow` rather than a `ptc_subagent` with
327
+ nothing to compose with. If another extension changes the tool set while the mode is on, the mode
328
+ yields and tells you.
335
329
 
336
330
  **The important consequence:** in a TUI session, bindings come from the loadout recorded _before_
337
331
  the mode narrowed it. That is what keeps `tools.read(…)` working — and it is why a `--tools`