@skill-harness/adapters 0.23.0 → 0.24.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 +4 -0
- package/dist/pi-json.js +15 -1
- package/dist/pi.js +37 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ 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 requires **exactly Pi 1.0.4**. Different or unavailable
|
|
8
|
+
versions produce execution ERROR before a subject call. Retained results remain
|
|
9
|
+
readable; the historical Pi 0.83.0 raw parser is not current qualification.
|
|
10
|
+
|
|
7
11
|
Internal — use the `skill-harness` CLI. See the repo for docs.
|
|
8
12
|
|
|
9
13
|
ESM, Node ≥ 20.
|
package/dist/pi-json.js
CHANGED
|
@@ -57,8 +57,22 @@ export function runPiJson(opts) {
|
|
|
57
57
|
// `forbid_calls` gate on that tool then passed for want of the evidence.
|
|
58
58
|
if (!line.trim())
|
|
59
59
|
return;
|
|
60
|
-
if (SKIPPED_TYPE_RE.test(line))
|
|
60
|
+
if (SKIPPED_TYPE_RE.test(line)) {
|
|
61
|
+
// Validate but never retain the quadratic payload. Otherwise a malformed
|
|
62
|
+
// update could disappear and make an incomplete capture look successful.
|
|
63
|
+
try {
|
|
64
|
+
const event = JSON.parse(line);
|
|
65
|
+
// A late update invalidates an earlier final/settlement. Consecutive
|
|
66
|
+
// updates need only one small state marker, not their accumulated text.
|
|
67
|
+
if (event.type === "message_update" && kept.at(-1) !== '{"type":"message_update"}') {
|
|
68
|
+
kept.push('{"type":"message_update"}');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
kept.push("null"); // one parser-owned malformed-record error per bad line
|
|
73
|
+
}
|
|
61
74
|
return;
|
|
75
|
+
}
|
|
62
76
|
kept.push(line);
|
|
63
77
|
if (providerFailure === null)
|
|
64
78
|
providerFailure = providerFailureFromJsonLine(line);
|
package/dist/pi.js
CHANGED
|
@@ -3,7 +3,7 @@ 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 { priceSubjectUsage } from "./model-pricing.js";
|
|
6
|
-
import { exec, onPath, envNum,
|
|
6
|
+
import { exec, onPath, envNum, piQualificationFailure, withProviderFailure, withExecutionFailure } from "@skill-harness/core";
|
|
7
7
|
const PI_TIMEOUT_MS = envNum("PI_TIMEOUT_MS", 300_000);
|
|
8
8
|
/**
|
|
9
9
|
* stderr fragments that mean the provider refused the request, so the run measured
|
|
@@ -107,12 +107,12 @@ export const piAdapter = {
|
|
|
107
107
|
* results.yaml as `harness_cli_version`.
|
|
108
108
|
*
|
|
109
109
|
* Null on any failure — a non-zero exit, empty output, or pi missing entirely.
|
|
110
|
-
*
|
|
111
|
-
*
|
|
110
|
+
* The probe never fabricates a version. Fresh subject execution refuses an
|
|
111
|
+
* unavailable or unqualified result before starting the model.
|
|
112
112
|
*/
|
|
113
|
-
async version() {
|
|
113
|
+
async version(options) {
|
|
114
114
|
try {
|
|
115
|
-
const r = await exec("pi", ["--version"], { timeoutMs: 30_000 });
|
|
115
|
+
const r = await exec("pi", ["--version"], { timeoutMs: 30_000, ...options });
|
|
116
116
|
const line = r.stdout.split("\n")[0]?.trim() ?? "";
|
|
117
117
|
if (r.code !== 0 || line === "")
|
|
118
118
|
return null;
|
|
@@ -142,12 +142,15 @@ export const piAdapter = {
|
|
|
142
142
|
const flags = req.systemPromptFile
|
|
143
143
|
? ["--no-skills", "--append-system-prompt", readFileSync(req.systemPromptFile, "utf8")]
|
|
144
144
|
: skillFlags(req.mode, req.skillDir);
|
|
145
|
+
const env = req.armEnv ? { ...process.env, ...req.armEnv } : undefined;
|
|
146
|
+
const qualificationFailure = piQualificationFailure(await this.version({ cwd: req.cwd, env }));
|
|
147
|
+
if (qualificationFailure)
|
|
148
|
+
return withExecutionFailure("", qualificationFailure);
|
|
145
149
|
const total = req.turns.length;
|
|
146
150
|
const parts = [];
|
|
147
151
|
// The arm's env, merged over the harness's own — undefined (not `process.env`)
|
|
148
152
|
// when there is none, so `exec`'s `env: opts.env ?? process.env` inherits
|
|
149
153
|
// normally and the control arm is unaffected.
|
|
150
|
-
const env = req.armEnv ? { ...process.env, ...req.armEnv } : undefined;
|
|
151
154
|
// Collected across turns and written by `withProviderFailure` into the
|
|
152
155
|
// transcript PREAMBLE at the end, never inline after an assistant turn: the
|
|
153
156
|
// preamble is the only region of the transcript the model provably cannot
|
|
@@ -209,15 +212,23 @@ export const piAdapter = {
|
|
|
209
212
|
const flags = req.systemPromptFile
|
|
210
213
|
? ["--no-skills", "--append-system-prompt", readFileSync(req.systemPromptFile, "utf8")]
|
|
211
214
|
: skillFlags(req.mode, req.skillDir);
|
|
212
|
-
const
|
|
215
|
+
const env = req.armEnv ? { ...process.env, ...req.armEnv } : undefined;
|
|
216
|
+
const piVersion = await this.version({ cwd: req.cwd, env });
|
|
217
|
+
const qualificationFailure = piQualificationFailure(piVersion);
|
|
218
|
+
if (qualificationFailure)
|
|
219
|
+
return {
|
|
220
|
+
transcript: withExecutionFailure("", qualificationFailure),
|
|
221
|
+
traces: [],
|
|
222
|
+
executionFailure: qualificationFailure,
|
|
223
|
+
};
|
|
213
224
|
const total = req.turns.length;
|
|
214
225
|
const traces = [];
|
|
215
226
|
const parts = [];
|
|
216
227
|
const session = total === 1 ? null : mkdtempSync(join(tmpdir(), "sc-pi-session-"));
|
|
217
228
|
let providerFailure = null;
|
|
229
|
+
let executionFailure = null;
|
|
218
230
|
// Same merge as `run()`: undefined when the arm carries no env, so `spawn`
|
|
219
231
|
// (which treats `undefined` as "inherit") leaves the control arm untouched.
|
|
220
|
-
const env = req.armEnv ? { ...process.env, ...req.armEnv } : undefined;
|
|
221
232
|
for (let i = 0; i < total; i++) {
|
|
222
233
|
const turnFlags = session === null
|
|
223
234
|
? ["--no-session"]
|
|
@@ -242,34 +253,44 @@ export const piAdapter = {
|
|
|
242
253
|
// Fail loudly here rather than let an empty trace satisfy a `forbid_calls`
|
|
243
254
|
// gate — "the model called nothing" and "we recorded nothing" must not
|
|
244
255
|
// reach the scorer looking the same.
|
|
245
|
-
|
|
256
|
+
// A settled stream can still end in error/abort, and exit zero is not
|
|
257
|
+
// terminal success. Preserve the trace and a durable preamble so an
|
|
258
|
+
// ungated run or later grade cannot turn unavailable delivery into PASS.
|
|
259
|
+
if (r.trace.final_status !== undefined &&
|
|
260
|
+
(r.trace.final_status !== "complete" || r.trace.capture_errors?.length || r.code !== 0)) {
|
|
261
|
+
executionFailure = `Pi ${piVersion} turn ${i + 1}/${total}: final delivery ${r.trace.final_status}` +
|
|
262
|
+
` (exit ${r.code}); ${r.trace.capture_errors?.join("; ") || "successful process completion was not established"}`;
|
|
263
|
+
}
|
|
264
|
+
if (!r.isComplete && !executionFailure) {
|
|
246
265
|
throw new Error(`pi --mode json produced no terminal events for turn ${i + 1}/${total}` +
|
|
247
266
|
` (exit ${r.code}${r.malformedLines ? `, ${r.malformedLines} malformed line(s)` : ""})` +
|
|
248
267
|
(r.stderr.trim() ? `: ${r.stderr.trim()}` : ""));
|
|
249
268
|
}
|
|
250
|
-
if (r.malformedLines > 0) {
|
|
251
|
-
r.trace.capture_errors = [`pi JSONL contained ${r.malformedLines} malformed line(s); absence-based trace assertions are unsafe`];
|
|
252
|
-
r.trace.trace_sha256 = traceSha256(r.trace);
|
|
253
|
-
}
|
|
254
269
|
// Recorded here, written into the transcript preamble by
|
|
255
270
|
// `withProviderFailure` below — not just returned on `providerFailure`: the
|
|
256
271
|
// artifact on disk is the only thing a later `grade`/`regrade` call ever
|
|
257
272
|
// reads (see `judgeOneRep` in core/regrade.ts), and the structured path
|
|
258
273
|
// exits 0 while carrying the evidence, so a field a re-judge never sees
|
|
259
274
|
// leaves it unrecoverable from the saved transcript.
|
|
260
|
-
|
|
275
|
+
// The qualified settlement contract can prove a successful retry. An
|
|
276
|
+
// earlier transport diagnostic alone must not override that final result.
|
|
277
|
+
if (providerFailure === null && r.providerFailure && r.trace.final_status !== "complete") {
|
|
261
278
|
providerFailure = r.providerFailure;
|
|
279
|
+
}
|
|
262
280
|
const pricedTrace = priceSubjectUsage(r.trace);
|
|
263
281
|
traces.push(pricedTrace);
|
|
264
282
|
parts.push(header(i + 1, total, req.turns[i]));
|
|
265
|
-
parts.push(`<<< ASSISTANT:\n${pricedTrace.final_text
|
|
283
|
+
parts.push(`<<< ASSISTANT:\n${pricedTrace.final_text}\n`);
|
|
266
284
|
if (r.code !== 0)
|
|
267
285
|
parts.push(`[pi exited ${r.code} on turn ${i + 1}]\n${r.stderr.trim()}\n`);
|
|
286
|
+
if (executionFailure || providerFailure)
|
|
287
|
+
break;
|
|
268
288
|
}
|
|
269
289
|
return {
|
|
270
|
-
transcript: withProviderFailure(parts.join("\n"), providerFailure),
|
|
290
|
+
transcript: withProviderFailure(withExecutionFailure(parts.join("\n"), executionFailure), providerFailure),
|
|
271
291
|
traces,
|
|
272
292
|
...(providerFailure ? { providerFailure } : {}),
|
|
293
|
+
...(executionFailure ? { executionFailure } : {}),
|
|
273
294
|
};
|
|
274
295
|
},
|
|
275
296
|
/** Run the judge through Pi: no skills, context files, extensions or session. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skill-harness/adapters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.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.24.0"
|
|
46
46
|
}
|
|
47
47
|
}
|