@f-o-h/cli 0.1.41 → 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,19 +32789,19 @@ 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
  }
32801
32801
  };
32802
32802
 
32803
32803
  // src/lib/cli-version.ts
32804
- var CLI_VERSION = "0.1.41";
32804
+ var CLI_VERSION = "0.1.42";
32805
32805
 
32806
32806
  // src/commands/mcp-serve.ts
32807
32807
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -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"]
@@ -39123,6 +39392,14 @@ function normalizeCodexSandboxBackend(value) {
39123
39392
  `Unsupported Codex sandbox backend: ${value}. Use default or legacy-landlock.`
39124
39393
  );
39125
39394
  }
39395
+ function normalizeCodexSandboxMode(value) {
39396
+ const normalized = (value || "workspace-write").trim().toLowerCase();
39397
+ if (normalized === "workspace-write" || normalized === "danger-full-access") return normalized;
39398
+ throw new ExternalAgentExecutorError(
39399
+ "invalid_codex_sandbox_mode",
39400
+ `Unsupported Codex sandbox mode: ${value}. Use workspace-write or danger-full-access.`
39401
+ );
39402
+ }
39126
39403
  function codexConfigArgs(input) {
39127
39404
  const args = [];
39128
39405
  if (input.backend === "legacy-landlock") {
@@ -39142,13 +39419,13 @@ function safeRunId(value) {
39142
39419
  return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "run";
39143
39420
  }
39144
39421
  function resolveWorkspaceRoot(input) {
39145
- if (input.workspaceRoot) return (0, import_path12.resolve)(input.workspaceRoot);
39146
- const batchStem = (0, import_path12.basename)((0, import_path12.resolve)(input.batchPath)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
39147
- const repoStem = (0, import_path12.basename)((0, import_path12.resolve)(input.privateRepoRoot)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
39148
- 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);
39149
39426
  }
39150
39427
  function promptVersionFromPath(promptPath) {
39151
- const raw = (0, import_fs14.readFileSync)(promptPath, "utf8");
39428
+ const raw = (0, import_fs15.readFileSync)(promptPath, "utf8");
39152
39429
  if (raw.includes("Do not assume access to the private source repository")) return "blank-setup.v1";
39153
39430
  return "unknown";
39154
39431
  }
@@ -39157,12 +39434,13 @@ function createExternalAgentExecutorPlan(options) {
39157
39434
  if (runner !== "codex") {
39158
39435
  throw new ExternalAgentExecutorError("unsupported_external_agent_runner", `Unsupported runner: ${runner}`);
39159
39436
  }
39160
- const batchPath = (0, import_path12.resolve)(options.batchPath);
39437
+ const batchPath = (0, import_path13.resolve)(options.batchPath);
39161
39438
  const batch = readBatch(batchPath);
39162
39439
  const runnerProbe = validateCodexRunner(options);
39163
39440
  const codexSandboxBackend = normalizeCodexSandboxBackend(options.codexSandboxBackend);
39441
+ const codexSandboxMode = normalizeCodexSandboxMode(options.codexSandboxMode);
39164
39442
  const codexNetworkAccess = options.codexNetworkAccess === true;
39165
- 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());
39166
39444
  const workspaceRoot = resolveWorkspaceRoot({ batchPath, workspaceRoot: options.workspaceRoot, privateRepoRoot });
39167
39445
  if (isPathInside(workspaceRoot, privateRepoRoot)) {
39168
39446
  throw new ExternalAgentExecutorError(
@@ -39170,17 +39448,17 @@ function createExternalAgentExecutorPlan(options) {
39170
39448
  `Workspace root must be outside the private repository. workspace=${workspaceRoot} repo=${privateRepoRoot}`
39171
39449
  );
39172
39450
  }
39173
- (0, import_fs14.mkdirSync)(workspaceRoot, { recursive: true });
39174
- 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, "..")));
39175
39453
  const timeoutMinutes = Number.isFinite(options.timeoutMinutes) && Number(options.timeoutMinutes) > 0 ? Number(options.timeoutMinutes) : 30;
39176
39454
  const runs = batch.runs.map((run) => {
39177
39455
  const runId = safeRunId(requireString(run.run_id, "runs[].run_id"));
39178
- const runDir = (0, import_path12.resolve)(requireString(run.run_dir, `runs[${runId}].run_dir`));
39179
- const promptPath = (0, import_path12.resolve)(requireString(run.prompt_path, `runs[${runId}].prompt_path`));
39180
- const workspaceDir = (0, import_path12.join)(workspaceRoot, runId);
39181
- (0, import_fs14.mkdirSync)(workspaceDir, { recursive: true });
39182
- (0, import_fs14.writeFileSync)(
39183
- (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"),
39184
39462
  [
39185
39463
  "# FOH External-Agent Workspace",
39186
39464
  "",
@@ -39197,11 +39475,11 @@ function createExternalAgentExecutorPlan(options) {
39197
39475
  promptVersion: promptVersionFromPath(promptPath)
39198
39476
  });
39199
39477
  const promptVersion = String(env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] || "unknown");
39200
- const jsonlPath = (0, import_path12.join)(runDir, "codex-exec.jsonl");
39201
- const lastMessagePath = (0, import_path12.join)(runDir, "codex-last-message.md");
39202
- const stderrPath = (0, import_path12.join)(runDir, "codex-stderr.txt");
39203
- const runPath = (0, import_path12.join)(runDir, "run.json");
39204
- 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");
39205
39483
  const args = [
39206
39484
  ...runnerProbe.globalArgs,
39207
39485
  "exec",
@@ -39212,7 +39490,7 @@ function createExternalAgentExecutorPlan(options) {
39212
39490
  "--ephemeral",
39213
39491
  "--ignore-rules",
39214
39492
  "--sandbox",
39215
- "workspace-write",
39493
+ codexSandboxMode,
39216
39494
  ...runnerProbe.execArgs,
39217
39495
  "--json",
39218
39496
  "--output-last-message",
@@ -39257,9 +39535,19 @@ function createExternalAgentExecutorPlan(options) {
39257
39535
  denied_env_names: [...CODEX_EXECUTOR_DENIED_ENV_NAMES],
39258
39536
  runner_probe: {
39259
39537
  binary_checked: runnerProbe.binaryChecked,
39260
- 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
39261
39548
  },
39262
39549
  codex_automation_mode: runnerProbe.automationMode,
39550
+ codex_sandbox_mode: codexSandboxMode,
39263
39551
  codex_sandbox_backend: codexSandboxBackend,
39264
39552
  codex_network_access: codexNetworkAccess
39265
39553
  },
@@ -39267,55 +39555,55 @@ function createExternalAgentExecutorPlan(options) {
39267
39555
  };
39268
39556
  }
39269
39557
  function writeExternalAgentExecutorPlan(plan) {
39270
- const path2 = (0, import_path12.join)(plan.batch_dir, "executor-plan.json");
39271
- (0, import_fs14.mkdirSync)(plan.batch_dir, { recursive: true });
39272
- (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)}
39273
39561
  `, "utf8");
39274
39562
  return path2;
39275
39563
  }
39276
39564
  function proofArtifactPasses(runDir) {
39277
- const proofPath = (0, import_path12.join)(runDir, "proof.json");
39278
- 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;
39279
39567
  try {
39280
- const parsed = JSON.parse((0, import_fs14.readFileSync)(proofPath, "utf8"));
39568
+ const parsed = JSON.parse((0, import_fs15.readFileSync)(proofPath, "utf8"));
39281
39569
  return parsed.ok === true || parsed.status === "pass" || parsed.status === "passed";
39282
39570
  } catch {
39283
39571
  return false;
39284
39572
  }
39285
39573
  }
39286
39574
  function readIfExists(path2) {
39287
- 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") : "";
39288
39576
  }
39289
39577
  function redactArtifactFile(path2, input = {}) {
39290
- if (!(0, import_fs14.existsSync)(path2)) return;
39291
- 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");
39292
39580
  const redacted = redactExternalAgentArtifactText(original, input);
39293
- if (redacted !== original) (0, import_fs14.writeFileSync)(path2, redacted, "utf8");
39581
+ if (redacted !== original) (0, import_fs15.writeFileSync)(path2, redacted, "utf8");
39294
39582
  }
39295
39583
  function redactOutputArtifacts(run, input = {}) {
39296
39584
  redactArtifactFile(run.outputs.jsonl, input);
39297
39585
  redactArtifactFile(run.outputs.last_message, input);
39298
39586
  redactArtifactFile(run.outputs.stderr, input);
39299
- redactArtifactFile((0, import_path12.join)(run.run_dir, "commands.ndjson"), input);
39300
- if (!(0, import_fs14.existsSync)(run.run_dir)) return;
39301
- 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)) {
39302
39590
  if (name.startsWith("command-output-cmd_") && !name.endsWith(".redacted")) {
39303
- redactArtifactFile((0, import_path12.join)(run.run_dir, name), input);
39591
+ redactArtifactFile((0, import_path13.join)(run.run_dir, name), input);
39304
39592
  }
39305
39593
  }
39306
39594
  }
39307
39595
  function copyCommandCaptureArtifacts(input) {
39308
- const commandLog = (0, import_path12.join)(input.captureDir, "commands.ndjson");
39309
- if (!(0, import_fs14.existsSync)(commandLog)) return;
39310
- (0, import_fs14.writeFileSync)((0, import_path12.join)(input.runDir, "commands.ndjson"), (0, import_fs14.readFileSync)(commandLog, "utf8"), "utf8");
39311
- 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)) {
39312
39600
  if (name.startsWith("command-output-cmd_")) {
39313
- (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));
39314
39602
  }
39315
39603
  }
39316
39604
  }
39317
39605
  function relativeArtifactName(path2) {
39318
- return (0, import_path12.basename)(path2);
39606
+ return (0, import_path13.basename)(path2);
39319
39607
  }
39320
39608
  function classifyRun(input) {
39321
39609
  if (input.timedOut) return { status: "hold", reasonCode: "codex_runner_timeout" };
@@ -39367,7 +39655,7 @@ ${stderr}`;
39367
39655
  if (/(?:blocked|rejected|declined) by policy|EXEC_POLICY_BLOCKED|command execution was rejected|shell commands were rejected/i.test(combined)) {
39368
39656
  return { status: "hold", reasonCode: "codex_exec_policy_blocked" };
39369
39657
  }
39370
- if (/bwrap:.*(?:RTM_NEWADDR|Operation not permitted)|bubblewrap.*(?:RTM_NEWADDR|Operation not permitted)|Failed RTM_NEWADDR|ENV_SANDBOX_EXEC_BLOCKED/i.test(combined)) {
39658
+ 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)) {
39371
39659
  return { status: "hold", reasonCode: "codex_sandbox_exec_blocked" };
39372
39660
  }
39373
39661
  if (/ENV_NETWORK_DNS_BLOCK|Could not resolve host|npm ping.*timeout|NO_EXECUTABLE_INSTALL/i.test(combined)) {
@@ -39409,6 +39697,7 @@ ${stderr}`;
39409
39697
  }
39410
39698
  function buildExecutedRunArtifact(input) {
39411
39699
  const commands = readCommandRecords(input.run.run_dir);
39700
+ const agentMetadata = readExternalAgentMetadata(input.run.run_dir);
39412
39701
  return {
39413
39702
  schema_version: "external_agent_run.v1",
39414
39703
  run_id: input.run.run_id,
@@ -39440,7 +39729,7 @@ function buildExecutedRunArtifact(input) {
39440
39729
  "npx --yes @f-o-h/cli@latest"
39441
39730
  ],
39442
39731
  commands_run: commands.map((command) => command.command),
39443
- docs_pages_used: [],
39732
+ docs_pages_used: agentMetadata.docs_pages_used,
39444
39733
  eval_state: {
39445
39734
  org_reuse_expected: true,
39446
39735
  agent_reuse_expected: true,
@@ -39454,21 +39743,22 @@ function buildExecutedRunArtifact(input) {
39454
39743
  },
39455
39744
  artifacts: {
39456
39745
  terminal_transcript: relativeArtifactName(input.run.outputs.jsonl),
39457
- command_log: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "commands.ndjson")) ? "commands.ndjson" : null,
39458
- proof_bundle: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "proof.json")) ? "proof.json" : null,
39459
- replay_packet: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "replay.json")) ? "replay.json" : null,
39460
- 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,
39461
39750
  improvement_packet: input.status === "pass" ? null : "improvement-packet.json",
39462
- 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,
39463
39753
  codex_last_message: relativeArtifactName(input.run.outputs.last_message),
39464
39754
  codex_stderr: relativeArtifactName(input.run.outputs.stderr),
39465
39755
  artifact_safety: relativeArtifactName(input.run.outputs.artifact_safety)
39466
39756
  },
39467
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}.`,
39468
- 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))] : [
39469
39759
  "foh eval external-agent scan-artifacts --run-dir <run_dir> --private-repo-root <private_repo_root> --write-redacted --json",
39470
39760
  "foh bug improve --from external-agent-run --file <run_dir>/run.json --out <run_dir>/improvement-packet.json --json",
39471
- "corepack pnpm eval:external-agent:runs:summary"
39761
+ externalAgentSummaryCommand((0, import_path13.dirname)(input.run.run_dir))
39472
39762
  ]
39473
39763
  };
39474
39764
  }
