@f-o-h/cli 0.1.57 → 0.1.59

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 (2) hide show
  1. package/dist/foh.js +100 -10
  2. package/package.json +1 -1
package/dist/foh.js CHANGED
@@ -32853,7 +32853,7 @@ var StdioServerTransport = class {
32853
32853
  };
32854
32854
 
32855
32855
  // src/lib/cli-version.ts
32856
- var CLI_VERSION = "0.1.57";
32856
+ var CLI_VERSION = "0.1.59";
32857
32857
 
32858
32858
  // src/commands/mcp-serve.ts
32859
32859
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -39244,6 +39244,7 @@ function readExternalAgentMetadata(runDir) {
39244
39244
  }
39245
39245
 
39246
39246
  // src/lib/external-agent-executor.ts
39247
+ var GEMINI_HEADLESS_PROBE_TIMEOUT_MS = 15e3;
39247
39248
  var CODEX_EXECUTOR_DENIED_ENV_PREFIXES = [
39248
39249
  "SUPABASE_",
39249
39250
  "DATABASE_",
@@ -39411,11 +39412,16 @@ function readBatch(batchPath) {
39411
39412
  return parsed;
39412
39413
  }
39413
39414
  function defaultRunnerProbe(command, args) {
39415
+ const isGeminiHeadlessSmoke = args.includes("FOH_GEMINI_HEADLESS_PROBE");
39416
+ const spawnOptions = {
39417
+ encoding: "utf8",
39418
+ timeout: isGeminiHeadlessSmoke ? GEMINI_HEADLESS_PROBE_TIMEOUT_MS : void 0
39419
+ };
39414
39420
  const result = process.platform === "win32" && command.toLowerCase().endsWith(".cmd") ? (0, import_child_process4.spawnSync)(
39415
39421
  "powershell.exe",
39416
39422
  ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", `& ${[command, ...args].map(quotePowerShellArg).join(" ")}`],
39417
- { encoding: "utf8" }
39418
- ) : (0, import_child_process4.spawnSync)(command, args, { encoding: "utf8" });
39423
+ spawnOptions
39424
+ ) : (0, import_child_process4.spawnSync)(command, args, spawnOptions);
39419
39425
  return {
39420
39426
  status: typeof result.status === "number" ? result.status : null,
39421
39427
  stdout: String(result.stdout || ""),
@@ -39423,6 +39429,9 @@ function defaultRunnerProbe(command, args) {
39423
39429
  error: result.error
39424
39430
  };
39425
39431
  }
39432
+ function geminiCapacityUnavailable(text) {
39433
+ return /MODEL_CAPACITY_EXHAUSTED|RESOURCE_EXHAUSTED|No capacity available|rateLimitExceeded|exhausted your capacity|status 429/i.test(text);
39434
+ }
39426
39435
  function quotePowerShellArg(value) {
39427
39436
  return `'${value.replace(/'/g, "''")}'`;
39428
39437
  }
@@ -39562,15 +39571,14 @@ ${yoloHelp.stderr}`;
39562
39571
  };
39563
39572
  }
39564
39573
  function validateGeminiRunner(options) {
39574
+ const geminiSandboxMode = normalizeGeminiSandboxMode(options.geminiSandboxMode);
39565
39575
  const execArgs = [
39566
39576
  "--approval-mode",
39567
39577
  "yolo",
39568
- "--sandbox",
39569
39578
  "--output-format",
39570
- "stream-json",
39571
- "--prompt",
39572
- "Execute the FOH external-agent prompt supplied on stdin."
39579
+ "stream-json"
39573
39580
  ];
39581
+ if (geminiSandboxMode === "required") execArgs.splice(2, 0, "--sandbox");
39574
39582
  if (options.skipRunnerProbe) {
39575
39583
  return {
39576
39584
  runner: "gemini",
@@ -39584,6 +39592,9 @@ function validateGeminiRunner(options) {
39584
39592
  supportsYoloAlias: null,
39585
39593
  yoloPolicy: "not_checked",
39586
39594
  supportsGeminiHeadless: true,
39595
+ supportsGeminiSandbox: geminiSandboxMode === "required",
39596
+ geminiSandboxMode,
39597
+ headlessInvocationChecked: false,
39587
39598
  automationMode: "gemini-yolo",
39588
39599
  globalArgs: [],
39589
39600
  execArgs
@@ -39603,7 +39614,8 @@ ${version2.stderr}`.trim() || null;
39603
39614
  }
