@f-o-h/cli 0.1.6 → 0.1.8

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/README.md CHANGED
@@ -4,7 +4,7 @@ AI-operator provisioning CLI for Front Of House.
4
4
 
5
5
  Public mirror: https://github.com/iiko38/front-of-house-cli
6
6
 
7
- Current published baseline: `@f-o-h/cli@0.1.5`
7
+ Current published baseline: `@f-o-h/cli@0.1.8`
8
8
 
9
9
  This mirror is a generated release artifact. The private product monorepo is not
10
10
  published here, and no open-source license is granted unless stated separately.
@@ -15,10 +15,12 @@ Package-local examples and schemas ship with the npm artifact:
15
15
  - `examples/proof-report.example.json`
16
16
  - `examples/transcript-export.example.json`
17
17
  - `examples/improvement-packet.example.json`
18
+ - `examples/external-agent-run.example.json`
18
19
  - `schemas/cli-envelope.schema.json`
19
20
  - `schemas/scenario-suite.schema.json`
20
21
  - `schemas/transcript-export.schema.json`
21
22
  - `schemas/improvement-packet.schema.json`
23
+ - `schemas/external-agent-run.schema.json`
22
24
 
23
25
  ## Install
24
26
 
@@ -81,6 +83,22 @@ automation when holds should fail the command, and `--mission voice` or
81
83
 
82
84
  The CLI defaults to the production API at `https://api.frontofhouse.okii.uk`.
83
85
 
86
+ ## External-Agent Eval Capture
87
+
88
+ Use this when testing whether a clean coding agent can start from public docs
89
+ and the public npm package without private repo context:
90
+
91
+ ```bash
92
+ foh eval external-agent run \
93
+ --model-provider openai \
94
+ --model-name codex \
95
+ --prompt-version blank-setup.v1
96
+ ```
97
+
98
+ The command writes a versioned prompt, launches an instrumented shell, captures
99
+ FOH CLI commands into `commands.ndjson`, and finalizes `run.json` as an
100
+ `external_agent_run.v1` artifact when the shell exits.
101
+
84
102
  ## Local Scenario Suites
85
103
 
