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