@@ -39483,8 +39773,8 @@ function spawnCodex(input) {
39483
39773
  stdio: ["pipe", "pipe", "pipe"],
39484
39774
  windowsHide: true
39485
39775
  });
39486
- const stdout = (0, import_fs14.createWriteStream)(input.stdoutPath, { flags: "w" });
39487
- 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" });
39488
39778
  child.stdout.pipe(stdout);
39489
39779
  child.stderr.pipe(stderr);
39490
39780
  child.stdin.end(input.prompt);
@@ -39521,8 +39811,8 @@ function spawnCodex(input) {
39521
39811
  }
39522
39812
  function buildCommandInvocation(command, args) {
39523
39813
  if (process.platform === "win32" && command.toLowerCase().endsWith(".cmd")) {
39524
- const codexEntrypoint = (0, import_path12.join)((0, import_path12.dirname)(command), "node_modules", "@openai", "codex", "bin", "codex.js");
39525
- 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] };
39526
39816
  }
39527
39817
  return { command, args };
39528
39818
  }
@@ -39532,8 +39822,8 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
39532
39822
  const runnerCommand = options.runnerCommand || resolveCodexExecutionCommand();
39533
39823
  for (const run of plan.runs) {
39534
39824
  const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
39535
- const commandCaptureDir = (0, import_path12.join)(run.workspace_dir, ".foh-capture");
39536
- (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 });
39537
39827
  const env = buildCodexExecutorEnv({
39538
39828
  sourceEnv: options.env,
39539
39829
  runDir: commandCaptureDir,
@@ -39544,7 +39834,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
39544
39834
  args: run.args,
39545
39835
  cwd: run.workspace_dir,
39546
39836
  env,
39547
- prompt: (0, import_fs14.readFileSync)(run.prompt_path, "utf8"),
39837
+ prompt: (0, import_fs15.readFileSync)(run.prompt_path, "utf8"),
39548
39838
  stdoutPath: run.outputs.jsonl,
39549
39839
  stderrPath: run.outputs.stderr,
39550
39840
  timeoutMs: plan.timeout_minutes * 60 * 1e3
@@ -39557,7 +39847,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
39557
39847
  privateRepoRoot,
39558
39848
  writeRedacted: true
39559
39849
  });