39604
39615
  const helpText = `${help.stdout}
39605
39616
  ${help.stderr}`;
39606
- const requiredFlags = ["--prompt", "--approval-mode", "--sandbox", "--output-format"];
39617
+ const requiredFlags = ["--approval-mode", "--output-format"];
39618
+ if (geminiSandboxMode === "required") requiredFlags.push("--sandbox");
39607
39619
  const missing = requiredFlags.filter((flag) => !helpText.includes(flag));
39608
39620
  if (missing.length > 0) {
39609
39621
  throw new ExternalAgentExecutorError(
@@ -39611,6 +39623,39 @@ ${help.stderr}`;
39611
39623
  `Gemini runner is missing required headless flag(s): ${missing.join(", ")}`
39612
39624
  );
39613
39625
  }
39626
+ const smoke = probe(probeCommand, [...execArgs, "FOH_GEMINI_HEADLESS_PROBE"]);
39627
+ const smokeText = `${smoke.stdout}
39628
+ ${smoke.stderr}`;
39629
+ if (/Cannot use both a positional prompt and the --prompt/i.test(smokeText)) {
39630
+ throw new ExternalAgentExecutorError(
39631
+ "external_agent_runner_headless_probe_failed",
39632
+ "Gemini runner rejected the planned headless invocation before auth."
39633
+ );
39634
+ }
39635
+ if (/GEMINI_SANDBOX.*failed|failed to determine command for sandbox|install docker or podman/i.test(smokeText)) {
39636
+ throw new ExternalAgentExecutorError(
39637
+ "external_agent_runner_sandbox_unavailable",
39638
+ "Gemini runner sandbox is unavailable on this host. Install/configure Docker/Podman or rerun only on an externally isolated host with --gemini-sandbox-mode disabled."
39639
+ );
39640
+ }
39641
+ if (geminiCapacityUnavailable(smokeText)) {
39642
+ throw new ExternalAgentExecutorError(
39643
+ "external_agent_runner_capacity_unavailable",
39644
+ "Gemini runner reached the provider but the selected model has no available capacity. Retry later or configure a supported lower-contention Gemini model before live eval."
39645
+ );
39646
+ }
39647
+ if (smoke.error || smoke.status !== 0 && !/Auth method|GEMINI_API_KEY|GOOGLE_GENAI_USE_VERTEXAI|GOOGLE_GENAI_USE_GCA/i.test(smokeText)) {
39648
+ if (smoke.error?.code === "ETIMEDOUT") {
39649
+ throw new ExternalAgentExecutorError(
39650
+ "external_agent_runner_headless_probe_timed_out",
39651
+ "Gemini runner headless invocation probe timed out before reaching an auth boundary or provider response."
39652
+ );
39653
+ }
39654
+ throw new ExternalAgentExecutorError(
39655
+ "external_agent_runner_headless_probe_failed",
39656
+ "Gemini runner headless invocation probe failed before reaching an auth boundary."
39657
+ );
39658
+ }
39614
39659
  return {
39615
39660
  runner: "gemini",
39616
39661
  binaryChecked: true,
@@ -39623,6 +39668,9 @@ ${help.stderr}`;
39623
39668
  supportsYoloAlias: helpText.includes("--yolo"),
39624
39669
  yoloPolicy: helpText.includes("--yolo") ? "observed_not_canonical" : "unsupported",
39625
39670
  supportsGeminiHeadless: true,
39671
+ supportsGeminiSandbox: geminiSandboxMode === "required",
39672
+ geminiSandboxMode,
39673
+ headlessInvocationChecked: true,
39626
39674
  automationMode: "gemini-yolo",
39627
39675
  globalArgs: [],
39628
39676
  execArgs
@@ -39648,6 +39696,14 @@ function normalizeCodexSandboxMode(value) {
39648
39696
  `Unsupported Codex sandbox mode: ${value}. Use workspace-write or danger-full-access.`
39649
39697
  );
39650
39698
  }
39699
+ function normalizeGeminiSandboxMode(value) {
39700
+ const normalized = (value || "required").trim().toLowerCase();
39701
+ if (normalized === "required" || normalized === "disabled") return normalized;
39702
+ throw new ExternalAgentExecutorError(
39703
+ "invalid_gemini_sandbox_mode",
39704
+ `Unsupported Gemini sandbox mode: ${value}. Use required or disabled.`
39705
+ );
39706
+ }
39651
39707
  function codexConfigArgs(input) {
39652
39708
  const args = [];
39653
39709
  if (input.backend === "legacy-landlock") {
@@ -39798,7 +39854,12 @@ function createExternalAgentExecutorPlan(options) {
39798
39854
  selected_automation_mode: runnerProbe.automationMode,
39799
39855
  canonical_command_policy: "explicit-flags",
39800
39856
  yolo_policy: runnerProbe.yoloPolicy,
39801
- ...runnerProbe.runner === "gemini" ? { supports_gemini_headless: runnerProbe.supportsGeminiHeadless } : {}
39857
+ ...runnerProbe.runner === "gemini" ? {
39858
+ supports_gemini_headless: runnerProbe.supportsGeminiHeadless,
39859
+ supports_gemini_sandbox: runnerProbe.supportsGeminiSandbox,
39860
+ gemini_sandbox_mode: runnerProbe.geminiSandboxMode,
39861
+ headless_invocation_checked: runnerProbe.headlessInvocationChecked
39862
+ } : {}
39802
39863
  },
39803
39864
  runner_automation_mode: runnerProbe.automationMode,
39804
39865
  codex_automation_mode: runner === "codex" ? runnerProbe.automationMode : null,
@@ -40276,6 +40337,34 @@ function executorRecoveryCommands(reasonCode, runner) {
40276
40337
  "Upgrade or reinstall the runner CLI on the isolated eval host, then rerun the dry-run executor plan."
40277
40338
  ];
40278
40339
  }
40340
+ if (reasonCode === "external_agent_runner_sandbox_unavailable") {
40341
+ return [
40342
+ "gemini --version",
40343
+ "gemini --help",
40344
+ "Install/configure Docker or Podman for Gemini sandboxing, or rerun only on an externally isolated host with `--gemini-sandbox-mode disabled` after confirming no private repo or runtime secrets are present."
40345
+ ];
40346
+ }
40347
+ if (reasonCode === "external_agent_runner_headless_probe_failed") {
40348
+ return [
40349
+ `${normalizedRunner} --version`,
40350
+ `${normalizedRunner} --help`,
40351
+ "Upgrade, downgrade, or reconfigure the runner CLI until the planned non-interactive invocation reaches an auth boundary without parser errors."
40352
+ ];
40353
+ }
40354
+ if (reasonCode === "external_agent_runner_headless_probe_timed_out") {
40355
+ return [
40356
+ `${normalizedRunner} --version`,
40357
+ `${normalizedRunner} --help`,
40358
+ "Retry the dry-run once. If it repeats, reduce the probe/model contention or use a different subscribed runner before launching live evals."
40359
+ ];
40360
+ }
40361
+ if (reasonCode === "external_agent_runner_capacity_unavailable") {
40362
+ return [
40363
+ "gemini --version",
40364
+ "gemini --help",
40365
+ "Retry after the Gemini capacity window resets, or configure a supported lower-contention Gemini model before rerunning the same executor dry-run."
40366
+ ];
40367
+ }
40279
40368
  return [
40280
40369
  "Fix the executor plan input or workspace path and rerun with --dry-run."
40281
40370
  ];
@@ -40586,7 +40675,7 @@ Exit the shell to finalize run.json.
40586
40675
  }), { json: Boolean(opts.json) });
40587
40676
  if (!report.ok) process.exitCode = 1;
40588
40677
  });
40589
- 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) => {
40678
+ 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("--gemini-sandbox-mode <mode>", "Gemini sandbox mode: required|disabled", "required").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) => {
40590
40679
  try {
40591
40680
  const plan = createExternalAgentExecutorPlan({
40592
40681
  batchPath: String(opts.batch),
@@ -40597,6 +40686,7 @@ Exit the shell to finalize run.json.
40597
40686
  codexSandboxBackend: String(opts.codexSandboxBackend || "default"),
40598
40687
  codexSandboxMode: String(opts.codexSandboxMode || "workspace-write"),
40599
40688
  codexNetworkAccess: Boolean(opts.codexNetworkAccess),
40689
+ geminiSandboxMode: String(opts.geminiSandboxMode || "required"),
40600
40690
  skipRunnerProbe: Boolean(opts.skipRunnerProbe),
40601
40691
  cwd: process.cwd()
40602
40692
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@f-o-h/cli",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
4
4
  "description": "FOH CLI - AI-operator provisioning tool for Front Of House",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {