@f-o-h/cli 0.1.54 → 0.1.56

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.
Files changed (3) hide show
  1. package/README.md +4 -4
  2. package/dist/foh.js +198 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -101,10 +101,10 @@ scaffold lane:
101
101
  FOH_CLI_SPEND_POLICY=no_spend foh setup --org <org-id> --agent-template <template-id> --agent-name "Demo Agent" --phone-mode observe --json
102
102
  ```
103
103
 
104
- `--phone-mode observe` checks whether a contact phone already exists without
105
- buying one. `--phone-mode skip` bypasses the phone step. `--phone-mode purchase`
106
- is the explicit paid contact path and is fail-closed when
107
- `FOH_CLI_SPEND_POLICY=no_spend` is set.
104
+ `--phone-mode observe` checks whether a contact phone already exists without
105
+ buying one. `--phone-mode skip` bypasses the phone step. `--phone-mode purchase`
106
+ is the explicit paid contact path and is fail-closed when
107
+ `FOH_CLI_SPEND_POLICY=no_spend` is set.
108
108
 
109
109
  If managed-number provisioning is blocked by account/provider capacity or empty
110
110
  reserve inventory, proof reports `provider_capacity_blocked`; fix capacity or
package/dist/foh.js CHANGED
@@ -16021,6 +16021,19 @@ function getTtsProviders(catalog) {
16021
16021
  const fromProviders = Array.isArray(catalog.providers?.tts) ? catalog.providers?.tts : [];
16022
16022
  return fromProviders.map((provider) => String(provider || "").trim().toLowerCase()).filter((provider) => provider.length > 0).sort();
16023
16023
  }
16024
+ function getSttProviders(catalog) {
16025
+ const providerRows = Array.isArray(catalog.voice_catalog?.providers) ? catalog.voice_catalog?.providers : [];
16026
+ const fromRows = providerRows.filter((provider) => provider?.kind === "stt").map((provider) => String(provider.id || "").trim().toLowerCase()).filter((provider) => provider.length > 0);
16027
+ if (fromRows.length > 0) return [...new Set(fromRows)].sort();
16028
+ const fromProviders = Array.isArray(catalog.providers?.stt) ? catalog.providers?.stt : [];
16029
+ return fromProviders.map((provider) => String(provider || "").trim().toLowerCase()).filter((provider) => provider.length > 0).sort();
16030
+ }
16031
+ function resolveDefaultSttProvider(providers) {
16032
+ for (const preferred of ["deepgram", "azure", "twilio", "none"]) {
16033
+ if (providers.includes(preferred)) return preferred;
16034
+ }
16035
+ return providers[0] || "deepgram";
16036
+ }
16024
16037
  function registerVoice(program3) {
16025
16038
  const voice = program3.command("voice").description("Manage voice provider configuration");
16026
16039
  voice.command("verify").description("Run voice verification lanes (quick/full/release) through authenticated API orchestration").option("--mode <m>", "Verification mode: quick, full, release", "release").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
@@ -16248,11 +16261,12 @@ function registerVoice(program3) {
16248
16261
  const allReady = providers.length > 0 && providers.every((provider) => provider?.ready === true);
16249
16262
  if (!allReady) markCommandFailed(1);
16250
16263
  }));
16251
- voice.command("configure").description("Configure voice settings for an agent (does not publish unless --publish)").requiredOption("--agent <id>", "Agent ID").requiredOption("--provider <p>", "TTS provider: openai, azure, twilio").requiredOption("--voice <id>", "Voice ID").option("--stt-provider <p>", "STT provider (default: openai)").option("--publish", "Validate, simulation-certify, then publish after configuring").option("--cert-mode <m>", "Simulation cert mode before publish: quick, full, stress", "full").option("--cert-adaptive-runs <n>", "Adaptive runs for full/stress certification (default: 30)", "30").option("--cert-max-improvement-rounds <n>", "Max prompt improvement rounds before publish (0-5)", "1").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
16264
+ voice.command("configure").description("Configure voice settings for an agent (does not publish unless --publish)").requiredOption("--agent <id>", "Agent ID").requiredOption("--provider <p>", "TTS provider: openai, azure, twilio").requiredOption("--voice <id>", "Voice ID").option("--stt-provider <p>", "STT provider (default: best available, preferring deepgram)").option("--publish", "Validate, simulation-certify, then publish after configuring").option("--cert-mode <m>", "Simulation cert mode before publish: quick, full, stress", "full").option("--cert-adaptive-runs <n>", "Adaptive runs for full/stress certification (default: 30)", "30").option("--cert-max-improvement-rounds <n>", "Max prompt improvement rounds before publish (0-5)", "1").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
16252
16265
  const provider = String(opts.provider || "").trim().toLowerCase();
16253
16266
  const voiceId = String(opts.voice || "").trim();
16254
16267
  const catalog = await getSpeechCatalog(opts.apiUrl);
16255
16268
  const supportedProviders = getTtsProviders(catalog);
16269
+ const supportedSttProviders = getSttProviders(catalog);
16256
16270
  if (!supportedProviders.includes(provider)) {
16257
16271
  throw new FohError({
16258
16272
  step: "voice.configure",
@@ -16271,10 +16285,19 @@ function registerVoice(program3) {
16271
16285
  statusCode: 400
16272
16286
  });
16273
16287
  }
16288
+ const sttProvider = String(opts.sttProvider || resolveDefaultSttProvider(supportedSttProviders)).trim().toLowerCase();
16289
+ if (supportedSttProviders.length > 0 && !supportedSttProviders.includes(sttProvider)) {
16290
+ throw new FohError({
16291
+ step: "voice.configure",
16292
+ error: `STT provider "${sttProvider}" is not available in speech catalog`,
16293
+ remediation: `Use --stt-provider ${resolveDefaultSttProvider(supportedSttProviders)} or run: foh voice list-providers${opts.apiUrl ? ` --api-url ${opts.apiUrl}` : ""}`,
16294
+ statusCode: 400
16295
+ });
16296
+ }
16274
16297
  const voiceConfig = {
16275
16298
  tts_provider: provider,
16276
16299
  tts_voice_id: voiceId,
16277
- stt_provider: opts.sttProvider ?? "openai"
16300
+ stt_provider: sttProvider
16278
16301
  };
16279
16302
  const draft = await apiFetch(`/v1/console/agents/${opts.agent}/draft`, {
16280
16303
  method: "PATCH",
@@ -32830,7 +32853,7 @@ var StdioServerTransport = class {
32830
32853
  };
32831
32854
 
32832
32855
  // src/lib/cli-version.ts
32833
- var CLI_VERSION = "0.1.54";
32856
+ var CLI_VERSION = "0.1.56";
32834
32857
 
32835
32858
  // src/commands/mcp-serve.ts
32836
32859
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -34479,6 +34502,28 @@ function normalizeSetupPhoneMode(raw) {
34479
34502
  reasonCode: "setup_invalid_phone_mode"
34480
34503
  });
34481
34504
  }
34505
+ function complianceSkipDetail(phoneMode) {
34506
+ return {
34507
+ reason_code: `compliance_skipped_phone_mode_${phoneMode}`,
34508
+ phone_mode: phoneMode,
34509
+ spend_policy: resolveCliSpendPolicy(),
34510
+ spend_class: "free",
34511
+ safe_to_retry: true,
34512
+ operator_note: "Compliance is only required before paid FOH-owned phone purchase."
34513
+ };
34514
+ }
34515
+ function isMissingAgentTestsError(error2) {
34516
+ if (!(error2 instanceof FohError)) return false;
34517
+ if (error2.statusCode !== 404) return false;
34518
+ const text = [
34519
+ error2.error,
34520
+ error2.reasonCode,
34521
+ error2.detail?.error,
34522
+ error2.detail?.message,
34523
+ error2.detail?.reason_code
34524
+ ].filter((value) => value !== void 0 && value !== null).join(" ").toLowerCase();
34525
+ return text.includes("no tests found") || text.includes("tests_not_found") || text.includes("agent_tests_not_configured");
34526
+ }
34482
34527
  function buildMissingOptionsPlan(missing, opts) {
34483
34528
  const missingFlags = missing.map(optionNameToFlag);
34484
34529
  const signInUrl = buildConsoleSignInUrl(resolveConsoleBaseUrl(opts.consoleUrl));
@@ -34722,6 +34767,9 @@ function registerSetup(program3) {
34722
34767
  });
34723
34768
  await step("submit_compliance", "Submit standard compliance", async () => {
34724
34769
  if (opts.skipCompliance) return { step: "submit_compliance", status: "skipped", detail: "--skip-compliance" };
34770
+ if (phoneMode !== "purchase") {
34771
+ return { step: "submit_compliance", status: "skipped", detail: complianceSkipDetail(phoneMode) };
34772
+ }
34725
34773
  const status = await apiFetch("/v1/console/org/compliance/status", {
34726
34774
  orgId: opts.org,
34727
34775
  apiUrlOverride: opts.apiUrl
@@ -34742,6 +34790,9 @@ function registerSetup(program3) {
34742
34790
  });
34743
34791
  await step("wait_compliance", "Poll until compliance approved", async () => {
34744
34792
  if (opts.skipCompliance) return { step: "wait_compliance", status: "skipped", detail: "--skip-compliance" };
34793
+ if (phoneMode !== "purchase") {
34794
+ return { step: "wait_compliance", status: "skipped", detail: complianceSkipDetail(phoneMode) };
34795
+ }
34745
34796
  await pollUntil(
34746
34797
  async () => {
34747
34798
  const status = await apiFetch("/v1/console/org/compliance/status", {
@@ -34930,7 +34981,7 @@ function registerSetup(program3) {
34930
34981
  voice: {
34931
34982
  tts_provider: opts.voiceProvider,
34932
34983
  tts_voice_id: opts.voiceId,
34933
- stt_provider: "openai"
34984
+ stt_provider: "deepgram"
34934
34985
  }
34935
34986
  }),
34936
34987
  apiUrlOverride: opts.apiUrl
@@ -34940,10 +34991,30 @@ function registerSetup(program3) {
34940
34991
  await step("run_smoke_test", "Run all agent tests", async () => {
34941
34992
  if (opts.skipTests) return { step: "run_smoke_test", status: "skipped", detail: "--skip-tests" };
34942
34993
  const resolvedAgentId = requireAgentId("run_smoke_test");
34943
- const batch = await apiFetch(`/v1/console/agents/${resolvedAgentId}/tests/run-all`, {
34944
- method: "POST",
34945
- apiUrlOverride: opts.apiUrl
34946
- });
34994
+ let batch;
34995
+ try {
34996
+ batch = await apiFetch(`/v1/console/agents/${resolvedAgentId}/tests/run-all`, {
34997
+ method: "POST",
34998
+ apiUrlOverride: opts.apiUrl
34999
+ });
35000
+ } catch (error2) {
35001
+ if (isMissingAgentTestsError(error2)) {
35002
+ return {
35003
+ step: "run_smoke_test",
35004
+ status: "skipped",
35005
+ detail: {
35006
+ reason_code: "agent_tests_not_configured",
35007
+ safe_to_retry: true,
35008
+ next_commands: [
35009
+ `foh test run --agent ${resolvedAgentId} --json`,
35010
+ `foh prove --agent ${resolvedAgentId} --mission widget --json`
35011
+ ],
35012
+ operator_note: "No saved agent tests exist yet; setup continues to simulation certification and widget proof."
35013
+ }
35014
+ };
35015
+ }
35016
+ throw error2;
35017
+ }
34947
35018
  if (!batch.batch_id) return { step: "run_smoke_test", status: "done" };
34948
35019
  const result = await pollUntil(
34949
35020
  async () => {
@@ -39179,6 +39250,8 @@ var CODEX_EXECUTOR_DENIED_ENV_PREFIXES = [
39179
39250
  "OPENAI_",
39180
39251
  "XAI_",
39181
39252
  "ANTHROPIC_",
39253
+ "GOOGLE_",
39254
+ "GEMINI_",
39182
39255
  "WHATSAPP_",
39183
39256
  "TWILIO_",
39184
39257
  "STRIPE_"
@@ -39382,9 +39455,22 @@ function resolveCodexProbeCommand() {
39382
39455
  function resolveCodexExecutionCommand() {
39383
39456
  return resolveCodexProbeCommand();
39384
39457
  }
39458
+ function resolveGeminiProbeCommand() {
39459
+ if (process.platform !== "win32") return "gemini";
39460
+ const appData = process.env.APPDATA;
39461
+ if (appData) {
39462
+ const appDataShim = (0, import_path13.join)(appData, "npm", "gemini.cmd");
39463
+ if ((0, import_fs15.existsSync)(appDataShim)) return appDataShim;
39464
+ }
39465
+ return "gemini.cmd";
39466
+ }
39467
+ function resolveRunnerExecutionCommand(runner) {
39468
+ return runner === "gemini" ? resolveGeminiProbeCommand() : resolveCodexExecutionCommand();
39469
+ }
39385
39470
  function validateCodexRunner(options) {
39386
39471
  if (options.skipRunnerProbe) {
39387
39472
  return {
39473
+ runner: "codex",
39388
39474
  binaryChecked: false,
39389
39475
  requiredFlagsChecked: false,
39390
39476
  version: null,
@@ -39444,6 +39530,7 @@ ${yoloHelp.stderr}`;
39444
39530
  }