39560
- (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)}
39561
39851
  `, "utf8");
39562
39852
  const runEndedAt = (/* @__PURE__ */ new Date()).toISOString();
39563
39853
  const classification = classifyRun({
@@ -39576,7 +39866,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
39576
39866
  timedOut: spawned.timedOut,
39577
39867
  durationMs: spawned.durationMs
39578
39868
  });
39579
- (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)}
39580
39870
  `, "utf8");
39581
39871
  results.push({
39582
39872
  run_id: run.run_id,
@@ -39609,7 +39899,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
39609
39899
  var DEFAULT_PROMPT_VERSION = "blank-setup.v1";
39610
39900
  var DEFAULT_BATCH_MODELS = "openai/codex,anthropic/claude,cursor/agent";
39611
39901
  var PROMPTS = {
39612
- "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.",
39613
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.",
39614
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.",
39615
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."
@@ -39624,13 +39914,13 @@ function defaultRunDir(modelName, promptVersion) {
39624
39914
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 23);
39625
39915
  const safeModel = String(modelName || "unknown-model").toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
39626
39916
  const safePrompt = String(promptVersion || DEFAULT_PROMPT_VERSION).toLowerCase().replace(/[^a-z0-9_.-]+/g, "-");
39627
- 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}`);
39628
39918
  }
39629
39919
  function defaultBatchDir(promptVersion) {
39630
39920
  const date4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
39631
39921
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 23);
39632
39922
  const safePrompt = String(promptVersion || DEFAULT_PROMPT_VERSION).toLowerCase().replace(/[^a-z0-9_.-]+/g, "-");
39633
- 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}`);
39634
39924
  }
