@skill-harness/adapters 0.9.0 → 0.11.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/dist/closed-schema.d.ts +50 -0
- package/dist/closed-schema.js +353 -0
- package/dist/pi-daddy-ledger-v2.d.ts +460 -0
- package/dist/pi-daddy-ledger-v2.js +649 -0
- package/dist/pi-json.d.ts +12 -0
- package/dist/pi-json.js +6 -2
- package/dist/pi.js +60 -10
- package/dist/trajectory.d.ts +40 -3
- package/dist/trajectory.js +700 -76
- package/package.json +2 -2
package/dist/pi-json.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
|
-
import { parseTrace } from "@skill-harness/core";
|
|
3
|
+
import { parseTrace, providerFailureFromJsonLine } from "@skill-harness/core";
|
|
4
4
|
/**
|
|
5
5
|
* Run `pi --mode json` and build an execution trace, **streaming**.
|
|
6
6
|
*
|
|
@@ -28,12 +28,14 @@ export function runPiJson(opts) {
|
|
|
28
28
|
return new Promise((resolve, reject) => {
|
|
29
29
|
const child = spawn("pi", opts.args, {
|
|
30
30
|
cwd: opts.cwd,
|
|
31
|
+
env: opts.env,
|
|
31
32
|
// stdin from /dev/null: pi hangs waiting on it otherwise, and a hang in a
|
|
32
33
|
// wave is indistinguishable from a slow model until the timeout fires.
|
|
33
34
|
stdio: ["ignore", "pipe", "pipe"],
|
|
34
35
|
});
|
|
35
36
|
const kept = [];
|
|
36
37
|
let stderr = "";
|
|
38
|
+
let providerFailure = null;
|
|
37
39
|
let settled = false;
|
|
38
40
|
const timer = setTimeout(() => {
|
|
39
41
|
if (settled)
|
|
@@ -58,6 +60,8 @@ export function runPiJson(opts) {
|
|
|
58
60
|
if (SKIPPED_TYPE_RE.test(line))
|
|
59
61
|
return;
|
|
60
62
|
kept.push(line);
|
|
63
|
+
if (providerFailure === null)
|
|
64
|
+
providerFailure = providerFailureFromJsonLine(line);
|
|
61
65
|
});
|
|
62
66
|
child.stderr.on("data", (chunk) => {
|
|
63
67
|
if (stderr.length < MAX_STDERR_CHARS)
|
|
@@ -85,7 +89,7 @@ export function runPiJson(opts) {
|
|
|
85
89
|
changedPaths: opts.changedPaths,
|
|
86
90
|
homeDir: opts.homeDir,
|
|
87
91
|
});
|
|
88
|
-
resolve({ ...parsed, code, stderr: stderr.slice(0, MAX_STDERR_CHARS) });
|
|
92
|
+
resolve({ ...parsed, code, stderr: stderr.slice(0, MAX_STDERR_CHARS), providerFailure });
|
|
89
93
|
});
|
|
90
94
|
});
|
|
91
95
|
}
|
package/dist/pi.js
CHANGED
|
@@ -3,8 +3,24 @@ import { tmpdir, homedir } from "node:os";
|
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { runPiJson } from "./pi-json.js";
|
|
5
5
|
import { collectTrajectorySources, normalizePiTraces, resequence } from "./trajectory.js";
|
|
6
|
-
import { exec, onPath, envNum, traceSha256 } from "@skill-harness/core";
|
|
6
|
+
import { exec, onPath, envNum, traceSha256, withProviderFailure } from "@skill-harness/core";
|
|
7
7
|
const PI_TIMEOUT_MS = envNum("PI_TIMEOUT_MS", 300_000);
|
|
8
|
+
/**
|
|
9
|
+
* stderr fragments that mean the provider refused the request, so the run measured
|
|
10
|
+
* nothing about the model. Substring matching on a message pi passes through from
|
|
11
|
+
* the provider — deliberately narrow: a stderr line we cannot classify stays an
|
|
12
|
+
* ordinary non-zero exit, because calling a real model failure "infrastructure"
|
|
13
|
+
* would hide a regression.
|
|
14
|
+
*/
|
|
15
|
+
const PROVIDER_STDERR_SIGNATURES = [
|
|
16
|
+
"invalidated oauth token",
|
|
17
|
+
"invalid_api_key",
|
|
18
|
+
"insufficient_quota",
|
|
19
|
+
];
|
|
20
|
+
function providerStderr(stderr) {
|
|
21
|
+
const hay = stderr.toLowerCase();
|
|
22
|
+
return PROVIDER_STDERR_SIGNATURES.some((sig) => hay.includes(sig)) ? stderr.trim() : null;
|
|
23
|
+
}
|
|
8
24
|
/**
|
|
9
25
|
* Refuse to hand pi a skill dir it will silently ignore.
|
|
10
26
|
*
|
|
@@ -127,26 +143,46 @@ export const piAdapter = {
|
|
|
127
143
|
: skillFlags(req.mode, req.skillDir);
|
|
128
144
|
const total = req.turns.length;
|
|
129
145
|
const parts = [];
|
|
146
|
+
// The arm's env, merged over the harness's own — undefined (not `process.env`)
|
|
147
|
+
// when there is none, so `exec`'s `env: opts.env ?? process.env` inherits
|
|
148
|
+
// normally and the control arm is unaffected.
|
|
149
|
+
const env = req.armEnv ? { ...process.env, ...req.armEnv } : undefined;
|
|
150
|
+
// Collected across turns and written by `withProviderFailure` into the
|
|
151
|
+
// transcript PREAMBLE at the end, never inline after an assistant turn: the
|
|
152
|
+
// preamble is the only region of the transcript the model provably cannot
|
|
153
|
+
// reach, and a marker anywhere else is forgeable by a model that types the
|
|
154
|
+
// words (which would convert a FAIL into ERROR and mute the judge forever).
|
|
155
|
+
let providerFailure = null;
|
|
130
156
|
if (total === 1) {
|
|
131
157
|
const args = [...flags, ...common, "--no-session", "-p", req.turns[0]];
|
|
132
|
-
const r = await exec("pi", args, { cwd: req.cwd, timeoutMs: PI_TIMEOUT_MS });
|
|
158
|
+
const r = await exec("pi", args, { cwd: req.cwd, timeoutMs: PI_TIMEOUT_MS, env });
|
|
133
159
|
parts.push(header(1, 1, req.turns[0]));
|
|
134
160
|
parts.push(`<<< ASSISTANT:\n${r.stdout.trim()}\n`);
|
|
135
|
-
if (r.code !== 0)
|
|
136
|
-
|
|
137
|
-
|
|
161
|
+
if (r.code !== 0) {
|
|
162
|
+
providerFailure = providerStderr(r.stderr);
|
|
163
|
+
if (!providerFailure)
|
|
164
|
+
parts.push(`[pi exited ${r.code}]\n${r.stderr.trim()}\n`);
|
|
165
|
+
}
|
|
166
|
+
return withProviderFailure(parts.join("\n"), providerFailure);
|
|
138
167
|
}
|
|
139
168
|
const session = mkdtempSync(join(tmpdir(), "sc-pi-session-"));
|
|
140
169
|
for (let i = 0; i < total; i++) {
|
|
141
170
|
const turnFlags = i === 0 ? ["--session-dir", session] : ["--session-dir", session, "-c"];
|
|
142
171
|
const args = [...flags, ...common, ...turnFlags, "-p", req.turns[i]];
|
|
143
|
-
const r = await exec("pi", args, { cwd: req.cwd, timeoutMs: PI_TIMEOUT_MS });
|
|
172
|
+
const r = await exec("pi", args, { cwd: req.cwd, timeoutMs: PI_TIMEOUT_MS, env });
|
|
144
173
|
parts.push(header(i + 1, total, req.turns[i]));
|
|
145
174
|
parts.push(`<<< ASSISTANT:\n${r.stdout.trim()}\n`);
|
|
146
|
-
if (r.code !== 0)
|
|
147
|
-
|
|
175
|
+
if (r.code !== 0) {
|
|
176
|
+
const provider = providerStderr(r.stderr);
|
|
177
|
+
// First failure wins, same as the structured path: the turns after an
|
|
178
|
+
// outage are downstream of it, not independent evidence.
|
|
179
|
+
if (provider && providerFailure === null)
|
|
180
|
+
providerFailure = provider;
|
|
181
|
+
if (!provider)
|
|
182
|
+
parts.push(`[pi exited ${r.code} on turn ${i + 1}]\n${r.stderr.trim()}\n`);
|
|
183
|
+
}
|
|
148
184
|
}
|
|
149
|
-
return parts.join("\n");
|
|
185
|
+
return withProviderFailure(parts.join("\n"), providerFailure);
|
|
150
186
|
},
|
|
151
187
|
/**
|
|
152
188
|
* Structured run: same flags, same turn loop, plus `--mode json` and a trace
|
|
@@ -178,6 +214,10 @@ export const piAdapter = {
|
|
|
178
214
|
const traces = [];
|
|
179
215
|
const parts = [];
|
|
180
216
|
const session = total === 1 ? null : mkdtempSync(join(tmpdir(), "sc-pi-session-"));
|
|
217
|
+
let providerFailure = null;
|
|
218
|
+
// Same merge as `run()`: undefined when the arm carries no env, so `spawn`
|
|
219
|
+
// (which treats `undefined` as "inherit") leaves the control arm untouched.
|
|
220
|
+
const env = req.armEnv ? { ...process.env, ...req.armEnv } : undefined;
|
|
181
221
|
for (let i = 0; i < total; i++) {
|
|
182
222
|
const turnFlags = session === null
|
|
183
223
|
? ["--no-session"]
|
|
@@ -196,6 +236,7 @@ export const piAdapter = {
|
|
|
196
236
|
rep: req.rep ?? 0,
|
|
197
237
|
turn: i,
|
|
198
238
|
homeDir: homedir(),
|
|
239
|
+
env,
|
|
199
240
|
});
|
|
200
241
|
// A stream with no terminal events at all is not evidence of a clean run.
|
|
201
242
|
// Fail loudly here rather than let an empty trace satisfy a `forbid_calls`
|
|
@@ -210,6 +251,14 @@ export const piAdapter = {
|
|
|
210
251
|
r.trace.capture_errors = [`pi JSONL contained ${r.malformedLines} malformed line(s); absence-based trace assertions are unsafe`];
|
|
211
252
|
r.trace.trace_sha256 = traceSha256(r.trace);
|
|
212
253
|
}
|
|
254
|
+
// Recorded here, written into the transcript preamble by
|
|
255
|
+
// `withProviderFailure` below — not just returned on `providerFailure`: the
|
|
256
|
+
// artifact on disk is the only thing a later `grade`/`regrade` call ever
|
|
257
|
+
// reads (see `judgeOneRep` in core/regrade.ts), and the structured path
|
|
258
|
+
// exits 0 while carrying the evidence, so a field a re-judge never sees
|
|
259
|
+
// leaves it unrecoverable from the saved transcript.
|
|
260
|
+
if (providerFailure === null && r.providerFailure)
|
|
261
|
+
providerFailure = r.providerFailure;
|
|
213
262
|
traces.push(r.trace);
|
|
214
263
|
parts.push(header(i + 1, total, req.turns[i]));
|
|
215
264
|
parts.push(`<<< ASSISTANT:\n${r.trace.final_text.trim()}\n`);
|
|
@@ -235,10 +284,11 @@ export const piAdapter = {
|
|
|
235
284
|
}
|
|
236
285
|
const eventErrors = [...native.errors, ...chronologyErrors];
|
|
237
286
|
return {
|
|
238
|
-
transcript: parts.join("\n"),
|
|
287
|
+
transcript: withProviderFailure(parts.join("\n"), providerFailure),
|
|
239
288
|
traces,
|
|
240
289
|
events: resequence(combined),
|
|
241
290
|
...(eventErrors.length ? { eventErrors } : {}),
|
|
291
|
+
...(providerFailure ? { providerFailure } : {}),
|
|
242
292
|
};
|
|
243
293
|
},
|
|
244
294
|
/**
|
package/dist/trajectory.d.ts
CHANGED
|
@@ -12,8 +12,45 @@ export declare function normalizePiTraces(traces: ExecutionTraceV1[]): Trajector
|
|
|
12
12
|
/** Normalize principal-pi-skills' immutable assurance event schema v1.0. */
|
|
13
13
|
export declare function normalizePrincipalAssuranceLedger(text: string): TrajectoryEventV1[];
|
|
14
14
|
/**
|
|
15
|
-
* Normalize
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Normalize pi-daddy's public ledgers: unversioned 0.17 GrantRecord lines and
|
|
16
|
+
* ledgerVersion 2 runtime events emitted by 0.18.0. Version detection precedes
|
|
17
|
+
* the legacy fallback so a new event can never be misdiagnosed as an old grant.
|
|
18
18
|
*/
|
|
19
19
|
export declare function normalizePiDaddyLedger(text: string): TrajectoryEventV1[];
|
|
20
|
+
/**
|
|
21
|
+
* pi-daddy's canonical refusal vocabulary, in the pinned contract's own order.
|
|
22
|
+
*
|
|
23
|
+
* This is a copy of `#/$defs/refusalCode` from the pinned schema and is exported
|
|
24
|
+
* so `pi-daddy-contract.test.ts` can assert set equality against the producer
|
|
25
|
+
* artifact — a hand-maintained second vocabulary without that drift assertion is
|
|
26
|
+
* exactly how `GRANT_ID_MALFORMED` came to be rejected as "unsupported".
|
|
27
|
+
*/
|
|
28
|
+
export declare const V2_REFUSAL_CODES: Set<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Every vocabulary the adapter restates from the pinned contract, paired with the
|
|
31
|
+
* place in the schema it must equal.
|
|
32
|
+
*
|
|
33
|
+
* The closed schema gates first, so a semantic check that has drifted *narrower*
|
|
34
|
+
* than the contract no longer opens a hole — it produces the opposite failure:
|
|
35
|
+
* a contract-valid record admitted by the schema and then thrown out by a stale
|
|
36
|
+
* harness set, which is precisely what `GRANT_ID_MALFORMED` did. One manifest, one
|
|
37
|
+
* test over all of it, so re-pinning cannot quietly leave a set behind.
|
|
38
|
+
*/
|
|
39
|
+
export declare const V2_RESTATED_VOCABULARIES: ReadonlyArray<{
|
|
40
|
+
name: string;
|
|
41
|
+
kind: "enum" | "propertyNames" | "discriminators" | "typeNames" | "numericPropertyNames";
|
|
42
|
+
pointer: string;
|
|
43
|
+
values: ReadonlySet<string>;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Harness-side subsets of a contract vocabulary, not restatements of one. They encode
|
|
47
|
+
* the harness's own semantics — which lease outcomes a receipt may be appended after,
|
|
48
|
+
* and which may precede that release — so the assertion on them is containment, not
|
|
49
|
+
* equality. Anything the test *equality*-asserts belongs in the manifest above
|
|
50
|
+
* instead; membership here is a claim that the harness deliberately holds a subset.
|
|
51
|
+
*/
|
|
52
|
+
export declare const V2_VOCABULARY_SUBSETS: ReadonlyArray<{
|
|
53
|
+
name: string;
|
|
54
|
+
pointer: string;
|
|
55
|
+
values: ReadonlySet<string>;
|
|
56
|
+
}>;
|