@f-o-h/cli 0.1.42 → 0.1.43

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 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 = resolve12.call(this, root, ref);
6049
+ let _sch = resolve13.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 resolve12(root, ref) {
6076
+ function resolve13(root, ref) {
6077
6077
  let sch;
6078
6078
  while (typeof (sch = this.refs[ref]) == "string")
6079
6079
  ref = sch;
@@ -6648,55 +6648,55 @@ var require_fast_uri = __commonJS({
6648
6648
  }
6649
6649
  return uri;
6650
6650
  }
6651
- function resolve12(baseURI, relativeURI, options) {
6651
+ function resolve13(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;
6655
6655
  return serialize(resolved, schemelessOptions);
6656
6656
  }
6657
- function resolveComponent(base, relative2, options, skipNormalization) {
6657
+ function resolveComponent(base, relative3, options, skipNormalization) {
6658
6658
  const target = {};
6659
6659
  if (!skipNormalization) {
6660
6660
  base = parse3(serialize(base, options), options);
6661
- relative2 = parse3(serialize(relative2, options), options);
6661
+ relative3 = parse3(serialize(relative3, options), options);
6662
6662
  }
6663
6663
  options = options || {};
6664
- if (!options.tolerant && relative2.scheme) {
6665
- target.scheme = relative2.scheme;
6666
- target.userinfo = relative2.userinfo;
6667
- target.host = relative2.host;
6668
- target.port = relative2.port;
6669
- target.path = removeDotSegments(relative2.path || "");
6670
- target.query = relative2.query;
6664
+ if (!options.tolerant && relative3.scheme) {
6665
+ target.scheme = relative3.scheme;
6666
+ target.userinfo = relative3.userinfo;
6667
+ target.host = relative3.host;
6668
+ target.port = relative3.port;
6669
+ target.path = removeDotSegments(relative3.path || "");
6670
+ target.query = relative3.query;
6671
6671
  } else {
6672
- if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
6673
- target.userinfo = relative2.userinfo;
6674
- target.host = relative2.host;
6675
- target.port = relative2.port;
6676
- target.path = removeDotSegments(relative2.path || "");
6677
- target.query = relative2.query;
6672
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
6673
+ target.userinfo = relative3.userinfo;
6674
+ target.host = relative3.host;
6675
+ target.port = relative3.port;
6676
+ target.path = removeDotSegments(relative3.path || "");
6677
+ target.query = relative3.query;
6678
6678
  } else {
6679
- if (!relative2.path) {
6679
+ if (!relative3.path) {
6680
6680
  target.path = base.path;
6681
- if (relative2.query !== void 0) {
6682
- target.query = relative2.query;
6681
+ if (relative3.query !== void 0) {
6682
+ target.query = relative3.query;
6683
6683
  } else {
6684
6684
  target.query = base.query;
6685
6685
  }
6686
6686
  } else {
6687
- if (relative2.path[0] === "/") {
6688
- target.path = removeDotSegments(relative2.path);
6687
+ if (relative3.path[0] === "/") {
6688
+ target.path = removeDotSegments(relative3.path);
6689
6689
  } else {
6690
6690
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
6691
- target.path = "/" + relative2.path;
6691
+ target.path = "/" + relative3.path;
6692
6692
  } else if (!base.path) {
6693
- target.path = relative2.path;
6693
+ target.path = relative3.path;
6694
6694
  } else {
6695
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
6695
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
6696
6696
  }
6697
6697
  target.path = removeDotSegments(target.path);
6698
6698
  }
6699
- target.query = relative2.query;
6699
+ target.query = relative3.query;
6700
6700
  }
6701
6701
  target.userinfo = base.userinfo;
6702
6702
  target.host = base.host;
@@ -6704,7 +6704,7 @@ var require_fast_uri = __commonJS({
6704
6704
  }
6705
6705
  target.scheme = base.scheme;
6706
6706
  }
6707
- target.fragment = relative2.fragment;
6707
+ target.fragment = relative3.fragment;
6708
6708
  return target;
6709
6709
  }
6710
6710
  function equal(uriA, uriB, options) {
@@ -6875,7 +6875,7 @@ var require_fast_uri = __commonJS({
6875
6875
  var fastUri = {
6876
6876
  SCHEMES,
6877
6877
  normalize,
6878
- resolve: resolve12,
6878
+ resolve: resolve13,
6879
6879
  resolveComponent,
6880
6880
  equal,
6881
6881
  serialize,
@@ -7213,8 +7213,8 @@ var require_core = __commonJS({
7213
7213
  return this;
7214
7214
  }
7215
7215
  case "object": {
7216
- const cacheKey2 = schemaKeyRef;
7217
- this._cache.delete(cacheKey2);
7216
+ const cacheKey3 = schemaKeyRef;
7217
+ this._cache.delete(cacheKey3);
7218
7218
  let id = schemaKeyRef[this.opts.schemaId];
7219
7219
  if (id) {
7220
7220
  id = (0, resolve_1.normalizeId)(id);
@@ -10158,21 +10158,21 @@ async function promptLine(label, {
10158
10158
  allowEmpty = false,
10159
10159
  defaultValue
10160
10160
  } = {}) {
10161
- return await new Promise((resolve12) => {
10161
+ return await new Promise((resolve13) => {
10162
10162
  const suffix = defaultValue ? ` [${defaultValue}]` : "";
10163
10163
  const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout, terminal: true });
10164
10164
  rl.question(`${label}${suffix}: `, (answer) => {
10165
10165
  rl.close();
10166
10166
  const value = String(answer ?? "").trim();
10167
10167
  if (!value && typeof defaultValue === "string") {
10168
- resolve12(defaultValue);
10168
+ resolve13(defaultValue);
10169
10169
  return;
10170
10170
  }
10171
10171
  if (!value && !allowEmpty) {
10172
- resolve12("");
10172
+ resolve13("");
10173
10173
  return;
10174
10174
  }
10175
- resolve12(value);
10175
+ resolve13(value);
10176
10176
  });
10177
10177
  });
10178
10178
  }
@@ -10180,7 +10180,7 @@ async function promptSecret(label) {
10180
10180
  if (!process.stdin.isTTY || !process.stdout.isTTY || typeof process.stdin.setRawMode !== "function") {
10181
10181
  return await promptLine(label);
10182
10182
  }
10183
- return await new Promise((resolve12) => {
10183
+ return await new Promise((resolve13) => {
10184
10184
  const stdin = process.stdin;
10185
10185
  const stdout = process.stdout;
10186
10186
  const wasRaw = Boolean(stdin.isRaw);
@@ -10194,7 +10194,7 @@ async function promptSecret(label) {
10194
10194
  const finish = () => {
10195
10195
  cleanup();
10196
10196
  stdout.write("\n");
10197
- resolve12(value);
10197
+ resolve13(value);
10198
10198
  };
10199
10199
  const onData = (chunk) => {
10200
10200
  const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
@@ -10203,7 +10203,7 @@ async function promptSecret(label) {
10203
10203
  cleanup();
10204
10204
  process.exitCode = 130;
10205
10205
  stdout.write("\n");
10206
- return resolve12("");
10206
+ return resolve13("");
10207
10207
  }
10208
10208
  if (char === "\r" || char === "\n") {
10209
10209
  finish();
@@ -10472,7 +10472,7 @@ async function storeAuthenticatedSession(params) {
10472
10472
  return output;
10473
10473
  }
10474
10474
  function sleep(ms) {
10475
- return new Promise((resolve12) => setTimeout(resolve12, ms));
10475
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
10476
10476
  }
10477
10477
  function hasExplicitTimeoutFlag(argv = process.argv) {
10478
10478
  return argv.some((arg) => arg === "--timeout-seconds" || arg.startsWith("--timeout-seconds="));
@@ -11024,7 +11024,7 @@ async function pollUntil(check2, opts) {
11024
11024
  }
11025
11025
  }
11026
11026
  function sleep2(ms) {
11027
- return new Promise((resolve12) => setTimeout(resolve12, ms));
11027
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
11028
11028
  }
11029
11029
 
11030
11030
  // src/commands/compliance.ts
@@ -14119,8 +14119,8 @@ function registerAgentGuardrailCommands(agent) {
14119
14119
  try {
14120
14120
  rule = JSON.parse(opts.rule);
14121
14121
  } catch {
14122
- const { readFileSync: readFileSync12 } = await import("fs");
14123
- rule = JSON.parse(readFileSync12(opts.rule, "utf-8"));
14122
+ const { readFileSync: readFileSync14 } = await import("fs");
14123
+ rule = JSON.parse(readFileSync14(opts.rule, "utf-8"));
14124
14124
  }
14125
14125
  const data = await apiFetch(`/v1/console/agents/${opts.agent}/guardrails`, {
14126
14126
  method: "POST",
@@ -14757,9 +14757,9 @@ function registerAgent(program3) {
14757
14757
  process.stdout.write(yaml);
14758
14758
  return;
14759
14759
  }
14760
- const { writeFileSync: writeFileSync10 } = await import("fs");
14760
+ const { writeFileSync: writeFileSync11 } = await import("fs");
14761
14761
  const outputPath = opts.output ?? "tenant.yaml";
14762
- writeFileSync10(
14762
+ writeFileSync11(
14763
14763
  outputPath,
14764
14764
  `# tenant.yaml - Front Of House agent manifest
14765
14765
  # Edit this file and run: foh plan tenant.yaml
@@ -16194,11 +16194,11 @@ function registerVoice(program3) {
16194
16194
  }
16195
16195
  const outputPath = String(opts.out || `foh-voice-preview-${provider}-${voiceId}.mp3`).trim();
16196
16196
  const audio = Buffer.from(await res.arrayBuffer());
16197
- const { mkdirSync: mkdirSync7, writeFileSync: writeFileSync10 } = await import("fs");
16198
- const { dirname: dirname6, resolve: resolve12 } = await import("path");
16199
- const absolutePath = resolve12(outputPath);
16200
- mkdirSync7(dirname6(absolutePath), { recursive: true });
16201
- writeFileSync10(absolutePath, audio);
16197
+ const { mkdirSync: mkdirSync8, writeFileSync: writeFileSync11 } = await import("fs");
16198
+ const { dirname: dirname8, resolve: resolve13 } = await import("path");
16199
+ const absolutePath = resolve13(outputPath);
16200
+ mkdirSync8(dirname8(absolutePath), { recursive: true });
16201
+ writeFileSync11(absolutePath, audio);
16202
16202
  format({
16203
16203
  status: "ok",
16204
16204
  provider,
@@ -30679,7 +30679,7 @@ var Protocol = class {
30679
30679
  return;
30680
30680
  }
30681
30681
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
30682
- await new Promise((resolve12) => setTimeout(resolve12, pollInterval));
30682
+ await new Promise((resolve13) => setTimeout(resolve13, pollInterval));
30683
30683
  options?.signal?.throwIfAborted();
30684
30684
  }
30685
30685
  } catch (error2) {
@@ -30696,7 +30696,7 @@ var Protocol = class {
30696
30696
  */
30697
30697
  request(request, resultSchema, options) {
30698
30698
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
30699
- return new Promise((resolve12, reject) => {
30699
+ return new Promise((resolve13, reject) => {
30700
30700
  const earlyReject = (error2) => {
30701
30701
  reject(error2);
30702
30702
  };
@@ -30774,7 +30774,7 @@ var Protocol = class {
30774
30774
  if (!parseResult.success) {
30775
30775
  reject(parseResult.error);
30776
30776
  } else {
30777
- resolve12(parseResult.data);
30777
+ resolve13(parseResult.data);
30778
30778
  }
30779
30779
  } catch (error2) {
30780
30780
  reject(error2);
@@ -31035,12 +31035,12 @@ var Protocol = class {
31035
31035
  }
31036
31036
  } catch {
31037
31037
  }
31038
- return new Promise((resolve12, reject) => {
31038
+ return new Promise((resolve13, reject) => {
31039
31039
  if (signal.aborted) {
31040
31040
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
31041
31041
  return;
31042
31042
  }
31043
- const timeoutId = setTimeout(resolve12, interval);
31043
+ const timeoutId = setTimeout(resolve13, interval);
31044
31044
  signal.addEventListener("abort", () => {
31045
31045
  clearTimeout(timeoutId);
31046
31046
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -32140,7 +32140,7 @@ var McpServer = class {
32140
32140
  let task = createTaskResult.task;
32141
32141
  const pollInterval = task.pollInterval ?? 5e3;
32142
32142
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
32143
- await new Promise((resolve12) => setTimeout(resolve12, pollInterval));
32143
+ await new Promise((resolve13) => setTimeout(resolve13, pollInterval));
32144
32144
  const updatedTask = await extra.taskStore.getTask(taskId);
32145
32145
  if (!updatedTask) {
32146
32146
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -32789,12 +32789,12 @@ var StdioServerTransport = class {
32789
32789
  this.onclose?.();
32790
32790
  }
32791
32791
  send(message) {
32792
- return new Promise((resolve12) => {
32792
+ return new Promise((resolve13) => {
32793
32793
  const json3 = serializeMessage(message);
32794
32794
  if (this._stdout.write(json3)) {
32795
- resolve12();
32795
+ resolve13();
32796
32796
  } else {
32797
- this._stdout.once("drain", resolve12);
32797
+ this._stdout.once("drain", resolve13);
32798
32798
  }
32799
32799
  });
32800
32800
  }
@@ -32986,7 +32986,7 @@ async function runFohCli(params) {
32986
32986
  effectiveArgv.push("--json");
32987
32987
  }
32988
32988
  const command = `foh ${effectiveArgv.join(" ")}`;
32989
- return await new Promise((resolve12) => {
32989
+ return await new Promise((resolve13) => {
32990
32990
  const child = (0, import_node_child_process.spawn)(process.execPath, [cliEntry, ...effectiveArgv], {
32991
32991
  stdio: ["ignore", "pipe", "pipe"],
32992
32992
  env: {
@@ -33011,7 +33011,7 @@ async function runFohCli(params) {
33011
33011
  });
33012
33012
  child.once("error", (error2) => {
33013
33013
  clearTimeout(timeoutHandle);
33014
- resolve12({
33014
+ resolve13({
33015
33015
  ok: false,
33016
33016
  command,
33017
33017
  argv: effectiveArgv,
@@ -33027,7 +33027,7 @@ async function runFohCli(params) {
33027
33027
  const stderrText = finalizeBoundedText(stderrBuffer);
33028
33028
  const exitCode = Number.isFinite(code ?? NaN) ? Number(code) : 1;
33029
33029
  const stdoutJson = tryParseJson(stdoutText);
33030
- resolve12({
33030
+ resolve13({
33031
33031
  ok: !timedOut && exitCode === 0,
33032
33032
  command,
33033
33033
  argv: effectiveArgv,
@@ -34405,6 +34405,12 @@ var SETUP_STEP_ORDER = [
34405
34405
  "publish_agent",
34406
34406
  "emit_summary"
34407
34407
  ];
34408
+ function extractGuardrailsList(response) {
34409
+ if (Array.isArray(response)) return response;
34410
+ if (Array.isArray(response.guardrails)) return response.guardrails;
34411
+ if (Array.isArray(response.rules)) return response.rules;
34412
+ return [];
34413
+ }
34408
34414
  function resolveResumeIndex(resumeFromRaw) {
34409
34415
  if (!resumeFromRaw) {
34410
34416
  return { resumeFrom: null, resumeIndex: 0 };
@@ -34696,7 +34702,10 @@ function registerSetup(program3) {
34696
34702
  }
34697
34703
  await apiFetch("/v1/console/org/compliance/submit", {
34698
34704
  method: "POST",
34699
- body: JSON.stringify({ type: "standard" }),
34705
+ body: JSON.stringify({
34706
+ type: "standard",
34707
+ friendlyName: opts.agentName
34708
+ }),
34700
34709
  orgId: opts.org,
34701
34710
  apiUrlOverride: opts.apiUrl
34702
34711
  });
@@ -34842,12 +34851,18 @@ function registerSetup(program3) {
34842
34851
  const existing = await apiFetch(`/v1/console/agents/${resolvedAgentId}/guardrails`, {
34843
34852
  apiUrlOverride: opts.apiUrl
34844
34853
  });
34845
- if (Array.isArray(existing.rules) && existing.rules.length > 0) {
34854
+ if (extractGuardrailsList(existing).length > 0) {
34846
34855
  return { step: "seed_guardrails", status: "skipped", detail: "guardrails already set" };
34847
34856
  }
34848
34857
  await apiFetch(`/v1/console/agents/${resolvedAgentId}/guardrails`, {
34849
34858
  method: "POST",
34850
- body: JSON.stringify({ name: "NoSilentDrop", mode: "log", condition: "always" }),
34859
+ body: JSON.stringify({
34860
+ rule_id: "no-silent-drop",
34861
+ type: "focus",
34862
+ name: "NoSilentDrop",
34863
+ mode: "log",
34864
+ topics: ["property", "real estate", "viewing", "valuation", "buyer", "seller", "landlord", "tenant"]
34865
+ }),
34851
34866
  apiUrlOverride: opts.apiUrl
34852
34867
  });
34853
34868
  return { step: "seed_guardrails", status: "done" };
@@ -35011,8 +35026,8 @@ function registerSetup(program3) {
35011
35026
  }
35012
35027
  try {
35013
35028
  const manifest = await agentExport(resolvedAgentId, { apiUrlOverride: opts.apiUrl });
35014
- const { writeFileSync: writeFileSync10 } = await import("fs");
35015
- writeFileSync10(
35029
+ const { writeFileSync: writeFileSync11 } = await import("fs");
35030
+ writeFileSync11(
35016
35031
  "tenant.yaml",
35017
35032
  `# tenant.yaml - Front Of House agent manifest
35018
35033
  # Edit this file and run: foh plan tenant.yaml
@@ -35182,8 +35197,8 @@ function registerSim(program3) {
35182
35197
  }
35183
35198
  const cert = response.certificate;
35184
35199
  if (opts.out) {
35185
- const { writeFileSync: writeFileSync10 } = await import("fs");
35186
- writeFileSync10(opts.out, JSON.stringify(cert, null, 2) + "\n", "utf-8");
35200
+ const { writeFileSync: writeFileSync11 } = await import("fs");
35201
+ writeFileSync11(opts.out, JSON.stringify(cert, null, 2) + "\n", "utf-8");
35187
35202
  process.stderr.write(` Certificate written to ${opts.out}
35188
35203
  `);
35189
35204
  }
@@ -35233,8 +35248,8 @@ function registerSim(program3) {
35233
35248
  });
35234
35249
  }
35235
35250
  if (opts.out) {
35236
- const { writeFileSync: writeFileSync10 } = await import("fs");
35237
- writeFileSync10(opts.out, JSON.stringify(response.certificate, null, 2) + "\n", "utf-8");
35251
+ const { writeFileSync: writeFileSync11 } = await import("fs");
35252
+ writeFileSync11(opts.out, JSON.stringify(response.certificate, null, 2) + "\n", "utf-8");
35238
35253
  process.stderr.write(` Final certificate written to ${opts.out}
35239
35254
  `);
35240
35255
  }
@@ -36678,7 +36693,7 @@ function inferReasonCode(artifact) {
36678
36693
  function inferPromotionDecision(sourceType, reasonCode) {
36679
36694
  const reason = String(reasonCode || "").toLowerCase();
36680
36695
  if (sourceType === "external_agent_run") {
36681
- if (reason.includes("friendlyname") || reason.includes("api") || reason.includes("500")) return "fix_api";
36696
+ if (reason.includes("friendlyname") || reason.includes("guardrail") || reason.includes("rule_id") || reason.includes("api") || reason.includes("http_4") || reason.includes("http_5") || reason.includes("404") || reason.includes("500") || reason.includes("roundtrip")) return "fix_api";
36682
36697
  if (reason.includes("contact_phone") || reason.includes("voice_contact") || reason.includes("voice_not_included") || reason.includes("agent_limit") || reason.includes("org_") || reason.includes("template_apply")) return "fix_config";
36683
36698
  if (reason.includes("exec_policy") || reason.includes("policy_blocked") || reason.includes("auth") || reason.includes("config")) return "fix_config";
36684
36699
  if (reason.includes("cli") || reason.includes("command") || reason.includes("flag")) return "fix_cli";
@@ -36690,6 +36705,24 @@ function inferPromotionDecision(sourceType, reasonCode) {
36690
36705
  if (sourceType === "replay_failure" || sourceType === "runtime_miss") return "add_test";
36691
36706
  return "fix_runtime";
36692
36707
  }
36708
+ function inferOwnerSubsystem(sourceType, reasonCode) {
36709
+ const reason = String(reasonCode || "").toLowerCase();
36710
+ if (sourceType === "external_agent_run") {
36711
+ if (reason.includes("simulation") || reason.includes("certification") || reason.includes("scenario")) return "dojo_certification";
36712
+ if (reason.includes("contact_phone") || reason.includes("voice_contact") || reason.includes("voice_not_included") || reason.includes("byon") || reason.includes("provider_capacity")) return "voice_contact";
36713
+ if (reason.includes("exec_policy") || reason.includes("policy_blocked") || reason.includes("sandbox") || reason.includes("runner") || reason.includes("codex")) return "infra_runner";
36714
+ if (reason.includes("friendlyname") || reason.includes("guardrail") || reason.includes("rule_id") || reason.includes("api") || reason.includes("http_4") || reason.includes("http_5") || reason.includes("404") || reason.includes("500") || reason.includes("roundtrip")) return "api_contract";
36715
+ if (reason.includes("cli") || reason.includes("command") || reason.includes("flag")) return "cli";
36716
+ if (reason.includes("docs") || reason.includes("unclear") || reason.includes("not_found")) return "docs";
36717
+ if (reason.includes("runtime") || reason.includes("widget") || reason.includes("proof")) return "runtime";
36718
+ return "product_ux";
36719
+ }
36720
+ if (sourceType === "knowledge_miss") return "docs";
36721
+ if (sourceType === "replay_failure" || sourceType === "runtime_miss") return "runtime";
36722
+ if (sourceType === "proof_failure" || sourceType === "live_proof_failure") return "runtime";
36723
+ if (sourceType === "setup_failure") return "product_ux";
36724
+ return "product_ux";
36725
+ }
36693
36726
  function collectIds(artifact, explicit = {}) {
36694
36727
  const source = getPath2(artifact, "source");
36695
36728
  const ids = {
@@ -36729,6 +36762,13 @@ function defaultNextCommands(input) {
36729
36762
  if (input.sourceType === "external_agent_run" && input.sourceArtifactPath) {
36730
36763
  commands.push(`foh bug improve --from external-agent-run --file ${input.sourceArtifactPath} --json`);
36731
36764
  }
36765
+ if (input.ownerSubsystem === "dojo_certification") {
36766
+ commands.push(input.ids.agent_id ? `foh sim certify-loop --agent ${input.ids.agent_id} --json` : "foh sim certify-loop --agent <agent_id> --json");
36767
+ }
36768
+ if (input.ownerSubsystem === "voice_contact") {
36769
+ commands.push("foh provision status --json");
36770
+ commands.push(input.ids.agent_id ? `foh prove --agent ${input.ids.agent_id} --mission voice --contact-path auto --json` : "foh prove --mission voice --contact-path auto --json");
36771
+ }
36732
36772
  if (input.sourceArtifactPath) {
36733
36773
  commands.push(`foh bug report --out test-results/bug-report.from-improvement.json --command "investigate ${input.reasonCode}" --request-url https://front-of-house-api.stldocs.app/api --response-status 500 --next-check "Review ${(0, import_path7.basename)(input.sourceArtifactPath)}" --json`);
36734
36774
  }
@@ -36784,12 +36824,13 @@ function buildImprovementPacket(input) {
36784
36824
  });
36785
36825
  }
36786
36826
  const promotionDecision = parseEnum(input.promotionDecision, IMPROVEMENT_DECISIONS, "--recommendation") ?? inferPromotionDecision(sourceType, reasonCode);
36827
+ const ownerSubsystem = inferOwnerSubsystem(sourceType, reasonCode);
36787
36828
  const evidenceSummary = redactString(
36788
36829
  nonEmpty2(input.evidenceSummary) ?? nonEmpty2(getPath2(artifact, "summary")) ?? `Improvement candidate generated from ${sourceType} with reason ${reasonCode}.`
36789
36830
  );
36790
36831
  const nextCommands = Array.from(new Set([
36791
36832
  ...input.nextCommands ?? [],
36792
- ...defaultNextCommands({ sourceType, ids, sourceArtifactPath: input.sourceArtifactPath, reasonCode })
36833
+ ...defaultNextCommands({ sourceType, ids, sourceArtifactPath: input.sourceArtifactPath, reasonCode, ownerSubsystem })
36793
36834
  ].map((command) => command.trim()).filter(Boolean)));
36794
36835
  const packet = {
36795
36836
  schema_version: "foh_improvement_packet.v1",
@@ -36797,6 +36838,12 @@ function buildImprovementPacket(input) {
36797
36838
  source_type: sourceType,
36798
36839
  reason_code: reasonCode,
36799
36840
  promotion_decision: promotionDecision,
36841
+ owner_subsystem: ownerSubsystem,
36842
+ routing: {
36843
+ owner_subsystem: ownerSubsystem,
36844
+ promotion_decision: promotionDecision,
36845
+ reason_family: ownerSubsystem
36846
+ },
36800
36847
  ids,
36801
36848
  evidence: {
36802
36849
  summary: evidenceSummary,
@@ -37173,6 +37220,106 @@ function registerBug(program3) {
37173
37220
  });
37174
37221
  }
37175
37222
 
37223
+ // src/lib/proof-cache.ts
37224
+ var import_node_crypto2 = require("node:crypto");
37225
+ var import_node_fs2 = require("node:fs");
37226
+ var import_node_path = require("node:path");
37227
+ var DEFAULT_MAX_AGE_MS = 15 * 60 * 1e3;
37228
+ var DEFAULT_WAIT_MS = 180 * 1e3;
37229
+ var DEFAULT_POLL_MS = 500;
37230
+ function sleep3(ms) {
37231
+ return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
37232
+ }
37233
+ function stableJson(value) {
37234
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
37235
+ if (value && typeof value === "object") {
37236
+ return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`).join(",")}}`;
37237
+ }
37238
+ return JSON.stringify(value);
37239
+ }
37240
+ function cacheKey2(kind, keyParts) {
37241
+ return (0, import_node_crypto2.createHash)("sha256").update(stableJson({ kind, keyParts, schema_version: "foh_proof_cache_key.v1" })).digest("hex").slice(0, 32);
37242
+ }
37243
+ function publicPath(filePath) {
37244
+ const rel = (0, import_node_path.relative)(process.cwd(), filePath).replaceAll("\\", "/");
37245
+ return rel.startsWith("..") ? filePath.replaceAll("\\", "/") : rel;
37246
+ }
37247
+ function readFreshCache(filePath, maxAgeMs) {
37248
+ try {
37249
+ const payload = JSON.parse((0, import_node_fs2.readFileSync)(filePath, "utf8"));
37250
+ const createdAt = Date.parse(String(payload.created_at || ""));
37251
+ if (!Number.isFinite(createdAt)) return null;
37252
+ if (Date.now() - createdAt > maxAgeMs) return null;
37253
+ return payload.value ?? null;
37254
+ } catch {
37255
+ return null;
37256
+ }
37257
+ }
37258
+ function writeCache(filePath, value) {
37259
+ (0, import_node_fs2.mkdirSync)((0, import_node_path.dirname)(filePath), { recursive: true });
37260
+ (0, import_node_fs2.writeFileSync)(filePath, `${JSON.stringify({
37261
+ schema_version: "foh_proof_cache_entry.v1",
37262
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
37263
+ value
37264
+ }, null, 2)}
37265
+ `, "utf8");
37266
+ }
37267
+ function resolveProofCacheDir(input) {
37268
+ const value = String(input || process.env.FOH_PROOF_CACHE_DIR || "").trim();
37269
+ if (!value) return null;
37270
+ return (0, import_node_path.isAbsolute)(value) ? value : (0, import_node_path.resolve)(process.cwd(), value);
37271
+ }
37272
+ async function withProofCache(options, run) {
37273
+ const resolvedDir = resolveProofCacheDir(options.cacheDir);
37274
+ if (!resolvedDir) {
37275
+ return {
37276
+ value: await run(),
37277
+ metadata: { hit: false, key: "disabled", cache_path: "", waited_ms: 0 }
37278
+ };
37279
+ }
37280
+ const key = cacheKey2(options.kind, options.keyParts);
37281
+ const cachePath = (0, import_node_path.join)(resolvedDir, `${key}.json`);
37282
+ const lockPath = (0, import_node_path.join)(resolvedDir, `${key}.lock`);
37283
+ const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
37284
+ const waitMs = options.waitMs ?? Number(process.env.FOH_PROOF_CACHE_WAIT_MS || DEFAULT_WAIT_MS);
37285
+ const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
37286
+ (0, import_node_fs2.mkdirSync)(resolvedDir, { recursive: true });
37287
+ const existing = readFreshCache(cachePath, maxAgeMs);
37288
+ if (existing) {
37289
+ return {
37290
+ value: existing,
37291
+ metadata: { hit: true, key, cache_path: publicPath(cachePath), waited_ms: 0 }
37292
+ };
37293
+ }
37294
+ let lockOwner = false;
37295
+ try {
37296
+ (0, import_node_fs2.mkdirSync)(lockPath);
37297
+ lockOwner = true;
37298
+ } catch {
37299
+ const started = Date.now();
37300
+ while (Date.now() - started < waitMs) {
37301
+ await sleep3(pollMs);
37302
+ const waitedValue = readFreshCache(cachePath, maxAgeMs);
37303
+ if (waitedValue) {
37304
+ return {
37305
+ value: waitedValue,
37306
+ metadata: { hit: true, key, cache_path: publicPath(cachePath), waited_ms: Date.now() - started }
37307
+ };
37308
+ }
37309
+ }
37310
+ }
37311
+ try {
37312
+ const value = await run();
37313
+ writeCache(cachePath, value);
37314
+ return {
37315
+ value,
37316
+ metadata: { hit: false, key, cache_path: publicPath(cachePath), waited_ms: 0 }
37317
+ };
37318
+ } finally {
37319
+ if (lockOwner) (0, import_node_fs2.rmSync)(lockPath, { recursive: true, force: true });
37320
+ }
37321
+ }
37322
+
37176
37323
  // src/commands/prove.ts
37177
37324
  function categoryForCheck(name) {
37178
37325
  if (name === "auth") return "auth";
@@ -37268,11 +37415,12 @@ function isProviderCapacityBlocked(onboarding) {
37268
37415
  return /maximum number of subaccounts|subaccount limit|reserve[- ]number pool|reserve pool exhausted|global safety limit/.test(message);
37269
37416
  }
37270
37417
  function registerProve(program3) {
37271
- program3.command("prove").description("Produce one setup/runtime proof bundle for an agent").option("--agent <id>", "Agent ID to prove").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--cert-mode <m>", "Simulation cert mode: quick, full, stress", "quick").option("--cert-adaptive-runs <n>", "Adaptive runs for full/stress certification", "30").option("--cert-max-improvement-rounds <n>", "Max prompt improvement rounds in cert loop (0-5)", "1").option("--mission <mission>", "Proof mission: setup, widget, voice, publish", "setup").option("--contact-path <mode>", "Voice contact path: auto, managed, or byon", "auto").option("--mutation-mode <mode>", "Proof mutation mode: read-only or ensure", "read-only").option("--repair", "Alias for --mutation-mode ensure").option("--require-phone", "Hold proof if no phone/contact number is provisioned").option("--skip-cert", "Skip simulation certification check").option("--skip-smoke", "Skip widget runtime smoke check").option("--skip-voice-health", "Skip realtime voice provider health check").option("--out <path>", "Write signed proof report JSON to this path").option("--strict", "Exit non-zero unless all non-skipped checks pass").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
37418
+ program3.command("prove").description("Produce one setup/runtime proof bundle for an agent").option("--agent <id>", "Agent ID to prove").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--cert-mode <m>", "Simulation cert mode: quick, full, stress", "quick").option("--cert-adaptive-runs <n>", "Adaptive runs for full/stress certification", "30").option("--cert-max-improvement-rounds <n>", "Max prompt improvement rounds in cert loop (0-5)", "1").option("--mission <mission>", "Proof mission: setup, widget, voice, publish", "setup").option("--contact-path <mode>", "Voice contact path: auto, managed, or byon", "auto").option("--mutation-mode <mode>", "Proof mutation mode: read-only or ensure", "read-only").option("--repair", "Alias for --mutation-mode ensure").option("--require-phone", "Hold proof if no phone/contact number is provisioned").option("--skip-cert", "Skip simulation certification check").option("--skip-smoke", "Skip widget runtime smoke check").option("--skip-voice-health", "Skip realtime voice provider health check").option("--proof-cache-dir <path>", "Optional local proof cache directory for shared certification results").option("--out <path>", "Write signed proof report JSON to this path").option("--strict", "Exit non-zero unless all non-skipped checks pass").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
37272
37419
  const checks = [];
37273
37420
  const mission = normalizeMission(opts.mission);
37274
37421
  const contactPath = normalizeContactPath(opts.contactPath);
37275
37422
  const mutationMode = normalizeMutationMode(opts.mutationMode, Boolean(opts.repair));
37423
+ let validationFingerprint = null;
37276
37424
  const ctx = {
37277
37425
  tokenPresent: false,
37278
37426
  traceIds: [],
@@ -37354,6 +37502,7 @@ function registerProve(program3) {
37354
37502
  apiUrlOverride: opts.apiUrl
37355
37503
  });
37356
37504
  const issues = Array.isArray(validation.issues) ? validation.issues : [];
37505
+ validationFingerprint = validation;
37357
37506
  if (validation.ok === false || issues.length > 0) {
37358
37507
  checks.push(hold("agent_validation", "agent_validation_issues", `Agent validation returned ${issues.length} issue(s).`, `foh agent validate --agent ${ctx.agentId} --json`, validation));
37359
37508
  } else {
@@ -37526,21 +37675,42 @@ function registerProve(program3) {
37526
37675
  } else {
37527
37676
  try {
37528
37677
  const certMode = normalizeAgentCertMode(opts.certMode);
37529
- const loop = await runSetupCertifyLoop(ctx.agentId, {
37678
+ const agentId = ctx.agentId;
37679
+ const adaptiveRuns = Math.max(1, Number(opts.certAdaptiveRuns ?? 30) || 30);
37680
+ const maxImprovementRounds = Math.max(0, Math.min(5, Number(opts.certMaxImprovementRounds ?? 1) || 1));
37681
+ const cached2 = await withProofCache({
37682
+ cacheDir: opts.proofCacheDir,
37683
+ kind: "simulation_certification",
37684
+ keyParts: {
37685
+ agent_id: agentId,
37686
+ org_id: ctx.orgId,
37687
+ api_url: opts.apiUrl || ctx.apiUrl || null,
37688
+ cert_mode: certMode,
37689
+ adaptive_runs: adaptiveRuns,
37690
+ max_improvement_rounds: maxImprovementRounds,
37691
+ validation_fingerprint: validationFingerprint
37692
+ }
37693
+ }, () => runSetupCertifyLoop(agentId, {
37530
37694
  mode: certMode,
37531
- adaptiveRuns: Math.max(1, Number(opts.certAdaptiveRuns ?? 30) || 30),
37532
- maxImprovementRounds: Math.max(0, Math.min(5, Number(opts.certMaxImprovementRounds ?? 1) || 1)),
37695
+ adaptiveRuns,
37696
+ maxImprovementRounds,
37533
37697
  orgId: ctx.orgId,
37534
37698
  apiUrlOverride: opts.apiUrl
37535
- });
37699
+ }));
37700
+ const loop = cached2.value;
37701
+ const loopWithCache = {
37702
+ ...loop,
37703
+ proof_cache: cached2.metadata
37704
+ };
37536
37705
  if (!loop.overall_pass) {
37537
- checks.push(hold("simulation_certification", "simulation_certification_failed", "Simulation certification did not pass.", `foh sim certify-loop --agent ${ctx.agentId} --${certMode === "quick" ? "full" : certMode} --json`, loop));
37706
+ checks.push(hold("simulation_certification", "simulation_certification_failed", "Simulation certification did not pass.", `foh sim certify-loop --agent ${agentId} --${certMode === "quick" ? "full" : certMode} --json`, loopWithCache));
37538
37707
  } else {
37539
37708
  checks.push(pass("simulation_certification", "Simulation certification passed.", {
37540
37709
  mode: loop.mode,
37541
37710
  attempts: loop.attempts?.length ?? 0,
37542
37711
  improvement_runs: loop.improvement_runs,
37543
- scenario_summary: loop.certificate?.scenario_summary
37712
+ scenario_summary: loop.certificate?.scenario_summary,
37713
+ proof_cache: cached2.metadata
37544
37714
  }));
37545
37715
  }
37546
37716
  } catch (error2) {
@@ -38034,7 +38204,7 @@ async function runSelf(args, apiUrlOverride) {
38034
38204
  if (apiUrlOverride && !spawnArgs.includes("--api-url")) {
38035
38205
  spawnArgs.push("--api-url", apiUrlOverride);
38036
38206
  }
38037
- return await new Promise((resolve12, reject) => {
38207
+ return await new Promise((resolve13, reject) => {
38038
38208
  const child = (0, import_child_process2.spawn)(process.execPath, [process.argv[1], ...spawnArgs], {
38039
38209
  stdio: "inherit",
38040
38210
  env: {
@@ -38044,7 +38214,7 @@ async function runSelf(args, apiUrlOverride) {
38044
38214
  }
38045
38215
  });
38046
38216
  child.once("error", reject);
38047
- child.once("close", (code) => resolve12(typeof code === "number" ? code : 1));
38217
+ child.once("close", (code) => resolve13(typeof code === "number" ? code : 1));
38048
38218
  });
38049
38219
  }
38050
38220
  function shouldUseInteractiveHome(argv) {
@@ -38422,17 +38592,17 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
38422
38592
  async function applyRepoUpdate(repoRoot) {
38423
38593
  const scriptPath = (0, import_path9.join)(repoRoot, "scripts", "Install-FohCli.ps1");
38424
38594
  if (process.platform === "win32") {
38425
- return await new Promise((resolve12, reject) => {
38595
+ return await new Promise((resolve13, reject) => {
38426
38596
  const child = (0, import_child_process3.spawn)(
38427
38597
  "powershell",
38428
38598
  ["-ExecutionPolicy", "Bypass", "-File", scriptPath],
38429
38599
  { stdio: "inherit" }
38430
38600
  );
38431
38601
  child.once("error", reject);
38432
- child.once("close", (code) => resolve12(typeof code === "number" ? code : 1));
38602
+ child.once("close", (code) => resolve13(typeof code === "number" ? code : 1));
38433
38603
  });
38434
38604
  }
38435
- return await new Promise((resolve12, reject) => {
38605
+ return await new Promise((resolve13, reject) => {
38436
38606
  const child = (0, import_child_process3.spawn)(
38437
38607
  "corepack",
38438
38608
  ["pnpm", "cli:install:global"],
@@ -38442,7 +38612,7 @@ async function applyRepoUpdate(repoRoot) {
38442
38612
  }
38443
38613
  );
38444
38614
  child.once("error", reject);
38445
- child.once("close", (code) => resolve12(typeof code === "number" ? code : 1));
38615
+ child.once("close", (code) => resolve13(typeof code === "number" ? code : 1));
38446
38616
  });
38447
38617
  }
38448
38618
  function shouldShowUpdateNotice(argv = process.argv) {
@@ -38578,8 +38748,8 @@ function registerUpdate(program3) {
38578
38748
  }
38579
38749
 
38580
38750
  // src/commands/eval.ts
38581
- var import_fs15 = require("fs");
38582
- var import_path13 = require("path");
38751
+ var import_fs16 = require("fs");
38752
+ var import_path14 = require("path");
38583
38753
  var import_child_process5 = require("child_process");
38584
38754
 
38585
38755
  // src/lib/external-agent-artifact-safety.ts
@@ -38869,6 +39039,8 @@ function completeExternalAgentCliInvocation(capture, input) {
38869
39039
  const completedAt = input.completedAt || (/* @__PURE__ */ new Date()).toISOString();
38870
39040
  const output = redactPath(String(input.output || "").slice(0, 256 * 1024));
38871
39041
  const parsed = parseEnvelope(output);
39042
+ const exitCode = Number.isInteger(input.exitCode) ? Number(input.exitCode) : null;
39043
+ const status = parsed.status ?? (exitCode === 0 ? "pass" : exitCode === null ? null : "fail");
38872
39044
  let artifact = null;
38873
39045
  if (output.trim()) {
38874
39046
  artifact = outputArtifactName(capture.commandId);
@@ -38888,8 +39060,8 @@ function completeExternalAgentCliInvocation(capture, input) {
38888
39060
  started_at: capture.startedAt,
38889
39061
  completed_at: completedAt,
38890
39062
  duration_ms: Math.max(0, Date.parse(completedAt) - Date.parse(capture.startedAt)),
38891
- exit_code: Number.isInteger(input.exitCode) ? Number(input.exitCode) : null,
38892
- status: parsed.status,
39063
+ exit_code: exitCode,
39064
+ status,
38893
39065
  reason_code: parsed.reasonCode,
38894
39066
  check_reason_codes: parsed.checkReasonCodes,
38895
39067
  output_artifact: artifact,
@@ -38912,10 +39084,62 @@ function readCommandRecords(runDir) {
38912
39084
  }
38913
39085
 
38914
39086
  // src/lib/external-agent-executor.ts
38915
- var import_fs14 = require("fs");
39087
+ var import_fs15 = require("fs");
38916
39088
  var import_os2 = require("os");
38917
- var import_path12 = require("path");
39089
+ var import_path13 = require("path");
38918
39090
  var import_child_process4 = require("child_process");
39091
+
39092
+ // src/lib/external-agent-metadata.ts
39093
+ var import_fs14 = require("fs");
39094
+ var import_path12 = require("path");
39095
+ var EXTERNAL_AGENT_METADATA_FILENAMES = [
39096
+ "external-agent-metadata.json",
39097
+ "agent-metadata.json"
39098
+ ];
39099
+ var PUBLIC_DOC_URL_RE = /^https:\/\/frontofhouse\.okii\.uk(?:\/[A-Za-z0-9._~:/?#[\]@!$&'()*+,;=%-]*)?$/;
39100
+ function normalizeDocUrl(value) {
39101
+ const raw = typeof value === "string" ? value : value && typeof value === "object" && typeof value.url === "string" ? String(value.url) : "";
39102
+ const url2 = raw.trim().replace(/[.?!:]+$/g, "");
39103
+ if (!PUBLIC_DOC_URL_RE.test(url2)) return null;
39104
+ return url2;
39105
+ }
39106
+ function collectDocsFrom(value, docs) {
39107
+ if (Array.isArray(value)) {
39108
+ for (const entry of value) {
39109
+ const url2 = normalizeDocUrl(entry);
39110
+ if (url2) docs.add(url2);
39111
+ }
39112
+ }
39113
+ }
39114
+ function readExternalAgentMetadata(runDir) {
39115
+ for (const filename of EXTERNAL_AGENT_METADATA_FILENAMES) {
39116
+ const path2 = (0, import_path12.join)(runDir, filename);
39117
+ if (!(0, import_fs14.existsSync)(path2)) continue;
39118
+ try {
39119
+ const parsed = JSON.parse((0, import_fs14.readFileSync)(path2, "utf8"));
39120
+ const docs = /* @__PURE__ */ new Set();
39121
+ collectDocsFrom(parsed.docs_pages_used, docs);
39122
+ collectDocsFrom(parsed.docs_pages_observed, docs);
39123
+ collectDocsFrom(parsed.docs_used, docs);
39124
+ collectDocsFrom(parsed.public_docs_used, docs);
39125
+ return {
39126
+ path: filename,
39127
+ docs_pages_used: Array.from(docs).sort()
39128
+ };
39129
+ } catch {
39130
+ return {
39131
+ path: filename,
39132
+ docs_pages_used: []
39133
+ };
39134
+ }
39135
+ }
39136
+ return {
39137
+ path: null,
39138
+ docs_pages_used: []
39139
+ };
39140
+ }
39141
+
39142
+ // src/lib/external-agent-executor.ts
38919
39143
  var CODEX_EXECUTOR_DENIED_ENV_PREFIXES = [
38920
39144
  "SUPABASE_",
38921
39145
  "DATABASE_",
@@ -38985,7 +39209,7 @@ function buildCodexExecutorEnv(input) {
38985
39209
  env[childKey] = value;
38986
39210
  }
38987
39211
  }
38988
- env.npm_config_cache = (0, import_path12.join)((0, import_path12.dirname)(input.runDir), ".npm-cache");
39212
+ env.npm_config_cache = (0, import_path13.join)((0, import_path13.dirname)(input.runDir), ".npm-cache");
38989
39213
  env.npm_config_prefer_online = "true";
38990
39214
  env.npm_config_update_notifier = "false";
38991
39215
  env.npm_config_yes = "true";
@@ -38996,14 +39220,14 @@ function buildCodexExecutorEnv(input) {
38996
39220
  return env;
38997
39221
  }
38998
39222
  function normalizeForCompare(path2) {
38999
- const resolved = (0, import_path12.resolve)(path2);
39223
+ const resolved = (0, import_path13.resolve)(path2);
39000
39224
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
39001
39225
  }
39002
39226
  function isPathInside(childPath, parentPath) {
39003
39227
  const child = normalizeForCompare(childPath);
39004
39228
  const parent = normalizeForCompare(parentPath);
39005
- const rel = (0, import_path12.relative)(parent, child);
39006
- return rel === "" || !!rel && !rel.startsWith("..") && !(0, import_path12.isAbsolute)(rel);
39229
+ const rel = (0, import_path13.relative)(parent, child);
39230
+ return rel === "" || !!rel && !rel.startsWith("..") && !(0, import_path13.isAbsolute)(rel);
39007
39231
  }
39008
39232
  function requireString(value, field) {
39009
39233
  if (typeof value !== "string" || value.trim() === "") {
@@ -39012,10 +39236,10 @@ function requireString(value, field) {
39012
39236
  return value;
39013
39237
  }
39014
39238
  function readBatch(batchPath) {
39015
- if (!(0, import_fs14.existsSync)(batchPath)) {
39239
+ if (!(0, import_fs15.existsSync)(batchPath)) {
39016
39240
  throw new ExternalAgentExecutorError("external_agent_batch_not_found", `Batch file not found: ${batchPath}`);
39017
39241
  }
39018
- const parsed = JSON.parse((0, import_fs14.readFileSync)(batchPath, "utf8"));
39242
+ const parsed = JSON.parse((0, import_fs15.readFileSync)(batchPath, "utf8"));
39019
39243
  if (parsed.schema_version !== "external_agent_batch_plan.v1") {
39020
39244
  throw new ExternalAgentExecutorError("invalid_external_agent_batch", "Batch schema_version must be external_agent_batch_plan.v1.");
39021
39245
  }
@@ -39040,12 +39264,29 @@ function defaultRunnerProbe(command, args) {
39040
39264
  function quotePowerShellArg(value) {
39041
39265
  return `'${value.replace(/'/g, "''")}'`;
39042
39266
  }
39267
+ function quoteShellArg(value) {
39268
+ const text = String(value);
39269
+ if (/^[A-Za-z0-9_./:=@-]+$/.test(text)) return text;
39270
+ return `"${text.replace(/(["$`])/g, "\\$1")}"`;
39271
+ }
39272
+ function externalAgentSummaryCommand(root) {
39273
+ return [
39274
+ "node",
39275
+ "scripts/summarize-external-agent-runs.mjs",
39276
+ "--root",
39277
+ quoteShellArg(root),
39278
+ "--out",
39279
+ quoteShellArg((0, import_path13.join)(root, "latest-summary.json")),
39280
+ "--report",
39281
+ quoteShellArg((0, import_path13.join)(root, "summary.report.json"))
39282
+ ].join(" ");
39283
+ }
39043
39284
  function resolveCodexProbeCommand() {
39044
39285
  if (process.platform !== "win32") return "codex";
39045
39286
  const appData = process.env.APPDATA;
39046
39287
  if (appData) {
39047
- const appDataShim = (0, import_path12.join)(appData, "npm", "codex.cmd");
39048
- if ((0, import_fs14.existsSync)(appDataShim)) return appDataShim;
39288
+ const appDataShim = (0, import_path13.join)(appData, "npm", "codex.cmd");
39289
+ if ((0, import_fs15.existsSync)(appDataShim)) return appDataShim;
39049
39290
  }
39050
39291
  return "codex.cmd";
39051
39292
  }
@@ -39057,6 +39298,13 @@ function validateCodexRunner(options) {
39057
39298
  return {
39058
39299
  binaryChecked: false,
39059
39300
  requiredFlagsChecked: false,
39301
+ version: null,
39302
+ rootHelpChecked: false,
39303
+ execHelpChecked: false,
39304
+ supportsModernApprovalMode: true,
39305
+ supportsLegacyFullAuto: false,
39306
+ supportsYoloAlias: null,
39307
+ yoloPolicy: "not_checked",
39060
39308
  automationMode: "ask-for-approval-never",
39061
39309
  globalArgs: ["--ask-for-approval", "never"],
39062
39310
  execArgs: []
@@ -39068,6 +39316,8 @@ function validateCodexRunner(options) {
39068
39316
  if (version2.error || version2.status !== 0) {
39069
39317
  throw new ExternalAgentExecutorError("external_agent_runner_binary_missing", "Codex runner probe failed: `codex --version` did not exit 0.");
39070
39318
  }
39319
+ const versionText = `${version2.stdout}
39320
+ ${version2.stderr}`.trim() || null;
39071
39321
  const help = probe(probeCommand, ["exec", "--help"]);
39072
39322
  if (help.error || help.status !== 0) {
39073
39323
  throw new ExternalAgentExecutorError("external_agent_runner_help_unavailable", "Codex runner probe failed: `codex exec --help` did not exit 0.");
@@ -39077,6 +39327,11 @@ ${help.stderr}`;
39077
39327
  const rootHelp = probe(probeCommand, ["--help"]);
39078
39328
  const rootHelpText = `${rootHelp.stdout}
39079
39329
  ${rootHelp.stderr}`;
39330
+ const yoloHelp = probe(probeCommand, ["--yolo", "--help"]);
39331
+ const yoloHelpText = `${yoloHelp.stdout}
39332
+ ${yoloHelp.stderr}`;
39333
+ const supportsYoloAlias = yoloHelp.status === 0 && /(?:Usage:\s*codex|Codex CLI)/i.test(yoloHelpText);
39334
+ const yoloPolicy = supportsYoloAlias ? "observed_not_canonical" : "unsupported";
39080
39335
  const commonExecFlags = [
39081
39336
  "--cd",
39082
39337
  "--skip-git-repo-check",
@@ -39102,6 +39357,13 @@ ${rootHelp.stderr}`;
39102
39357
  return {
39103
39358
  binaryChecked: true,
39104
39359
  requiredFlagsChecked: true,
39360
+ version: versionText,
39361
+ rootHelpChecked: rootHelp.status === 0,
39362
+ execHelpChecked: true,
39363
+ supportsModernApprovalMode,
39364
+ supportsLegacyFullAuto,
39365
+ supportsYoloAlias,
39366
+ yoloPolicy,
39105
39367
  automationMode: "ask-for-approval-never",
39106
39368
  globalArgs: ["--ask-for-approval", "never"],
39107
39369
  execArgs: []
@@ -39110,6 +39372,13 @@ ${rootHelp.stderr}`;
39110
39372
  return {
39111
39373
  binaryChecked: true,
39112
39374
  requiredFlagsChecked: true,
39375
+ version: versionText,
39376
+ rootHelpChecked: rootHelp.status === 0,
39377
+ execHelpChecked: true,
39378
+ supportsModernApprovalMode,
39379
+ supportsLegacyFullAuto,
39380
+ supportsYoloAlias,
39381
+ yoloPolicy,
39113
39382
  automationMode: "full-auto",
39114
39383
  globalArgs: [],
39115
39384
  execArgs: ["--full-auto"]
@@ -39150,13 +39419,13 @@ function safeRunId(value) {
39150
39419
  return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "run";
39151
39420
  }
39152
39421
  function resolveWorkspaceRoot(input) {
39153
- if (input.workspaceRoot) return (0, import_path12.resolve)(input.workspaceRoot);
39154
- const batchStem = (0, import_path12.basename)((0, import_path12.resolve)(input.batchPath)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
39155
- const repoStem = (0, import_path12.basename)((0, import_path12.resolve)(input.privateRepoRoot)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
39156
- return (0, import_path12.resolve)((0, import_os2.tmpdir)(), "foh-external-agent-workspaces", repoStem, batchStem);
39422
+ if (input.workspaceRoot) return (0, import_path13.resolve)(input.workspaceRoot);
39423
+ const batchStem = (0, import_path13.basename)((0, import_path13.resolve)(input.batchPath)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
39424
+ const repoStem = (0, import_path13.basename)((0, import_path13.resolve)(input.privateRepoRoot)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
39425
+ return (0, import_path13.resolve)((0, import_os2.tmpdir)(), "foh-external-agent-workspaces", repoStem, batchStem);
39157
39426
  }
39158
39427
  function promptVersionFromPath(promptPath) {
39159
- const raw = (0, import_fs14.readFileSync)(promptPath, "utf8");
39428
+ const raw = (0, import_fs15.readFileSync)(promptPath, "utf8");
39160
39429
  if (raw.includes("Do not assume access to the private source repository")) return "blank-setup.v1";
39161
39430
  return "unknown";
39162
39431
  }
@@ -39165,13 +39434,13 @@ function createExternalAgentExecutorPlan(options) {
39165
39434
  if (runner !== "codex") {
39166
39435
  throw new ExternalAgentExecutorError("unsupported_external_agent_runner", `Unsupported runner: ${runner}`);
39167
39436
  }
39168
- const batchPath = (0, import_path12.resolve)(options.batchPath);
39437
+ const batchPath = (0, import_path13.resolve)(options.batchPath);
39169
39438
  const batch = readBatch(batchPath);
39170
39439
  const runnerProbe = validateCodexRunner(options);
39171
39440
  const codexSandboxBackend = normalizeCodexSandboxBackend(options.codexSandboxBackend);
39172
39441
  const codexSandboxMode = normalizeCodexSandboxMode(options.codexSandboxMode);
39173
39442
  const codexNetworkAccess = options.codexNetworkAccess === true;
39174
- const privateRepoRoot = (0, import_path12.resolve)(options.privateRepoRoot || options.cwd || process.cwd());
39443
+ const privateRepoRoot = (0, import_path13.resolve)(options.privateRepoRoot || options.cwd || process.cwd());
39175
39444
  const workspaceRoot = resolveWorkspaceRoot({ batchPath, workspaceRoot: options.workspaceRoot, privateRepoRoot });
39176
39445
  if (isPathInside(workspaceRoot, privateRepoRoot)) {
39177
39446
  throw new ExternalAgentExecutorError(
@@ -39179,17 +39448,17 @@ function createExternalAgentExecutorPlan(options) {
39179
39448
  `Workspace root must be outside the private repository. workspace=${workspaceRoot} repo=${privateRepoRoot}`
39180
39449
  );
39181
39450
  }
39182
- (0, import_fs14.mkdirSync)(workspaceRoot, { recursive: true });
39183
- const batchDir = (0, import_path12.resolve)(String(batch.batch_dir || (0, import_path12.resolve)(batchPath, "..")));
39451
+ (0, import_fs15.mkdirSync)(workspaceRoot, { recursive: true });
39452
+ const batchDir = (0, import_path13.resolve)(String(batch.batch_dir || (0, import_path13.resolve)(batchPath, "..")));
39184
39453
  const timeoutMinutes = Number.isFinite(options.timeoutMinutes) && Number(options.timeoutMinutes) > 0 ? Number(options.timeoutMinutes) : 30;
39185
39454
  const runs = batch.runs.map((run) => {
39186
39455
  const runId = safeRunId(requireString(run.run_id, "runs[].run_id"));
39187
- const runDir = (0, import_path12.resolve)(requireString(run.run_dir, `runs[${runId}].run_dir`));
39188
- const promptPath = (0, import_path12.resolve)(requireString(run.prompt_path, `runs[${runId}].prompt_path`));
39189
- const workspaceDir = (0, import_path12.join)(workspaceRoot, runId);
39190
- (0, import_fs14.mkdirSync)(workspaceDir, { recursive: true });
39191
- (0, import_fs14.writeFileSync)(
39192
- (0, import_path12.join)(workspaceDir, "README.md"),
39456
+ const runDir = (0, import_path13.resolve)(requireString(run.run_dir, `runs[${runId}].run_dir`));
39457
+ const promptPath = (0, import_path13.resolve)(requireString(run.prompt_path, `runs[${runId}].prompt_path`));
39458
+ const workspaceDir = (0, import_path13.join)(workspaceRoot, runId);
39459
+ (0, import_fs15.mkdirSync)(workspaceDir, { recursive: true });
39460
+ (0, import_fs15.writeFileSync)(
39461
+ (0, import_path13.join)(workspaceDir, "README.md"),
39193
39462
  [
39194
39463
  "# FOH External-Agent Workspace",
39195
39464
  "",
@@ -39206,11 +39475,11 @@ function createExternalAgentExecutorPlan(options) {
39206
39475
  promptVersion: promptVersionFromPath(promptPath)
39207
39476
  });
39208
39477
  const promptVersion = String(env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] || "unknown");
39209
- const jsonlPath = (0, import_path12.join)(runDir, "codex-exec.jsonl");
39210
- const lastMessagePath = (0, import_path12.join)(runDir, "codex-last-message.md");
39211
- const stderrPath = (0, import_path12.join)(runDir, "codex-stderr.txt");
39212
- const runPath = (0, import_path12.join)(runDir, "run.json");
39213
- const artifactSafetyPath = (0, import_path12.join)(runDir, "artifact-safety.json");
39478
+ const jsonlPath = (0, import_path13.join)(runDir, "codex-exec.jsonl");
39479
+ const lastMessagePath = (0, import_path13.join)(runDir, "codex-last-message.md");
39480
+ const stderrPath = (0, import_path13.join)(runDir, "codex-stderr.txt");
39481
+ const runPath = (0, import_path13.join)(runDir, "run.json");
39482
+ const artifactSafetyPath = (0, import_path13.join)(runDir, "artifact-safety.json");
39214
39483
  const args = [
39215
39484
  ...runnerProbe.globalArgs,
39216
39485
  "exec",
@@ -39266,7 +39535,16 @@ function createExternalAgentExecutorPlan(options) {
39266
39535
  denied_env_names: [...CODEX_EXECUTOR_DENIED_ENV_NAMES],
39267
39536
  runner_probe: {
39268
39537
  binary_checked: runnerProbe.binaryChecked,
39269
- required_flags_checked: runnerProbe.requiredFlagsChecked
39538
+ required_flags_checked: runnerProbe.requiredFlagsChecked,
39539
+ version: runnerProbe.version,
39540
+ root_help_checked: runnerProbe.rootHelpChecked,
39541
+ exec_help_checked: runnerProbe.execHelpChecked,
39542
+ supports_modern_approval_mode: runnerProbe.supportsModernApprovalMode,
39543
+ supports_legacy_full_auto: runnerProbe.supportsLegacyFullAuto,
39544
+ supports_yolo_alias: runnerProbe.supportsYoloAlias,
39545
+ selected_automation_mode: runnerProbe.automationMode,
39546
+ canonical_command_policy: "explicit-flags",
39547
+ yolo_policy: runnerProbe.yoloPolicy
39270
39548
  },
39271
39549
  codex_automation_mode: runnerProbe.automationMode,
39272
39550
  codex_sandbox_mode: codexSandboxMode,
@@ -39277,55 +39555,55 @@ function createExternalAgentExecutorPlan(options) {
39277
39555
  };
39278
39556
  }
39279
39557
  function writeExternalAgentExecutorPlan(plan) {
39280
- const path2 = (0, import_path12.join)(plan.batch_dir, "executor-plan.json");
39281
- (0, import_fs14.mkdirSync)(plan.batch_dir, { recursive: true });
39282
- (0, import_fs14.writeFileSync)(path2, `${JSON.stringify(plan, null, 2)}
39558
+ const path2 = (0, import_path13.join)(plan.batch_dir, "executor-plan.json");
39559
+ (0, import_fs15.mkdirSync)(plan.batch_dir, { recursive: true });
39560
+ (0, import_fs15.writeFileSync)(path2, `${JSON.stringify(plan, null, 2)}
39283
39561
  `, "utf8");
39284
39562
  return path2;
39285
39563
  }
39286
39564
  function proofArtifactPasses(runDir) {
39287
- const proofPath = (0, import_path12.join)(runDir, "proof.json");
39288
- if (!(0, import_fs14.existsSync)(proofPath)) return false;
39565
+ const proofPath = (0, import_path13.join)(runDir, "proof.json");
39566
+ if (!(0, import_fs15.existsSync)(proofPath)) return false;
39289
39567
  try {
39290
- const parsed = JSON.parse((0, import_fs14.readFileSync)(proofPath, "utf8"));
39568
+ const parsed = JSON.parse((0, import_fs15.readFileSync)(proofPath, "utf8"));
39291
39569
  return parsed.ok === true || parsed.status === "pass" || parsed.status === "passed";
39292
39570
  } catch {
39293
39571
  return false;
39294
39572
  }
39295
39573
  }
39296
39574
  function readIfExists(path2) {
39297
- return (0, import_fs14.existsSync)(path2) ? (0, import_fs14.readFileSync)(path2, "utf8") : "";
39575
+ return (0, import_fs15.existsSync)(path2) ? (0, import_fs15.readFileSync)(path2, "utf8") : "";
39298
39576
  }
39299
39577
  function redactArtifactFile(path2, input = {}) {
39300
- if (!(0, import_fs14.existsSync)(path2)) return;
39301
- const original = (0, import_fs14.readFileSync)(path2, "utf8");
39578
+ if (!(0, import_fs15.existsSync)(path2)) return;
39579
+ const original = (0, import_fs15.readFileSync)(path2, "utf8");
39302
39580
  const redacted = redactExternalAgentArtifactText(original, input);
39303
- if (redacted !== original) (0, import_fs14.writeFileSync)(path2, redacted, "utf8");
39581
+ if (redacted !== original) (0, import_fs15.writeFileSync)(path2, redacted, "utf8");
39304
39582
  }
39305
39583
  function redactOutputArtifacts(run, input = {}) {
39306
39584
  redactArtifactFile(run.outputs.jsonl, input);
39307
39585
  redactArtifactFile(run.outputs.last_message, input);
39308
39586
  redactArtifactFile(run.outputs.stderr, input);
39309
- redactArtifactFile((0, import_path12.join)(run.run_dir, "commands.ndjson"), input);
39310
- if (!(0, import_fs14.existsSync)(run.run_dir)) return;
39311
- for (const name of (0, import_fs14.readdirSync)(run.run_dir)) {
39587
+ redactArtifactFile((0, import_path13.join)(run.run_dir, "commands.ndjson"), input);
39588
+ if (!(0, import_fs15.existsSync)(run.run_dir)) return;
39589
+ for (const name of (0, import_fs15.readdirSync)(run.run_dir)) {
39312
39590
  if (name.startsWith("command-output-cmd_") && !name.endsWith(".redacted")) {
39313
- redactArtifactFile((0, import_path12.join)(run.run_dir, name), input);
39591
+ redactArtifactFile((0, import_path13.join)(run.run_dir, name), input);
39314
39592
  }
39315
39593
  }
39316
39594
  }
39317
39595
  function copyCommandCaptureArtifacts(input) {
39318
- const commandLog = (0, import_path12.join)(input.captureDir, "commands.ndjson");
39319
- if (!(0, import_fs14.existsSync)(commandLog)) return;
39320
- (0, import_fs14.writeFileSync)((0, import_path12.join)(input.runDir, "commands.ndjson"), (0, import_fs14.readFileSync)(commandLog, "utf8"), "utf8");
39321
- for (const name of (0, import_fs14.readdirSync)(input.captureDir)) {
39596
+ const commandLog = (0, import_path13.join)(input.captureDir, "commands.ndjson");
39597
+ if (!(0, import_fs15.existsSync)(commandLog)) return;
39598
+ (0, import_fs15.writeFileSync)((0, import_path13.join)(input.runDir, "commands.ndjson"), (0, import_fs15.readFileSync)(commandLog, "utf8"), "utf8");
39599
+ for (const name of (0, import_fs15.readdirSync)(input.captureDir)) {
39322
39600
  if (name.startsWith("command-output-cmd_")) {
39323
- (0, import_fs14.copyFileSync)((0, import_path12.join)(input.captureDir, name), (0, import_path12.join)(input.runDir, name));
39601
+ (0, import_fs15.copyFileSync)((0, import_path13.join)(input.captureDir, name), (0, import_path13.join)(input.runDir, name));
39324
39602
  }
39325
39603
  }
39326
39604
  }
39327
39605
  function relativeArtifactName(path2) {
39328
- return (0, import_path12.basename)(path2);
39606
+ return (0, import_path13.basename)(path2);
39329
39607
  }
39330
39608
  function classifyRun(input) {
39331
39609
  if (input.timedOut) return { status: "hold", reasonCode: "codex_runner_timeout" };
@@ -39419,6 +39697,7 @@ ${stderr}`;
39419
39697
  }
39420
39698
  function buildExecutedRunArtifact(input) {
39421
39699
  const commands = readCommandRecords(input.run.run_dir);
39700
+ const agentMetadata = readExternalAgentMetadata(input.run.run_dir);
39422
39701
  return {
39423
39702
  schema_version: "external_agent_run.v1",
39424
39703
  run_id: input.run.run_id,
@@ -39450,7 +39729,7 @@ function buildExecutedRunArtifact(input) {
39450
39729
  "npx --yes @f-o-h/cli@latest"
39451
39730
  ],
39452
39731
  commands_run: commands.map((command) => command.command),
39453
- docs_pages_used: [],
39732
+ docs_pages_used: agentMetadata.docs_pages_used,
39454
39733
  eval_state: {
39455
39734
  org_reuse_expected: true,
39456
39735
  agent_reuse_expected: true,
@@ -39464,21 +39743,22 @@ function buildExecutedRunArtifact(input) {
39464
39743
  },
39465
39744
  artifacts: {
39466
39745
  terminal_transcript: relativeArtifactName(input.run.outputs.jsonl),
39467
- command_log: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "commands.ndjson")) ? "commands.ndjson" : null,
39468
- proof_bundle: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "proof.json")) ? "proof.json" : null,
39469
- replay_packet: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "replay.json")) ? "replay.json" : null,
39470
- knowledge_packet: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "knowledge.json")) ? "knowledge.json" : null,
39746
+ command_log: (0, import_fs15.existsSync)((0, import_path13.join)(input.run.run_dir, "commands.ndjson")) ? "commands.ndjson" : null,
39747
+ proof_bundle: (0, import_fs15.existsSync)((0, import_path13.join)(input.run.run_dir, "proof.json")) ? "proof.json" : null,
39748
+ replay_packet: (0, import_fs15.existsSync)((0, import_path13.join)(input.run.run_dir, "replay.json")) ? "replay.json" : null,
39749
+ knowledge_packet: (0, import_fs15.existsSync)((0, import_path13.join)(input.run.run_dir, "knowledge.json")) ? "knowledge.json" : null,
39471
39750
  improvement_packet: input.status === "pass" ? null : "improvement-packet.json",
39472
- notes: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "notes.md")) ? "notes.md" : null,
39751
+ agent_metadata: agentMetadata.path,
39752
+ notes: (0, import_fs15.existsSync)((0, import_path13.join)(input.run.run_dir, "notes.md")) ? "notes.md" : null,
39473
39753
  codex_last_message: relativeArtifactName(input.run.outputs.last_message),
39474
39754
  codex_stderr: relativeArtifactName(input.run.outputs.stderr),
39475
39755
  artifact_safety: relativeArtifactName(input.run.outputs.artifact_safety)
39476
39756
  },
39477
39757
  summary: input.status === "pass" ? "Controlled Codex external-agent run produced passing proof evidence." : `Controlled Codex external-agent run ended as ${input.status} with reason ${input.reasonCode}.`,
39478
- next_commands: input.status === "pass" ? ["corepack pnpm eval:external-agent:runs:summary"] : [
39758
+ next_commands: input.status === "pass" ? [externalAgentSummaryCommand((0, import_path13.dirname)(input.run.run_dir))] : [
39479
39759
  "foh eval external-agent scan-artifacts --run-dir <run_dir> --private-repo-root <private_repo_root> --write-redacted --json",
39480
39760
  "foh bug improve --from external-agent-run --file <run_dir>/run.json --out <run_dir>/improvement-packet.json --json",
39481
- "corepack pnpm eval:external-agent:runs:summary"
39761
+ externalAgentSummaryCommand((0, import_path13.dirname)(input.run.run_dir))
39482
39762
  ]
39483
39763
  };
39484
39764
  }
@@ -39493,8 +39773,8 @@ function spawnCodex(input) {
39493
39773
  stdio: ["pipe", "pipe", "pipe"],
39494
39774
  windowsHide: true
39495
39775
  });
39496
- const stdout = (0, import_fs14.createWriteStream)(input.stdoutPath, { flags: "w" });
39497
- const stderr = (0, import_fs14.createWriteStream)(input.stderrPath, { flags: "w" });
39776
+ const stdout = (0, import_fs15.createWriteStream)(input.stdoutPath, { flags: "w" });
39777
+ const stderr = (0, import_fs15.createWriteStream)(input.stderrPath, { flags: "w" });
39498
39778
  child.stdout.pipe(stdout);
39499
39779
  child.stderr.pipe(stderr);
39500
39780
  child.stdin.end(input.prompt);
@@ -39531,8 +39811,8 @@ function spawnCodex(input) {
39531
39811
  }
39532
39812
  function buildCommandInvocation(command, args) {
39533
39813
  if (process.platform === "win32" && command.toLowerCase().endsWith(".cmd")) {
39534
- const codexEntrypoint = (0, import_path12.join)((0, import_path12.dirname)(command), "node_modules", "@openai", "codex", "bin", "codex.js");
39535
- if ((0, import_fs14.existsSync)(codexEntrypoint)) return { command: process.execPath, args: [codexEntrypoint, ...args] };
39814
+ const codexEntrypoint = (0, import_path13.join)((0, import_path13.dirname)(command), "node_modules", "@openai", "codex", "bin", "codex.js");
39815
+ if ((0, import_fs15.existsSync)(codexEntrypoint)) return { command: process.execPath, args: [codexEntrypoint, ...args] };
39536
39816
  }
39537
39817
  return { command, args };
39538
39818
  }
@@ -39542,8 +39822,8 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
39542
39822
  const runnerCommand = options.runnerCommand || resolveCodexExecutionCommand();
39543
39823
  for (const run of plan.runs) {
39544
39824
  const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
39545
- const commandCaptureDir = (0, import_path12.join)(run.workspace_dir, ".foh-capture");
39546
- (0, import_fs14.mkdirSync)(commandCaptureDir, { recursive: true });
39825
+ const commandCaptureDir = (0, import_path13.join)(run.workspace_dir, ".foh-capture");
39826
+ (0, import_fs15.mkdirSync)(commandCaptureDir, { recursive: true });
39547
39827
  const env = buildCodexExecutorEnv({
39548
39828
  sourceEnv: options.env,
39549
39829
  runDir: commandCaptureDir,
@@ -39554,7 +39834,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
39554
39834
  args: run.args,
39555
39835
  cwd: run.workspace_dir,
39556
39836
  env,
39557
- prompt: (0, import_fs14.readFileSync)(run.prompt_path, "utf8"),
39837
+ prompt: (0, import_fs15.readFileSync)(run.prompt_path, "utf8"),
39558
39838
  stdoutPath: run.outputs.jsonl,
39559
39839
  stderrPath: run.outputs.stderr,
39560
39840
  timeoutMs: plan.timeout_minutes * 60 * 1e3
@@ -39567,7 +39847,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
39567
39847
  privateRepoRoot,
39568
39848
  writeRedacted: true
39569
39849
  });
39570
- (0, import_fs14.writeFileSync)(run.outputs.artifact_safety, `${JSON.stringify(artifactSafety, null, 2)}
39850
+ (0, import_fs15.writeFileSync)(run.outputs.artifact_safety, `${JSON.stringify(artifactSafety, null, 2)}
39571
39851
  `, "utf8");
39572
39852
  const runEndedAt = (/* @__PURE__ */ new Date()).toISOString();
39573
39853
  const classification = classifyRun({
@@ -39586,7 +39866,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
39586
39866
  timedOut: spawned.timedOut,
39587
39867
  durationMs: spawned.durationMs
39588
39868
  });
39589
- (0, import_fs14.writeFileSync)(run.outputs.run, `${JSON.stringify(runArtifact, null, 2)}
39869
+ (0, import_fs15.writeFileSync)(run.outputs.run, `${JSON.stringify(runArtifact, null, 2)}
39590
39870
  `, "utf8");
39591
39871
  results.push({
39592
39872
  run_id: run.run_id,
@@ -39619,7 +39899,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
39619
39899
  var DEFAULT_PROMPT_VERSION = "blank-setup.v1";
39620
39900
  var DEFAULT_BATCH_MODELS = "openai/codex,anthropic/claude,cursor/agent";
39621
39901
  var PROMPTS = {
39622
- "blank-setup.v1": "Go to https://frontofhouse.okii.uk. Use only public docs, public API docs, and the public npm CLI package. Always invoke the CLI with `npx --yes @f-o-h/cli@latest ...`; do not use unpinned `npx @f-o-h/cli ...`, because cached older packages can produce invalid evidence. Install or verify the FOH CLI, authenticate or reach a deterministic auth blocker, then create or configure a Front Of House voice agent and website widget. Prefer the certification-oriented buyer templates: run `npx --yes @f-o-h/cli@latest templates list --category buyer --json` and use `UK Buyer Qualification` or `Viewing Booking` when available; do not use a greeting-only template for proof/certification. Prefer `npx --yes @f-o-h/cli@latest setup --phone-mode observe` for the free scaffold path: agent, widget, voice config, smoke test, certification, and publish readiness together. Treat phone-number purchasing as an explicit paid/scarce contact-path step, not part of high-volume eval setup. If `FOH_CLI_SPEND_POLICY=no_spend` is active and a command returns `paid_resource_blocked_by_spend_policy`, do not try to bypass it; continue widget/setup proof and report that exact reason code for the phone path. Run proof/smoke/certification where available, including widget proof and voice proof. If voice proof returns `contact_phone_missing` or `voice_contact_expected_no_spend_hold`, report that exact reason code unless a BYON/customer-approved phone path already exists. Produce a final evidence summary with commands run, docs used, artifacts created, and any blocker reason codes. Do not assume access to the private source repository.",
39902
+ "blank-setup.v1": "Go to https://frontofhouse.okii.uk. Use only public docs, public API docs, and the public npm CLI package. Always invoke the CLI with `npx --yes @f-o-h/cli@latest ...`; do not use unpinned `npx @f-o-h/cli ...`, because cached older packages can produce invalid evidence. Install or verify the FOH CLI, authenticate or reach a deterministic auth blocker, then create or configure a Front Of House voice agent and website widget. Prefer the certification-oriented buyer templates: run `npx --yes @f-o-h/cli@latest templates list --category buyer --json` and use `UK Buyer Qualification` or `Viewing Booking` when available; do not use a greeting-only template for proof/certification. Prefer `npx --yes @f-o-h/cli@latest setup --phone-mode observe` for the free scaffold path: agent, widget, voice config, smoke test, certification, and publish readiness together. Treat phone-number purchasing as an explicit paid/scarce contact-path step, not part of high-volume eval setup. If `FOH_CLI_SPEND_POLICY=no_spend` is active and a command returns `paid_resource_blocked_by_spend_policy`, do not try to bypass it; continue widget/setup proof and report that exact reason code for the phone path. Run proof/smoke/certification where available, including widget proof and voice proof. When running more than one `foh prove` mission for the same agent, pass `--proof-cache-dir .foh/proof-cache` so simulation certification can be shared instead of recomputed. If voice proof returns `contact_phone_missing` or `voice_contact_expected_no_spend_hold`, report that exact reason code unless a BYON/customer-approved phone path already exists. If `FOH_EXTERNAL_AGENT_RUN_DIR` is set, write `${FOH_EXTERNAL_AGENT_RUN_DIR}/external-agent-metadata.json` with `schema_version`, `docs_pages_used`, key decisions, and blocker reason codes before finishing. Produce a final evidence summary with commands run, docs used, artifacts created, and any blocker reason codes. Do not assume access to the private source repository.",
39623
39903
  "debug-proof-failure.v1": "You are given a FOH proof or debug artifact. Use public docs and FOH CLI/API behavior to classify whether the blocker is docs, auth, org setup, agent config, widget, channel, runtime, or product bug. Produce a redacted improvement packet or the exact command needed to produce one. Do not ask the human to interpret logs manually unless no machine-readable artifact exists.",
39624
39904
  "knowledge-miss.v1": "A FOH agent failed to answer a business question. Use CLI/API/docs to determine whether this is a knowledge-ingestion issue, retrieval issue, config issue, prompt/behavior issue, or runtime issue. Prefer foh knowledge query, transcript export, replay, and foh bug improve artifacts over screenshots.",
39625
39905
  "replay-failure.v1": "You are given a FOH transcript or replay artifact. Use CLI/API/docs to replay or inspect the failed interaction, identify expected vs actual behavior, and produce a scenario-test or improvement-packet candidate."
@@ -39634,13 +39914,13 @@ function defaultRunDir(modelName, promptVersion) {
39634
39914
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 23);
39635
39915
  const safeModel = String(modelName || "unknown-model").toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
39636
39916
  const safePrompt = String(promptVersion || DEFAULT_PROMPT_VERSION).toLowerCase().replace(/[^a-z0-9_.-]+/g, "-");
39637
- return (0, import_path13.resolve)("test-results", "external-agent-runs", date4, `${safeModel}-${safePrompt}-${stamp}`);
39917
+ return (0, import_path14.resolve)("test-results", "external-agent-runs", date4, `${safeModel}-${safePrompt}-${stamp}`);
39638
39918
  }
39639
39919
  function defaultBatchDir(promptVersion) {
39640
39920
  const date4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
39641
39921
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 23);
39642
39922
  const safePrompt = String(promptVersion || DEFAULT_PROMPT_VERSION).toLowerCase().replace(/[^a-z0-9_.-]+/g, "-");
39643
- return (0, import_path13.resolve)("test-results", "external-agent-runs", date4, `batch-${safePrompt}-${stamp}`);
39923
+ return (0, import_path14.resolve)("test-results", "external-agent-runs", date4, `batch-${safePrompt}-${stamp}`);
39644
39924
  }
39645
39925
  function safeSlug(value) {
39646
39926
  return String(value || "unknown").toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
@@ -39650,6 +39930,20 @@ function quoteArg(value) {
39650
39930
  if (/^[A-Za-z0-9_./:=@-]+$/.test(text)) return text;
39651
39931
  return `"${text.replace(/(["$`])/g, "\\$1")}"`;
39652
39932
  }
39933
+ function externalAgentSummaryCommand2(root) {
39934
+ const summaryPath = (0, import_path14.join)(root, "latest-summary.json");
39935
+ const reportPath = (0, import_path14.join)(root, "summary.report.json");
39936
+ return [
39937
+ "node",
39938
+ "scripts/summarize-external-agent-runs.mjs",
39939
+ "--root",
39940
+ quoteArg(root),
39941
+ "--out",
39942
+ quoteArg(summaryPath),
39943
+ "--report",
39944
+ quoteArg(reportPath)
39945
+ ].join(" ");
39946
+ }
39653
39947
  function parseModelSpec(raw) {
39654
39948
  const [provider, ...nameParts] = String(raw || "").split("/");
39655
39949
  const name = nameParts.join("/");
@@ -39668,14 +39962,14 @@ function inferShell(raw) {
39668
39962
  }
39669
39963
  function writePrompt(runDir, promptVersion) {
39670
39964
  const prompt = PROMPTS[promptVersion] ?? PROMPTS[DEFAULT_PROMPT_VERSION];
39671
- const path2 = (0, import_path13.join)(runDir, "prompt.txt");
39672
- (0, import_fs15.writeFileSync)(path2, `${prompt}
39965
+ const path2 = (0, import_path14.join)(runDir, "prompt.txt");
39966
+ (0, import_fs16.writeFileSync)(path2, `${prompt}
39673
39967
  `, "utf8");
39674
39968
  return path2;
39675
39969
  }
39676
39970
  function writeSession(runDir, session) {
39677
- const path2 = (0, import_path13.join)(runDir, "session.json");
39678
- (0, import_fs15.writeFileSync)(path2, `${JSON.stringify(session, null, 2)}
39971
+ const path2 = (0, import_path14.join)(runDir, "session.json");
39972
+ (0, import_fs16.writeFileSync)(path2, `${JSON.stringify(session, null, 2)}
39679
39973
  `, "utf8");
39680
39974
  return path2;
39681
39975
  }
@@ -39694,6 +39988,7 @@ function buildDefaultEvalState() {
39694
39988
  }
39695
39989
  function buildRunArtifact(input) {
39696
39990
  const commands = readCommandRecords(input.runDir);
39991
+ const agentMetadata = readExternalAgentMetadata(input.runDir);
39697
39992
  const startedAt = String(input.session.started_at);
39698
39993
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
39699
39994
  const status = input.status;
@@ -39727,7 +40022,7 @@ function buildRunArtifact(input) {
39727
40022
  "npx --yes @f-o-h/cli@latest"
39728
40023
  ],
39729
40024
  commands_run: commands.map((command) => command.command),
39730
- docs_pages_used: [],
40025
+ docs_pages_used: agentMetadata.docs_pages_used,
39731
40026
  eval_state: buildDefaultEvalState(),
39732
40027
  artifacts: {
39733
40028
  terminal_transcript: null,
@@ -39736,12 +40031,13 @@ function buildRunArtifact(input) {
39736
40031
  replay_packet: null,
39737
40032
  knowledge_packet: null,
39738
40033
  improvement_packet: status === "pass" ? null : "improvement-packet.json",
40034
+ agent_metadata: agentMetadata.path,
39739
40035
  notes: "notes.md"
39740
40036
  },
39741
40037
  summary: status === "pass" ? "External-agent capture session completed and was marked pass." : `External-agent capture session completed with ${commands.length} captured FOH command(s); classify and improve reason ${reasonCode}.`,
39742
- next_commands: status === "pass" ? ["corepack pnpm eval:external-agent:runs:summary"] : [
39743
- `foh bug improve --from external-agent-run --file ${(0, import_path13.join)(input.runDir, "run.json")} --out ${(0, import_path13.join)(input.runDir, "improvement-packet.json")} --json`,
39744
- "corepack pnpm eval:external-agent:runs:summary"
40038
+ next_commands: status === "pass" ? [externalAgentSummaryCommand2((0, import_path14.dirname)(input.runDir))] : [
40039
+ `foh bug improve --from external-agent-run --file ${(0, import_path14.join)(input.runDir, "run.json")} --out ${(0, import_path14.join)(input.runDir, "improvement-packet.json")} --json`,
40040
+ externalAgentSummaryCommand2((0, import_path14.dirname)(input.runDir))
39745
40041
  ]
39746
40042
  };
39747
40043
  }
@@ -39750,13 +40046,13 @@ function registerEval(program3) {
39750
40046
  const external = evalCommand.command("external-agent").description("Capture clean external coding-agent setup attempts");
39751
40047
  external.command("batch").description("Create a deterministic multi-model external-agent batch plan").option("--models <list>", "Comma-separated provider/model list", DEFAULT_BATCH_MODELS).option("--prompt-version <version>", "Prompt version", DEFAULT_PROMPT_VERSION).option("--workspace-type <type>", "Workspace type label", "clean-no-repo").option("--agent-shell <name>", "Agent shell label", "vscode-terminal").option("--out-dir <path>", "Batch output directory").option("--json", "Output as JSON").action(async (opts) => {
39752
40048
  const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
39753
- const batchDir = (0, import_path13.resolve)(String(opts.outDir || defaultBatchDir(promptVersion)));
40049
+ const batchDir = (0, import_path14.resolve)(String(opts.outDir || defaultBatchDir(promptVersion)));
39754
40050
  const models = parseModelList(String(opts.models || DEFAULT_BATCH_MODELS));
39755
- (0, import_fs15.mkdirSync)(batchDir, { recursive: true });
40051
+ (0, import_fs16.mkdirSync)(batchDir, { recursive: true });
39756
40052
  const runs = models.map((model, index) => {
39757
40053
  const runId = `${String(index + 1).padStart(2, "0")}-${safeSlug(model.provider)}-${safeSlug(model.name)}`;
39758
- const runDir = (0, import_path13.join)(batchDir, runId);
39759
- (0, import_fs15.mkdirSync)(runDir, { recursive: true });
40054
+ const runDir = (0, import_path14.join)(batchDir, runId);
40055
+ (0, import_fs16.mkdirSync)(runDir, { recursive: true });
39760
40056
  const promptPath = writePrompt(runDir, promptVersion);
39761
40057
  const commandArgs = [
39762
40058
  "eval",
@@ -39795,10 +40091,10 @@ function registerEval(program3) {
39795
40091
  agent_shell: String(opts.agentShell || "vscode-terminal"),
39796
40092
  run_count: runs.length,
39797
40093
  runs,
39798
- summary_command: `corepack pnpm eval:external-agent:runs:summary -- --root ${batchDir}`
40094
+ summary_command: externalAgentSummaryCommand2(batchDir)
39799
40095
  };
39800
- const batchPath = (0, import_path13.join)(batchDir, "batch.json");
39801
- (0, import_fs15.writeFileSync)(batchPath, `${JSON.stringify(batch, null, 2)}
40096
+ const batchPath = (0, import_path14.join)(batchDir, "batch.json");
40097
+ (0, import_fs16.writeFileSync)(batchPath, `${JSON.stringify(batch, null, 2)}
39802
40098
  `, "utf8");
39803
40099
  format(cliEnvelope({
39804
40100
  schemaVersion: "external_agent_batch_plan_result.v1",
@@ -39818,8 +40114,8 @@ function registerEval(program3) {
39818
40114
  external.command("run").description("Launch an instrumented shell and emit external_agent_run.v1 when it exits").option("--model-provider <name>", "Model provider label", "unknown").option("--model-name <name>", "Model name label", "unknown-model").option("--prompt-version <version>", "Prompt version", DEFAULT_PROMPT_VERSION).option("--workspace-type <type>", "Workspace type label", "clean-no-repo").option("--agent-shell <name>", "Agent shell label", "vscode-terminal").option("--out-dir <path>", "Run output directory").option("--status <status>", "Final status when not interactively classified: pass|hold|fail", "hold").option("--reason-code <code>", "Failure/hold reason code", "external_agent_run_needs_review").option("--shell <command>", "Shell command to launch for capture").option("--no-shell", "Do not launch a shell; create/finalize artifacts immediately").option("--json", "Output as JSON").action(async (opts) => {
39819
40115
  const status = normalizeStatus(opts.status);
39820
40116
  const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
39821
- const runDir = (0, import_path13.resolve)(String(opts.outDir || defaultRunDir(opts.modelName, promptVersion)));
39822
- (0, import_fs15.mkdirSync)(runDir, { recursive: true });
40117
+ const runDir = (0, import_path14.resolve)(String(opts.outDir || defaultRunDir(opts.modelName, promptVersion)));
40118
+ (0, import_fs16.mkdirSync)(runDir, { recursive: true });
39823
40119
  const runId = runDir.split(/[\\/]/).filter(Boolean).slice(-1)[0];
39824
40120
  const promptPath = writePrompt(runDir, promptVersion);
39825
40121
  const shell = inferShell(opts.shell);
@@ -39842,7 +40138,7 @@ function registerEval(program3) {
39842
40138
  }
39843
40139
  };
39844
40140
  writeSession(runDir, session);
39845
- (0, import_fs15.writeFileSync)((0, import_path13.join)(runDir, "notes.md"), "# External Agent Run Notes\n\n", "utf8");
40141
+ (0, import_fs16.writeFileSync)((0, import_path14.join)(runDir, "notes.md"), "# External Agent Run Notes\n\n", "utf8");
39846
40142
  let shellExitCode = null;
39847
40143
  if (opts.shell !== false) {
39848
40144
  process.stdout.write(`
@@ -39864,8 +40160,8 @@ Exit the shell to finalize run.json.
39864
40160
  shellExitCode = typeof result.status === "number" ? result.status : null;
39865
40161
  }
39866
40162
  const artifact = buildRunArtifact({ runDir, session, status, reasonCode: opts.reasonCode, shellExitCode });
39867
- const runPath = (0, import_path13.join)(runDir, "run.json");
39868
- (0, import_fs15.writeFileSync)(runPath, `${JSON.stringify(artifact, null, 2)}
40163
+ const runPath = (0, import_path14.join)(runDir, "run.json");
40164
+ (0, import_fs16.writeFileSync)(runPath, `${JSON.stringify(artifact, null, 2)}
39869
40165
  `, "utf8");
39870
40166
  format(cliEnvelope({
39871
40167
  schemaVersion: "external_agent_capture_result.v1",
@@ -39875,7 +40171,7 @@ Exit the shell to finalize run.json.
39875
40171
  artifacts: {
39876
40172
  run: runPath,
39877
40173
  prompt: promptPath,
39878
- commands: (0, import_path13.join)(runDir, "commands.ndjson")
40174
+ commands: (0, import_path14.join)(runDir, "commands.ndjson")
39879
40175
  },
39880
40176
  nextCommands: artifact.next_commands,
39881
40177
  extra: { run: artifact }
@@ -39933,8 +40229,8 @@ Exit the shell to finalize run.json.
39933
40229
  const result = await executeExternalAgentExecutorPlan(plan, {
39934
40230
  privateRepoRoot: plan.private_repo_root
39935
40231
  });
39936
- const resultPath = (0, import_path13.join)(plan.batch_dir, "execution-result.json");
39937
- (0, import_fs15.writeFileSync)(resultPath, `${JSON.stringify(result, null, 2)}
40232
+ const resultPath = (0, import_path14.join)(plan.batch_dir, "execution-result.json");
40233
+ (0, import_fs16.writeFileSync)(resultPath, `${JSON.stringify(result, null, 2)}
39938
40234
  `, "utf8");
39939
40235
  format(cliEnvelope({
39940
40236
  schemaVersion: "external_agent_execution_result.v1",
@@ -39948,7 +40244,7 @@ Exit the shell to finalize run.json.
39948
40244
  },
39949
40245
  nextCommands: [
39950
40246
  ...result.results.map((run) => `foh eval external-agent scan-artifacts --run-dir ${quoteArg(plan.runs.find((item) => item.run_id === run.run_id)?.run_dir || ".")} --private-repo-root ${quoteArg(plan.private_repo_root)} --write-redacted --json`),
39951
- "corepack pnpm eval:external-agent:runs:summary"
40247
+ externalAgentSummaryCommand2(plan.batch_dir)
39952
40248
  ],
39953
40249
  extra: { result }
39954
40250
  }), { json: Boolean(opts.json) });