39635
39925
  function safeSlug(value) {
39636
39926
  return String(value || "unknown").toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
@@ -39640,6 +39930,20 @@ function quoteArg(value) {
39640
39930
  if (/^[A-Za-z0-9_./:=@-]+$/.test(text)) return text;
39641
39931
  return `"${text.replace(/(["$`])/g, "\\$1")}"`;
39642
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
+ }
39643
39947
  function parseModelSpec(raw) {
39644
39948
  const [provider, ...nameParts] = String(raw || "").split("/");
39645
39949
  const name = nameParts.join("/");
@@ -39658,14 +39962,14 @@ function inferShell(raw) {
39658
39962
  }
39659
39963
  function writePrompt(runDir, promptVersion) {
39660
39964
  const prompt = PROMPTS[promptVersion] ?? PROMPTS[DEFAULT_PROMPT_VERSION];
39661
- const path2 = (0, import_path13.join)(runDir, "prompt.txt");
39662
- (0, import_fs15.writeFileSync)(path2, `${prompt}
39965
+ const path2 = (0, import_path14.join)(runDir, "prompt.txt");
39966
+ (0, import_fs16.writeFileSync)(path2, `${prompt}
39663
39967
  `, "utf8");
39664
39968
  return path2;
39665
39969
  }
39666
39970
  function writeSession(runDir, session) {
39667
- const path2 = (0, import_path13.join)(runDir, "session.json");
39668
- (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)}
39669
39973
  `, "utf8");
39670
39974
  return path2;
39671
39975
  }
@@ -39684,6 +39988,7 @@ function buildDefaultEvalState() {
39684
39988
  }
39685
39989
  function buildRunArtifact(input) {
39686
39990
  const commands = readCommandRecords(input.runDir);
39991
+ const agentMetadata = readExternalAgentMetadata(input.runDir);
39687
39992
  const startedAt = String(input.session.started_at);
39688
39993
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
39689
39994
  const status = input.status;
@@ -39717,7 +40022,7 @@ function buildRunArtifact(input) {
39717
40022
  "npx --yes @f-o-h/cli@latest"
39718
40023
  ],
39719
40024
  commands_run: commands.map((command) => command.command),
39720
- docs_pages_used: [],
40025
+ docs_pages_used: agentMetadata.docs_pages_used,
39721
40026
  eval_state: buildDefaultEvalState(),
39722
40027
  artifacts: {
39723
40028
  terminal_transcript: null,
@@ -39726,12 +40031,13 @@ function buildRunArtifact(input) {
39726
40031
  replay_packet: null,
39727
40032
  knowledge_packet: null,
39728
40033
  improvement_packet: status === "pass" ? null : "improvement-packet.json",
40034
+ agent_metadata: agentMetadata.path,
39729
40035
  notes: "notes.md"
39730
40036
  },
39731
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}.`,
39732
- next_commands: status === "pass" ? ["corepack pnpm eval:external-agent:runs:summary"] : [
39733
- `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`,
39734
- "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))
39735
40041
  ]
39736
40042
  };
39737
40043
  }
@@ -39740,13 +40046,13 @@ function registerEval(program3) {
39740
40046
  const external = evalCommand.command("external-agent").description("Capture clean external coding-agent setup attempts");
39741
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) => {
39742
40048
  const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
39743
- const batchDir = (0, import_path13.resolve)(String(opts.outDir || defaultBatchDir(promptVersion)));
40049
+ const batchDir = (0, import_path14.resolve)(String(opts.outDir || defaultBatchDir(promptVersion)));
39744
40050
  const models = parseModelList(String(opts.models || DEFAULT_BATCH_MODELS));
39745
- (0, import_fs15.mkdirSync)(batchDir, { recursive: true });
40051
+ (0, import_fs16.mkdirSync)(batchDir, { recursive: true });
39746
40052
  const runs = models.map((model, index) => {
39747
40053
  const runId = `${String(index + 1).padStart(2, "0")}-${safeSlug(model.provider)}-${safeSlug(model.name)}`;
39748
- const runDir = (0, import_path13.join)(batchDir, runId);
39749
- (0, import_fs15.mkdirSync)(runDir, { recursive: true });
40054
+ const runDir = (0, import_path14.join)(batchDir, runId);
40055
+ (0, import_fs16.mkdirSync)(runDir, { recursive: true });
39750
40056
  const promptPath = writePrompt(runDir, promptVersion);
39751
40057
  const commandArgs = [
39752
40058
  "eval",
@@ -39785,10 +40091,10 @@ function registerEval(program3) {
39785
40091
  agent_shell: String(opts.agentShell || "vscode-terminal"),
39786
40092
  run_count: runs.length,
39787
40093
  runs,
39788
- summary_command: `corepack pnpm eval:external-agent:runs:summary -- --root ${batchDir}`
40094
+ summary_command: externalAgentSummaryCommand2(batchDir)
39789
40095
  };
39790
- const batchPath = (0, import_path13.join)(batchDir, "batch.json");
39791
- (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)}
39792
40098
  `, "utf8");
39793
40099
  format(cliEnvelope({
39794
40100
  schemaVersion: "external_agent_batch_plan_result.v1",
@@ -39808,8 +40114,8 @@ function registerEval(program3) {
39808
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) => {
39809
40115
  const status = normalizeStatus(opts.status);
39810
40116
  const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
39811
- const runDir = (0, import_path13.resolve)(String(opts.outDir || defaultRunDir(opts.modelName, promptVersion)));
39812
- (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 });
39813
40119
  const runId = runDir.split(/[\\/]/).filter(Boolean).slice(-1)[0];
39814
40120
  const promptPath = writePrompt(runDir, promptVersion);
39815
40121
  const shell = inferShell(opts.shell);
@@ -39832,7 +40138,7 @@ function registerEval(program3) {
39832
40138
  }
39833
40139
  };
39834
40140
  writeSession(runDir, session);
39835
- (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");
39836
40142
  let shellExitCode = null;
39837
40143
  if (opts.shell !== false) {
39838
40144
  process.stdout.write(`
@@ -39854,8 +40160,8 @@ Exit the shell to finalize run.json.
39854
40160
  shellExitCode = typeof result.status === "number" ? result.status : null;
39855
40161
  }
39856
40162
  const artifact = buildRunArtifact({ runDir, session, status, reasonCode: opts.reasonCode, shellExitCode });
39857
- const runPath = (0, import_path13.join)(runDir, "run.json");
39858
- (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)}
39859
40165
  `, "utf8");
39860
40166
  format(cliEnvelope({
39861
40167
  schemaVersion: "external_agent_capture_result.v1",
@@ -39865,7 +40171,7 @@ Exit the shell to finalize run.json.
39865
40171
  artifacts: {
39866
40172
  run: runPath,
39867
40173
  prompt: promptPath,
39868
- commands: (0, import_path13.join)(runDir, "commands.ndjson")
40174
+ commands: (0, import_path14.join)(runDir, "commands.ndjson")
39869
40175
  },
39870
40176
  nextCommands: artifact.next_commands,
39871
40177
  extra: { run: artifact }
@@ -39890,7 +40196,7 @@ Exit the shell to finalize run.json.
39890
40196
  }), { json: Boolean(opts.json) });
39891
40197
  if (!report.ok) process.exitCode = 1;
39892
40198
  });
39893
- external.command("execute").description("Create a guarded dry-run executor plan for programmable external-agent runners").requiredOption("--batch <path>", "Path to external_agent_batch_plan.v1 batch.json").option("--runner <runner>", "Runner implementation", "codex").option("--workspace-root <path>", "Clean executor workspace root; must be outside the private repo").option("--private-repo-root <path>", "Private repository root to guard against").option("--timeout-minutes <minutes>", "Per-run timeout planned for future execution", "30").option("--codex-sandbox-backend <backend>", "Codex sandbox backend override: default|legacy-landlock", "default").option("--codex-network-access", "Allow Codex workspace-write sandbox network access for public docs/npm probes").option("--skip-runner-probe", "Skip local runner binary/help probing").option("--dry-run", "Write the executor plan without launching the runner", true).option("--live", "Launch one controlled external-agent run after writing the guarded plan").option("--json", "Output as JSON").action(async (opts) => {
40199
+ external.command("execute").description("Create a guarded dry-run executor plan for programmable external-agent runners").requiredOption("--batch <path>", "Path to external_agent_batch_plan.v1 batch.json").option("--runner <runner>", "Runner implementation", "codex").option("--workspace-root <path>", "Clean executor workspace root; must be outside the private repo").option("--private-repo-root <path>", "Private repository root to guard against").option("--timeout-minutes <minutes>", "Per-run timeout planned for future execution", "30").option("--codex-sandbox-backend <backend>", "Codex sandbox backend override: default|legacy-landlock", "default").option("--codex-sandbox-mode <mode>", "Codex sandbox mode: workspace-write|danger-full-access", "workspace-write").option("--codex-network-access", "Allow Codex workspace-write sandbox network access for public docs/npm probes").option("--skip-runner-probe", "Skip local runner binary/help probing").option("--dry-run", "Write the executor plan without launching the runner", true).option("--live", "Launch one controlled external-agent run after writing the guarded plan").option("--json", "Output as JSON").action(async (opts) => {
39894
40200
  try {
39895
40201
  const plan = createExternalAgentExecutorPlan({
39896
40202
  batchPath: String(opts.batch),
@@ -39899,6 +40205,7 @@ Exit the shell to finalize run.json.
39899
40205
  privateRepoRoot: opts.privateRepoRoot ? String(opts.privateRepoRoot) : void 0,
39900
40206
  timeoutMinutes: Number(opts.timeoutMinutes || 30),
39901
40207
  codexSandboxBackend: String(opts.codexSandboxBackend || "default"),
40208
+ codexSandboxMode: String(opts.codexSandboxMode || "workspace-write"),
39902
40209
  codexNetworkAccess: Boolean(opts.codexNetworkAccess),
39903
40210
  skipRunnerProbe: Boolean(opts.skipRunnerProbe),
39904
40211
  cwd: process.cwd()
@@ -39922,8 +40229,8 @@ Exit the shell to finalize run.json.
39922
40229
  const result = await executeExternalAgentExecutorPlan(plan, {
39923
40230
  privateRepoRoot: plan.private_repo_root
39924
40231
  });
39925
- const resultPath = (0, import_path13.join)(plan.batch_dir, "execution-result.json");
39926
- (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)}
39927
40234
  `, "utf8");
39928
40235
  format(cliEnvelope({
39929
40236
  schemaVersion: "external_agent_execution_result.v1",
@@ -39937,7 +40244,7 @@ Exit the shell to finalize run.json.
39937
40244
  },
39938
40245
  nextCommands: [
39939
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`),
39940
- "corepack pnpm eval:external-agent:runs:summary"
40247
+ externalAgentSummaryCommand2(plan.batch_dir)
39941
40248
  ],
39942
40249
  extra: { result }
39943
40250
  }), { json: Boolean(opts.json) });