86
104
  `foh test run --suite <file>` runs deterministic widget-runtime checks for a
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 = resolve8.call(this, root, ref);
6049
+ let _sch = resolve10.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 resolve8(root, ref) {
6076
+ function resolve10(root, ref) {
6077
6077
  let sch;
6078
6078
  while (typeof (sch = this.refs[ref]) == "string")
6079
6079
  ref = sch;
@@ -6648,7 +6648,7 @@ var require_fast_uri = __commonJS({
6648
6648
  }
6649
6649
  return uri;
6650
6650
  }
6651
- function resolve8(baseURI, relativeURI, options) {
6651
+ function resolve10(baseURI, relativeURI, options) {
6652
6652
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
6653
6653
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
6654
6654
  schemelessOptions.skipEscape = true;
@@ -6875,7 +6875,7 @@ var require_fast_uri = __commonJS({
6875
6875
  var fastUri = {
6876
6876
  SCHEMES,
6877
6877
  normalize,
6878
- resolve: resolve8,
6878
+ resolve: resolve10,
6879
6879
  resolveComponent,
6880
6880
  equal,
6881
6881
  serialize,
@@ -10105,21 +10105,21 @@ async function promptLine(label, {
10105
10105
  allowEmpty = false,
10106
10106
  defaultValue
10107
10107
  } = {}) {
10108
- return await new Promise((resolve8) => {
10108
+ return await new Promise((resolve10) => {
10109
10109
  const suffix = defaultValue ? ` [${defaultValue}]` : "";
10110
10110
  const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout, terminal: true });
10111
10111
  rl.question(`${label}${suffix}: `, (answer) => {
10112
10112
  rl.close();
10113
10113
  const value = String(answer ?? "").trim();
10114
10114
  if (!value && typeof defaultValue === "string") {
10115
- resolve8(defaultValue);
10115
+ resolve10(defaultValue);
10116
10116
  return;
10117
10117
  }
10118
10118
  if (!value && !allowEmpty) {
10119
- resolve8("");
10119
+ resolve10("");
10120
10120
  return;
10121
10121
  }
10122
- resolve8(value);
10122
+ resolve10(value);
10123
10123
  });
10124
10124
  });
10125
10125
  }
@@ -10127,7 +10127,7 @@ async function promptSecret(label) {
10127
10127
  if (!process.stdin.isTTY || !process.stdout.isTTY || typeof process.stdin.setRawMode !== "function") {
10128
10128
  return await promptLine(label);
10129
10129
  }
10130
- return await new Promise((resolve8) => {
10130
+ return await new Promise((resolve10) => {
10131
10131
  const stdin = process.stdin;
10132
10132
  const stdout = process.stdout;
10133
10133
  const wasRaw = Boolean(stdin.isRaw);
@@ -10141,7 +10141,7 @@ async function promptSecret(label) {
10141
10141
  const finish = () => {
10142
10142
  cleanup();
10143
10143
  stdout.write("\n");
10144
- resolve8(value);
10144
+ resolve10(value);
10145
10145
  };
10146
10146
  const onData = (chunk) => {
10147
10147
  const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
@@ -10150,7 +10150,7 @@ async function promptSecret(label) {
10150
10150
  cleanup();
10151
10151
  process.exitCode = 130;
10152
10152
  stdout.write("\n");
10153
- return resolve8("");
10153
+ return resolve10("");
10154
10154
  }
10155
10155
  if (char === "\r" || char === "\n") {
10156
10156
  finish();
@@ -10419,7 +10419,7 @@ async function storeAuthenticatedSession(params) {
10419
10419
  return output;
10420
10420
  }
10421
10421
  function sleep(ms) {
10422
- return new Promise((resolve8) => setTimeout(resolve8, ms));
10422
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
10423
10423
  }
10424
10424
  async function runDeviceLogin(opts) {
10425
10425
  const jsonMode = Boolean(opts.json);
@@ -10957,7 +10957,7 @@ async function pollUntil(check2, opts) {
10957
10957
  }
10958
10958
  }
10959
10959
  function sleep2(ms) {
10960
- return new Promise((resolve8) => setTimeout(resolve8, ms));
10960
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
10961
10961
  }
10962
10962
 
10963
10963
  // src/commands/compliance.ts
@@ -13995,8 +13995,8 @@ function registerAgentGuardrailCommands(agent) {
13995
13995
  try {
13996
13996
  rule = JSON.parse(opts.rule);
13997
13997
  } catch {
13998
- const { readFileSync: readFileSync9 } = await import("fs");
13999
- rule = JSON.parse(readFileSync9(opts.rule, "utf-8"));
13998
+ const { readFileSync: readFileSync10 } = await import("fs");
13999
+ rule = JSON.parse(readFileSync10(opts.rule, "utf-8"));
14000
14000
  }
14001
14001
  const data = await apiFetch(`/v1/console/agents/${opts.agent}/guardrails`, {
14002
14002
  method: "POST",
@@ -14596,9 +14596,9 @@ function registerAgent(program3) {
14596
14596
  process.stdout.write(yaml);
14597
14597
  return;
14598
14598
  }
14599
- const { writeFileSync: writeFileSync6 } = await import("fs");
14599
+ const { writeFileSync: writeFileSync7 } = await import("fs");
14600
14600
  const outputPath = opts.output ?? "tenant.yaml";
14601
- writeFileSync6(
14601
+ writeFileSync7(
14602
14602
  outputPath,
14603
14603
  `# tenant.yaml - Front Of House agent manifest
14604
14604
  # Edit this file and run: foh plan tenant.yaml
@@ -16033,11 +16033,11 @@ function registerVoice(program3) {
16033
16033
  }
16034
16034
  const outputPath = String(opts.out || `foh-voice-preview-${provider}-${voiceId}.mp3`).trim();
16035
16035
  const audio = Buffer.from(await res.arrayBuffer());
16036
- const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync6 } = await import("fs");
16037
- const { dirname: dirname5, resolve: resolve8 } = await import("path");
16038
- const absolutePath = resolve8(outputPath);
16039
- mkdirSync4(dirname5(absolutePath), { recursive: true });
16040
- writeFileSync6(absolutePath, audio);
16036
+ const { mkdirSync: mkdirSync6, writeFileSync: writeFileSync7 } = await import("fs");
16037
+ const { dirname: dirname5, resolve: resolve10 } = await import("path");
16038
+ const absolutePath = resolve10(outputPath);
16039
+ mkdirSync6(dirname5(absolutePath), { recursive: true });
16040
+ writeFileSync7(absolutePath, audio);
16041
16041
  format({
16042
16042
  status: "ok",
16043
16043
  provider,
@@ -30518,7 +30518,7 @@ var Protocol = class {
30518
30518
  return;
30519
30519
  }
30520
30520
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
30521
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
30521
+ await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
30522
30522
  options?.signal?.throwIfAborted();
30523
30523
  }
30524
30524
  } catch (error2) {
@@ -30535,7 +30535,7 @@ var Protocol = class {
30535
30535
  */
30536
30536
  request(request, resultSchema, options) {
30537
30537
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
30538
- return new Promise((resolve8, reject) => {
30538
+ return new Promise((resolve10, reject) => {
30539
30539
  const earlyReject = (error2) => {
30540
30540
  reject(error2);
30541
30541
  };
@@ -30613,7 +30613,7 @@ var Protocol = class {
30613
30613
  if (!parseResult.success) {
30614
30614
  reject(parseResult.error);
30615
30615
  } else {
30616
- resolve8(parseResult.data);
30616
+ resolve10(parseResult.data);
30617
30617
  }
30618
30618
  } catch (error2) {
30619
30619
  reject(error2);
@@ -30874,12 +30874,12 @@ var Protocol = class {
30874
30874
  }
30875
30875
  } catch {
30876
30876
  }
30877
- return new Promise((resolve8, reject) => {
30877
+ return new Promise((resolve10, reject) => {
30878
30878
  if (signal.aborted) {
30879
30879
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
30880
30880
  return;
30881
30881
  }
30882
- const timeoutId = setTimeout(resolve8, interval);
30882
+ const timeoutId = setTimeout(resolve10, interval);
30883
30883
  signal.addEventListener("abort", () => {
30884
30884
  clearTimeout(timeoutId);
30885
30885
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -31979,7 +31979,7 @@ var McpServer = class {
31979
31979
  let task = createTaskResult.task;
31980
31980
  const pollInterval = task.pollInterval ?? 5e3;
31981
31981
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
31982
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
31982
+ await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
31983
31983
  const updatedTask = await extra.taskStore.getTask(taskId);
31984
31984
  if (!updatedTask) {
31985
31985
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -32628,19 +32628,19 @@ var StdioServerTransport = class {
32628
32628
  this.onclose?.();
32629
32629
  }
32630
32630
  send(message) {
32631
- return new Promise((resolve8) => {
32631
+ return new Promise((resolve10) => {
32632
32632
  const json3 = serializeMessage(message);
32633
32633
  if (this._stdout.write(json3)) {
32634
- resolve8();
32634
+ resolve10();
32635
32635
  } else {
32636
- this._stdout.once("drain", resolve8);
32636
+ this._stdout.once("drain", resolve10);
32637
32637
  }
32638
32638
  });
32639
32639
  }
32640
32640
  };
32641
32641
 
32642
32642
  // src/lib/cli-version.ts
32643
- var CLI_VERSION = "0.1.6";
32643
+ var CLI_VERSION = "0.1.8";
32644
32644
 
32645
32645
  // src/commands/mcp-serve.ts
32646
32646
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -32825,7 +32825,7 @@ async function runFohCli(params) {
32825
32825
  effectiveArgv.push("--json");
32826
32826
  }
32827
32827
  const command = `foh ${effectiveArgv.join(" ")}`;
32828
- return await new Promise((resolve8) => {
32828
+ return await new Promise((resolve10) => {
32829
32829
  const child = (0, import_node_child_process.spawn)(process.execPath, [cliEntry, ...effectiveArgv], {
32830
32830
  stdio: ["ignore", "pipe", "pipe"],
32831
32831
  env: {
@@ -32850,7 +32850,7 @@ async function runFohCli(params) {
32850
32850
  });
32851
32851
  child.once("error", (error2) => {
32852
32852
  clearTimeout(timeoutHandle);
32853
- resolve8({
32853
+ resolve10({
32854
32854
  ok: false,
32855
32855
  command,
32856
32856
  argv: effectiveArgv,
@@ -32866,7 +32866,7 @@ async function runFohCli(params) {
32866
32866
  const stderrText = finalizeBoundedText(stderrBuffer);
32867
32867
  const exitCode = Number.isFinite(code ?? NaN) ? Number(code) : 1;
32868
32868
  const stdoutJson = tryParseJson(stdoutText);
32869
- resolve8({
32869
+ resolve10({
32870
32870
  ok: !timedOut && exitCode === 0,
32871
32871
  command,
32872
32872
  argv: effectiveArgv,
@@ -34775,8 +34775,8 @@ function registerSetup(program3) {
34775
34775
  }
34776
34776
  try {
34777
34777
  const manifest = await agentExport(resolvedAgentId, { apiUrlOverride: opts.apiUrl });
34778
- const { writeFileSync: writeFileSync6 } = await import("fs");
34779
- writeFileSync6(
34778
+ const { writeFileSync: writeFileSync7 } = await import("fs");
34779
+ writeFileSync7(
34780
34780
  "tenant.yaml",
34781
34781
  `# tenant.yaml - Front Of House agent manifest
34782
34782
  # Edit this file and run: foh plan tenant.yaml
@@ -34944,8 +34944,8 @@ function registerSim(program3) {
34944
34944
  }
34945
34945
  const cert = response.certificate;
34946
34946
  if (opts.out) {
34947
- const { writeFileSync: writeFileSync6 } = await import("fs");
34948
- writeFileSync6(opts.out, JSON.stringify(cert, null, 2) + "\n", "utf-8");
34947
+ const { writeFileSync: writeFileSync7 } = await import("fs");
34948
+ writeFileSync7(opts.out, JSON.stringify(cert, null, 2) + "\n", "utf-8");
34949
34949
  process.stderr.write(` Certificate written to ${opts.out}
34950
34950
  `);
34951
34951
  }
@@ -34995,8 +34995,8 @@ function registerSim(program3) {
34995
34995
  });
34996
34996
  }
34997
34997
  if (opts.out) {
34998
- const { writeFileSync: writeFileSync6 } = await import("fs");
34999
- writeFileSync6(opts.out, JSON.stringify(response.certificate, null, 2) + "\n", "utf-8");
34998
+ const { writeFileSync: writeFileSync7 } = await import("fs");
34999
+ writeFileSync7(opts.out, JSON.stringify(response.certificate, null, 2) + "\n", "utf-8");
35000
35000
  process.stderr.write(` Final certificate written to ${opts.out}
35001
35001
  `);
35002
35002
  }
@@ -36362,6 +36362,7 @@ var import_path8 = require("path");
36362
36362
  var import_fs9 = require("fs");
36363
36363
  var import_path7 = require("path");
36364
36364
  var IMPROVEMENT_SOURCE_TYPES = [
36365
+ "external_agent_run",
36365
36366
  "setup_failure",
36366
36367
  "proof_failure",
36367
36368
  "replay_failure",
@@ -36373,6 +36374,8 @@ var IMPROVEMENT_DECISIONS = [
36373
36374
  "ignore",
36374
36375
  "fix_docs",
36375
36376
  "fix_config",
36377
+ "fix_cli",
36378
+ "fix_api",
36376
36379
  "fix_runtime",
36377
36380
  "add_test"
36378
36381
  ];
@@ -36408,6 +36411,7 @@ function parseEnum(raw, allowed, label) {
36408
36411
  function inferSourceType(artifact) {
36409
36412
  const schema2 = nonEmpty2(getPath2(artifact, "schema_version")) || "";
36410
36413
  const status = nonEmpty2(getPath2(artifact, "status")) || "";
36414
+ if (schema2 === "external_agent_run.v1") return "external_agent_run";
36411
36415
  if (schema2.includes("knowledge_query") || nonEmpty2(getPath2(artifact, "failure_packet.schema_version"))?.includes("knowledge_query")) {
36412
36416
  return "knowledge_miss";
36413
36417
  }
@@ -36419,6 +36423,8 @@ function inferSourceType(artifact) {
36419
36423
  function inferReasonCode(artifact) {
36420
36424
  const direct = nonEmpty2(getPath2(artifact, "reason_code"));
36421
36425
  if (direct) return direct;
36426
+ const failureReason = nonEmpty2(getPath2(artifact, "failure_reason_code"));
36427
+ if (failureReason) return failureReason;
36422
36428
  const nested = nonEmpty2(getPath2(artifact, "failure_packet.reason_code"));
36423
36429
  if (nested) return nested;
36424
36430
  const checks = getPath2(artifact, "checks");
@@ -36432,6 +36438,7 @@ function inferReasonCode(artifact) {
36432
36438
  return nonEmpty2(getPath2(artifact, "status"));
36433
36439
  }
36434
36440
  function inferPromotionDecision(sourceType) {
36441
+ if (sourceType === "external_agent_run") return "fix_docs";
36435
36442
  if (sourceType === "knowledge_miss") return "fix_docs";
36436
36443
  if (sourceType === "setup_failure" || sourceType === "proof_failure" || sourceType === "live_proof_failure") return "fix_config";
36437
36444
  if (sourceType === "replay_failure" || sourceType === "runtime_miss") return "add_test";
@@ -36473,6 +36480,9 @@ function defaultNextCommands(input) {
36473
36480
  if (input.sourceType === "knowledge_miss" && input.ids.agent_id) {
36474
36481
  commands.push(`foh knowledge query --agent ${input.ids.agent_id} --text "<question>" --explain --json`);
36475
36482
  }
36483
+ if (input.sourceType === "external_agent_run" && input.sourceArtifactPath) {
36484
+ commands.push(`foh bug improve --from external-agent-run --file ${input.sourceArtifactPath} --json`);
36485
+ }
36476
36486
  if (input.sourceArtifactPath) {
36477
36487
  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`);
36478
36488
  }
@@ -36835,18 +36845,20 @@ function registerBug(program3) {
36835
36845
  throw e;
36836
36846
  }
36837
36847
  });
36838
- bug.command("improve").description("Write a redacted failure-to-improvement packet from a setup/proof/replay/knowledge/runtime artifact").option("--from-file <path>", "Source JSON artifact to convert into an improvement packet").option("--out <path>", "Output JSON file path for improvement packet").option("--source-type <type>", "setup_failure|proof_failure|replay_failure|knowledge_miss|runtime_miss|live_proof_failure").option("--reason-code <code>", "Deterministic failure reason code").option("--recommendation <decision>", "ignore|fix_docs|fix_config|fix_runtime|add_test").option("--evidence-summary <text>", "Compact redacted evidence summary").option("--source-command <text>", "Command or operation that produced the source artifact").option("--org <id>", "Org ID for boundary check and packet IDs").option("--agent <id>", "Agent ID to attach").option("--conversation <id>", "Conversation ID to attach").option("--trace <id>", "Trace ID to attach").option("--correlation <id>", "Correlation ID to attach").option("--proof-artifact <path>", "Proof artifact path to attach").option(
36848
+ bug.command("improve").description("Write a redacted failure-to-improvement packet from an external-agent/setup/proof/replay/knowledge/runtime artifact").option("--from <type>", "Source artifact type alias, for example external-agent-run").option("--file <path>", "Source JSON artifact alias for --from-file").option("--from-file <path>", "Source JSON artifact to convert into an improvement packet").option("--out <path>", "Output JSON file path for improvement packet").option("--source-type <type>", "external_agent_run|setup_failure|proof_failure|replay_failure|knowledge_miss|runtime_miss|live_proof_failure").option("--reason-code <code>", "Deterministic failure reason code").option("--recommendation <decision>", "ignore|fix_docs|fix_config|fix_cli|fix_api|fix_runtime|add_test").option("--evidence-summary <text>", "Compact redacted evidence summary").option("--source-command <text>", "Command or operation that produced the source artifact").option("--org <id>", "Org ID for boundary check and packet IDs").option("--agent <id>", "Agent ID to attach").option("--conversation <id>", "Conversation ID to attach").option("--trace <id>", "Trace ID to attach").option("--correlation <id>", "Correlation ID to attach").option("--proof-artifact <path>", "Proof artifact path to attach").option(
36839
36849
  "--next-command <text>",
36840
36850
  "Deterministic next command (repeat this flag for multiple commands)",
36841
36851
  (value, previous = []) => [...previous, value],
36842
36852
  []
36843
36853
  ).option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36844
- const sourceArtifact = readSourceArtifact(opts.fromFile);
36854
+ const sourcePath = opts.fromFile ?? opts.file;
36855
+ const sourceType = opts.sourceType ?? (typeof opts.from === "string" ? opts.from.replaceAll("-", "_") : void 0);
36856
+ const sourceArtifact = readSourceArtifact(sourcePath);
36845
36857
  const outPath = String(opts.out || defaultImprovementArtifactPath());
36846
36858
  const packet = buildImprovementPacket({
36847
36859
  sourceArtifact,
36848
- sourceArtifactPath: opts.fromFile,
36849
- sourceType: opts.sourceType,
36860
+ sourceArtifactPath: sourcePath,
36861
+ sourceType,
36850
36862
  reasonCode: opts.reasonCode,
36851
36863
  evidenceSummary: opts.evidenceSummary,
36852
36864
  promotionDecision: opts.recommendation,
@@ -37698,7 +37710,7 @@ async function runSelf(args, apiUrlOverride) {
37698
37710
  if (apiUrlOverride && !spawnArgs.includes("--api-url")) {
37699
37711
  spawnArgs.push("--api-url", apiUrlOverride);
37700
37712
  }
37701
- return await new Promise((resolve8, reject) => {
37713
+ return await new Promise((resolve10, reject) => {
37702
37714
  const child = (0, import_child_process2.spawn)(process.execPath, [process.argv[1], ...spawnArgs], {
37703
37715
  stdio: "inherit",
37704
37716
  env: {
@@ -37708,7 +37720,7 @@ async function runSelf(args, apiUrlOverride) {
37708
37720
  }
37709
37721
  });
37710
37722
  child.once("error", reject);
37711
- child.once("close", (code) => resolve8(typeof code === "number" ? code : 1));
37723
+ child.once("close", (code) => resolve10(typeof code === "number" ? code : 1));
37712
37724
  });
37713
37725
  }
37714
37726
  function shouldUseInteractiveHome(argv) {
@@ -38086,17 +38098,17 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
38086
38098
  async function applyRepoUpdate(repoRoot) {
38087
38099
  const scriptPath = (0, import_path9.join)(repoRoot, "scripts", "Install-FohCli.ps1");
38088
38100
  if (process.platform === "win32") {
38089
- return await new Promise((resolve8, reject) => {
38101
+ return await new Promise((resolve10, reject) => {
38090
38102
  const child = (0, import_child_process3.spawn)(
38091
38103
  "powershell",
38092
38104
  ["-ExecutionPolicy", "Bypass", "-File", scriptPath],
38093
38105
  { stdio: "inherit" }
38094
38106
  );
38095
38107
  child.once("error", reject);
38096
- child.once("close", (code) => resolve8(typeof code === "number" ? code : 1));
38108
+ child.once("close", (code) => resolve10(typeof code === "number" ? code : 1));
38097
38109
  });
38098
38110
  }
38099
- return await new Promise((resolve8, reject) => {
38111
+ return await new Promise((resolve10, reject) => {
38100
38112
  const child = (0, import_child_process3.spawn)(
38101
38113
  "corepack",
38102
38114
  ["pnpm", "cli:install:global"],
@@ -38106,7 +38118,7 @@ async function applyRepoUpdate(repoRoot) {
38106
38118
  }
38107
38119
  );
38108
38120
  child.once("error", reject);
38109
- child.once("close", (code) => resolve8(typeof code === "number" ? code : 1));
38121
+ child.once("close", (code) => resolve10(typeof code === "number" ? code : 1));
38110
38122
  });
38111
38123
  }
38112
38124
  function shouldShowUpdateNotice(argv = process.argv) {
@@ -38241,6 +38253,214 @@ function registerUpdate(program3) {
38241
38253
  });
38242
38254
  }
38243
38255
 
38256
+ // src/commands/eval.ts
38257
+ var import_fs13 = require("fs");
38258
+ var import_path11 = require("path");
38259
+ var import_child_process4 = require("child_process");
38260
+
38261
+ // src/lib/external-agent-capture.ts
38262
+ var import_fs12 = require("fs");
38263
+ var import_path10 = require("path");
38264
+ var EXTERNAL_AGENT_RUN_DIR_ENV = "FOH_EXTERNAL_AGENT_RUN_DIR";
38265
+ var EXTERNAL_AGENT_PROMPT_VERSION_ENV = "FOH_EXTERNAL_AGENT_PROMPT_VERSION";
38266
+ var SECRET_RE2 = /\b(?:Bearer\s+)?(?:sk|pk|xai|whsec|EAAN|ghp|gho|github_pat|npm_)[A-Za-z0-9_\-.]{12,}\b/gi;
38267
+ function redactText(value) {
38268
+ return value.replace(SECRET_RE2, "[redacted_secret]");
38269
+ }
38270
+ function safeJsonLine(value) {
38271
+ return JSON.stringify(value).replace(/\r?\n/g, " ") + "\n";
38272
+ }
38273
+ function getExternalAgentRunDir() {
38274
+ const raw = process.env[EXTERNAL_AGENT_RUN_DIR_ENV];
38275
+ if (!raw || !raw.trim()) return null;
38276
+ return (0, import_path10.resolve)(raw);
38277
+ }
38278
+ function recordExternalAgentCliInvocation(input) {
38279
+ const runDir = getExternalAgentRunDir();
38280
+ if (!runDir) return;
38281
+ try {
38282
+ (0, import_fs12.mkdirSync)(runDir, { recursive: true });
38283
+ const args = input.argv.slice(2).map((arg) => redactText(String(arg)));
38284
+ const record2 = {
38285
+ schema_version: "external_agent_cli_command.v1",
38286
+ recorded_at: (/* @__PURE__ */ new Date()).toISOString(),
38287
+ cli_version: input.cliVersion,
38288
+ cwd: redactText(process.cwd()),
38289
+ argv: args,
38290
+ command: args.join(" "),
38291
+ json_requested: args.includes("--json"),
38292
+ prompt_version: process.env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] || null
38293
+ };
38294
+ (0, import_fs12.appendFileSync)((0, import_path10.join)(runDir, "commands.ndjson"), safeJsonLine(record2), "utf8");
38295
+ } catch {
38296
+ }
38297
+ }
38298
+ function readCommandRecords(runDir) {
38299
+ const commandLogPath = (0, import_path10.join)(runDir, "commands.ndjson");
38300
+ if (!(0, import_fs12.existsSync)(commandLogPath)) return [];
38301
+ return (0, import_fs12.readFileSync)(commandLogPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
38302
+ }
38303
+
38304
+ // src/commands/eval.ts
38305
+ var DEFAULT_PROMPT_VERSION = "blank-setup.v1";
38306
+ var PROMPTS = {
38307
+ "blank-setup.v1": "Go to https://frontofhouse.okii.uk. Use only public docs, public API docs, and the public npm CLI package. Install the FOH CLI, authenticate or reach a deterministic auth blocker, create or configure a Front Of House voice agent and website widget, run proof/smoke/certification where available, and 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.",
38308
+ "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.",
38309
+ "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.",
38310
+ "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."
38311
+ };
38312
+ function normalizeStatus(raw) {
38313
+ const value = String(raw ?? "hold").trim().toLowerCase();
38314
+ if (value === "pass" || value === "hold" || value === "fail") return value;
38315
+ throw new Error("Invalid --status. Use pass, hold, or fail.");
38316
+ }
38317
+ function defaultRunDir(modelName, promptVersion) {
38318
+ const date4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
38319
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 23);
38320
+ const safeModel = String(modelName || "unknown-model").toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
38321
+ const safePrompt = String(promptVersion || DEFAULT_PROMPT_VERSION).toLowerCase().replace(/[^a-z0-9_.-]+/g, "-");
38322
+ return (0, import_path11.resolve)("test-results", "external-agent-runs", date4, `${safeModel}-${safePrompt}-${stamp}`);
38323
+ }
38324
+ function inferShell(raw) {
38325
+ if (raw && raw.trim()) return { command: raw, args: [], label: raw };
38326
+ if (process.platform === "win32") return { command: "powershell.exe", args: ["-NoLogo", "-NoProfile"], label: "powershell" };
38327
+ return { command: process.env.SHELL || "sh", args: [], label: process.env.SHELL || "sh" };
38328
+ }
38329
+ function writePrompt(runDir, promptVersion) {
38330
+ const prompt = PROMPTS[promptVersion] ?? PROMPTS[DEFAULT_PROMPT_VERSION];
38331
+ const path2 = (0, import_path11.join)(runDir, "prompt.txt");
38332
+ (0, import_fs13.writeFileSync)(path2, `${prompt}
38333
+ `, "utf8");
38334
+ return path2;
38335
+ }
38336
+ function writeSession(runDir, session) {
38337
+ const path2 = (0, import_path11.join)(runDir, "session.json");
38338
+ (0, import_fs13.writeFileSync)(path2, `${JSON.stringify(session, null, 2)}
38339
+ `, "utf8");
38340
+ return path2;
38341
+ }
38342
+ function buildRunArtifact(input) {
38343
+ const commands = readCommandRecords(input.runDir);
38344
+ const startedAt = String(input.session.started_at);
38345
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
38346
+ const status = input.status;
38347
+ const reasonCode = status === "pass" ? null : String(input.reasonCode || "external_agent_run_needs_review");
38348
+ return {
38349
+ schema_version: "external_agent_run.v1",
38350
+ run_id: input.session.run_id,
38351
+ status,
38352
+ failure_reason_code: reasonCode,
38353
+ model_provider: input.session.model_provider,
38354
+ model_name: input.session.model_name,
38355
+ agent_shell: input.session.agent_shell,
38356
+ workspace_type: input.session.workspace_type,
38357
+ prompt_version: input.session.prompt_version,
38358
+ prompt_path: "prompt.txt",
38359
+ started_at: startedAt,
38360
+ ended_at: endedAt,
38361
+ manual_intervention_count: Number(input.session.manual_intervention_count || 0),
38362
+ manual_interventions: [],
38363
+ environment: {
38364
+ os: process.platform,
38365
+ node_version: process.version,
38366
+ npm_version: null,
38367
+ foh_cli_version: CLI_VERSION,
38368
+ shell_exit_code: input.shellExitCode ?? null
38369
+ },
38370
+ public_entrypoints: [
38371
+ "https://frontofhouse.okii.uk",
38372
+ "https://frontofhouse.okii.uk/llms.txt",
38373
+ "https://frontofhouse.okii.uk/openapi.yaml",
38374
+ "npx --yes @f-o-h/cli@latest"
38375
+ ],
38376
+ commands_run: commands.map((command) => command.command),
38377
+ docs_pages_used: [],
38378
+ artifacts: {
38379
+ terminal_transcript: null,
38380
+ command_log: "commands.ndjson",
38381
+ proof_bundle: null,
38382
+ replay_packet: null,
38383
+ knowledge_packet: null,
38384
+ improvement_packet: status === "pass" ? null : "improvement-packet.json",
38385
+ notes: "notes.md"
38386
+ },
38387
+ 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}.`,
38388
+ next_commands: status === "pass" ? ["corepack pnpm eval:external-agent:runs:summary"] : [
38389
+ `foh bug improve --from external-agent-run --file ${(0, import_path11.join)(input.runDir, "run.json")} --out ${(0, import_path11.join)(input.runDir, "improvement-packet.json")} --json`,
38390
+ "corepack pnpm eval:external-agent:runs:summary"
38391
+ ]
38392
+ };
38393
+ }
38394
+ function registerEval(program3) {
38395
+ const evalCommand = program3.command("eval").description("Run or summarize external-agent evaluation workflows");
38396
+ const external = evalCommand.command("external-agent").description("Capture clean external coding-agent setup attempts");
38397
+ 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) => {
38398
+ const status = normalizeStatus(opts.status);
38399
+ const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
38400
+ const runDir = (0, import_path11.resolve)(String(opts.outDir || defaultRunDir(opts.modelName, promptVersion)));
38401
+ (0, import_fs13.mkdirSync)(runDir, { recursive: true });
38402
+ const runId = runDir.split(/[\\/]/).filter(Boolean).slice(-1)[0];
38403
+ const promptPath = writePrompt(runDir, promptVersion);
38404
+ const shell = inferShell(opts.shell);
38405
+ const session = {
38406
+ schema_version: "external_agent_capture_session.v1",
38407
+ run_id: runId,
38408
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
38409
+ model_provider: String(opts.modelProvider || "unknown"),
38410
+ model_name: String(opts.modelName || "unknown-model"),
38411
+ prompt_version: promptVersion,
38412
+ workspace_type: String(opts.workspaceType || "clean-no-repo"),
38413
+ agent_shell: String(opts.agentShell || shell.label),
38414
+ manual_intervention_count: 0,
38415
+ run_dir: runDir,
38416
+ prompt_path: promptPath,
38417
+ capture_env: {
38418
+ [EXTERNAL_AGENT_RUN_DIR_ENV]: runDir,
38419
+ [EXTERNAL_AGENT_PROMPT_VERSION_ENV]: promptVersion
38420
+ }
38421
+ };
38422
+ writeSession(runDir, session);
38423
+ (0, import_fs13.writeFileSync)((0, import_path11.join)(runDir, "notes.md"), "# External Agent Run Notes\n\n", "utf8");
38424
+ let shellExitCode = null;
38425
+ if (opts.shell !== false) {
38426
+ process.stdout.write(`
38427
+ External-agent capture is active.
38428
+ Run dir: ${runDir}
38429
+ Prompt: ${promptPath}
38430
+ Exit the shell to finalize run.json.
38431
+
38432
+ `);
38433
+ const result = (0, import_child_process4.spawnSync)(shell.command, shell.args, {
38434
+ stdio: "inherit",
38435
+ env: {
38436
+ ...process.env,
38437
+ [EXTERNAL_AGENT_RUN_DIR_ENV]: runDir,
38438
+ [EXTERNAL_AGENT_PROMPT_VERSION_ENV]: promptVersion,
38439
+ FOH_CLI_SUPPRESS_BANNER: process.env.FOH_CLI_SUPPRESS_BANNER ?? "1"
38440
+ }
38441
+ });
38442
+ shellExitCode = typeof result.status === "number" ? result.status : null;
38443
+ }
38444
+ const artifact = buildRunArtifact({ runDir, session, status, reasonCode: opts.reasonCode, shellExitCode });
38445
+ const runPath = (0, import_path11.join)(runDir, "run.json");
38446
+ (0, import_fs13.writeFileSync)(runPath, `${JSON.stringify(artifact, null, 2)}
38447
+ `, "utf8");
38448
+ format(cliEnvelope({
38449
+ schemaVersion: "external_agent_capture_result.v1",
38450
+ status: "exported",
38451
+ reasonCode: "external_agent_run_created",
38452
+ summary: "External-agent run artifact created.",
38453
+ artifacts: {
38454
+ run: runPath,
38455
+ prompt: promptPath,
38456
+ commands: (0, import_path11.join)(runDir, "commands.ndjson")
38457
+ },
38458
+ nextCommands: artifact.next_commands,
38459
+ extra: { run: artifact }
38460
+ }), { json: Boolean(opts.json) });
38461
+ });
38462
+ }
38463
+
38244
38464
  // src/lib/banner.ts
38245
38465
  var import_picocolors6 = __toESM(require_picocolors());
38246
38466
  var BANNER_LINES = [
@@ -38311,6 +38531,7 @@ function installSoftExitTrap() {
38311
38531
 
38312
38532
  // src/index.ts
38313
38533
  installSoftExitTrap();
38534
+ recordExternalAgentCliInvocation({ argv: process.argv, cliVersion: CLI_VERSION });
38314
38535
  var program2 = new Command();
38315
38536
  var BUG_REPORT_URL = process.env.FOH_BUG_REPORT_URL ?? "https://github.com/iiko38/front-of-house/issues";
38316
38537
  function shouldRenderBanner2(argv) {
@@ -38392,6 +38613,7 @@ registerSim(program2);
38392
38613
  registerDiag(program2);
38393
38614
  registerBug(program2);
38394
38615
  registerProve(program2);
38616
+ registerEval(program2);
38395
38617
  registerUpdate(program2);
38396
38618
  registerHome(program2);
38397
38619
  hideInternalApiUrlOptions(program2);
@@ -0,0 +1,53 @@
1
+ {
2
+ "schema_version": "external_agent_run.v1",
3
+ "run_id": "2026-05-04-codex-001",
4
+ "status": "fail",
5
+ "failure_reason_code": "docs_widget_proof_path_unclear",
6
+ "model_provider": "openai",
7
+ "model_name": "codex",
8
+ "agent_shell": "vscode-terminal",
9
+ "workspace_type": "clean-no-repo",
10
+ "prompt_version": "blank-setup.v1",
11
+ "prompt_path": "Docs/operations/external-agent-feedback-loop.md#blank-setupv1",
12
+ "started_at": "2026-05-04T09:00:00.000Z",
13
+ "ended_at": "2026-05-04T09:30:00.000Z",
14
+ "manual_intervention_count": 1,
15
+ "manual_interventions": [
16
+ {
17
+ "at": "2026-05-04T09:12:00.000Z",
18
+ "summary": "Human pointed the agent from generic docs to the CLI install guide."
19
+ }
20
+ ],
21
+ "environment": {
22
+ "os": "windows",
23
+ "node_version": "v24.11.1",
24
+ "npm_version": "10.x",
25
+ "foh_cli_version": "0.1.6"
26
+ },
27
+ "public_entrypoints": [
28
+ "https://frontofhouse.okii.uk",
29
+ "https://frontofhouse.okii.uk/llms.txt",
30
+ "npx --yes @f-o-h/cli@latest"
31
+ ],
32
+ "commands_run": [
33
+ "npx --yes @f-o-h/cli@latest --version",
34
+ "npx --yes @f-o-h/cli@latest setup --help",
35
+ "npx --yes @f-o-h/cli@latest prove --help"
36
+ ],
37
+ "docs_pages_used": [
38
+ "https://frontofhouse.okii.uk/guides/cli-install-and-upgrade",
39
+ "https://frontofhouse.okii.uk/guides/error-handling-and-debugging"
40
+ ],
41
+ "artifacts": {
42
+ "terminal_transcript": "terminal-transcript.txt",
43
+ "proof_bundle": null,
44
+ "replay_packet": null,
45
+ "knowledge_packet": null,
46
+ "improvement_packet": "improvement-packet.json",
47
+ "notes": "notes.md"
48
+ },
49
+ "summary": "Agent installed the CLI but could not identify the shortest widget proof path without human direction.",
50
+ "next_commands": [
51
+ "foh bug improve --from external-agent-run --file run.json --out improvement-packet.json --json"
52
+ ]
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@f-o-h/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "FOH CLI - AI-operator provisioning tool for Front Of House",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {
@@ -0,0 +1,95 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://frontofhouse.okii.uk/schemas/external-agent-run.schema.json",
4
+ "title": "FOH External Agent Run",
5
+ "type": "object",
6
+ "required": [
7
+ "schema_version",
8
+ "run_id",
9
+ "status",
10
+ "model_provider",
11
+ "model_name",
12
+ "prompt_version",
13
+ "started_at",
14
+ "manual_intervention_count",
15
+ "environment",
16
+ "public_entrypoints",
17
+ "commands_run",
18
+ "docs_pages_used",
19
+ "artifacts"
20
+ ],
21
+ "properties": {
22
+ "schema_version": { "const": "external_agent_run.v1" },
23
+ "run_id": { "type": "string", "minLength": 1 },
24
+ "status": { "type": "string", "enum": ["pass", "hold", "fail"] },
25
+ "failure_reason_code": { "type": ["string", "null"] },
26
+ "model_provider": { "type": "string", "minLength": 1 },
27
+ "model_name": { "type": "string", "minLength": 1 },
28
+ "agent_shell": { "type": "string" },
29
+ "workspace_type": { "type": "string" },
30
+ "prompt_version": { "type": "string", "minLength": 1 },
31
+ "prompt_path": { "type": "string" },
32
+ "started_at": { "type": "string" },
33
+ "ended_at": { "type": ["string", "null"] },
34
+ "manual_intervention_count": { "type": "integer", "minimum": 0 },
35
+ "manual_interventions": {
36
+ "type": "array",
37
+ "items": {
38
+ "type": "object",
39
+ "properties": {
40
+ "at": { "type": "string" },
41
+ "summary": { "type": "string" }
42
+ },
43
+ "additionalProperties": true
44
+ }
45
+ },
46
+ "environment": {
47
+ "type": "object",
48
+ "required": ["os", "foh_cli_version"],
49
+ "properties": {
50
+ "os": { "type": "string", "minLength": 1 },
51
+ "node_version": { "type": ["string", "null"] },
52
+ "npm_version": { "type": ["string", "null"] },
53
+ "foh_cli_version": { "type": "string", "minLength": 1 }
54
+ },
55
+ "additionalProperties": true
56
+ },
57
+ "public_entrypoints": {
58
+ "type": "array",
59
+ "minItems": 1,
60
+ "items": { "type": "string", "minLength": 1 }
61
+ },
62
+ "commands_run": {
63
+ "type": "array",
64
+ "items": { "type": "string" }
65
+ },
66
+ "docs_pages_used": {
67
+ "type": "array",
68
+ "items": { "type": "string" }
69
+ },
70
+ "artifacts": {
71
+ "type": "object",
72
+ "properties": {
73
+ "terminal_transcript": { "type": ["string", "null"] },
74
+ "proof_bundle": { "type": ["string", "null"] },
75
+ "replay_packet": { "type": ["string", "null"] },
76
+ "knowledge_packet": { "type": ["string", "null"] },
77
+ "improvement_packet": { "type": ["string", "null"] },
78
+ "notes": { "type": ["string", "null"] }
79
+ },
80
+ "additionalProperties": true
81
+ },
82
+ "summary": { "type": "string" },
83
+ "next_commands": {
84
+ "type": "array",
85
+ "items": { "type": "string" }
86
+ }
87
+ },
88
+ "additionalProperties": true,
89
+ "allOf": [
90
+ {
91
+ "if": { "properties": { "status": { "enum": ["hold", "fail"] } }, "required": ["status"] },
92
+ "then": { "required": ["failure_reason_code"] }
93
+ }
94
+ ]
95
+ }
@@ -1,76 +1,77 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://frontofhouse.okii.uk/schemas/improvement-packet.schema.json",
4
- "title": "FOH Improvement Packet",
5
- "type": "object",
6
- "required": [
7
- "schema_version",
8
- "created_at",
9
- "source_type",
10
- "reason_code",
11
- "promotion_decision",
12
- "ids",
13
- "evidence",
14
- "next_commands"
15
- ],
16
- "properties": {
17
- "schema_version": { "const": "foh_improvement_packet.v1" },
18
- "created_at": { "type": "string" },
19
- "source_type": {
20
- "type": "string",
21
- "enum": [
22
- "setup_failure",
23
- "proof_failure",
24
- "replay_failure",
25
- "knowledge_miss",
26
- "runtime_miss",
27
- "live_proof_failure"
28
- ]
29
- },
30
- "reason_code": { "type": "string", "minLength": 1 },
31
- "promotion_decision": {
32
- "type": "string",
33
- "enum": ["ignore", "fix_docs", "fix_config", "fix_runtime", "add_test"]
34
- },
35
- "ids": {
36
- "type": "object",
37
- "properties": {
38
- "org_id": { "type": "string" },
39
- "agent_id": { "type": "string" },
40
- "conversation_id": { "type": "string" },
41
- "trace_id": { "type": "string" },
42
- "correlation_id": { "type": "string" },
43
- "proof_artifact": { "type": "string" }
44
- },
45
- "additionalProperties": false
46
- },
47
- "evidence": {
48
- "type": "object",
49
- "required": ["summary", "redaction"],
50
- "properties": {
51
- "summary": { "type": "string", "minLength": 1 },
52
- "source_artifact_path": { "type": ["string", "null"] },
53
- "source_command": { "type": ["string", "null"] },
54
- "redaction": {
55
- "type": "object",
56
- "required": ["enabled", "fields"],
57
- "properties": {
58
- "enabled": { "const": true },
59
- "fields": {
60
- "type": "array",
61
- "items": { "type": "string" }
62
- }
63
- },
64
- "additionalProperties": false
65
- },
66
- "redacted_source": {}
67
- },
68
- "additionalProperties": false
69
- },
70
- "next_commands": {
71
- "type": "array",
72
- "items": { "type": "string", "minLength": 1 }
73
- }
74
- },
75
- "additionalProperties": false
76
- }
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://frontofhouse.okii.uk/schemas/improvement-packet.schema.json",
4
+ "title": "FOH Improvement Packet",
5
+ "type": "object",
6
+ "required": [
7
+ "schema_version",
8
+ "created_at",
9
+ "source_type",
10
+ "reason_code",
11
+ "promotion_decision",
12
+ "ids",
13
+ "evidence",
14
+ "next_commands"
15
+ ],
16
+ "properties": {
17
+ "schema_version": { "const": "foh_improvement_packet.v1" },
18
+ "created_at": { "type": "string" },
19
+ "source_type": {
20
+ "type": "string",
21
+ "enum": [
22
+ "external_agent_run",
23
+ "setup_failure",
24
+ "proof_failure",
25
+ "replay_failure",
26
+ "knowledge_miss",
27
+ "runtime_miss",
28
+ "live_proof_failure"
29
+ ]
30
+ },
31
+ "reason_code": { "type": "string", "minLength": 1 },
32
+ "promotion_decision": {
33
+ "type": "string",
34
+ "enum": ["ignore", "fix_docs", "fix_config", "fix_cli", "fix_api", "fix_runtime", "add_test"]
35
+ },
36
+ "ids": {
37
+ "type": "object",
38
+ "properties": {
39
+ "org_id": { "type": "string" },
40
+ "agent_id": { "type": "string" },
41
+ "conversation_id": { "type": "string" },
42
+ "trace_id": { "type": "string" },
43
+ "correlation_id": { "type": "string" },
44
+ "proof_artifact": { "type": "string" }
45
+ },
46
+ "additionalProperties": false
47
+ },
48
+ "evidence": {
49
+ "type": "object",
50
+ "required": ["summary", "redaction"],
51
+ "properties": {
52
+ "summary": { "type": "string", "minLength": 1 },
53
+ "source_artifact_path": { "type": ["string", "null"] },
54
+ "source_command": { "type": ["string", "null"] },
55
+ "redaction": {
56
+ "type": "object",
57
+ "required": ["enabled", "fields"],
58
+ "properties": {
59
+ "enabled": { "const": true },
60
+ "fields": {
61
+ "type": "array",
62
+ "items": { "type": "string" }
63
+ }
64
+ },
65
+ "additionalProperties": false
66
+ },
67
+ "redacted_source": {}
68
+ },
69
+ "additionalProperties": false
70
+ },
71
+ "next_commands": {
72
+ "type": "array",
73
+ "items": { "type": "string", "minLength": 1 }
74
+ }
75
+ },
76
+ "additionalProperties": false
77
+ }