pi-ptc-subagents 5.0.1 → 6.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 DELETED
@@ -1,1265 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [5.0.1] - 2026-10-20
9
-
10
- ### Changed
11
-
12
- - **pi 1.3.0 is now measured, not merely claimed.** The package declares
13
- `@earendil-works/pi-coding-agent: ">=0.86.0"`, so it has always _claimed_ to work on every pi
14
- release in that range — but 1.3.0, published on 2026-10-19, had never been measured. The
15
- differential table's measured versions stopped at 1.2.0 and the compatibility matrix's version
16
- list did not name it. pi 1.3.0 was installed on its own and the forty codemode-switch cases were
17
- driven through **its own** `DefaultPackageManager.resolve()`: it answered identically to 1.2.0 on
18
- all forty, and the static capability probe answered ✓ on all sixteen capability points. The
19
- 1.3.0 column is that measurement, not an inference from 1.2.0 being older. A user on pi 1.3.0
20
- now has a version whose behaviour this package has actually observed rather than assumed.
21
- - **The test toolchain moves to vitest 5, and the compatibility matrix can measure the version the
22
- repository currently pins.** `vitest` 4.1.11 → 5.0.5, `@vitest/coverage-v8` 4.1.11 → 5.0.5,
23
- `vite-plus` 0.3.3 → 1.3.0, `oxfmt` 0.70.0 → 0.73.0, `oxlint` 1.85.0 → 1.89.0, `@types/node`
24
- 22.20.4 → 26.6.5. The coverage provider and the test runner have to move together —
25
- `@vitest/coverage-v8@5.0.5` declares `vitest: 5.0.5` as a peer, and the mismatched pair is what
26
- produced 79 unhandled rejections (one per test file) in the upgrade's own CI. Separately,
27
- `scripts/pi-compat-matrix.mjs` decided "this devDependency entry was not found" by asking
28
- whether **replacing the version changed the text** — and replacing `1.3.0` with `1.3.0` does
29
- not change the text, so the matrix could not measure the version the repository currently pins.
30
- It asks whether the pattern matched now, and a test that _runs_ the function pins that.
31
- - **The toolchain bump reformats 26 markdown files.** oxfmt 0.73 changed list-continuation
32
- indentation and thematic-break fences. No prose changed. The files it touched carry a single
33
- unpaired `---` rather than a YAML frontmatter block, so the site's frontmatter stripping never
34
- matched them and does not match `***` either; the three files that do carry real frontmatter are
35
- untouched.
36
-
37
- ### Fixed
38
-
39
- - **Nothing in the published package's behaviour changed.** The runtime code, the tool
40
- descriptions, the argument and result shapes and the declared peer range are all as 5.0.0
41
- shipped them; this release carries development-side and documentation-side work only.
42
-
43
- ## [5.0.0] - 2026-10-19
44
-
45
- ### Added
46
-
47
- - **A dispatch reports the model its child actually ran.** The model used to be resolved inside the
48
- child and never came back: neither `pi.dispatch`'s `DispatchResult` nor a background
49
- `TaskRecord` said which provider or model served the call, so a wrong-model delegation could
50
- only be inferred from a failure after the fact. Both now carry an optional observed-model pair —
51
- observed by the host from the child's own assistant turns (last turn wins), not taken from the
52
- request — and the model-facing surfaces state the same fact as a `model: provider/id` line on
53
- `ptc_subagent` and `ptc_task_output` and as a `model=` fact in a refusal. A transcript that
54
- carries only a model id reports the id alone; the absence of both means no observed assistant turn
55
- named the pair — no turn, a supported pi that does not report it, or an old record — not "the
56
- model is unknown". A background record gains the pair at the same terminal close that writes
57
- `usage`, so a failed or canceled close that named a model still states it. The pair is named
58
- `observedProvider` / `observedModel` (ADR-0045): the result's `model` input is the requested
59
- model, and two different facts no longer share a name. Records written before the rename are
60
- still read (the field rename itself is breaking for program-visible result shapes — see the
61
- rename entry under Changed). The rendered value is stripped of ANSI escape sequences and
62
- control characters and head-capped at 200 characters before it enters model-facing text,
63
- because the pair is untrusted child wire data. See
64
- ADR-0044, ADR-0016 §"The child's model", and ADR-0022 §3 as amended.
65
- - **The `ptc_subagent` call row states the model a generalist dispatch will inherit.** When the
66
- call names no model and no agent (a generalist child, where a definition cannot pin the tier),
67
- the request row says `model=inherit` and its expanded view names the provider/id the caller's
68
- session is on; a named agent gets no such claim, and a pinned `model` is stated as before. This
69
- is a request fact captured at the call's first render, so a later model switch does not change
70
- an already-rendered row — and it is not the observed model, which still appears only after the
71
- child ran. ADR-0044.
72
-
73
- ### Changed
74
-
75
- - **BREAKING — the observed-model fields are named `observedProvider` / `observedModel`.**
76
- `DispatchResult.provider` / `model` and `TaskRecord.provider` / `model` carried the observed
77
- model, while the input's `model` is the requested model; the shared name made two different
78
- facts look comparable. The output fields are renamed, the input keeps its name, the child wire
79
- and the model-facing `model:` text are unchanged, and a record written before the rename is
80
- normalized on read so its observation is not lost. No aliases: the old result keys are removed,
81
- which is why this is breaking for program-visible result shapes. See ADR-0045.
82
- - **BREAKING — a dispatch that names no model now runs on the caller session's current model, not
83
- pi's global default.** `pi.dispatch` and `ptc_subagent` resolve the child's model first match
84
- wins: the dispatch's `model` argument, then the agent definition's frontmatter `model:`, then
85
- the calling session's **current** model, then pi's configured default. A session that switched
86
- provider or model mid-conversation now carries that switch into its next dispatched child — the
87
- background-dispatch guide previously said the opposite, and that statement is what this changes.
88
- Resolution is a snapshot at spawn: a model switched after the child starts does not change it, a
89
- nested dispatch inherits the child's own session model, and re-running the dispatch is how a
90
- caller picks up a new model. Tiers 1–2 are unchanged, so pinning the model at the call site or in
91
- the definition is the explicit opt-out; no config key and no `model: "inherit"` sentinel is
92
- added. A direct library caller with no context model, an older pi, or a session started without a
93
- model all fall through to pi's default exactly as before — the session read is structural, so
94
- the declared 0.86.0 floor gains no new pi type dependency. See ADR-0044 for the decision and its
95
- rejected alternatives.
96
-
97
- ### Fixed
98
-
99
- - **A failed foreground dispatch no longer loses the child's own failure.** The child is a pi
100
- subprocess: a rate limit or a model error can make it exit 0 after writing
101
- `stopReason: "error"` and an `errorMessage` on its assistant turn. The foreground loop
102
- discarded that text and reported the generic `dispatch produced no final text`, while the
103
- background registry already recorded the child's reason (issue #70's rule). The close decision now
104
- returns the child's reported error verbatim, using the background registry's rule — the child's
105
- own error outranks the exit-code and truncation inference — with cancellation still first:
106
- cancelled, then exit-0-with-text (fulfilled), then the child's error, then exit-0-without-text,
107
- then truncation. It logs one warning on that newly diagnosed path only. `stderr` is no longer
108
- described as guaranteed; `errorMessage` is the structured failure channel. This is
109
- foreground/background parity: one verdict and one sentence for one failure. See ADR-0044
110
- Decision 3 and ADR-0016 §Decision 3.
111
- - **`ptc_subagent refused the call: undefined` is gone.** A rejected dispatch without an
112
- `errorMessage` used to render the literal string `undefined`, telling the model nothing the
113
- dispatcher knew. The refusal now states the cause plus bounded facts the result already carries —
114
- whether a child ran or the harness declined to start, the exit code, turns, report channel, and
115
- the observed model when there is one — tail-bounded to 800 characters so a provider's actionable
116
- status code survives the cut. See ADR-0044 Decision 3.
117
-
118
- ## [4.0.0] - 2026-10-19
119
-
120
- ### Added
121
-
122
- - **The `ptc_subagent` call row shows the dispatch, not its argument dump.** The tool had no
123
- renderer, so pi fell back to its raw argument serialisation — one enormous
124
- `ptc_subagent task="…\\n\\n…"` line of JSON-escaped prose (2026-10-19 field report). It now
125
- registers a `renderCall`: collapsed it reads as a heading (agent, foreground/background and
126
- read-only flags, and a pinned `model` when one was passed) with one task preview line and
127
- `… 还有 N 行`; `ctrl+e` expands `label` / `why` rows and the task block — the first task line
128
- plus up to the existing `MAX_LOG_LINES_EXPANDED` continuation lines, so no new cap is
129
- introduced. Follows ADR-0013's "no escaped JSON in the call row" rule.
130
- - **`whyEnabled`, a default-on switch for the dispatch `why` block.** `~/.pi/agent/ptc.json` takes a
131
- boolean `whyEnabled`; when it is `false`, a `why` passed to `pi.dispatch` / `ptc_subagent` is
132
- dropped instead of delivered, and both the foreground result and the background record say that a
133
- suppression happened. **The switch fails on:** a non-boolean value, an unparseable file, or a file
134
- that is not a JSON object all leave the feature ON — a broken value must never remove it. A broken
135
- _key_ is reported once at `session_start` with the path, the key and the type that arrived; a broken
136
- _file_ is already reported by the default-mode reader, so one typo produces one notice. The
137
- `why` argument stays on the schema either way, so a running program is never rejected for passing
138
- an argument whose value the switch drops. `DispatchResult.whySuppressed` and
139
- `TaskRecord.whySuppressed` record **that a reason was withheld, never what it was** — a background
140
- dispatch returns a handle rather than a result, so without the record it would have no way at all
141
- to say so. See ADR-0043.
142
- - **`/ptc why on|off`**, a session-scoped override for the switch above. It records one session entry
143
- and never writes `ptc.json` — the difference is "about this conversation" against "about every
144
- future one". A resumed session restores the last record; a new one starts from the file. Bare
145
- `/ptc why` reports the value _and_ which of the two is in force, and an argument that is neither
146
- `on` nor `off` warns and changes nothing. See ADR-0043.
147
- - **A succeeded background task's completion notification now states its report channel.** The
148
- `<bg-task-notification>` for a `succeeded` record gains a `<report-channel>` child —
149
- `tool`, `prompt-json`, `none` or `opted-out`, the same **total** field ADR-0032 defines —
150
- rendered between `<error-message>` and `<output-ref>`. Before this, a model that wanted to
151
- know whether a finished child had honoured the report contract had to poll `ptc_task_output`;
152
- the push it already receives now answers it, once per finished child. The element is absent from
153
- every other event because only a `succeeded` close declares a channel: a running task has not
154
- reported _yet_, and a `failed` / `lost` / `canceled` close has no report claim to state
155
- (`none` and `opted-out` are claims about a finished child, not the absence of one). See
156
- ADR-0022 §7 as amended (2026-10-19).
157
-
158
- ### Changed
159
-
160
- - **BREAKING — a dispatch's `goal` is now `why`.** `pi.dispatch` and `ptc_subagent` took an
161
- optional `goal` since 3.4.0; that argument is now `why`, and the child's block is now
162
- `<pi-ptc-why>` instead of `<pi-ptc-goal>`. A PTC program that passes `goal` must pass `why`.
163
- The block's **wording is unchanged** — it still reads "This goal is context for doing it well" —
164
- because that sentence was held constant across ADR-0042's 180 runs and renaming the tag does not
165
- re-test the contrast; rewriting the sentence would change what was held still. See ADR-0043.
166
- The rename is not cosmetic: in the agent ecosystem `goal` names an objective or a loop, a thing
167
- worked toward over time, which is not what this argument holds — the reason one slice was cut out.
168
- `background` was the other candidate and was unavailable, because the dispatch schema already
169
- carries a `background` argument meaning "detach the child". No compatibility alias is kept: the
170
- only consumer is a model reading the live schema each session, and a second name for one value
171
- would cost exactly the comprehension this change buys back.
172
- - **The background-dispatch guide now says which model a dispatched child runs, and that it is
173
- not the caller's.** The child is a fresh `pi` subprocess, so its model resolves — first match
174
- wins — as the dispatch's `model` argument, then the named agent definition's frontmatter
175
- `model:`, then pi's own global default; the calling session's current model (and therefore a
176
- provider switched to mid-session) is **not** inherited. Recorded after the 2026-10-19 field
177
- report: the parent had switched provider, and the child it dispatched afterwards ran on pi's
178
- default provider and hit a 429. No behaviour change — the resolution order is what the code
179
- already did, and the guide states it so the failure is also preventable. See the background
180
- dispatch guide, `docs/usage/bgdispatch.md`.
181
- - **The TUI task panel now shows why a background task failed.** The `task-output` panel prints
182
- the record's failure reason as an `error: …` line first in the output block — ahead of the
183
- preview or the empty-output placeholder — and the `task-list` panel appends one to each
184
- `failed` / `lost` row whose record carries an `errorMessage`, so a child that died before
185
- writing output stays legible in the panel instead of as an empty preview, without a
186
- `ptc_task_output` call. A `lost` record carries the sweep's reason
187
- (`session_ended_while_running` / `lost_on_session_restart`) and gets the row too; a
188
- `canceled` task carries no reason and does not. Both rows reuse the panel's existing
189
- `MAX_ERROR_CHARS` bound (120, first non-blank line), so no new cap or number is added, and
190
- neither annotation counts against the panel's row caps — see ADR-0022 §11. A settled
191
- failure's empty-output placeholder also stops claiming `(no output yet)`: with a reason on the
192
- record it reads `(no output)`, while a page with no recorded reason keeps the `yet` (this
193
- payload carries no status to tell a live task from an unlabelled close).
194
-
195
- ### Fixed
196
-
197
- - **A background task that failed with an empty log now says why — on the notification, in
198
- `ptc_task_output`, and in the host log.** A 2026-10-19 field report: a child was rate-limited
199
- (429) before writing anything, its output log was 0 bytes, the `TaskRecord` carried the reason
200
- in `errorMessage`, and the parent agent received only `status="failed"` — it read the empty
201
- log as an infrastructure fault and could not act on the real one. Three surfaces now carry the
202
- cause. A `<bg-task-notification>` whose record carries an `errorMessage` gains an optional
203
- `<error-message>` (head-truncated to at most 1024 code points, an ellipsis marking the cut, and
204
- XML-escaped; absent when the record has no such text), so the push that announces the failure
205
- states it. `ptc_task_output`
206
- renders the reason as an `error=…` line ahead of a failed/lost task's body and exposes the
207
- record's `errorMessage` on the codemode projection as `error_message` (and on `details` as
208
- `errorMessage`), so the pull after the push reads the same reason. The background pump logs a `logger.warn` with the
209
- task id and the failure reason, so the host operator no longer sees only an empty child log. The
210
- terminal empty-output fallback also stops saying "yet": a terminal task reads
211
- `(no output; task <id> is <status>)`, while `running` / `stopping` keep
212
- `(no output yet; …)`. See ADR-0022 §7 as amended (2026-10-19).
213
-
214
- ## [3.4.0] - 2026-10-19
215
-
216
- ### Added
217
-
218
- - **`goal` on a dispatch.** `pi.dispatch` and `ptc_subagent` take an optional `goal`: the caller's
219
- reason for this dispatch slice, not the conversation and not the user's original request. The
220
- child receives it as its own `<pi-ptc-goal>` block after the host's policy block, and is told the
221
- task remains the deliverable. Measured across five model configurations on twelve conflict
222
- fixtures: 35 discordant pairs favoured the real goal, none favoured either control, and an
223
- equal-length placebo passed 2 of 60 runs. See ADR-0042.
224
- - `docs/research/wayfinder-208-negative-results.md` records two candidate features that were built,
225
- measured, and dropped (the summary arms and the provenance fields), with the method notes that
226
- make their negative results reusable.
227
-
228
- ## [3.3.3] - 2026-10-18
229
-
230
- ### Fixed
231
-
232
- - **An agent is now reachable under the name it declares, not only under its file name.** A definition
233
- written at `beta.md` whose frontmatter said `name: gamma` was advertised by the roster as `gamma`
234
- and refused by dispatch — under `gamma` the path was rebuilt to `gamma.md`, which did not exist,
235
- and under `beta` the parsed name did not match the request, so **neither** name worked. Discovery
236
- now reads the directory and matches each definition's frontmatter `name:`, which is what pi's own
237
- subagent loader and every comparable system does. It shares one scan with the roster rather than
238
- spelling the identity rule a second time, so the 3.3.2 invariant — for one scope, the names a
239
- roster lists are the names a dispatch resolves — holds by construction rather than by two
240
- traversals happening to agree. Two files in one scope declaring one name resolve to the first in
241
- sorted file-name order, and the shadowed file is reported. Scope precedence is unchanged: user
242
- scope still wins over project scope, and `agentScope` still defaults to `"user"`. **Behaviour
243
- change:** a definition whose file name disagrees with its `name:` becomes dispatchable under the
244
- declared name, and stops being reachable under the file name.
245
-
246
- - **A malformed agent definition is now reported instead of silently dropped.** A `.md` file in an
247
- agents directory that could not be _read_ already warned, but one that could be read and not
248
- _parsed_ — a definition saved with its opening `---` lost — produced no roster entry, no mention in
249
- a refusal, and no message at all, so the user saw `registered agents: (none)` for a directory that
250
- visibly held files. A file whose first non-blank line opens a frontmatter block, or is a `name:`
251
- key, now emits a `console.warn` naming its path. Prose kept beside the agents — a `README.md`, a
252
- `notes.txt` — stays silent, and the registry listing is unchanged either way.
253
-
254
- ### Changed
255
-
256
- - **The `ptc_subagent` description and the unknown-agent refusal no longer imply that a definition's
257
- file name is its identity.** Both previously pointed at `<name>.md` as the shape to write. The
258
- refusal now states that the `name:` header is the agent's name, that naming the file after it is
259
- the convention, and that the two are independent. Model-facing text only; no tool, argument or
260
- return-value shape changed.
261
-
262
- ## [3.3.2] - 2026-10-18
263
-
264
- ### Fixed
265
-
266
- - **The agent roster and agent discovery now name the same agents.** A `.md` file whose frontmatter
267
- carried no `name:` was advertised by the roster under its file name and refused by dispatch as an
268
- unknown agent — in the same message, because the refusal renders that roster. A model told an
269
- agent exists could not dispatch it, and the self-contradicting refusal made the real cause hard to
270
- read. Identity is the frontmatter `name:` and nothing else, which is what pi's own subagent loader
271
- does: it skips a definition whose frontmatter has no string `name`, and never derives identity
272
- from the file name. The roster now applies the same predicate `discoverAgent` applies, so the
273
- invariant — for one scope, the names the roster lists are the names a dispatch resolves — holds by
274
- construction rather than by two patches that happen to agree. A file that exists but cannot be
275
- read is likewise no longer listed under its file name; it still emits a `console.warn`, so that
276
- failure remains observable. **Behaviour change:** a file relying on the file-name fallback stops
277
- appearing in the roster, in the refusal's registry listing, and in the `ptc_subagent` tool
278
- description. Add a frontmatter `name:` to keep it dispatchable.
279
-
280
- ## [3.3.1] - 2026-10-16
281
-
282
- ### Changed
283
-
284
- - **The PTC tool descriptions now state how a `codemode` script reaches the parallel binding.**
285
- Measured in the 2026-10-16 dispatch-loop spike: a script that decides to fan out enumerates its
286
- own tool table, finds no `pi.dispatch` — correctly, the binding is never a codemode tool on any
287
- surface — and nothing model-facing said the two-hop path (`codemode` → `ptc_run_code` /
288
- `ptc_workflow` → `tools["pi.dispatch"]`) existed. The script's author read that silence as a
289
- missing registration and fell back to `ptc_subagent`, which worked but left the compose-from-a-
290
- script path untaught. The binding's own calling convention was already documented in both
291
- descriptions, but those sentences address a reader _inside_ a program; the new note addresses a
292
- reader inside the script. One shared constant renders into both descriptions, so the two
293
- surfaces cannot drift. Model-facing text only: no tool, argument, or behaviour changed.
294
-
295
- ## [3.3.0] - 2026-10-16
296
-
297
- ### Added
298
-
299
- - **A dispatched child's token usage is persisted on the task record, so a child that reported
300
- nothing still accounts for what it cost (issue #199).** `TaskRecord` gains a top-level optional
301
- `usage` — the host's own six-field counter (`input`, `output`, `cacheRead`, `cacheWrite`, `cost`,
302
- `turns`), accumulated from the child's assistant `message_end` blocks — written on **every**
303
- terminal close for any child the host actually started, independent of `reportChannel` and of the
304
- outcome: a `"none"` / `"opted-out"` child is accounted for, and so is one that closed `failed` or
305
- `canceled`. The same measurement now reaches both model-facing readers of that child:
306
- `ptc_task_output` prints the host's `usage:` line for a child that has no report block to put it
307
- in, the foreground `ptc_subagent` renders it as a `usage` row in the child-report block it
308
- already emitted, and both tools' `structuredContent` gains a top-level `usage` with the same four
309
- numbers the text shows. **`report.usage` is unchanged**: still the four-field view (`input`,
310
- `output`, `cost`, `turns`), still written only when the close resolves `succeeded` with a report,
311
- and still what the report block renders — the two agree on the fields they share because both are
312
- read off the same accumulator. An absent `usage` is a claim rather than a gap: a `running` task
313
- has not closed yet, a `lost` record was never observed closing, a child that never started has
314
- nothing to account for, and a record written before the field existed carries none — while a
315
- record without one renders the block it rendered before, unchanged. ADR-0022 §3 and ADR-0032
316
- record the amendment.
317
-
318
- ## [3.2.0] - 2026-10-15
319
-
320
- ### Added
321
-
322
- - **A dispatched child can be asked to be read-only, per call.** A new optional `readOnly`
323
- argument on the dispatch call takes `"strict"` or `"lenient"` — strings only. Strict replaces
324
- the child's tool list with the read set — `read`, `grep`, `find`,
325
- `ls`, plus the child report tool when the report contract is on — so the write tools are never
326
- declared to the child and it cannot call them. Lenient leaves the tool list exactly as it would
327
- have been and adds one line to the `<pi-ptc-context>` block the child already receives.
328
- The design first carried a boolean short spelling, and it was reversed before release rather
329
- than after: measured, pi's validator coerces booleans — `readOnly: 0` would have silently
330
- become "no tier", a full-capability child for a caller who asked to be read-only — and a string
331
- enum refuses every wrong-typed value at the schema instead. No released version ever shipped
332
- the boolean form.
333
- A tier set on the call beats an agent definition's declared `tools:` for strict; for lenient
334
- there is no precedence question, because the tier does not touch the tool list at all.
335
- Nothing breaks for anyone who does not pass it: an ordinary dispatch's argv and prompt are
336
- unchanged. **This is a capability narrowing, not a security boundary** — `bash` is deliberately
337
- absent from the strict set, so a strict child cannot run tests or call `git`, and no hostile
338
- child is contained by any of it. ADR-0041 records the reasoning and the alternatives.
339
-
340
- ## [3.1.0] - 2026-10-15
341
-
342
- ### Changed
343
-
344
- - **A failing PTC binding now reports a bounded diagnostic on every supported pi version.** The
345
- binding wrapper catches a builtin tool that REJECTS and routes the failure text through
346
- `bindingFailureMessage`, the same 2000-character tail-kept cap the `isError` channel already
347
- used. Measured per pi version: `ls` / `read` / `grep` / `find` reject for a bad path on
348
- 0.86.1 through 1.2.0 — only `bash` moved onto `AgentToolResult.isError` (which arrived in
349
- 0.99.0). Before this, a rejected builtin crossed the boundary with pi's raw, head-first error
350
- text, unbounded. The per-binding contract text is unchanged: a failing call still REJECTS.
351
- - **A truncated child response is now named instead of silently rejected.** pi 1.2.0 changed print
352
- mode so a response cut off at the model's output token limit exits 1 rather than 0. The
353
- foreground `DispatchResult` and the background `TaskRecord` now both carry an explicit message for
354
- that close, and neither front labels an exit-0 close as a truncation — so the two cannot
355
- disagree about the same child.
356
-
357
- ### Added
358
-
359
- - **`detectedSurfaceMode` is re-exported from the built package.** The surface decision (the pure
360
- function behind which orchestration face this package registers) was reachable only from the
361
- source module, so no artifact-level test or probe could drive it directly. Its four input
362
- combinations are now pinned against `dist`.
363
- - **`pnpm run compat:pi`** measures this package's gate against more than one pi version (floor,
364
- pinned, ceiling), reports each step separately, and prints the version-specific legs as a
365
- third state so a skipped leg is never mistaken for a passing one. See
366
- [docs/agents/pi-compat-matrix.md](docs/agents/pi-compat-matrix.md).
367
-
368
- - **The declared peer floor is now a floor you can actually compile against.** `peerDependencies`
369
- says `>=0.86.0`, but `tsc --noEmit` over pi 0.86.0 used to report 20 errors — the package ran
370
- there and failed to typecheck, which is the worst of both. It now reports 0, and
371
- `pnpm run compat:pi 0.86.0` passes every step. The package owns the three post-0.86.0 type
372
- surfaces (`ToolExposure`, and `outputSchema` / `exposure` / `defaultActive` on `ToolDefinition`)
373
- so `src/` depends on no pi newer than the floor; those fields arrived in 0.99.0, and a pi that
374
- does not read them is unaffected by their presence.
375
-
376
- Note the two floors are different things: **0.99.0 is where those capabilities appear** —
377
- `isError`, codemode, `exposure`, `structuredContent` — and this package still falls back
378
- below it. **0.86.0 is the floor it compiles and runs on.** An earlier revision of these notes
379
- recorded 0.86.x/0.87.x as type-broken; that was measured before this work and no longer holds.
380
-
381
- ### Documentation
382
-
383
- - Measured capability and feature-surface matrices across all 14 published pi versions from
384
- 0.86.0 to 1.2.0, with the corrected finding that the capability floor is **0.99.0**, not 1.0.0.
385
-
386
- ## [3.0.0] - 2026-10-14
387
-
388
- ### Changed
389
-
390
- - **BREAKING: `tools.bash(...)` is no longer bound by default; it is an explicit opt-in.** A PTC
391
- program runs with the user's own machine identity and its binding calls bypass pi's `tool_call`
392
- guards, so a default shell binding hands every model-written program a shell with your identity —
393
- the largest single thing a program can reach, granted by nothing more than installing this package.
394
- `bash` is now an _opt-in_ binding: it stays in the known binding set, and a program only gets it
395
- when the install grants it in `~/.pi/agent/ptc.json`:
396
-
397
- ```json
398
- { "optInBindings": ["bash"] }
399
- ```
400
-
401
- **Migration:** a program that calls `tools.bash(...)` and was relying on the default now fails with
402
- `no binding named "bash" in this run; available bindings: …`. Either add the key above (restart
403
- pi to pick it up), or, for a narrower change, pass an explicit `names` list to
404
- `createBuiltinBindings` — a caller-curated list is honoured verbatim and needs no second key. The
405
- error names the bindings that _are_ available, so the fix is readable from the refusal.
406
-
407
- Two properties worth knowing: the opt-in is **separate from the session's tool set** — pi having
408
- `bash` enabled does not grant it, because "enabled" and "you agreed to give a model-written program
409
- a shell" are different questions; and the file is read with a **fail-closed** direction, so an
410
- absent, unparseable or misspelled key grants nothing and reports why at session start rather than
411
- falling back to the old default. Note this narrows relative to DSH, whose PTC preset mounts
412
- `tool-bash`/`tool-pwsh`; a program written against DSH that wants its shell back needs the key.
413
-
414
- ## [2.1.0] - 2026-10-12
415
-
416
- ### Added
417
-
418
- - **The registered agents reach the model before it dispatches, not after it fails.** An agent
419
- markdown file's `description` was parsed and then dropped: the listing carried names alone, so
420
- the only way a model learned an agent existed was to guess its name, be refused, and read the
421
- refusal — a signal it could act on only after it had already spent the call. `ptc_subagent`'s
422
- description now leads with the omission, then lists every definition under the effective scope
423
- with its description and whether it was found in user or project scope, and says that
424
- `agentScope` defaults to `"user"` and will not see the others. An empty registry says so rather
425
- than rendering as nothing. The roster is rendered from the same traversal the refusal uses, so
426
- the two cannot disagree about what is registered.
427
-
428
- ### Changed
429
-
430
- - **A dispatched child is told what it is.** Every child's context block now says that it was
431
- dispatched by another session, that it cannot see or ask that session anything, and that its final
432
- message is the deliverable. Nothing in pi's own system prompt says any of it, which left a child
433
- able to ask a question nobody would answer or to narrate its process where an answer was wanted.
434
- It applies to named definitions too, so a definition does not have to restate what the dispatch
435
- already knows. For a child with no definition the prompt file holds nothing but that block — pi's
436
- own system prompt is still underneath it, which is precisely why the block has to say what the
437
- child is. See ADR-0039.
438
-
439
- - **`agent` is no longer required by `ptc_subagent` or `pi.dispatch`.** Omitting it dispatches a
440
- generalist child — pi's own system prompt, pi's full toolset, nothing narrowed — so a fresh
441
- install can delegate without first learning the agent-markdown format and writing a file. Naming
442
- one is now how you _narrow_ a child, which is all a definition could ever do: the dispatch prompt
443
- is appended to pi's own rather than substituted for it, and no tool list is passed unless the
444
- definition declares one. See ADR-0039.
445
-
446
- ### Fixed
447
-
448
- - **A dispatch that named nothing was refused as if it were a mistake.** Both fronts answered an
449
- omitted `agent` with _"agent is required (there is no default agent)"_. A name that is _given_
450
- and unusable is still refused — a typo must not silently dispatch something else — but the
451
- refusal now also says that omitting the argument is an option. The `ptc_subagent` description and
452
- the agent roster it carries lead with the omission, so a model can choose it deliberately.
453
-
454
- ### Removed
455
-
456
- - **The startup warning that said `ptc_subagent` had nothing to dispatch.** It fired whenever no
457
- agent definition was registered, which 2.0.3 had just started reporting as a problem — and it
458
- stopped being one in the same release: with `agent` optional, a dispatch that names nothing runs
459
- a generalist child, so an install with an empty registry is a **working** install. The warning was
460
- a false alarm about a working tool on exactly the fresh install it was written for. The `ptc-mode`
461
- status marker that mirrored it for the rest of the session is gone too. See ADR-0039.
462
-
463
- ## [2.0.3] - 2026-10-12
464
-
465
- ### Fixed
466
-
467
- - **`ptc_subagent` was registered, described to the model, and still uncallable — with nothing
468
- saying so.** On a `subagents` surface the tool needs at least one agent definition under
469
- `~/.pi/agent/agents/<name>.md` or `<project>/.pi/agents/<name>.md`, and there is no default agent
470
- (ADR-0016). With an empty registry the tool was live and inert: a real 920-message session made
471
- 520 tool calls and used it zero times, the model decided subagents were right, spent twenty-plus
472
- calls hunting the install for definitions that were not there, and gave up. `session_start` now
473
- reports it — naming both directories and the file shape — and the same fact is painted on the
474
- `ptc-mode` status, so it is still readable after the notice is gone.
475
- - **Agent files saved with CRLF, a UTF-8 BOM, or a blank line before the delimiter parsed as
476
- "no such agent".** The frontmatter pattern matched exactly one shape, and a `null` return reads,
477
- from every layer above, as an unregistered name. The body is normalised after the match rather
478
- than inside it, so the system prompt handed to a child carries no stray carriage returns. A file
479
- that exists but cannot be parsed is now also distinguishable from one that is absent.
480
- - **`ptc_subagent`'s description states where definitions load from**, and that there is no default
481
- agent — it points at the refusal rather than restating it, so the actionable detail keeps one
482
- home (the renderer, wired to the same resolver a dispatch uses).
483
-
484
- ### Added
485
-
486
- - **A real-process smoke test**, with a fake `pi` on `PATH` rather than a stubbed lifecycle: the
487
- spawn, the `--append-system-prompt` materialisation, the stdout parsing and the exit code are
488
- all exercised. Both directions are asserted — an empty registry reports, and a registered agent
489
- dispatches to completion — because either alone passes against a build that is broken in the
490
- other direction.
491
- - **A `能力的可用前提` review dimension**, asking when a user learns a tool's precondition is
492
- unmet, with "never" as the answer that blocks.
493
-
494
- ## [2.0.2] - 2026-10-10
495
-
496
- ### Changed
497
-
498
- - **The README leads with what the package is instead of how it works.** The opening now
499
- names the two capabilities up front — subagent fan-out and programmable tool calling —
500
- and states the relationship with pi's own `codemode` in the first screen. The surface
501
- detection deep-dive (three-question table, startup notices, `--print` / `--no-extensions`
502
- edge cases) moves out of the README to
503
- [docs/usage/surface.md](./docs/usage/surface.md), which now owns it; the README keeps
504
- the short version and a pointer. Everything else is tightened, not deleted.
505
- - **The built-in tool enumeration no longer claims a count pi moved past.** The README used
506
- to say "all seven built-ins"; pi 1.1.0 ships eight (`powershell` joined), so the copy now
507
- enumerates the tools this package binds (`read`, `bash`, `edit`, `write`, `grep`, `find`,
508
- `ls`) without claiming that is pi's full set. Docs only; no code change — note the
509
- binding list itself is unchanged, and `powershell` is currently not reachable from PTC
510
- programs even when enabled (tracked separately).
511
-
512
- [3.3.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v3.2.0...v3.3.0
513
- [3.2.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v3.1.0...v3.2.0
514
- [3.1.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v3.0.0...v3.1.0
515
- [3.0.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v2.1.0...v3.0.0
516
- [2.1.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v2.0.3...v2.1.0
517
- [2.0.3]: https://github.com/a1121611810/pi-ptc-subagents/compare/v2.0.2...v2.0.3
518
- [2.0.2]: https://github.com/a1121611810/pi-ptc-subagents/compare/v2.0.1...v2.0.2
519
-
520
- ## [2.0.1] - 2026-10-10
521
-
522
- ### Changed
523
-
524
- - **The package introduction copy now describes the package the code ships.**
525
- The `description` field — the hero line of the
526
- [pi.dev package page](https://pi.dev/packages/pi-ptc-subagents) — named two
527
- tools (`ptc_run_code` + `ptc_workflow`) while the package registers six
528
- model-facing tools plus the `pi.dispatch` binding, and said nothing about
529
- the subagent fan-out the package name advertises. It now centres on both
530
- capabilities the package is — programmable tool calling and subagent
531
- fan-out — with no tool names hardcoded (the tool set is detected, not
532
- configured; see [ADR-0034](./docs/adr/0034-surface-is-detected-not-set.md)).
533
- The README's Tools section gains the missing `ptc_subagent` bullet. Docs
534
- only; no code change.
535
-
536
- [2.0.1]: https://github.com/a1121611810/pi-ptc-subagents/compare/v2.0.0...v2.0.1
537
-
538
- ## [2.0.0] - 2026-10-10
539
-
540
- ### Added
541
-
542
- - **`ptc_run_code` and `ptc_workflow` reach the line where pi's `codemode` orchestrates.** A
543
- session on the `subagents` surface used to hold `ptc_subagent` and the lifecycle face with no
544
- program behind them: pi's `codemode` was composing tool calls, and the one capability its
545
- QuickJS sandbox cannot provide — spawning a process — was unreachable, because `pi.dispatch`,
546
- background tasks and the frozen six-name environment all live inside the PTC worker. Both tools
547
- are now registered there at `codemode` reach, which makes them **callable from a `codemode`
548
- script without being declared to the model**: `AgentSession._isDeclarable` admits `direct` and
549
- `model-only` only, so the request pi sends still carries exactly one orchestrator and it is
550
- pi's. Verified end to end on a real pi 1.1.0 session — a `codemode` script enumerates all six
551
- `ptc_*` tools in `ALL_TOOLS` and successfully calls `await tools.ptc_run_code({ code: … })`,
552
- while the payload carries only the subagent face
553
- ([ADR-0025](./docs/adr/0025-extension-surface-is-a-setting.md) §3 as amended).
554
-
555
- **A pi older than 0.99.0 is unaffected.** `ToolExposure` does not exist there — `grep -r
556
- exposure` over `pi-coding-agent@0.86.1`'s `dist/` returns nothing, and so does
557
- `AgentSession._getCallableTools` — so the field is ignored and the tools are declared to the
558
- model, which is correct: such a pi ships no `codemode`, resolves to `full` anyway, and has
559
- nothing that could call a `codemode`-reach tool. Measured across 0.86.1 / 0.87.1 / 0.99.0 /
560
- 0.99.1 / 0.99.2 / 1.0.0 / 1.0.4 / 1.1.0.
561
-
562
- **Naming either tool in `--tools` or `defaultTools` overrides this.** A tool named there is
563
- recorded in pi's `toolsAdded`, which the prompt projection filters only against `hidden` and
564
- never against `codemode` — so it is declared to the model regardless of its exposure. That is
565
- a user's explicit choice and is left alone; it is recorded here because the exposure field does
566
- not survive it.
567
-
568
- ### Changed
569
-
570
- - **Surface detection asks pi for the loadout instead of reconstructing it.** The third question —
571
- will `codemode` be **active**, i.e. callable by the model — used to be answered by parsing
572
- `--tools` / `--exclude-tools` / `--no-tools` / `--no-builtin-tools` out of `process.argv`,
573
- merging `defaultTools` across the user-scope and project-scope settings files, and replaying pi's
574
- precedence over the result. That is deleted. At `session_start` — the first moment pi's runtime is
575
- bound — `getActiveTools()` answers directly, and pi has already applied both the command line and
576
- its project-trust decision to what it returns. Registration moves to the same event, where pi
577
- adds a tool from it and refreshes its own registry; the command-line reader, the `defaultTools`
578
- merge, the precedence replay and the differential test that held them honest all go with it
579
- ([ADR-0035](./docs/adr/0035-ask-pi-for-the-loadout.md)).
580
-
581
- **What this fixes.** A project's `defaultTools` was read off disk with no trust check, while pi
582
- drops project settings for any project it has not been told to trust. An untrusted project could
583
- therefore name `codemode` and put the session on the `subagents` surface — whose
584
- `ptc_run_code` and `ptc_workflow` are registered at `codemode` reach, so with no `codemode`
585
- running they are reachable from nowhere (#131). A trusted project that configures `codemode` that
586
- way keeps the `subagents` surface, exactly as before.
587
-
588
- **What does not change.** The MCP auto-enable evidence stays a file-side read, because pi
589
- activates `codemode` from inside its own `session_start` handler and extension order is not ours
590
- to choose — measured across pi 0.86.1 → 1.1.0, a read from ours is too early to see it, and the
591
- existing cross-check catches it when it lands. The **codemode switch** axis also stays as it is;
592
- it may also be collapsible into the live registry, but that loses the distinction between "not
593
- shipped" and "not loaded", and it is a separate decision. Behaviour is otherwise unchanged on
594
- every pi this package supports, verified from 0.86.1 upward under both trust decisions.
595
-
596
- - **BREAKING: the `surfaceMode` setting is removed, and the surface is detected only.** Which
597
- model-facing tools this package registers is now a pure function of what pi is — three probes
598
- (does this pi ship `codemode`, will pi load it, can the model call it), with `full` as the
599
- fallback for every case a probe cannot answer
600
- ([ADR-0034](./docs/adr/0034-surface-is-detected-not-set.md)).
601
-
602
- **What is gone:**
603
-
604
- - the `surfaceMode` key in `~/.pi/agent/ptc.json` (`off` / `subagents` / `full`);
605
- - the `off` value — a session that wants none of this package's tools no longer has a way to say
606
- so here;
607
- - the `/ptc surface [off|subagents|full]` subcommand. **`/ptc off` is unaffected** — it turns PTC
608
- _mode_ off, which was never the same thing as the extension surface;
609
- - the `surfaceModeConflict` notice about a pinned value disagreeing with detection.
610
-
611
- **Migrating: use pi, not this package.** Run `pi config` and disable this package's extensions
612
- there — measured on pi 1.1.0 against this package's own `dist/index.js`, a `packages` entry whose
613
- `extensions` is `[]` or `["!dist/index.js"]` is not loaded at all, while `["+dist/index.js"]` and
614
- an omitted key are. This is strictly better than any value of a key this package reads, because
615
- pi honours it **without loading the extension**: none of this package runs, rather than running
616
- and then deciding to register nothing. In a project-level `.pi/settings.json` write
617
- `["!dist/index.js"]` rather than `[]` — pi resolves the two scopes with two different functions
618
- (`package-manager.js:1850`), where an empty project delta means "no change".
619
-
620
- **Expect one warning on upgrade if you had the key set.** A `ptc.json` carrying `surfaceMode` is
621
- reported at session start and the key is **not** honoured — deliberately, including for `"off"`,
622
- which is why this package comes back after being switched off. The warning names the file, quotes
623
- the value, and points at `pi config`; deleting the key silences it.
624
-
625
- - **ADR-0025 decision 3 is amended.** `subagents` does register the programming pair now. What
626
- the decision protected — the model being offered one orchestration surface, not two — is
627
- unchanged and is now enforced by exposure rather than by an absent tool. `CONTEXT.md`'s
628
- _orchestration surface_ and _subagent surface_ entries and the README's surface list say so
629
- too.
630
-
631
- - **The `codemode` activation probe now reads every CLI flag that can remove the tool.** It reads
632
- `--exclude-tools` / `-xt` and `--no-tools` / `-nt` in addition to `--tools` / `-t`, and it applies
633
- them in pi's own order: `-t` decides the list and beats `-nt`, while `-xt` vetoes whatever list
634
- won. That last one was a real misreading — pi builds the active set as
635
- `(tools ?? configured).filter(name => !excluded.has(name))` (`sdk.js:148`), so **exclusion beats
636
- inclusion**, and `-t codemode -xt codemode` was previously read as `active`. The direction was the
637
- harmful one: under `subagents`, `ptc_run_code` / `ptc_workflow` are at `codemode` reach, so an
638
- orchestrator believed to be present but absent is an orchestrator nobody can call.
639
- `--no-builtin-tools` is deliberately still not read: it maps to `noTools: "builtin"` and
640
- `codemode` is not one of pi's built-in tools, so reading it would be a guess about a flag that
641
- cannot move it.
642
-
643
- [2.0.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v1.6.0...v2.0.0
644
-
645
- ## [1.6.0] - 2026-10-08
646
-
647
- ### Fixed
648
-
649
- - **On pi 1.0, a session with MCP servers was offered two orchestration tools at once.** pi's MCP
650
- extension activates pi's own `codemode` tool by itself — MCP tools default to `codemode`
651
- exposure so that they are reachable only from scripts — and it does that with a
652
- `pi.setActiveTools` call that no settings file records. This package's surface detection
653
- ([ADR-0025](./docs/adr/0025-extension-surface-is-a-setting.md)) asked whether `codemode` would
654
- be _active_ by mirroring pi's loadout resolution alone ([ADR-0029](./docs/adr/0029-surface-follows-codemode-activation.md)),
655
- so on exactly the sessions pi 1.0 is built around it answered _not active_, registered
656
- `ptc_run_code` + `ptc_workflow` as well, and handed the model two ways to compose tools. Detection
657
- now reads pi's MCP configuration (`<agentDir>/mcp.json`, then `<cwd>/.pi/mcp.json`) as a second
658
- evidence source and unions the two, so those sessions resolve `subagents`: `codemode` for
659
- orchestration, `ptc_subagent` and the task lifecycle for fan-out.
660
- [ADR-0033](./docs/adr/0033-mcp-auto-enable-evidence.md) records the mirror, what it deliberately
661
- does not mirror, and the one under-report path that stays open (a server registered through
662
- `pi.registerMcpServer()`, which no file records — the drift notice below is what covers it).
663
-
664
- ### Added
665
-
666
- - **A session start warning for an active `codemode` the probes did not predict.** Once per
667
- session, and only on a detected surface: if nothing in `settings.json` or a readable `mcp.json`
668
- said codemode would be active, the surface defaulted to `full`, and pi's real tool loadout
669
- contains `codemode` anyway, the session is now told it is carrying two orchestration surfaces and
670
- which setting picks one. Checked at session start and again on the first turn, because
671
- pi's MCP extension may activate codemode after this package's own `session_start` runs. It is
672
- `TUI`-only, like every other notice in this package.
673
- - **A named report for an `mcp.json` this package could not read.** A file that is absent is
674
- silent, matching pi; a file that exists and cannot be parsed or read is named at session start
675
- with its path, and when both files are broken both are named in the one line.
676
-
677
- ### Changed
678
-
679
- - **`CONTEXT.md` gains a `codemode activation` entry** defining the question surface detection asks
680
- and its two evidence classes, so "is codemode active" no longer has to be reconstructed from an
681
- ADR.
682
- - ADR-0029 carries an amendment: its under-reporting bound still holds, but the premise it rested
683
- on — that the loadout is the only way pi activates codemode — did not.
684
-
685
- ## [1.5.1] - 2026-10-08
686
-
687
- ### Fixed
688
-
689
- - **Two claims 1.5.0's changelog made about this repository were false, and are now corrected in
690
- the repository itself.** 1.5.0's tarball still carries the original wording — published tarballs
691
- are immutable — so the correction lives here, in `main`, and in
692
- [ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md)'s amendment blocks:
693
- - _"The repository is public … release authority is enforced by a `refs/tags/v*` ruleset."_ The
694
- repository was still private at 1.5.0's release, and no ruleset existed. Both were done
695
- 2026-10-08, hours after the gap was measured: the flip, then an active ruleset (id 24698892)
696
- on `refs/heads/main` — no pushes, no merges, no deletions, no force-pushes except by
697
- `a1121611810` as sole bypass actor, with one approving review and green `format` / `oxlint` /
698
- `test` checks required for everyone else.
699
- - _"The next version published from here carries an npm provenance attestation."_ No published
700
- version through 1.5.0 has one, because the provenance precondition (a public source
701
- repository) only became true with the flip above. **1.5.1 is the first version whose
702
- precondition held** — see `dist.attestations` on the registry.
703
-
704
- How the false claims shipped: they were written as decisions in an earlier session
705
- (`c1dda59`), recorded as facts, and every gate in this repository checks the shape of a claim —
706
- that a `file:line` resolves, that a constant matches a rule — not whether the thing a sentence
707
- describes exists. Five review rounds and a green release gate passed them. The same class of
708
- gap is recorded in `docs/testing-constraints.md` §"What the gate does not check".
709
-
710
- ### Added
711
-
712
- - **`docs/prototypes/` — the bgdispatch design records, recovered from research branches.** The
713
- ten research branches deleted in the run-up to going public carried twelve verdict /
714
- measurement / prototype files that had never been merged to `main`: the records of _why_ the
715
- background-dispatch design is what it is. They were recovered before the branches were deleted;
716
- `AGENTS.md` treats records as load-bearing.
717
- - **`.mailmap`**, so the public commit history displays `a1121611810` instead of the 224-character
718
- padded name that authored 309 commits. No history rewrite.
719
-
720
- ### Changed
721
-
722
- - **`.gitignore` gains `.zcode/` and `.scratch/`.** `.zcode/` was previously protected only by two
723
- nested self-ignoring `.gitignore` files that are themselves untracked. `.scratch/` (local ticket
724
- drafts, content duplicated on GitHub Issues) is removed from tracking.
725
- - **Twelve research / feature branches and two `backup/undo-*` tags deleted from the remote.**
726
- They carried the developer's machine-local paths (`/Users/lilianda`) that `main` had already
727
- scrubbed; GitHub publishes every ref, not just `main`, so a public reader could have clicked
728
- into them. Content was verified present in `main` (or recovered above) before each deletion.
729
- Four routine `dependabot/*` bumps remain as open branches for the maintainer to review.
730
- - **`docs/prototypes/**` excluded from oxlint.** The recovered files are measurement scripts and
731
- were never part of the linted source; they are records, not production code.
732
- - **The npm mirror noted during the audit turned out to be local configuration, not the
733
- lockfile** — `pnpm-lock.yaml` carries no registry URLs at all.
734
-
735
- ## [1.5.0] - 2026-10-08
736
-
737
- ### Added
738
-
739
- - **`verify:dist` is now part of the release gate.** `scripts/verify-dist-render.mjs` is the only
740
- check that exercises the _built_ artifact, and it ran nowhere: not in CI, not in the publish
741
- workflow, not in `prepublishOnly`. A feature in this project's own history passed three review
742
- rounds and 696 tests and then failed this script on the release artifact. It now runs on every
743
- pull request, in `publish.yml` before the publish step, and in `prepublishOnly`
744
- ([ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md) §D).
745
- - **`THIRD_PARTY_NOTICES.md`**, stating what is derived from DeepSeek Harness (MIT, Copyright (c)
746
- 2026 DeepSeek) and from `pi` (MIT), and shipped inside the npm tarball rather than only on
747
- GitHub. The MIT notice is an obligation for the source excerpts in `docs/research/`, not a
748
- courtesy.
749
- - **A dispatched child returns a _child report_ instead of prose alone.** `summary` in the child's
750
- own words, `findings` each carrying the independent thing that supports the claim,
751
- `files_touched`, and the token usage **the host measured**. The child's prose is kept alongside
752
- the report, never replaced by it ([ADR-0032](./docs/adr/0032-child-report.md)).
753
- - **Two delivery channels, and the result names which one delivered it.** A declared
754
- `ptc_child_report` tool (the reliable one) or a fenced JSON block in the child's final text (the
755
- fallback, for installs where this package does not load in the child). `reportChannel` is
756
- **always** present — `tool`, `prompt-json`, `none` or `opted-out` — because a degradation a
757
- caller cannot see is a silent failure, and "ran but did not comply" must not read as "returned
758
- nothing".
759
- - **The report contract is on by default** and an agent opts out with one frontmatter line,
760
- `childReport: false`. An opted-out agent reads as `opted-out`, not `none`: nobody was asked is a
761
- different claim from having been asked and ignored.
762
- - **`ptc_subagent` renders the report** where the model reads it, bounded at 20 findings /
763
- 20 files / 150 characters of evidence per finding, each bound stated in-band when it withholds.
764
- This is the first real reader of that tool's declared `structuredContent` — on the `subagents`
765
- surface there is no `codemode` to read it.
766
-
767
- ### Changed
768
-
769
- - `src/tools/subagent.ts` gained the OCR rule anchor it never had. It was resolving to the `**`
770
- catch-all and being reviewed against the generic floor only.
771
- - **The repository is public, and `main` is writable only by the maintainer.** Everything else is
772
- a pull request that needs CI green and one approving review. Release authority is enforced by a
773
- `refs/tags/v*` ruleset plus the npm package's "Require two-factor authentication and disallow
774
- tokens" setting, so it no longer depends on where a credential file is kept
775
- ([ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md) §A–§B).
776
- ~~**Measured 2026-10-08: this did not happen.** The repository is still private and no ruleset
777
- exists — see §Unreleased above and ADR-0031's correction block. The npm 2FA setting is unverified
778
- and is not claimed here.~~
779
- - **The next version published from here carries an npm provenance attestation.** Under trusted
780
- publishing npm generates it automatically once the source repository is public, with no workflow
781
- change — so the `homepage` and `repository` fields that pointed at a private GitHub now resolve,
782
- and the missing provenance badge that ADR-0018 §7 recorded as expected is no longer expected.
783
- ~~**Measured 2026-10-08: this is false and always was.** `dist.attestations` is empty for every
784
- published version including 1.5.0; the repository was never public, so the precondition never
785
- held and ADR-0018 §7 stands unamended.~~
786
- - **`node scripts/preview-ptc-render.mjs` requires `PI_ROOT`.** It imported pi's theme from a hard-coded
787
- path on one developer's machine, so following the README from anywhere else failed inside a
788
- module loader. It now reads the install directory from the environment and, when it is missing or
789
- wrong, says so with the commands to find it.
790
- - **The DSH citations in `docs/research/` point at the public upstream repository** instead of a
791
- temporary local extraction, so a reader can follow them. The baseline is tag `dsh-v0.2.0-rc.2`
792
- — the release the research actually read. `src/runtime/limits.ts:4` named `0.1.6-alpha.2`; the
793
- constants are byte-identical across both tags, so only the version label changed and no behaviour
794
- did ([ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md) §C).
795
-
796
- ## [1.4.0] - 2026-10-03
797
-
798
- ### Added
799
-
800
- - **`/ptc surface off|subagents|full` switches the extension surface without a new session.** The
801
- surface was configurable only by hand-editing `~/.pi/agent/ptc.json` and restarting, because it is
802
- read once in the extension factory and pi has no way to unregister a tool. The new subcommand
803
- writes the key and then performs pi's own `/reload`, which clears pi's extension cache and
804
- re-runs every factory — the same path the built-in takes, from inside the extension. A malformed
805
- `ptc.json` is reported and left byte-for-byte alone, `defaultMode` in the same file survives, and
806
- setting the value that is already there writes nothing and does not reload
807
- ([ADR-0030](./docs/adr/0030-surface-switch-reloads.md)).
808
-
809
- ### Fixed
810
-
811
- - **A default pi session no longer starts with no orchestration tool at all.** The detected surface
812
- followed two questions — does this pi ship a `codemode` directory, and will pi load it — and on a
813
- stock pi 1.0.0 install the answer to both is yes, so the surface resolved to `subagents`. But pi
814
- registers `codemode` with `defaultActive: false`: it joins the model's tool list only when a
815
- loadout names it, and a session that configured nothing has no such loadout. Since the
816
- `subagents` surface deliberately does not register `ptc_run_code`, the result was a session
817
- holding `ptc_subagent` and three `ptc_task_*` tools with no way to compose any of them, plus a
818
- startup warning on **every** session. The detection now asks a third question — whether `codemode`
819
- will actually be in the tool list, read from `--tools` and from `defaultTools` in the project and
820
- user settings the way pi resolves them — and `subagents` is chosen only on positive evidence that
821
- the model can call the tool. The default on an unconfigured install is now `full`, with no
822
- warning. To opt into `subagents`, add `codemode` to `defaultTools` or `--tools`
823
- ([ADR-0029](./docs/adr/0029-surface-follows-codemode-activation.md)).
824
- - **The "pi does not register codemode" startup notice no longer hard-codes the wrong surface.** With
825
- a fifth cell in the table, the detected surface is not always `subagents` in that branch, and the
826
- message named `SurfaceModeConfig.detected` — a field populated only when an explicit `surfaceMode`
827
- overrode detection, so on the very path the notice fires from it would have printed `undefined`.
828
- It reports the surface the session actually built.
829
-
830
- ## [1.3.0] - 2026-10-02
831
-
832
- ### Fixed
833
-
834
- - **A failing `bash` binding no longer resolves as a success on pi 1.0.0.** pi 1.0.0 added a
835
- non-throwing failure channel — `AgentToolResult.isError`: "Report a failure without throwing. The
836
- model sees `content` as an error result, like a thrown error" — and `bash` moved its non-zero exit
837
- onto it, so `tools.bash({ command: "exit 3" })` inside a PTC program **resolved** where on 0.86.1
838
- it threw. The binding wrapper forwarded only `{ content, details }`, so `isError` was dropped at
839
- the boundary and the failure crossed into the program as a successful resolution whose only tell
840
- was a sentence at the end of its stdout. The binding layer now translates `isError` back into the
841
- rejection ADR-0024's contract already promises, which is what keeps a failing call from being
842
- silent.
843
- - **Disabling pi's `codemode` now brings the PTC surfaces back.** The detected surface asked one
844
- question — does this pi ship a `codemode` extension directory — and since pi 0.99.0 that is no
845
- longer the same as the one that matters. A user who put `"extensions": ["-builtin:codemode"]` in
846
- their settings, or launched with `--no-extensions`, turned the orchestrator off and this package
847
- kept handing the orchestration to it: `ptc_run_code` and `ptc_workflow` stayed unregistered and
848
- the session was left with `ptc_subagent` and nothing to compose with. Nothing errored; two tools
849
- were simply missing.
850
-
851
- A second probe now reads the **switch** — whether pi will actually load the extension — from the
852
- same three places pi reads it and in the same order (command line, `<cwd>/.pi/settings.json`,
853
- `<agentDir>/settings.json`), and the detected default follows both questions:
854
-
855
- | ships `codemode`? | will load it? | surface |
856
- | ----------------- | ------------------------------------------- | ----------- |
857
- | yes | yes (default, or `+builtin:codemode`) | `subagents` |
858
- | yes | no (`-builtin:codemode`, `--no-extensions`) | `full` |
859
- | no | — | `full` |
860
-
861
- An explicit `surfaceMode` still wins; a settings file that cannot be read as a JSON object still
862
- falls back to the next source rather than half-applying. Two notices are added at session start:
863
- one naming an unreadable settings file, one reporting that a pinned `surfaceMode` disagrees with
864
- the table. [ADR-0027](./docs/adr/0027-codemode-switch-decides-surface.md)
865
-
866
- Verified on a real 1.0.0 install for the three reachable cells, asserting on the registered tool
867
- set rather than on the resolver: `-builtin:codemode` → `ptc_run_code` + `ptc_workflow`; no switch
868
- entry → `ptc_subagent`; `+builtin:codemode` → `ptc_subagent`. The fourth cell (no `codemode` on
869
- disk at all) is not reachable on a machine with 1.0 installed and is covered by unit tests only.
870
-
871
- ### Added
872
-
873
- - **The four model-facing tools declare a structured result, so pi's `codemode` can consume them.**
874
- A script calling a tool used to get back prose: `ptc_subagent` returned
875
- `"Started background task 01J…"` with the id embedded, and the `ptc_task_*` tools returned
876
- newline-joined lines to re-parse. `ptc_subagent` is the acute case — it is the only way a
877
- `subagents`-surface session can start a subagent at all, because pi's QuickJS sandbox has no file
878
- system, no network and no `child_process`.
879
-
880
- All four now declare an `outputSchema` and return a matching `structuredContent`, which codemode
881
- scripts receive instead of the text. The shape is a lean projection rather than a mirror of
882
- `details`: `ptc_task_list` mirroring `TaskRecord[]` would push ~200 KB of per-record
883
- `outputPreview` into a sandbox whose purpose is to keep intermediate data away from the model.
884
- `content` and `details` are byte-identical to before on every path, and the model still sees
885
- exactly the same text — `structuredContent` is documented as "not sent to the model".
886
- [ADR-0028](./docs/adr/0028-structured-results-for-codemode.md); usage in
887
- [Structured results for codemode](./docs/usage/structured-results.md).
888
-
889
- ### Changed
890
-
891
- - **The codemode switch now mirrors pi's own matchers instead of approximating them.** Reading the
892
- `!` bucket by comparing a pattern's literal prefix was the wrong SHAPE of fix: the bucket is a
893
- continuum (globs, character classes, extglobs, nested negations, backslash escapes, multi-segment
894
- paths), so every review round found one more member — four costful defects in four rounds, and a
895
- 412-case sweep of the prefix version still had 24 divergences. A case list cannot be finished.
896
- `resolveCodemodeSwitch` now calls the same `minimatch` pi does, through the same
897
- `matchesAnyPattern` / `normalizeExactPattern` helpers and with pi's own `baseDir` per scope, which
898
- adds one small runtime dependency (`minimatch ^10.2.6`, the version pi itself pins) and **shortens**
899
- the code. A 192-case sweep of the mirror has zero divergences in either direction, and all eight
900
- reachable cells agree with a real pi 1.0.0 end to end.
901
- - **The dev toolchain now compiles against pi 1.0.0** rather than 0.86.1 / 0.87.0, so the type check
902
- and the whole suite run against the pi this package is actually used with. `peerDependencies`
903
- already declared `>=0.86.0`; that claim had no evidence behind it until now. Two consequences of
904
- the bump are fixed in the same change: pi narrowed a tool's fifth `execute` parameter from
905
- `ExtensionContext` to `ExtensionToolContext` (which adds `tools` and `executeTool`), and the
906
- `isError` channel above.
907
-
908
- ## [1.2.1] - 2026-09-30
909
-
910
- ### Fixed
911
-
912
- - **The `ptc_subagent` tests no longer depend on a pi agent being registered.**
913
- Five tests reached for pi's `__smoke_echo` agent, which does not resolve on every pi build,
914
- so the suite was green on one platform and red on the release runner. The fixture is now
915
- written by the test into `<cwd>/.pi/agents`, the path the agent resolver actually reads at
916
- project scope, and every dispatch asks for project scope explicitly rather than inheriting
917
- the `user` default, which points at the real `~/.pi`. Verified with an empty `HOME`: the whole
918
- suite is green with no ambient agent state at all.
919
-
920
- No product code changed in this release.
921
-
922
- ## [1.2.0] - 2026-09-30
923
-
924
- ### Added
925
-
926
- - **The surface default is detected from the pi that loaded us (ADR-0026).** With no
927
- `surfaceMode` set, a pi that ships its own `codemode` resolves to `subagents` and a pi that
928
- does not resolves to `full`. Setting the key always wins, and a probe that cannot answer
929
- falls back to `full` rather than guessing.
930
-
931
- This is the one behaviour change that is not invisible on upgrade: on pi 0.99.1 or newer, a
932
- user who has never set `surfaceMode` moves from `full` to `subagents`. `codemode` ships
933
- inactive (`defaultActive: false`), so such a session has a subagent front and no active
934
- orchestrator until codemode is added to the tool list -- the startup warning says so. Set
935
- `{ "surfaceMode": "full" }` to keep today's tools.
936
-
937
- Detection is a filesystem probe over `process.argv[1]`, because pi's own tool listing is
938
- unavailable at factory time: `getAllTools()` is a `notInitialized` stub until `bindCore`
939
- runs, and a throwing factory makes the extension fail to load rather than return an empty
940
- list.
941
-
942
- A detection the user cannot see is the one failure this design has, so the outcome is
943
- reported: at session start, through the TUI notification channel, a session with no
944
- `surfaceMode` set is told how the probe came out whenever it could not answer, and which
945
- surface the default therefore is. The expected case — a pi that ships `codemode`, detected as
946
- `subagents` — stays silent. A second notice covers the case the probe structurally cannot
947
- see: it walks the filesystem, so under `--no-extensions` or `--exclude-tools codemode` it
948
- answers `present` for a tool the session does not have. `session_start` cross-checks the
949
- probe against pi's own `getAllTools()` and reports a disagreement in either direction.
950
-
951
- Both notices are `ui.notify`, which is TUI-only — and that gap is **new here**, not inherited:
952
- measured, three `--print` runs that each emit one produced 0 bytes on stdout and 0 on
953
- stderr. The README is the only channel on which a `--print` user learns why they got the
954
- surface they got. What is established is that the outcome is issued through the documented
955
- TUI channel; no test observes either notice end to end through a real pi TUI.
956
-
957
- - **The model-facing surface is now a setting, and a subagent can be started
958
- without writing a program (ADR-0025).** A new `surfaceMode` key in
959
- `~/.pi/agent/ptc.json` decides what this extension registers: `full`
960
- (today's behaviour, and the detected default on a pi without `codemode`)
961
- registers `ptc_run_code`, `ptc_workflow` and the `ptc_task_*` trio;
962
- `subagents` registers a new top-level `ptc_subagent`
963
- plus the `ptc_task_*` trio and leaves orchestration to pi's own `codemode`;
964
- `off` registers nothing at all, so the session is stock pi. The new tool takes
965
- the same arguments as the `pi.dispatch` binding and calls the same dispatcher,
966
- so depth, concurrency and the background task lifecycle behave identically and
967
- a task spawned through it is listable with `ptc_task_list`.
968
-
969
- Motivation, measured rather than assumed: pi 0.99.1 ships its own programmable
970
- tool calling as the built-in `codemode`, which is stronger than `ptc_run_code` at
971
- isolation and tool discovery but cannot spawn a process at all. With both
972
- installed the model is taught two programming models per request
973
- (`docs/research/codemode-vs-ptc-capability-20260930.md`).
974
-
975
- - **The PTC tool descriptions now declare what a binding call resolves to
976
- (ADR-0024).** `ptc_run_code` and `ptc_workflow` carry one shared binding
977
- contract: a binding result is `{ content, details }`, `content` is an array of
978
- content blocks (the text of a text-file result is `result.content[0].text`),
979
- `details` is an object or `null`, and there is no `files` / `output` /
980
- `matches` / `entries` field to read — `bash`, `grep`, `find` and `ls` return one text block
981
- of newline-separated rows that the program splits itself. A `builtin binding`
982
- that fails rejects with `ToolCallError`; `pi.dispatch` resolves to a
983
- `DispatchResult` with `text` and `status` and no `content`.
984
-
985
- Model-facing only: the wire, the worker and every existing program are
986
- unchanged. Field report before: a pty-driven run of the real TUI with PTC mode
987
- on produced 31 program crashes across 16 runs, the largest error class being the
988
- model treating a binding result as a string or as an object with a `files`
989
- field — neither is ever true, and nothing said so
990
- (`docs/research/ptc-binding-contract-measurement-20260930.md`).
991
-
992
- Re-measured after the change, same harness and tasks: **0 of those three crash
993
- classes in 16 runs**, the correct `result.content[0].text` access in 16 of 16,
994
- and the context median for a PTC run down from 36,669 to 8,360 with median
995
- turns from 8 to 2. Exact-match rate is not claimed to have improved — it moved
996
- 10/16 to 11/16 while the control arm moved 15/16 to 13/16, which is this
997
- harness's noise floor
998
- (`docs/research/ptc-binding-contract-re-measurement-20260930.md`).
999
-
1000
- ### Changed
1001
-
1002
- - **The dispatch concurrency cap is one counter per session, not one per run.** The acquire
1003
- moved out of the dispatcher and into `dispatch()`, so a single `DispatchSlotCounter` now
1004
- serves every front: concurrent programs in the same session, the `ptc_subagent` tool, and
1005
- background children. The value is still `PtcConfig.dispatchConcurrency` (default 8) and the
1006
- refusal shape is unchanged; what changed is whose calls the cap counts.
1007
-
1008
- This reduces effective concurrency in two measurable ways, and neither is a rounding
1009
- difference — 24 concurrent foreground calls against a real fake-`pi` spawn:
1010
-
1011
- - two concurrent programs that could each have 8 in flight now share 8 (16 spawned -> 8);
1012
- - a program sharing a session with 8 live background children can now be refused **every**
1013
- foreground slot (8 foreground spawned -> 0). The refusal is a hard reject with no queue, so
1014
- an over-cap call is not parked behind a long-running child.
1015
-
1016
- A single program on its own is unchanged (8 of 24, before and after), which is why the
1017
- change passed three review rounds. ADR-0016 §2 and ADR-0022 §9 are amended; the depth cap is
1018
- not.
1019
-
1020
- The knob is now live rather than dead: the live control is
1021
- `createBackgroundTaskRuntime({ concurrency })`, which sizes the session counter from
1022
- `PtcConfig.dispatchConcurrency`. Passing `dispatchConcurrency` through
1023
- `runPtcProgram({ config })` no longer sizes the cap a pi session uses (measured: 2
1024
- configured, 8 dispatched).
1025
-
1026
- ### Fixed
1027
-
1028
- - **A background task that produced no answer is no longer reported as a
1029
- success (#70, field report).** A child that hits a rate limit or a model
1030
- error still exits 0: pi writes the reason onto the assistant `message_end`
1031
- as `stopReason: "error"`, retries, and closes clean. The background pump
1032
- read the exit code alone, so such a task was recorded `succeeded` and the
1033
- model was told it had worked — while `ptc_task_output` answered "(no output
1034
- yet; task X is succeeded)". `succeeded` now requires the child to exit 0
1035
- **and** to have produced assistant text, which is the rule
1036
- `decideCloseOutcome` already applied to the foreground path; one failure now
1037
- has one verdict and one sentence in both paths. A failing `resolve-exit`
1038
- writes `errorMessage`, and the child's own `stopReason: "error"` text is
1039
- carried onto the record so the model is told _why_, not only _that_
1040
- (ADR-0022 §2, amended). A non-zero exit is still left unlabelled and a model
1041
- stop still resolves `canceled` — neither is relabelled by this change.
1042
-
1043
- ## [1.1.1] - 2026-09-30
1044
-
1045
- ### Fixed
1046
-
1047
- - **The expanded `phases` block now says how many phases it withheld.**
1048
- When a run declared more than eight phases, the block appended a bare
1049
- ` …` to the roll-up line — it told the reader that something was hidden
1050
- but not how much. ADR-0013 §3 requires a block to report _what_ it
1051
- withheld, and the other four blocks (`code`, `log`, `out`, `warn`) all
1052
- report a count. The tail is now `…+N more phases`, inline, because the
1053
- phases block is a single roll-up line rather than a list of rows.
1054
-
1055
- ### Changed
1056
-
1057
- - **Docs**: the README's `TUI rendering` section now documents the
1058
- model-facing text block's own bounds — 100 characters for the inline form,
1059
- 200 per line (ADR-0012) — next to the on-screen tree's 4 / 6 / 120, so the
1060
- two contracts are no longer conflated and lowering one to match the other
1061
- does not read as a typo fix.
1062
-
1063
- ## [1.1.0] - 2026-09-29
1064
-
1065
- ### Fixed
1066
-
1067
- - **Background dispatch children no longer reap their parent's tasks (field
1068
- report, 2026-09-29).** Background children share the session's task storage
1069
- via `--session-dir`; a child's startup reconcile and exit sweep used to be
1070
- directory-wide, so a dispatch child — or any `pi` process started in the same
1071
- directory — flipped every running task to `lost`. Task records now carry an
1072
- owner identity (`ownerPid` / `ownerBootMs`) and reaping is owner-scoped
1073
- (ADR-0023); `lost_on_session_restart` now means the owner process died
1074
- before the task finished, or the record predates ownership (pre-upgrade
1075
- ownerless rows, which any process binding the directory reaps).
1076
- - **`pi.dispatch` refusals are actionable.** A missing `agent` argument is
1077
- reported distinctly (`agent is required (there is no default agent)`) and
1078
- both the missing- and unknown-agent results list the agents registered under
1079
- the effective scope plus the two lookup paths and the agent-file shape. The
1080
- binding now runs its arguments through pi's own `validateToolArguments`
1081
- (the same validation the built-in bindings use) and a validation failure
1082
- comes back as a `rejected` DispatchResult instead of reaching the
1083
- subprocess layer.
1084
-
1085
- ### Changed
1086
-
1087
- - **PTC program prompts name the dispatch binding precisely.** The
1088
- `ptc_run_code` / `ptc_workflow` descriptions and the `/ptc` briefing state
1089
- that `pi.dispatch` is always bound under its literal dot name (call it as
1090
- `tools["pi.dispatch"](...)`; `tools.pi.dispatch` does not exist), and each
1091
- run exposes its actual bound names on the new `ptcBindings` global so
1092
- programs never guess the binding surface.
1093
- - **Docs**: `ptc_run_code`'s `timeoutMs` description and `docs/usage/bgdispatch.md`
1094
- now state that a run timeout terminates in-flight foreground dispatches, and
1095
- the guide gains prerequisites (no default agent) and an output-compactness
1096
- convention (page via `ptc_task_output` instead of truncating).
1097
-
1098
- ## [1.0.0] - 2026-09-24
1099
-
1100
- ### Added
1101
-
1102
- - **Background dispatch: PTC programs can fan out to long-lived children.**
1103
- `pi.dispatch(...)` gains an opt-in `{ background: true }` path: instead of
1104
- awaiting the child, the binding returns a `DispatchHandle`
1105
- (`{ taskId, label, status: "running" }`) immediately, and a detached pump
1106
- drives a session-level `TaskRecord` through
1107
- `running / stopping / succeeded / failed / canceled / lost`, past the end of
1108
- the program and of the turn. The handle is a frozen spawn-time projection;
1109
- live state comes from three model-facing tools that stay on when PTC mode is
1110
- off (`/ptc off` only blocks new spawns):
1111
- - **`ptc_task_list`** — list this session's tasks (`status?`, `limit?`,
1112
- default 100, newest first);
1113
- - **`ptc_task_output`** — read a task's captured output (`taskId`,
1114
- `sinceBytes?`), tail-truncated to pi's 50 KB / 2000-line contract
1115
- (ADR-0015) with the full text written to a temp file;
1116
- - **`ptc_task_stop`** — ask a running task to stop (`taskId`, `reason?`,
1117
- default `"model stop"`), moving it `running -> stopping -> canceled`.
1118
-
1119
- Lifecycle changes arrive as user-role `<bg-task-notification>` events under a
1120
- `<bg-task-notifications>` batch, cursor-delivered per subscriber with a
1121
- 2048-byte inline-preview ceiling. Background tasks count against the existing
1122
- `dispatchConcurrency` (8) for their whole lifetime and share the
1123
- `maxDispatchDepth` (3) recursion bound. Existing foreground `pi.dispatch`
1124
- keeps its `DispatchResult` shape unchanged. ADR-0022.
1125
-
1126
- ## [0.1.3] - 2026-09-23
1127
-
1128
- ### Added
1129
-
1130
- - **PTC rows show what the program is doing while it runs.** Two partial-state
1131
- visuals on the `ptc_run_code` / `ptc_workflow` row, both absent until now:
1132
- - a **shimmer** on the call row — same text, one character at a time bright,
1133
- the highlight sweeping at 150ms — so a running row is distinguishable from a
1134
- settled one in a column (ADR-0020);
1135
- - a **sub-call tree** under it: one row per binding call the program made,
1136
- live from the moment the call is made, with its own five-state status
1137
- (`running` / `ok` / `error` / `cancelled` / `rejected`) and duration,
1138
- visible without expanding the row and capped at 32 with a `+N more` tail
1139
- (ADR-0021). A failed run shows the failure text but no sub-call tree: the
1140
- tool throws (pi's convention), and pi builds that error result with an empty
1141
- `details`, so the tracked calls are dropped with it.
1142
-
1143
- ### Changed
1144
-
1145
- - **Build pipeline: minify + source-map exclusion.** `pnpm run build`
1146
- (`vp pack`) now produces minified `dist/*.js` (rolldown's built-in oxc
1147
- minifier; no new dependency). The npm tarball excludes `dist/**/*.map`
1148
- via the `package.json#files` whitelist — sourcemaps stay on disk for
1149
- local stack traces, but no longer ship. Tarball shrinks from 168.7 kB
1150
- packed / 548.6 kB unpacked (v0.1.2 baseline) to 40.5 kB / 113.6 kB
1151
- (−76% / −79%); `dist/*.js` total shrinks from 161,303 B to 53,959 B
1152
- (−66.5%). All 39 public exports retain their original names. ADR-0019.
1153
-
1154
- ## [0.1.2] - 2026-09-23
1155
-
1156
- ### Fixed
1157
-
1158
- - **`pi.dispatch` is registered in production again.** The injection check compared the
1159
- binding-name array by reference against `DEFAULT_BINDING_NAMES`; production resolves
1160
- names through a `.filter()` that always returns a fresh array, so the comparison never
1161
- held and shipped sessions had no `pi.dispatch` at all. Injection now keys off a new
1162
- `includeDispatch` option (defaulting to "the caller passed no explicit `names`"), and
1163
- both shipped tools pass it explicitly. ADR-0016.
1164
- - **The dispatch concurrency cap honours `dispatchConcurrency` (default 8) and rejects
1165
- immediately instead of queueing.** The dispatcher used to read `maxParallelSubCalls`
1166
- (default 10) and FIFO-queue the overflow; per ADR-0016 §2 the N+1th concurrent
1167
- `pi.dispatch` now resolves at once with `{ status: "rejected", errorMessage:
1168
- "dispatch concurrency limit reached" }`. The cap applies to `pi.dispatch` only and
1169
- has its own counter; builtin binding fan-out keeps DSH's `maxParallelSubCalls` (10)
1170
- FIFO-queueing semantics (ADR-0004), so in-flight builtin calls never consume
1171
- dispatch slots.
1172
- - **The depth-limit rejection message is verbatim again.** The program receives
1173
- exactly `dispatch depth limit reached`, matching what the child's
1174
- `<pi-ptc-context>` hint promises — the diagnostic suffix is gone.
1175
- - **`maxDispatchDepth` bounds recursion again.** Every run reported depth 0 and
1176
- children never inherited it, so the depth check could never fire. `dispatch()` now
1177
- stamps `PI_PTC_DEPTH` on the child subprocess's environment, the extension
1178
- entrypoint reads it back, and `runPtcProgram()` accepts a `depth` baseline that
1179
- reaches the binding context. ADR-0016 Recursive section.
1180
-
1181
- ## [0.1.1] - 2026-09-23
1182
-
1183
- PTC runs inside one agent turn no longer pay a cold start each. Nothing changes in
1184
- what a program may call or return; the difference is latency and cancellation
1185
- behaviour.
1186
-
1187
- ### Changed
1188
-
1189
- - **Per-turn worker pool.** One warm `worker_threads` Worker per surface
1190
- (`run_code`, `workflow`) is kept for the duration of an agent turn and reused
1191
- across its PTC runs — ~0 ms against ~58 ms for a cold spawn (median of 5). The
1192
- pool is created lazily by the first PTC run of the turn, the two surfaces do not
1193
- share one, and the extension's `turn_end` hook retires it. Idle workers are
1194
- `unref()`-ed, so a warm pool never keeps `pi` alive. `runPtcProgram()` gains an
1195
- optional `pool` field; without it the original cold-start path runs unchanged.
1196
- ADR-0017.
1197
- - **The worker entry is a real `dist/worker.js`.** The `data:` URL built from
1198
- `Function.prototype.toString()` is retired, so V8's code cache and Node's module
1199
- cache survive warm reuse. TypeScript the _model_ submits at run time is still
1200
- type-stripped inside the worker — that is `compileProgram`'s path, not the
1201
- bootstrap's.
1202
-
1203
- ### Fixed
1204
-
1205
- - **Cancellation settles within a bound in every ordering.** A still-armed deadline
1206
- is the ceiling when a cancel arrives first; a worker that never answers a timeout
1207
- settles at `timeoutMs + graceMs`. A superseded run's frames can no longer settle
1208
- its successor, and an idle worker answers a cancel instead of making the host wait
1209
- out its grace window. ADR-0017 §10.
1210
- - **`serializedBytes` no longer bills containers as zero bytes.** `[null × 100k]`
1211
- was counted as nothing, so a frame could slip past `maxMessageBytes`; the helper
1212
- now matches `JSON.stringify` byte-for-byte and sums `ArrayBuffer` / typed-array
1213
- leaves.
1214
-
1215
- ## [0.1.0] - 2026-09-22
1216
-
1217
- First npm release. Ports DSH PTC mode — Programmable Tool Calling, formerly
1218
- DSH "Code Mode" — to `pi`. Behaviour tracks
1219
- [`deepseek-ai/deepseek-harness`](https://github.com/deepseek-ai/deepseek-harness).
1220
- Not a new design. Re-alignment.
1221
-
1222
- Install with `pi install npm:pi-ptc-subagents`. PTC tools register on next
1223
- `pi` startup. No postinstall hook. No setup.
1224
-
1225
- ### Added
1226
-
1227
- - **`ptc_run_code`**. Model submits a JS/TS program. Multiple tools execute
1228
- in one shot. Return value and collected logs return to context. Worker
1229
- protocol, dispatcher, and surfaces back it.
1230
- - **`ptc_workflow`**. Narration over `ptc_run_code`. Uses the in-program
1231
- `log()` helper.
1232
- - **`pi.dispatch` binding**. PTC programs fan out to a fresh per-call `pi`
1233
- subprocess. DSH parity.
1234
- - **Builtin bindings**. `read`, `bash`, `edit`, `write`, `grep`, `find`,
1235
- `ls`. Callable from a PTC program as `tools.<name>(args)`. DSH parity.
1236
- - **Hoisted images**. Successful bindings lift image blocks onto the PTC
1237
- tool result. The model sees the picture. The program never carries it
1238
- through the return value. Parity with `dsh-tools`'s `exec.deferContext`.
1239
- - **Value-tree rendering**. Container completion values render as a tree.
1240
- One row per property or array index. Bounded by depth and width caps.
1241
- Withheld amount is shown in-band. Scalar values render as one-line hints.
1242
- - **Truncation contract**. Model-facing text honors `pi`'s truncation cap.
1243
- Container values render as compact PTC rows.
1244
- - **Skills restored**. The skills section `pi` withholds is restored. PTC
1245
- programs call them as bindings. DSH parity.
1246
- - **Build verification scripts**. `verify:dist`, `json-to-tui`,
1247
- `preview-ptc-render`. Run before publishing.
1248
-
1249
- ### Changed
1250
-
1251
- - Build pipeline switched to `vite-plus` (`vp pack`). Type declarations
1252
- bundle via `rolldown-plugin-dts`.
1253
- - Tooling moved to pnpm 12.5.1. `vitest` coverage pinned. Lockfile
1254
- determinism set via `minimumReleaseAge: 0`.
1255
- - Lint and format moved to `oxlint` (type-aware) and `oxfmt`.
1256
-
1257
- ### Fixed
1258
-
1259
- - `pi.dispatch` now spawns `pi` directly. The host script used to swallow
1260
- the child's exit.
1261
- - `maxPendingCalls` stays in the worker. The caller no longer trips on
1262
- arrivals.
1263
- - Bindings mirror the session's active tools. A session that disables a
1264
- tool also disables it inside PTC programs.
1265
- - pnpm overrides dropped. No-op under pnpm 12.5.1.