pi-ptc-subagents 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +113 -0
- package/README.md +38 -4
- package/THIRD_PARTY_NOTICES.md +59 -0
- package/dist/index.d.ts +139 -4
- package/dist/index.js +52 -43
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,119 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
(nothing yet)
|
|
11
|
+
|
|
12
|
+
## [1.5.1] - 2026-10-08
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Two claims 1.5.0's changelog made about this repository were false, and are now corrected in
|
|
17
|
+
the repository itself.** 1.5.0's tarball still carries the original wording — published tarballs
|
|
18
|
+
are immutable — so the correction lives here, in `main`, and in
|
|
19
|
+
[ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md)'s amendment blocks:
|
|
20
|
+
- _"The repository is public … release authority is enforced by a `refs/tags/v*` ruleset."_ The
|
|
21
|
+
repository was still private at 1.5.0's release, and no ruleset existed. Both were done
|
|
22
|
+
2026-10-08, hours after the gap was measured: the flip, then an active ruleset (id 24698892)
|
|
23
|
+
on `refs/heads/main` — no pushes, no merges, no deletions, no force-pushes except by
|
|
24
|
+
`a1121611810` as sole bypass actor, with one approving review and green `format` / `oxlint` /
|
|
25
|
+
`test` checks required for everyone else.
|
|
26
|
+
- _"The next version published from here carries an npm provenance attestation."_ No published
|
|
27
|
+
version through 1.5.0 has one, because the provenance precondition (a public source
|
|
28
|
+
repository) only became true with the flip above. **1.5.1 is the first version whose
|
|
29
|
+
precondition held** — see `dist.attestations` on the registry.
|
|
30
|
+
|
|
31
|
+
How the false claims shipped: they were written as decisions in an earlier session
|
|
32
|
+
(`c1dda59`), recorded as facts, and every gate in this repository checks the shape of a claim —
|
|
33
|
+
that a `file:line` resolves, that a constant matches a rule — not whether the thing a sentence
|
|
34
|
+
describes exists. Five review rounds and a green release gate passed them. The same class of
|
|
35
|
+
gap is recorded in `docs/testing-constraints.md` §"What the gate does not check".
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **`docs/prototypes/` — the bgdispatch design records, recovered from research branches.** The
|
|
40
|
+
ten research branches deleted in the run-up to going public carried twelve verdict /
|
|
41
|
+
measurement / prototype files that had never been merged to `main`: the records of _why_ the
|
|
42
|
+
background-dispatch design is what it is. They were recovered before the branches were deleted;
|
|
43
|
+
`AGENTS.md` treats records as load-bearing.
|
|
44
|
+
- **`.mailmap`**, so the public commit history displays `a1121611810` instead of the 224-character
|
|
45
|
+
padded name that authored 309 commits. No history rewrite.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- **`.gitignore` gains `.zcode/` and `.scratch/`.** `.zcode/` was previously protected only by two
|
|
50
|
+
nested self-ignoring `.gitignore` files that are themselves untracked. `.scratch/` (local ticket
|
|
51
|
+
drafts, content duplicated on GitHub Issues) is removed from tracking.
|
|
52
|
+
- **Twelve research / feature branches and two `backup/undo-*` tags deleted from the remote.**
|
|
53
|
+
They carried the developer's machine-local paths (`/Users/lilianda`) that `main` had already
|
|
54
|
+
scrubbed; GitHub publishes every ref, not just `main`, so a public reader could have clicked
|
|
55
|
+
into them. Content was verified present in `main` (or recovered above) before each deletion.
|
|
56
|
+
Four routine `dependabot/*` bumps remain as open branches for the maintainer to review.
|
|
57
|
+
- **`docs/prototypes/**` excluded from oxlint.** The recovered files are measurement scripts and
|
|
58
|
+
were never part of the linted source; they are records, not production code.
|
|
59
|
+
- **The npm mirror noted during the audit turned out to be local configuration, not the
|
|
60
|
+
lockfile** — `pnpm-lock.yaml` carries no registry URLs at all.
|
|
61
|
+
|
|
62
|
+
## [1.5.0] - 2026-10-08
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- **`verify:dist` is now part of the release gate.** `scripts/verify-dist-render.mjs` is the only
|
|
67
|
+
check that exercises the _built_ artifact, and it ran nowhere: not in CI, not in the publish
|
|
68
|
+
workflow, not in `prepublishOnly`. A feature in this project's own history passed three review
|
|
69
|
+
rounds and 696 tests and then failed this script on the release artifact. It now runs on every
|
|
70
|
+
pull request, in `publish.yml` before the publish step, and in `prepublishOnly`
|
|
71
|
+
([ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md) §D).
|
|
72
|
+
- **`THIRD_PARTY_NOTICES.md`**, stating what is derived from DeepSeek Harness (MIT, Copyright (c)
|
|
73
|
+
2026 DeepSeek) and from `pi` (MIT), and shipped inside the npm tarball rather than only on
|
|
74
|
+
GitHub. The MIT notice is an obligation for the source excerpts in `docs/research/`, not a
|
|
75
|
+
courtesy.
|
|
76
|
+
- **A dispatched child returns a _child report_ instead of prose alone.** `summary` in the child's
|
|
77
|
+
own words, `findings` each carrying the independent thing that supports the claim,
|
|
78
|
+
`files_touched`, and the token usage **the host measured**. The child's prose is kept alongside
|
|
79
|
+
the report, never replaced by it ([ADR-0032](./docs/adr/0032-child-report.md)).
|
|
80
|
+
- **Two delivery channels, and the result names which one delivered it.** A declared
|
|
81
|
+
`ptc_child_report` tool (the reliable one) or a fenced JSON block in the child's final text (the
|
|
82
|
+
fallback, for installs where this package does not load in the child). `reportChannel` is
|
|
83
|
+
**always** present — `tool`, `prompt-json`, `none` or `opted-out` — because a degradation a
|
|
84
|
+
caller cannot see is a silent failure, and "ran but did not comply" must not read as "returned
|
|
85
|
+
nothing".
|
|
86
|
+
- **The report contract is on by default** and an agent opts out with one frontmatter line,
|
|
87
|
+
`childReport: false`. An opted-out agent reads as `opted-out`, not `none`: nobody was asked is a
|
|
88
|
+
different claim from having been asked and ignored.
|
|
89
|
+
- **`ptc_subagent` renders the report** where the model reads it, bounded at 20 findings /
|
|
90
|
+
20 files / 150 characters of evidence per finding, each bound stated in-band when it withholds.
|
|
91
|
+
This is the first real reader of that tool's declared `structuredContent` — on the `subagents`
|
|
92
|
+
surface there is no `codemode` to read it.
|
|
93
|
+
|
|
94
|
+
### Changed
|
|
95
|
+
|
|
96
|
+
- `src/tools/subagent.ts` gained the OCR rule anchor it never had. It was resolving to the `**`
|
|
97
|
+
catch-all and being reviewed against the generic floor only.
|
|
98
|
+
- **The repository is public, and `main` is writable only by the maintainer.** Everything else is
|
|
99
|
+
a pull request that needs CI green and one approving review. Release authority is enforced by a
|
|
100
|
+
`refs/tags/v*` ruleset plus the npm package's "Require two-factor authentication and disallow
|
|
101
|
+
tokens" setting, so it no longer depends on where a credential file is kept
|
|
102
|
+
([ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md) §A–§B).
|
|
103
|
+
~~**Measured 2026-10-08: this did not happen.** The repository is still private and no ruleset
|
|
104
|
+
exists — see §Unreleased above and ADR-0031's correction block. The npm 2FA setting is unverified
|
|
105
|
+
and is not claimed here.~~
|
|
106
|
+
- **The next version published from here carries an npm provenance attestation.** Under trusted
|
|
107
|
+
publishing npm generates it automatically once the source repository is public, with no workflow
|
|
108
|
+
change — so the `homepage` and `repository` fields that pointed at a private GitHub now resolve,
|
|
109
|
+
and the missing provenance badge that ADR-0018 §7 recorded as expected is no longer expected.
|
|
110
|
+
~~**Measured 2026-10-08: this is false and always was.** `dist.attestations` is empty for every
|
|
111
|
+
published version including 1.5.0; the repository was never public, so the precondition never
|
|
112
|
+
held and ADR-0018 §7 stands unamended.~~
|
|
113
|
+
- **`node scripts/preview-ptc-render.mjs` requires `PI_ROOT`.** It imported pi's theme from a hard-coded
|
|
114
|
+
path on one developer's machine, so following the README from anywhere else failed inside a
|
|
115
|
+
module loader. It now reads the install directory from the environment and, when it is missing or
|
|
116
|
+
wrong, says so with the commands to find it.
|
|
117
|
+
- **The DSH citations in `docs/research/` point at the public upstream repository** instead of a
|
|
118
|
+
temporary local extraction, so a reader can follow them. The baseline is tag `dsh-v0.2.0-rc.2`
|
|
119
|
+
— the release the research actually read. `src/runtime/limits.ts:4` named `0.1.6-alpha.2`; the
|
|
120
|
+
constants are byte-identical across both tags, so only the version label changed and no behaviour
|
|
121
|
+
did ([ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md) §C).
|
|
122
|
+
|
|
10
123
|
## [1.4.0] - 2026-10-03
|
|
11
124
|
|
|
12
125
|
### Added
|
package/README.md
CHANGED
|
@@ -4,13 +4,21 @@ DSH-style **PTC mode** (Programmable Tool Calling) for [pi](https://pi.dev):
|
|
|
4
4
|
the model writes a JS/TS program that calls pi's tools from inside a worker,
|
|
5
5
|
and only the program's return value plus its logs come back to the model.
|
|
6
6
|
|
|
7
|
+
**Source is open.** This repository is public and the source is here — `dist/` on npm is the
|
|
8
|
+
compiled form of what you read below. Contributions go through pull requests: see
|
|
9
|
+
[CONTRIBUTING.md](./CONTRIBUTING.md) for the gate your PR has to pass, and
|
|
10
|
+
[SECURITY.md](./SECURITY.md) before reporting anything. Releases are cut from `main` by the
|
|
11
|
+
maintainer only; if you find something you think needs a release, open an issue and say so.
|
|
12
|
+
|
|
7
13
|
## Status
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
Functional and actively used: `ptc_run_code` and `ptc_workflow` are registered and run
|
|
10
16
|
programs through the same tested worker machinery (dispatcher, wire protocol,
|
|
11
17
|
budgets, built-in bindings). The implementation is written clean-room from
|
|
12
18
|
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) PTC
|
|
13
|
-
behaviour
|
|
19
|
+
behaviour — see [ADR-0002](./docs/adr/0002-source-strategy.md) for how that boundary is
|
|
20
|
+
kept, and [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for the attribution that
|
|
21
|
+
follows from it.
|
|
14
22
|
|
|
15
23
|
## Install
|
|
16
24
|
|
|
@@ -72,6 +80,23 @@ const [read, scoutA, scoutB] = await Promise.all([
|
|
|
72
80
|
]);
|
|
73
81
|
```
|
|
74
82
|
|
|
83
|
+
**The child report.** A dispatched child returns more than prose. Under the report contract ([ADR-0032](./docs/adr/0032-child-report.md)) a child hands back a **child report** — a `summary` in its own words, `findings` each carrying the independent thing that supports the claim, the `files_touched` it is sure about, and the token usage **the host measured** (never a number the child made up). The child's prose is kept alongside the report, never replaced by it.
|
|
84
|
+
|
|
85
|
+
The report travels one of two channels. It prefers a declared `ptc_child_report` tool, whose payload the host reads back as JSON. If that tool is not available to the child, the host still reads a fenced JSON block from its final message. Either way the result **names the channel that delivered it**:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
const r = await tools["pi.dispatch"]({ agent: "scout", task: "survey the auth code" });
|
|
89
|
+
if (r.reportChannel === "none") {
|
|
90
|
+
// The child ran and did not comply. r.text is its prose; treat it as unbacked.
|
|
91
|
+
} else {
|
|
92
|
+
for (const f of r.report?.findings ?? []) console.log(f.what, "←", f.evidence);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`reportChannel` is **always present** — `"tool"`, `"prompt-json"` or `"none"` — because a degradation a caller cannot see is a silent failure, and "ran but did not comply" must not read as "returned nothing". `ptc_subagent` renders the same report into the text the model reads, bounded at 20 findings with the withheld count stated in-band.
|
|
97
|
+
|
|
98
|
+
The contract is **on by default**. An agent opts out with one line of frontmatter, `childReport: false`, and then its channel reads `"opted-out"` — nobody was asked, which is a different claim from having been asked and ignored.
|
|
99
|
+
|
|
75
100
|
**Bounded.** Three knobs keep fan-out from running away:
|
|
76
101
|
|
|
77
102
|
- `PtcConfig.dispatchConcurrency` (default **8**) — hard cap on concurrently in-flight dispatch **in one pi session**. It is one counter, not one per run: foreground `pi.dispatch`, the top-level `ptc_subagent` front, and live background children all spend it, and a background child holds its slot for its whole lifetime. The N+1th concurrent call resolves immediately with `{ status: "rejected", errorMessage: "dispatch concurrency limit reached" }` instead of queuing or spawning — so a call over the cap is not made to wait for a slot to come back.
|
|
@@ -338,7 +363,8 @@ pnpm run test:ui # vp test --ui (local browser UI; not for CI)
|
|
|
338
363
|
pnpm exec vp test --run tests/render-ptc.test.ts # renderer unit tests only
|
|
339
364
|
pnpm run build # vp pack + declaration emit
|
|
340
365
|
pnpm run verify:dist # exercise renderCall/renderResult through the built dist (no LLM needed)
|
|
341
|
-
node
|
|
366
|
+
PI_ROOT=<global-node-modules>/@earendil-works/pi-coding-agent \
|
|
367
|
+
node scripts/preview-ptc-render.mjs # print the rendered rows with real theme colors
|
|
342
368
|
```
|
|
343
369
|
|
|
344
370
|
Tooling: [oxc](https://oxc.rs) — `oxlint` + `oxfmt` (official defaults) — alongside
|
|
@@ -346,6 +372,14 @@ Tooling: [oxc](https://oxc.rs) — `oxlint` + `oxfmt` (official defaults) — al
|
|
|
346
372
|
|
|
347
373
|
See ADR-0009 for the Vitest adoption decision (reopens ADR-0008's earlier deferment).
|
|
348
374
|
|
|
375
|
+
## Credits
|
|
376
|
+
|
|
377
|
+
Built clean-room from the PTC behaviour of
|
|
378
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (MIT,
|
|
379
|
+
Copyright (c) 2026 DeepSeek), read at tag `dsh-v0.2.0-rc.2`, and hosted by
|
|
380
|
+
[pi](https://pi.dev) (`earendil-works/pi`, MIT). Full attribution, and what is
|
|
381
|
+
derived from what, is in [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
|
|
382
|
+
|
|
349
383
|
## License
|
|
350
384
|
|
|
351
|
-
Apache-2.0
|
|
385
|
+
Apache-2.0. See [LICENSE](./LICENSE) and [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
`pi-ptc-subagents` is licensed under [Apache-2.0](./LICENSE). It depends on the third-party
|
|
4
|
+
software listed below. Each project remains under its own license; nothing in this file changes
|
|
5
|
+
those terms.
|
|
6
|
+
|
|
7
|
+
## What is actually derived from what
|
|
8
|
+
|
|
9
|
+
This project implements DSH's **PTC** (Programmable Tool Calling) mode as a `pi` extension. It is
|
|
10
|
+
a clean-room implementation: see [ADR-0002](docs/adr/0002-source-strategy.md). What the code
|
|
11
|
+
_derives_ from DeepSeek Harness is the **behavioural contract** — the tool surface, the
|
|
12
|
+
`run_code` semantics, the numeric limits — and the **research notes under `docs/research/` cite
|
|
13
|
+
it, line by line, from the public source.** Those notes reproduce substantial portions of DSH's
|
|
14
|
+
source, which is why the attribution below is a licence obligation rather than a courtesy.
|
|
15
|
+
|
|
16
|
+
| Project | Licence | Role |
|
|
17
|
+
| ------------------------------------------------------------------------------------------------------------------ | ------- | -------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| [`deepseek-ai/deepseek-harness`](https://github.com/deepseek-ai/deepseek-harness) | MIT | Behavioural reference and citation target for the PTC contract. Read at tag **`dsh-v0.2.0-rc.2`**. |
|
|
19
|
+
| [`earendil-works/pi`](https://github.com/earendil-works/pi) (`@earendil-works/pi-coding-agent`, `pi-ai`, `pi-tui`) | MIT | The host this extension loads into. Peer dependency, not vendored. |
|
|
20
|
+
|
|
21
|
+
## DeepSeek Harness
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
MIT License
|
|
25
|
+
|
|
26
|
+
Copyright (c) 2026 DeepSeek
|
|
27
|
+
|
|
28
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
29
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
30
|
+
in the Software without restriction, including without limitation the rights
|
|
31
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
32
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
33
|
+
furnished to do so, subject to the following conditions:
|
|
34
|
+
|
|
35
|
+
The above copyright notice and this permission notice shall be included in all
|
|
36
|
+
copies or substantial portions of the Software.
|
|
37
|
+
|
|
38
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
39
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
40
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
41
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
42
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
43
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
44
|
+
SOFTWARE.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Citations in `docs/research/upstream-20260930/` name paths inside that repository, pinned to
|
|
48
|
+
`dsh-v0.2.0-rc.2`. Upstream also carries its own `THIRD_PARTY_NOTICES.md` for its dependency
|
|
49
|
+
closure; that closure is upstream's concern, and this package depends on the harness only through
|
|
50
|
+
reading it, not through importing it.
|
|
51
|
+
|
|
52
|
+
## Runtime dependency
|
|
53
|
+
|
|
54
|
+
| Package | Licence |
|
|
55
|
+
| -------------------------------------------------- | ------------- |
|
|
56
|
+
| [`minimatch`](https://github.com/isaacs/minimatch) | BlueOak-1.0.0 |
|
|
57
|
+
|
|
58
|
+
`minimatch` is the only runtime (`dependencies`) entry; everything else is a `devDependency` or a
|
|
59
|
+
`peerDependency` on the host.
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,27 @@ interface ParsedAgentEvent {
|
|
|
41
41
|
errorMessage?: string;
|
|
42
42
|
};
|
|
43
43
|
message_text?: string;
|
|
44
|
+
/**
|
|
45
|
+
* `tool_execution_end` carries the executed tool's name (`pi-agent-core`'s
|
|
46
|
+
* `ToolExecutionEndEvent`, alongside `toolCallId` and `isError`).
|
|
47
|
+
*/
|
|
48
|
+
toolName?: string;
|
|
49
|
+
/** The tool call this event closes; carried through because the wire carries it. */
|
|
50
|
+
toolCallId?: string;
|
|
51
|
+
/** Whether that call was treated as an error; a failed call's payload is not a report. */
|
|
52
|
+
isError?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* The executed tool's full `AgentToolResult`, verbatim. `result.structuredContent` is where a
|
|
55
|
+
* tool that DECLARES an `outputSchema` puts its machine-readable value (ADR-0032's tool
|
|
56
|
+
* channel), and this repo already depends on that reaching it for three other tools.
|
|
57
|
+
*
|
|
58
|
+
* Typed `unknown` rather than a hand-written mirror of `AgentToolResult`: what arrives is
|
|
59
|
+
* whatever the child actually wrote, and the one reader of it validates through
|
|
60
|
+
* `validateChildReport` rather than trusting the shape to have survived the wire.
|
|
61
|
+
*/
|
|
62
|
+
result?: {
|
|
63
|
+
structuredContent?: unknown;
|
|
64
|
+
};
|
|
44
65
|
}
|
|
45
66
|
/**
|
|
46
67
|
* Opaque per-handle state owned by the adapter that produced it. The dispatch code
|
|
@@ -111,6 +132,76 @@ interface ChildProcessLifecycle {
|
|
|
111
132
|
stderr(handle: ChildHandle): Promise<string>;
|
|
112
133
|
}
|
|
113
134
|
//#endregion
|
|
135
|
+
//#region src/runtime/child-report.d.ts
|
|
136
|
+
/**
|
|
137
|
+
* The child report's types (ADR-0032, `CONTEXT.md` §child report).
|
|
138
|
+
*
|
|
139
|
+
* These four declarations used to live in `dispatch.ts` next to the extraction that produces
|
|
140
|
+
* them. They moved here so the *persisted* `TaskRecord` field (`task-storage.ts`, Layer 1) can
|
|
141
|
+
* name the shape without a type-only import back up into the dispatch layer — a record that
|
|
142
|
+
* stores a report has to be able to say what a report is, and the storage layer is below the
|
|
143
|
+
* dispatcher, not above it. `dispatch.ts` re-exports all four, so every existing import site
|
|
144
|
+
* (and the report tool's) keeps working unchanged.
|
|
145
|
+
*
|
|
146
|
+
* The EXTRACTION stays in `dispatch.ts`. This module owns vocabulary, not parsing.
|
|
147
|
+
*
|
|
148
|
+
* #101 added the last two constants here, and the reason is the same one: ADR-0032's "the
|
|
149
|
+
* contract has exactly one home". The report tool's NAME and the SHAPE it demands are both
|
|
150
|
+
* needed by two modules that must not know about each other — `dispatch.ts`, which has to put
|
|
151
|
+
* the tool in the child's argv and read its `structuredContent` back, and the tool declaration
|
|
152
|
+
* in `src/tools/`, which must not import `dispatch.ts` (that edge has broken this repo's tests
|
|
153
|
+
* once; see `CHILD_REPORT_MAX_FINDINGS` below for why). Neither can reach the other's module, so
|
|
154
|
+
* both read the text from here, which imports nothing at all.
|
|
155
|
+
*/
|
|
156
|
+
/** One claim the child makes, with the evidence it rests on. */
|
|
157
|
+
interface ChildReportFinding {
|
|
158
|
+
what: string;
|
|
159
|
+
evidence: string;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Which channel a {@link ChildReport} arrived over (ADR-0032 "The channel is always stated").
|
|
163
|
+
*
|
|
164
|
+
* `tool` is the report tool's `structuredContent`, read off `tool_execution_end`; `prompt-json`
|
|
165
|
+
* is the fenced block in the child's final assistant message, which is the channel that still
|
|
166
|
+
* works when this package does not load in the child at all; `none` means the contract was on and
|
|
167
|
+
* the child did not comply.
|
|
168
|
+
*
|
|
169
|
+
* `opted-out` is the fourth value, added by ticket #102, and it is the one that keeps `none`
|
|
170
|
+
* honest. Without it, an agent that opted out and a child that ignored the contract would produce
|
|
171
|
+
* the same string, and ADR-0032 has already said which of those two is a defect — so a reader
|
|
172
|
+
* could not tell "nobody asked" from "it did not comply". A field whose value cannot distinguish
|
|
173
|
+
* those is the silent failure `docs/testing-constraints.md` #3 forbids.
|
|
174
|
+
*/
|
|
175
|
+
type ChildReportChannel = "tool" | "prompt-json" | "none" | "opted-out";
|
|
176
|
+
/**
|
|
177
|
+
* What the child DECLARES. `files_touched` is snake_case on purpose: this object is produced by a
|
|
178
|
+
* model emitting JSON, and renaming it on the way in would mean the wire text and the type
|
|
179
|
+
* disagree. The child's prose is returned alongside it, never replaced by it.
|
|
180
|
+
*/
|
|
181
|
+
interface ChildReportPayload {
|
|
182
|
+
summary: string;
|
|
183
|
+
findings: ChildReportFinding[];
|
|
184
|
+
files_touched: string[];
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The child report as the host stamps it: the payload the child declared, plus `usage`, which is
|
|
188
|
+
* what the host OBSERVED and read off the child's own `message_end` usage blocks.
|
|
189
|
+
*
|
|
190
|
+
* `usage` is deliberately not on {@link ChildReportPayload}. A model cannot know its token
|
|
191
|
+
* count, so a child-declared `usage` would be a fabricated number that happened to look like a
|
|
192
|
+
* measurement — `docs/testing-constraints.md` #4 requires the expected value to point at an
|
|
193
|
+
* independent source, and the host's counter is that source. Anything the child puts under
|
|
194
|
+
* `usage` is read and discarded.
|
|
195
|
+
*/
|
|
196
|
+
interface ChildReport extends ChildReportPayload {
|
|
197
|
+
usage: {
|
|
198
|
+
input: number;
|
|
199
|
+
output: number;
|
|
200
|
+
cost: number;
|
|
201
|
+
turns: number;
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
114
205
|
//#region src/runtime/task-storage.d.ts
|
|
115
206
|
/**
|
|
116
207
|
* The 6-state TaskRecord status (ADR-0022 §2). `queued` is deliberately absent in v1
|
|
@@ -167,6 +258,26 @@ interface TaskRecord {
|
|
|
167
258
|
ownerPid?: number;
|
|
168
259
|
/** ADR-0023: wall-clock ms when the owning runtime instance started; pairs with `ownerPid`. */
|
|
169
260
|
ownerBootMs?: number;
|
|
261
|
+
/**
|
|
262
|
+
* ADR-0032: the child report a background child produced, read off its final message by the
|
|
263
|
+
* same extraction the foreground loop runs and stamped onto the record at the terminal
|
|
264
|
+
* transition — so `ptc_task_output` can hand a background child back with the same shape a
|
|
265
|
+
* foreground `DispatchResult` carries.
|
|
266
|
+
*
|
|
267
|
+
* **Both report fields are ABSENT unless the record reached `succeeded`** (the registry writes
|
|
268
|
+
* them; see `resolve-exit` in `task-registry.ts`). Absent is a claim in its own right: a child
|
|
269
|
+
* that is still running has not reported *yet*, and a child that failed did not report at all.
|
|
270
|
+
* Neither is the same claim as "ran and complied with nothing to say", which is what a
|
|
271
|
+
* `succeeded` record carrying `reportChannel: "none"` states.
|
|
272
|
+
*/
|
|
273
|
+
report?: ChildReport;
|
|
274
|
+
/**
|
|
275
|
+
* ADR-0032 "The channel is always stated": which channel delivered `report`, and — when there
|
|
276
|
+
* is no `report` — the explicit marker that the child ignored the contract. Written with
|
|
277
|
+
* `report` and only on a `succeeded` record, so `reportChannel === undefined` never has to be
|
|
278
|
+
* read as "none".
|
|
279
|
+
*/
|
|
280
|
+
reportChannel?: ChildReportChannel;
|
|
170
281
|
}
|
|
171
282
|
/**
|
|
172
283
|
* Per-subscriber cursor for one TaskRecord (ADR-0022 §5). Cursor is per-subscriber (not per-task)
|
|
@@ -273,6 +384,8 @@ type TaskCommand = {
|
|
|
273
384
|
outputBytes?: number;
|
|
274
385
|
outputPreview?: string;
|
|
275
386
|
childError?: string;
|
|
387
|
+
report?: ChildReport;
|
|
388
|
+
reportChannel: ChildReportChannel;
|
|
276
389
|
};
|
|
277
390
|
/** Outcome of one successful command: the persisted record, emitted events, and cursor. */
|
|
278
391
|
interface TransitionResult {
|
|
@@ -857,10 +970,32 @@ export declare function buildPtcSkillsSection(skills: readonly Skill[], format?:
|
|
|
857
970
|
/**
|
|
858
971
|
* PTC run limits and spawn-time hardening, in one frozen `DEFAULT_CONFIG`.
|
|
859
972
|
*
|
|
860
|
-
* The numbers are DSH's (`dsh-v0.
|
|
861
|
-
* carried over verbatim — see ADR-0003 (output budget),
|
|
862
|
-
* ADR-0005 (execution boundary, F1–F4). Tests assert against
|
|
863
|
-
* than repeating the literals, so a future re-sync only has to change
|
|
973
|
+
* The numbers are DSH's (`dsh-v0.2.0-rc.2`, `@deepseek-ai/dsh-ptc-runtime-node`,
|
|
974
|
+
* `NodePtcRuntime.Config` defaults) carried over verbatim — see ADR-0003 (output budget),
|
|
975
|
+
* ADR-0004 (pending calls) and ADR-0005 (execution boundary, F1–F4). Tests assert against
|
|
976
|
+
* these constants rather than repeating the literals, so a future re-sync only has to change
|
|
977
|
+
* this file.
|
|
978
|
+
*
|
|
979
|
+
* The baseline was `dsh-v0.1.6-alpha.2` until 2026-10-03. That tag was never the source of
|
|
980
|
+
* these numbers — the research the values came from read a `0.2.0-rc.2` checkout, and the
|
|
981
|
+
* values are byte-identical in both tags (verified field by field: 120000 / 600000 /
|
|
982
|
+
* 67108864 / 134217728 / 128 / 3000, in `packages/ptc-runtime/ptc-runtime-node/src/index.ts`).
|
|
983
|
+
* So the correction is to the version label only; no constant changed. The prior label was
|
|
984
|
+
* wrong for a different reason worth keeping in mind: it was read off this comment rather
|
|
985
|
+
* than off the research, and `docs/research/ptc-upstream-parity-audit-20260930.md` had
|
|
986
|
+
* already recorded the mismatch (and that this file's self-description was the stale side).
|
|
987
|
+
*
|
|
988
|
+
* **These limits match a generation of the upstream that upstream has since deprecated.**
|
|
989
|
+
* `dsh-v0.0.x` through `v0.1.6-alpha.2` ran a PTC program on `worker_threads` inside the host
|
|
990
|
+
* process; DSH superseded that on 2026-09-11 and moved Node PTC into a separate process in
|
|
991
|
+
* `v0.1.7-rc.1`, which also renamed the packages into the `ptc-runtime` family with no legacy
|
|
992
|
+
* aliases. The numeric defaults did not change across that move — which is why they still match
|
|
993
|
+
* — but the *shape* around them did, and this file configures the superseded shape (ADR-0005's
|
|
994
|
+
* worker boundary, not a process boundary). So "the numbers are DSH's" is true of two versions
|
|
995
|
+
* and describes an architecture upstream no longer recommends. Upstream's own README warns that
|
|
996
|
+
* there will be compatibility-breaking changes; the parity audit's recommendations 1-3 (upgrade
|
|
997
|
+
* pi, compare against its built-in `codemode`, and re-base this project's position) are the open
|
|
998
|
+
* work, and none of them is a comment fix.
|
|
864
999
|
*
|
|
865
1000
|
* Deliberately absent:
|
|
866
1001
|
* - `syncTimeoutMs` / `maxConcurrentAgents` / `maxTotalAgents` — workflow-engine caps
|