skill-harness 0.25.1 → 0.26.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/index.js +350 -34
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -10234,8 +10234,8 @@ function normalizePiDaddyRecordLedgerV12(raw) {
|
|
|
10234
10234
|
const record = parsed;
|
|
10235
10235
|
if (record.seq !== index + 1) throw new Error(`${where}: sequence gap`);
|
|
10236
10236
|
if (record.prev !== previous) throw new Error(`${where}: previous line hash mismatch`);
|
|
10237
|
-
const { digest:
|
|
10238
|
-
if (sha4(canonical3(unsigned)) !==
|
|
10237
|
+
const { digest: digest3, ...unsigned } = record;
|
|
10238
|
+
if (sha4(canonical3(unsigned)) !== digest3) throw new Error(`${where}: record digest mismatch`);
|
|
10239
10239
|
if (ids.has(record.id)) throw new Error(`${where}: duplicate record identity`);
|
|
10240
10240
|
ids.add(record.id);
|
|
10241
10241
|
validate2(PI_DADDY_RECORD_V1_GOVERNANCE_SCHEMA2, record.body, `${where} governance body`);
|
|
@@ -12933,7 +12933,7 @@ function normalizeLegacyGrant(record, index) {
|
|
|
12933
12933
|
const effective = record.effective;
|
|
12934
12934
|
const denied = record.denied;
|
|
12935
12935
|
const gated = record.gatedBlocked;
|
|
12936
|
-
const
|
|
12936
|
+
const digest3 = object3(record.definitionDigest);
|
|
12937
12937
|
const common2 = {
|
|
12938
12938
|
event_version: TRAJECTORY_EVENT_VERSION,
|
|
12939
12939
|
source: "pi-daddy-0.17",
|
|
@@ -12952,7 +12952,7 @@ function normalizeLegacyGrant(record, index) {
|
|
|
12952
12952
|
gate_outcome: record.gateOutcome,
|
|
12953
12953
|
human_denied: record.humanDenied === true,
|
|
12954
12954
|
reason: record.reason,
|
|
12955
|
-
definition_name:
|
|
12955
|
+
definition_name: digest3?.name,
|
|
12956
12956
|
legacy_schema: "pi-daddy-grant-ledger/0.17"
|
|
12957
12957
|
});
|
|
12958
12958
|
const refusal = record.blocked ? legacyRefusalCode(record) : void 0;
|
|
@@ -12962,7 +12962,7 @@ function normalizeLegacyGrant(record, index) {
|
|
|
12962
12962
|
requested_capabilities: requested,
|
|
12963
12963
|
effective_capabilities: effective,
|
|
12964
12964
|
refusal_code: refusal,
|
|
12965
|
-
digests: anyDefined({ definition: string(
|
|
12965
|
+
digests: anyDefined({ definition: string(digest3?.sha256) }),
|
|
12966
12966
|
attributes
|
|
12967
12967
|
});
|
|
12968
12968
|
const events = [
|
|
@@ -13570,7 +13570,7 @@ var init_src = __esm({
|
|
|
13570
13570
|
|
|
13571
13571
|
// packages/pi-extension/src/index.ts
|
|
13572
13572
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
13573
|
-
import { basename as basename4, dirname as dirname10, join as
|
|
13573
|
+
import { basename as basename4, dirname as dirname10, join as join35 } from "node:path";
|
|
13574
13574
|
|
|
13575
13575
|
// packages/pi-extension/src/commands.ts
|
|
13576
13576
|
init_dist();
|
|
@@ -15960,7 +15960,7 @@ async function runViaExtension(opts) {
|
|
|
15960
15960
|
}
|
|
15961
15961
|
|
|
15962
15962
|
// packages/pi-extension/src/commands.ts
|
|
15963
|
-
var USAGE = "usage: /skill-harness run [skill] [--model p:m] [--reps N] [--mode red|green|force] [--canary] [--judge p:m] | judge [run-dir] | review [skill] | coverage [skill] | jev enable|run|status|disable";
|
|
15963
|
+
var USAGE = "usage: /skill-harness run [skill] [--model p:m] [--reps N] [--mode red|green|force] [--canary] [--judge p:m] | judge [run-dir] | review [skill] | coverage [skill] | jev enable [workflow]|run|status|disable";
|
|
15964
15964
|
function parse(argstr) {
|
|
15965
15965
|
const tokens = argstr.trim().length ? argstr.trim().split(/\s+/) : [];
|
|
15966
15966
|
const [sub = "", ...rest] = tokens;
|
|
@@ -17368,7 +17368,7 @@ var RESEARCH_OPTIONAL = {
|
|
|
17368
17368
|
"run-pi": ["pi-node", "auth-path", "timeout-ms"]
|
|
17369
17369
|
};
|
|
17370
17370
|
async function researchCommand(command, flags, helpers, options) {
|
|
17371
|
-
const { cases, jsonFile: jsonFile2, textFile: textFile2, writeNew:
|
|
17371
|
+
const { cases, jsonFile: jsonFile2, textFile: textFile2, writeNew: writeNew3, readLegacyRun } = helpers;
|
|
17372
17372
|
const { emit = console.log, piRunner } = options;
|
|
17373
17373
|
if (command === "fixtures") {
|
|
17374
17374
|
const f = createLearningFixtures({});
|
|
@@ -17387,7 +17387,7 @@ async function researchCommand(command, flags, helpers, options) {
|
|
|
17387
17387
|
"label-evidence.json": refs,
|
|
17388
17388
|
"consents.json": []
|
|
17389
17389
|
}))
|
|
17390
|
-
await
|
|
17390
|
+
await writeNew3(join32(dir, name), value);
|
|
17391
17391
|
emit(
|
|
17392
17392
|
JSON.stringify({
|
|
17393
17393
|
saved: dir,
|
|
@@ -17405,7 +17405,7 @@ async function researchCommand(command, flags, helpers, options) {
|
|
|
17405
17405
|
sessionConsent: await jsonFile2(flags.consent),
|
|
17406
17406
|
experimentEntry: await jsonFile2(flags.entry)
|
|
17407
17407
|
});
|
|
17408
|
-
await
|
|
17408
|
+
await writeNew3(flags.out, result);
|
|
17409
17409
|
emit(JSON.stringify({ saved: resolve15(flags.out), trainingReady: false }));
|
|
17410
17410
|
return;
|
|
17411
17411
|
}
|
|
@@ -17502,7 +17502,7 @@ async function researchCommand(command, flags, helpers, options) {
|
|
|
17502
17502
|
}
|
|
17503
17503
|
const report = compareRuns(cases, labels, manifest, runs);
|
|
17504
17504
|
report.labelEvidenceReview = evidence;
|
|
17505
|
-
await
|
|
17505
|
+
await writeNew3(flags.out, report);
|
|
17506
17506
|
emit(
|
|
17507
17507
|
JSON.stringify({
|
|
17508
17508
|
saved: resolve15(flags.out),
|
|
@@ -17690,8 +17690,9 @@ function normalize3(payload) {
|
|
|
17690
17690
|
}
|
|
17691
17691
|
function deadline(signal) {
|
|
17692
17692
|
return new Promise((_, reject) => {
|
|
17693
|
-
|
|
17694
|
-
signal.
|
|
17693
|
+
const fail = () => reject(signal.reason instanceof Error ? signal.reason : new Error("provider request timed out"));
|
|
17694
|
+
if (signal.aborted) return fail();
|
|
17695
|
+
signal.addEventListener("abort", fail, { once: true });
|
|
17695
17696
|
});
|
|
17696
17697
|
}
|
|
17697
17698
|
async function readBounded(response, timedOut) {
|
|
@@ -17727,7 +17728,7 @@ async function readBounded(response, timedOut) {
|
|
|
17727
17728
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
17728
17729
|
}
|
|
17729
17730
|
function failure(startedAt, error) {
|
|
17730
|
-
const publicErrors = ["invalid provider response", "provider request timed out", "provider response exceeded limit"];
|
|
17731
|
+
const publicErrors = ["invalid provider response", "provider request timed out", "provider response exceeded limit", "provider request cancelled"];
|
|
17731
17732
|
const message = error instanceof Error && publicErrors.includes(error.message) ? error.message : "provider request failed";
|
|
17732
17733
|
return {
|
|
17733
17734
|
status: "error",
|
|
@@ -17742,6 +17743,7 @@ async function callProvider(provider, model, example, options = {}) {
|
|
|
17742
17743
|
const startedAt = performance.now();
|
|
17743
17744
|
let controller;
|
|
17744
17745
|
let timer;
|
|
17746
|
+
let cancel;
|
|
17745
17747
|
try {
|
|
17746
17748
|
const request = makeRequest(provider, model, example);
|
|
17747
17749
|
requiredString(options.apiKey, "apiKey", 16 * 1024);
|
|
@@ -17752,8 +17754,14 @@ async function callProvider(provider, model, example, options = {}) {
|
|
|
17752
17754
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 3e5) {
|
|
17753
17755
|
throw new TypeError("invalid timeout");
|
|
17754
17756
|
}
|
|
17757
|
+
if (options.signal !== void 0 && !(options.signal instanceof AbortSignal)) {
|
|
17758
|
+
throw new TypeError("signal must be an AbortSignal");
|
|
17759
|
+
}
|
|
17760
|
+
if (options.signal?.aborted) throw new Error("provider request cancelled");
|
|
17755
17761
|
controller = new AbortController();
|
|
17756
|
-
|
|
17762
|
+
cancel = () => controller.abort(new Error("provider request cancelled"));
|
|
17763
|
+
options.signal?.addEventListener("abort", cancel, { once: true });
|
|
17764
|
+
timer = setTimeout(() => controller.abort(new Error("provider request timed out")), timeoutMs);
|
|
17757
17765
|
const timedOut = deadline(controller.signal);
|
|
17758
17766
|
const response = await Promise.race([
|
|
17759
17767
|
(options.fetchImpl ?? fetch)(request.url, {
|
|
@@ -17778,6 +17786,7 @@ async function callProvider(provider, model, example, options = {}) {
|
|
|
17778
17786
|
return failure(startedAt, error);
|
|
17779
17787
|
} finally {
|
|
17780
17788
|
if (timer !== void 0) clearTimeout(timer);
|
|
17789
|
+
if (cancel) options.signal?.removeEventListener("abort", cancel);
|
|
17781
17790
|
controller?.abort();
|
|
17782
17791
|
}
|
|
17783
17792
|
}
|
|
@@ -18630,33 +18639,264 @@ async function main(argv, {
|
|
|
18630
18639
|
);
|
|
18631
18640
|
}
|
|
18632
18641
|
|
|
18642
|
+
// packages/pi-extension/src/jev-packet.ts
|
|
18643
|
+
import { createHash as createHash19 } from "node:crypto";
|
|
18644
|
+
import { closeSync as closeSync2, fsyncSync as fsyncSync2, lstatSync, mkdirSync as mkdirSync5, mkdtempSync as mkdtempSync4, openSync as openSync2, writeFileSync as writeFileSync8 } from "node:fs";
|
|
18645
|
+
import { homedir as homedir6 } from "node:os";
|
|
18646
|
+
import { join as join34 } from "node:path";
|
|
18647
|
+
var JEV_PROVIDER = "jev";
|
|
18648
|
+
var JEV_MODEL = "typesafe/jev-1.13";
|
|
18649
|
+
var JEV_WORKFLOW_LIMIT = 3;
|
|
18650
|
+
var JEV_QUESTION = "Does this handoff account for every explicitly required acceptance check with successful evidence tied to the reported candidate?";
|
|
18651
|
+
var JEV_SOURCE_KIND = "skill-harness-selected-handoff-v1";
|
|
18652
|
+
var digest2 = (text3) => createHash19("sha256").update(text3, "utf8").digest("hex");
|
|
18653
|
+
function requireIdentity(value, name) {
|
|
18654
|
+
if (typeof value !== "string" || !value.trim() || value.length > 256)
|
|
18655
|
+
throw new Error(`${name} must be a nonempty runtime identity of at most 256 characters`);
|
|
18656
|
+
}
|
|
18657
|
+
function prepareHandoff(value) {
|
|
18658
|
+
for (const field of ["candidate", "requirements", "evidence"]) {
|
|
18659
|
+
if (typeof value[field] !== "string" || !value[field].trim() || value[field].length > 32e3)
|
|
18660
|
+
throw new Error(`${field} must be a nonempty bounded string`);
|
|
18661
|
+
if (/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(value[field]))
|
|
18662
|
+
throw new Error(`${field} must contain valid Unicode`);
|
|
18663
|
+
}
|
|
18664
|
+
const input = JSON.stringify({
|
|
18665
|
+
candidate: value.candidate,
|
|
18666
|
+
requirements: value.requirements,
|
|
18667
|
+
evidence: value.evidence
|
|
18668
|
+
});
|
|
18669
|
+
if (Array.from(input).length > 16e3)
|
|
18670
|
+
throw new Error("The complete selected handoff packet must not exceed 16000 characters");
|
|
18671
|
+
return Object.freeze({ input, question: JEV_QUESTION, inputSha256: digest2(input) });
|
|
18672
|
+
}
|
|
18673
|
+
function createHandoffSource(options) {
|
|
18674
|
+
requireIdentity(options.sessionId, "Pi sessionId");
|
|
18675
|
+
requireIdentity(options.toolCallId, "Pi toolCallId");
|
|
18676
|
+
const source = {
|
|
18677
|
+
schema: 1,
|
|
18678
|
+
kind: JEV_SOURCE_KIND,
|
|
18679
|
+
sessionId: options.sessionId,
|
|
18680
|
+
toolCallId: options.toolCallId,
|
|
18681
|
+
frozenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18682
|
+
input: options.packet.input,
|
|
18683
|
+
inputSha256: options.packet.inputSha256,
|
|
18684
|
+
question: JEV_QUESTION,
|
|
18685
|
+
provider: JEV_PROVIDER,
|
|
18686
|
+
model: JEV_MODEL,
|
|
18687
|
+
consent: options.consent,
|
|
18688
|
+
authorization: options.authorization,
|
|
18689
|
+
provenance: "tool-selected-input",
|
|
18690
|
+
candidateIdentity: "caller-claimed",
|
|
18691
|
+
sourceBinding: "unassessed",
|
|
18692
|
+
redaction: "unassessed",
|
|
18693
|
+
rights: "unassessed",
|
|
18694
|
+
labelStatus: "unlabeled",
|
|
18695
|
+
trainingEligible: false,
|
|
18696
|
+
exportEligible: false,
|
|
18697
|
+
publicCaptureVerified: false
|
|
18698
|
+
};
|
|
18699
|
+
const bytes = JSON.stringify(source, null, 2) + "\n";
|
|
18700
|
+
return Object.freeze({ source, bytes, sha256: digest2(bytes) });
|
|
18701
|
+
}
|
|
18702
|
+
function privateDirectory(path) {
|
|
18703
|
+
try {
|
|
18704
|
+
mkdirSync5(path, { mode: 448 });
|
|
18705
|
+
} catch (error) {
|
|
18706
|
+
if (error.code !== "EEXIST") throw error;
|
|
18707
|
+
}
|
|
18708
|
+
const stat4 = lstatSync(path);
|
|
18709
|
+
if (!stat4.isDirectory() || stat4.isSymbolicLink())
|
|
18710
|
+
throw new Error("JEV storage must be a private directory, not a link");
|
|
18711
|
+
if (process.platform !== "win32" && ((stat4.mode & 63) !== 0 || process.getuid && stat4.uid !== process.getuid()))
|
|
18712
|
+
throw new Error("JEV storage directory must be owned by the current user with mode 0700");
|
|
18713
|
+
}
|
|
18714
|
+
function writeNew2(path, bytes) {
|
|
18715
|
+
const fd = openSync2(path, "wx", 384);
|
|
18716
|
+
try {
|
|
18717
|
+
writeFileSync8(fd, bytes);
|
|
18718
|
+
fsyncSync2(fd);
|
|
18719
|
+
} finally {
|
|
18720
|
+
closeSync2(fd);
|
|
18721
|
+
}
|
|
18722
|
+
}
|
|
18723
|
+
function retainHandoffSource(source, assertCurrent, storageHome = join34(homedir6(), ".skill-harness")) {
|
|
18724
|
+
assertCurrent();
|
|
18725
|
+
privateDirectory(storageHome);
|
|
18726
|
+
const root = join34(storageHome, "jev-workflow");
|
|
18727
|
+
privateDirectory(root);
|
|
18728
|
+
const directory = mkdtempSync4(join34(root, "selection-"));
|
|
18729
|
+
const path = join34(directory, "selection.json");
|
|
18730
|
+
writeNew2(path, source.bytes);
|
|
18731
|
+
return {
|
|
18732
|
+
path,
|
|
18733
|
+
writeOutcome(outcome) {
|
|
18734
|
+
assertCurrent();
|
|
18735
|
+
writeNew2(join34(directory, "outcome.json"), JSON.stringify({
|
|
18736
|
+
schema: 1,
|
|
18737
|
+
kind: "skill-harness-handoff-advice-v1",
|
|
18738
|
+
source: { path: "selection.json", sha256: source.sha256 },
|
|
18739
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18740
|
+
outcome,
|
|
18741
|
+
trainingEligible: false,
|
|
18742
|
+
exportEligible: false,
|
|
18743
|
+
labelStatus: "unlabeled"
|
|
18744
|
+
}, null, 2) + "\n");
|
|
18745
|
+
}
|
|
18746
|
+
};
|
|
18747
|
+
}
|
|
18748
|
+
|
|
18633
18749
|
// packages/pi-extension/src/jev-session.ts
|
|
18634
|
-
|
|
18750
|
+
var unmeasuredProvider = () => ({
|
|
18751
|
+
provider: JEV_PROVIDER,
|
|
18752
|
+
requestedModel: JEV_MODEL,
|
|
18753
|
+
resolvedModel: null,
|
|
18754
|
+
usage: { inputTokens: null, outputTokens: null, costUsd: null },
|
|
18755
|
+
latencyMs: null
|
|
18756
|
+
});
|
|
18757
|
+
function createJevController(pi, options = {}) {
|
|
18758
|
+
const run = options.run ?? main;
|
|
18635
18759
|
let epoch = 0;
|
|
18636
18760
|
let active = null;
|
|
18637
18761
|
const clearSession = () => {
|
|
18638
18762
|
epoch++;
|
|
18763
|
+
active?.abort.abort();
|
|
18639
18764
|
active = null;
|
|
18640
18765
|
};
|
|
18641
18766
|
pi.on("session_start", clearSession);
|
|
18642
18767
|
pi.on("session_shutdown", clearSession);
|
|
18643
|
-
|
|
18768
|
+
const currentId = (ctx) => {
|
|
18769
|
+
const id = ctx.sessionManager?.getSessionId();
|
|
18770
|
+
if (active && active.sessionId !== id) clearSession();
|
|
18771
|
+
return id;
|
|
18772
|
+
};
|
|
18773
|
+
const hasKey = () => !!(options.env ?? process.env).OPENROUTER_API_KEY?.trim();
|
|
18774
|
+
const status = (ctx) => {
|
|
18775
|
+
const id = currentId(ctx);
|
|
18776
|
+
const availability = !id ? "missing-session" : !active ? "disabled" : active.mode === "manual" ? "manual-only" : active.blocked ?? (active.busy ? "busy" : active.used >= JEV_WORKFLOW_LIMIT ? "limit-reached" : !hasKey() ? "missing-key" : "ready");
|
|
18777
|
+
return {
|
|
18778
|
+
enabled: active !== null,
|
|
18779
|
+
mode: active?.mode ?? "disabled",
|
|
18780
|
+
storage: active?.consent.decision ?? null,
|
|
18781
|
+
remaining: active?.mode === "workflow" ? JEV_WORKFLOW_LIMIT - active.used : 0,
|
|
18782
|
+
availability,
|
|
18783
|
+
advisory: true
|
|
18784
|
+
};
|
|
18785
|
+
};
|
|
18786
|
+
const evaluate = async (value, toolCallId, signal, ctx) => {
|
|
18787
|
+
requireIdentity(toolCallId, "Pi toolCallId");
|
|
18788
|
+
const packet = prepareHandoff(value);
|
|
18789
|
+
const id = currentId(ctx);
|
|
18790
|
+
const unavailable = (reason) => ({
|
|
18791
|
+
advisory: true,
|
|
18792
|
+
status: "unavailable",
|
|
18793
|
+
probability: null,
|
|
18794
|
+
reason,
|
|
18795
|
+
...unmeasuredProvider(),
|
|
18796
|
+
remaining: active?.mode === "workflow" ? JEV_WORKFLOW_LIMIT - active.used : 0
|
|
18797
|
+
});
|
|
18798
|
+
if (!id || !active || active.mode !== "workflow")
|
|
18799
|
+
return unavailable("Explicit /skill-harness jev enable workflow activation is required in this Pi session.");
|
|
18800
|
+
const bound = active;
|
|
18801
|
+
const assertCurrent = () => {
|
|
18802
|
+
if (active !== bound || ctx.sessionManager?.getSessionId() !== bound.sessionId || bound.abort.signal.aborted || signal?.aborted)
|
|
18803
|
+
throw new Error("Session, authorization or tool execution changed");
|
|
18804
|
+
};
|
|
18805
|
+
if (signal?.aborted) return unavailable("Tool execution was cancelled.");
|
|
18806
|
+
const cached = bound.cache.get(packet.inputSha256);
|
|
18807
|
+
if (cached) return { ...cached, remaining: JEV_WORKFLOW_LIMIT - bound.used, reused: true };
|
|
18808
|
+
if (bound.blocked) return unavailable("Workflow advice is suppressed after an error; explicit new activation is required.");
|
|
18809
|
+
if (bound.busy) return unavailable("An advisory request is already in flight; no additional call was made.");
|
|
18810
|
+
if (bound.used >= JEV_WORKFLOW_LIMIT) return unavailable("This activation's advisory call limit is reached.");
|
|
18811
|
+
if (!hasKey()) return unavailable("OPENROUTER_API_KEY is unavailable; no call was made.");
|
|
18812
|
+
const abort = new AbortController();
|
|
18813
|
+
const cancel = () => abort.abort();
|
|
18814
|
+
const signals = [bound.abort.signal, signal].filter((s) => s !== void 0);
|
|
18815
|
+
for (const s of signals) {
|
|
18816
|
+
if (s.aborted) cancel();
|
|
18817
|
+
else s.addEventListener("abort", cancel, { once: true });
|
|
18818
|
+
}
|
|
18819
|
+
bound.busy = true;
|
|
18820
|
+
bound.used++;
|
|
18821
|
+
let receipt;
|
|
18822
|
+
try {
|
|
18823
|
+
assertCurrent();
|
|
18824
|
+
const source = createHandoffSource({
|
|
18825
|
+
packet,
|
|
18826
|
+
sessionId: bound.sessionId,
|
|
18827
|
+
toolCallId,
|
|
18828
|
+
consent: bound.consent,
|
|
18829
|
+
authorization: bound.authorization
|
|
18830
|
+
});
|
|
18831
|
+
const retained = bound.consent.decision === "granted" ? retainHandoffSource(source, assertCurrent, options.storageHome) : void 0;
|
|
18832
|
+
receipt = {
|
|
18833
|
+
kind: JEV_SOURCE_KIND,
|
|
18834
|
+
sha256: source.sha256,
|
|
18835
|
+
sessionId: bound.sessionId,
|
|
18836
|
+
toolCallId,
|
|
18837
|
+
retained: !!retained,
|
|
18838
|
+
...retained ? { path: retained.path } : {}
|
|
18839
|
+
};
|
|
18840
|
+
assertCurrent();
|
|
18841
|
+
const result = await (options.provider ?? callProvider)(JEV_PROVIDER, JEV_MODEL, packet, {
|
|
18842
|
+
apiKey: (options.env ?? process.env).OPENROUTER_API_KEY,
|
|
18843
|
+
signal: abort.signal
|
|
18844
|
+
});
|
|
18845
|
+
assertCurrent();
|
|
18846
|
+
if (result.status !== "answered") bound.blocked = "provider-error";
|
|
18847
|
+
const advice = {
|
|
18848
|
+
advisory: true,
|
|
18849
|
+
status: result.status === "answered" ? "answered" : "unavailable",
|
|
18850
|
+
probability: result.status === "answered" ? result.probability : null,
|
|
18851
|
+
provider: JEV_PROVIDER,
|
|
18852
|
+
requestedModel: JEV_MODEL,
|
|
18853
|
+
resolvedModel: result.resolvedModel,
|
|
18854
|
+
usage: result.usage,
|
|
18855
|
+
latencyMs: result.latencyMs,
|
|
18856
|
+
...result.status === "answered" ? {} : { reason: "Provider unavailable; further workflow calls require explicit new activation." },
|
|
18857
|
+
remaining: JEV_WORKFLOW_LIMIT - bound.used,
|
|
18858
|
+
inputSha256: packet.inputSha256,
|
|
18859
|
+
source: receipt
|
|
18860
|
+
};
|
|
18861
|
+
try {
|
|
18862
|
+
retained?.writeOutcome(advice);
|
|
18863
|
+
} catch {
|
|
18864
|
+
assertCurrent();
|
|
18865
|
+
bound.blocked = "storage-error";
|
|
18866
|
+
advice.reason = "Provider outcome received, but outcome retention failed; further workflow calls require explicit new activation.";
|
|
18867
|
+
}
|
|
18868
|
+
bound.cache.set(packet.inputSha256, Object.freeze(advice));
|
|
18869
|
+
return advice;
|
|
18870
|
+
} catch {
|
|
18871
|
+
bound.blocked = "execution-error";
|
|
18872
|
+
return {
|
|
18873
|
+
advisory: true,
|
|
18874
|
+
status: "unavailable",
|
|
18875
|
+
probability: null,
|
|
18876
|
+
...unmeasuredProvider(),
|
|
18877
|
+
reason: "Advice unavailable or cancelled; no approval is implied. Explicit new activation is required for further workflow calls.",
|
|
18878
|
+
remaining: JEV_WORKFLOW_LIMIT - bound.used,
|
|
18879
|
+
inputSha256: packet.inputSha256,
|
|
18880
|
+
...receipt ? { source: receipt } : {}
|
|
18881
|
+
};
|
|
18882
|
+
} finally {
|
|
18883
|
+
bound.busy = false;
|
|
18884
|
+
for (const s of signals) s.removeEventListener("abort", cancel);
|
|
18885
|
+
}
|
|
18886
|
+
};
|
|
18887
|
+
const command = async (args, ctx) => {
|
|
18644
18888
|
const action = args.trim() || "status";
|
|
18645
|
-
const sessionId = ctx
|
|
18889
|
+
const sessionId = currentId(ctx);
|
|
18646
18890
|
if (!sessionId) throw Error("JEV requires the current Pi session identity");
|
|
18647
|
-
|
|
18648
|
-
epoch++;
|
|
18649
|
-
active = null;
|
|
18650
|
-
}
|
|
18891
|
+
requireIdentity(sessionId, "Pi sessionId");
|
|
18651
18892
|
if (action === "status") {
|
|
18652
18893
|
ctx.ui.notify(
|
|
18653
|
-
|
|
18894
|
+
JSON.stringify(status(ctx))
|
|
18654
18895
|
);
|
|
18655
18896
|
return;
|
|
18656
18897
|
}
|
|
18657
18898
|
if (action === "disable") {
|
|
18658
|
-
|
|
18659
|
-
active = null;
|
|
18899
|
+
clearSession();
|
|
18660
18900
|
ctx.ui.notify(
|
|
18661
18901
|
"JEV disabled. Existing selected records remain local; disable storage in their review before export if needed."
|
|
18662
18902
|
);
|
|
@@ -18666,15 +18906,35 @@ function createJevSessionHandler(pi, run = main) {
|
|
|
18666
18906
|
throw Error(
|
|
18667
18907
|
"Interactive JEV activation requires a per-session storage answer. Use the explicit CLI storage option in noninteractive mode."
|
|
18668
18908
|
);
|
|
18669
|
-
if (action === "enable") {
|
|
18670
|
-
|
|
18671
|
-
|
|
18909
|
+
if (action === "enable" || action === "enable workflow") {
|
|
18910
|
+
clearSession();
|
|
18911
|
+
const activation = epoch;
|
|
18912
|
+
const workflow = action === "enable workflow";
|
|
18913
|
+
let authorization = null;
|
|
18914
|
+
if (workflow) {
|
|
18915
|
+
if (!ctx.ui.confirm) throw new Error("Workflow JEV activation requires interactive paid-scope confirmation");
|
|
18916
|
+
const confirmed = await ctx.ui.confirm(
|
|
18917
|
+
"Enable optional JEV handoff advice for THIS Pi session?",
|
|
18918
|
+
`Allow up to ${JEV_WORKFLOW_LIMIT} automatic workflow paid calls to ${JEV_MODEL} through OpenRouter using OPENROUTER_API_KEY. The coordinator may send selected candidate, acceptance requirements and evidence (at most 16000 characters per packet) for the fixed handoff-readiness question when uncertainty remains after deterministic checks, before independent review. Advice never grants approval. This is separate from your Pi subscription; no retry or fallback. Manual jev run calls remain separately confirmed outside this workflow limit.`
|
|
18919
|
+
);
|
|
18920
|
+
if (activation !== epoch || ctx.sessionManager?.getSessionId() !== sessionId || confirmed !== true) return;
|
|
18921
|
+
authorization = {
|
|
18922
|
+
kind: "jev-workflow-paid-scope",
|
|
18923
|
+
interactionId: randomUUID3(),
|
|
18924
|
+
sessionId,
|
|
18925
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18926
|
+
provider: JEV_PROVIDER,
|
|
18927
|
+
model: JEV_MODEL,
|
|
18928
|
+
question: JEV_QUESTION,
|
|
18929
|
+
maximumCalls: JEV_WORKFLOW_LIMIT
|
|
18930
|
+
};
|
|
18931
|
+
}
|
|
18672
18932
|
const choices = [
|
|
18673
18933
|
"No \u2014 use JEV without retaining data for LoRA",
|
|
18674
18934
|
"Yes \u2014 retain selected decision data for later LoRA review"
|
|
18675
18935
|
];
|
|
18676
18936
|
const answer = await ctx.ui.select(
|
|
18677
|
-
"Store selected data from THIS session for future LoRA dataset review? Storage does not grant training permission.",
|
|
18937
|
+
"Store selected data from THIS session for future LoRA dataset review? Workflow selections are saved under ~/.skill-harness/jev-workflow. Storage does not grant training permission; No does not change Pi native session history.",
|
|
18678
18938
|
choices
|
|
18679
18939
|
);
|
|
18680
18940
|
if (activation !== epoch || ctx.sessionManager?.getSessionId() !== sessionId)
|
|
@@ -18687,14 +18947,24 @@ function createJevSessionHandler(pi, run = main) {
|
|
|
18687
18947
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18688
18948
|
});
|
|
18689
18949
|
pi.appendEntry?.("skill-harness-jev-storage-choice", consent);
|
|
18690
|
-
active = {
|
|
18950
|
+
active = {
|
|
18951
|
+
sessionId,
|
|
18952
|
+
consent,
|
|
18953
|
+
authorization,
|
|
18954
|
+
mode: workflow ? "workflow" : "manual",
|
|
18955
|
+
abort: new AbortController(),
|
|
18956
|
+
used: 0,
|
|
18957
|
+
busy: false,
|
|
18958
|
+
blocked: null,
|
|
18959
|
+
cache: /* @__PURE__ */ new Map()
|
|
18960
|
+
};
|
|
18691
18961
|
ctx.ui.notify(
|
|
18692
|
-
"JEV enabled for explicitly selected paid calls. LoRA storage " + consent.decision + " for this session only."
|
|
18962
|
+
(workflow ? `JEV workflow advice enabled (up to ${JEV_WORKFLOW_LIMIT} paid calls). LoRA storage ` : "JEV enabled for explicitly selected paid calls. LoRA storage ") + consent.decision + " for this session only."
|
|
18693
18963
|
);
|
|
18694
18964
|
return;
|
|
18695
18965
|
}
|
|
18696
18966
|
if (action !== "run")
|
|
18697
|
-
throw Error("usage: /skill-harness jev enable | status | disable | run");
|
|
18967
|
+
throw Error("usage: /skill-harness jev enable [workflow] | status | disable | run");
|
|
18698
18968
|
if (!active)
|
|
18699
18969
|
throw Error(
|
|
18700
18970
|
"Enable JEV in this session first: /skill-harness jev enable"
|
|
@@ -18784,13 +19054,59 @@ function createJevSessionHandler(pi, run = main) {
|
|
|
18784
19054
|
}
|
|
18785
19055
|
);
|
|
18786
19056
|
};
|
|
19057
|
+
return { command, status, evaluate };
|
|
19058
|
+
}
|
|
19059
|
+
|
|
19060
|
+
// packages/pi-extension/src/jev-advice.ts
|
|
19061
|
+
import { Type as Type3 } from "typebox";
|
|
19062
|
+
function createJevAdviceTool(controller) {
|
|
19063
|
+
return {
|
|
19064
|
+
name: "jev_advice",
|
|
19065
|
+
label: "JEV handoff advice",
|
|
19066
|
+
description: "Check session-local JEV workflow status without a provider call, or evaluate selected handoff evidence against the fixed acceptance-readiness question. Evaluation requires the user's explicit /skill-harness jev enable workflow activation. Advisory probability never grants approval or replaces tests/review. Select only relevant public or already-redacted evidence; do not include secrets, transcripts, reviewer verdicts or hidden reasoning.",
|
|
19067
|
+
promptGuidelines: [
|
|
19068
|
+
"Use jev_advice action=status to discover whether JEV workflow advice is enabled.",
|
|
19069
|
+
"When enabled and useful uncertainty remains after deterministic checks, optionally evaluate a selected handoff before independent review. Do not call for every feature or retry unavailable advice.",
|
|
19070
|
+
"Keep JEV advice out of the independent reviewer's inputs; act on independently verified issues rather than treating its probability as a verdict."
|
|
19071
|
+
],
|
|
19072
|
+
parameters: Type3.Object({
|
|
19073
|
+
action: Type3.Union([Type3.Literal("status"), Type3.Literal("evaluate")]),
|
|
19074
|
+
candidate: Type3.Optional(Type3.String({ minLength: 1, maxLength: 16e3, description: "Reported candidate identity; this is a claim, not an authenticated Git binding." })),
|
|
19075
|
+
requirements: Type3.Optional(Type3.String({ minLength: 1, maxLength: 16e3, description: "Explicit acceptance checks, before seeing independent review or JEV outcomes." })),
|
|
19076
|
+
evidence: Type3.Optional(Type3.String({ minLength: 1, maxLength: 16e3, description: "Selected decision-time handoff evidence. Complete JSON packet is limited to 16000 characters." }))
|
|
19077
|
+
}, { additionalProperties: false }),
|
|
19078
|
+
async execute(id, params, signal, _onUpdate, ctx) {
|
|
19079
|
+
requireIdentity(id, "Pi toolCallId");
|
|
19080
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("Invalid jev_advice parameters");
|
|
19081
|
+
const p = params;
|
|
19082
|
+
const expected = p.action === "status" ? ["action"] : ["action", "candidate", "requirements", "evidence"];
|
|
19083
|
+
if (Object.keys(p).length !== expected.length || expected.some((key) => !Object.hasOwn(p, key)))
|
|
19084
|
+
throw new Error("jev_advice accepts only its documented status or evaluate fields");
|
|
19085
|
+
let details;
|
|
19086
|
+
if (p.action === "status") details = controller.status(ctx);
|
|
19087
|
+
else if (p.action === "evaluate")
|
|
19088
|
+
details = await controller.evaluate(
|
|
19089
|
+
{ candidate: p.candidate, requirements: p.requirements, evidence: p.evidence },
|
|
19090
|
+
id,
|
|
19091
|
+
signal,
|
|
19092
|
+
ctx
|
|
19093
|
+
);
|
|
19094
|
+
else throw new Error("jev_advice action must be status or evaluate");
|
|
19095
|
+
return { content: [{ type: "text", text: JSON.stringify(details) }], details };
|
|
19096
|
+
}
|
|
19097
|
+
};
|
|
19098
|
+
}
|
|
19099
|
+
function registerJevAdvice(pi, controller) {
|
|
19100
|
+
pi.registerTool(createJevAdviceTool(controller));
|
|
18787
19101
|
}
|
|
18788
19102
|
|
|
18789
19103
|
// packages/pi-extension/src/index.ts
|
|
18790
19104
|
function index_default(pi) {
|
|
18791
19105
|
const moduleDir = dirname10(fileURLToPath2(import.meta.url));
|
|
18792
|
-
const assetsDir = basename4(dirname10(moduleDir)) === "skill-harness" ?
|
|
18793
|
-
|
|
19106
|
+
const assetsDir = basename4(dirname10(moduleDir)) === "skill-harness" ? join35(moduleDir, "..", "assets") : join35(moduleDir, "..", "..", "..", "assets");
|
|
19107
|
+
const jev = createJevController(pi);
|
|
19108
|
+
registerCommand(pi, assetsDir, jev.command);
|
|
19109
|
+
registerJevAdvice(pi, jev);
|
|
18794
19110
|
registerTool(pi);
|
|
18795
19111
|
pi.on("session_shutdown", async () => {
|
|
18796
19112
|
closeReview();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skill-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Test/optimize loop for agent skills \u2014 run spec'd scenarios on pi, LLM-judge, score, review, re-run",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@skill-harness/cli": "0.
|
|
34
|
+
"@skill-harness/cli": "0.26.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"typebox": "*"
|