@f-o-h/cli 0.1.86 → 0.1.87
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/foh.js +1487 -780
- package/package.json +1 -1
package/dist/foh.js
CHANGED
|
@@ -6046,7 +6046,7 @@ var require_compile = __commonJS({
|
|
|
6046
6046
|
const schOrFunc = root.refs[ref];
|
|
6047
6047
|
if (schOrFunc)
|
|
6048
6048
|
return schOrFunc;
|
|
6049
|
-
let _sch =
|
|
6049
|
+
let _sch = resolve16.call(this, root, ref);
|
|
6050
6050
|
if (_sch === void 0) {
|
|
6051
6051
|
const schema2 = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
|
|
6052
6052
|
const { schemaId } = this.opts;
|
|
@@ -6073,7 +6073,7 @@ var require_compile = __commonJS({
|
|
|
6073
6073
|
function sameSchemaEnv(s1, s2) {
|
|
6074
6074
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
6075
6075
|
}
|
|
6076
|
-
function
|
|
6076
|
+
function resolve16(root, ref) {
|
|
6077
6077
|
let sch;
|
|
6078
6078
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
6079
6079
|
ref = sch;
|
|
@@ -6648,7 +6648,7 @@ var require_fast_uri = __commonJS({
|
|
|
6648
6648
|
}
|
|
6649
6649
|
return uri;
|
|
6650
6650
|
}
|
|
6651
|
-
function
|
|
6651
|
+
function resolve16(baseURI, relativeURI, options) {
|
|
6652
6652
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
6653
6653
|
const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
6654
6654
|
schemelessOptions.skipEscape = true;
|
|
@@ -6875,7 +6875,7 @@ var require_fast_uri = __commonJS({
|
|
|
6875
6875
|
var fastUri = {
|
|
6876
6876
|
SCHEMES,
|
|
6877
6877
|
normalize,
|
|
6878
|
-
resolve:
|
|
6878
|
+
resolve: resolve16,
|
|
6879
6879
|
resolveComponent,
|
|
6880
6880
|
equal,
|
|
6881
6881
|
serialize,
|
|
@@ -10183,21 +10183,21 @@ async function promptLine(label, {
|
|
|
10183
10183
|
allowEmpty = false,
|
|
10184
10184
|
defaultValue
|
|
10185
10185
|
} = {}) {
|
|
10186
|
-
return await new Promise((
|
|
10186
|
+
return await new Promise((resolve16) => {
|
|
10187
10187
|
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
10188
10188
|
const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout, terminal: true });
|
|
10189
10189
|
rl.question(`${label}${suffix}: `, (answer) => {
|
|
10190
10190
|
rl.close();
|
|
10191
10191
|
const value = String(answer ?? "").trim();
|
|
10192
10192
|
if (!value && typeof defaultValue === "string") {
|
|
10193
|
-
|
|
10193
|
+
resolve16(defaultValue);
|
|
10194
10194
|
return;
|
|
10195
10195
|
}
|
|
10196
10196
|
if (!value && !allowEmpty) {
|
|
10197
|
-
|
|
10197
|
+
resolve16("");
|
|
10198
10198
|
return;
|
|
10199
10199
|
}
|
|
10200
|
-
|
|
10200
|
+
resolve16(value);
|
|
10201
10201
|
});
|
|
10202
10202
|
});
|
|
10203
10203
|
}
|
|
@@ -10205,7 +10205,7 @@ async function promptSecret(label) {
|
|
|
10205
10205
|
if (!process.stdin.isTTY || !process.stdout.isTTY || typeof process.stdin.setRawMode !== "function") {
|
|
10206
10206
|
return await promptLine(label);
|
|
10207
10207
|
}
|
|
10208
|
-
return await new Promise((
|
|
10208
|
+
return await new Promise((resolve16) => {
|
|
10209
10209
|
const stdin = process.stdin;
|
|
10210
10210
|
const stdout = process.stdout;
|
|
10211
10211
|
const wasRaw = Boolean(stdin.isRaw);
|
|
@@ -10219,7 +10219,7 @@ async function promptSecret(label) {
|
|
|
10219
10219
|
const finish = () => {
|
|
10220
10220
|
cleanup();
|
|
10221
10221
|
stdout.write("\n");
|
|
10222
|
-
|
|
10222
|
+
resolve16(value);
|
|
10223
10223
|
};
|
|
10224
10224
|
const onData = (chunk) => {
|
|
10225
10225
|
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
@@ -10228,7 +10228,7 @@ async function promptSecret(label) {
|
|
|
10228
10228
|
cleanup();
|
|
10229
10229
|
process.exitCode = 130;
|
|
10230
10230
|
stdout.write("\n");
|
|
10231
|
-
return
|
|
10231
|
+
return resolve16("");
|
|
10232
10232
|
}
|
|
10233
10233
|
if (char === "\r" || char === "\n") {
|
|
10234
10234
|
finish();
|
|
@@ -10583,7 +10583,7 @@ async function storeAuthenticatedSession(params) {
|
|
|
10583
10583
|
return output;
|
|
10584
10584
|
}
|
|
10585
10585
|
function sleep(ms) {
|
|
10586
|
-
return new Promise((
|
|
10586
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
10587
10587
|
}
|
|
10588
10588
|
function hasExplicitTimeoutFlag(argv = process.argv) {
|
|
10589
10589
|
return argv.some((arg) => arg === "--timeout-seconds" || arg.startsWith("--timeout-seconds="));
|
|
@@ -11141,7 +11141,7 @@ async function pollUntil(check2, opts) {
|
|
|
11141
11141
|
}
|
|
11142
11142
|
}
|
|
11143
11143
|
function sleep2(ms) {
|
|
11144
|
-
return new Promise((
|
|
11144
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
11145
11145
|
}
|
|
11146
11146
|
|
|
11147
11147
|
// src/commands/compliance.ts
|
|
@@ -14265,8 +14265,8 @@ function registerAgentGuardrailCommands(agent) {
|
|
|
14265
14265
|
try {
|
|
14266
14266
|
rule = JSON.parse(opts.rule);
|
|
14267
14267
|
} catch {
|
|
14268
|
-
const { readFileSync:
|
|
14269
|
-
rule = JSON.parse(
|
|
14268
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
14269
|
+
rule = JSON.parse(readFileSync20(opts.rule, "utf-8"));
|
|
14270
14270
|
}
|
|
14271
14271
|
const data = await apiFetch(`/v1/console/agents/${opts.agent}/guardrails`, {
|
|
14272
14272
|
method: "POST",
|
|
@@ -14835,9 +14835,9 @@ function registerAgent(program3) {
|
|
|
14835
14835
|
process.stdout.write(yaml);
|
|
14836
14836
|
return;
|
|
14837
14837
|
}
|
|
14838
|
-
const { writeFileSync:
|
|
14838
|
+
const { writeFileSync: writeFileSync16 } = await import("fs");
|
|
14839
14839
|
const outputPath = opts.output ?? "tenant.yaml";
|
|
14840
|
-
|
|
14840
|
+
writeFileSync16(
|
|
14841
14841
|
outputPath,
|
|
14842
14842
|
`# tenant.yaml - Front Of House agent manifest
|
|
14843
14843
|
# Edit this file and run: foh plan tenant.yaml
|
|
@@ -15368,9 +15368,6 @@ function registerInstagramChannelCommands(instagram, addCommonOptions) {
|
|
|
15368
15368
|
}));
|
|
15369
15369
|
}
|
|
15370
15370
|
|
|
15371
|
-
// src/commands/channel-whatsapp.ts
|
|
15372
|
-
var import_node_fs2 = require("node:fs");
|
|
15373
|
-
|
|
15374
15371
|
// src/commands/channel-whatsapp-helpers.ts
|
|
15375
15372
|
function parsePositiveNumber(value, fallback) {
|
|
15376
15373
|
if (value === void 0 || value === null || String(value).trim() === "") return fallback;
|
|
@@ -15549,6 +15546,9 @@ function resolveLiveProof({
|
|
|
15549
15546
|
};
|
|
15550
15547
|
}
|
|
15551
15548
|
|
|
15549
|
+
// src/commands/channel-whatsapp-onboarding.ts
|
|
15550
|
+
var import_node_fs2 = require("node:fs");
|
|
15551
|
+
|
|
15552
15552
|
// src/commands/channel-whatsapp-setup.ts
|
|
15553
15553
|
var import_node_crypto = require("node:crypto");
|
|
15554
15554
|
var WHATSAPP_WEBHOOK_CHALLENGE_TIMEOUT_MS = 1e4;
|
|
@@ -15657,7 +15657,7 @@ function assertProofPass(strict, reasons) {
|
|
|
15657
15657
|
}
|
|
15658
15658
|
}
|
|
15659
15659
|
|
|
15660
|
-
// src/commands/channel-whatsapp.ts
|
|
15660
|
+
// src/commands/channel-whatsapp-onboarding.ts
|
|
15661
15661
|
async function runWhatsAppOnboardingSession(params) {
|
|
15662
15662
|
return await apiFetch("/v1/console/channels/whatsapp/onboarding-session", {
|
|
15663
15663
|
method: "POST",
|
|
@@ -15765,11 +15765,11 @@ async function runWhatsAppOnboardingWizard(opts) {
|
|
|
15765
15765
|
}
|
|
15766
15766
|
]
|
|
15767
15767
|
});
|
|
15768
|
-
|
|
15768
|
+
const accessToken = String(wizardState.accessToken || "").trim();
|
|
15769
15769
|
let wabaId = String(opts.wabaId || "").trim();
|
|
15770
15770
|
let phoneNumberId = String(opts.phoneNumberId || "").trim();
|
|
15771
|
-
|
|
15772
|
-
|
|
15771
|
+
const verifyToken = String(opts.verifyToken || "").trim();
|
|
15772
|
+
const appSecret = String(wizardState.appSecret || "").trim();
|
|
15773
15773
|
if (!accessToken) {
|
|
15774
15774
|
throw new FohError({
|
|
15775
15775
|
step: "channel.whatsapp.onboard",
|
|
@@ -15846,7 +15846,7 @@ async function runWhatsAppOnboardingWizard(opts) {
|
|
|
15846
15846
|
remediation: "Run again with --phone-number-id and --waba-id, or use `foh channel whatsapp start` for deterministic next steps."
|
|
15847
15847
|
});
|
|
15848
15848
|
}
|
|
15849
|
-
function
|
|
15849
|
+
function registerWhatsAppOnboardingCommands(whatsapp, addCommonOptions) {
|
|
15850
15850
|
addCommonOptions(
|
|
15851
15851
|
whatsapp.command("start").description("[Deprecated wrapper] Start onboarding using the canonical session flow")
|
|
15852
15852
|
).option("--access-token <token>", "Meta access token with WhatsApp Business access").option("--waba-id <id>", "Optional explicit WhatsApp Business Account id when /me discovery is restricted").option("--phone-number-id <id>", "Optional explicit phone number id when discovery returns multiple candidates").option("--agent-id <id>", "Agent to bind to the WhatsApp channel").option("--verify-token <token>", "Optional webhook verify token (auto-generated when omitted)").option("--app-secret <secret>", "Optional Meta app secret (required for full signature-ready closure)").option("--business-slug <slug>", "Business identifier for operator-level targeting").option("--audio-enabled <bool>", "Enable inbound WhatsApp audio fallback auto-reply (true/false)", "true").option("--wizard", "Run guided onboarding with automatic recovery prompts").action(async (opts) => withCommandErrorHandling(async () => {
|
|
@@ -16062,6 +16062,11 @@ function registerWhatsAppChannelCommands(whatsapp, addCommonOptions) {
|
|
|
16062
16062
|
legacy_wrapper: legacy
|
|
16063
16063
|
}, { json: opts.json ?? false });
|
|
16064
16064
|
}));
|
|
16065
|
+
}
|
|
16066
|
+
|
|
16067
|
+
// src/commands/channel-whatsapp.ts
|
|
16068
|
+
function registerWhatsAppChannelCommands(whatsapp, addCommonOptions) {
|
|
16069
|
+
registerWhatsAppOnboardingCommands(whatsapp, addCommonOptions);
|
|
16065
16070
|
addCommonOptions(
|
|
16066
16071
|
whatsapp.command("status").description("Get WhatsApp channel status")
|
|
16067
16072
|
).action(async (opts) => withCommandErrorHandling(async () => {
|
|
@@ -16438,11 +16443,11 @@ function registerVoice(program3) {
|
|
|
16438
16443
|
}
|
|
16439
16444
|
const outputPath = String(opts.out || `foh-voice-preview-${provider}-${voiceId}.mp3`).trim();
|
|
16440
16445
|
const audio = Buffer.from(await res.arrayBuffer());
|
|
16441
|
-
const { mkdirSync:
|
|
16442
|
-
const { dirname: dirname13, resolve:
|
|
16443
|
-
const absolutePath =
|
|
16444
|
-
|
|
16445
|
-
|
|
16446
|
+
const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync16 } = await import("fs");
|
|
16447
|
+
const { dirname: dirname13, resolve: resolve16 } = await import("path");
|
|
16448
|
+
const absolutePath = resolve16(outputPath);
|
|
16449
|
+
mkdirSync11(dirname13(absolutePath), { recursive: true });
|
|
16450
|
+
writeFileSync16(absolutePath, audio);
|
|
16446
16451
|
format({
|
|
16447
16452
|
status: "ok",
|
|
16448
16453
|
provider,
|
|
@@ -30933,7 +30938,7 @@ var Protocol = class {
|
|
|
30933
30938
|
return;
|
|
30934
30939
|
}
|
|
30935
30940
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
30936
|
-
await new Promise((
|
|
30941
|
+
await new Promise((resolve16) => setTimeout(resolve16, pollInterval));
|
|
30937
30942
|
options?.signal?.throwIfAborted();
|
|
30938
30943
|
}
|
|
30939
30944
|
} catch (error2) {
|
|
@@ -30950,7 +30955,7 @@ var Protocol = class {
|
|
|
30950
30955
|
*/
|
|
30951
30956
|
request(request, resultSchema, options) {
|
|
30952
30957
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
30953
|
-
return new Promise((
|
|
30958
|
+
return new Promise((resolve16, reject) => {
|
|
30954
30959
|
const earlyReject = (error2) => {
|
|
30955
30960
|
reject(error2);
|
|
30956
30961
|
};
|
|
@@ -31028,7 +31033,7 @@ var Protocol = class {
|
|
|
31028
31033
|
if (!parseResult.success) {
|
|
31029
31034
|
reject(parseResult.error);
|
|
31030
31035
|
} else {
|
|
31031
|
-
|
|
31036
|
+
resolve16(parseResult.data);
|
|
31032
31037
|
}
|
|
31033
31038
|
} catch (error2) {
|
|
31034
31039
|
reject(error2);
|
|
@@ -31289,12 +31294,12 @@ var Protocol = class {
|
|
|
31289
31294
|
}
|
|
31290
31295
|
} catch {
|
|
31291
31296
|
}
|
|
31292
|
-
return new Promise((
|
|
31297
|
+
return new Promise((resolve16, reject) => {
|
|
31293
31298
|
if (signal.aborted) {
|
|
31294
31299
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
31295
31300
|
return;
|
|
31296
31301
|
}
|
|
31297
|
-
const timeoutId = setTimeout(
|
|
31302
|
+
const timeoutId = setTimeout(resolve16, interval);
|
|
31298
31303
|
signal.addEventListener("abort", () => {
|
|
31299
31304
|
clearTimeout(timeoutId);
|
|
31300
31305
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -32394,7 +32399,7 @@ var McpServer = class {
|
|
|
32394
32399
|
let task = createTaskResult.task;
|
|
32395
32400
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
32396
32401
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
32397
|
-
await new Promise((
|
|
32402
|
+
await new Promise((resolve16) => setTimeout(resolve16, pollInterval));
|
|
32398
32403
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
32399
32404
|
if (!updatedTask) {
|
|
32400
32405
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -33043,19 +33048,19 @@ var StdioServerTransport = class {
|
|
|
33043
33048
|
this.onclose?.();
|
|
33044
33049
|
}
|
|
33045
33050
|
send(message) {
|
|
33046
|
-
return new Promise((
|
|
33051
|
+
return new Promise((resolve16) => {
|
|
33047
33052
|
const json3 = serializeMessage(message);
|
|
33048
33053
|
if (this._stdout.write(json3)) {
|
|
33049
|
-
|
|
33054
|
+
resolve16();
|
|
33050
33055
|
} else {
|
|
33051
|
-
this._stdout.once("drain",
|
|
33056
|
+
this._stdout.once("drain", resolve16);
|
|
33052
33057
|
}
|
|
33053
33058
|
});
|
|
33054
33059
|
}
|
|
33055
33060
|
};
|
|
33056
33061
|
|
|
33057
33062
|
// src/lib/cli-version.ts
|
|
33058
|
-
var injectedVersion = true ? String("0.1.
|
|
33063
|
+
var injectedVersion = true ? String("0.1.87").trim() : "";
|
|
33059
33064
|
var envVersion = String(process.env.FOH_CLI_VERSION || process.env.npm_package_version || "").trim();
|
|
33060
33065
|
var CLI_VERSION = injectedVersion || envVersion || "0.0.0-dev";
|
|
33061
33066
|
|
|
@@ -33242,7 +33247,7 @@ async function runFohCli(params) {
|
|
|
33242
33247
|
effectiveArgv.push("--json");
|
|
33243
33248
|
}
|
|
33244
33249
|
const command = `foh ${effectiveArgv.join(" ")}`;
|
|
33245
|
-
return await new Promise((
|
|
33250
|
+
return await new Promise((resolve16) => {
|
|
33246
33251
|
const child = (0, import_node_child_process.spawn)(process.execPath, [cliEntry, ...effectiveArgv], {
|
|
33247
33252
|
stdio: ["ignore", "pipe", "pipe"],
|
|
33248
33253
|
env: {
|
|
@@ -33267,7 +33272,7 @@ async function runFohCli(params) {
|
|
|
33267
33272
|
});
|
|
33268
33273
|
child.once("error", (error2) => {
|
|
33269
33274
|
clearTimeout(timeoutHandle);
|
|
33270
|
-
|
|
33275
|
+
resolve16({
|
|
33271
33276
|
ok: false,
|
|
33272
33277
|
command,
|
|
33273
33278
|
argv: effectiveArgv,
|
|
@@ -33283,7 +33288,7 @@ async function runFohCli(params) {
|
|
|
33283
33288
|
const stderrText = finalizeBoundedText(stderrBuffer);
|
|
33284
33289
|
const exitCode = Number.isFinite(code ?? NaN) ? Number(code) : 1;
|
|
33285
33290
|
const stdoutJson = tryParseJson(stdoutText);
|
|
33286
|
-
|
|
33291
|
+
resolve16({
|
|
33287
33292
|
ok: !timedOut && exitCode === 0,
|
|
33288
33293
|
command,
|
|
33289
33294
|
argv: effectiveArgv,
|
|
@@ -35453,8 +35458,8 @@ function registerSetup(program3) {
|
|
|
35453
35458
|
}
|
|
35454
35459
|
try {
|
|
35455
35460
|
const manifest = await agentExport(resolvedAgentId, { apiUrlOverride: opts.apiUrl });
|
|
35456
|
-
const { writeFileSync:
|
|
35457
|
-
|
|
35461
|
+
const { writeFileSync: writeFileSync16 } = await import("fs");
|
|
35462
|
+
writeFileSync16(
|
|
35458
35463
|
"tenant.yaml",
|
|
35459
35464
|
`# tenant.yaml - Front Of House agent manifest
|
|
35460
35465
|
# Edit this file and run: foh plan tenant.yaml
|
|
@@ -35624,8 +35629,8 @@ function registerSim(program3) {
|
|
|
35624
35629
|
}
|
|
35625
35630
|
const cert = response.certificate;
|
|
35626
35631
|
if (opts.out) {
|
|
35627
|
-
const { writeFileSync:
|
|
35628
|
-
|
|
35632
|
+
const { writeFileSync: writeFileSync16 } = await import("fs");
|
|
35633
|
+
writeFileSync16(opts.out, JSON.stringify(cert, null, 2) + "\n", "utf-8");
|
|
35629
35634
|
process.stderr.write(` Certificate written to ${opts.out}
|
|
35630
35635
|
`);
|
|
35631
35636
|
}
|
|
@@ -35675,8 +35680,8 @@ function registerSim(program3) {
|
|
|
35675
35680
|
});
|
|
35676
35681
|
}
|
|
35677
35682
|
if (opts.out) {
|
|
35678
|
-
const { writeFileSync:
|
|
35679
|
-
|
|
35683
|
+
const { writeFileSync: writeFileSync16 } = await import("fs");
|
|
35684
|
+
writeFileSync16(opts.out, JSON.stringify(response.certificate, null, 2) + "\n", "utf-8");
|
|
35680
35685
|
process.stderr.write(` Final certificate written to ${opts.out}
|
|
35681
35686
|
`);
|
|
35682
35687
|
}
|
|
@@ -38542,6 +38547,629 @@ function registerProve(program3) {
|
|
|
38542
38547
|
}));
|
|
38543
38548
|
}
|
|
38544
38549
|
|
|
38550
|
+
// src/commands/objective.ts
|
|
38551
|
+
var import_node_fs5 = require("node:fs");
|
|
38552
|
+
var import_node_path2 = require("node:path");
|
|
38553
|
+
var DEFAULT_OBJECTIVE_REPORT_PATH = "test-results/objective-status.latest.json";
|
|
38554
|
+
function asRecord3(value) {
|
|
38555
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
38556
|
+
}
|
|
38557
|
+
function normalizeString(value) {
|
|
38558
|
+
return typeof value === "string" ? value.trim() : "";
|
|
38559
|
+
}
|
|
38560
|
+
function uniqueStrings(values) {
|
|
38561
|
+
return Array.from(new Set(values.map(normalizeString).filter(Boolean)));
|
|
38562
|
+
}
|
|
38563
|
+
function asArray(value) {
|
|
38564
|
+
return Array.isArray(value) ? value : [];
|
|
38565
|
+
}
|
|
38566
|
+
function collectStringArrays(value, keys, output = []) {
|
|
38567
|
+
if (!value || typeof value !== "object") return output;
|
|
38568
|
+
if (Array.isArray(value)) {
|
|
38569
|
+
for (const item of value) collectStringArrays(item, keys, output);
|
|
38570
|
+
return output;
|
|
38571
|
+
}
|
|
38572
|
+
for (const [key, child] of Object.entries(value)) {
|
|
38573
|
+
if (keys.has(key) && Array.isArray(child)) {
|
|
38574
|
+
for (const item of child) {
|
|
38575
|
+
if (typeof item === "string") output.push(item);
|
|
38576
|
+
}
|
|
38577
|
+
} else if (child && typeof child === "object") {
|
|
38578
|
+
collectStringArrays(child, keys, output);
|
|
38579
|
+
}
|
|
38580
|
+
}
|
|
38581
|
+
return output;
|
|
38582
|
+
}
|
|
38583
|
+
function collectArtifactRefs(value, output = []) {
|
|
38584
|
+
if (!value || typeof value !== "object") return output;
|
|
38585
|
+
if (Array.isArray(value)) {
|
|
38586
|
+
for (const item of value) collectArtifactRefs(item, output);
|
|
38587
|
+
return output;
|
|
38588
|
+
}
|
|
38589
|
+
for (const [key, child] of Object.entries(value)) {
|
|
38590
|
+
if (typeof child === "string") {
|
|
38591
|
+
const normalized = child.trim();
|
|
38592
|
+
if (normalized && (key.toLowerCase().includes("artifact") || key.toLowerCase().includes("report") || normalized.includes("test-results/") || normalized.includes("test-results\\"))) {
|
|
38593
|
+
output.push(normalized);
|
|
38594
|
+
}
|
|
38595
|
+
} else if (child && typeof child === "object") {
|
|
38596
|
+
collectArtifactRefs(child, output);
|
|
38597
|
+
}
|
|
38598
|
+
}
|
|
38599
|
+
return output;
|
|
38600
|
+
}
|
|
38601
|
+
function summarizeSourceReport(value) {
|
|
38602
|
+
const report = asRecord3(value);
|
|
38603
|
+
return {
|
|
38604
|
+
decision: firstString(report, ["decision", "current_decision", "status"]) || null,
|
|
38605
|
+
go_live_allowed: report.go_live_allowed === true || report.customer_live_ready === true || report.production_ready === true,
|
|
38606
|
+
allowed_mode: firstString(report, ["allowed_mode", "highest_allowed_mode", "current_mode"]) || null,
|
|
38607
|
+
blocked_modes: uniqueStrings(collectStringArrays(report, /* @__PURE__ */ new Set(["blocked_modes"]))),
|
|
38608
|
+
reason_codes: uniqueStrings(collectStringArrays(report, /* @__PURE__ */ new Set(["reason_codes", "blocker_reason_codes"]))),
|
|
38609
|
+
next_commands: uniqueStrings(collectStringArrays(report, /* @__PURE__ */ new Set(["next_commands"]))),
|
|
38610
|
+
evidence_refs: uniqueStrings(collectArtifactRefs(report))
|
|
38611
|
+
};
|
|
38612
|
+
}
|
|
38613
|
+
function finiteNumber(value) {
|
|
38614
|
+
const number3 = Number(value);
|
|
38615
|
+
return Number.isFinite(number3) ? number3 : null;
|
|
38616
|
+
}
|
|
38617
|
+
function normalizeCustomerEvidenceActions(value) {
|
|
38618
|
+
return asArray(value).map(asRecord3).map((action) => ({
|
|
38619
|
+
id: firstString(action, ["id", "action_id"]),
|
|
38620
|
+
title: firstString(action, ["title"]) || null,
|
|
38621
|
+
owner: firstString(action, ["owner"]) || null,
|
|
38622
|
+
blocker_count: finiteNumber(action.blocker_count) ?? (uniqueStrings(asArray(action.reason_codes)).length || null),
|
|
38623
|
+
target_evidence_paths: uniqueStrings([
|
|
38624
|
+
...asArray(action.target_evidence_paths),
|
|
38625
|
+
firstString(action, ["target_evidence_path"])
|
|
38626
|
+
]),
|
|
38627
|
+
validator_commands: uniqueStrings([
|
|
38628
|
+
...asArray(action.validator_commands),
|
|
38629
|
+
...asArray(action.next_commands)
|
|
38630
|
+
]),
|
|
38631
|
+
required_evidence: firstString(action, ["required_evidence"]) || null,
|
|
38632
|
+
unlocks: firstString(action, ["unlocks"]) || null
|
|
38633
|
+
})).filter((action) => normalizeString(action.id));
|
|
38634
|
+
}
|
|
38635
|
+
function normalizeCustomerEvidenceActionPacket(value) {
|
|
38636
|
+
const packet = asRecord3(value);
|
|
38637
|
+
const actions = normalizeCustomerEvidenceActions(packet.actions);
|
|
38638
|
+
if (actions.length === 0) return void 0;
|
|
38639
|
+
return {
|
|
38640
|
+
packet_role: firstString(packet, ["packet_role"]) || "compressed_customer_operator_action_plan",
|
|
38641
|
+
action_count: finiteNumber(packet.action_count) ?? actions.length,
|
|
38642
|
+
external_hold_count: finiteNumber(packet.external_hold_count),
|
|
38643
|
+
raw_reason_code_count: finiteNumber(packet.raw_reason_code_count),
|
|
38644
|
+
compression_ratio: finiteNumber(packet.compression_ratio),
|
|
38645
|
+
actions,
|
|
38646
|
+
validation_commands: uniqueStrings(asArray(packet.validation_commands)),
|
|
38647
|
+
instructions: uniqueStrings(asArray(packet.instructions))
|
|
38648
|
+
};
|
|
38649
|
+
}
|
|
38650
|
+
function customerEvidenceActionPacketFromSources(...sources) {
|
|
38651
|
+
for (const source of sources) {
|
|
38652
|
+
const report = asRecord3(source);
|
|
38653
|
+
const requestPackets = asRecord3(report.request_packets);
|
|
38654
|
+
const operatorStatus = asRecord3(report.operator_status);
|
|
38655
|
+
const candidates = [
|
|
38656
|
+
requestPackets.customer_operator_action_packet,
|
|
38657
|
+
report.customer_operator_action_packet,
|
|
38658
|
+
report.customer_evidence_action_packet,
|
|
38659
|
+
operatorStatus.customer_operator_action_packet,
|
|
38660
|
+
operatorStatus.customer_evidence_action_packet,
|
|
38661
|
+
operatorStatus.customer_actions ? {
|
|
38662
|
+
packet_role: "compressed_customer_operator_action_plan",
|
|
38663
|
+
action_count: asArray(operatorStatus.customer_actions).length,
|
|
38664
|
+
actions: operatorStatus.customer_actions,
|
|
38665
|
+
validation_commands: collectStringArrays(operatorStatus, /* @__PURE__ */ new Set(["next_commands"]))
|
|
38666
|
+
} : null,
|
|
38667
|
+
report.customer_actions ? { actions: report.customer_actions } : null
|
|
38668
|
+
];
|
|
38669
|
+
for (const candidate of candidates) {
|
|
38670
|
+
const normalized = normalizeCustomerEvidenceActionPacket(candidate);
|
|
38671
|
+
if (normalized) return normalized;
|
|
38672
|
+
}
|
|
38673
|
+
}
|
|
38674
|
+
return void 0;
|
|
38675
|
+
}
|
|
38676
|
+
function normalizeObjectiveArtifactPath(value) {
|
|
38677
|
+
return value.trim() ? (0, import_node_path2.resolve)(value.trim()) : "";
|
|
38678
|
+
}
|
|
38679
|
+
function resolveObjectiveArtifactPath(value) {
|
|
38680
|
+
return normalizeObjectiveArtifactPath(value);
|
|
38681
|
+
}
|
|
38682
|
+
function firstNonEmptyObject(value) {
|
|
38683
|
+
const record2 = asRecord3(value);
|
|
38684
|
+
return Object.keys(record2).length > 0 ? record2 : void 0;
|
|
38685
|
+
}
|
|
38686
|
+
function pickEvidencePacket(value) {
|
|
38687
|
+
const record2 = firstNonEmptyObject(value);
|
|
38688
|
+
if (!record2) return void 0;
|
|
38689
|
+
const payload = firstNonEmptyObject(record2.payload);
|
|
38690
|
+
return payload ?? record2;
|
|
38691
|
+
}
|
|
38692
|
+
function readEvidencePacketFromPlan(path2) {
|
|
38693
|
+
const plan = asRecord3(readJsonArtifact(path2));
|
|
38694
|
+
const sourceReports = asRecord3(plan.source_reports);
|
|
38695
|
+
return pickEvidencePacket(plan.evidence) ?? pickEvidencePacket(plan.evidence_packet) ?? pickEvidencePacket(sourceReports.customer_live_status && asRecord3(sourceReports.customer_live_status).evidence) ?? pickEvidencePacket(asRecord3(sourceReports.setup_workflow).evidence_packet) ?? {};
|
|
38696
|
+
}
|
|
38697
|
+
function inferEvidenceFromProofPaths(evidence) {
|
|
38698
|
+
const record2 = asRecord3(evidence);
|
|
38699
|
+
if (!record2 || Object.keys(record2).length === 0) return evidence;
|
|
38700
|
+
return record2.evidence ?? evidence;
|
|
38701
|
+
}
|
|
38702
|
+
async function resolveEvidenceInput(opts) {
|
|
38703
|
+
if (opts.evidence) {
|
|
38704
|
+
return inferEvidenceFromProofPaths(await parseJsonOption(opts.evidence, "--evidence"));
|
|
38705
|
+
}
|
|
38706
|
+
if (!opts.plan) {
|
|
38707
|
+
throw new FohError({
|
|
38708
|
+
step: "objective.apply",
|
|
38709
|
+
error: "Missing objective evidence input: pass --evidence or --plan.",
|
|
38710
|
+
remediation: "Provide either --evidence <json|@file> or --plan <path-to-plan-json>.",
|
|
38711
|
+
statusCode: 400
|
|
38712
|
+
});
|
|
38713
|
+
}
|
|
38714
|
+
const planPath = resolveObjectiveArtifactPath(opts.plan);
|
|
38715
|
+
return inferEvidenceFromProofPaths(readEvidencePacketFromPlan(planPath));
|
|
38716
|
+
}
|
|
38717
|
+
function firstString(record2, keys) {
|
|
38718
|
+
for (const key of keys) {
|
|
38719
|
+
const value = normalizeString(record2[key]);
|
|
38720
|
+
if (value) return value;
|
|
38721
|
+
}
|
|
38722
|
+
return "";
|
|
38723
|
+
}
|
|
38724
|
+
function firstStringFromUnknown(value, keys) {
|
|
38725
|
+
return firstString(asRecord3(value), keys);
|
|
38726
|
+
}
|
|
38727
|
+
function truthy(record2, keys) {
|
|
38728
|
+
return keys.some((key) => record2[key] === true);
|
|
38729
|
+
}
|
|
38730
|
+
function statusFromDecision(value) {
|
|
38731
|
+
const normalized = value.toLowerCase();
|
|
38732
|
+
if (/fail|failed|error/.test(normalized)) return "fail";
|
|
38733
|
+
if (/ready|pass|passed|success|go_live/.test(normalized)) return "pass";
|
|
38734
|
+
return "hold";
|
|
38735
|
+
}
|
|
38736
|
+
function firstCustomerEvidenceAction(packet) {
|
|
38737
|
+
const actions = asArray(packet?.actions).map(asRecord3).filter((action) => normalizeString(action.id));
|
|
38738
|
+
return actions[0] ?? null;
|
|
38739
|
+
}
|
|
38740
|
+
function buildDeveloperReadinessPacket(input) {
|
|
38741
|
+
const businessName = resolveBusinessName(input.opts);
|
|
38742
|
+
const sourceUrl = normalizeString(input.opts.sourceUrl);
|
|
38743
|
+
const businessObjective = normalizeString(input.opts.businessObjective);
|
|
38744
|
+
const location = resolveLocation(input.opts);
|
|
38745
|
+
const tools = parseCsvOption(input.opts.tools ?? "widget,voice,email,callback,calendar,crm,webhook,whatsapp") ?? [];
|
|
38746
|
+
const setupDecision = firstString(input.setupWorkflow, ["decision", "current_decision", "status"]);
|
|
38747
|
+
const liveDecision = firstString(input.customerLiveStatus, ["decision", "current_decision", "status"]);
|
|
38748
|
+
const explicitGoLive = truthy(input.customerLiveStatus, ["go_live_allowed", "customer_live_ready", "production_ready"]) || truthy(input.setupWorkflow, ["go_live_allowed", "customer_live_ready", "production_ready"]);
|
|
38749
|
+
const firstAction = firstCustomerEvidenceAction(input.customerEvidenceActionPacket);
|
|
38750
|
+
const firstActionValidator = firstAction ? uniqueStrings(asArray(firstAction.validator_commands))[0] ?? null : null;
|
|
38751
|
+
const firstNextCommand = input.nextCommands[0] ?? null;
|
|
38752
|
+
return {
|
|
38753
|
+
schema_version: "developer_objective_readiness_packet.v1",
|
|
38754
|
+
packet_role: "coding_agent_objective_readiness",
|
|
38755
|
+
decision: input.classification.status,
|
|
38756
|
+
reason_code: input.classification.reasonCode,
|
|
38757
|
+
business_context: {
|
|
38758
|
+
business_name: businessName || null,
|
|
38759
|
+
business_objective: businessObjective || null,
|
|
38760
|
+
source_url: sourceUrl || null,
|
|
38761
|
+
location: location || null,
|
|
38762
|
+
target_mode: normalizeString(input.opts.targetMode) || "customer_owned_voice_trial",
|
|
38763
|
+
tools
|
|
38764
|
+
},
|
|
38765
|
+
input_completeness: {
|
|
38766
|
+
business_name: Boolean(businessName),
|
|
38767
|
+
source_url: Boolean(sourceUrl),
|
|
38768
|
+
business_objective: Boolean(businessObjective),
|
|
38769
|
+
requested_tools: tools.length > 0,
|
|
38770
|
+
target_mode: Boolean(normalizeString(input.opts.targetMode) || "customer_owned_voice_trial")
|
|
38771
|
+
},
|
|
38772
|
+
readiness_dimensions: {
|
|
38773
|
+
setup_workflow: {
|
|
38774
|
+
status: statusFromDecision(setupDecision),
|
|
38775
|
+
decision: setupDecision || null
|
|
38776
|
+
},
|
|
38777
|
+
customer_live_status: {
|
|
38778
|
+
status: explicitGoLive ? "pass" : statusFromDecision(liveDecision),
|
|
38779
|
+
decision: liveDecision || null
|
|
38780
|
+
},
|
|
38781
|
+
customer_evidence_actions: {
|
|
38782
|
+
status: firstAction ? "hold" : "pass",
|
|
38783
|
+
action_count: finiteNumber(input.customerEvidenceActionPacket?.action_count) ?? asArray(input.customerEvidenceActionPacket?.actions).length,
|
|
38784
|
+
first_action_id: firstAction ? normalizeString(firstAction.id) : null,
|
|
38785
|
+
first_validator_command: firstActionValidator
|
|
38786
|
+
},
|
|
38787
|
+
agent_operability: {
|
|
38788
|
+
status: firstNextCommand ? "pass" : "fail",
|
|
38789
|
+
first_next_command: firstNextCommand,
|
|
38790
|
+
normal_path_requires_raw_artifacts: false
|
|
38791
|
+
}
|
|
38792
|
+
},
|
|
38793
|
+
next_command: firstNextCommand,
|
|
38794
|
+
claim_boundaries: {
|
|
38795
|
+
developer_setup_loop_ready: Boolean(firstNextCommand),
|
|
38796
|
+
customer_live_claim_allowed: explicitGoLive && input.classification.status === "pass",
|
|
38797
|
+
production_claim_allowed: explicitGoLive && input.classification.status === "pass",
|
|
38798
|
+
do_not_claim: explicitGoLive && input.classification.status === "pass" ? [] : ["customer-live", "production-ready", "fully autonomous customer setup"]
|
|
38799
|
+
},
|
|
38800
|
+
agent_instructions: [
|
|
38801
|
+
"Use this packet before source_reports or raw artifacts.",
|
|
38802
|
+
"Execute next_command first, then rerun objective status.",
|
|
38803
|
+
"Treat customer evidence actions as request work, not proof."
|
|
38804
|
+
]
|
|
38805
|
+
};
|
|
38806
|
+
}
|
|
38807
|
+
function resolveObjectiveReportPath(value) {
|
|
38808
|
+
const raw = normalizeString(value);
|
|
38809
|
+
if (!raw || raw === "latest") return (0, import_node_path2.resolve)(DEFAULT_OBJECTIVE_REPORT_PATH);
|
|
38810
|
+
return (0, import_node_path2.resolve)(raw);
|
|
38811
|
+
}
|
|
38812
|
+
function writeJsonArtifact2(path2, value) {
|
|
38813
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path2.dirname)(path2), { recursive: true });
|
|
38814
|
+
(0, import_node_fs5.writeFileSync)(path2, `${JSON.stringify(value, null, 2)}
|
|
38815
|
+
`, "utf8");
|
|
38816
|
+
}
|
|
38817
|
+
function readJsonArtifact(path2) {
|
|
38818
|
+
return JSON.parse((0, import_node_fs5.readFileSync)(path2, "utf8"));
|
|
38819
|
+
}
|
|
38820
|
+
function buildSetupBody(opts) {
|
|
38821
|
+
const tools = parseCsvOption(opts.tools ?? "widget,voice,email,callback,calendar,crm,webhook,whatsapp") ?? [];
|
|
38822
|
+
const businessName = resolveBusinessName(opts);
|
|
38823
|
+
const location = resolveLocation(opts);
|
|
38824
|
+
const body = {
|
|
38825
|
+
agency_name: businessName,
|
|
38826
|
+
business_objective: normalizeString(opts.businessObjective) || null,
|
|
38827
|
+
requested_tool_surface: tools,
|
|
38828
|
+
target_exposure_mode: normalizeString(opts.targetMode) || "customer_owned_voice_trial"
|
|
38829
|
+
};
|
|
38830
|
+
if (opts.sourceUrl) body.source_url = normalizeString(opts.sourceUrl);
|
|
38831
|
+
if (location) body.branch_location = location;
|
|
38832
|
+
return body;
|
|
38833
|
+
}
|
|
38834
|
+
function buildStatusParams(opts) {
|
|
38835
|
+
const params = new URLSearchParams();
|
|
38836
|
+
const businessName = resolveBusinessName(opts);
|
|
38837
|
+
const location = resolveLocation(opts);
|
|
38838
|
+
if (opts.environment) params.set("environment", normalizeString(opts.environment));
|
|
38839
|
+
if (businessName) params.set("agency_name", businessName);
|
|
38840
|
+
if (opts.sourceUrl) params.set("source_url", normalizeString(opts.sourceUrl));
|
|
38841
|
+
if (location) params.set("branch_location", location);
|
|
38842
|
+
if (opts.tools) params.set("tools", normalizeString(opts.tools));
|
|
38843
|
+
if (opts.targetMode) params.set("target_mode", normalizeString(opts.targetMode));
|
|
38844
|
+
return params;
|
|
38845
|
+
}
|
|
38846
|
+
function resolveBusinessName(opts) {
|
|
38847
|
+
return normalizeString(opts.businessName) || normalizeString(opts.agencyName);
|
|
38848
|
+
}
|
|
38849
|
+
function resolveLocation(opts) {
|
|
38850
|
+
return normalizeString(opts.location) || normalizeString(opts.branchLocation);
|
|
38851
|
+
}
|
|
38852
|
+
function assertBusinessName(opts, step) {
|
|
38853
|
+
if (resolveBusinessName(opts)) return;
|
|
38854
|
+
throw new FohError({
|
|
38855
|
+
step,
|
|
38856
|
+
error: "Missing business name.",
|
|
38857
|
+
remediation: "Pass --business-name <name>. Legacy --agency-name <name> is still accepted.",
|
|
38858
|
+
statusCode: 400
|
|
38859
|
+
});
|
|
38860
|
+
}
|
|
38861
|
+
function classifyStatus(input) {
|
|
38862
|
+
const setup = asRecord3(input.setupWorkflow);
|
|
38863
|
+
const live = asRecord3(input.customerLiveStatus);
|
|
38864
|
+
const allReasonCodes = uniqueStrings([
|
|
38865
|
+
...collectStringArrays(setup, /* @__PURE__ */ new Set(["reason_codes", "blocker_reason_codes"])),
|
|
38866
|
+
...collectStringArrays(live, /* @__PURE__ */ new Set(["reason_codes", "blocker_reason_codes"]))
|
|
38867
|
+
]);
|
|
38868
|
+
const setupDecision = firstString(setup, ["decision", "current_decision", "status"]);
|
|
38869
|
+
const liveDecision = firstString(live, ["decision", "current_decision", "status"]);
|
|
38870
|
+
const explicitGoLive = truthy(live, ["go_live_allowed", "customer_live_ready", "production_ready"]) || truthy(setup, ["go_live_allowed", "customer_live_ready", "production_ready"]);
|
|
38871
|
+
const hasBlockingReason = allReasonCodes.some((code) => !/_ok$|passed|ready|no_findings/.test(code));
|
|
38872
|
+
const hasHoldDecision = [setupDecision, liveDecision].some(
|
|
38873
|
+
(decision) => /hold|blocked|fail|failed|no_go|developer_supervised|founder_assisted/.test(decision.toLowerCase())
|
|
38874
|
+
);
|
|
38875
|
+
if (explicitGoLive && !hasBlockingReason && !hasHoldDecision) {
|
|
38876
|
+
return {
|
|
38877
|
+
status: "pass",
|
|
38878
|
+
reasonCode: "objective_ready",
|
|
38879
|
+
summary: "Objective evidence is ready for the requested launch mode."
|
|
38880
|
+
};
|
|
38881
|
+
}
|
|
38882
|
+
return {
|
|
38883
|
+
status: "hold",
|
|
38884
|
+
reasonCode: allReasonCodes[0] || "objective_needs_next_action",
|
|
38885
|
+
summary: "Objective is not yet proven for the requested mode; follow next_commands exactly."
|
|
38886
|
+
};
|
|
38887
|
+
}
|
|
38888
|
+
function buildObjectiveReport(input) {
|
|
38889
|
+
const setup = asRecord3(input.setupWorkflow);
|
|
38890
|
+
const live = asRecord3(input.customerLiveStatus);
|
|
38891
|
+
const classification = classifyStatus(input);
|
|
38892
|
+
const reasonCodes = uniqueStrings([
|
|
38893
|
+
classification.reasonCode,
|
|
38894
|
+
...collectStringArrays(setup, /* @__PURE__ */ new Set(["reason_codes", "blocker_reason_codes"])),
|
|
38895
|
+
...collectStringArrays(live, /* @__PURE__ */ new Set(["reason_codes", "blocker_reason_codes"]))
|
|
38896
|
+
]);
|
|
38897
|
+
const debugSource = normalizeString(input.opts.out) || "latest";
|
|
38898
|
+
const nextCommands = dedupeCommands([
|
|
38899
|
+
...collectStringArrays(setup, /* @__PURE__ */ new Set(["next_commands"])),
|
|
38900
|
+
...collectStringArrays(live, /* @__PURE__ */ new Set(["next_commands"])),
|
|
38901
|
+
`foh objective debug --from ${debugSource} --json`
|
|
38902
|
+
]);
|
|
38903
|
+
const allowedMode = firstString(live, ["allowed_mode", "highest_allowed_mode", "current_mode"]) || firstString(setup, ["allowed_mode", "highest_allowed_mode", "current_mode"]) || null;
|
|
38904
|
+
const blockedModes = uniqueStrings([
|
|
38905
|
+
...collectStringArrays(setup, /* @__PURE__ */ new Set(["blocked_modes"])),
|
|
38906
|
+
...collectStringArrays(live, /* @__PURE__ */ new Set(["blocked_modes"]))
|
|
38907
|
+
]);
|
|
38908
|
+
const evidenceRefs = uniqueStrings([
|
|
38909
|
+
...collectArtifactRefs(setup),
|
|
38910
|
+
...collectArtifactRefs(live)
|
|
38911
|
+
]);
|
|
38912
|
+
const customerEvidenceActionPacket = customerEvidenceActionPacketFromSources(live, setup);
|
|
38913
|
+
const developerReadinessPacket = buildDeveloperReadinessPacket({
|
|
38914
|
+
opts: input.opts,
|
|
38915
|
+
setupWorkflow: setup,
|
|
38916
|
+
customerLiveStatus: live,
|
|
38917
|
+
classification,
|
|
38918
|
+
nextCommands,
|
|
38919
|
+
customerEvidenceActionPacket
|
|
38920
|
+
});
|
|
38921
|
+
return cliEnvelope({
|
|
38922
|
+
schemaVersion: "agent_workbench_report.v1",
|
|
38923
|
+
status: classification.status,
|
|
38924
|
+
reasonCode: classification.reasonCode,
|
|
38925
|
+
summary: classification.summary,
|
|
38926
|
+
ids: {
|
|
38927
|
+
org_id: input.opts.org ?? null
|
|
38928
|
+
},
|
|
38929
|
+
artifacts: {
|
|
38930
|
+
evidence_refs: evidenceRefs
|
|
38931
|
+
},
|
|
38932
|
+
nextCommands,
|
|
38933
|
+
spendClass: "free",
|
|
38934
|
+
safeToRetry: true,
|
|
38935
|
+
extra: {
|
|
38936
|
+
objective: {
|
|
38937
|
+
business_objective: normalizeString(input.opts.businessObjective) || null,
|
|
38938
|
+
business_name: resolveBusinessName(input.opts) || null,
|
|
38939
|
+
agency_name: normalizeString(input.opts.agencyName) || resolveBusinessName(input.opts) || null,
|
|
38940
|
+
source_url: normalizeString(input.opts.sourceUrl) || null,
|
|
38941
|
+
location: resolveLocation(input.opts) || null,
|
|
38942
|
+
branch_location: normalizeString(input.opts.branchLocation) || resolveLocation(input.opts) || null,
|
|
38943
|
+
target_mode: normalizeString(input.opts.targetMode) || "customer_owned_voice_trial",
|
|
38944
|
+
tools: parseCsvOption(input.opts.tools ?? "widget,voice,email,callback,calendar,crm,webhook,whatsapp") ?? []
|
|
38945
|
+
},
|
|
38946
|
+
allowed_mode: allowedMode,
|
|
38947
|
+
blocked_modes: blockedModes,
|
|
38948
|
+
reason_codes: reasonCodes,
|
|
38949
|
+
next_action: nextCommands[0] ?? null,
|
|
38950
|
+
developer_readiness_packet: developerReadinessPacket,
|
|
38951
|
+
customer_evidence_action_packet: customerEvidenceActionPacket ?? null,
|
|
38952
|
+
source_report_summaries: {
|
|
38953
|
+
setup_workflow: summarizeSourceReport(setup),
|
|
38954
|
+
customer_live_status: summarizeSourceReport(live)
|
|
38955
|
+
},
|
|
38956
|
+
artifact_policy: {
|
|
38957
|
+
normal_path_fields: ["status", "reason_code", "reason_codes", "next_action", "next_commands", "developer_readiness_packet", "customer_evidence_action_packet", "source_report_summaries", "agent_guidance"],
|
|
38958
|
+
diagnostic_fields: ["source_reports", "artifacts.evidence_refs"],
|
|
38959
|
+
raw_artifact_reads_required: false,
|
|
38960
|
+
mutable_latest_json_policy: "diagnostic_only",
|
|
38961
|
+
immutable_evidence_refs_required: true
|
|
38962
|
+
},
|
|
38963
|
+
source_reports: {
|
|
38964
|
+
setup_workflow: setup,
|
|
38965
|
+
customer_live_status: live
|
|
38966
|
+
},
|
|
38967
|
+
agent_guidance: {
|
|
38968
|
+
normal_user_path: "Use objective commands first; inspect source_reports only when debug asks for it.",
|
|
38969
|
+
do_not_infer_from_raw_artifacts: true
|
|
38970
|
+
}
|
|
38971
|
+
}
|
|
38972
|
+
});
|
|
38973
|
+
}
|
|
38974
|
+
function withObjectiveArtifactPath(report, artifactPath) {
|
|
38975
|
+
return {
|
|
38976
|
+
...report,
|
|
38977
|
+
artifacts: {
|
|
38978
|
+
...asRecord3(report.artifacts),
|
|
38979
|
+
objective_report: artifactPath
|
|
38980
|
+
},
|
|
38981
|
+
artifact_path: artifactPath
|
|
38982
|
+
};
|
|
38983
|
+
}
|
|
38984
|
+
function stripDiagnosticField(target, fieldPath) {
|
|
38985
|
+
const parts = fieldPath.split(".").map((part) => part.trim()).filter(Boolean);
|
|
38986
|
+
if (parts.length === 0) return;
|
|
38987
|
+
let current = target;
|
|
38988
|
+
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
38989
|
+
const next = asRecord3(current?.[parts[index]]);
|
|
38990
|
+
if (Object.keys(next).length === 0) return;
|
|
38991
|
+
current = next;
|
|
38992
|
+
}
|
|
38993
|
+
if (!current) return;
|
|
38994
|
+
delete current[parts[parts.length - 1]];
|
|
38995
|
+
}
|
|
38996
|
+
function buildObjectiveNormalPathOutput(report) {
|
|
38997
|
+
const output = JSON.parse(JSON.stringify(report));
|
|
38998
|
+
const artifactPolicy = asRecord3(output.artifact_policy);
|
|
38999
|
+
const diagnosticFields = uniqueStrings(asArray(artifactPolicy.diagnostic_fields).map((value) => normalizeString(value)));
|
|
39000
|
+
for (const fieldPath of diagnosticFields) stripDiagnosticField(output, fieldPath);
|
|
39001
|
+
return output;
|
|
39002
|
+
}
|
|
39003
|
+
function classifyReasonCode(reasonCode) {
|
|
39004
|
+
const normalized = reasonCode.toLowerCase();
|
|
39005
|
+
if (/credential|token|auth|login|permission|scope|key|secret/.test(normalized)) return "credentials";
|
|
39006
|
+
if (/source|official|fact|approval|conflict|provenance|unknown|intake/.test(normalized)) return "source_facts";
|
|
39007
|
+
if (/customer.*evidence|evidence|signoff|ownership|customer_owned|live.*validation/.test(normalized)) return "customer_evidence";
|
|
39008
|
+
if (/voice|stt|tts|turn|audio|latency|tool|webhook|calendar|crm|email|callback|whatsapp/.test(normalized)) return "voice_or_tool_quality";
|
|
39009
|
+
if (/deploy|production|backup|rollback|runtime|env|release|drift/.test(normalized)) return "deploy_or_runtime";
|
|
39010
|
+
if (/docs|api|route|command|registry|schema|contract/.test(normalized)) return "docs_api_mismatch";
|
|
39011
|
+
if (/proof|simulation|cert|widget|artifact|gate|readiness/.test(normalized)) return "runtime_proof";
|
|
39012
|
+
return "uncategorized";
|
|
39013
|
+
}
|
|
39014
|
+
function buildDebugReport(sourcePath, objectiveReport) {
|
|
39015
|
+
const report = asRecord3(objectiveReport);
|
|
39016
|
+
const reasonCodes = uniqueStrings([
|
|
39017
|
+
firstString(report, ["reason_code"]),
|
|
39018
|
+
...collectStringArrays(report, /* @__PURE__ */ new Set(["reason_codes", "blocker_reason_codes"]))
|
|
39019
|
+
]);
|
|
39020
|
+
const nextCommands = dedupeCommands([
|
|
39021
|
+
...collectStringArrays(report, /* @__PURE__ */ new Set(["next_commands"])),
|
|
39022
|
+
"foh objective status --business-name <name> --source-url <official_url> --out test-results/objective-status.latest.json --json"
|
|
39023
|
+
]);
|
|
39024
|
+
const categories = uniqueStrings(reasonCodes.map(classifyReasonCode));
|
|
39025
|
+
const status = firstString(report, ["status"]);
|
|
39026
|
+
const isReady = report.ok === true || status === "pass" || status === "success";
|
|
39027
|
+
const dominantCategory = categories.find((category) => category !== "uncategorized") ?? categories[0] ?? "uncategorized";
|
|
39028
|
+
const sourceReports = asRecord3(report.source_reports);
|
|
39029
|
+
const customerEvidenceActionPacket = firstNonEmptyObject(report.customer_evidence_action_packet);
|
|
39030
|
+
const developerReadinessPacket = firstNonEmptyObject(report.developer_readiness_packet);
|
|
39031
|
+
if (isReady && reasonCodes.length <= 1 && reasonCodes[0] === "objective_ready") {
|
|
39032
|
+
return cliEnvelope({
|
|
39033
|
+
schemaVersion: "agent_workbench_debug.v1",
|
|
39034
|
+
status: "pass",
|
|
39035
|
+
reasonCode: "objective_debug_no_blockers",
|
|
39036
|
+
summary: "Objective report is ready; no repair packet is required.",
|
|
39037
|
+
artifacts: { objective_report: sourcePath },
|
|
39038
|
+
nextCommands,
|
|
39039
|
+
spendClass: "free",
|
|
39040
|
+
safeToRetry: true,
|
|
39041
|
+
extra: {
|
|
39042
|
+
blocker_categories: [],
|
|
39043
|
+
dominant_category: null,
|
|
39044
|
+
repair_packet: null
|
|
39045
|
+
}
|
|
39046
|
+
});
|
|
39047
|
+
}
|
|
39048
|
+
return cliEnvelope({
|
|
39049
|
+
schemaVersion: "agent_workbench_debug.v1",
|
|
39050
|
+
status: "hold",
|
|
39051
|
+
reasonCode: "objective_debug_repair_packet_ready",
|
|
39052
|
+
summary: `Objective is held by ${dominantCategory}; execute the first repair command before rerunning status.`,
|
|
39053
|
+
artifacts: { objective_report: sourcePath },
|
|
39054
|
+
nextCommands,
|
|
39055
|
+
spendClass: "free",
|
|
39056
|
+
safeToRetry: true,
|
|
39057
|
+
extra: {
|
|
39058
|
+
blocker_categories: categories,
|
|
39059
|
+
dominant_category: dominantCategory,
|
|
39060
|
+
repair_packet: {
|
|
39061
|
+
category: dominantCategory,
|
|
39062
|
+
reason_codes: reasonCodes,
|
|
39063
|
+
first_repair_command: nextCommands[0] ?? null,
|
|
39064
|
+
developer_readiness_packet: developerReadinessPacket ?? null,
|
|
39065
|
+
customer_evidence_action_packet: customerEvidenceActionPacket ?? null,
|
|
39066
|
+
setup_decision: firstStringFromUnknown(sourceReports.setup_workflow, ["decision", "current_decision", "status"]) || null,
|
|
39067
|
+
customer_live_decision: firstStringFromUnknown(sourceReports.customer_live_status, ["decision", "current_decision", "status"]) || null,
|
|
39068
|
+
principle: "Do not infer readiness from raw artifacts; repair the named blocker and rerun objective status."
|
|
39069
|
+
}
|
|
39070
|
+
}
|
|
39071
|
+
});
|
|
39072
|
+
}
|
|
39073
|
+
function registerObjective(program3) {
|
|
39074
|
+
const objective = program3.command("objective").description("Agent-native objective workflow: plan, apply, prove, status, debug");
|
|
39075
|
+
objective.command("plan").description("Generate an objective setup/workflow plan").option("--business-name <name>", "Business trading name").option("--agency-name <name>", "Legacy alias for --business-name").option("--business-objective <text>", "Natural-language business objective").option("--source-url <url>", "Official source URL").option("--location <value>", "Location or branch this setup represents").option("--branch-location <value>", "Legacy alias for --location").option("--tools <csv>", "Requested tool surfaces", "widget,voice,email,callback,calendar,crm,webhook,whatsapp").option("--target-mode <mode>", "Target launch/exposure mode", "customer_owned_voice_trial").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--out <path>", "Write objective plan JSON to this file path").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
39076
|
+
assertBusinessName(opts, "objective.plan");
|
|
39077
|
+
const report = await apiFetch("/v1/console/agency-setup/workflow", {
|
|
39078
|
+
method: "POST",
|
|
39079
|
+
body: JSON.stringify(buildSetupBody(opts)),
|
|
39080
|
+
orgId: opts.org,
|
|
39081
|
+
apiUrlOverride: opts.apiUrl
|
|
39082
|
+
});
|
|
39083
|
+
const outPath = opts.out ? resolveObjectiveReportPath(opts.out) : null;
|
|
39084
|
+
const output = outPath ? { ...asRecord3(report), artifact_path: outPath } : report;
|
|
39085
|
+
if (outPath) writeJsonArtifact2(outPath, output);
|
|
39086
|
+
format(output, { json: opts.json ?? false });
|
|
39087
|
+
}));
|
|
39088
|
+
objective.command("apply").description("Apply objective evidence to release launch packet gating").option("--evidence <json|@file>", "Redacted evidence payload JSON, or @/path/to/file.json").option("--plan <path>", "Optional objective plan file to derive evidence payload from").option("--dry-run", "Validate evidence payload without persisting it").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--out <path>", "Write objective apply result JSON to this file path").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
39089
|
+
const evidence = await resolveEvidenceInput(opts);
|
|
39090
|
+
const body = {
|
|
39091
|
+
...evidence && typeof evidence === "object" && !Array.isArray(evidence) ? evidence : { evidence }
|
|
39092
|
+
};
|
|
39093
|
+
if (!opts.dryRun) body.apply = true;
|
|
39094
|
+
const report = await apiFetch("/v1/console/release-launch-packet/evidence-dry-run", {
|
|
39095
|
+
method: "POST",
|
|
39096
|
+
body: JSON.stringify(body),
|
|
39097
|
+
orgId: opts.org,
|
|
39098
|
+
apiUrlOverride: opts.apiUrl
|
|
39099
|
+
});
|
|
39100
|
+
const outPath = opts.out ? resolveObjectiveReportPath(opts.out) : null;
|
|
39101
|
+
const output = outPath ? { ...asRecord3(report), artifact_path: outPath } : report;
|
|
39102
|
+
if (outPath) writeJsonArtifact2(outPath, output);
|
|
39103
|
+
format(output, { json: opts.json ?? false });
|
|
39104
|
+
}));
|
|
39105
|
+
objective.command("prove").description("Run objective proof status against customer-live gate").option("--business-name <name>", "Business trading name").option("--agency-name <name>", "Legacy alias for --business-name").option("--business-objective <text>", "Natural-language business objective").option("--source-url <url>", "Official source URL").option("--location <value>", "Location or branch this setup represents").option("--branch-location <value>", "Legacy alias for --location").option("--tools <csv>", "Requested tool surfaces", "widget,voice,email,callback,calendar,crm,webhook,whatsapp").option("--target-mode <mode>", "Target launch/exposure mode", "customer_owned_voice_trial").option("--environment <value>", "Environment filter").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--out <path>", "Write objective proof JSON to this file path").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
39106
|
+
assertBusinessName(opts, "objective.prove");
|
|
39107
|
+
const status = await apiFetch(withQuery("/v1/console/customer-live-status", buildStatusParams(opts)), {
|
|
39108
|
+
orgId: opts.org,
|
|
39109
|
+
apiUrlOverride: opts.apiUrl
|
|
39110
|
+
});
|
|
39111
|
+
const outPath = opts.out ? resolveObjectiveReportPath(opts.out) : null;
|
|
39112
|
+
const output = outPath ? { ...asRecord3(status), artifact_path: outPath } : status;
|
|
39113
|
+
if (outPath) writeJsonArtifact2(outPath, output);
|
|
39114
|
+
format(output, { json: opts.json ?? false });
|
|
39115
|
+
}));
|
|
39116
|
+
objective.command("status").description("Compose setup and launch evidence into one agent workbench status envelope").option("--business-name <name>", "Business trading name").option("--agency-name <name>", "Legacy alias for --business-name").option("--business-objective <text>", "Natural-language business objective").option("--source-url <url>", "Official source URL").option("--location <value>", "Location or branch this setup represents").option("--branch-location <value>", "Legacy alias for --location").option("--tools <csv>", "Requested tool surfaces", "widget,voice,email,callback,calendar,crm,webhook,whatsapp").option("--target-mode <mode>", "Target launch/exposure mode", "customer_owned_voice_trial").option("--environment <value>", "Environment filter").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--out <path>", "Write objective report JSON to this file path").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
39117
|
+
assertBusinessName(opts, "objective.status");
|
|
39118
|
+
const setupWorkflow = await apiFetch("/v1/console/agency-setup/workflow", {
|
|
39119
|
+
method: "POST",
|
|
39120
|
+
body: JSON.stringify(buildSetupBody(opts)),
|
|
39121
|
+
orgId: opts.org,
|
|
39122
|
+
apiUrlOverride: opts.apiUrl
|
|
39123
|
+
});
|
|
39124
|
+
const customerLiveStatus = await apiFetch(withQuery("/v1/console/customer-live-status", buildStatusParams(opts)), {
|
|
39125
|
+
orgId: opts.org,
|
|
39126
|
+
apiUrlOverride: opts.apiUrl
|
|
39127
|
+
});
|
|
39128
|
+
const report = buildObjectiveReport({ opts, setupWorkflow, customerLiveStatus });
|
|
39129
|
+
const artifactPath = resolveObjectiveReportPath(opts.out);
|
|
39130
|
+
const fullOutput = withObjectiveArtifactPath(report, artifactPath);
|
|
39131
|
+
writeJsonArtifact2(artifactPath, fullOutput);
|
|
39132
|
+
format(buildObjectiveNormalPathOutput(fullOutput), { json: opts.json ?? false });
|
|
39133
|
+
}));
|
|
39134
|
+
objective.command("debug").description("Explain the next debugging action for an objective report").option("--from <source>", "Report source alias or path", "latest").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
39135
|
+
const sourcePath = resolveObjectiveReportPath(opts.from);
|
|
39136
|
+
if (!(0, import_node_fs5.existsSync)(sourcePath)) {
|
|
39137
|
+
format(cliEnvelope({
|
|
39138
|
+
schemaVersion: "agent_workbench_debug.v1",
|
|
39139
|
+
status: "fail",
|
|
39140
|
+
reasonCode: "objective_report_not_found",
|
|
39141
|
+
summary: `Objective report not found at ${sourcePath}.`,
|
|
39142
|
+
artifacts: { objective_report: sourcePath },
|
|
39143
|
+
nextCommands: [
|
|
39144
|
+
`foh objective status --business-name <name> --source-url <official_url> --out ${DEFAULT_OBJECTIVE_REPORT_PATH} --json`
|
|
39145
|
+
],
|
|
39146
|
+
spendClass: "free",
|
|
39147
|
+
safeToRetry: true
|
|
39148
|
+
}), { json: opts.json ?? false });
|
|
39149
|
+
return;
|
|
39150
|
+
}
|
|
39151
|
+
let objectiveReport;
|
|
39152
|
+
try {
|
|
39153
|
+
objectiveReport = readJsonArtifact(sourcePath);
|
|
39154
|
+
} catch {
|
|
39155
|
+
format(cliEnvelope({
|
|
39156
|
+
schemaVersion: "agent_workbench_debug.v1",
|
|
39157
|
+
status: "fail",
|
|
39158
|
+
reasonCode: "objective_report_invalid",
|
|
39159
|
+
summary: `Objective report at ${sourcePath} is not valid JSON.`,
|
|
39160
|
+
artifacts: { objective_report: sourcePath },
|
|
39161
|
+
nextCommands: [
|
|
39162
|
+
`foh objective status --business-name <name> --source-url <official_url> --out ${DEFAULT_OBJECTIVE_REPORT_PATH} --json`
|
|
39163
|
+
],
|
|
39164
|
+
spendClass: "free",
|
|
39165
|
+
safeToRetry: true
|
|
39166
|
+
}), { json: opts.json ?? false });
|
|
39167
|
+
return;
|
|
39168
|
+
}
|
|
39169
|
+
format(buildDebugReport(sourcePath, objectiveReport), { json: opts.json ?? false });
|
|
39170
|
+
}));
|
|
39171
|
+
}
|
|
39172
|
+
|
|
38545
39173
|
// src/commands/interactive.ts
|
|
38546
39174
|
var import_readline2 = require("readline");
|
|
38547
39175
|
var import_child_process2 = require("child_process");
|
|
@@ -38651,6 +39279,66 @@ var COMMAND_SURFACE_DEFINITIONS = [
|
|
|
38651
39279
|
shellSlash: "/status",
|
|
38652
39280
|
includeInSuggestions: true
|
|
38653
39281
|
},
|
|
39282
|
+
{
|
|
39283
|
+
id: "objective_status",
|
|
39284
|
+
commandPath: ["objective", "status"],
|
|
39285
|
+
invokeArgs: ["objective", "status", "--help"],
|
|
39286
|
+
label: "objective status",
|
|
39287
|
+
descriptionFallback: "Compose setup and launch evidence into one agent workbench status envelope.",
|
|
39288
|
+
requiresAuth: true,
|
|
39289
|
+
requiresOrg: true,
|
|
39290
|
+
mutatesState: "read",
|
|
39291
|
+
examples: ['foh objective status --business-name "Acme" --source-url https://example.com --out test-results/objective-status.latest.json --json'],
|
|
39292
|
+
homeContexts: ["authenticated_with_org"],
|
|
39293
|
+
homeAliases: ["objective", "status objective"],
|
|
39294
|
+
shellSlash: "/objective",
|
|
39295
|
+
includeInSuggestions: true
|
|
39296
|
+
},
|
|
39297
|
+
{
|
|
39298
|
+
id: "objective_plan",
|
|
39299
|
+
commandPath: ["objective", "plan"],
|
|
39300
|
+
invokeArgs: ["objective", "plan", "--help"],
|
|
39301
|
+
label: "objective plan",
|
|
39302
|
+
descriptionFallback: "Generate an objective setup/workflow plan.",
|
|
39303
|
+
requiresAuth: true,
|
|
39304
|
+
requiresOrg: true,
|
|
39305
|
+
mutatesState: "read",
|
|
39306
|
+
examples: ['foh objective plan --business-name "Acme" --business-objective "Qualify leads" --source-url https://example.com --out test-results/objective-plan.latest.json --json'],
|
|
39307
|
+
homeContexts: ["authenticated_with_org"],
|
|
39308
|
+
homeAliases: ["objective plan"],
|
|
39309
|
+
shellSlash: "/objective-plan",
|
|
39310
|
+
includeInSuggestions: true
|
|
39311
|
+
},
|
|
39312
|
+
{
|
|
39313
|
+
id: "objective_apply",
|
|
39314
|
+
commandPath: ["objective", "apply"],
|
|
39315
|
+
invokeArgs: ["objective", "apply", "--help"],
|
|
39316
|
+
label: "objective apply",
|
|
39317
|
+
descriptionFallback: "Apply objective evidence packets against customer-live launch packet gates.",
|
|
39318
|
+
requiresAuth: true,
|
|
39319
|
+
requiresOrg: true,
|
|
39320
|
+
mutatesState: "write",
|
|
39321
|
+
examples: ["foh objective apply --evidence @test-results/evidence.json --org <org-id> --out test-results/objective-apply.latest.json --json"],
|
|
39322
|
+
homeContexts: ["authenticated_with_org"],
|
|
39323
|
+
homeAliases: ["objective apply"],
|
|
39324
|
+
shellSlash: "/objective-apply",
|
|
39325
|
+
includeInSuggestions: true
|
|
39326
|
+
},
|
|
39327
|
+
{
|
|
39328
|
+
id: "objective_prove",
|
|
39329
|
+
commandPath: ["objective", "prove"],
|
|
39330
|
+
invokeArgs: ["objective", "prove", "--help"],
|
|
39331
|
+
label: "objective prove",
|
|
39332
|
+
descriptionFallback: "Check one objective against customer-live proof gates.",
|
|
39333
|
+
requiresAuth: true,
|
|
39334
|
+
requiresOrg: true,
|
|
39335
|
+
mutatesState: "read",
|
|
39336
|
+
examples: ['foh objective prove --business-name "Acme" --source-url https://example.com --environment staging --json'],
|
|
39337
|
+
homeContexts: ["authenticated_with_org"],
|
|
39338
|
+
homeAliases: ["objective prove"],
|
|
39339
|
+
shellSlash: "/objective-prove",
|
|
39340
|
+
includeInSuggestions: true
|
|
39341
|
+
},
|
|
38654
39342
|
{
|
|
38655
39343
|
id: "templates_list",
|
|
38656
39344
|
commandPath: ["templates", "list"],
|
|
@@ -38791,6 +39479,7 @@ var HOME_QUICK_ACTION_ORDER = {
|
|
|
38791
39479
|
unauthenticated: ["auth_login", "interactive_shell", "full_help", "auth_login_help"],
|
|
38792
39480
|
authenticated_no_org: ["org_list", "interactive_shell", "org_use_help", "auth_whoami", "full_help"],
|
|
38793
39481
|
authenticated_with_org: [
|
|
39482
|
+
"objective_status",
|
|
38794
39483
|
"tenant_status",
|
|
38795
39484
|
"interactive_shell",
|
|
38796
39485
|
"templates_list",
|
|
@@ -39133,7 +39822,7 @@ async function runSelf(args, apiUrlOverride) {
|
|
|
39133
39822
|
if (apiUrlOverride && !spawnArgs.includes("--api-url")) {
|
|
39134
39823
|
spawnArgs.push("--api-url", apiUrlOverride);
|
|
39135
39824
|
}
|
|
39136
|
-
return await new Promise((
|
|
39825
|
+
return await new Promise((resolve16, reject) => {
|
|
39137
39826
|
const child = (0, import_child_process2.spawn)(process.execPath, [process.argv[1], ...spawnArgs], {
|
|
39138
39827
|
stdio: "inherit",
|
|
39139
39828
|
env: {
|
|
@@ -39143,7 +39832,7 @@ async function runSelf(args, apiUrlOverride) {
|
|
|
39143
39832
|
}
|
|
39144
39833
|
});
|
|
39145
39834
|
child.once("error", reject);
|
|
39146
|
-
child.once("close", (code) =>
|
|
39835
|
+
child.once("close", (code) => resolve16(typeof code === "number" ? code : 1));
|
|
39147
39836
|
});
|
|
39148
39837
|
}
|
|
39149
39838
|
function resolveInteractiveState(apiUrlOverride) {
|
|
@@ -39330,10 +40019,10 @@ async function confirmMutationIfNeeded(rl, args) {
|
|
|
39330
40019
|
const commandEntry = getCommandGraphEntryForArgs(args);
|
|
39331
40020
|
const label = commandEntry ? `foh ${commandEntry.args.join(" ")}` : `foh ${args.join(" ")}`;
|
|
39332
40021
|
const warning = mutationState === "high_risk" ? "high-risk" : "write";
|
|
39333
|
-
const approved = await new Promise((
|
|
40022
|
+
const approved = await new Promise((resolve16) => {
|
|
39334
40023
|
rl.question(`Confirm ${warning} command ${label}? [y/N]: `, (answer) => {
|
|
39335
40024
|
const normalized = answer.trim().toLowerCase();
|
|
39336
|
-
|
|
40025
|
+
resolve16(normalized === "y" || normalized === "yes");
|
|
39337
40026
|
});
|
|
39338
40027
|
});
|
|
39339
40028
|
if (approved) return { approved: true };
|
|
@@ -39501,7 +40190,7 @@ function registerInteractive(program3) {
|
|
|
39501
40190
|
rl.on("SIGINT", () => {
|
|
39502
40191
|
rl.close();
|
|
39503
40192
|
});
|
|
39504
|
-
await new Promise((
|
|
40193
|
+
await new Promise((resolve16) => {
|
|
39505
40194
|
rl.on("close", () => {
|
|
39506
40195
|
process.stdout.write("\n");
|
|
39507
40196
|
memory.history = rl.history.slice(0, 500);
|
|
@@ -39509,7 +40198,7 @@ function registerInteractive(program3) {
|
|
|
39509
40198
|
flushInteractiveSessionArtifact(sessionArtifact);
|
|
39510
40199
|
flushInteractiveSessionReport(sessionArtifact);
|
|
39511
40200
|
saveInteractiveShellMemory(memory);
|
|
39512
|
-
|
|
40201
|
+
resolve16();
|
|
39513
40202
|
});
|
|
39514
40203
|
});
|
|
39515
40204
|
});
|
|
@@ -39841,7 +40530,7 @@ async function runSelf2(args, apiUrlOverride) {
|
|
|
39841
40530
|
if (apiUrlOverride && !spawnArgs.includes("--api-url")) {
|
|
39842
40531
|
spawnArgs.push("--api-url", apiUrlOverride);
|
|
39843
40532
|
}
|
|
39844
|
-
return await new Promise((
|
|
40533
|
+
return await new Promise((resolve16, reject) => {
|
|
39845
40534
|
const child = (0, import_child_process3.spawn)(process.execPath, [process.argv[1], ...spawnArgs], {
|
|
39846
40535
|
stdio: "inherit",
|
|
39847
40536
|
env: {
|
|
@@ -39851,7 +40540,7 @@ async function runSelf2(args, apiUrlOverride) {
|
|
|
39851
40540
|
}
|
|
39852
40541
|
});
|
|
39853
40542
|
child.once("error", reject);
|
|
39854
|
-
child.once("close", (code) =>
|
|
40543
|
+
child.once("close", (code) => resolve16(typeof code === "number" ? code : 1));
|
|
39855
40544
|
});
|
|
39856
40545
|
}
|
|
39857
40546
|
function shouldUseInteractiveHome(argv) {
|
|
@@ -40200,17 +40889,17 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
|
|
|
40200
40889
|
async function applyRepoUpdate(repoRoot) {
|
|
40201
40890
|
const scriptPath = (0, import_path10.join)(repoRoot, "scripts", "Install-FohCli.ps1");
|
|
40202
40891
|
if (process.platform === "win32") {
|
|
40203
|
-
return await new Promise((
|
|
40892
|
+
return await new Promise((resolve16, reject) => {
|
|
40204
40893
|
const child = (0, import_child_process4.spawn)(
|
|
40205
40894
|
"powershell",
|
|
40206
40895
|
["-ExecutionPolicy", "Bypass", "-File", scriptPath],
|
|
40207
40896
|
{ stdio: "inherit" }
|
|
40208
40897
|
);
|
|
40209
40898
|
child.once("error", reject);
|
|
40210
|
-
child.once("close", (code) =>
|
|
40899
|
+
child.once("close", (code) => resolve16(typeof code === "number" ? code : 1));
|
|
40211
40900
|
});
|
|
40212
40901
|
}
|
|
40213
|
-
return await new Promise((
|
|
40902
|
+
return await new Promise((resolve16, reject) => {
|
|
40214
40903
|
const child = (0, import_child_process4.spawn)(
|
|
40215
40904
|
"corepack",
|
|
40216
40905
|
["pnpm", "cli:install:global"],
|
|
@@ -40220,7 +40909,7 @@ async function applyRepoUpdate(repoRoot) {
|
|
|
40220
40909
|
}
|
|
40221
40910
|
);
|
|
40222
40911
|
child.once("error", reject);
|
|
40223
|
-
child.once("close", (code) =>
|
|
40912
|
+
child.once("close", (code) => resolve16(typeof code === "number" ? code : 1));
|
|
40224
40913
|
});
|
|
40225
40914
|
}
|
|
40226
40915
|
function shouldShowUpdateNotice(argv = process.argv) {
|
|
@@ -40692,9 +41381,9 @@ function readCommandRecords(runDir) {
|
|
|
40692
41381
|
}
|
|
40693
41382
|
|
|
40694
41383
|
// src/lib/external-agent-executor.ts
|
|
40695
|
-
var
|
|
41384
|
+
var import_fs19 = require("fs");
|
|
40696
41385
|
var import_os3 = require("os");
|
|
40697
|
-
var
|
|
41386
|
+
var import_path18 = require("path");
|
|
40698
41387
|
var import_child_process6 = require("child_process");
|
|
40699
41388
|
|
|
40700
41389
|
// src/lib/external-agent-executor-env.ts
|
|
@@ -40865,695 +41554,174 @@ function copyExternalAgentCommandCaptureArtifacts(input) {
|
|
|
40865
41554
|
}
|
|
40866
41555
|
|
|
40867
41556
|
// src/lib/external-agent-executor-classification.ts
|
|
40868
|
-
var import_fs18 = require("fs");
|
|
40869
|
-
var import_path17 = require("path");
|
|
40870
|
-
|
|
40871
|
-
// src/lib/external-agent-run-summary.ts
|
|
40872
41557
|
var import_fs17 = require("fs");
|
|
40873
41558
|
var import_path16 = require("path");
|
|
40874
|
-
|
|
40875
|
-
"
|
|
40876
|
-
|
|
40877
|
-
|
|
40878
|
-
|
|
40879
|
-
|
|
40880
|
-
|
|
40881
|
-
|
|
40882
|
-
|
|
40883
|
-
"environment",
|
|
40884
|
-
"public_entrypoints",
|
|
40885
|
-
"commands_run",
|
|
40886
|
-
"docs_pages_used",
|
|
40887
|
-
"artifacts"
|
|
40888
|
-
];
|
|
40889
|
-
var VALID_STATUSES = /* @__PURE__ */ new Set(["pass", "hold", "fail"]);
|
|
40890
|
-
var DOC_URL_RE = /https:\/\/frontofhouse\.okii\.uk\/[^\s"'`)<>,;\\\]}]*/g;
|
|
40891
|
-
function quoteShellArg(value) {
|
|
40892
|
-
const text = String(value);
|
|
40893
|
-
if (/^[A-Za-z0-9_./:=@-]+$/.test(text)) return text;
|
|
40894
|
-
return `"${text.replace(/(["$`])/g, "\\$1")}"`;
|
|
41559
|
+
function proofArtifactPasses(runDir) {
|
|
41560
|
+
const proofPath = (0, import_path16.join)(runDir, "proof.json");
|
|
41561
|
+
if (!(0, import_fs17.existsSync)(proofPath)) return false;
|
|
41562
|
+
try {
|
|
41563
|
+
const parsed = JSON.parse((0, import_fs17.readFileSync)(proofPath, "utf8"));
|
|
41564
|
+
return parsed.ok === true || parsed.status === "pass" || parsed.status === "passed";
|
|
41565
|
+
} catch {
|
|
41566
|
+
return false;
|
|
41567
|
+
}
|
|
40895
41568
|
}
|
|
40896
|
-
function
|
|
40897
|
-
|
|
40898
|
-
|
|
41569
|
+
function readIfExists(path2) {
|
|
41570
|
+
return (0, import_fs17.existsSync)(path2) ? (0, import_fs17.readFileSync)(path2, "utf8") : "";
|
|
41571
|
+
}
|
|
41572
|
+
function relativeArtifactName(path2) {
|
|
41573
|
+
return (0, import_path16.basename)(path2);
|
|
41574
|
+
}
|
|
41575
|
+
function externalAgentSummaryTemplateCommand() {
|
|
40899
41576
|
return [
|
|
40900
41577
|
"foh",
|
|
40901
41578
|
"eval",
|
|
40902
41579
|
"external-agent",
|
|
40903
41580
|
"summary",
|
|
40904
41581
|
"--root",
|
|
40905
|
-
|
|
41582
|
+
"<batch_dir>",
|
|
40906
41583
|
"--out",
|
|
40907
|
-
|
|
41584
|
+
"<batch_dir>/latest-summary.json",
|
|
40908
41585
|
"--report",
|
|
40909
|
-
|
|
41586
|
+
"<batch_dir>/summary.report.json",
|
|
40910
41587
|
"--json"
|
|
40911
41588
|
].join(" ");
|
|
40912
41589
|
}
|
|
40913
|
-
function
|
|
40914
|
-
|
|
40915
|
-
}
|
|
40916
|
-
|
|
40917
|
-
|
|
40918
|
-
|
|
40919
|
-
|
|
40920
|
-
const parsed = JSON.parse(line);
|
|
40921
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
40922
|
-
} catch {
|
|
40923
|
-
return null;
|
|
40924
|
-
}
|
|
40925
|
-
}).filter((record2) => Boolean(record2));
|
|
40926
|
-
}
|
|
40927
|
-
function asObject(value) {
|
|
40928
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
40929
|
-
}
|
|
40930
|
-
function toArray2(value) {
|
|
40931
|
-
return Array.isArray(value) ? value : [];
|
|
40932
|
-
}
|
|
40933
|
-
function increment(map3, key, amount = 1) {
|
|
40934
|
-
const normalized = String(key || "unknown");
|
|
40935
|
-
map3.set(normalized, (map3.get(normalized) || 0) + amount);
|
|
40936
|
-
}
|
|
40937
|
-
function ranked(map3) {
|
|
40938
|
-
return Array.from(map3.entries()).map(([key, count]) => ({ key, count })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
|
|
40939
|
-
}
|
|
40940
|
-
function collectDocUrls(text) {
|
|
40941
|
-
return Array.from(new Set((String(text || "").match(DOC_URL_RE) || []).map((url2) => url2.replace(/[.?!:]+$/g, "")).filter((url2) => url2.startsWith("https://frontofhouse.okii.uk/")))).sort();
|
|
40942
|
-
}
|
|
40943
|
-
function findRunCandidates(root) {
|
|
40944
|
-
if (!(0, import_fs17.existsSync)(root)) return [];
|
|
40945
|
-
const candidates = [];
|
|
40946
|
-
const seenRunDirs = /* @__PURE__ */ new Set();
|
|
40947
|
-
const captureDirs = [];
|
|
40948
|
-
const stack = [root];
|
|
40949
|
-
while (stack.length > 0) {
|
|
40950
|
-
const current = stack.pop();
|
|
40951
|
-
if (!current) continue;
|
|
40952
|
-
for (const entry of (0, import_fs17.readdirSync)(current, { withFileTypes: true })) {
|
|
40953
|
-
const absolute = (0, import_path16.join)(current, entry.name);
|
|
40954
|
-
if (entry.isDirectory()) {
|
|
40955
|
-
stack.push(absolute);
|
|
40956
|
-
} else if (entry.isFile() && entry.name === "run.json") {
|
|
40957
|
-
candidates.push({ path: absolute, synthetic: false });
|
|
40958
|
-
seenRunDirs.add((0, import_path16.dirname)(absolute));
|
|
40959
|
-
} else if (entry.isFile() && entry.name === "commands.ndjson") {
|
|
40960
|
-
captureDirs.push(current);
|
|
40961
|
-
}
|
|
40962
|
-
}
|
|
41590
|
+
function classifyExternalAgentRun(input) {
|
|
41591
|
+
if (input.timedOut) return { status: "hold", reasonCode: `${input.run.command}_runner_timeout` };
|
|
41592
|
+
if (!input.artifactSafetyOk) return { status: "fail", reasonCode: "external_agent_artifact_safety_blocked" };
|
|
41593
|
+
const completedCommands = readCommandRecords(input.run.run_dir).filter((record2) => record2.phase === "completed");
|
|
41594
|
+
const observedVersions = completedCommands.map((record2) => String(record2.cli_version || "").trim()).filter((version2) => /^\d+\.\d+\.\d+$/.test(version2));
|
|
41595
|
+
if (observedVersions.some((version2) => version2 !== CLI_VERSION)) {
|
|
41596
|
+
return { status: "hold", reasonCode: "external_agent_cli_version_drift" };
|
|
40963
41597
|
}
|
|
40964
|
-
|
|
40965
|
-
|
|
40966
|
-
|
|
41598
|
+
const commandReasonCodes2 = completedCommands.flatMap((record2) => [
|
|
41599
|
+
String(record2.reason_code || ""),
|
|
41600
|
+
...Array.isArray(record2.check_reason_codes) ? record2.check_reason_codes.map((code) => String(code || "")) : []
|
|
41601
|
+
]).filter(Boolean);
|
|
41602
|
+
const hasCommandReason = (pattern) => commandReasonCodes2.some((reason) => pattern.test(reason));
|
|
41603
|
+
if (hasCommandReason(new RegExp(PAID_RESOURCE_BLOCKED_REASON_CODE, "i"))) {
|
|
41604
|
+
return { status: "hold", reasonCode: PAID_RESOURCE_BLOCKED_REASON_CODE };
|
|
40967
41605
|
}
|
|
40968
|
-
|
|
40969
|
-
}
|
|
40970
|
-
function validateExternalAgentRun(value) {
|
|
40971
|
-
const findings = [];
|
|
40972
|
-
const run = asObject(value);
|
|
40973
|
-
if (!run) return [{ id: "run_not_object", detail: "run artifact must be an object" }];
|
|
40974
|
-
for (const field of REQUIRED_RUN_FIELDS) {
|
|
40975
|
-
if (!(field in run)) findings.push({ id: "required_field_missing", field });
|
|
41606
|
+
if (hasCommandReason(/provider_capacity_blocked/i)) {
|
|
41607
|
+
return { status: "hold", reasonCode: "provider_capacity_blocked" };
|
|
40976
41608
|
}
|
|
40977
|
-
if (
|
|
40978
|
-
|
|
41609
|
+
if (hasCommandReason(/byon_voice_number_not_configured/i)) {
|
|
41610
|
+
return { status: "hold", reasonCode: "byon_voice_number_not_configured" };
|
|
40979
41611
|
}
|
|
40980
|
-
if (
|
|
40981
|
-
|
|
41612
|
+
if (hasCommandReason(/contact_phone_provisioning_failed/i)) {
|
|
41613
|
+
return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
|
|
40982
41614
|
}
|
|
40983
|
-
if ((
|
|
40984
|
-
|
|
41615
|
+
if (hasCommandReason(/voice_contact_expected_no_spend_hold/i)) {
|
|
41616
|
+
return { status: "hold", reasonCode: "voice_contact_expected_no_spend_hold" };
|
|
40985
41617
|
}
|
|
40986
|
-
if (
|
|
40987
|
-
|
|
41618
|
+
if (hasCommandReason(/contact_phone_missing/i)) {
|
|
41619
|
+
return { status: "hold", reasonCode: "voice_contact_phone_missing" };
|
|
40988
41620
|
}
|
|
40989
|
-
if (
|
|
40990
|
-
|
|
40991
|
-
if (!asObject(run.environment)) findings.push({ id: "environment_invalid" });
|
|
40992
|
-
if (!asObject(run.artifacts)) findings.push({ id: "artifacts_invalid" });
|
|
40993
|
-
if (toArray2(run.public_entrypoints).length === 0) findings.push({ id: "public_entrypoints_missing" });
|
|
40994
|
-
return findings;
|
|
40995
|
-
}
|
|
40996
|
-
function runSortTime(run) {
|
|
40997
|
-
const raw = String(run.ended_at || run.started_at || "");
|
|
40998
|
-
const time3 = Date.parse(raw);
|
|
40999
|
-
return Number.isFinite(time3) ? time3 : 0;
|
|
41000
|
-
}
|
|
41001
|
-
function latestCommandTime(commands) {
|
|
41002
|
-
const times = commands.map((command) => String(command.completed_at || command.started_at || command.recorded_at || "")).map((raw) => ({ raw, time: Date.parse(raw) })).filter((entry) => Number.isFinite(entry.time)).sort((a, b) => b.time - a.time);
|
|
41003
|
-
return times[0]?.raw ?? null;
|
|
41004
|
-
}
|
|
41005
|
-
function firstCommandTime(commands) {
|
|
41006
|
-
const times = commands.map((command) => String(command.started_at || command.recorded_at || command.completed_at || "")).map((raw) => ({ raw, time: Date.parse(raw) })).filter((entry) => Number.isFinite(entry.time)).sort((a, b) => a.time - b.time);
|
|
41007
|
-
return times[0]?.raw ?? null;
|
|
41008
|
-
}
|
|
41009
|
-
function commandReasonCodes(commands) {
|
|
41010
|
-
const codes = /* @__PURE__ */ new Set();
|
|
41011
|
-
for (const command of commands) {
|
|
41012
|
-
if (command.reason_code) codes.add(String(command.reason_code));
|
|
41013
|
-
for (const reasonCode of toArray2(command.check_reason_codes)) {
|
|
41014
|
-
if (reasonCode) codes.add(String(reasonCode));
|
|
41015
|
-
}
|
|
41621
|
+
if (hasCommandReason(/sim(?:ulation)?[_-]?cert(?:ify|ification)?.*failed|simulation_certification_failed/i)) {
|
|
41622
|
+
return { status: "hold", reasonCode: "simulation_certification_failed" };
|
|
41016
41623
|
}
|
|
41017
|
-
|
|
41018
|
-
}
|
|
41019
|
-
function syntheticStatusFromCommands(commands) {
|
|
41020
|
-
const commandReasons = commandReasonCodes(commands);
|
|
41021
|
-
const failed = commands.find((command) => {
|
|
41022
|
-
const status = String(command.status || "").toLowerCase();
|
|
41023
|
-
return status === "fail" || typeof command.exit_code === "number" && command.exit_code !== 0 && status !== "hold";
|
|
41024
|
-
});
|
|
41025
|
-
if (failed) {
|
|
41026
|
-
return {
|
|
41027
|
-
status: "fail",
|
|
41028
|
-
reasonCode: String(failed.reason_code || commandReasons[0] || "external_agent_command_failed")
|
|
41029
|
-
};
|
|
41624
|
+
if (hasCommandReason(/proof_held/i)) {
|
|
41625
|
+
return { status: "hold", reasonCode: "external_agent_proof_held" };
|
|
41030
41626
|
}
|
|
41031
|
-
|
|
41032
|
-
|
|
41033
|
-
return {
|
|
41034
|
-
status: "hold",
|
|
41035
|
-
reasonCode: String(held.reason_code || commandReasons[0] || "external_agent_command_held")
|
|
41036
|
-
};
|
|
41627
|
+
if (hasCommandReason(/agent_limit_reached/i)) {
|
|
41628
|
+
return { status: "hold", reasonCode: "eval_org_agent_limit_reached" };
|
|
41037
41629
|
}
|
|
41038
|
-
|
|
41039
|
-
|
|
41630
|
+
const agentMetadata = readExternalAgentMetadata(input.run.run_dir);
|
|
41631
|
+
const metadataBlockerCodes = agentMetadata.blocker_reason_codes;
|
|
41632
|
+
const hasMetadataBlocker = (pattern) => metadataBlockerCodes.some((reason) => pattern.test(reason));
|
|
41633
|
+
if (hasMetadataBlocker(/^customer_owned_requirement_unverified:/i)) {
|
|
41634
|
+
return { status: "hold", reasonCode: "customer_owned_requirements_unverified_expected_hold" };
|
|
41040
41635
|
}
|
|
41041
|
-
|
|
41636
|
+
if (hasMetadataBlocker(/^api_health:/i) && metadataBlockerCodes.some((reason) => /^customer_owned_requirement_unverified:/i.test(reason))) {
|
|
41637
|
+
return { status: "hold", reasonCode: "customer_owned_requirements_unverified_expected_hold" };
|
|
41638
|
+
}
|
|
41639
|
+
const firstCommandReasonCode = commandReasonCodes2.find((reason) => reason.trim().length > 0) ?? null;
|
|
41640
|
+
if (firstCommandReasonCode) {
|
|
41641
|
+
return { status: "hold", reasonCode: firstCommandReasonCode };
|
|
41642
|
+
}
|
|
41643
|
+
const lastMessage = readIfExists(input.run.outputs.last_message);
|
|
41644
|
+
const stderr = readIfExists(input.run.outputs.stderr);
|
|
41645
|
+
const combined = `${lastMessage}
|
|
41646
|
+
${stderr}`;
|
|
41647
|
+
if (/need[^.\n]*(?:private|source)[^.\n]*repo|cannot[^.\n]*without[^.\n]*(?:private|source)[^.\n]*repo|clone[^.\n]*(?:private|source)[^.\n]*repo/i.test(combined)) {
|
|
41648
|
+
return { status: "fail", reasonCode: "private_repo_assumption_detected" };
|
|
41649
|
+
}
|
|
41650
|
+
if (/(?:blocked|rejected|declined) by policy|EXEC_POLICY_BLOCKED|command execution was rejected|shell commands were rejected/i.test(combined)) {
|
|
41651
|
+
return { status: "hold", reasonCode: "codex_exec_policy_blocked" };
|
|
41652
|
+
}
|
|
41653
|
+
if (/bwrap:.*(?:RTM_NEWADDR|Operation not permitted|setting up uid map: Permission denied)|bubblewrap.*(?:RTM_NEWADDR|Operation not permitted|setting up uid map: Permission denied)|Failed RTM_NEWADDR|ENV_SANDBOX_EXEC_BLOCKED|permission profiles requiring direct runtime enforcement are incompatible with --use-legacy-landlock|legacy[_ -]?landlock.*incompatible/i.test(combined)) {
|
|
41654
|
+
return { status: "hold", reasonCode: "codex_sandbox_exec_blocked" };
|
|
41655
|
+
}
|
|
41656
|
+
if (/ENV_NETWORK_DNS_BLOCK|Could not resolve host|npm ping.*timeout|NO_EXECUTABLE_INSTALL/i.test(combined)) {
|
|
41657
|
+
return { status: "hold", reasonCode: "codex_network_dns_blocked" };
|
|
41658
|
+
}
|
|
41659
|
+
if (new RegExp(PAID_RESOURCE_BLOCKED_REASON_CODE, "i").test(combined)) {
|
|
41660
|
+
return { status: "hold", reasonCode: PAID_RESOURCE_BLOCKED_REASON_CODE };
|
|
41661
|
+
}
|
|
41662
|
+
if (/provider_capacity_blocked/i.test(combined)) {
|
|
41663
|
+
return { status: "hold", reasonCode: "provider_capacity_blocked" };
|
|
41664
|
+
}
|
|
41665
|
+
if (/byon_voice_number_not_configured/i.test(combined)) {
|
|
41666
|
+
return { status: "hold", reasonCode: "byon_voice_number_not_configured" };
|
|
41667
|
+
}
|
|
41668
|
+
if (/contact_phone_provisioning_failed/i.test(combined)) {
|
|
41669
|
+
return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
|
|
41670
|
+
}
|
|
41671
|
+
if (/voice_contact_expected_no_spend_hold/i.test(combined)) {
|
|
41672
|
+
return { status: "hold", reasonCode: "voice_contact_expected_no_spend_hold" };
|
|
41673
|
+
}
|
|
41674
|
+
if (/contact_phone_missing/i.test(combined)) {
|
|
41675
|
+
return { status: "hold", reasonCode: "voice_contact_phone_missing" };
|
|
41676
|
+
}
|
|
41677
|
+
if (/simulation_certification_failed/i.test(combined)) {
|
|
41678
|
+
return { status: "hold", reasonCode: "simulation_certification_failed" };
|
|
41679
|
+
}
|
|
41680
|
+
if (/proof_held/i.test(combined)) {
|
|
41681
|
+
return { status: "hold", reasonCode: "external_agent_proof_held" };
|
|
41682
|
+
}
|
|
41683
|
+
if (/agent_limit_reached/i.test(combined)) {
|
|
41684
|
+
return { status: "hold", reasonCode: "eval_org_agent_limit_reached" };
|
|
41685
|
+
}
|
|
41686
|
+
if (/browser|approve|approval|login|auth|sign in/i.test(combined) && !proofArtifactPasses(input.run.run_dir)) {
|
|
41687
|
+
return { status: "hold", reasonCode: "auth_browser_approval_required" };
|
|
41688
|
+
}
|
|
41689
|
+
if (input.exitCode !== 0) return { status: "hold", reasonCode: `${input.run.command}_runner_nonzero_exit` };
|
|
41690
|
+
if (proofArtifactPasses(input.run.run_dir)) return { status: "pass", reasonCode: null };
|
|
41691
|
+
return { status: "hold", reasonCode: "external_agent_proof_artifact_missing" };
|
|
41042
41692
|
}
|
|
41043
|
-
function
|
|
41044
|
-
const
|
|
41045
|
-
const
|
|
41046
|
-
const metadata = asObject((0, import_fs17.existsSync)((0, import_path16.join)(runDir, "external-agent-metadata.json")) ? readJson((0, import_path16.join)(runDir, "external-agent-metadata.json")) : {});
|
|
41047
|
-
const blockerCodes = toArray2(metadata?.blocker_reason_codes).map(String).filter(Boolean);
|
|
41048
|
-
const commandClassification = syntheticStatusFromCommands(commands);
|
|
41049
|
-
const status = commandClassification.status === "fail" ? "fail" : blockerCodes.length > 0 ? "hold" : commandClassification.status;
|
|
41050
|
-
const reasonCode = commandClassification.status === "fail" ? commandClassification.reasonCode : blockerCodes[0] || commandClassification.reasonCode;
|
|
41051
|
-
const firstCommand = commands[0] || {};
|
|
41052
|
-
const startedAt = firstCommandTime(commands) || (/* @__PURE__ */ new Date(0)).toISOString();
|
|
41053
|
-
const endedAt = latestCommandTime(commands) || startedAt;
|
|
41054
|
-
const docs = toArray2(metadata?.docs_pages_used).map(String).filter(Boolean);
|
|
41055
|
-
const runId = (0, import_path16.dirname)(runPath).split(/[\\/]/).filter(Boolean).slice(-3).join("-") || "capture-only-run";
|
|
41693
|
+
function buildExecutedExternalAgentRunArtifact(input) {
|
|
41694
|
+
const commands = readCommandRecords(input.run.run_dir);
|
|
41695
|
+
const agentMetadata = readExternalAgentMetadata(input.run.run_dir);
|
|
41056
41696
|
return {
|
|
41057
41697
|
schema_version: "external_agent_run.v1",
|
|
41058
|
-
run_id:
|
|
41059
|
-
status,
|
|
41060
|
-
failure_reason_code:
|
|
41061
|
-
model_provider:
|
|
41062
|
-
model_name:
|
|
41063
|
-
|
|
41064
|
-
|
|
41065
|
-
|
|
41698
|
+
run_id: input.run.run_id,
|
|
41699
|
+
status: input.status,
|
|
41700
|
+
failure_reason_code: input.reasonCode,
|
|
41701
|
+
model_provider: input.run.model_provider,
|
|
41702
|
+
model_name: input.run.model_name,
|
|
41703
|
+
runner_model: input.run.runner_model,
|
|
41704
|
+
agent_shell: `${input.run.command}-exec`,
|
|
41705
|
+
workspace_type: "clean-no-repo-programmatic",
|
|
41706
|
+
prompt_version: input.run.prompt_version,
|
|
41707
|
+
prompt_path: "prompt.txt",
|
|
41708
|
+
started_at: input.startedAt,
|
|
41709
|
+
ended_at: input.endedAt,
|
|
41066
41710
|
manual_intervention_count: 0,
|
|
41711
|
+
manual_interventions: [],
|
|
41067
41712
|
environment: {
|
|
41068
|
-
|
|
41069
|
-
|
|
41713
|
+
os: process.platform,
|
|
41714
|
+
node_version: process.version,
|
|
41715
|
+
npm_version: null,
|
|
41716
|
+
foh_cli_version: CLI_VERSION,
|
|
41717
|
+
runner_exit_code: input.exitCode,
|
|
41718
|
+
runner_timed_out: input.timedOut,
|
|
41719
|
+
duration_ms: input.durationMs
|
|
41070
41720
|
},
|
|
41071
41721
|
public_entrypoints: [
|
|
41072
41722
|
"https://frontofhouse.okii.uk",
|
|
41073
|
-
"
|
|
41074
|
-
|
|
41075
|
-
commands_run: commands.map((command) => String(command.command || "")).filter(Boolean),
|
|
41076
|
-
docs_pages_used: docs,
|
|
41077
|
-
artifacts: {
|
|
41078
|
-
command_log: "commands.ndjson",
|
|
41079
|
-
agent_metadata: (0, import_fs17.existsSync)((0, import_path16.join)(runDir, "external-agent-metadata.json")) ? "external-agent-metadata.json" : null,
|
|
41080
|
-
capture_only: true
|
|
41081
|
-
},
|
|
41082
|
-
summary: "Synthetic run record created from external-agent capture artifacts because run.json was missing."
|
|
41083
|
-
};
|
|
41084
|
-
}
|
|
41085
|
-
function cohortIdForRunPath(root, runPath) {
|
|
41086
|
-
const normalized = (0, import_path16.relative)(root, (0, import_path16.dirname)(runPath)).replaceAll("\\", "/");
|
|
41087
|
-
const parts = normalized.split("/").filter(Boolean);
|
|
41088
|
-
if (parts.length === 0) return ".";
|
|
41089
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(parts[0]) && parts[1]) return `${parts[0]}/${parts[1]}`;
|
|
41090
|
-
return parts[0];
|
|
41091
|
-
}
|
|
41092
|
-
function readRunRecords(root, cwd) {
|
|
41093
|
-
const records = [];
|
|
41094
|
-
const invalid_runs = [];
|
|
41095
|
-
for (const candidate of findRunCandidates(root)) {
|
|
41096
|
-
const file2 = candidate.path;
|
|
41097
|
-
try {
|
|
41098
|
-
const parsed = candidate.synthetic ? synthesizeRunFromCapture(file2) : readJson(file2);
|
|
41099
|
-
const findings = validateExternalAgentRun(parsed);
|
|
41100
|
-
if (findings.length > 0) {
|
|
41101
|
-
invalid_runs.push({ path: (0, import_path16.relative)(cwd, file2).replaceAll("\\", "/"), findings });
|
|
41102
|
-
continue;
|
|
41103
|
-
}
|
|
41104
|
-
const run = parsed;
|
|
41105
|
-
records.push({
|
|
41106
|
-
path: file2,
|
|
41107
|
-
run,
|
|
41108
|
-
cohort_id: cohortIdForRunPath(root, file2),
|
|
41109
|
-
sort_time: runSortTime(run)
|
|
41110
|
-
});
|
|
41111
|
-
} catch (error2) {
|
|
41112
|
-
invalid_runs.push({
|
|
41113
|
-
path: (0, import_path16.relative)(cwd, file2).replaceAll("\\", "/"),
|
|
41114
|
-
findings: [{ id: "json_parse_failed", detail: error2 instanceof Error ? error2.message : String(error2) }]
|
|
41115
|
-
});
|
|
41116
|
-
}
|
|
41117
|
-
}
|
|
41118
|
-
return { records, invalid_runs };
|
|
41119
|
-
}
|
|
41120
|
-
function latestCohortId(records) {
|
|
41121
|
-
return records.slice().sort((a, b) => b.sort_time - a.sort_time || b.path.localeCompare(a.path))[0]?.cohort_id ?? null;
|
|
41122
|
-
}
|
|
41123
|
-
function ownerSubsystemFor(reasonCode) {
|
|
41124
|
-
const reason = String(reasonCode || "").toLowerCase();
|
|
41125
|
-
if (reason.includes("simulation") || reason.includes("certification") || reason.includes("scenario")) return "dojo_certification";
|
|
41126
|
-
if (reason.includes("contact_phone") || reason.includes("voice_contact") || reason.includes("provider_capacity") || reason.includes("byon")) return "voice_contact";
|
|
41127
|
-
if (reason.includes("exec_policy") || reason.includes("policy_blocked") || reason.includes("sandbox") || reason.includes("runner") || reason.includes("codex")) return "infra_runner";
|
|
41128
|
-
if (reason.includes("api") || reason.includes("http_4") || reason.includes("http_5") || reason.includes("404") || reason.includes("500") || reason.includes("roundtrip")) return "api_contract";
|
|
41129
|
-
if (reason.includes("cli") || reason.includes("command") || reason.includes("flag")) return "cli";
|
|
41130
|
-
if (reason.includes("docs") || reason.includes("unclear") || reason.includes("not_found")) return "docs";
|
|
41131
|
-
if (reason.includes("auth") || reason.includes("org") || reason.includes("config")) return "infra_runner";
|
|
41132
|
-
if (reason.includes("runtime") || reason.includes("widget") || reason.includes("proof")) return "runtime";
|
|
41133
|
-
return "product_ux";
|
|
41134
|
-
}
|
|
41135
|
-
function recommendedFixFor(reasonCode) {
|
|
41136
|
-
const owner = ownerSubsystemFor(reasonCode);
|
|
41137
|
-
if (owner === "api_contract") return "fix_api";
|
|
41138
|
-
if (owner === "cli") return "fix_cli";
|
|
41139
|
-
if (owner === "docs") return "fix_docs";
|
|
41140
|
-
if (owner === "runtime") return "fix_runtime";
|
|
41141
|
-
if (owner === "dojo_certification") return "add_test";
|
|
41142
|
-
return "fix_config";
|
|
41143
|
-
}
|
|
41144
|
-
function collapseCommandRecords(records) {
|
|
41145
|
-
const order = [];
|
|
41146
|
-
const byId = /* @__PURE__ */ new Map();
|
|
41147
|
-
for (const record2 of records) {
|
|
41148
|
-
const id = String(record2.command_id || `${record2.recorded_at || ""}:${record2.command || ""}`);
|
|
41149
|
-
if (!byId.has(id)) order.push(id);
|
|
41150
|
-
const previous = byId.get(id);
|
|
41151
|
-
byId.set(id, record2.phase === "completed" ? record2 : previous || record2);
|
|
41152
|
-
}
|
|
41153
|
-
return order.map((id) => byId.get(id)).filter((record2) => Boolean(record2));
|
|
41154
|
-
}
|
|
41155
|
-
function readCommandOutputJson(runDir, command) {
|
|
41156
|
-
const artifact = typeof command.output_artifact === "string" && command.output_artifact.trim() ? command.output_artifact.trim() : "";
|
|
41157
|
-
if (!artifact || artifact.includes("..") || artifact.includes("/") || artifact.includes("\\")) return null;
|
|
41158
|
-
const artifactPath = (0, import_path16.join)(runDir, artifact);
|
|
41159
|
-
if (!(0, import_fs17.existsSync)(artifactPath)) return null;
|
|
41160
|
-
try {
|
|
41161
|
-
const text = (0, import_fs17.readFileSync)(artifactPath, "utf8");
|
|
41162
|
-
const firstObject = text.indexOf("{");
|
|
41163
|
-
const lastObject = text.lastIndexOf("}");
|
|
41164
|
-
if (firstObject < 0 || lastObject <= firstObject) return null;
|
|
41165
|
-
return JSON.parse(text.slice(firstObject, lastObject + 1));
|
|
41166
|
-
} catch {
|
|
41167
|
-
return null;
|
|
41168
|
-
}
|
|
41169
|
-
}
|
|
41170
|
-
function commandTimingBreakdown(command, output) {
|
|
41171
|
-
const schemaVersion = String(output?.schema_version || "");
|
|
41172
|
-
if (schemaVersion === "foh_cli_proof_report.v1") {
|
|
41173
|
-
const timing = asObject(output?.timing) || {};
|
|
41174
|
-
const cacheSources = /* @__PURE__ */ new Map();
|
|
41175
|
-
let cacheWaitMs = 0;
|
|
41176
|
-
let cacheHitCount = 0;
|
|
41177
|
-
let cacheMissCount = 0;
|
|
41178
|
-
for (const check2 of toArray2(output?.checks)) {
|
|
41179
|
-
const detail = asObject(check2)?.detail;
|
|
41180
|
-
const proofCache = asObject(asObject(detail)?.proof_cache);
|
|
41181
|
-
if (!proofCache) continue;
|
|
41182
|
-
if (proofCache["hit"] === true) cacheHitCount += 1;
|
|
41183
|
-
if (proofCache["miss"] === true) cacheMissCount += 1;
|
|
41184
|
-
cacheWaitMs += Number(proofCache["waited_ms"] || 0);
|
|
41185
|
-
increment(cacheSources, proofCache["source"] || "unknown");
|
|
41186
|
-
}
|
|
41187
|
-
return {
|
|
41188
|
-
kind: "proof",
|
|
41189
|
-
command: command.command || "",
|
|
41190
|
-
total_ms: Number(timing.total_ms || command.duration_ms || 0),
|
|
41191
|
-
total_check_duration_ms: Number(timing.total_check_duration_ms || 0),
|
|
41192
|
-
slow_checks: toArray2(timing.slow_checks).slice(0, 5),
|
|
41193
|
-
cache_wait_ms: cacheWaitMs,
|
|
41194
|
-
cache_hit_count: cacheHitCount,
|
|
41195
|
-
cache_miss_count: cacheMissCount,
|
|
41196
|
-
cache_sources: ranked(cacheSources)
|
|
41197
|
-
};
|
|
41198
|
-
}
|
|
41199
|
-
if (schemaVersion === "foh_certification_run.v1") {
|
|
41200
|
-
const timing = asObject(output?.timing) || {};
|
|
41201
|
-
const certificate = asObject(output?.certificate) || {};
|
|
41202
|
-
return {
|
|
41203
|
-
kind: "certification",
|
|
41204
|
-
command: command.command || "",
|
|
41205
|
-
total_ms: Number(timing.total_ms || command.duration_ms || 0),
|
|
41206
|
-
api_ms: Number(timing.api_ms || 0),
|
|
41207
|
-
status: output?.status || null,
|
|
41208
|
-
reason_code: output?.reason_code || null,
|
|
41209
|
-
scenario_summary: certificate.scenario_summary || null
|
|
41210
|
-
};
|
|
41211
|
-
}
|
|
41212
|
-
return null;
|
|
41213
|
-
}
|
|
41214
|
-
function analyzeRunArtifacts(runPath, run, cwd) {
|
|
41215
|
-
const runDir = (0, import_path16.dirname)(runPath);
|
|
41216
|
-
const commands = collapseCommandRecords(readNdjson((0, import_path16.join)(runDir, "commands.ndjson")));
|
|
41217
|
-
const reasonCounts = /* @__PURE__ */ new Map();
|
|
41218
|
-
const slowSteps = [];
|
|
41219
|
-
const timingBreakdowns = [];
|
|
41220
|
-
let completed = 0;
|
|
41221
|
-
let withDuration = 0;
|
|
41222
|
-
let totalDuration = 0;
|
|
41223
|
-
for (const command of commands) {
|
|
41224
|
-
const output = readCommandOutputJson(runDir, command);
|
|
41225
|
-
const breakdown = commandTimingBreakdown(command, output);
|
|
41226
|
-
if (breakdown) timingBreakdowns.push({
|
|
41227
|
-
run_id: run.run_id,
|
|
41228
|
-
run_path: (0, import_path16.relative)(cwd, runPath).replaceAll("\\", "/"),
|
|
41229
|
-
...breakdown
|
|
41230
|
-
});
|
|
41231
|
-
if (command.phase === "completed" || command.completed_at) completed += 1;
|
|
41232
|
-
if (typeof command.duration_ms === "number") {
|
|
41233
|
-
withDuration += 1;
|
|
41234
|
-
totalDuration += command.duration_ms;
|
|
41235
|
-
slowSteps.push({
|
|
41236
|
-
run_id: run.run_id,
|
|
41237
|
-
run_path: (0, import_path16.relative)(cwd, runPath).replaceAll("\\", "/"),
|
|
41238
|
-
command: command.command || "",
|
|
41239
|
-
duration_ms: command.duration_ms,
|
|
41240
|
-
status: command.status || null,
|
|
41241
|
-
reason_code: command.reason_code || null,
|
|
41242
|
-
check_reason_codes: Array.isArray(command.check_reason_codes) ? command.check_reason_codes : []
|
|
41243
|
-
});
|
|
41244
|
-
}
|
|
41245
|
-
if (command.reason_code) increment(reasonCounts, command.reason_code);
|
|
41246
|
-
for (const reasonCode of toArray2(command.check_reason_codes)) {
|
|
41247
|
-
if (reasonCode) increment(reasonCounts, reasonCode);
|
|
41248
|
-
}
|
|
41249
|
-
}
|
|
41250
|
-
const codexEvents = readNdjson((0, import_path16.join)(runDir, "codex-exec.jsonl"));
|
|
41251
|
-
const codexDocs = /* @__PURE__ */ new Set();
|
|
41252
|
-
let codexCommandExecutions = 0;
|
|
41253
|
-
let codexFailedExitCodes = 0;
|
|
41254
|
-
for (const event of codexEvents) {
|
|
41255
|
-
const item = asObject(event.item) || event;
|
|
41256
|
-
if (item.type === "command_execution" && item.status === "completed") {
|
|
41257
|
-
codexCommandExecutions += 1;
|
|
41258
|
-
if (typeof item.exit_code === "number" && item.exit_code !== 0) codexFailedExitCodes += 1;
|
|
41259
|
-
}
|
|
41260
|
-
for (const url2 of collectDocUrls(JSON.stringify(event))) codexDocs.add(url2);
|
|
41261
|
-
}
|
|
41262
|
-
const docs = /* @__PURE__ */ new Set([
|
|
41263
|
-
...toArray2(run.docs_pages_used).map(String),
|
|
41264
|
-
...Array.from(codexDocs)
|
|
41265
|
-
]);
|
|
41266
|
-
return {
|
|
41267
|
-
command_log_present: (0, import_fs17.existsSync)((0, import_path16.join)(runDir, "commands.ndjson")),
|
|
41268
|
-
command_count: commands.length,
|
|
41269
|
-
completed_command_count: completed,
|
|
41270
|
-
missing_completion_count: Math.max(0, commands.length - completed),
|
|
41271
|
-
commands_with_duration_count: withDuration,
|
|
41272
|
-
total_command_duration_ms: totalDuration,
|
|
41273
|
-
command_reason_codes: ranked(reasonCounts),
|
|
41274
|
-
slow_steps: slowSteps.sort((a, b) => Number(b.duration_ms) - Number(a.duration_ms)).slice(0, 10),
|
|
41275
|
-
timing_breakdowns: timingBreakdowns,
|
|
41276
|
-
docs_pages_observed: Array.from(docs).sort(),
|
|
41277
|
-
codex_command_execution_completed_count: codexCommandExecutions,
|
|
41278
|
-
codex_failed_exit_code_count: codexFailedExitCodes
|
|
41279
|
-
};
|
|
41280
|
-
}
|
|
41281
|
-
function summarizeExternalAgentRuns(options) {
|
|
41282
|
-
const cwd = (0, import_path16.resolve)(options.cwd || process.cwd());
|
|
41283
|
-
const root = (0, import_path16.resolve)(cwd, options.root);
|
|
41284
|
-
const loaded = readRunRecords(root, cwd);
|
|
41285
|
-
const selectedCohortId = options.cohortId || (options.currentBaselineOnly ? latestCohortId(loaded.records) : null);
|
|
41286
|
-
const records = selectedCohortId ? loaded.records.filter((record2) => record2.cohort_id === selectedCohortId) : loaded.records;
|
|
41287
|
-
const statusCounts = /* @__PURE__ */ new Map();
|
|
41288
|
-
const modelCounts = /* @__PURE__ */ new Map();
|
|
41289
|
-
const failureCounts = /* @__PURE__ */ new Map();
|
|
41290
|
-
const commandReasonCounts = /* @__PURE__ */ new Map();
|
|
41291
|
-
const docsCounts = /* @__PURE__ */ new Map();
|
|
41292
|
-
const slowSteps = [];
|
|
41293
|
-
const timingBreakdowns = [];
|
|
41294
|
-
let manualInterventions = 0;
|
|
41295
|
-
let commandCount = 0;
|
|
41296
|
-
let completedCommandCount = 0;
|
|
41297
|
-
let missingCompletionCount = 0;
|
|
41298
|
-
let commandsWithDurationCount = 0;
|
|
41299
|
-
let totalCommandDurationMs = 0;
|
|
41300
|
-
let commandLogRunCount = 0;
|
|
41301
|
-
let codexCommandExecutions = 0;
|
|
41302
|
-
let codexFailedExitCodes = 0;
|
|
41303
|
-
for (const record2 of records) {
|
|
41304
|
-
const run = record2.run;
|
|
41305
|
-
increment(statusCounts, run.status);
|
|
41306
|
-
increment(modelCounts, `${run.model_provider}/${run.model_name}`);
|
|
41307
|
-
manualInterventions += Number(run.manual_intervention_count || 0);
|
|
41308
|
-
if (run.status !== "pass") increment(failureCounts, run.failure_reason_code || "unknown");
|
|
41309
|
-
const artifactSummary = analyzeRunArtifacts(record2.path, run, cwd);
|
|
41310
|
-
if (artifactSummary.command_log_present) commandLogRunCount += 1;
|
|
41311
|
-
commandCount += Number(artifactSummary.command_count || 0);
|
|
41312
|
-
completedCommandCount += Number(artifactSummary.completed_command_count || 0);
|
|
41313
|
-
missingCompletionCount += Number(artifactSummary.missing_completion_count || 0);
|
|
41314
|
-
commandsWithDurationCount += Number(artifactSummary.commands_with_duration_count || 0);
|
|
41315
|
-
totalCommandDurationMs += Number(artifactSummary.total_command_duration_ms || 0);
|
|
41316
|
-
codexCommandExecutions += Number(artifactSummary.codex_command_execution_completed_count || 0);
|
|
41317
|
-
codexFailedExitCodes += Number(artifactSummary.codex_failed_exit_code_count || 0);
|
|
41318
|
-
for (const row of toArray2(artifactSummary.slow_steps)) slowSteps.push(row);
|
|
41319
|
-
for (const row of toArray2(artifactSummary.timing_breakdowns)) timingBreakdowns.push(row);
|
|
41320
|
-
for (const row of toArray2(artifactSummary.command_reason_codes)) {
|
|
41321
|
-
const entry = asObject(row);
|
|
41322
|
-
if (entry) increment(commandReasonCounts, entry.key, Number(entry.count || 1));
|
|
41323
|
-
}
|
|
41324
|
-
for (const page of toArray2(artifactSummary.docs_pages_observed)) increment(docsCounts, page);
|
|
41325
|
-
}
|
|
41326
|
-
const topFailures = ranked(failureCounts);
|
|
41327
|
-
const commandReasonCodes2 = ranked(commandReasonCounts);
|
|
41328
|
-
const recommendedFixes = topFailures.map((failure) => ({
|
|
41329
|
-
reason_code: failure.key,
|
|
41330
|
-
count: failure.count,
|
|
41331
|
-
recommended_fix: recommendedFixFor(failure.key),
|
|
41332
|
-
owner_subsystem: ownerSubsystemFor(failure.key)
|
|
41333
|
-
}));
|
|
41334
|
-
const nextRecommendedFix = recommendedFixes[0] || null;
|
|
41335
|
-
return {
|
|
41336
|
-
schema_version: "external_agent_run_summary.v1",
|
|
41337
|
-
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41338
|
-
root: (0, import_path16.relative)(cwd, root).replaceAll("\\", "/") || ".",
|
|
41339
|
-
cohort_id: selectedCohortId,
|
|
41340
|
-
current_baseline_only: Boolean(selectedCohortId),
|
|
41341
|
-
run_count: records.length,
|
|
41342
|
-
invalid_run_count: selectedCohortId ? 0 : loaded.invalid_runs.length,
|
|
41343
|
-
status_counts: Object.fromEntries(statusCounts),
|
|
41344
|
-
model_counts: ranked(modelCounts),
|
|
41345
|
-
manual_intervention_count: manualInterventions,
|
|
41346
|
-
top_failure_reason_codes: topFailures,
|
|
41347
|
-
docs_pages_observed: ranked(docsCounts),
|
|
41348
|
-
command_telemetry: {
|
|
41349
|
-
run_count_with_command_log: commandLogRunCount,
|
|
41350
|
-
command_count: commandCount,
|
|
41351
|
-
completed_command_count: completedCommandCount,
|
|
41352
|
-
missing_completion_count: missingCompletionCount,
|
|
41353
|
-
commands_with_duration_count: commandsWithDurationCount,
|
|
41354
|
-
total_command_duration_ms: totalCommandDurationMs,
|
|
41355
|
-
command_reason_codes: commandReasonCodes2,
|
|
41356
|
-
slow_steps: slowSteps.sort((a, b) => Number(b.duration_ms || 0) - Number(a.duration_ms || 0) || String(a.command || "").localeCompare(String(b.command || ""))).slice(0, 20),
|
|
41357
|
-
timing_breakdowns: timingBreakdowns.sort((a, b) => Number(b.total_ms || 0) - Number(a.total_ms || 0) || String(a.command || "").localeCompare(String(b.command || ""))).slice(0, 20)
|
|
41358
|
-
},
|
|
41359
|
-
codex_telemetry: {
|
|
41360
|
-
command_execution_completed_count: codexCommandExecutions,
|
|
41361
|
-
failed_exit_code_count: codexFailedExitCodes
|
|
41362
|
-
},
|
|
41363
|
-
recommended_fixes: recommendedFixes,
|
|
41364
|
-
next_recommended_fix: nextRecommendedFix,
|
|
41365
|
-
fix_selection_policy: {
|
|
41366
|
-
mode: "coherent_failure_cluster_first",
|
|
41367
|
-
rule: "Fix the highest-impact owner subsystem locally with focused proof, then rerun the same prompt once externally.",
|
|
41368
|
-
run_failure_weight: 3,
|
|
41369
|
-
command_reason_weight: 1
|
|
41370
|
-
},
|
|
41371
|
-
next_commands: nextRecommendedFix ? [`foh bug improve --from external-agent-run --file <run_dir>/run.json --json`] : [],
|
|
41372
|
-
invalid_runs: selectedCohortId ? [] : loaded.invalid_runs,
|
|
41373
|
-
run_paths: records.map((record2) => (0, import_path16.relative)(cwd, record2.path).replaceAll("\\", "/")).sort()
|
|
41374
|
-
};
|
|
41375
|
-
}
|
|
41376
|
-
function runExternalAgentRunSummary(options) {
|
|
41377
|
-
const summary = summarizeExternalAgentRuns(options);
|
|
41378
|
-
const invalidRuns = toArray2(summary.invalid_runs);
|
|
41379
|
-
const status = invalidRuns.length > 0 ? "failed" : "passed";
|
|
41380
|
-
const report = {
|
|
41381
|
-
report_schema_version: "script_report.v1",
|
|
41382
|
-
script: "foh eval external-agent summary",
|
|
41383
|
-
checked_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41384
|
-
status,
|
|
41385
|
-
errors: invalidRuns.map((entry) => {
|
|
41386
|
-
const object3 = asObject(entry);
|
|
41387
|
-
return `${object3?.path || "unknown"}: ${JSON.stringify(object3?.findings || [])}`;
|
|
41388
|
-
}),
|
|
41389
|
-
warnings: Number(summary.run_count || 0) === 0 ? ["no external-agent run artifacts found"] : [],
|
|
41390
|
-
report: summary
|
|
41391
|
-
};
|
|
41392
|
-
if (options.out) {
|
|
41393
|
-
(0, import_fs17.mkdirSync)((0, import_path16.dirname)((0, import_path16.resolve)(options.cwd || process.cwd(), options.out)), { recursive: true });
|
|
41394
|
-
(0, import_fs17.writeFileSync)((0, import_path16.resolve)(options.cwd || process.cwd(), options.out), `${JSON.stringify(summary, null, 2)}
|
|
41395
|
-
`, "utf8");
|
|
41396
|
-
}
|
|
41397
|
-
if (options.report) {
|
|
41398
|
-
(0, import_fs17.mkdirSync)((0, import_path16.dirname)((0, import_path16.resolve)(options.cwd || process.cwd(), options.report)), { recursive: true });
|
|
41399
|
-
(0, import_fs17.writeFileSync)((0, import_path16.resolve)(options.cwd || process.cwd(), options.report), `${JSON.stringify(report, null, 2)}
|
|
41400
|
-
`, "utf8");
|
|
41401
|
-
}
|
|
41402
|
-
return { summary, report };
|
|
41403
|
-
}
|
|
41404
|
-
|
|
41405
|
-
// src/lib/external-agent-executor-classification.ts
|
|
41406
|
-
function proofArtifactPasses(runDir) {
|
|
41407
|
-
const proofPath = (0, import_path17.join)(runDir, "proof.json");
|
|
41408
|
-
if (!(0, import_fs18.existsSync)(proofPath)) return false;
|
|
41409
|
-
try {
|
|
41410
|
-
const parsed = JSON.parse((0, import_fs18.readFileSync)(proofPath, "utf8"));
|
|
41411
|
-
return parsed.ok === true || parsed.status === "pass" || parsed.status === "passed";
|
|
41412
|
-
} catch {
|
|
41413
|
-
return false;
|
|
41414
|
-
}
|
|
41415
|
-
}
|
|
41416
|
-
function readIfExists(path2) {
|
|
41417
|
-
return (0, import_fs18.existsSync)(path2) ? (0, import_fs18.readFileSync)(path2, "utf8") : "";
|
|
41418
|
-
}
|
|
41419
|
-
function relativeArtifactName(path2) {
|
|
41420
|
-
return (0, import_path17.basename)(path2);
|
|
41421
|
-
}
|
|
41422
|
-
function classifyExternalAgentRun(input) {
|
|
41423
|
-
if (input.timedOut) return { status: "hold", reasonCode: `${input.run.command}_runner_timeout` };
|
|
41424
|
-
if (!input.artifactSafetyOk) return { status: "fail", reasonCode: "external_agent_artifact_safety_blocked" };
|
|
41425
|
-
const completedCommands = readCommandRecords(input.run.run_dir).filter((record2) => record2.phase === "completed");
|
|
41426
|
-
const observedVersions = completedCommands.map((record2) => String(record2.cli_version || "").trim()).filter((version2) => /^\d+\.\d+\.\d+$/.test(version2));
|
|
41427
|
-
if (observedVersions.some((version2) => version2 !== CLI_VERSION)) {
|
|
41428
|
-
return { status: "hold", reasonCode: "external_agent_cli_version_drift" };
|
|
41429
|
-
}
|
|
41430
|
-
const commandReasonCodes2 = completedCommands.flatMap((record2) => [
|
|
41431
|
-
String(record2.reason_code || ""),
|
|
41432
|
-
...Array.isArray(record2.check_reason_codes) ? record2.check_reason_codes.map((code) => String(code || "")) : []
|
|
41433
|
-
]).filter(Boolean);
|
|
41434
|
-
const hasCommandReason = (pattern) => commandReasonCodes2.some((reason) => pattern.test(reason));
|
|
41435
|
-
if (hasCommandReason(new RegExp(PAID_RESOURCE_BLOCKED_REASON_CODE, "i"))) {
|
|
41436
|
-
return { status: "hold", reasonCode: PAID_RESOURCE_BLOCKED_REASON_CODE };
|
|
41437
|
-
}
|
|
41438
|
-
if (hasCommandReason(/provider_capacity_blocked/i)) {
|
|
41439
|
-
return { status: "hold", reasonCode: "provider_capacity_blocked" };
|
|
41440
|
-
}
|
|
41441
|
-
if (hasCommandReason(/byon_voice_number_not_configured/i)) {
|
|
41442
|
-
return { status: "hold", reasonCode: "byon_voice_number_not_configured" };
|
|
41443
|
-
}
|
|
41444
|
-
if (hasCommandReason(/contact_phone_provisioning_failed/i)) {
|
|
41445
|
-
return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
|
|
41446
|
-
}
|
|
41447
|
-
if (hasCommandReason(/voice_contact_expected_no_spend_hold/i)) {
|
|
41448
|
-
return { status: "hold", reasonCode: "voice_contact_expected_no_spend_hold" };
|
|
41449
|
-
}
|
|
41450
|
-
if (hasCommandReason(/contact_phone_missing/i)) {
|
|
41451
|
-
return { status: "hold", reasonCode: "voice_contact_phone_missing" };
|
|
41452
|
-
}
|
|
41453
|
-
if (hasCommandReason(/sim(?:ulation)?[_-]?cert(?:ify|ification)?.*failed|simulation_certification_failed/i)) {
|
|
41454
|
-
return { status: "hold", reasonCode: "simulation_certification_failed" };
|
|
41455
|
-
}
|
|
41456
|
-
if (hasCommandReason(/proof_held/i)) {
|
|
41457
|
-
return { status: "hold", reasonCode: "external_agent_proof_held" };
|
|
41458
|
-
}
|
|
41459
|
-
if (hasCommandReason(/agent_limit_reached/i)) {
|
|
41460
|
-
return { status: "hold", reasonCode: "eval_org_agent_limit_reached" };
|
|
41461
|
-
}
|
|
41462
|
-
const agentMetadata = readExternalAgentMetadata(input.run.run_dir);
|
|
41463
|
-
const metadataBlockerCodes = agentMetadata.blocker_reason_codes;
|
|
41464
|
-
const hasMetadataBlocker = (pattern) => metadataBlockerCodes.some((reason) => pattern.test(reason));
|
|
41465
|
-
if (hasMetadataBlocker(/^customer_owned_requirement_unverified:/i)) {
|
|
41466
|
-
return { status: "hold", reasonCode: "customer_owned_requirements_unverified_expected_hold" };
|
|
41467
|
-
}
|
|
41468
|
-
if (hasMetadataBlocker(/^api_health:/i) && metadataBlockerCodes.some((reason) => /^customer_owned_requirement_unverified:/i.test(reason))) {
|
|
41469
|
-
return { status: "hold", reasonCode: "customer_owned_requirements_unverified_expected_hold" };
|
|
41470
|
-
}
|
|
41471
|
-
const firstCommandReasonCode = commandReasonCodes2.find((reason) => reason.trim().length > 0) ?? null;
|
|
41472
|
-
if (firstCommandReasonCode) {
|
|
41473
|
-
return { status: "hold", reasonCode: firstCommandReasonCode };
|
|
41474
|
-
}
|
|
41475
|
-
const lastMessage = readIfExists(input.run.outputs.last_message);
|
|
41476
|
-
const stderr = readIfExists(input.run.outputs.stderr);
|
|
41477
|
-
const combined = `${lastMessage}
|
|
41478
|
-
${stderr}`;
|
|
41479
|
-
if (/need[^.\n]*(?:private|source)[^.\n]*repo|cannot[^.\n]*without[^.\n]*(?:private|source)[^.\n]*repo|clone[^.\n]*(?:private|source)[^.\n]*repo/i.test(combined)) {
|
|
41480
|
-
return { status: "fail", reasonCode: "private_repo_assumption_detected" };
|
|
41481
|
-
}
|
|
41482
|
-
if (/(?:blocked|rejected|declined) by policy|EXEC_POLICY_BLOCKED|command execution was rejected|shell commands were rejected/i.test(combined)) {
|
|
41483
|
-
return { status: "hold", reasonCode: "codex_exec_policy_blocked" };
|
|
41484
|
-
}
|
|
41485
|
-
if (/bwrap:.*(?:RTM_NEWADDR|Operation not permitted|setting up uid map: Permission denied)|bubblewrap.*(?:RTM_NEWADDR|Operation not permitted|setting up uid map: Permission denied)|Failed RTM_NEWADDR|ENV_SANDBOX_EXEC_BLOCKED|permission profiles requiring direct runtime enforcement are incompatible with --use-legacy-landlock|legacy[_ -]?landlock.*incompatible/i.test(combined)) {
|
|
41486
|
-
return { status: "hold", reasonCode: "codex_sandbox_exec_blocked" };
|
|
41487
|
-
}
|
|
41488
|
-
if (/ENV_NETWORK_DNS_BLOCK|Could not resolve host|npm ping.*timeout|NO_EXECUTABLE_INSTALL/i.test(combined)) {
|
|
41489
|
-
return { status: "hold", reasonCode: "codex_network_dns_blocked" };
|
|
41490
|
-
}
|
|
41491
|
-
if (new RegExp(PAID_RESOURCE_BLOCKED_REASON_CODE, "i").test(combined)) {
|
|
41492
|
-
return { status: "hold", reasonCode: PAID_RESOURCE_BLOCKED_REASON_CODE };
|
|
41493
|
-
}
|
|
41494
|
-
if (/provider_capacity_blocked/i.test(combined)) {
|
|
41495
|
-
return { status: "hold", reasonCode: "provider_capacity_blocked" };
|
|
41496
|
-
}
|
|
41497
|
-
if (/byon_voice_number_not_configured/i.test(combined)) {
|
|
41498
|
-
return { status: "hold", reasonCode: "byon_voice_number_not_configured" };
|
|
41499
|
-
}
|
|
41500
|
-
if (/contact_phone_provisioning_failed/i.test(combined)) {
|
|
41501
|
-
return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
|
|
41502
|
-
}
|
|
41503
|
-
if (/voice_contact_expected_no_spend_hold/i.test(combined)) {
|
|
41504
|
-
return { status: "hold", reasonCode: "voice_contact_expected_no_spend_hold" };
|
|
41505
|
-
}
|
|
41506
|
-
if (/contact_phone_missing/i.test(combined)) {
|
|
41507
|
-
return { status: "hold", reasonCode: "voice_contact_phone_missing" };
|
|
41508
|
-
}
|
|
41509
|
-
if (/simulation_certification_failed/i.test(combined)) {
|
|
41510
|
-
return { status: "hold", reasonCode: "simulation_certification_failed" };
|
|
41511
|
-
}
|
|
41512
|
-
if (/proof_held/i.test(combined)) {
|
|
41513
|
-
return { status: "hold", reasonCode: "external_agent_proof_held" };
|
|
41514
|
-
}
|
|
41515
|
-
if (/agent_limit_reached/i.test(combined)) {
|
|
41516
|
-
return { status: "hold", reasonCode: "eval_org_agent_limit_reached" };
|
|
41517
|
-
}
|
|
41518
|
-
if (/browser|approve|approval|login|auth|sign in/i.test(combined) && !proofArtifactPasses(input.run.run_dir)) {
|
|
41519
|
-
return { status: "hold", reasonCode: "auth_browser_approval_required" };
|
|
41520
|
-
}
|
|
41521
|
-
if (input.exitCode !== 0) return { status: "hold", reasonCode: `${input.run.command}_runner_nonzero_exit` };
|
|
41522
|
-
if (proofArtifactPasses(input.run.run_dir)) return { status: "pass", reasonCode: null };
|
|
41523
|
-
return { status: "hold", reasonCode: "external_agent_proof_artifact_missing" };
|
|
41524
|
-
}
|
|
41525
|
-
function buildExecutedExternalAgentRunArtifact(input) {
|
|
41526
|
-
const commands = readCommandRecords(input.run.run_dir);
|
|
41527
|
-
const agentMetadata = readExternalAgentMetadata(input.run.run_dir);
|
|
41528
|
-
return {
|
|
41529
|
-
schema_version: "external_agent_run.v1",
|
|
41530
|
-
run_id: input.run.run_id,
|
|
41531
|
-
status: input.status,
|
|
41532
|
-
failure_reason_code: input.reasonCode,
|
|
41533
|
-
model_provider: input.run.model_provider,
|
|
41534
|
-
model_name: input.run.model_name,
|
|
41535
|
-
runner_model: input.run.runner_model,
|
|
41536
|
-
agent_shell: `${input.run.command}-exec`,
|
|
41537
|
-
workspace_type: "clean-no-repo-programmatic",
|
|
41538
|
-
prompt_version: input.run.prompt_version,
|
|
41539
|
-
prompt_path: "prompt.txt",
|
|
41540
|
-
started_at: input.startedAt,
|
|
41541
|
-
ended_at: input.endedAt,
|
|
41542
|
-
manual_intervention_count: 0,
|
|
41543
|
-
manual_interventions: [],
|
|
41544
|
-
environment: {
|
|
41545
|
-
os: process.platform,
|
|
41546
|
-
node_version: process.version,
|
|
41547
|
-
npm_version: null,
|
|
41548
|
-
foh_cli_version: CLI_VERSION,
|
|
41549
|
-
runner_exit_code: input.exitCode,
|
|
41550
|
-
runner_timed_out: input.timedOut,
|
|
41551
|
-
duration_ms: input.durationMs
|
|
41552
|
-
},
|
|
41553
|
-
public_entrypoints: [
|
|
41554
|
-
"https://frontofhouse.okii.uk",
|
|
41555
|
-
"https://frontofhouse.okii.uk/llms.txt",
|
|
41556
|
-
"https://frontofhouse.okii.uk/openapi.yaml",
|
|
41723
|
+
"https://frontofhouse.okii.uk/llms.txt",
|
|
41724
|
+
"https://frontofhouse.okii.uk/openapi.yaml",
|
|
41557
41725
|
"npx --yes @f-o-h/cli@latest"
|
|
41558
41726
|
],
|
|
41559
41727
|
commands_run: commands.map((command) => command.command),
|
|
@@ -41576,13 +41744,13 @@ function buildExecutedExternalAgentRunArtifact(input) {
|
|
|
41576
41744
|
},
|
|
41577
41745
|
artifacts: {
|
|
41578
41746
|
terminal_transcript: relativeArtifactName(input.run.outputs.jsonl),
|
|
41579
|
-
command_log: (0,
|
|
41580
|
-
proof_bundle: (0,
|
|
41581
|
-
replay_packet: (0,
|
|
41582
|
-
knowledge_packet: (0,
|
|
41747
|
+
command_log: (0, import_fs17.existsSync)((0, import_path16.join)(input.run.run_dir, "commands.ndjson")) ? "commands.ndjson" : null,
|
|
41748
|
+
proof_bundle: (0, import_fs17.existsSync)((0, import_path16.join)(input.run.run_dir, "proof.json")) ? "proof.json" : null,
|
|
41749
|
+
replay_packet: (0, import_fs17.existsSync)((0, import_path16.join)(input.run.run_dir, "replay.json")) ? "replay.json" : null,
|
|
41750
|
+
knowledge_packet: (0, import_fs17.existsSync)((0, import_path16.join)(input.run.run_dir, "knowledge.json")) ? "knowledge.json" : null,
|
|
41583
41751
|
improvement_packet: input.status === "pass" ? null : "improvement-packet.json",
|
|
41584
41752
|
agent_metadata: agentMetadata.path,
|
|
41585
|
-
notes: (0,
|
|
41753
|
+
notes: (0, import_fs17.existsSync)((0, import_path16.join)(input.run.run_dir, "notes.md")) ? "notes.md" : null,
|
|
41586
41754
|
runner_last_message: relativeArtifactName(input.run.outputs.last_message),
|
|
41587
41755
|
runner_stderr: relativeArtifactName(input.run.outputs.stderr),
|
|
41588
41756
|
codex_last_message: input.run.command === "codex" ? relativeArtifactName(input.run.outputs.last_message) : null,
|
|
@@ -41590,25 +41758,25 @@ function buildExecutedExternalAgentRunArtifact(input) {
|
|
|
41590
41758
|
artifact_safety: relativeArtifactName(input.run.outputs.artifact_safety)
|
|
41591
41759
|
},
|
|
41592
41760
|
summary: input.status === "pass" ? `Controlled ${input.run.command} external-agent run produced passing proof evidence.` : `Controlled ${input.run.command} external-agent run ended as ${input.status} with reason ${input.reasonCode}.`,
|
|
41593
|
-
next_commands: input.status === "pass" ? [
|
|
41761
|
+
next_commands: input.status === "pass" ? [externalAgentSummaryTemplateCommand()] : [
|
|
41594
41762
|
"foh eval external-agent scan-artifacts --run-dir <run_dir> --private-repo-root <private_repo_root> --write-redacted --json",
|
|
41595
41763
|
"foh bug improve --from external-agent-run --file <run_dir>/run.json --out <run_dir>/improvement-packet.json --json",
|
|
41596
|
-
|
|
41764
|
+
externalAgentSummaryTemplateCommand()
|
|
41597
41765
|
]
|
|
41598
41766
|
};
|
|
41599
41767
|
}
|
|
41600
41768
|
|
|
41601
41769
|
// src/lib/external-agent-runner-execution.ts
|
|
41602
41770
|
var import_child_process5 = require("child_process");
|
|
41603
|
-
var
|
|
41604
|
-
var
|
|
41771
|
+
var import_fs18 = require("fs");
|
|
41772
|
+
var import_path17 = require("path");
|
|
41605
41773
|
function buildCommandInvocation(command, args) {
|
|
41606
|
-
if (
|
|
41607
|
-
const binDir = (0,
|
|
41608
|
-
const codexEntrypoint = (0,
|
|
41609
|
-
if ((0,
|
|
41610
|
-
const geminiEntrypoint = (0,
|
|
41611
|
-
if ((0,
|
|
41774
|
+
if (command.toLowerCase().endsWith(".cmd")) {
|
|
41775
|
+
const binDir = (0, import_path17.dirname)(command);
|
|
41776
|
+
const codexEntrypoint = (0, import_path17.join)(binDir, "node_modules", "@openai", "codex", "bin", "codex.js");
|
|
41777
|
+
if ((0, import_fs18.existsSync)(codexEntrypoint)) return { command: process.execPath, args: [codexEntrypoint, ...args] };
|
|
41778
|
+
const geminiEntrypoint = (0, import_path17.join)(binDir, "node_modules", "@google", "gemini-cli", "bundle", "gemini.js");
|
|
41779
|
+
if ((0, import_fs18.existsSync)(geminiEntrypoint)) return { command: process.execPath, args: ["--no-warnings=DEP0040", geminiEntrypoint, ...args] };
|
|
41612
41780
|
}
|
|
41613
41781
|
return { command, args };
|
|
41614
41782
|
}
|
|
@@ -41623,8 +41791,8 @@ function spawnExternalAgentRunner(input) {
|
|
|
41623
41791
|
stdio: ["pipe", "pipe", "pipe"],
|
|
41624
41792
|
windowsHide: true
|
|
41625
41793
|
});
|
|
41626
|
-
const stdout = (0,
|
|
41627
|
-
const stderr = (0,
|
|
41794
|
+
const stdout = (0, import_fs18.createWriteStream)(input.stdoutPath, { flags: "w" });
|
|
41795
|
+
const stderr = (0, import_fs18.createWriteStream)(input.stderrPath, { flags: "w" });
|
|
41628
41796
|
child.stdout.pipe(stdout);
|
|
41629
41797
|
child.stderr.pipe(stderr);
|
|
41630
41798
|
child.stdin.end(input.prompt);
|
|
@@ -41736,14 +41904,14 @@ async function runExternalAgentEvalAuthPreflight(env = process.env, options = {}
|
|
|
41736
41904
|
};
|
|
41737
41905
|
}
|
|
41738
41906
|
function normalizeForCompare(path2) {
|
|
41739
|
-
const resolved = (0,
|
|
41907
|
+
const resolved = (0, import_path18.resolve)(path2);
|
|
41740
41908
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
41741
41909
|
}
|
|
41742
41910
|
function isPathInside(childPath, parentPath) {
|
|
41743
41911
|
const child = normalizeForCompare(childPath);
|
|
41744
41912
|
const parent = normalizeForCompare(parentPath);
|
|
41745
|
-
const rel = (0,
|
|
41746
|
-
return rel === "" || !!rel && !rel.startsWith("..") && !(0,
|
|
41913
|
+
const rel = (0, import_path18.relative)(parent, child);
|
|
41914
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !(0, import_path18.isAbsolute)(rel);
|
|
41747
41915
|
}
|
|
41748
41916
|
function requireString(value, field) {
|
|
41749
41917
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -41752,10 +41920,10 @@ function requireString(value, field) {
|
|
|
41752
41920
|
return value;
|
|
41753
41921
|
}
|
|
41754
41922
|
function readBatch(batchPath) {
|
|
41755
|
-
if (!(0,
|
|
41923
|
+
if (!(0, import_fs19.existsSync)(batchPath)) {
|
|
41756
41924
|
throw new ExternalAgentExecutorError("external_agent_batch_not_found", `Batch file not found: ${batchPath}`);
|
|
41757
41925
|
}
|
|
41758
|
-
const parsed = JSON.parse((0,
|
|
41926
|
+
const parsed = JSON.parse((0, import_fs19.readFileSync)(batchPath, "utf8"));
|
|
41759
41927
|
if (parsed.schema_version !== "external_agent_batch_plan.v1") {
|
|
41760
41928
|
throw new ExternalAgentExecutorError("invalid_external_agent_batch", "Batch schema_version must be external_agent_batch_plan.v1.");
|
|
41761
41929
|
}
|
|
@@ -41792,8 +41960,8 @@ function resolveCodexProbeCommand() {
|
|
|
41792
41960
|
if (process.platform !== "win32") return "codex";
|
|
41793
41961
|
const appData = process.env.APPDATA;
|
|
41794
41962
|
if (appData) {
|
|
41795
|
-
const appDataShim = (0,
|
|
41796
|
-
if ((0,
|
|
41963
|
+
const appDataShim = (0, import_path18.join)(appData, "npm", "codex.cmd");
|
|
41964
|
+
if ((0, import_fs19.existsSync)(appDataShim)) return appDataShim;
|
|
41797
41965
|
}
|
|
41798
41966
|
return "codex.cmd";
|
|
41799
41967
|
}
|
|
@@ -41804,8 +41972,8 @@ function resolveGeminiProbeCommand() {
|
|
|
41804
41972
|
if (process.platform !== "win32") return "gemini";
|
|
41805
41973
|
const appData = process.env.APPDATA;
|
|
41806
41974
|
if (appData) {
|
|
41807
|
-
const appDataShim = (0,
|
|
41808
|
-
if ((0,
|
|
41975
|
+
const appDataShim = (0, import_path18.join)(appData, "npm", "gemini.cmd");
|
|
41976
|
+
if ((0, import_fs19.existsSync)(appDataShim)) return appDataShim;
|
|
41809
41977
|
}
|
|
41810
41978
|
return "gemini.cmd";
|
|
41811
41979
|
}
|
|
@@ -42076,34 +42244,34 @@ function safeRunId(value) {
|
|
|
42076
42244
|
return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
42077
42245
|
}
|
|
42078
42246
|
function resolveWorkspaceRoot(input) {
|
|
42079
|
-
if (input.workspaceRoot) return (0,
|
|
42080
|
-
const batchStem = (0,
|
|
42081
|
-
const repoStem = (0,
|
|
42082
|
-
return (0,
|
|
42247
|
+
if (input.workspaceRoot) return (0, import_path18.resolve)(input.workspaceRoot);
|
|
42248
|
+
const batchStem = (0, import_path18.basename)((0, import_path18.resolve)(input.batchPath)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
|
|
42249
|
+
const repoStem = (0, import_path18.basename)((0, import_path18.resolve)(input.privateRepoRoot)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
|
|
42250
|
+
return (0, import_path18.resolve)((0, import_os3.tmpdir)(), "foh-external-agent-workspaces", repoStem, batchStem);
|
|
42083
42251
|
}
|
|
42084
42252
|
function findNearestGitRoot(startPath) {
|
|
42085
|
-
let current = (0,
|
|
42253
|
+
let current = (0, import_path18.resolve)(startPath);
|
|
42086
42254
|
while (true) {
|
|
42087
|
-
if ((0,
|
|
42088
|
-
const parent = (0,
|
|
42255
|
+
if ((0, import_fs19.existsSync)((0, import_path18.join)(current, ".git"))) return current;
|
|
42256
|
+
const parent = (0, import_path18.dirname)(current);
|
|
42089
42257
|
if (parent === current) return null;
|
|
42090
42258
|
current = parent;
|
|
42091
42259
|
}
|
|
42092
42260
|
}
|
|
42093
42261
|
function resolvePrivateRepoRoot(input) {
|
|
42094
42262
|
if (input.explicitPrivateRepoRoot) {
|
|
42095
|
-
return { root: (0,
|
|
42263
|
+
return { root: (0, import_path18.resolve)(input.explicitPrivateRepoRoot), explicit: true };
|
|
42096
42264
|
}
|
|
42097
|
-
const cwd = (0,
|
|
42265
|
+
const cwd = (0, import_path18.resolve)(input.cwd || process.cwd());
|
|
42098
42266
|
const gitRoot = findNearestGitRoot(cwd);
|
|
42099
42267
|
if (gitRoot) return { root: gitRoot, explicit: false };
|
|
42100
42268
|
return {
|
|
42101
|
-
root: (0,
|
|
42269
|
+
root: (0, import_path18.join)(cwd, ".foh-no-private-repo-root-sentinel"),
|
|
42102
42270
|
explicit: false
|
|
42103
42271
|
};
|
|
42104
42272
|
}
|
|
42105
42273
|
function promptVersionFromPath(promptPath) {
|
|
42106
|
-
const raw = (0,
|
|
42274
|
+
const raw = (0, import_fs19.readFileSync)(promptPath, "utf8");
|
|
42107
42275
|
if (raw.includes("arbitrary-agency setup context") || raw.includes("Mission: prove whether an arbitrary estate agency") || raw.includes("ops reporting agency-setup-preview") || raw.includes("ops reporting agency-setup-workflow")) return "arbitrary-agency-setup-release.v1";
|
|
42108
42276
|
if (raw.includes("Do not assume access to the private source repository")) return "blank-setup.v1";
|
|
42109
42277
|
return "unknown";
|
|
@@ -42113,7 +42281,7 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
42113
42281
|
if (runner !== "codex" && runner !== "gemini") {
|
|
42114
42282
|
throw new ExternalAgentExecutorError("unsupported_external_agent_runner", `Unsupported runner: ${runner}`);
|
|
42115
42283
|
}
|
|
42116
|
-
const batchPath = (0,
|
|
42284
|
+
const batchPath = (0, import_path18.resolve)(options.batchPath);
|
|
42117
42285
|
const batch = readBatch(batchPath);
|
|
42118
42286
|
const runnerProbe = validateRunner(options, runner);
|
|
42119
42287
|
const codexSandboxBackend = normalizeCodexSandboxBackend(options.codexSandboxBackend);
|
|
@@ -42132,17 +42300,17 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
42132
42300
|
`Workspace root must be outside the private repository. workspace=${workspaceRoot} repo=${privateRepoRoot}`
|
|
42133
42301
|
);
|
|
42134
42302
|
}
|
|
42135
|
-
(0,
|
|
42136
|
-
const batchDir = (0,
|
|
42303
|
+
(0, import_fs19.mkdirSync)(workspaceRoot, { recursive: true });
|
|
42304
|
+
const batchDir = (0, import_path18.resolve)(String(batch.batch_dir || (0, import_path18.resolve)(batchPath, "..")));
|
|
42137
42305
|
const timeoutMinutes = Number.isFinite(options.timeoutMinutes) && Number(options.timeoutMinutes) > 0 ? Number(options.timeoutMinutes) : 30;
|
|
42138
42306
|
const runs = batch.runs.map((run) => {
|
|
42139
42307
|
const runId = safeRunId(requireString(run.run_id, "runs[].run_id"));
|
|
42140
|
-
const runDir = (0,
|
|
42141
|
-
const promptPath = (0,
|
|
42142
|
-
const workspaceDir = (0,
|
|
42143
|
-
(0,
|
|
42144
|
-
(0,
|
|
42145
|
-
(0,
|
|
42308
|
+
const runDir = (0, import_path18.resolve)(requireString(run.run_dir, `runs[${runId}].run_dir`));
|
|
42309
|
+
const promptPath = (0, import_path18.resolve)(requireString(run.prompt_path, `runs[${runId}].prompt_path`));
|
|
42310
|
+
const workspaceDir = (0, import_path18.join)(workspaceRoot, runId);
|
|
42311
|
+
(0, import_fs19.mkdirSync)(workspaceDir, { recursive: true });
|
|
42312
|
+
(0, import_fs19.writeFileSync)(
|
|
42313
|
+
(0, import_path18.join)(workspaceDir, "README.md"),
|
|
42146
42314
|
[
|
|
42147
42315
|
"# FOH External-Agent Workspace",
|
|
42148
42316
|
"",
|
|
@@ -42160,11 +42328,11 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
42160
42328
|
});
|
|
42161
42329
|
const promptVersion = String(env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] || "unknown");
|
|
42162
42330
|
const outputStem = runner === "gemini" ? "gemini" : "codex";
|
|
42163
|
-
const jsonlPath = (0,
|
|
42164
|
-
const lastMessagePath = (0,
|
|
42165
|
-
const stderrPath = (0,
|
|
42166
|
-
const runPath = (0,
|
|
42167
|
-
const artifactSafetyPath = (0,
|
|
42331
|
+
const jsonlPath = (0, import_path18.join)(runDir, `${outputStem}-exec.jsonl`);
|
|
42332
|
+
const lastMessagePath = (0, import_path18.join)(runDir, `${outputStem}-last-message.md`);
|
|
42333
|
+
const stderrPath = (0, import_path18.join)(runDir, `${outputStem}-stderr.txt`);
|
|
42334
|
+
const runPath = (0, import_path18.join)(runDir, "run.json");
|
|
42335
|
+
const artifactSafetyPath = (0, import_path18.join)(runDir, "artifact-safety.json");
|
|
42168
42336
|
const args = runner === "gemini" ? [
|
|
42169
42337
|
...runnerProbe.globalArgs,
|
|
42170
42338
|
...runnerProbe.execArgs
|
|
@@ -42255,9 +42423,9 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
42255
42423
|
};
|
|
42256
42424
|
}
|
|
42257
42425
|
function writeExternalAgentExecutorPlan(plan) {
|
|
42258
|
-
const path2 = (0,
|
|
42259
|
-
(0,
|
|
42260
|
-
(0,
|
|
42426
|
+
const path2 = (0, import_path18.join)(plan.batch_dir, "executor-plan.json");
|
|
42427
|
+
(0, import_fs19.mkdirSync)(plan.batch_dir, { recursive: true });
|
|
42428
|
+
(0, import_fs19.writeFileSync)(path2, `${JSON.stringify(plan, null, 2)}
|
|
42261
42429
|
`, "utf8");
|
|
42262
42430
|
return path2;
|
|
42263
42431
|
}
|
|
@@ -42272,7 +42440,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
42272
42440
|
if (authPreflight && !authPreflight.ok) {
|
|
42273
42441
|
const endedAt2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
42274
42442
|
const blockedResults = plan.runs.map((run) => {
|
|
42275
|
-
(0,
|
|
42443
|
+
(0, import_fs19.mkdirSync)(run.run_dir, { recursive: true });
|
|
42276
42444
|
const runArtifact = buildExecutedExternalAgentRunArtifact({
|
|
42277
42445
|
run,
|
|
42278
42446
|
startedAt,
|
|
@@ -42283,7 +42451,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
42283
42451
|
timedOut: false,
|
|
42284
42452
|
durationMs: 0
|
|
42285
42453
|
});
|
|
42286
|
-
(0,
|
|
42454
|
+
(0, import_fs19.writeFileSync)(run.outputs.run, `${JSON.stringify(runArtifact, null, 2)}
|
|
42287
42455
|
`, "utf8");
|
|
42288
42456
|
return {
|
|
42289
42457
|
run_id: run.run_id,
|
|
@@ -42310,8 +42478,8 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
42310
42478
|
}
|
|
42311
42479
|
for (const run of plan.runs) {
|
|
42312
42480
|
const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
42313
|
-
const commandCaptureDir = (0,
|
|
42314
|
-
(0,
|
|
42481
|
+
const commandCaptureDir = (0, import_path18.join)(run.workspace_dir, ".foh-capture");
|
|
42482
|
+
(0, import_fs19.mkdirSync)(commandCaptureDir, { recursive: true });
|
|
42315
42483
|
const env = buildCodexExecutorEnv({
|
|
42316
42484
|
sourceEnv: options.env,
|
|
42317
42485
|
runDir: commandCaptureDir,
|
|
@@ -42322,7 +42490,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
42322
42490
|
args: run.args,
|
|
42323
42491
|
cwd: run.workspace_dir,
|
|
42324
42492
|
env,
|
|
42325
|
-
prompt: (0,
|
|
42493
|
+
prompt: (0, import_fs19.readFileSync)(run.prompt_path, "utf8"),
|
|
42326
42494
|
stdoutPath: run.outputs.jsonl,
|
|
42327
42495
|
stderrPath: run.outputs.stderr,
|
|
42328
42496
|
timeoutMs: plan.timeout_minutes * 60 * 1e3
|
|
@@ -42335,7 +42503,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
42335
42503
|
privateRepoRoot,
|
|
42336
42504
|
writeRedacted: true
|
|
42337
42505
|
});
|
|
42338
|
-
(0,
|
|
42506
|
+
(0, import_fs19.writeFileSync)(run.outputs.artifact_safety, `${JSON.stringify(artifactSafety, null, 2)}
|
|
42339
42507
|
`, "utf8");
|
|
42340
42508
|
const runEndedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
42341
42509
|
const classification = classifyExternalAgentRun({
|
|
@@ -42354,7 +42522,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
42354
42522
|
timedOut: spawned.timedOut,
|
|
42355
42523
|
durationMs: spawned.durationMs
|
|
42356
42524
|
});
|
|
42357
|
-
(0,
|
|
42525
|
+
(0, import_fs19.writeFileSync)(run.outputs.run, `${JSON.stringify(runArtifact, null, 2)}
|
|
42358
42526
|
`, "utf8");
|
|
42359
42527
|
results.push({
|
|
42360
42528
|
run_id: run.run_id,
|
|
@@ -42384,6 +42552,540 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
42384
42552
|
};
|
|
42385
42553
|
}
|
|
42386
42554
|
|
|
42555
|
+
// src/lib/external-agent-run-summary.ts
|
|
42556
|
+
var import_fs20 = require("fs");
|
|
42557
|
+
var import_path19 = require("path");
|
|
42558
|
+
var REQUIRED_RUN_FIELDS = [
|
|
42559
|
+
"schema_version",
|
|
42560
|
+
"run_id",
|
|
42561
|
+
"status",
|
|
42562
|
+
"model_provider",
|
|
42563
|
+
"model_name",
|
|
42564
|
+
"prompt_version",
|
|
42565
|
+
"started_at",
|
|
42566
|
+
"manual_intervention_count",
|
|
42567
|
+
"environment",
|
|
42568
|
+
"public_entrypoints",
|
|
42569
|
+
"commands_run",
|
|
42570
|
+
"docs_pages_used",
|
|
42571
|
+
"artifacts"
|
|
42572
|
+
];
|
|
42573
|
+
var VALID_STATUSES = /* @__PURE__ */ new Set(["pass", "hold", "fail"]);
|
|
42574
|
+
var DOC_URL_RE = /https:\/\/frontofhouse\.okii\.uk\/[^\s"'`)<>,;\\\]}]*/g;
|
|
42575
|
+
function quoteShellArg(value) {
|
|
42576
|
+
const text = String(value);
|
|
42577
|
+
if (/^[A-Za-z0-9_./:=@-]+$/.test(text)) return text;
|
|
42578
|
+
return `"${text.replace(/(["$`])/g, "\\$1")}"`;
|
|
42579
|
+
}
|
|
42580
|
+
function externalAgentSummaryCommand(root) {
|
|
42581
|
+
const summaryPath = (0, import_path19.join)(root, "latest-summary.json");
|
|
42582
|
+
const reportPath = (0, import_path19.join)(root, "summary.report.json");
|
|
42583
|
+
return [
|
|
42584
|
+
"foh",
|
|
42585
|
+
"eval",
|
|
42586
|
+
"external-agent",
|
|
42587
|
+
"summary",
|
|
42588
|
+
"--root",
|
|
42589
|
+
quoteShellArg(root),
|
|
42590
|
+
"--out",
|
|
42591
|
+
quoteShellArg(summaryPath),
|
|
42592
|
+
"--report",
|
|
42593
|
+
quoteShellArg(reportPath),
|
|
42594
|
+
"--json"
|
|
42595
|
+
].join(" ");
|
|
42596
|
+
}
|
|
42597
|
+
function readJson(filePath) {
|
|
42598
|
+
return JSON.parse((0, import_fs20.readFileSync)(filePath, "utf8").replace(/^\uFEFF/, ""));
|
|
42599
|
+
}
|
|
42600
|
+
function readNdjson(filePath) {
|
|
42601
|
+
if (!(0, import_fs20.existsSync)(filePath)) return [];
|
|
42602
|
+
return (0, import_fs20.readFileSync)(filePath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
42603
|
+
try {
|
|
42604
|
+
const parsed = JSON.parse(line);
|
|
42605
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
42606
|
+
} catch {
|
|
42607
|
+
return null;
|
|
42608
|
+
}
|
|
42609
|
+
}).filter((record2) => Boolean(record2));
|
|
42610
|
+
}
|
|
42611
|
+
function asObject(value) {
|
|
42612
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
42613
|
+
}
|
|
42614
|
+
function toArray2(value) {
|
|
42615
|
+
return Array.isArray(value) ? value : [];
|
|
42616
|
+
}
|
|
42617
|
+
function increment(map3, key, amount = 1) {
|
|
42618
|
+
const normalized = String(key || "unknown");
|
|
42619
|
+
map3.set(normalized, (map3.get(normalized) || 0) + amount);
|
|
42620
|
+
}
|
|
42621
|
+
function ranked(map3) {
|
|
42622
|
+
return Array.from(map3.entries()).map(([key, count]) => ({ key, count })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
|
|
42623
|
+
}
|
|
42624
|
+
function collectDocUrls(text) {
|
|
42625
|
+
return Array.from(new Set((String(text || "").match(DOC_URL_RE) || []).map((url2) => url2.replace(/[.?!:]+$/g, "")).filter((url2) => url2.startsWith("https://frontofhouse.okii.uk/")))).sort();
|
|
42626
|
+
}
|
|
42627
|
+
function findRunCandidates(root) {
|
|
42628
|
+
if (!(0, import_fs20.existsSync)(root)) return [];
|
|
42629
|
+
const candidates = [];
|
|
42630
|
+
const seenRunDirs = /* @__PURE__ */ new Set();
|
|
42631
|
+
const captureDirs = [];
|
|
42632
|
+
const stack = [root];
|
|
42633
|
+
while (stack.length > 0) {
|
|
42634
|
+
const current = stack.pop();
|
|
42635
|
+
if (!current) continue;
|
|
42636
|
+
for (const entry of (0, import_fs20.readdirSync)(current, { withFileTypes: true })) {
|
|
42637
|
+
const absolute = (0, import_path19.join)(current, entry.name);
|
|
42638
|
+
if (entry.isDirectory()) {
|
|
42639
|
+
stack.push(absolute);
|
|
42640
|
+
} else if (entry.isFile() && entry.name === "run.json") {
|
|
42641
|
+
candidates.push({ path: absolute, synthetic: false });
|
|
42642
|
+
seenRunDirs.add((0, import_path19.dirname)(absolute));
|
|
42643
|
+
} else if (entry.isFile() && entry.name === "commands.ndjson") {
|
|
42644
|
+
captureDirs.push(current);
|
|
42645
|
+
}
|
|
42646
|
+
}
|
|
42647
|
+
}
|
|
42648
|
+
for (const captureDir of captureDirs) {
|
|
42649
|
+
if (seenRunDirs.has(captureDir)) continue;
|
|
42650
|
+
candidates.push({ path: (0, import_path19.join)(captureDir, "run.json"), synthetic: true });
|
|
42651
|
+
}
|
|
42652
|
+
return candidates.sort((a, b) => a.path.localeCompare(b.path));
|
|
42653
|
+
}
|
|
42654
|
+
function validateExternalAgentRun(value) {
|
|
42655
|
+
const findings = [];
|
|
42656
|
+
const run = asObject(value);
|
|
42657
|
+
if (!run) return [{ id: "run_not_object", detail: "run artifact must be an object" }];
|
|
42658
|
+
for (const field of REQUIRED_RUN_FIELDS) {
|
|
42659
|
+
if (!(field in run)) findings.push({ id: "required_field_missing", field });
|
|
42660
|
+
}
|
|
42661
|
+
if (run.schema_version !== "external_agent_run.v1") {
|
|
42662
|
+
findings.push({ id: "schema_version_invalid", expected: "external_agent_run.v1", actual: run.schema_version ?? null });
|
|
42663
|
+
}
|
|
42664
|
+
if (!VALID_STATUSES.has(String(run.status || ""))) {
|
|
42665
|
+
findings.push({ id: "status_invalid", expected: Array.from(VALID_STATUSES), actual: run.status ?? null });
|
|
42666
|
+
}
|
|
42667
|
+
if ((run.status === "hold" || run.status === "fail") && !String(run.failure_reason_code || "").trim()) {
|
|
42668
|
+
findings.push({ id: "failure_reason_code_missing" });
|
|
42669
|
+
}
|
|
42670
|
+
if (!Number.isInteger(run.manual_intervention_count) || Number(run.manual_intervention_count) < 0) {
|
|
42671
|
+
findings.push({ id: "manual_intervention_count_invalid" });
|
|
42672
|
+
}
|
|
42673
|
+
if (!Array.isArray(run.commands_run)) findings.push({ id: "commands_run_invalid" });
|
|
42674
|
+
if (!Array.isArray(run.docs_pages_used)) findings.push({ id: "docs_pages_used_invalid" });
|
|
42675
|
+
if (!asObject(run.environment)) findings.push({ id: "environment_invalid" });
|
|
42676
|
+
if (!asObject(run.artifacts)) findings.push({ id: "artifacts_invalid" });
|
|
42677
|
+
if (toArray2(run.public_entrypoints).length === 0) findings.push({ id: "public_entrypoints_missing" });
|
|
42678
|
+
return findings;
|
|
42679
|
+
}
|
|
42680
|
+
function runSortTime(run) {
|
|
42681
|
+
const raw = String(run.ended_at || run.started_at || "");
|
|
42682
|
+
const time3 = Date.parse(raw);
|
|
42683
|
+
return Number.isFinite(time3) ? time3 : 0;
|
|
42684
|
+
}
|
|
42685
|
+
function latestCommandTime(commands) {
|
|
42686
|
+
const times = commands.map((command) => String(command.completed_at || command.started_at || command.recorded_at || "")).map((raw) => ({ raw, time: Date.parse(raw) })).filter((entry) => Number.isFinite(entry.time)).sort((a, b) => b.time - a.time);
|
|
42687
|
+
return times[0]?.raw ?? null;
|
|
42688
|
+
}
|
|
42689
|
+
function firstCommandTime(commands) {
|
|
42690
|
+
const times = commands.map((command) => String(command.started_at || command.recorded_at || command.completed_at || "")).map((raw) => ({ raw, time: Date.parse(raw) })).filter((entry) => Number.isFinite(entry.time)).sort((a, b) => a.time - b.time);
|
|
42691
|
+
return times[0]?.raw ?? null;
|
|
42692
|
+
}
|
|
42693
|
+
function commandReasonCodes(commands) {
|
|
42694
|
+
const codes = /* @__PURE__ */ new Set();
|
|
42695
|
+
for (const command of commands) {
|
|
42696
|
+
if (command.reason_code) codes.add(String(command.reason_code));
|
|
42697
|
+
for (const reasonCode of toArray2(command.check_reason_codes)) {
|
|
42698
|
+
if (reasonCode) codes.add(String(reasonCode));
|
|
42699
|
+
}
|
|
42700
|
+
}
|
|
42701
|
+
return Array.from(codes);
|
|
42702
|
+
}
|
|
42703
|
+
function syntheticStatusFromCommands(commands) {
|
|
42704
|
+
const commandReasons = commandReasonCodes(commands);
|
|
42705
|
+
const failed = commands.find((command) => {
|
|
42706
|
+
const status = String(command.status || "").toLowerCase();
|
|
42707
|
+
return status === "fail" || typeof command.exit_code === "number" && command.exit_code !== 0 && status !== "hold";
|
|
42708
|
+
});
|
|
42709
|
+
if (failed) {
|
|
42710
|
+
return {
|
|
42711
|
+
status: "fail",
|
|
42712
|
+
reasonCode: String(failed.reason_code || commandReasons[0] || "external_agent_command_failed")
|
|
42713
|
+
};
|
|
42714
|
+
}
|
|
42715
|
+
const held = commands.find((command) => String(command.status || "").toLowerCase() === "hold");
|
|
42716
|
+
if (held) {
|
|
42717
|
+
return {
|
|
42718
|
+
status: "hold",
|
|
42719
|
+
reasonCode: String(held.reason_code || commandReasons[0] || "external_agent_command_held")
|
|
42720
|
+
};
|
|
42721
|
+
}
|
|
42722
|
+
if (commands.length === 0) {
|
|
42723
|
+
return { status: "hold", reasonCode: "external_agent_capture_empty" };
|
|
42724
|
+
}
|
|
42725
|
+
return { status: "pass", reasonCode: null };
|
|
42726
|
+
}
|
|
42727
|
+
function synthesizeRunFromCapture(runPath) {
|
|
42728
|
+
const runDir = (0, import_path19.dirname)(runPath);
|
|
42729
|
+
const commands = collapseCommandRecords(readNdjson((0, import_path19.join)(runDir, "commands.ndjson")));
|
|
42730
|
+
const metadata = asObject((0, import_fs20.existsSync)((0, import_path19.join)(runDir, "external-agent-metadata.json")) ? readJson((0, import_path19.join)(runDir, "external-agent-metadata.json")) : {});
|
|
42731
|
+
const blockerCodes = toArray2(metadata?.blocker_reason_codes).map(String).filter(Boolean);
|
|
42732
|
+
const commandClassification = syntheticStatusFromCommands(commands);
|
|
42733
|
+
const status = commandClassification.status === "fail" ? "fail" : blockerCodes.length > 0 ? "hold" : commandClassification.status;
|
|
42734
|
+
const reasonCode = commandClassification.status === "fail" ? commandClassification.reasonCode : blockerCodes[0] || commandClassification.reasonCode;
|
|
42735
|
+
const firstCommand = commands[0] || {};
|
|
42736
|
+
const startedAt = firstCommandTime(commands) || (/* @__PURE__ */ new Date(0)).toISOString();
|
|
42737
|
+
const endedAt = latestCommandTime(commands) || startedAt;
|
|
42738
|
+
const docs = toArray2(metadata?.docs_pages_used).map(String).filter(Boolean);
|
|
42739
|
+
const runId = (0, import_path19.dirname)(runPath).split(/[\\/]/).filter(Boolean).slice(-3).join("-") || "capture-only-run";
|
|
42740
|
+
return {
|
|
42741
|
+
schema_version: "external_agent_run.v1",
|
|
42742
|
+
run_id: runId,
|
|
42743
|
+
status,
|
|
42744
|
+
failure_reason_code: status === "pass" ? null : reasonCode || "external_agent_capture_unfinalized",
|
|
42745
|
+
model_provider: "unknown",
|
|
42746
|
+
model_name: "unknown",
|
|
42747
|
+
prompt_version: String(firstCommand.prompt_version || "unknown"),
|
|
42748
|
+
started_at: startedAt,
|
|
42749
|
+
ended_at: endedAt,
|
|
42750
|
+
manual_intervention_count: 0,
|
|
42751
|
+
environment: {
|
|
42752
|
+
foh_cli_version: firstCommand.cli_version || null,
|
|
42753
|
+
capture_only: true
|
|
42754
|
+
},
|
|
42755
|
+
public_entrypoints: [
|
|
42756
|
+
"https://frontofhouse.okii.uk",
|
|
42757
|
+
"npx --yes @f-o-h/cli@latest"
|
|
42758
|
+
],
|
|
42759
|
+
commands_run: commands.map((command) => String(command.command || "")).filter(Boolean),
|
|
42760
|
+
docs_pages_used: docs,
|
|
42761
|
+
artifacts: {
|
|
42762
|
+
command_log: "commands.ndjson",
|
|
42763
|
+
agent_metadata: (0, import_fs20.existsSync)((0, import_path19.join)(runDir, "external-agent-metadata.json")) ? "external-agent-metadata.json" : null,
|
|
42764
|
+
capture_only: true
|
|
42765
|
+
},
|
|
42766
|
+
summary: "Synthetic run record created from external-agent capture artifacts because run.json was missing."
|
|
42767
|
+
};
|
|
42768
|
+
}
|
|
42769
|
+
function cohortIdForRunPath(root, runPath) {
|
|
42770
|
+
const normalized = (0, import_path19.relative)(root, (0, import_path19.dirname)(runPath)).replaceAll("\\", "/");
|
|
42771
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
42772
|
+
if (parts.length === 0) return ".";
|
|
42773
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(parts[0]) && parts[1]) return `${parts[0]}/${parts[1]}`;
|
|
42774
|
+
return parts[0];
|
|
42775
|
+
}
|
|
42776
|
+
function readRunRecords(root, cwd) {
|
|
42777
|
+
const records = [];
|
|
42778
|
+
const invalid_runs = [];
|
|
42779
|
+
for (const candidate of findRunCandidates(root)) {
|
|
42780
|
+
const file2 = candidate.path;
|
|
42781
|
+
try {
|
|
42782
|
+
const parsed = candidate.synthetic ? synthesizeRunFromCapture(file2) : readJson(file2);
|
|
42783
|
+
const findings = validateExternalAgentRun(parsed);
|
|
42784
|
+
if (findings.length > 0) {
|
|
42785
|
+
invalid_runs.push({ path: (0, import_path19.relative)(cwd, file2).replaceAll("\\", "/"), findings });
|
|
42786
|
+
continue;
|
|
42787
|
+
}
|
|
42788
|
+
const run = parsed;
|
|
42789
|
+
records.push({
|
|
42790
|
+
path: file2,
|
|
42791
|
+
run,
|
|
42792
|
+
cohort_id: cohortIdForRunPath(root, file2),
|
|
42793
|
+
sort_time: runSortTime(run)
|
|
42794
|
+
});
|
|
42795
|
+
} catch (error2) {
|
|
42796
|
+
invalid_runs.push({
|
|
42797
|
+
path: (0, import_path19.relative)(cwd, file2).replaceAll("\\", "/"),
|
|
42798
|
+
findings: [{ id: "json_parse_failed", detail: error2 instanceof Error ? error2.message : String(error2) }]
|
|
42799
|
+
});
|
|
42800
|
+
}
|
|
42801
|
+
}
|
|
42802
|
+
return { records, invalid_runs };
|
|
42803
|
+
}
|
|
42804
|
+
function latestCohortId(records) {
|
|
42805
|
+
return records.slice().sort((a, b) => b.sort_time - a.sort_time || b.path.localeCompare(a.path))[0]?.cohort_id ?? null;
|
|
42806
|
+
}
|
|
42807
|
+
function ownerSubsystemFor(reasonCode) {
|
|
42808
|
+
const reason = String(reasonCode || "").toLowerCase();
|
|
42809
|
+
if (reason.includes("simulation") || reason.includes("certification") || reason.includes("scenario")) return "dojo_certification";
|
|
42810
|
+
if (reason.includes("contact_phone") || reason.includes("voice_contact") || reason.includes("provider_capacity") || reason.includes("byon")) return "voice_contact";
|
|
42811
|
+
if (reason.includes("exec_policy") || reason.includes("policy_blocked") || reason.includes("sandbox") || reason.includes("runner") || reason.includes("codex")) return "infra_runner";
|
|
42812
|
+
if (reason.includes("api") || reason.includes("http_4") || reason.includes("http_5") || reason.includes("404") || reason.includes("500") || reason.includes("roundtrip")) return "api_contract";
|
|
42813
|
+
if (reason.includes("cli") || reason.includes("command") || reason.includes("flag")) return "cli";
|
|
42814
|
+
if (reason.includes("docs") || reason.includes("unclear") || reason.includes("not_found")) return "docs";
|
|
42815
|
+
if (reason.includes("auth") || reason.includes("org") || reason.includes("config")) return "infra_runner";
|
|
42816
|
+
if (reason.includes("runtime") || reason.includes("widget") || reason.includes("proof")) return "runtime";
|
|
42817
|
+
return "product_ux";
|
|
42818
|
+
}
|
|
42819
|
+
function recommendedFixFor(reasonCode) {
|
|
42820
|
+
const owner = ownerSubsystemFor(reasonCode);
|
|
42821
|
+
if (owner === "api_contract") return "fix_api";
|
|
42822
|
+
if (owner === "cli") return "fix_cli";
|
|
42823
|
+
if (owner === "docs") return "fix_docs";
|
|
42824
|
+
if (owner === "runtime") return "fix_runtime";
|
|
42825
|
+
if (owner === "dojo_certification") return "add_test";
|
|
42826
|
+
return "fix_config";
|
|
42827
|
+
}
|
|
42828
|
+
function collapseCommandRecords(records) {
|
|
42829
|
+
const order = [];
|
|
42830
|
+
const byId = /* @__PURE__ */ new Map();
|
|
42831
|
+
for (const record2 of records) {
|
|
42832
|
+
const id = String(record2.command_id || `${record2.recorded_at || ""}:${record2.command || ""}`);
|
|
42833
|
+
if (!byId.has(id)) order.push(id);
|
|
42834
|
+
const previous = byId.get(id);
|
|
42835
|
+
byId.set(id, record2.phase === "completed" ? record2 : previous || record2);
|
|
42836
|
+
}
|
|
42837
|
+
return order.map((id) => byId.get(id)).filter((record2) => Boolean(record2));
|
|
42838
|
+
}
|
|
42839
|
+
function readCommandOutputJson(runDir, command) {
|
|
42840
|
+
const artifact = typeof command.output_artifact === "string" && command.output_artifact.trim() ? command.output_artifact.trim() : "";
|
|
42841
|
+
if (!artifact || artifact.includes("..") || artifact.includes("/") || artifact.includes("\\")) return null;
|
|
42842
|
+
const artifactPath = (0, import_path19.join)(runDir, artifact);
|
|
42843
|
+
if (!(0, import_fs20.existsSync)(artifactPath)) return null;
|
|
42844
|
+
try {
|
|
42845
|
+
const text = (0, import_fs20.readFileSync)(artifactPath, "utf8");
|
|
42846
|
+
const firstObject = text.indexOf("{");
|
|
42847
|
+
const lastObject = text.lastIndexOf("}");
|
|
42848
|
+
if (firstObject < 0 || lastObject <= firstObject) return null;
|
|
42849
|
+
return JSON.parse(text.slice(firstObject, lastObject + 1));
|
|
42850
|
+
} catch {
|
|
42851
|
+
return null;
|
|
42852
|
+
}
|
|
42853
|
+
}
|
|
42854
|
+
function commandTimingBreakdown(command, output) {
|
|
42855
|
+
const schemaVersion = String(output?.schema_version || "");
|
|
42856
|
+
if (schemaVersion === "foh_cli_proof_report.v1") {
|
|
42857
|
+
const timing = asObject(output?.timing) || {};
|
|
42858
|
+
const cacheSources = /* @__PURE__ */ new Map();
|
|
42859
|
+
let cacheWaitMs = 0;
|
|
42860
|
+
let cacheHitCount = 0;
|
|
42861
|
+
let cacheMissCount = 0;
|
|
42862
|
+
for (const check2 of toArray2(output?.checks)) {
|
|
42863
|
+
const detail = asObject(check2)?.detail;
|
|
42864
|
+
const proofCache = asObject(asObject(detail)?.proof_cache);
|
|
42865
|
+
if (!proofCache) continue;
|
|
42866
|
+
if (proofCache["hit"] === true) cacheHitCount += 1;
|
|
42867
|
+
if (proofCache["miss"] === true) cacheMissCount += 1;
|
|
42868
|
+
cacheWaitMs += Number(proofCache["waited_ms"] || 0);
|
|
42869
|
+
increment(cacheSources, proofCache["source"] || "unknown");
|
|
42870
|
+
}
|
|
42871
|
+
return {
|
|
42872
|
+
kind: "proof",
|
|
42873
|
+
command: command.command || "",
|
|
42874
|
+
total_ms: Number(timing.total_ms || command.duration_ms || 0),
|
|
42875
|
+
total_check_duration_ms: Number(timing.total_check_duration_ms || 0),
|
|
42876
|
+
slow_checks: toArray2(timing.slow_checks).slice(0, 5),
|
|
42877
|
+
cache_wait_ms: cacheWaitMs,
|
|
42878
|
+
cache_hit_count: cacheHitCount,
|
|
42879
|
+
cache_miss_count: cacheMissCount,
|
|
42880
|
+
cache_sources: ranked(cacheSources)
|
|
42881
|
+
};
|
|
42882
|
+
}
|
|
42883
|
+
if (schemaVersion === "foh_certification_run.v1") {
|
|
42884
|
+
const timing = asObject(output?.timing) || {};
|
|
42885
|
+
const certificate = asObject(output?.certificate) || {};
|
|
42886
|
+
return {
|
|
42887
|
+
kind: "certification",
|
|
42888
|
+
command: command.command || "",
|
|
42889
|
+
total_ms: Number(timing.total_ms || command.duration_ms || 0),
|
|
42890
|
+
api_ms: Number(timing.api_ms || 0),
|
|
42891
|
+
status: output?.status || null,
|
|
42892
|
+
reason_code: output?.reason_code || null,
|
|
42893
|
+
scenario_summary: certificate.scenario_summary || null
|
|
42894
|
+
};
|
|
42895
|
+
}
|
|
42896
|
+
return null;
|
|
42897
|
+
}
|
|
42898
|
+
function analyzeRunArtifacts(runPath, run, cwd) {
|
|
42899
|
+
const runDir = (0, import_path19.dirname)(runPath);
|
|
42900
|
+
const commands = collapseCommandRecords(readNdjson((0, import_path19.join)(runDir, "commands.ndjson")));
|
|
42901
|
+
const reasonCounts = /* @__PURE__ */ new Map();
|
|
42902
|
+
const slowSteps = [];
|
|
42903
|
+
const timingBreakdowns = [];
|
|
42904
|
+
let completed = 0;
|
|
42905
|
+
let withDuration = 0;
|
|
42906
|
+
let totalDuration = 0;
|
|
42907
|
+
for (const command of commands) {
|
|
42908
|
+
const output = readCommandOutputJson(runDir, command);
|
|
42909
|
+
const breakdown = commandTimingBreakdown(command, output);
|
|
42910
|
+
if (breakdown) timingBreakdowns.push({
|
|
42911
|
+
run_id: run.run_id,
|
|
42912
|
+
run_path: (0, import_path19.relative)(cwd, runPath).replaceAll("\\", "/"),
|
|
42913
|
+
...breakdown
|
|
42914
|
+
});
|
|
42915
|
+
if (command.phase === "completed" || command.completed_at) completed += 1;
|
|
42916
|
+
if (typeof command.duration_ms === "number") {
|
|
42917
|
+
withDuration += 1;
|
|
42918
|
+
totalDuration += command.duration_ms;
|
|
42919
|
+
slowSteps.push({
|
|
42920
|
+
run_id: run.run_id,
|
|
42921
|
+
run_path: (0, import_path19.relative)(cwd, runPath).replaceAll("\\", "/"),
|
|
42922
|
+
command: command.command || "",
|
|
42923
|
+
duration_ms: command.duration_ms,
|
|
42924
|
+
status: command.status || null,
|
|
42925
|
+
reason_code: command.reason_code || null,
|
|
42926
|
+
check_reason_codes: Array.isArray(command.check_reason_codes) ? command.check_reason_codes : []
|
|
42927
|
+
});
|
|
42928
|
+
}
|
|
42929
|
+
if (command.reason_code) increment(reasonCounts, command.reason_code);
|
|
42930
|
+
for (const reasonCode of toArray2(command.check_reason_codes)) {
|
|
42931
|
+
if (reasonCode) increment(reasonCounts, reasonCode);
|
|
42932
|
+
}
|
|
42933
|
+
}
|
|
42934
|
+
const codexEvents = readNdjson((0, import_path19.join)(runDir, "codex-exec.jsonl"));
|
|
42935
|
+
const codexDocs = /* @__PURE__ */ new Set();
|
|
42936
|
+
let codexCommandExecutions = 0;
|
|
42937
|
+
let codexFailedExitCodes = 0;
|
|
42938
|
+
for (const event of codexEvents) {
|
|
42939
|
+
const item = asObject(event.item) || event;
|
|
42940
|
+
if (item.type === "command_execution" && item.status === "completed") {
|
|
42941
|
+
codexCommandExecutions += 1;
|
|
42942
|
+
if (typeof item.exit_code === "number" && item.exit_code !== 0) codexFailedExitCodes += 1;
|
|
42943
|
+
}
|
|
42944
|
+
for (const url2 of collectDocUrls(JSON.stringify(event))) codexDocs.add(url2);
|
|
42945
|
+
}
|
|
42946
|
+
const docs = /* @__PURE__ */ new Set([
|
|
42947
|
+
...toArray2(run.docs_pages_used).map(String),
|
|
42948
|
+
...Array.from(codexDocs)
|
|
42949
|
+
]);
|
|
42950
|
+
return {
|
|
42951
|
+
command_log_present: (0, import_fs20.existsSync)((0, import_path19.join)(runDir, "commands.ndjson")),
|
|
42952
|
+
command_count: commands.length,
|
|
42953
|
+
completed_command_count: completed,
|
|
42954
|
+
missing_completion_count: Math.max(0, commands.length - completed),
|
|
42955
|
+
commands_with_duration_count: withDuration,
|
|
42956
|
+
total_command_duration_ms: totalDuration,
|
|
42957
|
+
command_reason_codes: ranked(reasonCounts),
|
|
42958
|
+
slow_steps: slowSteps.sort((a, b) => Number(b.duration_ms) - Number(a.duration_ms)).slice(0, 10),
|
|
42959
|
+
timing_breakdowns: timingBreakdowns,
|
|
42960
|
+
docs_pages_observed: Array.from(docs).sort(),
|
|
42961
|
+
codex_command_execution_completed_count: codexCommandExecutions,
|
|
42962
|
+
codex_failed_exit_code_count: codexFailedExitCodes
|
|
42963
|
+
};
|
|
42964
|
+
}
|
|
42965
|
+
function summarizeExternalAgentRuns(options) {
|
|
42966
|
+
const cwd = (0, import_path19.resolve)(options.cwd || process.cwd());
|
|
42967
|
+
const root = (0, import_path19.resolve)(cwd, options.root);
|
|
42968
|
+
const loaded = readRunRecords(root, cwd);
|
|
42969
|
+
const selectedCohortId = options.cohortId || (options.currentBaselineOnly ? latestCohortId(loaded.records) : null);
|
|
42970
|
+
const records = selectedCohortId ? loaded.records.filter((record2) => record2.cohort_id === selectedCohortId) : loaded.records;
|
|
42971
|
+
const statusCounts = /* @__PURE__ */ new Map();
|
|
42972
|
+
const modelCounts = /* @__PURE__ */ new Map();
|
|
42973
|
+
const failureCounts = /* @__PURE__ */ new Map();
|
|
42974
|
+
const commandReasonCounts = /* @__PURE__ */ new Map();
|
|
42975
|
+
const docsCounts = /* @__PURE__ */ new Map();
|
|
42976
|
+
const slowSteps = [];
|
|
42977
|
+
const timingBreakdowns = [];
|
|
42978
|
+
let manualInterventions = 0;
|
|
42979
|
+
let commandCount = 0;
|
|
42980
|
+
let completedCommandCount = 0;
|
|
42981
|
+
let missingCompletionCount = 0;
|
|
42982
|
+
let commandsWithDurationCount = 0;
|
|
42983
|
+
let totalCommandDurationMs = 0;
|
|
42984
|
+
let commandLogRunCount = 0;
|
|
42985
|
+
let codexCommandExecutions = 0;
|
|
42986
|
+
let codexFailedExitCodes = 0;
|
|
42987
|
+
for (const record2 of records) {
|
|
42988
|
+
const run = record2.run;
|
|
42989
|
+
increment(statusCounts, run.status);
|
|
42990
|
+
increment(modelCounts, `${run.model_provider}/${run.model_name}`);
|
|
42991
|
+
manualInterventions += Number(run.manual_intervention_count || 0);
|
|
42992
|
+
if (run.status !== "pass") increment(failureCounts, run.failure_reason_code || "unknown");
|
|
42993
|
+
const artifactSummary = analyzeRunArtifacts(record2.path, run, cwd);
|
|
42994
|
+
if (artifactSummary.command_log_present) commandLogRunCount += 1;
|
|
42995
|
+
commandCount += Number(artifactSummary.command_count || 0);
|
|
42996
|
+
completedCommandCount += Number(artifactSummary.completed_command_count || 0);
|
|
42997
|
+
missingCompletionCount += Number(artifactSummary.missing_completion_count || 0);
|
|
42998
|
+
commandsWithDurationCount += Number(artifactSummary.commands_with_duration_count || 0);
|
|
42999
|
+
totalCommandDurationMs += Number(artifactSummary.total_command_duration_ms || 0);
|
|
43000
|
+
codexCommandExecutions += Number(artifactSummary.codex_command_execution_completed_count || 0);
|
|
43001
|
+
codexFailedExitCodes += Number(artifactSummary.codex_failed_exit_code_count || 0);
|
|
43002
|
+
for (const row of toArray2(artifactSummary.slow_steps)) slowSteps.push(row);
|
|
43003
|
+
for (const row of toArray2(artifactSummary.timing_breakdowns)) timingBreakdowns.push(row);
|
|
43004
|
+
for (const row of toArray2(artifactSummary.command_reason_codes)) {
|
|
43005
|
+
const entry = asObject(row);
|
|
43006
|
+
if (entry) increment(commandReasonCounts, entry.key, Number(entry.count || 1));
|
|
43007
|
+
}
|
|
43008
|
+
for (const page of toArray2(artifactSummary.docs_pages_observed)) increment(docsCounts, page);
|
|
43009
|
+
}
|
|
43010
|
+
const topFailures = ranked(failureCounts);
|
|
43011
|
+
const commandReasonCodes2 = ranked(commandReasonCounts);
|
|
43012
|
+
const recommendedFixes = topFailures.map((failure) => ({
|
|
43013
|
+
reason_code: failure.key,
|
|
43014
|
+
count: failure.count,
|
|
43015
|
+
recommended_fix: recommendedFixFor(failure.key),
|
|
43016
|
+
owner_subsystem: ownerSubsystemFor(failure.key)
|
|
43017
|
+
}));
|
|
43018
|
+
const nextRecommendedFix = recommendedFixes[0] || null;
|
|
43019
|
+
return {
|
|
43020
|
+
schema_version: "external_agent_run_summary.v1",
|
|
43021
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
43022
|
+
root: (0, import_path19.relative)(cwd, root).replaceAll("\\", "/") || ".",
|
|
43023
|
+
cohort_id: selectedCohortId,
|
|
43024
|
+
current_baseline_only: Boolean(selectedCohortId),
|
|
43025
|
+
run_count: records.length,
|
|
43026
|
+
invalid_run_count: selectedCohortId ? 0 : loaded.invalid_runs.length,
|
|
43027
|
+
status_counts: Object.fromEntries(statusCounts),
|
|
43028
|
+
model_counts: ranked(modelCounts),
|
|
43029
|
+
manual_intervention_count: manualInterventions,
|
|
43030
|
+
top_failure_reason_codes: topFailures,
|
|
43031
|
+
docs_pages_observed: ranked(docsCounts),
|
|
43032
|
+
command_telemetry: {
|
|
43033
|
+
run_count_with_command_log: commandLogRunCount,
|
|
43034
|
+
command_count: commandCount,
|
|
43035
|
+
completed_command_count: completedCommandCount,
|
|
43036
|
+
missing_completion_count: missingCompletionCount,
|
|
43037
|
+
commands_with_duration_count: commandsWithDurationCount,
|
|
43038
|
+
total_command_duration_ms: totalCommandDurationMs,
|
|
43039
|
+
command_reason_codes: commandReasonCodes2,
|
|
43040
|
+
slow_steps: slowSteps.sort((a, b) => Number(b.duration_ms || 0) - Number(a.duration_ms || 0) || String(a.command || "").localeCompare(String(b.command || ""))).slice(0, 20),
|
|
43041
|
+
timing_breakdowns: timingBreakdowns.sort((a, b) => Number(b.total_ms || 0) - Number(a.total_ms || 0) || String(a.command || "").localeCompare(String(b.command || ""))).slice(0, 20)
|
|
43042
|
+
},
|
|
43043
|
+
codex_telemetry: {
|
|
43044
|
+
command_execution_completed_count: codexCommandExecutions,
|
|
43045
|
+
failed_exit_code_count: codexFailedExitCodes
|
|
43046
|
+
},
|
|
43047
|
+
recommended_fixes: recommendedFixes,
|
|
43048
|
+
next_recommended_fix: nextRecommendedFix,
|
|
43049
|
+
fix_selection_policy: {
|
|
43050
|
+
mode: "coherent_failure_cluster_first",
|
|
43051
|
+
rule: "Fix the highest-impact owner subsystem locally with focused proof, then rerun the same prompt once externally.",
|
|
43052
|
+
run_failure_weight: 3,
|
|
43053
|
+
command_reason_weight: 1
|
|
43054
|
+
},
|
|
43055
|
+
next_commands: nextRecommendedFix ? [`foh bug improve --from external-agent-run --file <run_dir>/run.json --json`] : [],
|
|
43056
|
+
invalid_runs: selectedCohortId ? [] : loaded.invalid_runs,
|
|
43057
|
+
run_paths: records.map((record2) => (0, import_path19.relative)(cwd, record2.path).replaceAll("\\", "/")).sort()
|
|
43058
|
+
};
|
|
43059
|
+
}
|
|
43060
|
+
function runExternalAgentRunSummary(options) {
|
|
43061
|
+
const summary = summarizeExternalAgentRuns(options);
|
|
43062
|
+
const invalidRuns = toArray2(summary.invalid_runs);
|
|
43063
|
+
const status = invalidRuns.length > 0 ? "failed" : "passed";
|
|
43064
|
+
const report = {
|
|
43065
|
+
report_schema_version: "script_report.v1",
|
|
43066
|
+
script: "foh eval external-agent summary",
|
|
43067
|
+
checked_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
43068
|
+
status,
|
|
43069
|
+
errors: invalidRuns.map((entry) => {
|
|
43070
|
+
const object3 = asObject(entry);
|
|
43071
|
+
return `${object3?.path || "unknown"}: ${JSON.stringify(object3?.findings || [])}`;
|
|
43072
|
+
}),
|
|
43073
|
+
warnings: Number(summary.run_count || 0) === 0 ? ["no external-agent run artifacts found"] : [],
|
|
43074
|
+
report: summary
|
|
43075
|
+
};
|
|
43076
|
+
if (options.out) {
|
|
43077
|
+
(0, import_fs20.mkdirSync)((0, import_path19.dirname)((0, import_path19.resolve)(options.cwd || process.cwd(), options.out)), { recursive: true });
|
|
43078
|
+
(0, import_fs20.writeFileSync)((0, import_path19.resolve)(options.cwd || process.cwd(), options.out), `${JSON.stringify(summary, null, 2)}
|
|
43079
|
+
`, "utf8");
|
|
43080
|
+
}
|
|
43081
|
+
if (options.report) {
|
|
43082
|
+
(0, import_fs20.mkdirSync)((0, import_path19.dirname)((0, import_path19.resolve)(options.cwd || process.cwd(), options.report)), { recursive: true });
|
|
43083
|
+
(0, import_fs20.writeFileSync)((0, import_path19.resolve)(options.cwd || process.cwd(), options.report), `${JSON.stringify(report, null, 2)}
|
|
43084
|
+
`, "utf8");
|
|
43085
|
+
}
|
|
43086
|
+
return { summary, report };
|
|
43087
|
+
}
|
|
43088
|
+
|
|
42387
43089
|
// src/commands/eval.ts
|
|
42388
43090
|
var DEFAULT_PROMPT_VERSION = "blank-setup.v1";
|
|
42389
43091
|
var DEFAULT_BATCH_MODELS = "openai/codex,anthropic/claude,cursor/agent";
|
|
@@ -43066,6 +43768,10 @@ function installSoftExitTrap() {
|
|
|
43066
43768
|
// src/lib/mission-help.ts
|
|
43067
43769
|
var CLI_MISSION_EXAMPLES = [
|
|
43068
43770
|
{ mission: "Start", command: "foh start", description: "guided setup and next action selector" },
|
|
43771
|
+
{ mission: "Objective Plan", command: "foh objective plan --business-name <name> --source-url <url> --out test-results/objective-plan.latest.json --json", description: "generate setup and onboarding context" },
|
|
43772
|
+
{ mission: "Objective Apply", command: "foh objective apply --evidence <json|@file> --out test-results/objective-apply.latest.json --json", description: "submit verified evidence into customer-live gating path" },
|
|
43773
|
+
{ mission: "Objective Prove", command: "foh objective prove --business-name <name> --source-url <url> --out test-results/objective-live.latest.json --json", description: "run customer-live proof check directly" },
|
|
43774
|
+
{ mission: "Objective Status", command: "foh objective status --business-name <name> --source-url <url> --out test-results/objective-status.latest.json --json", description: "compose setup and live status into one report envelope" },
|
|
43069
43775
|
{ mission: "Setup", command: "foh setup --phone-mode observe --json", description: "create or update agent, widget, voice config, and proof scaffold" },
|
|
43070
43776
|
{ mission: "Prove", command: "foh prove --agent <agent_id> --mission widget --json", description: "produce a machine-readable proof report" },
|
|
43071
43777
|
{ mission: "Publish", command: "foh publish --agent <agent_id> --json", description: "publish when proof and release evidence pass" },
|
|
@@ -43170,6 +43876,7 @@ registerCertify(program2);
|
|
|
43170
43876
|
registerDiag(program2);
|
|
43171
43877
|
registerBug(program2);
|
|
43172
43878
|
registerProve(program2);
|
|
43879
|
+
registerObjective(program2);
|
|
43173
43880
|
registerInteractive(program2);
|
|
43174
43881
|
registerAgentPublishCommand(program2, { publicAlias: true });
|
|
43175
43882
|
registerEval(program2);
|