@skill-harness/adapters 0.29.0 → 0.31.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 +1 -1
- package/dist/pi-json.d.ts +10 -4
- package/dist/pi-json.js +92 -100
- package/dist/pi.js +6 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Harness adapters for [skill-harness](https://github.com/mojomanyana/skill-harnes
|
|
|
4
4
|
the Pi subject/judge runner, the frozen pi-daddy 0.17/v2
|
|
5
5
|
normalizer, and the separately pinned production ledger-v3 normalizer.
|
|
6
6
|
|
|
7
|
-
Fresh subject execution
|
|
7
|
+
Fresh subject execution supports **Pi 1.0.4 and 1.1.0**. Other or unavailable
|
|
8
8
|
versions produce execution ERROR before a subject call. Retained results remain
|
|
9
9
|
readable; the historical Pi 0.83.0 raw parser is not current qualification.
|
|
10
10
|
|
package/dist/pi-json.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExecutionTraceV1, ModelRef, RunMode } from "@skill-harness/core";
|
|
1
|
+
import type { ExecutionTraceV1, ModelRef, RunMode, ProcessObservation } from "@skill-harness/core";
|
|
2
2
|
/**
|
|
3
3
|
* Run `pi --mode json` and build an execution trace, **streaming**.
|
|
4
4
|
*
|
|
@@ -9,10 +9,10 @@ import type { ExecutionTraceV1, ModelRef, RunMode } from "@skill-harness/core";
|
|
|
9
9
|
* into a string (which is what the shared `exec()` helper does) would exhaust
|
|
10
10
|
* memory partway through a long wave, taking the whole run with it.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* The lifecycle is shared with `exec()`, but capture is streaming. Quadratic events are
|
|
13
13
|
* dropped as each line arrives, so the giant ones are never retained; the
|
|
14
|
-
* remainder
|
|
15
|
-
*
|
|
14
|
+
* remainder is bounded by both bytes and record count. Oversized lines or retained
|
|
15
|
+
* evidence terminate the owned process and report an infrastructure error.
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
18
|
* The two quadratic event types, matched at the head of the object where pi
|
|
@@ -37,6 +37,11 @@ export interface PiJsonRunOptions {
|
|
|
37
37
|
* as "inherit", so the control arm (which never sets this) is unaffected.
|
|
38
38
|
*/
|
|
39
39
|
env?: NodeJS.ProcessEnv;
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
maxLineBytes?: number;
|
|
42
|
+
maxRetainedBytes?: number;
|
|
43
|
+
maxRecords?: number;
|
|
44
|
+
maxOutputBytes?: number;
|
|
40
45
|
}
|
|
41
46
|
export interface PiJsonRunResult {
|
|
42
47
|
trace: ExecutionTraceV1;
|
|
@@ -51,5 +56,6 @@ export interface PiJsonRunResult {
|
|
|
51
56
|
* verdict.
|
|
52
57
|
*/
|
|
53
58
|
providerFailure: string | null;
|
|
59
|
+
observation?: ProcessObservation;
|
|
54
60
|
}
|
|
55
61
|
export declare function runPiJson(opts: PiJsonRunOptions): Promise<PiJsonRunResult>;
|
package/dist/pi-json.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { StringDecoder } from "node:string_decoder";
|
|
3
|
-
import { parseTrace, providerFailureFromJsonLine } from "@skill-harness/core";
|
|
1
|
+
import { BoundedCapture, ProcessOutputLimitError, runProcess, parseTrace, providerFailureFromJsonLine } from "@skill-harness/core";
|
|
4
2
|
/**
|
|
5
3
|
* Run `pi --mode json` and build an execution trace, **streaming**.
|
|
6
4
|
*
|
|
@@ -11,10 +9,10 @@ import { parseTrace, providerFailureFromJsonLine } from "@skill-harness/core";
|
|
|
11
9
|
* into a string (which is what the shared `exec()` helper does) would exhaust
|
|
12
10
|
* memory partway through a long wave, taking the whole run with it.
|
|
13
11
|
*
|
|
14
|
-
*
|
|
12
|
+
* The lifecycle is shared with `exec()`, but capture is streaming. Quadratic events are
|
|
15
13
|
* dropped as each line arrives, so the giant ones are never retained; the
|
|
16
|
-
* remainder
|
|
17
|
-
*
|
|
14
|
+
* remainder is bounded by both bytes and record count. Oversized lines or retained
|
|
15
|
+
* evidence terminate the owned process and report an infrastructure error.
|
|
18
16
|
*/
|
|
19
17
|
/**
|
|
20
18
|
* The two quadratic event types, matched at the head of the object where pi
|
|
@@ -24,105 +22,99 @@ import { parseTrace, providerFailureFromJsonLine } from "@skill-harness/core";
|
|
|
24
22
|
export const SKIPPED_TYPE_RE = /^\s*\{\s*"type"\s*:\s*"(?:message_update|tool_execution_update)"/;
|
|
25
23
|
/** How much stderr to retain — enough to diagnose, bounded so a loop cannot blow up. */
|
|
26
24
|
const MAX_STDERR_CHARS = 8000;
|
|
27
|
-
export function runPiJson(opts) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (event.type === "message_update" && kept.at(-1) !== '{"type":"message_update"}') {
|
|
67
|
-
kept.push('{"type":"message_update"}');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
kept.push("null"); // one parser-owned malformed-record error per bad line
|
|
72
|
-
}
|
|
25
|
+
export async function runPiJson(opts) {
|
|
26
|
+
const kept = [];
|
|
27
|
+
const stderr = new BoundedCapture(MAX_STDERR_CHARS);
|
|
28
|
+
const maxLine = opts.maxLineBytes ?? 8 * 1024 * 1024;
|
|
29
|
+
const maxRetained = opts.maxRetainedBytes ?? 16 * 1024 * 1024;
|
|
30
|
+
const maxRecords = opts.maxRecords ?? 50_000;
|
|
31
|
+
for (const limit of [maxLine, maxRetained, maxRecords])
|
|
32
|
+
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
33
|
+
throw new Error("Invalid Pi capture limit");
|
|
34
|
+
let retainedBytes = 0;
|
|
35
|
+
let providerFailure = null;
|
|
36
|
+
const keep = (line) => {
|
|
37
|
+
const size = Buffer.byteLength(line, "utf8");
|
|
38
|
+
if (retainedBytes + size > maxRetained || kept.length >= maxRecords)
|
|
39
|
+
throw new ProcessOutputLimitError("Pi retained JSON evidence limit exceeded");
|
|
40
|
+
kept.push(line);
|
|
41
|
+
retainedBytes += size;
|
|
42
|
+
};
|
|
43
|
+
const acceptLine = (line) => {
|
|
44
|
+
// Prefilter before the full parse: the events skipped here are both the
|
|
45
|
+
// overwhelming majority of lines and by far the largest.
|
|
46
|
+
//
|
|
47
|
+
// Anchored on the `type` field, NOT a substring of the whole line. A raw
|
|
48
|
+
// `line.includes('"message_update"')` also matched any event whose
|
|
49
|
+
// ARGUMENTS contained that text — so a `tool_execution_start` for, say,
|
|
50
|
+
// `grep '"message_update"' logs/` was dropped before parsing, and a
|
|
51
|
+
// dropped start means the call never enters the trace at all. A
|
|
52
|
+
// `forbid_calls` gate on that tool then passed for want of the evidence.
|
|
53
|
+
if (!line.trim())
|
|
54
|
+
return;
|
|
55
|
+
if (SKIPPED_TYPE_RE.test(line)) {
|
|
56
|
+
// Validate but never retain the quadratic payload. Otherwise a malformed
|
|
57
|
+
// update could disappear and make an incomplete capture look successful.
|
|
58
|
+
let event;
|
|
59
|
+
try {
|
|
60
|
+
event = JSON.parse(line);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
keep("null"); // one parser-owned malformed-record error per bad line
|
|
73
64
|
return;
|
|
74
65
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
66
|
+
// A late update invalidates an earlier final/settlement. Consecutive
|
|
67
|
+
// updates need only one small state marker, not their accumulated text.
|
|
68
|
+
if (event.type === "message_update" && kept.at(-1) !== '{"type":"message_update"}')
|
|
69
|
+
keep('{"type":"message_update"}');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
keep(line);
|
|
73
|
+
if (providerFailure === null)
|
|
74
|
+
providerFailure = providerFailureFromJsonLine(line);
|
|
75
|
+
};
|
|
76
|
+
// Frame bytes at LF, so split UTF-8 and legal Unicode separators stay intact.
|
|
77
|
+
// Enforce the bound before decoding or parsing an unterminated giant line.
|
|
78
|
+
let pending = new BoundedCapture(maxLine);
|
|
79
|
+
const append = (chunk) => {
|
|
80
|
+
pending.push(chunk);
|
|
81
|
+
if (pending.truncated)
|
|
82
|
+
throw new ProcessOutputLimitError("Pi JSON line byte limit exceeded");
|
|
83
|
+
};
|
|
84
|
+
const flush = () => {
|
|
85
|
+
if (pending.bytes)
|
|
86
|
+
acceptLine(pending.text());
|
|
87
|
+
pending = new BoundedCapture(maxLine);
|
|
88
|
+
};
|
|
89
|
+
const result = await runProcess("pi", opts.args, { cwd: opts.cwd, env: opts.env,
|
|
90
|
+
timeoutMs: opts.timeoutMs, signal: opts.signal, maxOutputBytes: opts.maxOutputBytes,
|
|
91
|
+
onStdout(chunk) {
|
|
85
92
|
let start = 0, end;
|
|
86
|
-
while ((end =
|
|
87
|
-
|
|
93
|
+
while ((end = chunk.indexOf(10, start)) >= 0) {
|
|
94
|
+
append(chunk.subarray(start, end));
|
|
95
|
+
flush();
|
|
88
96
|
start = end + 1;
|
|
89
97
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
child.on("close", (code) => {
|
|
110
|
-
if (settled)
|
|
111
|
-
return;
|
|
112
|
-
settled = true;
|
|
113
|
-
clearTimeout(timer);
|
|
114
|
-
const parsed = parseTrace(kept, {
|
|
115
|
-
piVersion: opts.piVersion,
|
|
116
|
-
subject: opts.subject,
|
|
117
|
-
scenarioId: opts.scenarioId,
|
|
118
|
-
mode: opts.mode,
|
|
119
|
-
rep: opts.rep,
|
|
120
|
-
turn: opts.turn,
|
|
121
|
-
changedPaths: opts.changedPaths,
|
|
122
|
-
homeDir: opts.homeDir,
|
|
123
|
-
});
|
|
124
|
-
resolve({ ...parsed, code, stderr: stderr.slice(0, MAX_STDERR_CHARS), providerFailure });
|
|
125
|
-
});
|
|
98
|
+
if (start < chunk.length)
|
|
99
|
+
append(chunk.subarray(start));
|
|
100
|
+
},
|
|
101
|
+
onStderr(chunk) { stderr.push(chunk); },
|
|
102
|
+
onStdoutEnd: flush, });
|
|
103
|
+
if (result.error) {
|
|
104
|
+
if (result.observation.reason === "timeout")
|
|
105
|
+
throw new Error(`pi --mode json timed out after ${opts.timeoutMs}ms`);
|
|
106
|
+
throw result.error;
|
|
107
|
+
}
|
|
108
|
+
const parsed = parseTrace(kept, {
|
|
109
|
+
piVersion: opts.piVersion,
|
|
110
|
+
subject: opts.subject,
|
|
111
|
+
scenarioId: opts.scenarioId,
|
|
112
|
+
mode: opts.mode,
|
|
113
|
+
rep: opts.rep,
|
|
114
|
+
turn: opts.turn,
|
|
115
|
+
changedPaths: opts.changedPaths,
|
|
116
|
+
homeDir: opts.homeDir,
|
|
126
117
|
});
|
|
118
|
+
return { ...parsed, code: result.code, stderr: stderr.text(), providerFailure, observation: result.observation };
|
|
127
119
|
}
|
|
128
120
|
//# sourceMappingURL=pi-json.js.map
|
package/dist/pi.js
CHANGED
|
@@ -134,6 +134,7 @@ export const piAdapter = {
|
|
|
134
134
|
"--no-context-files",
|
|
135
135
|
"--no-extensions",
|
|
136
136
|
...extensionFlags(req.extensions),
|
|
137
|
+
...(req.tools === undefined ? [] : req.tools.length ? ["--tools", req.tools.join(",")] : ["--no-tools"]),
|
|
137
138
|
"--provider",
|
|
138
139
|
req.model.provider,
|
|
139
140
|
"--model",
|
|
@@ -204,6 +205,7 @@ export const piAdapter = {
|
|
|
204
205
|
"--no-context-files",
|
|
205
206
|
"--no-extensions",
|
|
206
207
|
...extensionFlags(req.extensions),
|
|
208
|
+
...(req.tools === undefined ? [] : req.tools.length ? ["--tools", req.tools.join(",")] : ["--no-tools"]),
|
|
207
209
|
"--provider",
|
|
208
210
|
req.model.provider,
|
|
209
211
|
"--model",
|
|
@@ -293,7 +295,7 @@ export const piAdapter = {
|
|
|
293
295
|
...(executionFailure ? { executionFailure } : {}),
|
|
294
296
|
};
|
|
295
297
|
},
|
|
296
|
-
/**
|
|
298
|
+
/** Judge only the supplied evidence: no tools, MCP, skills, context files, extensions or session. */
|
|
297
299
|
async judge(req) {
|
|
298
300
|
if (req.model.provider === "claude-code") {
|
|
299
301
|
throw new Error("judge provider `claude-code` was removed; configure a Pi provider such as `openai-codex`");
|
|
@@ -303,6 +305,8 @@ export const piAdapter = {
|
|
|
303
305
|
"--no-context-files",
|
|
304
306
|
"--no-extensions",
|
|
305
307
|
"--no-session",
|
|
308
|
+
"--no-tools",
|
|
309
|
+
"--no-mcp",
|
|
306
310
|
"--provider",
|
|
307
311
|
req.model.provider,
|
|
308
312
|
"--model",
|
|
@@ -313,7 +317,7 @@ export const piAdapter = {
|
|
|
313
317
|
const r = await exec("pi", args, { cwd: req.cwd, timeoutMs: PI_TIMEOUT_MS });
|
|
314
318
|
// Surface failures: pi writes provider errors (auth, out-of-credits) to stderr
|
|
315
319
|
// and exits non-zero with empty stdout. Pass them through so grading can report.
|
|
316
|
-
if (r.stdout.trim().length === 0 &&
|
|
320
|
+
if (r.code !== 0 || (r.stdout.trim().length === 0 && r.stderr.trim())) {
|
|
317
321
|
return `[judge error: pi exited ${r.code}] ${r.stderr.trim()}`;
|
|
318
322
|
}
|
|
319
323
|
return r.stdout;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skill-harness/adapters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "skill-harness Pi runner and judge adapter (internal API)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,6 +42,6 @@
|
|
|
42
42
|
"prepack": "node ../../scripts/release-pack.mjs --guard"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@skill-harness/core": "0.
|
|
45
|
+
"@skill-harness/core": "0.31.0"
|
|
46
46
|
}
|
|
47
47
|
}
|