39445
39531
  if (supportsModernApprovalMode) {
39446
39532
  return {
39533
+ runner: "codex",
39447
39534
  binaryChecked: true,
39448
39535
  requiredFlagsChecked: true,
39449
39536
  version: versionText,
@@ -39459,6 +39546,7 @@ ${yoloHelp.stderr}`;
39459
39546
  };
39460
39547
  }
39461
39548
  return {
39549
+ runner: "codex",
39462
39550
  binaryChecked: true,
39463
39551
  requiredFlagsChecked: true,
39464
39552
  version: versionText,
@@ -39473,6 +39561,77 @@ ${yoloHelp.stderr}`;
39473
39561
  execArgs: ["--full-auto"]
39474
39562
  };
39475
39563
  }
39564
+ function validateGeminiRunner(options) {
39565
+ const execArgs = [
39566
+ "--approval-mode",
39567
+ "yolo",
39568
+ "--sandbox",
39569
+ "--output-format",
39570
+ "stream-json",
39571
+ "--prompt",
39572
+ "Execute the FOH external-agent prompt supplied on stdin."
39573
+ ];
39574
+ if (options.skipRunnerProbe) {
39575
+ return {
39576
+ runner: "gemini",
39577
+ binaryChecked: false,
39578
+ requiredFlagsChecked: false,
39579
+ version: null,
39580
+ rootHelpChecked: false,
39581
+ execHelpChecked: false,
39582
+ supportsModernApprovalMode: false,
39583
+ supportsLegacyFullAuto: false,
39584
+ supportsYoloAlias: null,
39585
+ yoloPolicy: "not_checked",
39586
+ supportsGeminiHeadless: true,
39587
+ automationMode: "gemini-yolo",
39588
+ globalArgs: [],
39589
+ execArgs
39590
+ };
39591
+ }
39592
+ const probe = options.runnerProbe ?? defaultRunnerProbe;
39593
+ const probeCommand = resolveGeminiProbeCommand();
39594
+ const version2 = probe(probeCommand, ["--version"]);
39595
+ if (version2.error || version2.status !== 0) {
39596
+ throw new ExternalAgentExecutorError("external_agent_runner_binary_missing", "Gemini runner probe failed: `gemini --version` did not exit 0.");
39597
+ }
39598
+ const versionText = `${version2.stdout}
39599
+ ${version2.stderr}`.trim() || null;
39600
+ const help = probe(probeCommand, ["--help"]);
39601
+ if (help.error || help.status !== 0) {
39602
+ throw new ExternalAgentExecutorError("external_agent_runner_help_unavailable", "Gemini runner probe failed: `gemini --help` did not exit 0.");
39603
+ }
39604
+ const helpText = `${help.stdout}
39605
+ ${help.stderr}`;
39606
+ const requiredFlags = ["--prompt", "--approval-mode", "--sandbox", "--output-format"];
39607
+ const missing = requiredFlags.filter((flag) => !helpText.includes(flag));
39608
+ if (missing.length > 0) {
39609
+ throw new ExternalAgentExecutorError(
39610
+ "external_agent_runner_required_flags_missing",
39611
+ `Gemini runner is missing required headless flag(s): ${missing.join(", ")}`
39612
+ );
39613
+ }
39614
+ return {
39615
+ runner: "gemini",
39616
+ binaryChecked: true,
39617
+ requiredFlagsChecked: true,
39618
+ version: versionText,
39619
+ rootHelpChecked: true,
39620
+ execHelpChecked: true,
39621
+ supportsModernApprovalMode: false,
39622
+ supportsLegacyFullAuto: false,
39623
+ supportsYoloAlias: helpText.includes("--yolo"),
39624
+ yoloPolicy: helpText.includes("--yolo") ? "observed_not_canonical" : "unsupported",
39625
+ supportsGeminiHeadless: true,
39626
+ automationMode: "gemini-yolo",
39627
+ globalArgs: [],
39628
+ execArgs
39629
+ };
39630
+ }
39631
+ function validateRunner(options, runner) {
39632
+ if (runner === "gemini") return validateGeminiRunner(options);
39633
+ return validateCodexRunner(options);
39634
+ }
39476
39635
  function normalizeCodexSandboxBackend(value) {
39477
39636
  const normalized = (value || "default").trim().toLowerCase();
39478
39637
  if (normalized === "default" || normalized === "legacy-landlock") return normalized;
@@ -39520,12 +39679,12 @@ function promptVersionFromPath(promptPath) {
39520
39679
  }
39521
39680
  function createExternalAgentExecutorPlan(options) {
39522
39681
  const runner = String(options.runner || "codex");
39523
- if (runner !== "codex") {
39682
+ if (runner !== "codex" && runner !== "gemini") {
39524
39683
  throw new ExternalAgentExecutorError("unsupported_external_agent_runner", `Unsupported runner: ${runner}`);
39525
39684
  }
39526
39685
  const batchPath = (0, import_path13.resolve)(options.batchPath);
39527
39686
  const batch = readBatch(batchPath);
39528
- const runnerProbe = validateCodexRunner(options);
39687
+ const runnerProbe = validateRunner(options, runner);
39529
39688
  const codexSandboxBackend = normalizeCodexSandboxBackend(options.codexSandboxBackend);
39530
39689
  const codexSandboxMode = normalizeCodexSandboxMode(options.codexSandboxMode);
39531
39690
  const codexNetworkAccess = options.codexNetworkAccess === true;
@@ -39564,12 +39723,16 @@ function createExternalAgentExecutorPlan(options) {
39564
39723
  promptVersion: promptVersionFromPath(promptPath)
39565
39724
  });
39566
39725
  const promptVersion = String(env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] || "unknown");
39567
- const jsonlPath = (0, import_path13.join)(runDir, "codex-exec.jsonl");
39568
- const lastMessagePath = (0, import_path13.join)(runDir, "codex-last-message.md");
39569
- const stderrPath = (0, import_path13.join)(runDir, "codex-stderr.txt");
39726
+ const outputStem = runner === "gemini" ? "gemini" : "codex";
39727
+ const jsonlPath = (0, import_path13.join)(runDir, `${outputStem}-exec.jsonl`);
39728
+ const lastMessagePath = (0, import_path13.join)(runDir, `${outputStem}-last-message.md`);
39729
+ const stderrPath = (0, import_path13.join)(runDir, `${outputStem}-stderr.txt`);
39570
39730
  const runPath = (0, import_path13.join)(runDir, "run.json");
39571
39731
  const artifactSafetyPath = (0, import_path13.join)(runDir, "artifact-safety.json");
39572
- const args = [
39732
+ const args = runner === "gemini" ? [
39733
+ ...runnerProbe.globalArgs,
39734
+ ...runnerProbe.execArgs
39735
+ ] : [
39573
39736
  ...runnerProbe.globalArgs,
39574
39737
  "exec",
39575
39738
  ...codexConfigArgs({ backend: codexSandboxBackend, networkAccess: codexNetworkAccess }),
@@ -39594,7 +39757,7 @@ function createExternalAgentExecutorPlan(options) {
39594
39757
  run_dir: runDir,
39595
39758
  prompt_path: promptPath,
39596
39759
  workspace_dir: workspaceDir,
39597
- command: "codex",
39760
+ command: runner,
39598
39761
  args,
39599
39762
  env_keys: Object.keys(env).sort(),
39600
39763
  outputs: {
@@ -39624,6 +39787,7 @@ function createExternalAgentExecutorPlan(options) {
39624
39787
  denied_env_names: [...CODEX_EXECUTOR_DENIED_ENV_NAMES],
39625
39788
  runner_probe: {
39626
39789
  binary_checked: runnerProbe.binaryChecked,
39790
+ runner: runnerProbe.runner,
39627
39791
  required_flags_checked: runnerProbe.requiredFlagsChecked,
39628
39792
  version: runnerProbe.version,
39629
39793
  root_help_checked: runnerProbe.rootHelpChecked,
@@ -39633,9 +39797,11 @@ function createExternalAgentExecutorPlan(options) {
39633
39797
  supports_yolo_alias: runnerProbe.supportsYoloAlias,
39634
39798
  selected_automation_mode: runnerProbe.automationMode,
39635
39799
  canonical_command_policy: "explicit-flags",
39636
- yolo_policy: runnerProbe.yoloPolicy
39800
+ yolo_policy: runnerProbe.yoloPolicy,
39801
+ ...runnerProbe.runner === "gemini" ? { supports_gemini_headless: runnerProbe.supportsGeminiHeadless } : {}
39637
39802
  },
39638
- codex_automation_mode: runnerProbe.automationMode,
39803
+ runner_automation_mode: runnerProbe.automationMode,
39804
+ codex_automation_mode: runner === "codex" ? runnerProbe.automationMode : null,
39639
39805
  codex_sandbox_mode: codexSandboxMode,
39640
39806
  codex_sandbox_backend: codexSandboxBackend,
39641
39807
  codex_network_access: codexNetworkAccess
@@ -39698,7 +39864,7 @@ function relativeArtifactName(path2) {
39698
39864
  return (0, import_path13.basename)(path2);
39699
39865
  }
39700
39866
  function classifyRun(input) {
39701
- if (input.timedOut) return { status: "hold", reasonCode: "codex_runner_timeout" };
39867
+ if (input.timedOut) return { status: "hold", reasonCode: `${input.run.command}_runner_timeout` };
39702
39868
  if (!input.artifactSafetyOk) return { status: "fail", reasonCode: "external_agent_artifact_safety_blocked" };
39703
39869
  const completedCommands = readCommandRecords(input.run.run_dir).filter((record2) => record2.phase === "completed");
39704
39870
  const observedVersions = completedCommands.map((record2) => String(record2.cli_version || "").trim()).filter((version2) => /^\d+\.\d+\.\d+$/.test(version2));
@@ -39783,7 +39949,7 @@ ${stderr}`;
39783
39949
  if (/browser|approve|approval|login|auth|sign in/i.test(combined) && !proofArtifactPasses(input.run.run_dir)) {
39784
39950
  return { status: "hold", reasonCode: "auth_browser_approval_required" };
39785
39951
  }
39786
- if (input.exitCode !== 0) return { status: "hold", reasonCode: "codex_runner_nonzero_exit" };
39952
+ if (input.exitCode !== 0) return { status: "hold", reasonCode: `${input.run.command}_runner_nonzero_exit` };
39787
39953
  if (proofArtifactPasses(input.run.run_dir)) return { status: "pass", reasonCode: null };
39788
39954
  return { status: "hold", reasonCode: "external_agent_proof_artifact_missing" };
39789
39955
  }
@@ -39797,7 +39963,7 @@ function buildExecutedRunArtifact(input) {
39797
39963
  failure_reason_code: input.reasonCode,
39798
39964
  model_provider: input.run.model_provider,
39799
39965
  model_name: input.run.model_name,
39800
- agent_shell: "codex-exec",
39966
+ agent_shell: `${input.run.command}-exec`,
39801
39967
  workspace_type: "clean-no-repo-programmatic",
39802
39968
  prompt_version: input.run.prompt_version,
39803
39969
  prompt_path: "prompt.txt",
@@ -39847,11 +40013,13 @@ function buildExecutedRunArtifact(input) {
39847
40013
  improvement_packet: input.status === "pass" ? null : "improvement-packet.json",
39848
40014
  agent_metadata: agentMetadata.path,
39849
40015
  notes: (0, import_fs15.existsSync)((0, import_path13.join)(input.run.run_dir, "notes.md")) ? "notes.md" : null,
39850
- codex_last_message: relativeArtifactName(input.run.outputs.last_message),
39851
- codex_stderr: relativeArtifactName(input.run.outputs.stderr),
40016
+ runner_last_message: relativeArtifactName(input.run.outputs.last_message),
40017
+ runner_stderr: relativeArtifactName(input.run.outputs.stderr),
40018
+ codex_last_message: input.run.command === "codex" ? relativeArtifactName(input.run.outputs.last_message) : null,
40019
+ codex_stderr: input.run.command === "codex" ? relativeArtifactName(input.run.outputs.stderr) : null,
39852
40020
  artifact_safety: relativeArtifactName(input.run.outputs.artifact_safety)
39853
40021
  },
39854
- 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}.`,
40022
+ summary: input.status === "pass" ? `Controlled ${input.run.command} external-agent run produced passing proof evidence.` : `Controlled ${input.run.command} external-agent run ended as ${input.status} with reason ${input.reasonCode}.`,
39855
40023
  next_commands: input.status === "pass" ? [externalAgentSummaryCommand((0, import_path13.dirname)(input.run.run_dir))] : [
39856
40024
  "foh eval external-agent scan-artifacts --run-dir <run_dir> --private-repo-root <private_repo_root> --write-redacted --json",
39857
40025
  "foh bug improve --from external-agent-run --file <run_dir>/run.json --out <run_dir>/improvement-packet.json --json",
@@ -39859,7 +40027,7 @@ function buildExecutedRunArtifact(input) {
39859
40027
  ]
39860
40028
  };
39861
40029
  }
39862
- function spawnCodex(input) {
40030
+ function spawnRunner(input) {
39863
40031
  return new Promise((resolveRun) => {
39864
40032
  const started = Date.now();
39865
40033
  const commandInvocation = buildCommandInvocation(input.command, input.args);
@@ -39908,15 +40076,18 @@ function spawnCodex(input) {
39908
40076
  }
39909
40077
  function buildCommandInvocation(command, args) {
39910
40078
  if (process.platform === "win32" && command.toLowerCase().endsWith(".cmd")) {
39911
- const codexEntrypoint = (0, import_path13.join)((0, import_path13.dirname)(command), "node_modules", "@openai", "codex", "bin", "codex.js");
40079
+ const binDir = (0, import_path13.dirname)(command);
40080
+ const codexEntrypoint = (0, import_path13.join)(binDir, "node_modules", "@openai", "codex", "bin", "codex.js");
39912
40081
  if ((0, import_fs15.existsSync)(codexEntrypoint)) return { command: process.execPath, args: [codexEntrypoint, ...args] };
40082
+ const geminiEntrypoint = (0, import_path13.join)(binDir, "node_modules", "@google", "gemini-cli", "bundle", "gemini.js");
40083
+ if ((0, import_fs15.existsSync)(geminiEntrypoint)) return { command: process.execPath, args: ["--no-warnings=DEP0040", geminiEntrypoint, ...args] };
39913
40084
  }
39914
40085
  return { command, args };
39915
40086
  }
39916
40087
  async function executeExternalAgentExecutorPlan(plan, options = {}) {
39917
40088
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
39918
40089
  const results = [];
39919
- const runnerCommand = options.runnerCommand || resolveCodexExecutionCommand();
40090
+ const runnerCommand = options.runnerCommand || resolveRunnerExecutionCommand(plan.runner);
39920
40091
  const authPreflight = options.env ? await runExternalAgentEvalAuthPreflight(options.env) : null;
39921
40092
  if (authPreflight && !authPreflight.ok) {
39922
40093
  const endedAt2 = (/* @__PURE__ */ new Date()).toISOString();
@@ -39966,7 +40137,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
39966
40137
  runDir: commandCaptureDir,
39967
40138
  promptVersion: run.prompt_version
39968
40139
  });
39969
- const spawned = await spawnCodex({
40140
+ const spawned = await spawnRunner({
39970
40141
  command: runnerCommand,
39971
40142
  args: run.args,
39972
40143
  cwd: run.workspace_dir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@f-o-h/cli",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "description": "FOH CLI - AI-operator provisioning tool for Front Of House",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {