@dungle-scrubs/harness-cli-normalizer 0.1.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/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/execution/channel.d.ts +33 -0
- package/dist/execution/channel.d.ts.map +1 -0
- package/dist/execution/channel.js +86 -0
- package/dist/execution/channel.js.map +1 -0
- package/dist/execution/decode.d.ts +16 -0
- package/dist/execution/decode.d.ts.map +1 -0
- package/dist/execution/decode.js +60 -0
- package/dist/execution/decode.js.map +1 -0
- package/dist/execution/deps.d.ts +53 -0
- package/dist/execution/deps.d.ts.map +1 -0
- package/dist/execution/deps.js +8 -0
- package/dist/execution/deps.js.map +1 -0
- package/dist/execution/events.d.ts +54 -0
- package/dist/execution/events.d.ts.map +1 -0
- package/dist/execution/events.js +2 -0
- package/dist/execution/events.js.map +1 -0
- package/dist/execution/index.d.ts +19 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +19 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/lines.d.ts +21 -0
- package/dist/execution/lines.d.ts.map +1 -0
- package/dist/execution/lines.js +70 -0
- package/dist/execution/lines.js.map +1 -0
- package/dist/execution/node-deps.d.ts +13 -0
- package/dist/execution/node-deps.d.ts.map +1 -0
- package/dist/execution/node-deps.js +164 -0
- package/dist/execution/node-deps.js.map +1 -0
- package/dist/execution/open-session.d.ts +31 -0
- package/dist/execution/open-session.d.ts.map +1 -0
- package/dist/execution/open-session.js +360 -0
- package/dist/execution/open-session.js.map +1 -0
- package/dist/execution/stream-turn.d.ts +43 -0
- package/dist/execution/stream-turn.d.ts.map +1 -0
- package/dist/execution/stream-turn.js +283 -0
- package/dist/execution/stream-turn.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/interpretation/argv.d.ts +35 -0
- package/dist/interpretation/argv.d.ts.map +1 -0
- package/dist/interpretation/argv.js +147 -0
- package/dist/interpretation/argv.js.map +1 -0
- package/dist/interpretation/capabilities.d.ts +19 -0
- package/dist/interpretation/capabilities.d.ts.map +1 -0
- package/dist/interpretation/capabilities.js +25 -0
- package/dist/interpretation/capabilities.js.map +1 -0
- package/dist/interpretation/content.d.ts +33 -0
- package/dist/interpretation/content.d.ts.map +1 -0
- package/dist/interpretation/content.js +169 -0
- package/dist/interpretation/content.js.map +1 -0
- package/dist/interpretation/context.d.ts +17 -0
- package/dist/interpretation/context.d.ts.map +1 -0
- package/dist/interpretation/context.js +16 -0
- package/dist/interpretation/context.js.map +1 -0
- package/dist/interpretation/dimensions.d.ts +13 -0
- package/dist/interpretation/dimensions.d.ts.map +1 -0
- package/dist/interpretation/dimensions.js +6 -0
- package/dist/interpretation/dimensions.js.map +1 -0
- package/dist/interpretation/identity.d.ts +32 -0
- package/dist/interpretation/identity.d.ts.map +1 -0
- package/dist/interpretation/identity.js +44 -0
- package/dist/interpretation/identity.js.map +1 -0
- package/dist/interpretation/index.d.ts +23 -0
- package/dist/interpretation/index.d.ts.map +1 -0
- package/dist/interpretation/index.js +23 -0
- package/dist/interpretation/index.js.map +1 -0
- package/dist/interpretation/limits.d.ts +20 -0
- package/dist/interpretation/limits.d.ts.map +1 -0
- package/dist/interpretation/limits.js +35 -0
- package/dist/interpretation/limits.js.map +1 -0
- package/dist/interpretation/parse-resume.d.ts +28 -0
- package/dist/interpretation/parse-resume.d.ts.map +1 -0
- package/dist/interpretation/parse-resume.js +91 -0
- package/dist/interpretation/parse-resume.js.map +1 -0
- package/dist/interpretation/presence.d.ts +16 -0
- package/dist/interpretation/presence.d.ts.map +1 -0
- package/dist/interpretation/presence.js +32 -0
- package/dist/interpretation/presence.js.map +1 -0
- package/dist/interpretation/resume-last.d.ts +32 -0
- package/dist/interpretation/resume-last.d.ts.map +1 -0
- package/dist/interpretation/resume-last.js +70 -0
- package/dist/interpretation/resume-last.js.map +1 -0
- package/dist/interpretation/session-id.d.ts +22 -0
- package/dist/interpretation/session-id.d.ts.map +1 -0
- package/dist/interpretation/session-id.js +29 -0
- package/dist/interpretation/session-id.js.map +1 -0
- package/dist/interpretation/session-input.d.ts +13 -0
- package/dist/interpretation/session-input.d.ts.map +1 -0
- package/dist/interpretation/session-input.js +32 -0
- package/dist/interpretation/session-input.js.map +1 -0
- package/dist/interpretation/shape.d.ts +17 -0
- package/dist/interpretation/shape.d.ts.map +1 -0
- package/dist/interpretation/shape.js +54 -0
- package/dist/interpretation/shape.js.map +1 -0
- package/dist/interpretation/store.d.ts +16 -0
- package/dist/interpretation/store.d.ts.map +1 -0
- package/dist/interpretation/store.js +27 -0
- package/dist/interpretation/store.js.map +1 -0
- package/dist/interpretation/versions.d.ts +16 -0
- package/dist/interpretation/versions.d.ts.map +1 -0
- package/dist/interpretation/versions.js +44 -0
- package/dist/interpretation/versions.js.map +1 -0
- package/dist/interpretation/vocabulary.d.ts +29 -0
- package/dist/interpretation/vocabulary.d.ts.map +1 -0
- package/dist/interpretation/vocabulary.js +55 -0
- package/dist/interpretation/vocabulary.js.map +1 -0
- package/dist/knowledge/claude-code.d.ts +8 -0
- package/dist/knowledge/claude-code.d.ts.map +1 -0
- package/dist/knowledge/claude-code.js +132 -0
- package/dist/knowledge/claude-code.js.map +1 -0
- package/dist/knowledge/codex.d.ts +9 -0
- package/dist/knowledge/codex.d.ts.map +1 -0
- package/dist/knowledge/codex.js +106 -0
- package/dist/knowledge/codex.js.map +1 -0
- package/dist/knowledge/descriptor.d.ts +211 -0
- package/dist/knowledge/descriptor.d.ts.map +1 -0
- package/dist/knowledge/descriptor.js +20 -0
- package/dist/knowledge/descriptor.js.map +1 -0
- package/dist/knowledge/index.d.ts +19 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +19 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/knowledge/matchers.d.ts +10 -0
- package/dist/knowledge/matchers.d.ts.map +1 -0
- package/dist/knowledge/matchers.js +11 -0
- package/dist/knowledge/matchers.js.map +1 -0
- package/dist/knowledge/muse.d.ts +12 -0
- package/dist/knowledge/muse.d.ts.map +1 -0
- package/dist/knowledge/muse.js +94 -0
- package/dist/knowledge/muse.js.map +1 -0
- package/dist/knowledge/overrides.d.ts +11 -0
- package/dist/knowledge/overrides.d.ts.map +1 -0
- package/dist/knowledge/overrides.js +166 -0
- package/dist/knowledge/overrides.js.map +1 -0
- package/dist/knowledge/pi.d.ts +11 -0
- package/dist/knowledge/pi.d.ts.map +1 -0
- package/dist/knowledge/pi.js +97 -0
- package/dist/knowledge/pi.js.map +1 -0
- package/package.json +69 -0
- package/src/execution/channel.ts +86 -0
- package/src/execution/decode.ts +79 -0
- package/src/execution/deps.ts +59 -0
- package/src/execution/events.ts +37 -0
- package/src/execution/index.ts +18 -0
- package/src/execution/lines.ts +71 -0
- package/src/execution/node-deps.ts +179 -0
- package/src/execution/open-session.ts +392 -0
- package/src/execution/stream-turn.ts +310 -0
- package/src/index.ts +7 -0
- package/src/interpretation/argv.ts +188 -0
- package/src/interpretation/capabilities.ts +51 -0
- package/src/interpretation/content.ts +191 -0
- package/src/interpretation/context.ts +28 -0
- package/src/interpretation/dimensions.ts +20 -0
- package/src/interpretation/identity.ts +81 -0
- package/src/interpretation/index.ts +22 -0
- package/src/interpretation/limits.ts +63 -0
- package/src/interpretation/parse-resume.ts +117 -0
- package/src/interpretation/presence.ts +49 -0
- package/src/interpretation/resume-last.ts +93 -0
- package/src/interpretation/session-id.ts +35 -0
- package/src/interpretation/session-input.ts +45 -0
- package/src/interpretation/shape.ts +55 -0
- package/src/interpretation/store.ts +43 -0
- package/src/interpretation/versions.ts +43 -0
- package/src/interpretation/vocabulary.ts +74 -0
- package/src/knowledge/claude-code.ts +132 -0
- package/src/knowledge/codex.ts +106 -0
- package/src/knowledge/descriptor.ts +217 -0
- package/src/knowledge/index.ts +23 -0
- package/src/knowledge/matchers.ts +19 -0
- package/src/knowledge/muse.ts +94 -0
- package/src/knowledge/overrides.ts +182 -0
- package/src/knowledge/pi.ts +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kevin Frilot <kevinfrilot@icloud.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# harness-cli-normalizer
|
|
2
|
+
|
|
3
|
+
One stable interface to four coding-agent CLIs that survives their updates.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/dungle-scrubs/harness-cli-normalizer/actions/workflows/ci.yml) [](https://www.npmjs.com/package/@dungle-scrubs/harness-cli-normalizer) [](LICENSE)
|
|
6
|
+
|
|
7
|
+
harness-cli-normalizer describes each of four coding-agent CLIs (Claude Code, Codex, pi, and Muse) as pure data and normalizes their headless output into a single `HarnessEvent` stream. You write one consumer against that surface instead of a bespoke spawn-and-parse path for each agent. A separate execution layer drives any of them as a child process and emits the events. Today most teams hold these agents together by hand, one parser per CLI, and redo the work whenever an agent ships a new version.
|
|
8
|
+
|
|
9
|
+
You reach for this when you are building on top of more than one coding agent: an orchestrator, an observer, a benchmark rig, or a way to switch which agent does a job. Each descriptor is pinned to the CLI version its facts were verified against, and a weekly check flags when a harness has shipped ahead of its descriptor. You learn drift is possible from a check, not from a crash.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @dungle-scrubs/harness-cli-normalizer
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
Requires Node 24 or newer. Install the public package from npm with your package manager:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add @dungle-scrubs/harness-cli-normalizer
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The repository uses pnpm and Bun for development and its dual-runtime test lane. Consumers do not
|
|
24
|
+
need either one unless their application runs on Bun.
|
|
25
|
+
|
|
26
|
+
## Use it
|
|
27
|
+
|
|
28
|
+
One-shot headless turn, the simplest path:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {
|
|
32
|
+
claudeCode,
|
|
33
|
+
nodeRunnerDeps,
|
|
34
|
+
streamTurn,
|
|
35
|
+
} from "@dungle-scrubs/harness-cli-normalizer";
|
|
36
|
+
|
|
37
|
+
for await (const event of streamTurn(
|
|
38
|
+
claudeCode,
|
|
39
|
+
{ prompt: "explain a monad in one sentence", cwd: process.cwd() },
|
|
40
|
+
nodeRunnerDeps(),
|
|
41
|
+
)) {
|
|
42
|
+
if (event.kind === "message") console.log(event.text);
|
|
43
|
+
if (event.kind === "done") console.log(`exit: ${event.cause}`);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Swap `claudeCode` for `codexCli`, `piCli`, or `museCode` and the consumer code does not change. The
|
|
48
|
+
three layers are also available as `@dungle-scrubs/harness-cli-normalizer/knowledge`,
|
|
49
|
+
`@dungle-scrubs/harness-cli-normalizer/interpretation`, and
|
|
50
|
+
`@dungle-scrubs/harness-cli-normalizer/execution` for narrower imports.
|
|
51
|
+
|
|
52
|
+
To watch the normalized stream render live against a real harness:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bun run demo claude "explain a monad in one sentence"
|
|
56
|
+
bun run demo codex "what is 2+2"
|
|
57
|
+
bun run demo pi "name three primes"
|
|
58
|
+
bun run demo muse "say hi"
|
|
59
|
+
bun run demo --chat claude # interactive session (claude only)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For a multi-turn session you drive yourself, `openSession` returns a handle whose `turns` you iterate and whose `send()` you call between turns. See `scripts/demo.ts` for the working pattern.
|
|
63
|
+
|
|
64
|
+
## Concepts
|
|
65
|
+
|
|
66
|
+
### Descriptors are data, not behavior
|
|
67
|
+
|
|
68
|
+
Each harness is a frozen record of facts: its binary, its argv shapes, its stream flags, its session and resume grammar, its capability claims, and the CLI version those facts were checked against. You read a descriptor; you do not configure or subclass it. Adding a fifth harness means writing a new data file, not editing branching logic.
|
|
69
|
+
|
|
70
|
+
### Three layers, one direction
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
knowledge (pure data) -> interpretation (pure functions) -> execution (child process)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`knowledge` and `interpretation` are 100% pure: no `node:` imports, no `process.env`, no clock, no randomness. Tests enforce this, not comments. Keep your own logic in the pure layers and reach `execution` only for actual process I/O. The execution layer never imports `child_process`; spawning, timing, and signalling arrive as injected primitives, so it runs the same on Node and Bun.
|
|
77
|
+
|
|
78
|
+
### Version-pinning and drift
|
|
79
|
+
|
|
80
|
+
Every descriptor carries a `verifiedAgainst` version, the anchor for all of its facts. A weekly CI job compares each harness's published version to its descriptor and opens a tracking issue when one has moved ahead. Trust a descriptor only at its pinned version, and bump `verifiedAgainst` only after re-running the capability checks locally.
|
|
81
|
+
|
|
82
|
+
## Reference
|
|
83
|
+
|
|
84
|
+
- Descriptors live in `src/knowledge/` (`claude-code.ts`, `codex.ts`, `pi.ts`, `muse.ts`), with shared types in `descriptor.ts`.
|
|
85
|
+
- The normalized event surface is `HarnessEvent` in `src/execution/events.ts`: `identity`, `token`, `message`, `progress`, `tool`, `context`, `limit`, `error`, `done`.
|
|
86
|
+
- Narrow or override a descriptor's facts with `parseOverrides` (`src/knowledge/overrides.ts`). An override a harness cannot satisfy throws `OverrideRefusalError` instead of producing a broken argv.
|
|
87
|
+
- `DROPPABLE_KINDS` (`token`, `progress`, `context`) marks events safe to drop when you only need the full messages.
|
|
88
|
+
|
|
89
|
+
## Contributing
|
|
90
|
+
|
|
91
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). The short version: run `pnpm check` before pushing, so lint, typecheck, vitest, and the bun test lane all pass. Keep the layer purity and chat-seam invariants intact, and do not edit files under `test/fixtures/`. Commits follow Conventional Commits.
|
|
92
|
+
|
|
93
|
+
## Status
|
|
94
|
+
|
|
95
|
+
Pre-1.0. Four harnesses are described (Claude Code, Codex, pi, Muse). Drift detection runs weekly in CI; re-verifying a descriptor's capability claims against a new CLI version is a local, manual step (`bun run smoke:seven`), not CI. Authentication and usage-limit signals are parsed from each harness's stream, but this library never holds or ships credentials; each harness authenticates under the end user's own session.
|
|
96
|
+
|
|
97
|
+
## Prior art
|
|
98
|
+
|
|
99
|
+
The four harness CLIs this normalizes: [Claude Code](https://www.npmjs.com/package/@anthropic-ai/claude-code), [Codex](https://www.npmjs.com/package/@openai/codex), [pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent), and Muse (installed from source, not on a registry).
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single-consumer async channel with producer backpressure: push from
|
|
3
|
+
* producers (await the returned promise - past the high water mark it
|
|
4
|
+
* blocks until the consumer drains below low water, so OS pipe
|
|
5
|
+
* backpressure can reach a child process), iterate from EXACTLY ONE
|
|
6
|
+
* consumer, close to end iteration. A second concurrent iterator would
|
|
7
|
+
* overwrite the consumer wake and strand the first - it throws instead.
|
|
8
|
+
* push after close is a silent no-op by contract (the closer decided the
|
|
9
|
+
* stream is over; racing producers must not crash).
|
|
10
|
+
*/
|
|
11
|
+
export declare class AsyncChannel<T> implements AsyncIterable<T> {
|
|
12
|
+
private readonly highWater;
|
|
13
|
+
private readonly lowWater;
|
|
14
|
+
private items;
|
|
15
|
+
private head;
|
|
16
|
+
private closed;
|
|
17
|
+
private wake;
|
|
18
|
+
private producerWaiters;
|
|
19
|
+
private consuming;
|
|
20
|
+
private backpressureEnabled;
|
|
21
|
+
constructor(highWater?: number, lowWater?: number);
|
|
22
|
+
private get size();
|
|
23
|
+
push(item: T): Promise<void>;
|
|
24
|
+
close(): void;
|
|
25
|
+
/** Stop blocking producers without closing delivery. This is a terminal
|
|
26
|
+
* process state: the child has exited and output reads are being disposed,
|
|
27
|
+
* so only already-buffered pipe data can still be added. */
|
|
28
|
+
releaseBackpressure(): void;
|
|
29
|
+
get isClosed(): boolean;
|
|
30
|
+
private releaseProducers;
|
|
31
|
+
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/execution/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,YAAY,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAUpD,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAV3B,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,mBAAmB,CAAQ;IAEnC,YACmB,SAAS,SAAO,EAChB,QAAQ,SAAM,EAC7B;IAEJ,OAAO,KAAK,IAAI,GAEf;IAED,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ3B;IAED,KAAK,IAAI,IAAI,CAIZ;IAED;;gEAE4D;IAC5D,mBAAmB,IAAI,IAAI,CAG1B;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,OAAO,CAAC,gBAAgB;IAMjB,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAsBhD;CACF"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single-consumer async channel with producer backpressure: push from
|
|
3
|
+
* producers (await the returned promise - past the high water mark it
|
|
4
|
+
* blocks until the consumer drains below low water, so OS pipe
|
|
5
|
+
* backpressure can reach a child process), iterate from EXACTLY ONE
|
|
6
|
+
* consumer, close to end iteration. A second concurrent iterator would
|
|
7
|
+
* overwrite the consumer wake and strand the first - it throws instead.
|
|
8
|
+
* push after close is a silent no-op by contract (the closer decided the
|
|
9
|
+
* stream is over; racing producers must not crash).
|
|
10
|
+
*/
|
|
11
|
+
export class AsyncChannel {
|
|
12
|
+
highWater;
|
|
13
|
+
lowWater;
|
|
14
|
+
items = [];
|
|
15
|
+
head = 0;
|
|
16
|
+
closed = false;
|
|
17
|
+
wake = null;
|
|
18
|
+
producerWaiters = [];
|
|
19
|
+
consuming = false;
|
|
20
|
+
backpressureEnabled = true;
|
|
21
|
+
constructor(highWater = 1024, lowWater = 256) {
|
|
22
|
+
this.highWater = highWater;
|
|
23
|
+
this.lowWater = lowWater;
|
|
24
|
+
}
|
|
25
|
+
get size() {
|
|
26
|
+
return this.items.length - this.head;
|
|
27
|
+
}
|
|
28
|
+
push(item) {
|
|
29
|
+
if (this.closed)
|
|
30
|
+
return Promise.resolve();
|
|
31
|
+
this.items.push(item);
|
|
32
|
+
this.wake?.();
|
|
33
|
+
if (this.backpressureEnabled && this.size > this.highWater) {
|
|
34
|
+
return new Promise((resolve) => this.producerWaiters.push(resolve));
|
|
35
|
+
}
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
}
|
|
38
|
+
close() {
|
|
39
|
+
this.closed = true;
|
|
40
|
+
this.wake?.();
|
|
41
|
+
this.releaseProducers();
|
|
42
|
+
}
|
|
43
|
+
/** Stop blocking producers without closing delivery. This is a terminal
|
|
44
|
+
* process state: the child has exited and output reads are being disposed,
|
|
45
|
+
* so only already-buffered pipe data can still be added. */
|
|
46
|
+
releaseBackpressure() {
|
|
47
|
+
this.backpressureEnabled = false;
|
|
48
|
+
this.releaseProducers();
|
|
49
|
+
}
|
|
50
|
+
get isClosed() {
|
|
51
|
+
return this.closed;
|
|
52
|
+
}
|
|
53
|
+
releaseProducers() {
|
|
54
|
+
const waiters = this.producerWaiters;
|
|
55
|
+
this.producerWaiters = [];
|
|
56
|
+
for (const release of waiters)
|
|
57
|
+
release();
|
|
58
|
+
}
|
|
59
|
+
async *[Symbol.asyncIterator]() {
|
|
60
|
+
if (this.consuming) {
|
|
61
|
+
throw new Error("AsyncChannel is single-consumer; a second iterator would strand the first");
|
|
62
|
+
}
|
|
63
|
+
this.consuming = true;
|
|
64
|
+
while (true) {
|
|
65
|
+
if (this.size > 0) {
|
|
66
|
+
const item = this.items[this.head++];
|
|
67
|
+
if (this.head === this.items.length) {
|
|
68
|
+
this.items = [];
|
|
69
|
+
this.head = 0;
|
|
70
|
+
}
|
|
71
|
+
if (this.size < this.lowWater)
|
|
72
|
+
this.releaseProducers();
|
|
73
|
+
if (item !== undefined)
|
|
74
|
+
yield item;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (this.closed)
|
|
78
|
+
return;
|
|
79
|
+
await new Promise((resolve) => {
|
|
80
|
+
this.wake = resolve;
|
|
81
|
+
});
|
|
82
|
+
this.wake = null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/execution/channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HarnessDescriptor } from "../knowledge/descriptor.js";
|
|
2
|
+
import type { HarnessEvent } from "./events.js";
|
|
3
|
+
export interface DecodeState {
|
|
4
|
+
lastSeenId: string | null;
|
|
5
|
+
limitSeen: boolean;
|
|
6
|
+
/** The id this turn expects (resume paths); rotation is classified
|
|
7
|
+
* against it. Null for fresh launches. */
|
|
8
|
+
requestedId: string | null;
|
|
9
|
+
}
|
|
10
|
+
export declare const freshDecodeState: (requestedId?: string | null) => DecodeState;
|
|
11
|
+
export declare const decodeLine: (h: HarnessDescriptor, line: string, state: DecodeState, model: string) => HarnessEvent[];
|
|
12
|
+
/** The parsed-record half of decodeLine, for pumps that already parsed the
|
|
13
|
+
* line once (a session pump inspects `type` before routing) - the hottest
|
|
14
|
+
* path must not JSON.parse every token delta twice. */
|
|
15
|
+
export declare const decodeParsed: (h: HarnessDescriptor, raw: unknown, state: DecodeState, model: string) => HarnessEvent[];
|
|
16
|
+
//# sourceMappingURL=decode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../src/execution/decode.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB;8CAC0C;IAC1C,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,eAAO,MAAM,gBAAgB,iBAAiB,MAAM,GAAG,IAAI,KAAU,WAInE,CAAC;AAEH,eAAO,MAAM,UAAU,MAClB,iBAAiB,QACd,MAAM,SACL,WAAW,SACX,MAAM,KACZ,YAAY,EAcd,CAAC;AAEF;;uDAEuD;AACvD,eAAO,MAAM,YAAY,MACpB,iBAAiB,OACf,OAAO,SACL,WAAW,SACX,MAAM,KACZ,YAAY,EAsBd,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stream-line decoding: one claude stream-json line in, zero or more
|
|
3
|
+
* HarnessEvents out. Stateful only through the explicit DecodeState the
|
|
4
|
+
* caller threads (identity dedupe per D-022). Unparseable lines on a
|
|
5
|
+
* structured stream are tolerated - scanned for walls, never fatal.
|
|
6
|
+
*/
|
|
7
|
+
import { capabilitiesOf } from "../interpretation/capabilities.js";
|
|
8
|
+
import { contentEventsOf } from "../interpretation/content.js";
|
|
9
|
+
import { decodeIdentity } from "../interpretation/identity.js";
|
|
10
|
+
import { detectLimitInLine } from "../interpretation/limits.js";
|
|
11
|
+
export const freshDecodeState = (requestedId = null) => ({
|
|
12
|
+
lastSeenId: null,
|
|
13
|
+
limitSeen: false,
|
|
14
|
+
requestedId,
|
|
15
|
+
});
|
|
16
|
+
export const decodeLine = (h, line, state, model) => {
|
|
17
|
+
let raw;
|
|
18
|
+
try {
|
|
19
|
+
raw = JSON.parse(line);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Not structured output: the only signal a plain line can carry is a wall.
|
|
23
|
+
const code = detectLimitInLine(h, line);
|
|
24
|
+
if (code !== null) {
|
|
25
|
+
state.limitSeen = true;
|
|
26
|
+
return [{ kind: "limit", code, message: `limit wall detected (${code})` }];
|
|
27
|
+
}
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
return decodeParsed(h, raw, state, model);
|
|
31
|
+
};
|
|
32
|
+
/** The parsed-record half of decodeLine, for pumps that already parsed the
|
|
33
|
+
* line once (a session pump inspects `type` before routing) - the hottest
|
|
34
|
+
* path must not JSON.parse every token delta twice. */
|
|
35
|
+
export const decodeParsed = (h, raw, state, model) => {
|
|
36
|
+
const events = [];
|
|
37
|
+
const decoded = decodeIdentity(h, raw, state.lastSeenId, state.requestedId);
|
|
38
|
+
if (decoded.sessionId !== null)
|
|
39
|
+
state.lastSeenId = decoded.sessionId;
|
|
40
|
+
if (decoded.identity !== null) {
|
|
41
|
+
events.push({
|
|
42
|
+
kind: "identity",
|
|
43
|
+
sessionId: decoded.identity,
|
|
44
|
+
authority: h.identity.authority,
|
|
45
|
+
capabilities: capabilitiesOf(h, model, "headless-turn"),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else if (decoded.outcome === "malformed" || decoded.outcome === "rotated") {
|
|
49
|
+
// The interpretation layer classified an identity anomaly; swallowing
|
|
50
|
+
// it would leave the runner waiting for an identity that already
|
|
51
|
+
// failed to arrive (or bind a rotated one).
|
|
52
|
+
events.push({ kind: "error", message: `identity ${decoded.outcome}` });
|
|
53
|
+
}
|
|
54
|
+
// Content (message/token/tool/error) is per-harness; identity above is
|
|
55
|
+
// descriptor-driven. contentEventsOf dispatches by harness name.
|
|
56
|
+
for (const content of contentEventsOf(h.name, raw))
|
|
57
|
+
events.push(content);
|
|
58
|
+
return events;
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=decode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode.js","sourceRoot":"","sources":["../../src/execution/decode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAYhE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,WAAW,GAAkB,IAAI,EAAe,EAAE,CAAC,CAAC;IACnF,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,KAAK;IAChB,WAAW;CACZ,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,CAAoB,EACpB,IAAY,EACZ,KAAkB,EAClB,KAAa,EACG,EAAE;IAClB,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,MAAM,IAAI,GAAG,iBAAiB,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACxC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;YACvB,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,wBAAwB,IAAI,GAAG,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAC5C,CAAC,CAAC;AAEF;;uDAEuD;AACvD,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,CAAoB,EACpB,GAAY,EACZ,KAAkB,EAClB,KAAa,EACG,EAAE;IAClB,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5E,IAAI,OAAO,CAAC,SAAS,KAAK,IAAI;QAAE,KAAK,CAAC,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IACrE,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,UAAU;YAChB,SAAS,EAAE,OAAO,CAAC,QAAQ;YAC3B,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS;YAC/B,YAAY,EAAE,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC;SACxD,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,OAAO,CAAC,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC5E,sEAAsE;QACtE,iEAAiE;QACjE,4CAA4C;QAC5C,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,uEAAuE;IACvE,iEAAiE;IACjE,KAAK,MAAM,OAAO,IAAI,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzE,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injected runtime primitives (D-005): the execution layer never imports
|
|
3
|
+
* child_process, never calls process.kill, and never reads a wall clock -
|
|
4
|
+
* spawn, clock, and signalling arrive as data so Node/Bun is a real
|
|
5
|
+
* portability boundary and tests run against fakes deterministically.
|
|
6
|
+
*/
|
|
7
|
+
export interface SpawnedProcess {
|
|
8
|
+
readonly stdout: AsyncIterable<string | Uint8Array>;
|
|
9
|
+
readonly stderr: AsyncIterable<string | Uint8Array>;
|
|
10
|
+
/** Idempotently terminate stdout and stderr reads without signalling the
|
|
11
|
+
* child. An adapter MUST preserve chunks already buffered when disposal is
|
|
12
|
+
* requested, then settle the first read beyond that finite snapshot.
|
|
13
|
+
* Terminal cleanup owns process signalling separately. */
|
|
14
|
+
disposeOutput(): void;
|
|
15
|
+
/** Resolves with the exit code, or null when the process died to a
|
|
16
|
+
* signal without one. */
|
|
17
|
+
readonly exited: Promise<number | null>;
|
|
18
|
+
/** Present when the spawner opened a writable stdin (session mode needs
|
|
19
|
+
* it; one-shot turns close or inherit stdin instead). */
|
|
20
|
+
readonly stdin?: {
|
|
21
|
+
write(data: string): void;
|
|
22
|
+
end(): void;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export interface SpawnOptions {
|
|
26
|
+
readonly cwd?: string;
|
|
27
|
+
/** `pipe` opens a writable stdin (sessions require it); `inherit` hands
|
|
28
|
+
* the child the parent's fd 0; `close` gives it nothing (pi's rule). */
|
|
29
|
+
readonly stdin: "inherit" | "close" | "pipe";
|
|
30
|
+
}
|
|
31
|
+
export type TimerHandle = number;
|
|
32
|
+
export interface Clock {
|
|
33
|
+
now(): number;
|
|
34
|
+
setTimeout(fn: () => void, ms: number): TimerHandle;
|
|
35
|
+
clearTimeout(handle: TimerHandle): void;
|
|
36
|
+
}
|
|
37
|
+
export type SignalName = "SIGTERM" | "SIGKILL";
|
|
38
|
+
/** One structured line per boundary transition - spawn, exit, stall,
|
|
39
|
+
* redacted argv. Always-on baseline evidence; the host decides where it
|
|
40
|
+
* goes. */
|
|
41
|
+
export type BoundaryLog = (event: Record<string, unknown>) => void;
|
|
42
|
+
export interface RunnerDeps {
|
|
43
|
+
readonly spawn: (argv: readonly string[], opts: SpawnOptions) => SpawnedProcess;
|
|
44
|
+
readonly clock: Clock;
|
|
45
|
+
readonly signal: (proc: SpawnedProcess, sig: SignalName) => void;
|
|
46
|
+
/** Stall watchdog budget; armed ONLY for none-granularity invocations. */
|
|
47
|
+
readonly stallMs?: number;
|
|
48
|
+
readonly log?: BoundaryLog;
|
|
49
|
+
/** Host-minted correlation id; without it the runner falls back to a
|
|
50
|
+
* per-process monotonic counter (collides across processes). */
|
|
51
|
+
readonly turnId?: string;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=deps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../src/execution/deps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IACpD,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IACpD;;;8DAG0D;IAC1D,aAAa,IAAI,IAAI,CAAC;IACtB;6BACyB;IACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC;6DACyD;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,GAAG,IAAI,IAAI,CAAC;KACb,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB;4EACwE;IACxE,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;CAC9C;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAEjC,MAAM,WAAW,KAAK;IACpB,GAAG,IAAI,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,CAAC;IACpD,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/C;;WAEW;AACX,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAEnE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,YAAY,KAAK,cAAc,CAAC;IAChF,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,UAAU,KAAK,IAAI,CAAC;IACjE,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC;IAC3B;oEACgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injected runtime primitives (D-005): the execution layer never imports
|
|
3
|
+
* child_process, never calls process.kill, and never reads a wall clock -
|
|
4
|
+
* spawn, clock, and signalling arrive as data so Node/Bun is a real
|
|
5
|
+
* portability boundary and tests run against fakes deterministically.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=deps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/execution/deps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HarnessEvent: the runner's output vocabulary (PLAN.md Part 0). The
|
|
3
|
+
* normalizer owns these events and the parsing that produces them; it does
|
|
4
|
+
* NOT own the chat protocol - lucid maps events into frames downstream.
|
|
5
|
+
* Only `done` is terminal. A turn may emit MULTIPLE `error` events before
|
|
6
|
+
* it (a decoded mid-stream error, then a crash-exit stderr tail, etc.), so
|
|
7
|
+
* a consumer treats `error` as informational and waits for `done`.
|
|
8
|
+
*
|
|
9
|
+
* Event classes are load-bearing for backpressure: token/progress/context
|
|
10
|
+
* are droppable (coalescible, latest-wins); the rest are lossless. The
|
|
11
|
+
* split is DECLARED here because the normalizer emits the events; the
|
|
12
|
+
* coalescing policy that consumes it lives in the chat layer (lucid-v2).
|
|
13
|
+
* Under token granularity a turn's text arrives twice by design: as token
|
|
14
|
+
* deltas AND as the whole trailing message event - render one, never
|
|
15
|
+
* concatenate both.
|
|
16
|
+
*/
|
|
17
|
+
import type { CapabilityResult } from "../interpretation/capabilities.js";
|
|
18
|
+
export type ExitCause = "clean" | "limit" | "crash" | "stall" | "killed";
|
|
19
|
+
export type HarnessEvent = {
|
|
20
|
+
readonly kind: "identity";
|
|
21
|
+
readonly sessionId: string;
|
|
22
|
+
readonly authority: "caller-assigned" | "harness-minted";
|
|
23
|
+
readonly capabilities: CapabilityResult;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: "token";
|
|
26
|
+
readonly text: string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "message";
|
|
29
|
+
readonly role: string;
|
|
30
|
+
readonly text: string;
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: "progress";
|
|
33
|
+
readonly label: string;
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: "tool";
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly input?: unknown;
|
|
38
|
+
} | {
|
|
39
|
+
readonly kind: "context";
|
|
40
|
+
readonly usedPct: number;
|
|
41
|
+
} | {
|
|
42
|
+
readonly kind: "limit";
|
|
43
|
+
readonly code: string;
|
|
44
|
+
readonly message: string;
|
|
45
|
+
} | {
|
|
46
|
+
readonly kind: "error";
|
|
47
|
+
readonly message: string;
|
|
48
|
+
} | {
|
|
49
|
+
readonly kind: "done";
|
|
50
|
+
readonly exitCode: number | null;
|
|
51
|
+
readonly cause: ExitCause;
|
|
52
|
+
};
|
|
53
|
+
export declare const DROPPABLE_KINDS: Set<string>;
|
|
54
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/execution/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEzE,MAAM,MAAM,YAAY,GACpB;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,GAAG,gBAAgB,CAAC;IACzD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;CACzC,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3E;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,CAAC;AAE3F,eAAO,MAAM,eAAe,aAA4C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/execution/events.ts"],"names":[],"mappings":"AAoCA,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution layer: owns the process lifecycle for a harness session.
|
|
3
|
+
*
|
|
4
|
+
* It exposes openSession() and streamTurn(), which drive a CLI process via
|
|
5
|
+
* injected {spawn, clock, signal} primitives and emit HarnessEvent values.
|
|
6
|
+
* This module knows nothing about any chat protocol. Because it must run
|
|
7
|
+
* identically on Node and Bun, it never imports child_process and never calls
|
|
8
|
+
* process.kill directly outside the node-deps adapter - all process I/O and
|
|
9
|
+
* signalling flows through the injected primitives.
|
|
10
|
+
*/
|
|
11
|
+
export { AsyncChannel } from "./channel.js";
|
|
12
|
+
export * from "./decode.js";
|
|
13
|
+
export * from "./deps.js";
|
|
14
|
+
export * from "./events.js";
|
|
15
|
+
export { LINE_MAX, LineBuffer } from "./lines.js";
|
|
16
|
+
export { nodeRunnerDeps } from "./node-deps.js";
|
|
17
|
+
export * from "./open-session.js";
|
|
18
|
+
export * from "./stream-turn.js";
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/execution/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution layer: owns the process lifecycle for a harness session.
|
|
3
|
+
*
|
|
4
|
+
* It exposes openSession() and streamTurn(), which drive a CLI process via
|
|
5
|
+
* injected {spawn, clock, signal} primitives and emit HarnessEvent values.
|
|
6
|
+
* This module knows nothing about any chat protocol. Because it must run
|
|
7
|
+
* identically on Node and Bun, it never imports child_process and never calls
|
|
8
|
+
* process.kill directly outside the node-deps adapter - all process I/O and
|
|
9
|
+
* signalling flows through the injected primitives.
|
|
10
|
+
*/
|
|
11
|
+
export { AsyncChannel } from "./channel.js";
|
|
12
|
+
export * from "./decode.js";
|
|
13
|
+
export * from "./deps.js";
|
|
14
|
+
export * from "./events.js";
|
|
15
|
+
export { LINE_MAX, LineBuffer } from "./lines.js";
|
|
16
|
+
export { nodeRunnerDeps } from "./node-deps.js";
|
|
17
|
+
export * from "./open-session.js";
|
|
18
|
+
export * from "./stream-turn.js";
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/execution/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line assembly for process streams: chunks arrive torn at arbitrary BYTE
|
|
3
|
+
* boundaries, so the byte->string step must stream (a multibyte character
|
|
4
|
+
* split across chunks decodes whole, never as replacement bytes), and
|
|
5
|
+
* complete lines are only ever cut at '\n'. The pending partial is scanned
|
|
6
|
+
* incrementally (never re-split from the start) and bounded: a payload
|
|
7
|
+
* must not become parseable - or resident - by luck of where the pipe
|
|
8
|
+
* split it, so an over-long line flips to discard until its newline.
|
|
9
|
+
*/
|
|
10
|
+
export declare const LINE_MAX = 65536;
|
|
11
|
+
export declare class LineBuffer {
|
|
12
|
+
private readonly decoder;
|
|
13
|
+
private pending;
|
|
14
|
+
private scanFrom;
|
|
15
|
+
private discarding;
|
|
16
|
+
push(chunk: string | Uint8Array): string[];
|
|
17
|
+
private ingest;
|
|
18
|
+
/** The final partial line at stream close, if any. */
|
|
19
|
+
flush(): string | null;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=lines.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lines.d.ts","sourceRoot":"","sources":["../../src/execution/lines.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,QAAQ,QAAS,CAAC;AAE/B,qBAAa,UAAU;IAGrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,OAAO,CAAM;IACrB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,UAAU,CAAS;IAE3B,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,EAAE,CAGzC;IAED,OAAO,CAAC,MAAM;IA2Bd,sDAAsD;IACtD,KAAK,IAAI,MAAM,GAAG,IAAI,CAQrB;CACF"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line assembly for process streams: chunks arrive torn at arbitrary BYTE
|
|
3
|
+
* boundaries, so the byte->string step must stream (a multibyte character
|
|
4
|
+
* split across chunks decodes whole, never as replacement bytes), and
|
|
5
|
+
* complete lines are only ever cut at '\n'. The pending partial is scanned
|
|
6
|
+
* incrementally (never re-split from the start) and bounded: a payload
|
|
7
|
+
* must not become parseable - or resident - by luck of where the pipe
|
|
8
|
+
* split it, so an over-long line flips to discard until its newline.
|
|
9
|
+
*/
|
|
10
|
+
export const LINE_MAX = 65_536;
|
|
11
|
+
export class LineBuffer {
|
|
12
|
+
// Per-instance and stateful ({stream:true}): a shared decoder would carry
|
|
13
|
+
// partial-sequence state across two streams and corrupt both.
|
|
14
|
+
decoder = new TextDecoder();
|
|
15
|
+
pending = "";
|
|
16
|
+
scanFrom = 0;
|
|
17
|
+
discarding = false;
|
|
18
|
+
push(chunk) {
|
|
19
|
+
const text = typeof chunk === "string" ? chunk : this.decoder.decode(chunk, { stream: true });
|
|
20
|
+
return this.ingest(text);
|
|
21
|
+
}
|
|
22
|
+
ingest(text) {
|
|
23
|
+
this.pending += text;
|
|
24
|
+
const lines = [];
|
|
25
|
+
while (true) {
|
|
26
|
+
const at = this.pending.indexOf("\n", this.scanFrom);
|
|
27
|
+
if (at === -1)
|
|
28
|
+
break;
|
|
29
|
+
const line = this.pending.slice(0, at);
|
|
30
|
+
this.pending = this.pending.slice(at + 1);
|
|
31
|
+
this.scanFrom = 0;
|
|
32
|
+
if (this.discarding) {
|
|
33
|
+
// The truncated head of an over-long line ends here; drop it whole.
|
|
34
|
+
this.discarding = false;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// The bound applies to COMPLETE lines too - an oversized line whose
|
|
38
|
+
// newline arrived in the same chunk must not bypass it.
|
|
39
|
+
if (line.length <= LINE_MAX && !isBlank(line))
|
|
40
|
+
lines.push(line);
|
|
41
|
+
}
|
|
42
|
+
this.scanFrom = this.pending.length;
|
|
43
|
+
if (this.pending.length > LINE_MAX) {
|
|
44
|
+
this.pending = "";
|
|
45
|
+
this.scanFrom = 0;
|
|
46
|
+
this.discarding = true;
|
|
47
|
+
}
|
|
48
|
+
return lines;
|
|
49
|
+
}
|
|
50
|
+
/** The final partial line at stream close, if any. */
|
|
51
|
+
flush() {
|
|
52
|
+
const tail = this.decoder.decode();
|
|
53
|
+
if (tail !== "")
|
|
54
|
+
this.pending += tail;
|
|
55
|
+
const rest = this.discarding || isBlank(this.pending) ? null : this.pending;
|
|
56
|
+
this.pending = "";
|
|
57
|
+
this.scanFrom = 0;
|
|
58
|
+
this.discarding = false;
|
|
59
|
+
return rest;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const isBlank = (line) => {
|
|
63
|
+
for (let i = 0; i < line.length; i++) {
|
|
64
|
+
const c = line.charCodeAt(i);
|
|
65
|
+
if (c !== 32 && c !== 9 && c !== 13)
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=lines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lines.js","sourceRoot":"","sources":["../../src/execution/lines.ts"],"names":[],"mappings":"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"}
|