@testmuai/rook 0.1.2 → 0.1.3
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/cli.js +1084 -732
- package/dist/cli.js.map +1 -1
- package/package.json +1 -2
package/dist/cli.js
CHANGED
|
@@ -499,7 +499,7 @@ var VERSION, SHA, VERSION_LABEL, CLIENT_NAME, PUBLIC_DOCS_URL, PUBLIC_UPDATE_COM
|
|
|
499
499
|
var init_constants = __esm({
|
|
500
500
|
"src/constants.ts"() {
|
|
501
501
|
"use strict";
|
|
502
|
-
VERSION = true ? "0.1.
|
|
502
|
+
VERSION = true ? "0.1.3" : "0.0.0-dev";
|
|
503
503
|
SHA = /^[0-9a-f]{40}$/;
|
|
504
504
|
VERSION_LABEL = SHA.test(VERSION) ? VERSION.slice(0, 7) : VERSION.includes("-") ? VERSION : `${VERSION}-alpha`;
|
|
505
505
|
CLIENT_NAME = "ROOK";
|
|
@@ -19494,8 +19494,26 @@ var init_drift = __esm({
|
|
|
19494
19494
|
});
|
|
19495
19495
|
|
|
19496
19496
|
// src/run/materialise.ts
|
|
19497
|
-
import {
|
|
19498
|
-
|
|
19497
|
+
import {
|
|
19498
|
+
cpSync,
|
|
19499
|
+
existsSync as existsSync31,
|
|
19500
|
+
mkdirSync as mkdirSync25,
|
|
19501
|
+
readFileSync as readFileSync34,
|
|
19502
|
+
readdirSync as readdirSync19,
|
|
19503
|
+
realpathSync as realpathSync8,
|
|
19504
|
+
rmSync as rmSync9,
|
|
19505
|
+
statSync as statSync16,
|
|
19506
|
+
writeFileSync as writeFileSync23
|
|
19507
|
+
} from "fs";
|
|
19508
|
+
import {
|
|
19509
|
+
basename as basename8,
|
|
19510
|
+
dirname as dirname21,
|
|
19511
|
+
isAbsolute as isAbsolute8,
|
|
19512
|
+
join as join36,
|
|
19513
|
+
relative as relative14,
|
|
19514
|
+
resolve as resolve15,
|
|
19515
|
+
sep as sep15
|
|
19516
|
+
} from "path";
|
|
19499
19517
|
import yaml10 from "js-yaml";
|
|
19500
19518
|
function scenarioDir(root, agentId, runId, scenarioId) {
|
|
19501
19519
|
return join36(runDir(root, agentId, runId), "scenarios", scenarioId);
|
|
@@ -19531,7 +19549,10 @@ function materialiseContext(root, agentId, runId, context) {
|
|
|
19531
19549
|
);
|
|
19532
19550
|
write2("agent.yaml", context.spec);
|
|
19533
19551
|
write2("features.yaml", context.features);
|
|
19534
|
-
write2(
|
|
19552
|
+
write2(
|
|
19553
|
+
"profile.yaml",
|
|
19554
|
+
readableProfile(root, resolve15(dir2, "..", ".."), context.profile)
|
|
19555
|
+
);
|
|
19535
19556
|
}
|
|
19536
19557
|
function priorWork(root, agentId, runId) {
|
|
19537
19558
|
const dir2 = join36(runDir(root, agentId, runId), "scenarios");
|
|
@@ -19551,34 +19572,99 @@ function priorWork(root, agentId, runId) {
|
|
|
19551
19572
|
}
|
|
19552
19573
|
return { decided, ungraded };
|
|
19553
19574
|
}
|
|
19554
|
-
function carryForward(root, agentId, from, to) {
|
|
19575
|
+
function carryForward(root, agentId, from, to, opts = {}) {
|
|
19555
19576
|
const src = scenarioDir(root, agentId, from.runId, from.scenarioId);
|
|
19556
19577
|
const dst = scenarioDir(root, agentId, to.runId, to.scenarioId);
|
|
19557
19578
|
if (!existsSync31(src)) return "nothing";
|
|
19558
|
-
|
|
19559
|
-
|
|
19560
|
-
|
|
19561
|
-
|
|
19562
|
-
|
|
19563
|
-
|
|
19564
|
-
|
|
19565
|
-
|
|
19566
|
-
const
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19579
|
+
const carried = () => {
|
|
19580
|
+
const take = (name) => {
|
|
19581
|
+
try {
|
|
19582
|
+
return readFileSync34(join36(src, name), "utf-8");
|
|
19583
|
+
} catch {
|
|
19584
|
+
return null;
|
|
19585
|
+
}
|
|
19586
|
+
};
|
|
19587
|
+
const put = (name, body) => {
|
|
19588
|
+
mkdirSync25(dst, { recursive: true });
|
|
19589
|
+
writeFileSync23(join36(dst, name), body, "utf-8");
|
|
19590
|
+
};
|
|
19591
|
+
const move2 = (name) => {
|
|
19592
|
+
const body = take(name);
|
|
19593
|
+
if (body === null) return false;
|
|
19594
|
+
put(name, body);
|
|
19595
|
+
return true;
|
|
19596
|
+
};
|
|
19597
|
+
const record = (body, parse2) => {
|
|
19598
|
+
if (body === null) return null;
|
|
19599
|
+
let parsed;
|
|
19600
|
+
try {
|
|
19601
|
+
parsed = parse2(body);
|
|
19602
|
+
} catch {
|
|
19603
|
+
return null;
|
|
19604
|
+
}
|
|
19605
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
19606
|
+
return null;
|
|
19607
|
+
return parsed;
|
|
19608
|
+
};
|
|
19609
|
+
const moveTree = (name, skip) => {
|
|
19610
|
+
const at = join36(src, name);
|
|
19611
|
+
if (!existsSync31(at)) return false;
|
|
19612
|
+
mkdirSync25(dst, { recursive: true });
|
|
19613
|
+
cpSync(at, join36(dst, name), {
|
|
19614
|
+
recursive: true,
|
|
19615
|
+
...skip ? { filter: (f) => basename8(f) !== skip } : {}
|
|
19616
|
+
});
|
|
19617
|
+
return true;
|
|
19618
|
+
};
|
|
19619
|
+
const response = record(take("response.json"), JSON.parse);
|
|
19620
|
+
if (!response || !judgeable(response, from.scenarioId)) return "nothing";
|
|
19621
|
+
move2("request.json");
|
|
19622
|
+
move2("output.txt");
|
|
19623
|
+
move2("hooks.json");
|
|
19624
|
+
moveTree("artifacts");
|
|
19625
|
+
const within2 = (base, file) => {
|
|
19626
|
+
const path = relative14(base, file);
|
|
19627
|
+
return path !== "" && path !== ".." && !path.startsWith(`..${sep15}`) && !isAbsolute8(path);
|
|
19628
|
+
};
|
|
19629
|
+
const artifacts = response.artifacts.map((name) => {
|
|
19630
|
+
const source = resolve15(root, name);
|
|
19631
|
+
if (!within2(join36(src, "artifacts"), source) || !within2(realpathSync8(src), realpathSync8(source)))
|
|
19632
|
+
throw new Error("artifact is outside the prior scenario's artifacts directory");
|
|
19633
|
+
const destination2 = join36(dst, relative14(src, source));
|
|
19634
|
+
if (!within2(realpathSync8(dst), realpathSync8(destination2)) || !statSync16(destination2).isFile()) throw new Error("copied artifact is missing");
|
|
19635
|
+
return relative14(root, destination2);
|
|
19636
|
+
});
|
|
19637
|
+
moveTree("hook-state");
|
|
19638
|
+
const verdict3 = opts.verdict === false ? null : record(take("verdict.yaml"), (t) => yaml10.load(t));
|
|
19639
|
+
const carriesVerdict = verdict3 !== null && completeVerdict(verdict3, from.scenarioId);
|
|
19640
|
+
moveTree("evidence", carriesVerdict ? void 0 : JUDGE_WORKING);
|
|
19641
|
+
if (carriesVerdict) {
|
|
19642
|
+
put(
|
|
19643
|
+
"verdict.yaml",
|
|
19644
|
+
yaml10.dump(verdict3, { lineWidth: 100, noRefs: true, sortKeys: false })
|
|
19645
|
+
);
|
|
19646
|
+
}
|
|
19647
|
+
put("response.json", JSON.stringify({ ...response, artifacts }, null, 2));
|
|
19648
|
+
return carriesVerdict ? "verdict" : "response";
|
|
19570
19649
|
};
|
|
19571
|
-
|
|
19572
|
-
|
|
19573
|
-
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
|
|
19577
|
-
|
|
19578
|
-
|
|
19579
|
-
|
|
19580
|
-
|
|
19581
|
-
|
|
19650
|
+
try {
|
|
19651
|
+
return carried();
|
|
19652
|
+
} catch {
|
|
19653
|
+
for (const name of CARRIED_FILES) {
|
|
19654
|
+
try {
|
|
19655
|
+
rmSync9(join36(dst, name), { recursive: true, force: true });
|
|
19656
|
+
} catch {
|
|
19657
|
+
}
|
|
19658
|
+
}
|
|
19659
|
+
return "nothing";
|
|
19660
|
+
}
|
|
19661
|
+
}
|
|
19662
|
+
function completeVerdict(value, scenarioId) {
|
|
19663
|
+
const record = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
19664
|
+
return value.scenario_id === scenarioId && typeof value.run_id === "string" && value.run_id.length > 0 && DECIDED.includes(value.status) && typeof value.summary === "string" && ["pass_count", "fail_count", "unable_to_verify_count", "compliance_percentage", "latency_ms", "turns"].every((key2) => typeof value[key2] === "number" && Number.isFinite(value[key2])) && Array.isArray(value.forbidden_hits) && value.forbidden_hits.every((hit) => typeof hit === "string") && record(value.metrics) && Object.values(value.metrics).every((metric) => typeof metric === "number" && Number.isFinite(metric)) && Array.isArray(value.criteria) && value.criteria.every((row) => record(row) && ["criterion_id", "criterion", "expected", "achieved", "evidence"].every((key2) => typeof row[key2] === "string") && DECIDED.includes(row.status) && ["High", "Medium", "Low"].includes(row.confidence));
|
|
19665
|
+
}
|
|
19666
|
+
function judgeable(response, scenarioId) {
|
|
19667
|
+
return response.scenario_id === scenarioId && typeof response.status === "string" && typeof response.output === "string" && Array.isArray(response.transcript) && Array.isArray(response.artifacts) && response.artifacts.every((path) => typeof path === "string" && path.length > 0);
|
|
19582
19668
|
}
|
|
19583
19669
|
function phasesRunIn(root, agentId, runId) {
|
|
19584
19670
|
const dir2 = join36(runDir(root, agentId, runId), "scenarios");
|
|
@@ -19603,7 +19689,10 @@ function phasesRunIn(root, agentId, runId) {
|
|
|
19603
19689
|
return [...seen];
|
|
19604
19690
|
}
|
|
19605
19691
|
function loadSnapshot(root, agentId, runId, scenarioId) {
|
|
19606
|
-
const at = join36(
|
|
19692
|
+
const at = join36(
|
|
19693
|
+
scenarioDir(root, agentId, runId, scenarioId),
|
|
19694
|
+
"snapshot.yaml"
|
|
19695
|
+
);
|
|
19607
19696
|
let parsed;
|
|
19608
19697
|
try {
|
|
19609
19698
|
parsed = yaml10.load(readFileSync34(at, "utf-8"));
|
|
@@ -19648,12 +19737,25 @@ function real(p) {
|
|
|
19648
19737
|
return parent === p ? p : join36(real(parent), basename8(p));
|
|
19649
19738
|
}
|
|
19650
19739
|
}
|
|
19740
|
+
var CARRIED_FILES, DECIDED, JUDGE_WORKING;
|
|
19651
19741
|
var init_materialise = __esm({
|
|
19652
19742
|
"src/run/materialise.ts"() {
|
|
19653
19743
|
"use strict";
|
|
19654
19744
|
init_spec2();
|
|
19655
19745
|
init_plan_file();
|
|
19656
19746
|
init_types3();
|
|
19747
|
+
CARRIED_FILES = [
|
|
19748
|
+
"response.json",
|
|
19749
|
+
"request.json",
|
|
19750
|
+
"output.txt",
|
|
19751
|
+
"hooks.json",
|
|
19752
|
+
"verdict.yaml",
|
|
19753
|
+
"artifacts",
|
|
19754
|
+
"evidence",
|
|
19755
|
+
"hook-state"
|
|
19756
|
+
];
|
|
19757
|
+
DECIDED = ["Pass", "Fail", "Unable to Verify"];
|
|
19758
|
+
JUDGE_WORKING = "judge-working.json";
|
|
19657
19759
|
}
|
|
19658
19760
|
});
|
|
19659
19761
|
|
|
@@ -20092,6 +20194,522 @@ var init_hook_scripts = __esm({
|
|
|
20092
20194
|
}
|
|
20093
20195
|
});
|
|
20094
20196
|
|
|
20197
|
+
// src/run/plan.ts
|
|
20198
|
+
import yaml13 from "js-yaml";
|
|
20199
|
+
function staticPlan(input) {
|
|
20200
|
+
const { partition: partition2 } = input;
|
|
20201
|
+
const included = partition2.candidates.map((c) => ({
|
|
20202
|
+
scenario_id: c.local_id,
|
|
20203
|
+
why: "a candidate, and nothing narrowed this run further",
|
|
20204
|
+
confidence: "high"
|
|
20205
|
+
}));
|
|
20206
|
+
const judge_only = [];
|
|
20207
|
+
const carried = [];
|
|
20208
|
+
const skipped = [
|
|
20209
|
+
...partition2.excluded.map((e) => ({
|
|
20210
|
+
scenario_id: e.scenario_id,
|
|
20211
|
+
why: `excluded by the flags: ${e.why}`,
|
|
20212
|
+
confidence: "high"
|
|
20213
|
+
})),
|
|
20214
|
+
...partition2.filtered.map((f) => ({
|
|
20215
|
+
scenario_id: f.scenario_id,
|
|
20216
|
+
why: `left out by the tree: ${f.why}`,
|
|
20217
|
+
confidence: "high"
|
|
20218
|
+
}))
|
|
20219
|
+
];
|
|
20220
|
+
const counts = [
|
|
20221
|
+
included.length ? `${included.length} to execute` : "",
|
|
20222
|
+
// SAID, not implied. Somebody who typed `--resume` expects work to be
|
|
20223
|
+
// reused, and a plan that silently re-ran everything would look like one
|
|
20224
|
+
// that had reused nothing to reuse.
|
|
20225
|
+
input.resumeHint ? `nothing reused from ${input.resumeHint}` : ""
|
|
20226
|
+
].filter(Boolean);
|
|
20227
|
+
return {
|
|
20228
|
+
included,
|
|
20229
|
+
skipped,
|
|
20230
|
+
judge_only,
|
|
20231
|
+
carried,
|
|
20232
|
+
/*
|
|
20233
|
+
* `--rca` and nothing else. There is no sentence for anything to read an
|
|
20234
|
+
* intent out of, and RCA costs a model call per failure cluster — turning
|
|
20235
|
+
* it on because a run happens to be large would spend on an explanation
|
|
20236
|
+
* nobody asked for.
|
|
20237
|
+
*/
|
|
20238
|
+
rca: Boolean(input.rca),
|
|
20239
|
+
summary: counts.length ? `no instruction given \u2014 ${counts.join(", ")}. Every candidate the flags and the tree left.` : "no instruction given, and nothing is left to run.",
|
|
20240
|
+
...input.resumeHint ? { resume_from: input.resumeHint } : {},
|
|
20241
|
+
/*
|
|
20242
|
+
* No `name` and no `concurrency`. Both are proposals a planner makes from
|
|
20243
|
+
* a sentence, and `applyPlanned` already lets an explicit flag win — so
|
|
20244
|
+
* omitting them leaves the flag, or the default, exactly as typed.
|
|
20245
|
+
*/
|
|
20246
|
+
// Nothing was spent. A non-zero cost here would bill a run for a decision
|
|
20247
|
+
// no model was asked to make.
|
|
20248
|
+
credits: 0
|
|
20249
|
+
};
|
|
20250
|
+
}
|
|
20251
|
+
async function planRun(input, deps) {
|
|
20252
|
+
const { partition: partition2 } = input;
|
|
20253
|
+
const flagsAsked = input.flags ?? [];
|
|
20254
|
+
const task4 = [
|
|
20255
|
+
input.instruction ? `## WHAT THE USER ASKED FOR
|
|
20256
|
+
|
|
20257
|
+
${input.instruction}
|
|
20258
|
+
` : "",
|
|
20259
|
+
input.resumeHint ? `## THEY NAMED A RUN
|
|
20260
|
+
|
|
20261
|
+
${input.resumeHint} \u2014 look it up before planning.
|
|
20262
|
+
` : "",
|
|
20263
|
+
/**
|
|
20264
|
+
* The same rendering of the agent every other planner gets.
|
|
20265
|
+
*
|
|
20266
|
+
* From the same function, so a planner deciding what to SPEND cannot be
|
|
20267
|
+
* reasoning about a different agent than the writers described.
|
|
20268
|
+
*/
|
|
20269
|
+
agentContext({
|
|
20270
|
+
spec: input.spec,
|
|
20271
|
+
features: input.features,
|
|
20272
|
+
...input.findings?.length ? { findings: input.findings } : {},
|
|
20273
|
+
...input.callers?.length ? { callers: input.callers } : {},
|
|
20274
|
+
...input.callees?.length ? { callees: input.callees } : {}
|
|
20275
|
+
}),
|
|
20276
|
+
"",
|
|
20277
|
+
/**
|
|
20278
|
+
* How the agent is reached, and what rook can SEE while it runs.
|
|
20279
|
+
*
|
|
20280
|
+
* The second half is what decides whether a scenario is checkable at all,
|
|
20281
|
+
* and the unrunnable list below is computed from it — so the planner is
|
|
20282
|
+
* shown the reason as well as the verdict.
|
|
20283
|
+
*/
|
|
20284
|
+
"## THE PROFILE THIS RUN WILL USE",
|
|
20285
|
+
"",
|
|
20286
|
+
"```yaml",
|
|
20287
|
+
yaml13.dump(
|
|
20288
|
+
{
|
|
20289
|
+
id: input.profile.id,
|
|
20290
|
+
/*
|
|
20291
|
+
* WHICH PHASES, not which transport.
|
|
20292
|
+
*
|
|
20293
|
+
* `kind` and `mode` were here and told the planner the agent was
|
|
20294
|
+
* reached over http, synchronously — neither of which changes what
|
|
20295
|
+
* to run. The phases do: a profile with a `collect` can be run now
|
|
20296
|
+
* and collected later, and one without cannot, so a planner asked
|
|
20297
|
+
* about staging a run needs this and had no way to see it.
|
|
20298
|
+
*/
|
|
20299
|
+
phases: definedPhases(input.profile),
|
|
20300
|
+
multi_turn: Boolean(input.profile.capabilities?.multi_turn),
|
|
20301
|
+
/*
|
|
20302
|
+
* What the profile REPORTS, and it must not contradict the gap block
|
|
20303
|
+
* below it.
|
|
20304
|
+
*
|
|
20305
|
+
* This was hardcoded `false` upstream, because on the old schema
|
|
20306
|
+
* nothing observed a call — `observe.mcp: proxy` was a declaration
|
|
20307
|
+
* with no implementation (#717). A `collect` hook can hand calls back
|
|
20308
|
+
* now, and `capabilities.calls` is what one real invocation showed.
|
|
20309
|
+
* The gate agrees: it raises a `tool_calls` gap only when this is
|
|
20310
|
+
* false.
|
|
20311
|
+
*/
|
|
20312
|
+
observes_tool_calls: Boolean(input.profile.capabilities?.calls),
|
|
20313
|
+
// Names and set/unset only. A planner that can read a token has no
|
|
20314
|
+
// reason to, and this text is one prompt away from a log.
|
|
20315
|
+
needs: requiredVars(input.profile).map((s) => ({
|
|
20316
|
+
variable: s.variable,
|
|
20317
|
+
set_here: !missingVars(input.profile).includes(s.variable),
|
|
20318
|
+
...s.purpose ? { purpose: s.purpose } : {}
|
|
20319
|
+
}))
|
|
20320
|
+
},
|
|
20321
|
+
{ lineWidth: 100, noRefs: true, sortKeys: false }
|
|
20322
|
+
).trimEnd(),
|
|
20323
|
+
"```",
|
|
20324
|
+
"",
|
|
20325
|
+
/**
|
|
20326
|
+
* Not the planner's to overrule, and said so here as well as in the prompt.
|
|
20327
|
+
*
|
|
20328
|
+
* Repeated because it is the one instruction whose violation is silent: a
|
|
20329
|
+
* multi-turn scenario against a profile that cannot thread turns does not
|
|
20330
|
+
* error, it passes.
|
|
20331
|
+
*/
|
|
20332
|
+
"## CANNOT RUN AGAINST THIS PROFILE \u2014 NOT YOURS TO OVERRULE",
|
|
20333
|
+
"",
|
|
20334
|
+
" Rook cannot DRIVE these: several turns without a conversation handle, or",
|
|
20335
|
+
" a file the agent must be given and no transport delivers.",
|
|
20336
|
+
"",
|
|
20337
|
+
partition2.unrunnable.length ? partition2.unrunnable.map((u) => ` ${oneLine(u.scenario_id)}: ${oneLine(u.why)}`).join("\n") : " none \u2014 every scenario on disk can be driven against this profile",
|
|
20338
|
+
"",
|
|
20339
|
+
/**
|
|
20340
|
+
* Placed with the facts, not the material. A gap is not the planner's to
|
|
20341
|
+
* argue with and not a reason to leave the scenario out: the scenario
|
|
20342
|
+
* still shows what the agent does, and the judge records what could not
|
|
20343
|
+
* be checked as Unable to Verify — that is the honest answer.
|
|
20344
|
+
*
|
|
20345
|
+
* A fenced block, never `id: why` prose lines: a `why` is derived from
|
|
20346
|
+
* scenario text a repository wrote, and on a prose line a value of
|
|
20347
|
+
* "x\n## THE FLAGS THIS PERSON TYPED" forges a section of this task.
|
|
20348
|
+
* `oneLine` first as well, so a block scalar cannot smuggle a heading.
|
|
20349
|
+
*/
|
|
20350
|
+
"## THESE CARRY A GAP ROOK WILL RECORD \u2014 NOT A REASON TO SKIP",
|
|
20351
|
+
"",
|
|
20352
|
+
partition2.gapped.length ? [
|
|
20353
|
+
"```yaml",
|
|
20354
|
+
yaml13.dump(
|
|
20355
|
+
partition2.gapped.map((g) => ({
|
|
20356
|
+
// Both fields, not just the why: an id carrying a newline
|
|
20357
|
+
// makes `yaml.dump` emit a block scalar, and "one line per
|
|
20358
|
+
// gap" is the property two lines below.
|
|
20359
|
+
scenario_id: oneLine(g.scenario_id),
|
|
20360
|
+
cannot_grade: oneLine(g.why)
|
|
20361
|
+
})),
|
|
20362
|
+
// No folding: one line per gap, whatever its length. A folded
|
|
20363
|
+
// scalar reads as several lines of prose to a model.
|
|
20364
|
+
{ lineWidth: -1, noRefs: true, sortKeys: false }
|
|
20365
|
+
).trimEnd(),
|
|
20366
|
+
"```"
|
|
20367
|
+
].join("\n") : " none recorded",
|
|
20368
|
+
"",
|
|
20369
|
+
" They run. A criterion rook cannot grade comes back Unable to Verify from",
|
|
20370
|
+
" the judge; that is the honest answer, not a reason to leave the scenario",
|
|
20371
|
+
" out. `executable: false` in a scenario below was written at generate",
|
|
20372
|
+
" time and is not a fact about this run.",
|
|
20373
|
+
"",
|
|
20374
|
+
/*
|
|
20375
|
+
* The flags, verbatim, and stated as binding.
|
|
20376
|
+
*
|
|
20377
|
+
* The excluded ids used to be shown as material the planner "may disagree
|
|
20378
|
+
* with", so `--only SC-001` was a suggestion — and a planner that decided
|
|
20379
|
+
* the neighbouring scenarios were worth running got them, because nothing
|
|
20380
|
+
* downstream checked. Naming the flags as well as their effect matters:
|
|
20381
|
+
* a list of excluded ids does not tell a model that a PERSON narrowed
|
|
20382
|
+
* this, and that is the fact that decides whether it may argue.
|
|
20383
|
+
*/
|
|
20384
|
+
...flagsAsked.length ? [
|
|
20385
|
+
"## THE FLAGS THIS PERSON TYPED \u2014 BINDING, NOT YOURS TO OVERRULE",
|
|
20386
|
+
"",
|
|
20387
|
+
...flagsAsked.map((f) => ` ${f}`),
|
|
20388
|
+
"",
|
|
20389
|
+
" They narrowed this run deliberately. Every scenario those flags",
|
|
20390
|
+
" excluded is listed below and is OUT \u2014 naming one in included,",
|
|
20391
|
+
" judge_only or carried is an error, and rook drops it. If the flags",
|
|
20392
|
+
" look wrong for what they asked, say so in summary rather than",
|
|
20393
|
+
" planning around them.",
|
|
20394
|
+
"",
|
|
20395
|
+
"## WHAT THOSE FLAGS EXCLUDED \u2014 out of this run",
|
|
20396
|
+
"",
|
|
20397
|
+
partition2.excluded.length ? partition2.excluded.map((f) => ` ${f.scenario_id}: ${f.why}`).join("\n") : " nothing \u2014 every scenario on disk matched them",
|
|
20398
|
+
""
|
|
20399
|
+
] : [],
|
|
20400
|
+
"## WHAT THE TREE LEFT OUT \u2014 material, and you may disagree",
|
|
20401
|
+
"",
|
|
20402
|
+
partition2.filtered.length ? partition2.filtered.map((f) => ` ${f.scenario_id}: ${f.why}`).join("\n") : " nothing \u2014 no scenario was dropped for the state it is in",
|
|
20403
|
+
"",
|
|
20404
|
+
"## YOUR CANDIDATES \u2014 everything you may choose from",
|
|
20405
|
+
"",
|
|
20406
|
+
partition2.candidates.length ? partition2.candidates.map((c) => ` ${c.local_id} ${c.title}`).join("\n") : " nothing \u2014 the flags excluded everything runnable",
|
|
20407
|
+
"",
|
|
20408
|
+
...input.stale.length ? [
|
|
20409
|
+
"## THESE PIN OLDER FEATURE TEXT",
|
|
20410
|
+
"",
|
|
20411
|
+
` ${input.stale.join(", ")}`,
|
|
20412
|
+
"",
|
|
20413
|
+
"They still run \u2014 an outdated test that passes is information. What must",
|
|
20414
|
+
"not happen is a plan describing them as though they were written",
|
|
20415
|
+
"against the feature as it stands.",
|
|
20416
|
+
""
|
|
20417
|
+
] : [],
|
|
20418
|
+
/**
|
|
20419
|
+
* Every scenario, in full, and LAST — the state block.
|
|
20420
|
+
*
|
|
20421
|
+
* Titles and counts cannot tell six behaviours from six rewordings, and
|
|
20422
|
+
* that difference is exactly what decides whether a run is worth its cost.
|
|
20423
|
+
* Last because it is the part that changes every run, which is what prompt
|
|
20424
|
+
* caching wants.
|
|
20425
|
+
*/
|
|
20426
|
+
"## EVERY SCENARIO ON DISK",
|
|
20427
|
+
"",
|
|
20428
|
+
"```yaml",
|
|
20429
|
+
yaml13.dump(input.scenarios, { lineWidth: 100, noRefs: true, sortKeys: false }).trimEnd(),
|
|
20430
|
+
"```",
|
|
20431
|
+
""
|
|
20432
|
+
].filter(Boolean).join("\n");
|
|
20433
|
+
const child = await spawnSubagent(
|
|
20434
|
+
{
|
|
20435
|
+
role: "run_planner",
|
|
20436
|
+
label: `${partition2.candidates.length} candidate(s)`,
|
|
20437
|
+
task: task4,
|
|
20438
|
+
// The BUDGET phase, not the command. `plan_scenarios` bills to
|
|
20439
|
+
// "generate" for the same reason: a planner is charged to the work it is
|
|
20440
|
+
// planning, so one bucket covers deciding and doing.
|
|
20441
|
+
phase: "execute",
|
|
20442
|
+
responseSchema: PLAN_SCHEMA2,
|
|
20443
|
+
tools: toolsForRole("run_planner", { root: input.root, mcpAgent: null })
|
|
20444
|
+
},
|
|
20445
|
+
deps
|
|
20446
|
+
);
|
|
20447
|
+
if (!child.ok || child.data == null)
|
|
20448
|
+
throw new Error(roleFailure("run_planner", child));
|
|
20449
|
+
const forbidden = /* @__PURE__ */ new Set([
|
|
20450
|
+
...partition2.unrunnable.map((u) => u.scenario_id),
|
|
20451
|
+
...partition2.excluded.map((e) => e.scenario_id)
|
|
20452
|
+
]);
|
|
20453
|
+
const known = new Set(input.scenarios.map((s) => s.local_id));
|
|
20454
|
+
const included = (child.data.included ?? []).filter(
|
|
20455
|
+
(e) => e?.scenario_id && known.has(e.scenario_id) && !forbidden.has(e.scenario_id)
|
|
20456
|
+
);
|
|
20457
|
+
const skipped = (child.data.skipped ?? []).filter(
|
|
20458
|
+
(e) => e?.scenario_id && known.has(e.scenario_id)
|
|
20459
|
+
);
|
|
20460
|
+
const canJudge = new Set(input.ungraded ?? []);
|
|
20461
|
+
const canCarry = new Set(input.decided ?? []);
|
|
20462
|
+
const eligible = (entries, allowed) => {
|
|
20463
|
+
const kept = [];
|
|
20464
|
+
const demoted = [];
|
|
20465
|
+
for (const e of entries ?? []) {
|
|
20466
|
+
if (!e?.scenario_id || !known.has(e.scenario_id)) continue;
|
|
20467
|
+
if (forbidden.has(e.scenario_id)) continue;
|
|
20468
|
+
(allowed.has(e.scenario_id) ? kept : demoted).push(e);
|
|
20469
|
+
}
|
|
20470
|
+
return { kept, demoted };
|
|
20471
|
+
};
|
|
20472
|
+
const judgeOnly = eligible(child.data.judge_only, canJudge);
|
|
20473
|
+
const carried = eligible(child.data.carried, canCarry);
|
|
20474
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
20475
|
+
const takeOnce = (entries) => entries.filter(
|
|
20476
|
+
(e) => !claimed.has(e.scenario_id) && claimed.add(e.scenario_id)
|
|
20477
|
+
);
|
|
20478
|
+
const finalIncluded = takeOnce([
|
|
20479
|
+
...included,
|
|
20480
|
+
...judgeOnly.demoted,
|
|
20481
|
+
...carried.demoted
|
|
20482
|
+
]);
|
|
20483
|
+
const finalJudgeOnly = takeOnce(judgeOnly.kept);
|
|
20484
|
+
const finalCarried = takeOnce(carried.kept);
|
|
20485
|
+
return {
|
|
20486
|
+
included: finalIncluded,
|
|
20487
|
+
skipped,
|
|
20488
|
+
judge_only: finalJudgeOnly,
|
|
20489
|
+
carried: finalCarried,
|
|
20490
|
+
/**
|
|
20491
|
+
* The flag forces it ON and never off.
|
|
20492
|
+
*
|
|
20493
|
+
* A planner that declined costs nothing to overrule, and somebody who typed
|
|
20494
|
+
* `--rca` has asked plainly. The reverse — a flag that silences a planner
|
|
20495
|
+
* which decided an explanation was needed — would hide the reasoning at the
|
|
20496
|
+
* moment it is most wanted.
|
|
20497
|
+
*/
|
|
20498
|
+
rca: Boolean(child.data.rca) || Boolean(input.rca),
|
|
20499
|
+
summary: child.data.summary ?? "",
|
|
20500
|
+
...child.data.resume_from ? { resume_from: child.data.resume_from } : {},
|
|
20501
|
+
...child.data.name ? { name: child.data.name } : {},
|
|
20502
|
+
...child.data.concurrency ? { concurrency: child.data.concurrency } : {},
|
|
20503
|
+
credits: child.credits
|
|
20504
|
+
};
|
|
20505
|
+
}
|
|
20506
|
+
function applyPlanned(plan2, flags) {
|
|
20507
|
+
const proposed = plan2.concurrency ?? 1;
|
|
20508
|
+
return {
|
|
20509
|
+
...flags.name ?? plan2.name ? { name: flags.name ?? plan2.name } : {},
|
|
20510
|
+
concurrency: flags.concurrency ?? Math.min(Math.max(1, Math.trunc(proposed)), MAX_PLANNED_CONCURRENCY)
|
|
20511
|
+
};
|
|
20512
|
+
}
|
|
20513
|
+
function renderRunPlan(plan2, view) {
|
|
20514
|
+
const gap = new Map(view.gapped.map((g) => [g.scenario_id, g.why]));
|
|
20515
|
+
const excluded = new Set(view.excluded.map((e) => e.scenario_id));
|
|
20516
|
+
const gapLine = (id) => {
|
|
20517
|
+
const why2 = gap.get(id);
|
|
20518
|
+
return why2 === void 0 ? [] : [` cannot grade: ${oneLine(why2)}`];
|
|
20519
|
+
};
|
|
20520
|
+
const lines = [`plan: ${plan2.included.length} scenario(s) to run`];
|
|
20521
|
+
for (const e of plan2.included) {
|
|
20522
|
+
const sure = e.confidence && e.confidence !== "high" ? ` (${e.confidence} confidence)` : "";
|
|
20523
|
+
lines.push(` ${oneLine(e.scenario_id)}${sure}`);
|
|
20524
|
+
lines.push(` ${oneLine(e.why)}`);
|
|
20525
|
+
lines.push(...gapLine(e.scenario_id));
|
|
20526
|
+
}
|
|
20527
|
+
for (const e of plan2.judge_only) {
|
|
20528
|
+
lines.push(` ${oneLine(e.scenario_id)} judge only \u2014 ${oneLine(e.why)}`);
|
|
20529
|
+
lines.push(...gapLine(e.scenario_id));
|
|
20530
|
+
}
|
|
20531
|
+
for (const e of plan2.carried) {
|
|
20532
|
+
lines.push(` ${oneLine(e.scenario_id)} carried \u2014 ${oneLine(e.why)}`);
|
|
20533
|
+
lines.push(...gapLine(e.scenario_id));
|
|
20534
|
+
}
|
|
20535
|
+
for (const e of plan2.skipped) {
|
|
20536
|
+
lines.push(` ${oneLine(e.scenario_id)} skipped \u2014 ${oneLine(e.why)}`);
|
|
20537
|
+
const why2 = gap.get(e.scenario_id);
|
|
20538
|
+
if (why2 === void 0) continue;
|
|
20539
|
+
lines.push(
|
|
20540
|
+
excluded.has(e.scenario_id) ? ` carries a gap: ${oneLine(why2)}` : ` carries a gap, which is not a reason to skip: ${oneLine(why2)}`
|
|
20541
|
+
);
|
|
20542
|
+
}
|
|
20543
|
+
if (view.unrunnable.length)
|
|
20544
|
+
lines.push(` ${view.unrunnable.length} cannot run against this profile`);
|
|
20545
|
+
if (plan2.resume_from)
|
|
20546
|
+
lines.push(` carrying finished work from ${plan2.resume_from}`);
|
|
20547
|
+
return lines;
|
|
20548
|
+
}
|
|
20549
|
+
function decidedElsewhere(plan2) {
|
|
20550
|
+
return new Set(
|
|
20551
|
+
[...plan2.judge_only ?? [], ...plan2.carried ?? []].map(
|
|
20552
|
+
(e) => e.scenario_id
|
|
20553
|
+
)
|
|
20554
|
+
);
|
|
20555
|
+
}
|
|
20556
|
+
function scenariosOf(plan2) {
|
|
20557
|
+
return [
|
|
20558
|
+
...plan2.included,
|
|
20559
|
+
...plan2.judge_only ?? [],
|
|
20560
|
+
...plan2.carried ?? []
|
|
20561
|
+
];
|
|
20562
|
+
}
|
|
20563
|
+
function judgedHere(plan2) {
|
|
20564
|
+
return [...plan2.included, ...plan2.judge_only ?? []];
|
|
20565
|
+
}
|
|
20566
|
+
function gapLines(view, plan2) {
|
|
20567
|
+
const does = /* @__PURE__ */ new Map();
|
|
20568
|
+
for (const e of plan2.included) does.set(e.scenario_id, "runs");
|
|
20569
|
+
for (const e of plan2.judge_only ?? []) does.set(e.scenario_id, "is judged");
|
|
20570
|
+
return view.gapped.filter((g) => does.has(g.scenario_id)).map(
|
|
20571
|
+
(g) => `${oneLine(g.scenario_id)}: ${does.get(g.scenario_id)} \u2014 cannot grade: ${oneLine(g.why)}`
|
|
20572
|
+
);
|
|
20573
|
+
}
|
|
20574
|
+
var PLAN_SCHEMA2, MAX_PLANNED_CONCURRENCY;
|
|
20575
|
+
var init_plan2 = __esm({
|
|
20576
|
+
"src/run/plan.ts"() {
|
|
20577
|
+
"use strict";
|
|
20578
|
+
init_subagent();
|
|
20579
|
+
init_role_failure();
|
|
20580
|
+
init_registry4();
|
|
20581
|
+
init_context();
|
|
20582
|
+
init_types3();
|
|
20583
|
+
init_env();
|
|
20584
|
+
init_render();
|
|
20585
|
+
PLAN_SCHEMA2 = {
|
|
20586
|
+
type: "object",
|
|
20587
|
+
/*
|
|
20588
|
+
* `rca` is NOT required, and requiring it failed real runs.
|
|
20589
|
+
*
|
|
20590
|
+
* "run_planner: the reply does not match the shape asked for — rca:
|
|
20591
|
+
* required, and absent" is the whole of `/run` refusing because a boolean
|
|
20592
|
+
* with an obvious default was left out. The schema's own description says
|
|
20593
|
+
* "false is the ordinary answer", and `planRun` already reads it as
|
|
20594
|
+
* `Boolean(child.data.rca) || Boolean(input.rca)` — so absence was always
|
|
20595
|
+
* handled everywhere except the validator that rejected it first.
|
|
20596
|
+
*
|
|
20597
|
+
* The three that remain are the answer itself: what to run, what was left
|
|
20598
|
+
* out, and the sentence somebody reads before spending. A reply missing any
|
|
20599
|
+
* of those is not a plan.
|
|
20600
|
+
*/
|
|
20601
|
+
required: ["included", "skipped", "summary"],
|
|
20602
|
+
properties: {
|
|
20603
|
+
/**
|
|
20604
|
+
* The four lists are a PARTITION of the candidates.
|
|
20605
|
+
*
|
|
20606
|
+
* Every candidate lands in exactly one, and rook enforces it — a scenario
|
|
20607
|
+
* in two is ambiguous about whether it costs an agent call, and one in none
|
|
20608
|
+
* is silently dropped. `judge_only` and `carried` are the resume half: rook
|
|
20609
|
+
* works out which scenarios are ELIGIBLE for each from the files, and the
|
|
20610
|
+
* planner decides which of them the request actually wants that done to.
|
|
20611
|
+
*/
|
|
20612
|
+
included: {
|
|
20613
|
+
type: "array",
|
|
20614
|
+
description: "Execute these: call the agent, then judge. One entry per scenario.",
|
|
20615
|
+
items: {
|
|
20616
|
+
type: "object",
|
|
20617
|
+
required: ["scenario_id", "why", "confidence"],
|
|
20618
|
+
properties: {
|
|
20619
|
+
scenario_id: { type: "string" },
|
|
20620
|
+
why: {
|
|
20621
|
+
type: "string",
|
|
20622
|
+
description: "What this one establishes that the others do not. Not 'important coverage', which is true of everything and decides nothing."
|
|
20623
|
+
},
|
|
20624
|
+
confidence: {
|
|
20625
|
+
type: "string",
|
|
20626
|
+
enum: ["high", "medium", "low"],
|
|
20627
|
+
description: "`low` says you are unsure it earns its cost \u2014 exactly what somebody about to approve wants to know."
|
|
20628
|
+
}
|
|
20629
|
+
}
|
|
20630
|
+
}
|
|
20631
|
+
},
|
|
20632
|
+
judge_only: {
|
|
20633
|
+
type: "array",
|
|
20634
|
+
description: "Already executed by the prior run and never judged. A judge call and NO agent call. Only ids listed as ungraded below are eligible.",
|
|
20635
|
+
items: {
|
|
20636
|
+
type: "object",
|
|
20637
|
+
required: ["scenario_id", "why"],
|
|
20638
|
+
properties: {
|
|
20639
|
+
scenario_id: { type: "string" },
|
|
20640
|
+
why: { type: "string" },
|
|
20641
|
+
confidence: { type: "string", enum: ["high", "medium", "low"] }
|
|
20642
|
+
}
|
|
20643
|
+
}
|
|
20644
|
+
},
|
|
20645
|
+
carried: {
|
|
20646
|
+
type: "array",
|
|
20647
|
+
description: "Already decided by the prior run. Copy the verdict forward \u2014 no agent call, no judge call. Only ids listed as decided below are eligible.",
|
|
20648
|
+
items: {
|
|
20649
|
+
type: "object",
|
|
20650
|
+
required: ["scenario_id", "why"],
|
|
20651
|
+
properties: {
|
|
20652
|
+
scenario_id: { type: "string" },
|
|
20653
|
+
why: { type: "string" },
|
|
20654
|
+
confidence: { type: "string", enum: ["high", "medium", "low"] }
|
|
20655
|
+
}
|
|
20656
|
+
}
|
|
20657
|
+
},
|
|
20658
|
+
rca: {
|
|
20659
|
+
type: "boolean",
|
|
20660
|
+
description: "Explain the failure clusters when this run ends. True when the request asks WHY something fails, or when a run this large failing would leave somebody with nowhere to start. It costs one model call per cluster, so false is the ordinary answer."
|
|
20661
|
+
},
|
|
20662
|
+
skipped: {
|
|
20663
|
+
type: "array",
|
|
20664
|
+
description: "One entry per scenario you deliberately left out.",
|
|
20665
|
+
items: {
|
|
20666
|
+
type: "object",
|
|
20667
|
+
required: ["scenario_id", "why", "confidence"],
|
|
20668
|
+
properties: {
|
|
20669
|
+
scenario_id: { type: "string" },
|
|
20670
|
+
why: {
|
|
20671
|
+
type: "string",
|
|
20672
|
+
description: "Why it is not worth running now. A scenario nobody considered and one deliberately excluded look identical afterwards."
|
|
20673
|
+
},
|
|
20674
|
+
confidence: { type: "string", enum: ["high", "medium", "low"] }
|
|
20675
|
+
}
|
|
20676
|
+
}
|
|
20677
|
+
},
|
|
20678
|
+
summary: {
|
|
20679
|
+
type: "string",
|
|
20680
|
+
description: "What this run will establish, and what it will not. The sentence a person reads before deciding to spend."
|
|
20681
|
+
},
|
|
20682
|
+
resume_from: {
|
|
20683
|
+
type: "string",
|
|
20684
|
+
description: "A prior run to carry finished work from. Omit unless proposing one."
|
|
20685
|
+
},
|
|
20686
|
+
/**
|
|
20687
|
+
* Two fields of `run.yaml` the planner may set, and only these two.
|
|
20688
|
+
*
|
|
20689
|
+
* Both are things a person expresses in words — "call this the threshold
|
|
20690
|
+
* sweep", "run them one at a time" — and neither changes what a run MEANS.
|
|
20691
|
+
*
|
|
20692
|
+
* `test_mode` is deliberately NOT here. It decides whether the run is
|
|
20693
|
+
* pinned upstream at all, so a model able to set it could turn a real run
|
|
20694
|
+
* into one that records nothing, or a smoke test into a version-pinned
|
|
20695
|
+
* result. That is a flag, and flags are for people.
|
|
20696
|
+
*
|
|
20697
|
+
* An explicit flag always wins over both — see `applyPlanned`.
|
|
20698
|
+
*/
|
|
20699
|
+
name: {
|
|
20700
|
+
type: "string",
|
|
20701
|
+
description: "A short name for this run, when the request suggests one. Omit rather than inventing a label nobody used."
|
|
20702
|
+
},
|
|
20703
|
+
concurrency: {
|
|
20704
|
+
type: "integer",
|
|
20705
|
+
description: "How many scenarios to run at once, when the request says. One is the default and the right answer for anything hitting a rate-limited API."
|
|
20706
|
+
}
|
|
20707
|
+
}
|
|
20708
|
+
};
|
|
20709
|
+
MAX_PLANNED_CONCURRENCY = 8;
|
|
20710
|
+
}
|
|
20711
|
+
});
|
|
20712
|
+
|
|
20095
20713
|
// src/report/cluster.ts
|
|
20096
20714
|
function shapeOf2(verdict3) {
|
|
20097
20715
|
if (verdict3.compromised) return `compromised:${verdict3.impact ?? "unrated"}`;
|
|
@@ -20555,9 +21173,38 @@ function resolveRun(root, agentId, runId) {
|
|
|
20555
21173
|
const plans = readdirSync21(dir2).map((id) => loadPlan(root, agentId, id)).filter((p) => Boolean(p?.id)).sort((a, b) => (b.created ?? "").localeCompare(a.created ?? ""));
|
|
20556
21174
|
return plans[0] ?? null;
|
|
20557
21175
|
}
|
|
21176
|
+
function snapshotsOf(root, agentId, plan2) {
|
|
21177
|
+
return judgedHere(plan2).map((e) => {
|
|
21178
|
+
const at = join44(
|
|
21179
|
+
scenarioDir(root, agentId, plan2.id, e.scenario_id),
|
|
21180
|
+
"snapshot.yaml"
|
|
21181
|
+
);
|
|
21182
|
+
try {
|
|
21183
|
+
return yaml15.load(readFileSync40(at, "utf-8"));
|
|
21184
|
+
} catch {
|
|
21185
|
+
return null;
|
|
21186
|
+
}
|
|
21187
|
+
}).filter((s) => Boolean(s?.local_id));
|
|
21188
|
+
}
|
|
21189
|
+
function fallbackTotals(plan2, verdicts) {
|
|
21190
|
+
const planned = judgedHere(plan2).length;
|
|
21191
|
+
return {
|
|
21192
|
+
planned,
|
|
21193
|
+
executed: verdicts.length,
|
|
21194
|
+
passed: verdicts.filter((v) => v.status === "Pass").length,
|
|
21195
|
+
failed: verdicts.filter((v) => v.status === "Fail").length,
|
|
21196
|
+
unverifiable: verdicts.filter((v) => v.status === "Unable to Verify").length,
|
|
21197
|
+
unjudged: 0,
|
|
21198
|
+
not_run: Math.max(0, planned - verdicts.length),
|
|
21199
|
+
unrunnable: plan2.unrunnable.length,
|
|
21200
|
+
decided: verdicts.filter((v) => v.status !== "Unable to Verify").length,
|
|
21201
|
+
pass_rate: null,
|
|
21202
|
+
carried_forward: (plan2.carried ?? []).length
|
|
21203
|
+
};
|
|
21204
|
+
}
|
|
20558
21205
|
function runVerdicts(root, agentId, plan2) {
|
|
20559
21206
|
const out = [];
|
|
20560
|
-
for (const entry of plan2
|
|
21207
|
+
for (const entry of judgedHere(plan2)) {
|
|
20561
21208
|
const at = join44(
|
|
20562
21209
|
scenarioDir(root, agentId, plan2.id, entry.scenario_id),
|
|
20563
21210
|
"verdict.yaml"
|
|
@@ -20622,19 +21269,7 @@ async function explainRun(input, deps) {
|
|
|
20622
21269
|
);
|
|
20623
21270
|
return null;
|
|
20624
21271
|
}
|
|
20625
|
-
const scenarios2 =
|
|
20626
|
-
const at = join44(
|
|
20627
|
-
scenarioDir(input.root, input.agentId, plan2.id, e.scenario_id),
|
|
20628
|
-
"snapshot.yaml"
|
|
20629
|
-
);
|
|
20630
|
-
try {
|
|
20631
|
-
return yaml15.load(readFileSync40(at, "utf-8"));
|
|
20632
|
-
} catch {
|
|
20633
|
-
return null;
|
|
20634
|
-
}
|
|
20635
|
-
}).filter(
|
|
20636
|
-
(s) => Boolean(s?.local_id)
|
|
20637
|
-
);
|
|
21272
|
+
const scenarios2 = snapshotsOf(input.root, input.agentId, plan2);
|
|
20638
21273
|
const prior = (() => {
|
|
20639
21274
|
try {
|
|
20640
21275
|
return yaml15.load(
|
|
@@ -20683,19 +21318,7 @@ async function explainRun(input, deps) {
|
|
|
20683
21318
|
// Kept from the run that produced them. Recomputing totals here would
|
|
20684
21319
|
// count only the scenarios that left a verdict file, silently dropping
|
|
20685
21320
|
// everything the run never reached.
|
|
20686
|
-
totals: prior?.totals ??
|
|
20687
|
-
planned: plan2.included.length,
|
|
20688
|
-
executed: verdicts.length,
|
|
20689
|
-
passed: verdicts.filter((v) => v.status === "Pass").length,
|
|
20690
|
-
failed: verdicts.filter((v) => v.status === "Fail").length,
|
|
20691
|
-
unverifiable: verdicts.filter((v) => v.status === "Unable to Verify").length,
|
|
20692
|
-
unjudged: 0,
|
|
20693
|
-
not_run: Math.max(0, plan2.included.length - verdicts.length),
|
|
20694
|
-
unrunnable: plan2.unrunnable.length,
|
|
20695
|
-
decided: verdicts.filter((v) => v.status !== "Unable to Verify").length,
|
|
20696
|
-
pass_rate: null,
|
|
20697
|
-
carried_forward: 0
|
|
20698
|
-
},
|
|
21321
|
+
totals: prior?.totals ?? fallbackTotals(plan2, verdicts),
|
|
20699
21322
|
...prior?.metrics ? { metrics: prior.metrics } : {},
|
|
20700
21323
|
say: input.say,
|
|
20701
21324
|
rca: true
|
|
@@ -20714,6 +21337,7 @@ var init_command = __esm({
|
|
|
20714
21337
|
init_store3();
|
|
20715
21338
|
init_plan_file();
|
|
20716
21339
|
init_materialise();
|
|
21340
|
+
init_plan2();
|
|
20717
21341
|
init_report();
|
|
20718
21342
|
init_hook_scripts();
|
|
20719
21343
|
}
|
|
@@ -20819,6 +21443,59 @@ var init_output = __esm({
|
|
|
20819
21443
|
}
|
|
20820
21444
|
});
|
|
20821
21445
|
|
|
21446
|
+
// src/command-arguments.ts
|
|
21447
|
+
function argumentPlaceholder(arg) {
|
|
21448
|
+
return arg.required ? `<${arg.name()}>` : `[${arg.name()}]`;
|
|
21449
|
+
}
|
|
21450
|
+
function declaredChoices(arg) {
|
|
21451
|
+
return arg.argChoices?.length ? `(choices: ${arg.argChoices.join(", ")})` : "";
|
|
21452
|
+
}
|
|
21453
|
+
function argumentDescription(arg) {
|
|
21454
|
+
return [arg.description, declaredChoices(arg)].filter(Boolean).join(" ");
|
|
21455
|
+
}
|
|
21456
|
+
function argumentSummary(arg) {
|
|
21457
|
+
return [argumentPlaceholder(arg), declaredChoices(arg)].filter(Boolean).join(" ");
|
|
21458
|
+
}
|
|
21459
|
+
function commandWords(line) {
|
|
21460
|
+
const out = [];
|
|
21461
|
+
let current2 = "";
|
|
21462
|
+
let quote2 = null;
|
|
21463
|
+
let started = false;
|
|
21464
|
+
for (const ch of line) {
|
|
21465
|
+
if (quote2) {
|
|
21466
|
+
if (ch === quote2) quote2 = null;
|
|
21467
|
+
else current2 += ch;
|
|
21468
|
+
continue;
|
|
21469
|
+
}
|
|
21470
|
+
if (ch === '"' || ch === "'") {
|
|
21471
|
+
quote2 = ch;
|
|
21472
|
+
started = true;
|
|
21473
|
+
continue;
|
|
21474
|
+
}
|
|
21475
|
+
if (/\s/.test(ch)) {
|
|
21476
|
+
if (current2 || started) out.push(current2);
|
|
21477
|
+
current2 = "";
|
|
21478
|
+
started = false;
|
|
21479
|
+
continue;
|
|
21480
|
+
}
|
|
21481
|
+
current2 += ch;
|
|
21482
|
+
}
|
|
21483
|
+
if (current2 || started) out.push(current2);
|
|
21484
|
+
return {
|
|
21485
|
+
words: out,
|
|
21486
|
+
complete: !started && current2 === "",
|
|
21487
|
+
openQuote: quote2 !== null
|
|
21488
|
+
};
|
|
21489
|
+
}
|
|
21490
|
+
function split2(line) {
|
|
21491
|
+
return commandWords(line).words;
|
|
21492
|
+
}
|
|
21493
|
+
var init_command_arguments = __esm({
|
|
21494
|
+
"src/command-arguments.ts"() {
|
|
21495
|
+
"use strict";
|
|
21496
|
+
}
|
|
21497
|
+
});
|
|
21498
|
+
|
|
20822
21499
|
// src/classify/material.ts
|
|
20823
21500
|
function nutshell(root, projectId2) {
|
|
20824
21501
|
const active2 = activeAgentId(root);
|
|
@@ -20850,12 +21527,12 @@ function commandForm(surface, name) {
|
|
|
20850
21527
|
function commandCatalogue(program2, surface = "cli") {
|
|
20851
21528
|
const lines = [];
|
|
20852
21529
|
for (const cmd of program2.commands) {
|
|
20853
|
-
const args = cmd.registeredArguments.map(
|
|
21530
|
+
const args = cmd.registeredArguments.map(argumentSummary).join(" ");
|
|
20854
21531
|
lines.push(
|
|
20855
21532
|
` ${commandForm(surface, cmd.name())}${args ? ` ${args}` : ""} \u2014 ${cmd.description()}`
|
|
20856
21533
|
);
|
|
20857
21534
|
for (const sub of cmd.commands) {
|
|
20858
|
-
const subArgs = sub.registeredArguments.map(
|
|
21535
|
+
const subArgs = sub.registeredArguments.map(argumentSummary).join(" ");
|
|
20859
21536
|
lines.push(
|
|
20860
21537
|
` ${sub.name()}${subArgs ? ` ${subArgs}` : ""} \u2014 ${sub.description()}`
|
|
20861
21538
|
);
|
|
@@ -20872,6 +21549,7 @@ function roleCatalogue() {
|
|
|
20872
21549
|
var init_material = __esm({
|
|
20873
21550
|
"src/classify/material.ts"() {
|
|
20874
21551
|
"use strict";
|
|
21552
|
+
init_command_arguments();
|
|
20875
21553
|
init_dist();
|
|
20876
21554
|
init_registry2();
|
|
20877
21555
|
init_state();
|
|
@@ -20884,7 +21562,7 @@ var init_material = __esm({
|
|
|
20884
21562
|
|
|
20885
21563
|
// src/help.ts
|
|
20886
21564
|
function usageOf(cmd, surface = "cli") {
|
|
20887
|
-
const args = cmd.registeredArguments.map(
|
|
21565
|
+
const args = cmd.registeredArguments.map(argumentPlaceholder).join(" ");
|
|
20888
21566
|
const subs = cmd.commands.length > 0 ? " <subcommand>" : "";
|
|
20889
21567
|
const name = surface === "tui" ? `/${cmd.name()}` : cmd.name();
|
|
20890
21568
|
return `${name}${subs}${args ? ` ${args}` : ""}`;
|
|
@@ -20955,12 +21633,33 @@ function renderCommandHelp(program2, name, surface = "cli") {
|
|
|
20955
21633
|
lines.push(
|
|
20956
21634
|
` ${usageOf(sub).padEnd(w)} ${sub.description()}${isDefault ? " (default)" : ""}`
|
|
20957
21635
|
);
|
|
21636
|
+
for (const arg of sub.registeredArguments) {
|
|
21637
|
+
if (!argumentDescription(arg)) continue;
|
|
21638
|
+
lines.push(
|
|
21639
|
+
` ${argumentPlaceholder(arg)} ${argumentDescription(arg)}`
|
|
21640
|
+
);
|
|
21641
|
+
}
|
|
20958
21642
|
for (const opt of sub.options) {
|
|
20959
21643
|
lines.push(` ${opt.flags.padEnd(fw)} ${opt.description}`);
|
|
20960
21644
|
}
|
|
20961
21645
|
}
|
|
20962
21646
|
lines.push("");
|
|
20963
21647
|
}
|
|
21648
|
+
const describedArguments = cmd.registeredArguments.filter(
|
|
21649
|
+
(a) => argumentDescription(a)
|
|
21650
|
+
);
|
|
21651
|
+
if (describedArguments.length > 0) {
|
|
21652
|
+
lines.push("ARGUMENTS", "");
|
|
21653
|
+
const width = Math.max(
|
|
21654
|
+
...describedArguments.map((a) => argumentPlaceholder(a).length)
|
|
21655
|
+
);
|
|
21656
|
+
for (const arg of describedArguments) {
|
|
21657
|
+
lines.push(
|
|
21658
|
+
` ${argumentPlaceholder(arg).padEnd(width)} ${argumentDescription(arg)}`
|
|
21659
|
+
);
|
|
21660
|
+
}
|
|
21661
|
+
lines.push("");
|
|
21662
|
+
}
|
|
20964
21663
|
if (cmd.options.length > 0) {
|
|
20965
21664
|
lines.push("OPTIONS", "");
|
|
20966
21665
|
const w = Math.max(...cmd.options.map((o) => o.flags.length));
|
|
@@ -21045,6 +21744,7 @@ var ORDER2, GROUPS;
|
|
|
21045
21744
|
var init_help = __esm({
|
|
21046
21745
|
"src/help.ts"() {
|
|
21047
21746
|
"use strict";
|
|
21747
|
+
init_command_arguments();
|
|
21048
21748
|
init_material();
|
|
21049
21749
|
ORDER2 = [
|
|
21050
21750
|
{
|
|
@@ -21904,6 +22604,7 @@ var init_Choice = __esm({
|
|
|
21904
22604
|
});
|
|
21905
22605
|
|
|
21906
22606
|
// src/app/commands.ts
|
|
22607
|
+
import { Command, Option } from "commander";
|
|
21907
22608
|
function slashCommands(program2) {
|
|
21908
22609
|
return program2.commands.map((c) => ({ name: `/${c.name()}`, description: c.description() })).sort((a, b) => a.name.localeCompare(b.name));
|
|
21909
22610
|
}
|
|
@@ -21925,23 +22626,33 @@ function completionsFor(program2, input) {
|
|
|
21925
22626
|
return finishedTyping ? [] : slashCommands(program2).filter((c) => c.name.startsWith(head));
|
|
21926
22627
|
}
|
|
21927
22628
|
if (!finishedTyping) return [];
|
|
21928
|
-
const
|
|
21929
|
-
|
|
21930
|
-
const
|
|
21931
|
-
const
|
|
21932
|
-
|
|
21933
|
-
const
|
|
22629
|
+
const parsed = commandWords(trimmed);
|
|
22630
|
+
if (parsed.openQuote) return [];
|
|
22631
|
+
const typed = parsed.words.slice(1);
|
|
22632
|
+
const partial = parsed.complete ? "" : typed.pop() ?? "";
|
|
22633
|
+
const state4 = completionState(command, typed, partial);
|
|
22634
|
+
const cmd = state4.command;
|
|
22635
|
+
if (state4.unknown || state4.pending && (state4.requiresValue || partial === "" || !partial.startsWith("-")))
|
|
22636
|
+
return [];
|
|
22637
|
+
const positional = cmd.registeredArguments[state4.position];
|
|
22638
|
+
const values = positional && !positional.variadic ? (positional.argChoices ?? []).filter((value) => value.startsWith(partial)).map((value) => ({
|
|
22639
|
+
name: value,
|
|
22640
|
+
description: positional.description
|
|
22641
|
+
})) : [];
|
|
22642
|
+
if (state4.literal) return values;
|
|
22643
|
+
const subs = state4.position === 0 ? cmd.commands.filter((sc) => sc.name().startsWith(partial)).map((sc) => ({
|
|
21934
22644
|
name: [sc.name(), placeholders(sc)].filter(Boolean).join(" "),
|
|
21935
22645
|
description: sc.description()
|
|
21936
|
-
}));
|
|
22646
|
+
})) : [];
|
|
21937
22647
|
return [
|
|
21938
22648
|
...subs,
|
|
21939
|
-
...
|
|
21940
|
-
...
|
|
22649
|
+
...values,
|
|
22650
|
+
...flagsOf(cmd, partial, state4.given),
|
|
22651
|
+
...cmd === command ? instruction(cmd, partial) : []
|
|
21941
22652
|
];
|
|
21942
22653
|
}
|
|
21943
22654
|
function placeholders(cmd) {
|
|
21944
|
-
return cmd.registeredArguments.filter((a) => !a.variadic).map(
|
|
22655
|
+
return cmd.registeredArguments.filter((a) => !a.variadic).map(argumentPlaceholder).join(" ");
|
|
21945
22656
|
}
|
|
21946
22657
|
function flagsOf(cmd, partial, given) {
|
|
21947
22658
|
return cmd.options.filter((o) => !o.hidden).map((o) => ({ flag: longest(o.flags), option: o })).filter(({ flag }) => flag !== null && !given.has(flag)).filter(({ flag }) => flag.startsWith(partial)).map(({ flag, option }) => ({
|
|
@@ -21955,14 +22666,109 @@ function longest(flags) {
|
|
|
21955
22666
|
const match = flags.match(/--[a-z][\w-]*/);
|
|
21956
22667
|
return match ? match[0] : null;
|
|
21957
22668
|
}
|
|
21958
|
-
function instruction(cmd, partial
|
|
22669
|
+
function instruction(cmd, partial) {
|
|
21959
22670
|
const variadic = cmd.registeredArguments.find((a) => a.variadic);
|
|
21960
|
-
if (!variadic ||
|
|
22671
|
+
if (!variadic || !"--".startsWith(partial)) return [];
|
|
21961
22672
|
return [{ name: "-- <text>", description: variadic.description }];
|
|
21962
22673
|
}
|
|
22674
|
+
function completionState(command, words, partial) {
|
|
22675
|
+
let position = 0;
|
|
22676
|
+
let pending2;
|
|
22677
|
+
let requiresValue = false;
|
|
22678
|
+
let literal = false;
|
|
22679
|
+
const initial = withoutAncestorOptions(command, words, partial);
|
|
22680
|
+
let unknown = initial === void 0;
|
|
22681
|
+
const given = /* @__PURE__ */ new Set();
|
|
22682
|
+
const tokens = initial ?? [];
|
|
22683
|
+
while (tokens.length) {
|
|
22684
|
+
const word = tokens.shift();
|
|
22685
|
+
if (pending2) {
|
|
22686
|
+
if (requiresValue || !word.startsWith("-") || word === "-") {
|
|
22687
|
+
requiresValue = false;
|
|
22688
|
+
pending2 = pending2.variadic ? pending2 : void 0;
|
|
22689
|
+
continue;
|
|
22690
|
+
}
|
|
22691
|
+
pending2 = void 0;
|
|
22692
|
+
}
|
|
22693
|
+
if (!literal && word === "--") {
|
|
22694
|
+
literal = true;
|
|
22695
|
+
given.add(word);
|
|
22696
|
+
continue;
|
|
22697
|
+
}
|
|
22698
|
+
if (!literal && word.startsWith("-") && word !== "-") {
|
|
22699
|
+
const equals = word.startsWith("--") ? word.indexOf("=") : -1;
|
|
22700
|
+
const flag = equals >= 0 ? word.slice(0, equals) : word;
|
|
22701
|
+
const options = command.options;
|
|
22702
|
+
let option = options.find((o) => o.short === flag || o.long === flag);
|
|
22703
|
+
let inline = equals >= 0;
|
|
22704
|
+
if (!option && !word.startsWith("--") && word.length > 2) {
|
|
22705
|
+
option = options.find((o) => o.short === word.slice(0, 2));
|
|
22706
|
+
if (option) {
|
|
22707
|
+
inline = option.required || option.optional;
|
|
22708
|
+
if (!inline) tokens.unshift(`-${word.slice(2)}`);
|
|
22709
|
+
}
|
|
22710
|
+
}
|
|
22711
|
+
if (!option || inline && word.startsWith("--") && !option.required && !option.optional) {
|
|
22712
|
+
unknown = true;
|
|
22713
|
+
break;
|
|
22714
|
+
}
|
|
22715
|
+
given.add(option.long ?? option.short);
|
|
22716
|
+
if (!inline && (option.required || option.optional)) {
|
|
22717
|
+
pending2 = option;
|
|
22718
|
+
requiresValue = option.required;
|
|
22719
|
+
}
|
|
22720
|
+
continue;
|
|
22721
|
+
}
|
|
22722
|
+
const sub = !literal && position === 0 ? command.commands.find(
|
|
22723
|
+
(c) => c.name() === word || c.aliases().includes(word)
|
|
22724
|
+
) : void 0;
|
|
22725
|
+
if (sub) {
|
|
22726
|
+
command = sub;
|
|
22727
|
+
const remaining = withoutAncestorOptions(command, tokens, partial);
|
|
22728
|
+
if (!remaining) {
|
|
22729
|
+
unknown = true;
|
|
22730
|
+
break;
|
|
22731
|
+
}
|
|
22732
|
+
tokens.splice(0, tokens.length, ...remaining);
|
|
22733
|
+
given.clear();
|
|
22734
|
+
} else {
|
|
22735
|
+
position++;
|
|
22736
|
+
}
|
|
22737
|
+
}
|
|
22738
|
+
return { command, position, given, pending: pending2, requiresValue, literal, unknown };
|
|
22739
|
+
}
|
|
22740
|
+
function withoutAncestorOptions(command, words, partial) {
|
|
22741
|
+
const ancestors = [];
|
|
22742
|
+
for (let scope = command.parent; scope; scope = scope.parent)
|
|
22743
|
+
ancestors.unshift(scope);
|
|
22744
|
+
let remaining = [...words];
|
|
22745
|
+
for (const scope of ancestors) {
|
|
22746
|
+
if (scope.options.length === 0) continue;
|
|
22747
|
+
const parser = new Command().exitOverride().configureOutput({ writeOut: () => {
|
|
22748
|
+
}, writeErr: () => {
|
|
22749
|
+
} });
|
|
22750
|
+
for (const option of scope.options)
|
|
22751
|
+
parser.addOption(new Option(option.flags));
|
|
22752
|
+
let boundary = "--__rook_completion_boundary";
|
|
22753
|
+
while (remaining.includes(boundary) || scope.options.some((o) => o.long === boundary))
|
|
22754
|
+
boundary += "_";
|
|
22755
|
+
let cursor = partial.length > 1 && partial.startsWith("-") ? "--__rook_cursor" : "__rook_cursor";
|
|
22756
|
+
while (remaining.includes(cursor) || scope.options.some((o) => o.long === cursor))
|
|
22757
|
+
cursor += "_";
|
|
22758
|
+
try {
|
|
22759
|
+
const parsed = parser.parseOptions([boundary, ...remaining, cursor]);
|
|
22760
|
+
if (!parsed.unknown.includes(cursor)) return void 0;
|
|
22761
|
+
remaining = parsed.unknown.slice(1, -1);
|
|
22762
|
+
} catch {
|
|
22763
|
+
return void 0;
|
|
22764
|
+
}
|
|
22765
|
+
}
|
|
22766
|
+
return remaining;
|
|
22767
|
+
}
|
|
21963
22768
|
var init_commands = __esm({
|
|
21964
22769
|
"src/app/commands.ts"() {
|
|
21965
22770
|
"use strict";
|
|
22771
|
+
init_command_arguments();
|
|
21966
22772
|
init_help();
|
|
21967
22773
|
}
|
|
21968
22774
|
});
|
|
@@ -22021,10 +22827,8 @@ function InputPrompt({
|
|
|
22021
22827
|
};
|
|
22022
22828
|
const accept = (name) => {
|
|
22023
22829
|
const word = name.split(" ")[0];
|
|
22024
|
-
const
|
|
22025
|
-
const
|
|
22026
|
-
if (!trailing && parts.length > 0) parts.pop();
|
|
22027
|
-
const next = [...parts, word].join(" ") + " ";
|
|
22830
|
+
const prefix = value.replace(/\S*$/, "");
|
|
22831
|
+
const next = `${prefix}${word} `;
|
|
22028
22832
|
edit(next, next.length);
|
|
22029
22833
|
};
|
|
22030
22834
|
const wordStart = (text, at) => {
|
|
@@ -23506,33 +24310,6 @@ var init_ThinkingLine = __esm({
|
|
|
23506
24310
|
|
|
23507
24311
|
// src/app/dispatch.ts
|
|
23508
24312
|
import { CommanderError } from "commander";
|
|
23509
|
-
function split3(line) {
|
|
23510
|
-
const out = [];
|
|
23511
|
-
let current2 = "";
|
|
23512
|
-
let quote2 = null;
|
|
23513
|
-
let started = false;
|
|
23514
|
-
for (const ch of line) {
|
|
23515
|
-
if (quote2) {
|
|
23516
|
-
if (ch === quote2) quote2 = null;
|
|
23517
|
-
else current2 += ch;
|
|
23518
|
-
continue;
|
|
23519
|
-
}
|
|
23520
|
-
if (ch === '"' || ch === "'") {
|
|
23521
|
-
quote2 = ch;
|
|
23522
|
-
started = true;
|
|
23523
|
-
continue;
|
|
23524
|
-
}
|
|
23525
|
-
if (/\s/.test(ch)) {
|
|
23526
|
-
if (current2 || started) out.push(current2);
|
|
23527
|
-
current2 = "";
|
|
23528
|
-
started = false;
|
|
23529
|
-
continue;
|
|
23530
|
-
}
|
|
23531
|
-
current2 += ch;
|
|
23532
|
-
}
|
|
23533
|
-
if (current2 || started) out.push(current2);
|
|
23534
|
-
return out;
|
|
23535
|
-
}
|
|
23536
24313
|
function joinLists(command, argv) {
|
|
23537
24314
|
const out = [];
|
|
23538
24315
|
let cmd = command;
|
|
@@ -23683,7 +24460,7 @@ function resetOptions(cmd) {
|
|
|
23683
24460
|
for (const sub of cmd.commands) resetOptions(sub);
|
|
23684
24461
|
}
|
|
23685
24462
|
async function dispatch(deps) {
|
|
23686
|
-
const argv =
|
|
24463
|
+
const argv = split2(deps.line.trim());
|
|
23687
24464
|
const head = argv[0] ?? "";
|
|
23688
24465
|
const name = head.startsWith("/") ? head.slice(1) : head;
|
|
23689
24466
|
if (!name) return { kind: "unknown", name: "" };
|
|
@@ -23731,6 +24508,8 @@ function withExitOverride(program2) {
|
|
|
23731
24508
|
var init_dispatch = __esm({
|
|
23732
24509
|
"src/app/dispatch.ts"() {
|
|
23733
24510
|
"use strict";
|
|
24511
|
+
init_command_arguments();
|
|
24512
|
+
init_command_arguments();
|
|
23734
24513
|
init_commands();
|
|
23735
24514
|
init_flags();
|
|
23736
24515
|
}
|
|
@@ -25157,7 +25936,7 @@ function fatalExit(err, context = "command") {
|
|
|
25157
25936
|
// src/index.ts
|
|
25158
25937
|
init_flags();
|
|
25159
25938
|
init_client();
|
|
25160
|
-
import { Command } from "commander";
|
|
25939
|
+
import { Argument, Command as Command2 } from "commander";
|
|
25161
25940
|
|
|
25162
25941
|
// src/auth/account.ts
|
|
25163
25942
|
function accountView(identity, credits) {
|
|
@@ -26847,7 +27626,7 @@ import { join as join42 } from "path";
|
|
|
26847
27626
|
|
|
26848
27627
|
// src/judge/judge.ts
|
|
26849
27628
|
init_envelope();
|
|
26850
|
-
import { copyFileSync
|
|
27629
|
+
import { copyFileSync, mkdirSync as mkdirSync27, statSync as statSync17, writeFileSync as writeFileSync25 } from "fs";
|
|
26851
27630
|
import { basename as basename9, join as join39, relative as relative15 } from "path";
|
|
26852
27631
|
import yaml12 from "js-yaml";
|
|
26853
27632
|
|
|
@@ -27808,7 +28587,7 @@ function evidencePack(scope, scenarioLocalId) {
|
|
|
27808
28587
|
record(absolutePath) {
|
|
27809
28588
|
if (seen.has(absolutePath)) return;
|
|
27810
28589
|
try {
|
|
27811
|
-
const size =
|
|
28590
|
+
const size = statSync17(absolutePath).size;
|
|
27812
28591
|
mkdirSync27(dir2, { recursive: true });
|
|
27813
28592
|
const name = `${seen.size + 1}-${basename9(absolutePath)}`;
|
|
27814
28593
|
if (size > PACK_FILE_BYTES_MAX) {
|
|
@@ -27818,7 +28597,7 @@ function evidencePack(scope, scenarioLocalId) {
|
|
|
27818
28597
|
);
|
|
27819
28598
|
return;
|
|
27820
28599
|
}
|
|
27821
|
-
|
|
28600
|
+
copyFileSync(absolutePath, join39(dir2, name));
|
|
27822
28601
|
seen.set(absolutePath, name);
|
|
27823
28602
|
} catch (err) {
|
|
27824
28603
|
seen.set(absolutePath, `(not copied \u2014 ${err.message})`);
|
|
@@ -28497,14 +29276,14 @@ import {
|
|
|
28497
29276
|
mkdirSync as mkdirSync29,
|
|
28498
29277
|
readFileSync as readFileSync38,
|
|
28499
29278
|
readdirSync as readdirSync20,
|
|
28500
|
-
statSync as
|
|
29279
|
+
statSync as statSync19,
|
|
28501
29280
|
writeFileSync as writeFileSync27
|
|
28502
29281
|
} from "fs";
|
|
28503
29282
|
import { join as join41, relative as relative16 } from "path";
|
|
28504
29283
|
|
|
28505
29284
|
// src/run/capture.ts
|
|
28506
29285
|
init_constants();
|
|
28507
|
-
import { copyFileSync as
|
|
29286
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync35, mkdirSync as mkdirSync28, statSync as statSync18, writeFileSync as writeFileSync26 } from "fs";
|
|
28508
29287
|
import { basename as basename10, extname as extname8, join as join40 } from "path";
|
|
28509
29288
|
var MAX_BYTES = 8 * 1024 * 1024;
|
|
28510
29289
|
var MAX_ARTEFACTS = 12;
|
|
@@ -28620,7 +29399,7 @@ async function captureArtifacts(scenarioDir2, data) {
|
|
|
28620
29399
|
continue;
|
|
28621
29400
|
}
|
|
28622
29401
|
try {
|
|
28623
|
-
const stat =
|
|
29402
|
+
const stat = statSync18(source);
|
|
28624
29403
|
if (!stat.isFile()) {
|
|
28625
29404
|
result2.missed.push({ source, reason: "not a file" });
|
|
28626
29405
|
continue;
|
|
@@ -28636,7 +29415,7 @@ async function captureArtifacts(scenarioDir2, data) {
|
|
|
28636
29415
|
});
|
|
28637
29416
|
continue;
|
|
28638
29417
|
}
|
|
28639
|
-
|
|
29418
|
+
copyFileSync2(source, target);
|
|
28640
29419
|
bytes += stat.size;
|
|
28641
29420
|
result2.files.push(target);
|
|
28642
29421
|
} catch {
|
|
@@ -29030,7 +29809,7 @@ function capturedFiles(scenarioDirectory) {
|
|
|
29030
29809
|
const dir2 = join41(scenarioDirectory, "artifacts");
|
|
29031
29810
|
if (!existsSync36(dir2)) return [];
|
|
29032
29811
|
try {
|
|
29033
|
-
return readdirSync20(dir2).map((name) => join41(dir2, name)).filter((at) =>
|
|
29812
|
+
return readdirSync20(dir2).map((name) => join41(dir2, name)).filter((at) => statSync19(at).isFile());
|
|
29034
29813
|
} catch {
|
|
29035
29814
|
return [];
|
|
29036
29815
|
}
|
|
@@ -29251,6 +30030,7 @@ async function executeRun(opts, deps) {
|
|
|
29251
30030
|
}
|
|
29252
30031
|
}
|
|
29253
30032
|
const total = opts.scenarios.length;
|
|
30033
|
+
const planned = total + (opts.judgeOnly?.length ?? 0);
|
|
29254
30034
|
const reachAtStart = resolveReach(opts.root);
|
|
29255
30035
|
const verdicts = [];
|
|
29256
30036
|
const outcomes = /* @__PURE__ */ new Map();
|
|
@@ -29259,6 +30039,7 @@ async function executeRun(opts, deps) {
|
|
|
29259
30039
|
let haltReason;
|
|
29260
30040
|
let next = 0;
|
|
29261
30041
|
let consecutiveUnreached = 0;
|
|
30042
|
+
let missingEvidence = false;
|
|
29262
30043
|
const stop = (why2) => {
|
|
29263
30044
|
if (!halted2) haltReason = why2;
|
|
29264
30045
|
halted2 = true;
|
|
@@ -29268,25 +30049,29 @@ async function executeRun(opts, deps) {
|
|
|
29268
30049
|
const index = next++;
|
|
29269
30050
|
return { scenario: opts.scenarios[index], index };
|
|
29270
30051
|
};
|
|
30052
|
+
const stopDue = () => {
|
|
30053
|
+
if (opts.signal.aborted) {
|
|
30054
|
+
stop("interrupted");
|
|
30055
|
+
return true;
|
|
30056
|
+
}
|
|
30057
|
+
if (!deps.budget.canStart()) {
|
|
30058
|
+
opts.onProgress?.(
|
|
30059
|
+
`${stoppedBecause("budget_halt")} \u2014 stopping before the next scenario`
|
|
30060
|
+
);
|
|
30061
|
+
stop("budget");
|
|
30062
|
+
return true;
|
|
30063
|
+
}
|
|
30064
|
+
const halt = halted();
|
|
30065
|
+
if (halt) {
|
|
30066
|
+
opts.onProgress?.(halt.message);
|
|
30067
|
+
stop(halt.kind);
|
|
30068
|
+
return true;
|
|
30069
|
+
}
|
|
30070
|
+
return false;
|
|
30071
|
+
};
|
|
29271
30072
|
const worker = async () => {
|
|
29272
30073
|
for (; ; ) {
|
|
29273
|
-
if (
|
|
29274
|
-
stop("interrupted");
|
|
29275
|
-
return;
|
|
29276
|
-
}
|
|
29277
|
-
if (!deps.budget.canStart()) {
|
|
29278
|
-
opts.onProgress?.(
|
|
29279
|
-
`${stoppedBecause("budget_halt")} \u2014 stopping before the next scenario`
|
|
29280
|
-
);
|
|
29281
|
-
stop("budget");
|
|
29282
|
-
return;
|
|
29283
|
-
}
|
|
29284
|
-
const halt = halted();
|
|
29285
|
-
if (halt) {
|
|
29286
|
-
opts.onProgress?.(halt.message);
|
|
29287
|
-
stop(halt.kind);
|
|
29288
|
-
return;
|
|
29289
|
-
}
|
|
30074
|
+
if (stopDue()) return;
|
|
29290
30075
|
const item = takeOne();
|
|
29291
30076
|
if (!item) return;
|
|
29292
30077
|
const { scenario: scenario2, index } = item;
|
|
@@ -29367,36 +30152,56 @@ async function executeRun(opts, deps) {
|
|
|
29367
30152
|
}
|
|
29368
30153
|
};
|
|
29369
30154
|
for (const scenario2 of opts.judgeOnly ?? []) {
|
|
29370
|
-
|
|
29371
|
-
|
|
29372
|
-
|
|
30155
|
+
const execution = opts.unavailableEvidence?.has(scenario2.local_id) ? null : readCarriedResponse(opts, scenario2);
|
|
30156
|
+
if (!execution) {
|
|
30157
|
+
opts.onProgress?.(
|
|
30158
|
+
"carried evidence could not be read \u2014 restore it and start a newly approved run",
|
|
30159
|
+
scenario2.local_id
|
|
30160
|
+
);
|
|
30161
|
+
missingEvidence = true;
|
|
30162
|
+
continue;
|
|
30163
|
+
}
|
|
30164
|
+
if (!wants("judge")) {
|
|
30165
|
+
opts.onProgress?.(
|
|
30166
|
+
"not judged \u2014 rerun with --phases judge when you want a verdict",
|
|
30167
|
+
scenario2.local_id
|
|
30168
|
+
);
|
|
30169
|
+
outcomes.set(scenario2.local_id, "unjudged");
|
|
30170
|
+
continue;
|
|
30171
|
+
}
|
|
30172
|
+
if (stopDue()) break;
|
|
29373
30173
|
opts.onProgress?.("judging what the last run executed", scenario2.local_id);
|
|
29374
|
-
const
|
|
29375
|
-
|
|
29376
|
-
|
|
29377
|
-
|
|
29378
|
-
opts,
|
|
30174
|
+
const judgeDeps = {
|
|
30175
|
+
...deps,
|
|
30176
|
+
ctx: {
|
|
30177
|
+
...deps.ctx,
|
|
30178
|
+
runId: opts.upstream?.run_id,
|
|
30179
|
+
runScenarioId: opts.upstream?.scenario_ids[planKey(scenario2.local_id, 1)]
|
|
30180
|
+
}
|
|
30181
|
+
};
|
|
30182
|
+
try {
|
|
30183
|
+
const judged = await judgeScenario(
|
|
30184
|
+
{ root: opts.root, agentId: opts.agentId, runId: opts.runId },
|
|
29379
30185
|
scenario2,
|
|
29380
|
-
|
|
29381
|
-
|
|
29382
|
-
|
|
29383
|
-
|
|
29384
|
-
|
|
29385
|
-
|
|
29386
|
-
|
|
29387
|
-
|
|
29388
|
-
|
|
29389
|
-
|
|
29390
|
-
|
|
29391
|
-
)
|
|
29392
|
-
|
|
29393
|
-
|
|
29394
|
-
|
|
29395
|
-
|
|
29396
|
-
|
|
29397
|
-
|
|
29398
|
-
|
|
29399
|
-
await opts.onVerdict?.(judged.verdict, scenario2, 1);
|
|
30186
|
+
judgeContext(
|
|
30187
|
+
opts,
|
|
30188
|
+
scenario2,
|
|
30189
|
+
unmetNow(opts, scenario2, 1, reachAtStart, false)
|
|
30190
|
+
),
|
|
30191
|
+
execution,
|
|
30192
|
+
judgeDeps,
|
|
30193
|
+
(m) => opts.onProgress?.(m, scenario2.local_id)
|
|
30194
|
+
);
|
|
30195
|
+
credits += judged.credits;
|
|
30196
|
+
verdicts.push(judged.verdict);
|
|
30197
|
+
outcomes.set(scenario2.local_id, outcomeOf(judged.verdict));
|
|
30198
|
+
await opts.onVerdict?.(judged.verdict, scenario2, 1);
|
|
30199
|
+
} catch (err) {
|
|
30200
|
+
opts.onProgress?.(
|
|
30201
|
+
`judge failed: ${err?.message ?? String(err)}`,
|
|
30202
|
+
scenario2.local_id
|
|
30203
|
+
);
|
|
30204
|
+
}
|
|
29400
30205
|
}
|
|
29401
30206
|
const results = await Promise.allSettled(
|
|
29402
30207
|
Array.from(
|
|
@@ -29412,6 +30217,7 @@ async function executeRun(opts, deps) {
|
|
|
29412
30217
|
stop("runner_error");
|
|
29413
30218
|
}
|
|
29414
30219
|
}
|
|
30220
|
+
if (missingEvidence) stop("runner_error");
|
|
29415
30221
|
const order = new Map(opts.scenarios.map((s, i) => [s.local_id, i]));
|
|
29416
30222
|
verdicts.sort((a, b) => {
|
|
29417
30223
|
const byScenario = (order.get(a.scenario_id) ?? 1e9) - (order.get(b.scenario_id) ?? 1e9);
|
|
@@ -29419,7 +30225,7 @@ async function executeRun(opts, deps) {
|
|
|
29419
30225
|
});
|
|
29420
30226
|
return {
|
|
29421
30227
|
verdicts,
|
|
29422
|
-
totals: tally(outcomes, { planned
|
|
30228
|
+
totals: tally(outcomes, { planned, unrunnable: opts.unrunnable }),
|
|
29423
30229
|
credits,
|
|
29424
30230
|
halted: halted2,
|
|
29425
30231
|
haltReason
|
|
@@ -29561,508 +30367,7 @@ init_manifest();
|
|
|
29561
30367
|
init_sync2();
|
|
29562
30368
|
init_materialise();
|
|
29563
30369
|
init_plan_file();
|
|
29564
|
-
|
|
29565
|
-
// src/run/plan.ts
|
|
29566
|
-
init_subagent();
|
|
29567
|
-
init_role_failure();
|
|
29568
|
-
init_registry4();
|
|
29569
|
-
init_context();
|
|
29570
|
-
init_types3();
|
|
29571
|
-
init_env();
|
|
29572
|
-
init_render();
|
|
29573
|
-
import yaml13 from "js-yaml";
|
|
29574
|
-
var PLAN_SCHEMA2 = {
|
|
29575
|
-
type: "object",
|
|
29576
|
-
/*
|
|
29577
|
-
* `rca` is NOT required, and requiring it failed real runs.
|
|
29578
|
-
*
|
|
29579
|
-
* "run_planner: the reply does not match the shape asked for — rca:
|
|
29580
|
-
* required, and absent" is the whole of `/run` refusing because a boolean
|
|
29581
|
-
* with an obvious default was left out. The schema's own description says
|
|
29582
|
-
* "false is the ordinary answer", and `planRun` already reads it as
|
|
29583
|
-
* `Boolean(child.data.rca) || Boolean(input.rca)` — so absence was always
|
|
29584
|
-
* handled everywhere except the validator that rejected it first.
|
|
29585
|
-
*
|
|
29586
|
-
* The three that remain are the answer itself: what to run, what was left
|
|
29587
|
-
* out, and the sentence somebody reads before spending. A reply missing any
|
|
29588
|
-
* of those is not a plan.
|
|
29589
|
-
*/
|
|
29590
|
-
required: ["included", "skipped", "summary"],
|
|
29591
|
-
properties: {
|
|
29592
|
-
/**
|
|
29593
|
-
* The four lists are a PARTITION of the candidates.
|
|
29594
|
-
*
|
|
29595
|
-
* Every candidate lands in exactly one, and rook enforces it — a scenario
|
|
29596
|
-
* in two is ambiguous about whether it costs an agent call, and one in none
|
|
29597
|
-
* is silently dropped. `judge_only` and `carried` are the resume half: rook
|
|
29598
|
-
* works out which scenarios are ELIGIBLE for each from the files, and the
|
|
29599
|
-
* planner decides which of them the request actually wants that done to.
|
|
29600
|
-
*/
|
|
29601
|
-
included: {
|
|
29602
|
-
type: "array",
|
|
29603
|
-
description: "Execute these: call the agent, then judge. One entry per scenario.",
|
|
29604
|
-
items: {
|
|
29605
|
-
type: "object",
|
|
29606
|
-
required: ["scenario_id", "why", "confidence"],
|
|
29607
|
-
properties: {
|
|
29608
|
-
scenario_id: { type: "string" },
|
|
29609
|
-
why: {
|
|
29610
|
-
type: "string",
|
|
29611
|
-
description: "What this one establishes that the others do not. Not 'important coverage', which is true of everything and decides nothing."
|
|
29612
|
-
},
|
|
29613
|
-
confidence: {
|
|
29614
|
-
type: "string",
|
|
29615
|
-
enum: ["high", "medium", "low"],
|
|
29616
|
-
description: "`low` says you are unsure it earns its cost \u2014 exactly what somebody about to approve wants to know."
|
|
29617
|
-
}
|
|
29618
|
-
}
|
|
29619
|
-
}
|
|
29620
|
-
},
|
|
29621
|
-
judge_only: {
|
|
29622
|
-
type: "array",
|
|
29623
|
-
description: "Already executed by the prior run and never judged. A judge call and NO agent call. Only ids listed as ungraded below are eligible.",
|
|
29624
|
-
items: {
|
|
29625
|
-
type: "object",
|
|
29626
|
-
required: ["scenario_id", "why"],
|
|
29627
|
-
properties: {
|
|
29628
|
-
scenario_id: { type: "string" },
|
|
29629
|
-
why: { type: "string" },
|
|
29630
|
-
confidence: { type: "string", enum: ["high", "medium", "low"] }
|
|
29631
|
-
}
|
|
29632
|
-
}
|
|
29633
|
-
},
|
|
29634
|
-
carried: {
|
|
29635
|
-
type: "array",
|
|
29636
|
-
description: "Already decided by the prior run. Copy the verdict forward \u2014 no agent call, no judge call. Only ids listed as decided below are eligible.",
|
|
29637
|
-
items: {
|
|
29638
|
-
type: "object",
|
|
29639
|
-
required: ["scenario_id", "why"],
|
|
29640
|
-
properties: {
|
|
29641
|
-
scenario_id: { type: "string" },
|
|
29642
|
-
why: { type: "string" },
|
|
29643
|
-
confidence: { type: "string", enum: ["high", "medium", "low"] }
|
|
29644
|
-
}
|
|
29645
|
-
}
|
|
29646
|
-
},
|
|
29647
|
-
rca: {
|
|
29648
|
-
type: "boolean",
|
|
29649
|
-
description: "Explain the failure clusters when this run ends. True when the request asks WHY something fails, or when a run this large failing would leave somebody with nowhere to start. It costs one model call per cluster, so false is the ordinary answer."
|
|
29650
|
-
},
|
|
29651
|
-
skipped: {
|
|
29652
|
-
type: "array",
|
|
29653
|
-
description: "One entry per scenario you deliberately left out.",
|
|
29654
|
-
items: {
|
|
29655
|
-
type: "object",
|
|
29656
|
-
required: ["scenario_id", "why", "confidence"],
|
|
29657
|
-
properties: {
|
|
29658
|
-
scenario_id: { type: "string" },
|
|
29659
|
-
why: {
|
|
29660
|
-
type: "string",
|
|
29661
|
-
description: "Why it is not worth running now. A scenario nobody considered and one deliberately excluded look identical afterwards."
|
|
29662
|
-
},
|
|
29663
|
-
confidence: { type: "string", enum: ["high", "medium", "low"] }
|
|
29664
|
-
}
|
|
29665
|
-
}
|
|
29666
|
-
},
|
|
29667
|
-
summary: {
|
|
29668
|
-
type: "string",
|
|
29669
|
-
description: "What this run will establish, and what it will not. The sentence a person reads before deciding to spend."
|
|
29670
|
-
},
|
|
29671
|
-
resume_from: {
|
|
29672
|
-
type: "string",
|
|
29673
|
-
description: "A prior run to carry finished work from. Omit unless proposing one."
|
|
29674
|
-
},
|
|
29675
|
-
/**
|
|
29676
|
-
* Two fields of `run.yaml` the planner may set, and only these two.
|
|
29677
|
-
*
|
|
29678
|
-
* Both are things a person expresses in words — "call this the threshold
|
|
29679
|
-
* sweep", "run them one at a time" — and neither changes what a run MEANS.
|
|
29680
|
-
*
|
|
29681
|
-
* `test_mode` is deliberately NOT here. It decides whether the run is
|
|
29682
|
-
* pinned upstream at all, so a model able to set it could turn a real run
|
|
29683
|
-
* into one that records nothing, or a smoke test into a version-pinned
|
|
29684
|
-
* result. That is a flag, and flags are for people.
|
|
29685
|
-
*
|
|
29686
|
-
* An explicit flag always wins over both — see `applyPlanned`.
|
|
29687
|
-
*/
|
|
29688
|
-
name: {
|
|
29689
|
-
type: "string",
|
|
29690
|
-
description: "A short name for this run, when the request suggests one. Omit rather than inventing a label nobody used."
|
|
29691
|
-
},
|
|
29692
|
-
concurrency: {
|
|
29693
|
-
type: "integer",
|
|
29694
|
-
description: "How many scenarios to run at once, when the request says. One is the default and the right answer for anything hitting a rate-limited API."
|
|
29695
|
-
}
|
|
29696
|
-
}
|
|
29697
|
-
};
|
|
29698
|
-
function staticPlan(input) {
|
|
29699
|
-
const { partition: partition2 } = input;
|
|
29700
|
-
const included = partition2.candidates.map((c) => ({
|
|
29701
|
-
scenario_id: c.local_id,
|
|
29702
|
-
why: "a candidate, and nothing narrowed this run further",
|
|
29703
|
-
confidence: "high"
|
|
29704
|
-
}));
|
|
29705
|
-
const judge_only = [];
|
|
29706
|
-
const carried = [];
|
|
29707
|
-
const skipped = [
|
|
29708
|
-
...partition2.excluded.map((e) => ({
|
|
29709
|
-
scenario_id: e.scenario_id,
|
|
29710
|
-
why: `excluded by the flags: ${e.why}`,
|
|
29711
|
-
confidence: "high"
|
|
29712
|
-
})),
|
|
29713
|
-
...partition2.filtered.map((f) => ({
|
|
29714
|
-
scenario_id: f.scenario_id,
|
|
29715
|
-
why: `left out by the tree: ${f.why}`,
|
|
29716
|
-
confidence: "high"
|
|
29717
|
-
}))
|
|
29718
|
-
];
|
|
29719
|
-
const counts = [
|
|
29720
|
-
included.length ? `${included.length} to execute` : "",
|
|
29721
|
-
// SAID, not implied. Somebody who typed `--resume` expects work to be
|
|
29722
|
-
// reused, and a plan that silently re-ran everything would look like one
|
|
29723
|
-
// that had reused nothing to reuse.
|
|
29724
|
-
input.resumeHint ? `nothing reused from ${input.resumeHint}` : ""
|
|
29725
|
-
].filter(Boolean);
|
|
29726
|
-
return {
|
|
29727
|
-
included,
|
|
29728
|
-
skipped,
|
|
29729
|
-
judge_only,
|
|
29730
|
-
carried,
|
|
29731
|
-
/*
|
|
29732
|
-
* `--rca` and nothing else. There is no sentence for anything to read an
|
|
29733
|
-
* intent out of, and RCA costs a model call per failure cluster — turning
|
|
29734
|
-
* it on because a run happens to be large would spend on an explanation
|
|
29735
|
-
* nobody asked for.
|
|
29736
|
-
*/
|
|
29737
|
-
rca: Boolean(input.rca),
|
|
29738
|
-
summary: counts.length ? `no instruction given \u2014 ${counts.join(", ")}. Every candidate the flags and the tree left.` : "no instruction given, and nothing is left to run.",
|
|
29739
|
-
...input.resumeHint ? { resume_from: input.resumeHint } : {},
|
|
29740
|
-
/*
|
|
29741
|
-
* No `name` and no `concurrency`. Both are proposals a planner makes from
|
|
29742
|
-
* a sentence, and `applyPlanned` already lets an explicit flag win — so
|
|
29743
|
-
* omitting them leaves the flag, or the default, exactly as typed.
|
|
29744
|
-
*/
|
|
29745
|
-
// Nothing was spent. A non-zero cost here would bill a run for a decision
|
|
29746
|
-
// no model was asked to make.
|
|
29747
|
-
credits: 0
|
|
29748
|
-
};
|
|
29749
|
-
}
|
|
29750
|
-
async function planRun(input, deps) {
|
|
29751
|
-
const { partition: partition2 } = input;
|
|
29752
|
-
const flagsAsked = input.flags ?? [];
|
|
29753
|
-
const task4 = [
|
|
29754
|
-
input.instruction ? `## WHAT THE USER ASKED FOR
|
|
29755
|
-
|
|
29756
|
-
${input.instruction}
|
|
29757
|
-
` : "",
|
|
29758
|
-
input.resumeHint ? `## THEY NAMED A RUN
|
|
29759
|
-
|
|
29760
|
-
${input.resumeHint} \u2014 look it up before planning.
|
|
29761
|
-
` : "",
|
|
29762
|
-
/**
|
|
29763
|
-
* The same rendering of the agent every other planner gets.
|
|
29764
|
-
*
|
|
29765
|
-
* From the same function, so a planner deciding what to SPEND cannot be
|
|
29766
|
-
* reasoning about a different agent than the writers described.
|
|
29767
|
-
*/
|
|
29768
|
-
agentContext({
|
|
29769
|
-
spec: input.spec,
|
|
29770
|
-
features: input.features,
|
|
29771
|
-
...input.findings?.length ? { findings: input.findings } : {},
|
|
29772
|
-
...input.callers?.length ? { callers: input.callers } : {},
|
|
29773
|
-
...input.callees?.length ? { callees: input.callees } : {}
|
|
29774
|
-
}),
|
|
29775
|
-
"",
|
|
29776
|
-
/**
|
|
29777
|
-
* How the agent is reached, and what rook can SEE while it runs.
|
|
29778
|
-
*
|
|
29779
|
-
* The second half is what decides whether a scenario is checkable at all,
|
|
29780
|
-
* and the unrunnable list below is computed from it — so the planner is
|
|
29781
|
-
* shown the reason as well as the verdict.
|
|
29782
|
-
*/
|
|
29783
|
-
"## THE PROFILE THIS RUN WILL USE",
|
|
29784
|
-
"",
|
|
29785
|
-
"```yaml",
|
|
29786
|
-
yaml13.dump(
|
|
29787
|
-
{
|
|
29788
|
-
id: input.profile.id,
|
|
29789
|
-
/*
|
|
29790
|
-
* WHICH PHASES, not which transport.
|
|
29791
|
-
*
|
|
29792
|
-
* `kind` and `mode` were here and told the planner the agent was
|
|
29793
|
-
* reached over http, synchronously — neither of which changes what
|
|
29794
|
-
* to run. The phases do: a profile with a `collect` can be run now
|
|
29795
|
-
* and collected later, and one without cannot, so a planner asked
|
|
29796
|
-
* about staging a run needs this and had no way to see it.
|
|
29797
|
-
*/
|
|
29798
|
-
phases: definedPhases(input.profile),
|
|
29799
|
-
multi_turn: Boolean(input.profile.capabilities?.multi_turn),
|
|
29800
|
-
/*
|
|
29801
|
-
* What the profile REPORTS, and it must not contradict the gap block
|
|
29802
|
-
* below it.
|
|
29803
|
-
*
|
|
29804
|
-
* This was hardcoded `false` upstream, because on the old schema
|
|
29805
|
-
* nothing observed a call — `observe.mcp: proxy` was a declaration
|
|
29806
|
-
* with no implementation (#717). A `collect` hook can hand calls back
|
|
29807
|
-
* now, and `capabilities.calls` is what one real invocation showed.
|
|
29808
|
-
* The gate agrees: it raises a `tool_calls` gap only when this is
|
|
29809
|
-
* false.
|
|
29810
|
-
*/
|
|
29811
|
-
observes_tool_calls: Boolean(input.profile.capabilities?.calls),
|
|
29812
|
-
// Names and set/unset only. A planner that can read a token has no
|
|
29813
|
-
// reason to, and this text is one prompt away from a log.
|
|
29814
|
-
needs: requiredVars(input.profile).map((s) => ({
|
|
29815
|
-
variable: s.variable,
|
|
29816
|
-
set_here: !missingVars(input.profile).includes(s.variable),
|
|
29817
|
-
...s.purpose ? { purpose: s.purpose } : {}
|
|
29818
|
-
}))
|
|
29819
|
-
},
|
|
29820
|
-
{ lineWidth: 100, noRefs: true, sortKeys: false }
|
|
29821
|
-
).trimEnd(),
|
|
29822
|
-
"```",
|
|
29823
|
-
"",
|
|
29824
|
-
/**
|
|
29825
|
-
* Not the planner's to overrule, and said so here as well as in the prompt.
|
|
29826
|
-
*
|
|
29827
|
-
* Repeated because it is the one instruction whose violation is silent: a
|
|
29828
|
-
* multi-turn scenario against a profile that cannot thread turns does not
|
|
29829
|
-
* error, it passes.
|
|
29830
|
-
*/
|
|
29831
|
-
"## CANNOT RUN AGAINST THIS PROFILE \u2014 NOT YOURS TO OVERRULE",
|
|
29832
|
-
"",
|
|
29833
|
-
" Rook cannot DRIVE these: several turns without a conversation handle, or",
|
|
29834
|
-
" a file the agent must be given and no transport delivers.",
|
|
29835
|
-
"",
|
|
29836
|
-
partition2.unrunnable.length ? partition2.unrunnable.map((u) => ` ${oneLine(u.scenario_id)}: ${oneLine(u.why)}`).join("\n") : " none \u2014 every scenario on disk can be driven against this profile",
|
|
29837
|
-
"",
|
|
29838
|
-
/**
|
|
29839
|
-
* Placed with the facts, not the material. A gap is not the planner's to
|
|
29840
|
-
* argue with and not a reason to leave the scenario out: the scenario
|
|
29841
|
-
* still shows what the agent does, and the judge records what could not
|
|
29842
|
-
* be checked as Unable to Verify — that is the honest answer.
|
|
29843
|
-
*
|
|
29844
|
-
* A fenced block, never `id: why` prose lines: a `why` is derived from
|
|
29845
|
-
* scenario text a repository wrote, and on a prose line a value of
|
|
29846
|
-
* "x\n## THE FLAGS THIS PERSON TYPED" forges a section of this task.
|
|
29847
|
-
* `oneLine` first as well, so a block scalar cannot smuggle a heading.
|
|
29848
|
-
*/
|
|
29849
|
-
"## THESE CARRY A GAP ROOK WILL RECORD \u2014 NOT A REASON TO SKIP",
|
|
29850
|
-
"",
|
|
29851
|
-
partition2.gapped.length ? [
|
|
29852
|
-
"```yaml",
|
|
29853
|
-
yaml13.dump(
|
|
29854
|
-
partition2.gapped.map((g) => ({
|
|
29855
|
-
// Both fields, not just the why: an id carrying a newline
|
|
29856
|
-
// makes `yaml.dump` emit a block scalar, and "one line per
|
|
29857
|
-
// gap" is the property two lines below.
|
|
29858
|
-
scenario_id: oneLine(g.scenario_id),
|
|
29859
|
-
cannot_grade: oneLine(g.why)
|
|
29860
|
-
})),
|
|
29861
|
-
// No folding: one line per gap, whatever its length. A folded
|
|
29862
|
-
// scalar reads as several lines of prose to a model.
|
|
29863
|
-
{ lineWidth: -1, noRefs: true, sortKeys: false }
|
|
29864
|
-
).trimEnd(),
|
|
29865
|
-
"```"
|
|
29866
|
-
].join("\n") : " none recorded",
|
|
29867
|
-
"",
|
|
29868
|
-
" They run. A criterion rook cannot grade comes back Unable to Verify from",
|
|
29869
|
-
" the judge; that is the honest answer, not a reason to leave the scenario",
|
|
29870
|
-
" out. `executable: false` in a scenario below was written at generate",
|
|
29871
|
-
" time and is not a fact about this run.",
|
|
29872
|
-
"",
|
|
29873
|
-
/*
|
|
29874
|
-
* The flags, verbatim, and stated as binding.
|
|
29875
|
-
*
|
|
29876
|
-
* The excluded ids used to be shown as material the planner "may disagree
|
|
29877
|
-
* with", so `--only SC-001` was a suggestion — and a planner that decided
|
|
29878
|
-
* the neighbouring scenarios were worth running got them, because nothing
|
|
29879
|
-
* downstream checked. Naming the flags as well as their effect matters:
|
|
29880
|
-
* a list of excluded ids does not tell a model that a PERSON narrowed
|
|
29881
|
-
* this, and that is the fact that decides whether it may argue.
|
|
29882
|
-
*/
|
|
29883
|
-
...flagsAsked.length ? [
|
|
29884
|
-
"## THE FLAGS THIS PERSON TYPED \u2014 BINDING, NOT YOURS TO OVERRULE",
|
|
29885
|
-
"",
|
|
29886
|
-
...flagsAsked.map((f) => ` ${f}`),
|
|
29887
|
-
"",
|
|
29888
|
-
" They narrowed this run deliberately. Every scenario those flags",
|
|
29889
|
-
" excluded is listed below and is OUT \u2014 naming one in included,",
|
|
29890
|
-
" judge_only or carried is an error, and rook drops it. If the flags",
|
|
29891
|
-
" look wrong for what they asked, say so in summary rather than",
|
|
29892
|
-
" planning around them.",
|
|
29893
|
-
"",
|
|
29894
|
-
"## WHAT THOSE FLAGS EXCLUDED \u2014 out of this run",
|
|
29895
|
-
"",
|
|
29896
|
-
partition2.excluded.length ? partition2.excluded.map((f) => ` ${f.scenario_id}: ${f.why}`).join("\n") : " nothing \u2014 every scenario on disk matched them",
|
|
29897
|
-
""
|
|
29898
|
-
] : [],
|
|
29899
|
-
"## WHAT THE TREE LEFT OUT \u2014 material, and you may disagree",
|
|
29900
|
-
"",
|
|
29901
|
-
partition2.filtered.length ? partition2.filtered.map((f) => ` ${f.scenario_id}: ${f.why}`).join("\n") : " nothing \u2014 no scenario was dropped for the state it is in",
|
|
29902
|
-
"",
|
|
29903
|
-
"## YOUR CANDIDATES \u2014 everything you may choose from",
|
|
29904
|
-
"",
|
|
29905
|
-
partition2.candidates.length ? partition2.candidates.map((c) => ` ${c.local_id} ${c.title}`).join("\n") : " nothing \u2014 the flags excluded everything runnable",
|
|
29906
|
-
"",
|
|
29907
|
-
...input.stale.length ? [
|
|
29908
|
-
"## THESE PIN OLDER FEATURE TEXT",
|
|
29909
|
-
"",
|
|
29910
|
-
` ${input.stale.join(", ")}`,
|
|
29911
|
-
"",
|
|
29912
|
-
"They still run \u2014 an outdated test that passes is information. What must",
|
|
29913
|
-
"not happen is a plan describing them as though they were written",
|
|
29914
|
-
"against the feature as it stands.",
|
|
29915
|
-
""
|
|
29916
|
-
] : [],
|
|
29917
|
-
/**
|
|
29918
|
-
* Every scenario, in full, and LAST — the state block.
|
|
29919
|
-
*
|
|
29920
|
-
* Titles and counts cannot tell six behaviours from six rewordings, and
|
|
29921
|
-
* that difference is exactly what decides whether a run is worth its cost.
|
|
29922
|
-
* Last because it is the part that changes every run, which is what prompt
|
|
29923
|
-
* caching wants.
|
|
29924
|
-
*/
|
|
29925
|
-
"## EVERY SCENARIO ON DISK",
|
|
29926
|
-
"",
|
|
29927
|
-
"```yaml",
|
|
29928
|
-
yaml13.dump(input.scenarios, { lineWidth: 100, noRefs: true, sortKeys: false }).trimEnd(),
|
|
29929
|
-
"```",
|
|
29930
|
-
""
|
|
29931
|
-
].filter(Boolean).join("\n");
|
|
29932
|
-
const child = await spawnSubagent(
|
|
29933
|
-
{
|
|
29934
|
-
role: "run_planner",
|
|
29935
|
-
label: `${partition2.candidates.length} candidate(s)`,
|
|
29936
|
-
task: task4,
|
|
29937
|
-
// The BUDGET phase, not the command. `plan_scenarios` bills to
|
|
29938
|
-
// "generate" for the same reason: a planner is charged to the work it is
|
|
29939
|
-
// planning, so one bucket covers deciding and doing.
|
|
29940
|
-
phase: "execute",
|
|
29941
|
-
responseSchema: PLAN_SCHEMA2,
|
|
29942
|
-
tools: toolsForRole("run_planner", { root: input.root, mcpAgent: null })
|
|
29943
|
-
},
|
|
29944
|
-
deps
|
|
29945
|
-
);
|
|
29946
|
-
if (!child.ok || child.data == null)
|
|
29947
|
-
throw new Error(roleFailure("run_planner", child));
|
|
29948
|
-
const forbidden = /* @__PURE__ */ new Set([
|
|
29949
|
-
...partition2.unrunnable.map((u) => u.scenario_id),
|
|
29950
|
-
...partition2.excluded.map((e) => e.scenario_id)
|
|
29951
|
-
]);
|
|
29952
|
-
const known = new Set(input.scenarios.map((s) => s.local_id));
|
|
29953
|
-
const included = (child.data.included ?? []).filter(
|
|
29954
|
-
(e) => e?.scenario_id && known.has(e.scenario_id) && !forbidden.has(e.scenario_id)
|
|
29955
|
-
);
|
|
29956
|
-
const skipped = (child.data.skipped ?? []).filter(
|
|
29957
|
-
(e) => e?.scenario_id && known.has(e.scenario_id)
|
|
29958
|
-
);
|
|
29959
|
-
const canJudge = new Set(input.ungraded ?? []);
|
|
29960
|
-
const canCarry = new Set(input.decided ?? []);
|
|
29961
|
-
const eligible = (entries, allowed) => {
|
|
29962
|
-
const kept = [];
|
|
29963
|
-
const demoted = [];
|
|
29964
|
-
for (const e of entries ?? []) {
|
|
29965
|
-
if (!e?.scenario_id || !known.has(e.scenario_id)) continue;
|
|
29966
|
-
if (forbidden.has(e.scenario_id)) continue;
|
|
29967
|
-
(allowed.has(e.scenario_id) ? kept : demoted).push(e);
|
|
29968
|
-
}
|
|
29969
|
-
return { kept, demoted };
|
|
29970
|
-
};
|
|
29971
|
-
const judgeOnly = eligible(child.data.judge_only, canJudge);
|
|
29972
|
-
const carried = eligible(child.data.carried, canCarry);
|
|
29973
|
-
const claimed = /* @__PURE__ */ new Set();
|
|
29974
|
-
const takeOnce = (entries) => entries.filter(
|
|
29975
|
-
(e) => !claimed.has(e.scenario_id) && claimed.add(e.scenario_id)
|
|
29976
|
-
);
|
|
29977
|
-
const finalIncluded = takeOnce([
|
|
29978
|
-
...included,
|
|
29979
|
-
...judgeOnly.demoted,
|
|
29980
|
-
...carried.demoted
|
|
29981
|
-
]);
|
|
29982
|
-
const finalJudgeOnly = takeOnce(judgeOnly.kept);
|
|
29983
|
-
const finalCarried = takeOnce(carried.kept);
|
|
29984
|
-
return {
|
|
29985
|
-
included: finalIncluded,
|
|
29986
|
-
skipped,
|
|
29987
|
-
judge_only: finalJudgeOnly,
|
|
29988
|
-
carried: finalCarried,
|
|
29989
|
-
/**
|
|
29990
|
-
* The flag forces it ON and never off.
|
|
29991
|
-
*
|
|
29992
|
-
* A planner that declined costs nothing to overrule, and somebody who typed
|
|
29993
|
-
* `--rca` has asked plainly. The reverse — a flag that silences a planner
|
|
29994
|
-
* which decided an explanation was needed — would hide the reasoning at the
|
|
29995
|
-
* moment it is most wanted.
|
|
29996
|
-
*/
|
|
29997
|
-
rca: Boolean(child.data.rca) || Boolean(input.rca),
|
|
29998
|
-
summary: child.data.summary ?? "",
|
|
29999
|
-
...child.data.resume_from ? { resume_from: child.data.resume_from } : {},
|
|
30000
|
-
...child.data.name ? { name: child.data.name } : {},
|
|
30001
|
-
...child.data.concurrency ? { concurrency: child.data.concurrency } : {},
|
|
30002
|
-
credits: child.credits
|
|
30003
|
-
};
|
|
30004
|
-
}
|
|
30005
|
-
var MAX_PLANNED_CONCURRENCY = 8;
|
|
30006
|
-
function applyPlanned(plan2, flags) {
|
|
30007
|
-
const proposed = plan2.concurrency ?? 1;
|
|
30008
|
-
return {
|
|
30009
|
-
...flags.name ?? plan2.name ? { name: flags.name ?? plan2.name } : {},
|
|
30010
|
-
concurrency: flags.concurrency ?? Math.min(Math.max(1, Math.trunc(proposed)), MAX_PLANNED_CONCURRENCY)
|
|
30011
|
-
};
|
|
30012
|
-
}
|
|
30013
|
-
function renderRunPlan(plan2, view) {
|
|
30014
|
-
const gap = new Map(view.gapped.map((g) => [g.scenario_id, g.why]));
|
|
30015
|
-
const excluded = new Set(view.excluded.map((e) => e.scenario_id));
|
|
30016
|
-
const gapLine = (id) => {
|
|
30017
|
-
const why2 = gap.get(id);
|
|
30018
|
-
return why2 === void 0 ? [] : [` cannot grade: ${oneLine(why2)}`];
|
|
30019
|
-
};
|
|
30020
|
-
const lines = [`plan: ${plan2.included.length} scenario(s) to run`];
|
|
30021
|
-
for (const e of plan2.included) {
|
|
30022
|
-
const sure = e.confidence && e.confidence !== "high" ? ` (${e.confidence} confidence)` : "";
|
|
30023
|
-
lines.push(` ${oneLine(e.scenario_id)}${sure}`);
|
|
30024
|
-
lines.push(` ${oneLine(e.why)}`);
|
|
30025
|
-
lines.push(...gapLine(e.scenario_id));
|
|
30026
|
-
}
|
|
30027
|
-
for (const e of plan2.judge_only) {
|
|
30028
|
-
lines.push(` ${oneLine(e.scenario_id)} judge only \u2014 ${oneLine(e.why)}`);
|
|
30029
|
-
lines.push(...gapLine(e.scenario_id));
|
|
30030
|
-
}
|
|
30031
|
-
for (const e of plan2.carried) {
|
|
30032
|
-
lines.push(` ${oneLine(e.scenario_id)} carried \u2014 ${oneLine(e.why)}`);
|
|
30033
|
-
lines.push(...gapLine(e.scenario_id));
|
|
30034
|
-
}
|
|
30035
|
-
for (const e of plan2.skipped) {
|
|
30036
|
-
lines.push(` ${oneLine(e.scenario_id)} skipped \u2014 ${oneLine(e.why)}`);
|
|
30037
|
-
const why2 = gap.get(e.scenario_id);
|
|
30038
|
-
if (why2 === void 0) continue;
|
|
30039
|
-
lines.push(
|
|
30040
|
-
excluded.has(e.scenario_id) ? ` carries a gap: ${oneLine(why2)}` : ` carries a gap, which is not a reason to skip: ${oneLine(why2)}`
|
|
30041
|
-
);
|
|
30042
|
-
}
|
|
30043
|
-
if (view.unrunnable.length)
|
|
30044
|
-
lines.push(` ${view.unrunnable.length} cannot run against this profile`);
|
|
30045
|
-
if (plan2.resume_from)
|
|
30046
|
-
lines.push(` carrying finished work from ${plan2.resume_from}`);
|
|
30047
|
-
return lines;
|
|
30048
|
-
}
|
|
30049
|
-
function decidedElsewhere(plan2) {
|
|
30050
|
-
return new Set(
|
|
30051
|
-
[...plan2.judge_only ?? [], ...plan2.carried ?? []].map(
|
|
30052
|
-
(e) => e.scenario_id
|
|
30053
|
-
)
|
|
30054
|
-
);
|
|
30055
|
-
}
|
|
30056
|
-
function gapLines(view, plan2) {
|
|
30057
|
-
const does = /* @__PURE__ */ new Map();
|
|
30058
|
-
for (const e of plan2.included) does.set(e.scenario_id, "runs");
|
|
30059
|
-
for (const e of plan2.judge_only ?? []) does.set(e.scenario_id, "is judged");
|
|
30060
|
-
return view.gapped.filter((g) => does.has(g.scenario_id)).map(
|
|
30061
|
-
(g) => `${oneLine(g.scenario_id)}: ${does.get(g.scenario_id)} \u2014 cannot grade: ${oneLine(g.why)}`
|
|
30062
|
-
);
|
|
30063
|
-
}
|
|
30064
|
-
|
|
30065
|
-
// src/run/run.ts
|
|
30370
|
+
init_plan2();
|
|
30066
30371
|
init_render();
|
|
30067
30372
|
init_flags();
|
|
30068
30373
|
init_tally();
|
|
@@ -30079,7 +30384,7 @@ import {
|
|
|
30079
30384
|
mkdirSync as mkdirSync31,
|
|
30080
30385
|
readFileSync as readFileSync41,
|
|
30081
30386
|
readdirSync as readdirSync22,
|
|
30082
|
-
statSync as
|
|
30387
|
+
statSync as statSync20,
|
|
30083
30388
|
writeFileSync as writeFileSync29
|
|
30084
30389
|
} from "fs";
|
|
30085
30390
|
import { dirname as dirname23, join as join45 } from "path";
|
|
@@ -30182,7 +30487,7 @@ async function uploadDir(scope, dir2, kind, scenarioId, prefix) {
|
|
|
30182
30487
|
for (const name of names) {
|
|
30183
30488
|
const at = join45(dir2, name);
|
|
30184
30489
|
try {
|
|
30185
|
-
if (!
|
|
30490
|
+
if (!statSync20(at).isFile()) continue;
|
|
30186
30491
|
} catch {
|
|
30187
30492
|
continue;
|
|
30188
30493
|
}
|
|
@@ -30287,6 +30592,7 @@ async function uploadReport(ctx, upstreamRunId, root, agentId, runId, report2) {
|
|
|
30287
30592
|
// src/run/upstream.ts
|
|
30288
30593
|
init_client();
|
|
30289
30594
|
init_materialise();
|
|
30595
|
+
init_plan2();
|
|
30290
30596
|
init_plan_file();
|
|
30291
30597
|
import { readFileSync as readFileSync42 } from "fs";
|
|
30292
30598
|
import { join as join46 } from "path";
|
|
@@ -30449,7 +30755,7 @@ async function reconcileRuns(ctx, root, agentId, say2) {
|
|
|
30449
30755
|
if (!runId) continue;
|
|
30450
30756
|
const reported = new Set(plan2.upstream?.reported ?? []);
|
|
30451
30757
|
const owed = [];
|
|
30452
|
-
for (const entry of plan2
|
|
30758
|
+
for (const entry of judgedHere(plan2)) {
|
|
30453
30759
|
const attempts = 8;
|
|
30454
30760
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
30455
30761
|
const key2 = planKey(entry.scenario_id, attempt);
|
|
@@ -30802,7 +31108,7 @@ async function run3(opts, deps) {
|
|
|
30802
31108
|
why: r.why ?? `continuing ${opts.continueRun}`
|
|
30803
31109
|
}));
|
|
30804
31110
|
plan2 = {
|
|
30805
|
-
included: asEntries(prior
|
|
31111
|
+
included: asEntries(judgedHere(prior)),
|
|
30806
31112
|
skipped: asEntries(prior.skipped ?? []),
|
|
30807
31113
|
/*
|
|
30808
31114
|
* Both empty, and neither is a resume.
|
|
@@ -30852,12 +31158,6 @@ async function run3(opts, deps) {
|
|
|
30852
31158
|
partition: split4,
|
|
30853
31159
|
...opts.resumeHint ? { resumeHint: opts.resumeHint } : {},
|
|
30854
31160
|
...opts.rca ? { rca: true } : {}
|
|
30855
|
-
/*
|
|
30856
|
-
* `eligible` is NOT passed, and `staticPlan`'s header says why: the
|
|
30857
|
-
* lists it would fill are dropped a hundred lines below, where
|
|
30858
|
-
* `included` is built from `plan.included` alone. Handing them over
|
|
30859
|
-
* would settle scenarios that then get no folder and no verdict.
|
|
30860
|
-
*/
|
|
30861
31161
|
});
|
|
30862
31162
|
credits += planned.credits;
|
|
30863
31163
|
plan2 = planned;
|
|
@@ -30952,48 +31252,102 @@ async function run3(opts, deps) {
|
|
|
30952
31252
|
reason: "no plan"
|
|
30953
31253
|
};
|
|
30954
31254
|
const byId = new Map(scenarios2.map((s) => [s.local_id, s]));
|
|
30955
|
-
const
|
|
31255
|
+
const acted = scenariosOf(plan2).map(
|
|
30956
31256
|
(e) => (opts.continueRun ? loadSnapshot(root, agentId, runId, e.scenario_id) : null) ?? byId.get(e.scenario_id)
|
|
30957
31257
|
).filter((s) => Boolean(s));
|
|
31258
|
+
const actedById = new Map(acted.map((s) => [s.local_id, s]));
|
|
30958
31259
|
if (!opts.continueRun) {
|
|
30959
|
-
materialise(root, agentId, runId,
|
|
31260
|
+
materialise(root, agentId, runId, acted);
|
|
30960
31261
|
materialiseContext(root, agentId, runId, { spec, features, profile });
|
|
30961
31262
|
}
|
|
30962
|
-
const carriedIds = new Set(
|
|
30963
|
-
const judgeOnlyIds = new Set(plan2.judge_only.map((e) => e.scenario_id));
|
|
31263
|
+
const carriedIds = /* @__PURE__ */ new Set();
|
|
30964
31264
|
let carried = 0;
|
|
30965
31265
|
const toJudgeOnly = [];
|
|
30966
|
-
|
|
30967
|
-
|
|
30968
|
-
|
|
30969
|
-
|
|
31266
|
+
const unavailableEvidence = /* @__PURE__ */ new Set();
|
|
31267
|
+
const planFailure = (err) => {
|
|
31268
|
+
const reason = `cannot read or update ${planPath(root, agentId, runId)}: ${err?.message ?? String(err)}`;
|
|
31269
|
+
say2("error", `${reason} \u2014 restore access and create a new run; no target-agent work started`);
|
|
31270
|
+
return { runId, credits, halted: true, reason };
|
|
31271
|
+
};
|
|
31272
|
+
const moved = [];
|
|
31273
|
+
if (resumeFrom) {
|
|
31274
|
+
const wanted = [
|
|
31275
|
+
...plan2.carried.map((entry) => ({ entry, as: "carried" })),
|
|
31276
|
+
...plan2.judge_only.map((entry) => ({ entry, as: "judge_only" }))
|
|
31277
|
+
];
|
|
31278
|
+
for (const { entry, as } of wanted) {
|
|
31279
|
+
const sc = actedById.get(entry.scenario_id);
|
|
31280
|
+
if (!sc) continue;
|
|
30970
31281
|
const what = carryForward(
|
|
30971
31282
|
root,
|
|
30972
31283
|
agentId,
|
|
30973
31284
|
{ runId: resumeFrom, scenarioId: sc.local_id },
|
|
30974
|
-
{ runId, scenarioId: sc.local_id }
|
|
31285
|
+
{ runId, scenarioId: sc.local_id },
|
|
31286
|
+
{ verdict: as === "carried" }
|
|
30975
31287
|
);
|
|
30976
|
-
if (
|
|
31288
|
+
if (as === "carried" && what === "verdict") {
|
|
30977
31289
|
carried++;
|
|
31290
|
+
carriedIds.add(sc.local_id);
|
|
30978
31291
|
continue;
|
|
30979
31292
|
}
|
|
30980
|
-
if (what === "response" || what === "verdict")
|
|
31293
|
+
if (what === "response" || what === "verdict") {
|
|
31294
|
+
toJudgeOnly.push(sc);
|
|
31295
|
+
if (as === "carried")
|
|
31296
|
+
moved.push({
|
|
31297
|
+
entry,
|
|
31298
|
+
why: `${resumeFrom} no longer holds a readable verdict.yaml \u2014 judged here instead`
|
|
31299
|
+
});
|
|
31300
|
+
continue;
|
|
31301
|
+
}
|
|
31302
|
+
unavailableEvidence.add(sc.local_id);
|
|
31303
|
+
toJudgeOnly.push(sc);
|
|
31304
|
+
moved.push({
|
|
31305
|
+
entry,
|
|
31306
|
+
why: `${resumeFrom} no longer holds a readable response.json \u2014 nothing to judge, and this run was not approved to execute it`
|
|
31307
|
+
});
|
|
30981
31308
|
}
|
|
30982
31309
|
if (carried > 0) {
|
|
30983
31310
|
say2("system", `carried ${carried} verdict(s) forward from ${resumeFrom}`);
|
|
30984
31311
|
}
|
|
30985
|
-
if (toJudgeOnly.length >
|
|
31312
|
+
if (toJudgeOnly.length > unavailableEvidence.size && selection.phases.includes("judge")) {
|
|
30986
31313
|
say2(
|
|
30987
31314
|
"system",
|
|
30988
|
-
`${toJudgeOnly.length} already executed \u2014 judging without calling the agent`
|
|
31315
|
+
`${toJudgeOnly.length - unavailableEvidence.size} already executed \u2014 judging without calling the agent`
|
|
30989
31316
|
);
|
|
30990
31317
|
}
|
|
31318
|
+
if (moved.length > 0) {
|
|
31319
|
+
for (const m of moved) say2("warn", `${m.entry.scenario_id}: ${m.why}`);
|
|
31320
|
+
try {
|
|
31321
|
+
const before = loadPlanOrThrow(root, agentId, runId);
|
|
31322
|
+
const movedIds = new Set(moved.map((m) => m.entry.scenario_id));
|
|
31323
|
+
const row = (m) => ({
|
|
31324
|
+
scenario_id: m.entry.scenario_id,
|
|
31325
|
+
why: m.why,
|
|
31326
|
+
confidence: "high"
|
|
31327
|
+
});
|
|
31328
|
+
savePlan(root, agentId, {
|
|
31329
|
+
...before,
|
|
31330
|
+
judge_only: [
|
|
31331
|
+
...(before.judge_only ?? []).filter(
|
|
31332
|
+
(e) => !movedIds.has(e.scenario_id)
|
|
31333
|
+
),
|
|
31334
|
+
...moved.map(row)
|
|
31335
|
+
],
|
|
31336
|
+
carried: (before.carried ?? []).filter(
|
|
31337
|
+
(e) => !movedIds.has(e.scenario_id)
|
|
31338
|
+
)
|
|
31339
|
+
});
|
|
31340
|
+
} catch (err) {
|
|
31341
|
+
return planFailure(err);
|
|
31342
|
+
}
|
|
31343
|
+
}
|
|
30991
31344
|
}
|
|
30992
31345
|
const settled = /* @__PURE__ */ new Set([
|
|
30993
|
-
...
|
|
31346
|
+
...carriedIds,
|
|
30994
31347
|
...toJudgeOnly.map((s) => s.local_id)
|
|
30995
31348
|
]);
|
|
30996
|
-
const toExecute =
|
|
31349
|
+
const toExecute = acted.filter((s) => !settled.has(s.local_id));
|
|
31350
|
+
const own = acted.filter((s) => !carriedIds.has(s.local_id));
|
|
30997
31351
|
const upstreamCtx = {
|
|
30998
31352
|
api: opts.api,
|
|
30999
31353
|
projectId: opts.projectId,
|
|
@@ -31001,7 +31355,12 @@ async function run3(opts, deps) {
|
|
|
31001
31355
|
testMode: Boolean(opts.testMode)
|
|
31002
31356
|
};
|
|
31003
31357
|
const revisionOf = state4?.revisions?.scenarios ?? {};
|
|
31004
|
-
|
|
31358
|
+
let saved;
|
|
31359
|
+
try {
|
|
31360
|
+
saved = loadPlanOrThrow(root, agentId, runId);
|
|
31361
|
+
} catch (err) {
|
|
31362
|
+
return planFailure(err);
|
|
31363
|
+
}
|
|
31005
31364
|
let upstream = null;
|
|
31006
31365
|
try {
|
|
31007
31366
|
upstream = await startRunUpstream(
|
|
@@ -31025,7 +31384,7 @@ async function run3(opts, deps) {
|
|
|
31025
31384
|
// One or the other, never one field meaning both.
|
|
31026
31385
|
...state4?.profile_ids?.[profile.id] ? { profileId: state4.profile_ids[profile.id] } : { profileLocalId: profile.id }
|
|
31027
31386
|
},
|
|
31028
|
-
|
|
31387
|
+
own,
|
|
31029
31388
|
revisionOf
|
|
31030
31389
|
);
|
|
31031
31390
|
} catch (err) {
|
|
@@ -31066,6 +31425,7 @@ async function run3(opts, deps) {
|
|
|
31066
31425
|
profile,
|
|
31067
31426
|
scenarios: toExecute,
|
|
31068
31427
|
...toJudgeOnly.length > 0 ? { judgeOnly: toJudgeOnly } : {},
|
|
31428
|
+
unavailableEvidence,
|
|
31069
31429
|
unrunnable: split4.unrunnable.length,
|
|
31070
31430
|
// The agent, once, for every judge this run spawns.
|
|
31071
31431
|
spec,
|
|
@@ -31166,10 +31526,6 @@ async function run3(opts, deps) {
|
|
|
31166
31526
|
}
|
|
31167
31527
|
const totals = {
|
|
31168
31528
|
...result2.totals,
|
|
31169
|
-
// Judged-without-executing counts as PLANNED work this run did; carried
|
|
31170
|
-
// verdicts did not. A resume that judged two and inherited nine did not
|
|
31171
|
-
// run eleven, and did not run two either.
|
|
31172
|
-
planned: toExecute.length + toJudgeOnly.length,
|
|
31173
31529
|
carried_forward: carried
|
|
31174
31530
|
};
|
|
31175
31531
|
await verdicts.flush();
|
|
@@ -31182,7 +31538,9 @@ async function run3(opts, deps) {
|
|
|
31182
31538
|
const reported = opts.continueRun ? tallyFromVerdicts(allVerdicts, {
|
|
31183
31539
|
planned: totals.planned,
|
|
31184
31540
|
unrunnable: split4.unrunnable.length,
|
|
31185
|
-
|
|
31541
|
+
// Nothing is carried on a continuation; what the resume carried is on
|
|
31542
|
+
// its plan, and a later pass still inherited exactly that much.
|
|
31543
|
+
carriedForward: (saved.carried ?? []).length
|
|
31186
31544
|
}) : totals;
|
|
31187
31545
|
const invokedHooks = selection.phases.some(
|
|
31188
31546
|
(p) => HOOK_PHASES.includes(p)
|
|
@@ -31201,7 +31559,7 @@ async function run3(opts, deps) {
|
|
|
31201
31559
|
spec,
|
|
31202
31560
|
features,
|
|
31203
31561
|
verdicts: allVerdicts,
|
|
31204
|
-
scenarios:
|
|
31562
|
+
scenarios: own,
|
|
31205
31563
|
totals: reported,
|
|
31206
31564
|
metrics,
|
|
31207
31565
|
say: say2,
|
|
@@ -31404,8 +31762,8 @@ import {
|
|
|
31404
31762
|
mkdirSync as mkdirSync32,
|
|
31405
31763
|
mkdtempSync,
|
|
31406
31764
|
readdirSync as readdirSync23,
|
|
31407
|
-
rmSync as
|
|
31408
|
-
statSync as
|
|
31765
|
+
rmSync as rmSync10,
|
|
31766
|
+
statSync as statSync21,
|
|
31409
31767
|
writeFileSync as writeFileSync31
|
|
31410
31768
|
} from "fs";
|
|
31411
31769
|
import { tmpdir } from "os";
|
|
@@ -31479,10 +31837,10 @@ function exportLogs(opts) {
|
|
|
31479
31837
|
const out = destination(opts.out, name, tool.ext);
|
|
31480
31838
|
mkdirSync32(dirname24(out), { recursive: true });
|
|
31481
31839
|
build(tool.cmd, staging, name, out);
|
|
31482
|
-
const bytes = existsSync40(out) ?
|
|
31840
|
+
const bytes = existsSync40(out) ? statSync21(out).size : 0;
|
|
31483
31841
|
return { path: out, included, bytes };
|
|
31484
31842
|
} finally {
|
|
31485
|
-
|
|
31843
|
+
rmSync10(staging, { recursive: true, force: true });
|
|
31486
31844
|
}
|
|
31487
31845
|
}
|
|
31488
31846
|
function notALink(src) {
|
|
@@ -31497,11 +31855,11 @@ function scrub(box) {
|
|
|
31497
31855
|
for (const entry of readdirSync23(dir2, { withFileTypes: true })) {
|
|
31498
31856
|
const at = join47(dir2, entry.name);
|
|
31499
31857
|
if (entry.isSymbolicLink()) {
|
|
31500
|
-
|
|
31858
|
+
rmSync10(at, { force: true, recursive: true });
|
|
31501
31859
|
} else if (entry.isDirectory()) {
|
|
31502
31860
|
walk4(at);
|
|
31503
31861
|
} else if (NEVER.includes(entry.name)) {
|
|
31504
|
-
|
|
31862
|
+
rmSync10(at, { force: true });
|
|
31505
31863
|
}
|
|
31506
31864
|
}
|
|
31507
31865
|
};
|
|
@@ -31597,7 +31955,7 @@ function terminal() {
|
|
|
31597
31955
|
};
|
|
31598
31956
|
}
|
|
31599
31957
|
function runCommand(line) {
|
|
31600
|
-
const parts =
|
|
31958
|
+
const parts = split3(line);
|
|
31601
31959
|
const args = parts[0] === "rook" ? parts.slice(1) : parts;
|
|
31602
31960
|
if (args.length === 0) return Promise.resolve(1);
|
|
31603
31961
|
return new Promise((done) => {
|
|
@@ -31610,7 +31968,7 @@ function runCommand(line) {
|
|
|
31610
31968
|
child.on("close", (code) => done(code ?? 1));
|
|
31611
31969
|
});
|
|
31612
31970
|
}
|
|
31613
|
-
function
|
|
31971
|
+
function split3(line) {
|
|
31614
31972
|
const out = [];
|
|
31615
31973
|
let current2 = "";
|
|
31616
31974
|
let quote2 = null;
|
|
@@ -32091,7 +32449,7 @@ import {
|
|
|
32091
32449
|
mkdtempSync as mkdtempSync2,
|
|
32092
32450
|
readFileSync as readFileSync44,
|
|
32093
32451
|
realpathSync as realpathSync10,
|
|
32094
|
-
rmSync as
|
|
32452
|
+
rmSync as rmSync11,
|
|
32095
32453
|
writeFileSync as writeFileSync32
|
|
32096
32454
|
} from "fs";
|
|
32097
32455
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -32257,9 +32615,9 @@ function readPackageName(pkgRoot) {
|
|
|
32257
32615
|
// src/updater.ts
|
|
32258
32616
|
init_version_check();
|
|
32259
32617
|
var CURL_INSTALL_COMMAND = "curl -fsSL https://raw.githubusercontent.com/LambdaTest/rook/main/install.sh | bash";
|
|
32260
|
-
var
|
|
32618
|
+
var SCOPE_REGISTRY_ARG = `--${PUBLISHED_SCOPE}:registry=${NPM_REGISTRY_BASE}`;
|
|
32261
32619
|
function npmInstallCommand(latest) {
|
|
32262
|
-
return `npm install -g --registry ${NPM_REGISTRY_BASE} ${
|
|
32620
|
+
return `npm install -g --registry ${NPM_REGISTRY_BASE} ${SCOPE_REGISTRY_ARG} ${PUBLISHED_PACKAGE_NAME}@${latest}`;
|
|
32263
32621
|
}
|
|
32264
32622
|
function candidateCommands(latest) {
|
|
32265
32623
|
return [npmInstallCommand(latest), CURL_INSTALL_COMMAND, "brew upgrade rook"];
|
|
@@ -32322,7 +32680,7 @@ async function runUpdate(deps) {
|
|
|
32322
32680
|
case "npm-print": {
|
|
32323
32681
|
const cause = provenance.reason === "layout" ? "not a global install \u2014 the layout above it is an npx cache, or a project-local node_modules, not an npm prefix" : "a global npm install, but the running copy could not be confirmed to be the one <prefix>/bin/rook points at";
|
|
32324
32682
|
deps.out(
|
|
32325
|
-
`this is the npm package, but ${cause}. If a global install exists, run: npm install -g --registry ${NPM_REGISTRY_BASE} ${
|
|
32683
|
+
`this is the npm package, but ${cause}. If a global install exists, run: npm install -g --registry ${NPM_REGISTRY_BASE} ${SCOPE_REGISTRY_ARG} ${shellQuote(`${PUBLISHED_PACKAGE_NAME}@${latest}`)}`
|
|
32326
32684
|
);
|
|
32327
32685
|
return 1;
|
|
32328
32686
|
}
|
|
@@ -32361,8 +32719,7 @@ async function runUpdate(deps) {
|
|
|
32361
32719
|
provenance.prefix,
|
|
32362
32720
|
"--registry",
|
|
32363
32721
|
NPM_REGISTRY_BASE,
|
|
32364
|
-
|
|
32365
|
-
NPM_REGISTRY_BASE,
|
|
32722
|
+
SCOPE_REGISTRY_ARG,
|
|
32366
32723
|
`${PUBLISHED_PACKAGE_NAME}@${latest}`
|
|
32367
32724
|
];
|
|
32368
32725
|
return dispatchAndConfirm(
|
|
@@ -32413,7 +32770,7 @@ async function runUpdate(deps) {
|
|
|
32413
32770
|
);
|
|
32414
32771
|
return 1;
|
|
32415
32772
|
} finally {
|
|
32416
|
-
|
|
32773
|
+
rmSync11(tmpDir, { recursive: true, force: true });
|
|
32417
32774
|
}
|
|
32418
32775
|
}
|
|
32419
32776
|
}
|
|
@@ -32606,7 +32963,13 @@ function announceUpdateIfAny() {
|
|
|
32606
32963
|
writeErr(`Update available: ${VERSION} \u2192 ${updateInfo.latest}
|
|
32607
32964
|
`);
|
|
32608
32965
|
}
|
|
32609
|
-
var program = new
|
|
32966
|
+
var program = new Command2().configureOutput({
|
|
32967
|
+
writeOut,
|
|
32968
|
+
writeErr,
|
|
32969
|
+
outputError: (text, write2) => {
|
|
32970
|
+
if (currentClient() === "cli") write2(text);
|
|
32971
|
+
}
|
|
32972
|
+
});
|
|
32610
32973
|
program.name("rook").description("Agent Assurance by Testmu AI").on("option:version", () => announceUpdateIfAny()).version(VERSION, "-v, --version");
|
|
32611
32974
|
async function admit(command) {
|
|
32612
32975
|
const auth3 = await resolveBootAuth();
|
|
@@ -34476,7 +34839,12 @@ program.command("docs").description("the public repo, printed and opened").optio
|
|
|
34476
34839
|
`);
|
|
34477
34840
|
await announceOpen(PUBLIC_DOCS_URL, flags.open);
|
|
34478
34841
|
});
|
|
34479
|
-
program.command("update").description("check for a newer rook, and how to get it").
|
|
34842
|
+
program.command("update").description("check for a newer rook, and how to get it").addArgument(
|
|
34843
|
+
new Argument(
|
|
34844
|
+
"[auto]",
|
|
34845
|
+
"`auto` re-enables the notice after 'never ask again'"
|
|
34846
|
+
).choices(["auto"])
|
|
34847
|
+
).option("--json", "machine-readable output").action(async (sub, flags) => {
|
|
34480
34848
|
const mode = { json: Boolean(flags.json), verbose: false };
|
|
34481
34849
|
const say2 = sayFor(mode);
|
|
34482
34850
|
if (sub === "auto") {
|
|
@@ -34489,11 +34857,6 @@ program.command("update").description("check for a newer rook, and how to get it
|
|
|
34489
34857
|
if (!cleared) process.exitCode = 1;
|
|
34490
34858
|
return;
|
|
34491
34859
|
}
|
|
34492
|
-
if (sub) {
|
|
34493
|
-
say2("error", "usage: rook update [auto]");
|
|
34494
|
-
process.exitCode = 1;
|
|
34495
|
-
return;
|
|
34496
|
-
}
|
|
34497
34860
|
if (needsProxyReexec(process.env)) {
|
|
34498
34861
|
const { spawn: spawn8 } = await import("child_process");
|
|
34499
34862
|
const child = spawn8(
|
|
@@ -34626,16 +34989,10 @@ mcp.command("approve <name>").description(
|
|
|
34626
34989
|
).option("--json", "machine-readable output").action((name, flags) => {
|
|
34627
34990
|
runMcp(approve(process.cwd(), name, flags), Boolean(flags.json));
|
|
34628
34991
|
});
|
|
34629
|
-
|
|
34992
|
+
var helpArgument = new Argument("[command]", "one command, in full");
|
|
34993
|
+
program.command("help").description("list commands \u2014 rook help <command> for its flags").addArgument(helpArgument).action((name) => {
|
|
34630
34994
|
const surface = currentClient();
|
|
34631
34995
|
const lines = name ? renderCommandHelp(program, name, surface) : renderHelp(program, surface);
|
|
34632
|
-
if (!lines) {
|
|
34633
|
-
writeErr(`no such command: ${name}
|
|
34634
|
-
`);
|
|
34635
|
-
writeErr("rook help lists them\n");
|
|
34636
|
-
process.exitCode = 1;
|
|
34637
|
-
return;
|
|
34638
|
-
}
|
|
34639
34996
|
writeOut(`${lines.join("\n")}
|
|
34640
34997
|
`);
|
|
34641
34998
|
});
|
|
@@ -34729,16 +35086,8 @@ ${out.ran.length} command(s) run this conversation`
|
|
|
34729
35086
|
}
|
|
34730
35087
|
}
|
|
34731
35088
|
);
|
|
34732
|
-
program.command("export").description("bundle rook's logs for a bug report").
|
|
35089
|
+
program.command("export").description("bundle rook's logs for a bug report").addArgument(new Argument("<what>", "what to export").choices(["logs"])).requiredOption("--out <path>", "a directory, or a filename ending .zip/.tgz").option("--session <id>", "also include this session's transcript").option("--all-sessions", "include every session recorded for this project").action(
|
|
34733
35090
|
async (what, flags) => {
|
|
34734
|
-
if (what !== "logs") {
|
|
34735
|
-
writeErr(
|
|
34736
|
-
`rook export ${what}: not something rook exports. Try 'logs'
|
|
34737
|
-
`
|
|
34738
|
-
);
|
|
34739
|
-
process.exitCode = 1;
|
|
34740
|
-
return;
|
|
34741
|
-
}
|
|
34742
35091
|
try {
|
|
34743
35092
|
const out = exportLogs({
|
|
34744
35093
|
root: process.cwd(),
|
|
@@ -34831,6 +35180,9 @@ program.command("doctor").description("check the environment without starting an
|
|
|
34831
35180
|
`);
|
|
34832
35181
|
}
|
|
34833
35182
|
});
|
|
35183
|
+
helpArgument.choices(
|
|
35184
|
+
program.commands.flatMap((cmd) => [cmd.name(), ...cmd.aliases()])
|
|
35185
|
+
);
|
|
34834
35186
|
|
|
34835
35187
|
// src/cli.ts
|
|
34836
35188
|
init_dispatch();
|