@f-o-h/cli 0.1.14 → 0.1.16
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 +32 -10
- package/dist/foh.js +153 -19
- package/package.json +1 -1
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.
|
|
7
|
+
Current published baseline: `@f-o-h/cli@0.1.16`
|
|
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.
|
|
@@ -68,6 +68,13 @@ foh agent replay --file ./transcript-export.json --json
|
|
|
68
68
|
foh bug improve --from-file foh-proof.json --out foh-improvement.json --json
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
Trusted server-side automation can use a scoped service token without browser
|
|
72
|
+
approval:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
FOH_SERVICE_TOKEN="$FOH_SERVICE_TOKEN" FOH_ORG_ID="$FOH_ORG_ID" foh auth whoami --json
|
|
76
|
+
```
|
|
77
|
+
|
|
71
78
|
`auth signup --web` opens the console signup page when possible and always
|
|
72
79
|
prints the fallback URL. `auth login --web` starts browser device
|
|
73
80
|
authorization, opens `/cli-auth`, waits for console approval, and stores the
|
|
@@ -137,15 +144,30 @@ After dry-run review, one controlled Codex run can be launched explicitly with
|
|
|
137
144
|
`run.json` even on timeout or non-zero exit:
|
|
138
145
|
|
|
139
146
|
```bash
|
|
140
|
-
foh eval external-agent execute \
|
|
141
|
-
--runner codex \
|
|
142
|
-
--batch test-results/external-agent-runs/<one-model-batch>/batch.json \
|
|
143
|
-
--timeout-minutes 30 \
|
|
144
|
-
--live \
|
|
145
|
-
--json
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
foh eval external-agent execute \
|
|
148
|
+
--runner codex \
|
|
149
|
+
--batch test-results/external-agent-runs/<one-model-batch>/batch.json \
|
|
150
|
+
--timeout-minutes 30 \
|
|
151
|
+
--live \
|
|
152
|
+
--json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For Linux eval hosts where Codex's default Bubblewrap sandbox cannot configure
|
|
156
|
+
loopback networking, keep sandboxing enabled and select the legacy Landlock
|
|
157
|
+
backend explicitly:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
foh eval external-agent execute \
|
|
161
|
+
--runner codex \
|
|
162
|
+
--batch test-results/external-agent-runs/<one-model-batch>/batch.json \
|
|
163
|
+
--codex-sandbox-backend legacy-landlock \
|
|
164
|
+
--codex-network-access \
|
|
165
|
+
--timeout-minutes 30 \
|
|
166
|
+
--live \
|
|
167
|
+
--json
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Local Scenario Suites
|
|
149
171
|
|
|
150
172
|
`foh test run --suite <file>` runs deterministic widget-runtime checks for a
|
|
151
173
|
specific agent. The suite format supports reply text checks plus structured
|
package/dist/foh.js
CHANGED
|
@@ -10029,9 +10029,56 @@ function resolveApiBaseUrl(cliOverride) {
|
|
|
10029
10029
|
return resolveApiUrlOverride(cliOverride) ?? DEFAULT_FOH_API_URL;
|
|
10030
10030
|
}
|
|
10031
10031
|
|
|
10032
|
+
// src/lib/org-id.ts
|
|
10033
|
+
function isUsableOrgId(value) {
|
|
10034
|
+
const orgId = String(value ?? "").trim();
|
|
10035
|
+
return Boolean(orgId);
|
|
10036
|
+
}
|
|
10037
|
+
|
|
10032
10038
|
// src/lib/credentials.ts
|
|
10033
10039
|
var CONFIG_PATH = (0, import_path.join)((0, import_os.homedir)(), ".config", "foh", "credentials.json");
|
|
10040
|
+
var SERVICE_TOKEN_ENV = "FOH_SERVICE_TOKEN";
|
|
10041
|
+
var ORG_ID_ENV = "FOH_ORG_ID";
|
|
10042
|
+
var API_URL_ENV = "FOH_API_URL";
|
|
10043
|
+
var TOKEN_EXPIRES_AT_ENV = "FOH_TOKEN_EXPIRES_AT";
|
|
10044
|
+
function normalizeEnv(value) {
|
|
10045
|
+
const normalized = String(value ?? "").trim();
|
|
10046
|
+
return normalized ? normalized : void 0;
|
|
10047
|
+
}
|
|
10048
|
+
function decodeBase64UrlJson(value) {
|
|
10049
|
+
try {
|
|
10050
|
+
const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
|
|
10051
|
+
return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
10052
|
+
} catch {
|
|
10053
|
+
return void 0;
|
|
10054
|
+
}
|
|
10055
|
+
}
|
|
10056
|
+
function expiryFromJwt(token) {
|
|
10057
|
+
const [, payload] = token.split(".");
|
|
10058
|
+
if (!payload) return void 0;
|
|
10059
|
+
const decoded = decodeBase64UrlJson(payload);
|
|
10060
|
+
const exp = Number(decoded?.exp);
|
|
10061
|
+
if (!Number.isFinite(exp) || exp <= 0) return void 0;
|
|
10062
|
+
return new Date(exp * 1e3).toISOString();
|
|
10063
|
+
}
|
|
10064
|
+
function fallbackExpiry() {
|
|
10065
|
+
return new Date(Date.now() + 60 * 60 * 1e3).toISOString();
|
|
10066
|
+
}
|
|
10067
|
+
function credentialsFromEnv(apiUrlOverride, env = process.env) {
|
|
10068
|
+
const token = normalizeEnv(env[SERVICE_TOKEN_ENV]);
|
|
10069
|
+
if (!token) return void 0;
|
|
10070
|
+
const apiUrl = resolveApiUrlOverride(apiUrlOverride) ?? normalizeEnv(env[API_URL_ENV]) ?? DEFAULT_FOH_API_URL;
|
|
10071
|
+
const orgId = normalizeEnv(env[ORG_ID_ENV]);
|
|
10072
|
+
return {
|
|
10073
|
+
apiUrl,
|
|
10074
|
+
token,
|
|
10075
|
+
expiresAt: normalizeEnv(env[TOKEN_EXPIRES_AT_ENV]) ?? expiryFromJwt(token) ?? fallbackExpiry(),
|
|
10076
|
+
...isUsableOrgId(orgId) ? { orgId } : {}
|
|
10077
|
+
};
|
|
10078
|
+
}
|
|
10034
10079
|
function loadCredentials(apiUrlOverride) {
|
|
10080
|
+
const envCreds = credentialsFromEnv(apiUrlOverride);
|
|
10081
|
+
if (envCreds) return envCreds;
|
|
10035
10082
|
let creds;
|
|
10036
10083
|
try {
|
|
10037
10084
|
const raw = (0, import_fs.readFileSync)(CONFIG_PATH, "utf-8");
|
|
@@ -10059,12 +10106,6 @@ function clearCredentials() {
|
|
|
10059
10106
|
}
|
|
10060
10107
|
}
|
|
10061
10108
|
|
|
10062
|
-
// src/lib/org-id.ts
|
|
10063
|
-
function isUsableOrgId(value) {
|
|
10064
|
-
const orgId = String(value ?? "").trim();
|
|
10065
|
-
return Boolean(orgId);
|
|
10066
|
-
}
|
|
10067
|
-
|
|
10068
10109
|
// src/lib/command-runtime.ts
|
|
10069
10110
|
function markCommandFailed(code = 1) {
|
|
10070
10111
|
const normalized = Number.isFinite(code) ? Math.max(1, Math.trunc(code)) : 1;
|
|
@@ -16034,9 +16075,9 @@ function registerVoice(program3) {
|
|
|
16034
16075
|
const outputPath = String(opts.out || `foh-voice-preview-${provider}-${voiceId}.mp3`).trim();
|
|
16035
16076
|
const audio = Buffer.from(await res.arrayBuffer());
|
|
16036
16077
|
const { mkdirSync: mkdirSync7, writeFileSync: writeFileSync9 } = await import("fs");
|
|
16037
|
-
const { dirname:
|
|
16078
|
+
const { dirname: dirname6, resolve: resolve12 } = await import("path");
|
|
16038
16079
|
const absolutePath = resolve12(outputPath);
|
|
16039
|
-
mkdirSync7(
|
|
16080
|
+
mkdirSync7(dirname6(absolutePath), { recursive: true });
|
|
16040
16081
|
writeFileSync9(absolutePath, audio);
|
|
16041
16082
|
format({
|
|
16042
16083
|
status: "ok",
|
|
@@ -32640,7 +32681,7 @@ var StdioServerTransport = class {
|
|
|
32640
32681
|
};
|
|
32641
32682
|
|
|
32642
32683
|
// src/lib/cli-version.ts
|
|
32643
|
-
var CLI_VERSION = "0.1.
|
|
32684
|
+
var CLI_VERSION = "0.1.16";
|
|
32644
32685
|
|
|
32645
32686
|
// src/commands/mcp-serve.ts
|
|
32646
32687
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -38281,7 +38322,8 @@ var TEXT_ARTIFACT_NAMES = /* @__PURE__ */ new Set([
|
|
|
38281
38322
|
"run.json",
|
|
38282
38323
|
"terminal-transcript.txt"
|
|
38283
38324
|
]);
|
|
38284
|
-
var SECRET_RE2 = /\b(?:Bearer\s+
|
|
38325
|
+
var SECRET_RE2 = /\b(?:Bearer\s+[A-Za-z0-9_\-.]{12,}|(?:sk|pk|xai|whsec|EAAN|ghp|gho|github_pat)[A-Za-z0-9_\-.]{12,}|npm_[A-Za-z0-9]{20,})\b/g;
|
|
38326
|
+
var SECRET_QUERY_PARAM_RE = /\b((?:device_code|access_token|refresh_token|api_key|token)=)[A-Za-z0-9_.~-]{12,}/gi;
|
|
38285
38327
|
var EMAIL_RE2 = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
38286
38328
|
var PHONE_RE2 = /(?<!\w)(?:\+?\d[\d\s().-]{7,}\d)(?!\w)/g;
|
|
38287
38329
|
function escapeRegExp(value) {
|
|
@@ -38308,6 +38350,9 @@ function matchesFor(pattern, text) {
|
|
|
38308
38350
|
const matches = text.match(pattern);
|
|
38309
38351
|
return matches ? matches.length : 0;
|
|
38310
38352
|
}
|
|
38353
|
+
function secretMatches(text) {
|
|
38354
|
+
return matchesFor(SECRET_RE2, text) + matchesFor(SECRET_QUERY_PARAM_RE, text);
|
|
38355
|
+
}
|
|
38311
38356
|
function redactionPatterns(input) {
|
|
38312
38357
|
return {
|
|
38313
38358
|
secret: SECRET_RE2,
|
|
@@ -38319,11 +38364,14 @@ function redactionPatterns(input) {
|
|
|
38319
38364
|
}
|
|
38320
38365
|
function redactExternalAgentArtifactText(text, input = {}) {
|
|
38321
38366
|
const patterns = redactionPatterns(input);
|
|
38322
|
-
let redacted = text
|
|
38367
|
+
let redacted = redactExternalAgentSecretText(text).replace(patterns.email, "[redacted_email]").replace(patterns.phone, "[redacted_phone]");
|
|
38323
38368
|
if (patterns.privateRepo) redacted = redacted.replace(patterns.privateRepo, "[redacted_private_repo_path]");
|
|
38324
38369
|
if (patterns.home) redacted = redacted.replace(patterns.home, "[redacted_home_path]");
|
|
38325
38370
|
return redacted;
|
|
38326
38371
|
}
|
|
38372
|
+
function redactExternalAgentSecretText(text) {
|
|
38373
|
+
return text.replace(SECRET_QUERY_PARAM_RE, "$1[redacted_secret]").replace(SECRET_RE2, "[redacted_secret]");
|
|
38374
|
+
}
|
|
38327
38375
|
function artifactFiles(runDir) {
|
|
38328
38376
|
if (!(0, import_fs12.existsSync)(runDir)) return [];
|
|
38329
38377
|
return (0, import_fs12.readdirSync)(runDir).map((name) => (0, import_path10.join)(runDir, name)).filter((path2) => {
|
|
@@ -38343,7 +38391,7 @@ function finding(kind, file2, count) {
|
|
|
38343
38391
|
function scanText(input) {
|
|
38344
38392
|
const patterns = redactionPatterns(input);
|
|
38345
38393
|
return [
|
|
38346
|
-
finding("secret_like_token", input.file,
|
|
38394
|
+
finding("secret_like_token", input.file, secretMatches(input.text)),
|
|
38347
38395
|
finding("private_repo_path", input.file, matchesFor(patterns.privateRepo, input.text)),
|
|
38348
38396
|
finding("local_home_path", input.file, matchesFor(patterns.home, input.text)),
|
|
38349
38397
|
finding("email_address", input.file, matchesFor(patterns.email, input.text)),
|
|
@@ -38483,6 +38531,12 @@ var CHILD_ENV_ALLOWLIST = [
|
|
|
38483
38531
|
"USERPROFILE",
|
|
38484
38532
|
"WINDIR"
|
|
38485
38533
|
];
|
|
38534
|
+
var EXTERNAL_AGENT_EVAL_AUTH_ENV_MAP = {
|
|
38535
|
+
FOH_EXTERNAL_AGENT_EVAL_TOKEN: "FOH_SERVICE_TOKEN",
|
|
38536
|
+
FOH_EXTERNAL_AGENT_EVAL_ORG_ID: "FOH_ORG_ID",
|
|
38537
|
+
FOH_EXTERNAL_AGENT_EVAL_API_URL: "FOH_API_URL",
|
|
38538
|
+
FOH_EXTERNAL_AGENT_EVAL_TOKEN_EXPIRES_AT: "FOH_TOKEN_EXPIRES_AT"
|
|
38539
|
+
};
|
|
38486
38540
|
var ExternalAgentExecutorError = class extends Error {
|
|
38487
38541
|
reasonCode;
|
|
38488
38542
|
constructor(reasonCode, message) {
|
|
@@ -38505,6 +38559,12 @@ function buildCodexExecutorEnv(input) {
|
|
|
38505
38559
|
env[key] = value;
|
|
38506
38560
|
}
|
|
38507
38561
|
}
|
|
38562
|
+
for (const [sourceKey, childKey] of Object.entries(EXTERNAL_AGENT_EVAL_AUTH_ENV_MAP)) {
|
|
38563
|
+
const value = source[sourceKey];
|
|
38564
|
+
if (typeof value === "string" && value.trim() && !isDeniedEnvKey(childKey)) {
|
|
38565
|
+
env[childKey] = value;
|
|
38566
|
+
}
|
|
38567
|
+
}
|
|
38508
38568
|
env[EXTERNAL_AGENT_RUN_DIR_ENV] = input.runDir;
|
|
38509
38569
|
env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] = input.promptVersion;
|
|
38510
38570
|
env.FOH_CLI_SUPPRESS_BANNER = "1";
|
|
@@ -38602,6 +38662,29 @@ ${help.stderr}`;
|
|
|
38602
38662
|
}
|
|
38603
38663
|
return { binaryChecked: true, requiredFlagsChecked: true };
|
|
38604
38664
|
}
|
|
38665
|
+
function normalizeCodexSandboxBackend(value) {
|
|
38666
|
+
const normalized = (value || "default").trim().toLowerCase();
|
|
38667
|
+
if (normalized === "default" || normalized === "legacy-landlock") return normalized;
|
|
38668
|
+
throw new ExternalAgentExecutorError(
|
|
38669
|
+
"invalid_codex_sandbox_backend",
|
|
38670
|
+
`Unsupported Codex sandbox backend: ${value}. Use default or legacy-landlock.`
|
|
38671
|
+
);
|
|
38672
|
+
}
|
|
38673
|
+
function codexConfigArgs(input) {
|
|
38674
|
+
const args = [];
|
|
38675
|
+
if (input.backend === "legacy-landlock") {
|
|
38676
|
+
args.push(
|
|
38677
|
+
"-c",
|
|
38678
|
+
"features.use_legacy_landlock=true",
|
|
38679
|
+
"-c",
|
|
38680
|
+
"features.use_linux_sandbox_bwrap=false"
|
|
38681
|
+
);
|
|
38682
|
+
}
|
|
38683
|
+
if (input.networkAccess) {
|
|
38684
|
+
args.push("-c", "sandbox_workspace_write.network_access=true");
|
|
38685
|
+
}
|
|
38686
|
+
return args;
|
|
38687
|
+
}
|
|
38605
38688
|
function safeRunId(value) {
|
|
38606
38689
|
return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
38607
38690
|
}
|
|
@@ -38624,6 +38707,8 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
38624
38707
|
const batchPath = (0, import_path12.resolve)(options.batchPath);
|
|
38625
38708
|
const batch = readBatch(batchPath);
|
|
38626
38709
|
const runnerProbe = validateCodexRunner(options);
|
|
38710
|
+
const codexSandboxBackend = normalizeCodexSandboxBackend(options.codexSandboxBackend);
|
|
38711
|
+
const codexNetworkAccess = options.codexNetworkAccess === true;
|
|
38627
38712
|
const privateRepoRoot = (0, import_path12.resolve)(options.privateRepoRoot || options.cwd || process.cwd());
|
|
38628
38713
|
const workspaceRoot = resolveWorkspaceRoot({ batchPath, workspaceRoot: options.workspaceRoot, privateRepoRoot });
|
|
38629
38714
|
if (isPathInside(workspaceRoot, privateRepoRoot)) {
|
|
@@ -38666,12 +38751,12 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
38666
38751
|
const artifactSafetyPath = (0, import_path12.join)(runDir, "artifact-safety.json");
|
|
38667
38752
|
const args = [
|
|
38668
38753
|
"exec",
|
|
38754
|
+
...codexConfigArgs({ backend: codexSandboxBackend, networkAccess: codexNetworkAccess }),
|
|
38669
38755
|
"--cd",
|
|
38670
38756
|
workspaceDir,
|
|
38671
38757
|
"--skip-git-repo-check",
|
|
38672
38758
|
"--ephemeral",
|
|
38673
38759
|
"--ignore-rules",
|
|
38674
|
-
"--ignore-user-config",
|
|
38675
38760
|
"--sandbox",
|
|
38676
38761
|
"workspace-write",
|
|
38677
38762
|
"--full-auto",
|
|
@@ -38719,7 +38804,9 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
38719
38804
|
runner_probe: {
|
|
38720
38805
|
binary_checked: runnerProbe.binaryChecked,
|
|
38721
38806
|
required_flags_checked: runnerProbe.requiredFlagsChecked
|
|
38722
|
-
}
|
|
38807
|
+
},
|
|
38808
|
+
codex_sandbox_backend: codexSandboxBackend,
|
|
38809
|
+
codex_network_access: codexNetworkAccess
|
|
38723
38810
|
},
|
|
38724
38811
|
runs
|
|
38725
38812
|
};
|
|
@@ -38744,6 +38831,22 @@ function proofArtifactPasses(runDir) {
|
|
|
38744
38831
|
function readIfExists(path2) {
|
|
38745
38832
|
return (0, import_fs14.existsSync)(path2) ? (0, import_fs14.readFileSync)(path2, "utf8") : "";
|
|
38746
38833
|
}
|
|
38834
|
+
function redactSecretLikeFile(path2) {
|
|
38835
|
+
if (!(0, import_fs14.existsSync)(path2)) return;
|
|
38836
|
+
const original = (0, import_fs14.readFileSync)(path2, "utf8");
|
|
38837
|
+
const redacted = redactExternalAgentSecretText(original);
|
|
38838
|
+
if (redacted !== original) (0, import_fs14.writeFileSync)(path2, redacted, "utf8");
|
|
38839
|
+
}
|
|
38840
|
+
function redactSecretLikeOutputArtifacts(run) {
|
|
38841
|
+
redactSecretLikeFile(run.outputs.jsonl);
|
|
38842
|
+
redactSecretLikeFile(run.outputs.last_message);
|
|
38843
|
+
redactSecretLikeFile(run.outputs.stderr);
|
|
38844
|
+
}
|
|
38845
|
+
function copyCommandCaptureArtifacts(input) {
|
|
38846
|
+
const commandLog = (0, import_path12.join)(input.captureDir, "commands.ndjson");
|
|
38847
|
+
if (!(0, import_fs14.existsSync)(commandLog)) return;
|
|
38848
|
+
(0, import_fs14.writeFileSync)((0, import_path12.join)(input.runDir, "commands.ndjson"), (0, import_fs14.readFileSync)(commandLog, "utf8"), "utf8");
|
|
38849
|
+
}
|
|
38747
38850
|
function relativeArtifactName(path2) {
|
|
38748
38851
|
return (0, import_path12.basename)(path2);
|
|
38749
38852
|
}
|
|
@@ -38760,6 +38863,24 @@ ${stderr}`;
|
|
|
38760
38863
|
if (/(?:blocked|rejected|declined) by policy|EXEC_POLICY_BLOCKED|command execution was rejected|shell commands were rejected/i.test(combined)) {
|
|
38761
38864
|
return { status: "hold", reasonCode: "codex_exec_policy_blocked" };
|
|
38762
38865
|
}
|
|
38866
|
+
if (/bwrap:.*(?:RTM_NEWADDR|Operation not permitted)|bubblewrap.*(?:RTM_NEWADDR|Operation not permitted)|Failed RTM_NEWADDR|ENV_SANDBOX_EXEC_BLOCKED/i.test(combined)) {
|
|
38867
|
+
return { status: "hold", reasonCode: "codex_sandbox_exec_blocked" };
|
|
38868
|
+
}
|
|
38869
|
+
if (/ENV_NETWORK_DNS_BLOCK|Could not resolve host|npm ping.*timeout|NO_EXECUTABLE_INSTALL/i.test(combined)) {
|
|
38870
|
+
return { status: "hold", reasonCode: "codex_network_dns_blocked" };
|
|
38871
|
+
}
|
|
38872
|
+
if (/contact_phone_missing/i.test(combined)) {
|
|
38873
|
+
return { status: "hold", reasonCode: "voice_contact_phone_missing" };
|
|
38874
|
+
}
|
|
38875
|
+
if (/simulation_certification_failed/i.test(combined)) {
|
|
38876
|
+
return { status: "hold", reasonCode: "simulation_certification_failed" };
|
|
38877
|
+
}
|
|
38878
|
+
if (/proof_held/i.test(combined)) {
|
|
38879
|
+
return { status: "hold", reasonCode: "external_agent_proof_held" };
|
|
38880
|
+
}
|
|
38881
|
+
if (/agent_limit_reached/i.test(combined)) {
|
|
38882
|
+
return { status: "hold", reasonCode: "eval_org_agent_limit_reached" };
|
|
38883
|
+
}
|
|
38763
38884
|
if (/browser|approve|approval|login|auth|sign in/i.test(combined) && !proofArtifactPasses(input.run.run_dir)) {
|
|
38764
38885
|
return { status: "hold", reasonCode: "auth_browser_approval_required" };
|
|
38765
38886
|
}
|
|
@@ -38824,11 +38945,11 @@ function buildExecutedRunArtifact(input) {
|
|
|
38824
38945
|
function spawnCodex(input) {
|
|
38825
38946
|
return new Promise((resolveRun) => {
|
|
38826
38947
|
const started = Date.now();
|
|
38827
|
-
const
|
|
38828
|
-
const child = (0, import_child_process4.spawn)(
|
|
38948
|
+
const commandInvocation = buildCommandInvocation(input.command, input.args);
|
|
38949
|
+
const child = (0, import_child_process4.spawn)(commandInvocation.command, commandInvocation.args, {
|
|
38829
38950
|
cwd: input.cwd,
|
|
38830
38951
|
env: input.env,
|
|
38831
|
-
shell:
|
|
38952
|
+
shell: false,
|
|
38832
38953
|
stdio: ["pipe", "pipe", "pipe"],
|
|
38833
38954
|
windowsHide: true
|
|
38834
38955
|
});
|
|
@@ -38868,15 +38989,24 @@ function spawnCodex(input) {
|
|
|
38868
38989
|
});
|
|
38869
38990
|
});
|
|
38870
38991
|
}
|
|
38992
|
+
function buildCommandInvocation(command, args) {
|
|
38993
|
+
if (process.platform === "win32" && command.toLowerCase().endsWith(".cmd")) {
|
|
38994
|
+
const codexEntrypoint = (0, import_path12.join)((0, import_path12.dirname)(command), "node_modules", "@openai", "codex", "bin", "codex.js");
|
|
38995
|
+
if ((0, import_fs14.existsSync)(codexEntrypoint)) return { command: process.execPath, args: [codexEntrypoint, ...args] };
|
|
38996
|
+
}
|
|
38997
|
+
return { command, args };
|
|
38998
|
+
}
|
|
38871
38999
|
async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
38872
39000
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38873
39001
|
const results = [];
|
|
38874
39002
|
const runnerCommand = options.runnerCommand || resolveCodexExecutionCommand();
|
|
38875
39003
|
for (const run of plan.runs) {
|
|
38876
39004
|
const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39005
|
+
const commandCaptureDir = (0, import_path12.join)(run.workspace_dir, ".foh-capture");
|
|
39006
|
+
(0, import_fs14.mkdirSync)(commandCaptureDir, { recursive: true });
|
|
38877
39007
|
const env = buildCodexExecutorEnv({
|
|
38878
39008
|
sourceEnv: options.env,
|
|
38879
|
-
runDir:
|
|
39009
|
+
runDir: commandCaptureDir,
|
|
38880
39010
|
promptVersion: run.prompt_version
|
|
38881
39011
|
});
|
|
38882
39012
|
const spawned = await spawnCodex({
|
|
@@ -38889,6 +39019,8 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
38889
39019
|
stderrPath: run.outputs.stderr,
|
|
38890
39020
|
timeoutMs: plan.timeout_minutes * 60 * 1e3
|
|
38891
39021
|
});
|
|
39022
|
+
copyCommandCaptureArtifacts({ captureDir: commandCaptureDir, runDir: run.run_dir });
|
|
39023
|
+
redactSecretLikeOutputArtifacts(run);
|
|
38892
39024
|
const artifactSafety = scanExternalAgentArtifacts({
|
|
38893
39025
|
runDir: run.run_dir,
|
|
38894
39026
|
privateRepoRoot: options.privateRepoRoot || plan.private_repo_root,
|
|
@@ -39212,7 +39344,7 @@ Exit the shell to finalize run.json.
|
|
|
39212
39344
|
}), { json: Boolean(opts.json) });
|
|
39213
39345
|
if (!report.ok) process.exitCode = 1;
|
|
39214
39346
|
});
|
|
39215
|
-
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("--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) => {
|
|
39347
|
+
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) => {
|
|
39216
39348
|
try {
|
|
39217
39349
|
const plan = createExternalAgentExecutorPlan({
|
|
39218
39350
|
batchPath: String(opts.batch),
|
|
@@ -39220,6 +39352,8 @@ Exit the shell to finalize run.json.
|
|
|
39220
39352
|
workspaceRoot: opts.workspaceRoot ? String(opts.workspaceRoot) : void 0,
|
|
39221
39353
|
privateRepoRoot: opts.privateRepoRoot ? String(opts.privateRepoRoot) : void 0,
|
|
39222
39354
|
timeoutMinutes: Number(opts.timeoutMinutes || 30),
|
|
39355
|
+
codexSandboxBackend: String(opts.codexSandboxBackend || "default"),
|
|
39356
|
+
codexNetworkAccess: Boolean(opts.codexNetworkAccess),
|
|
39223
39357
|
skipRunnerProbe: Boolean(opts.skipRunnerProbe),
|
|
39224
39358
|
cwd: process.cwd()
|
|
39225
39359
|
});
|