@f-o-h/cli 0.1.13 → 0.1.15
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 +17 -10
- package/dist/foh.js +114 -20
- 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.15`
|
|
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.
|
|
@@ -62,15 +62,22 @@ foh auth login --email "$FOH_EMAIL" --password "$FOH_PASSWORD" --json
|
|
|
62
62
|
foh org list --json
|
|
63
63
|
foh org use --org <org-id> --json
|
|
64
64
|
foh setup --org <org-id> --agent-template <template-id> --agent-name "Demo Agent" --json
|
|
65
|
-
foh prove --agent <agent-id> --json --out foh-proof.json
|
|
66
|
-
foh test run --suite ./suite.yml --agent <agent-id> --json --out foh-test-report.json
|
|
67
|
-
foh agent replay --file ./transcript-export.json --json
|
|
68
|
-
foh bug improve --from-file foh-proof.json --out foh-improvement.json --json
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
foh prove --agent <agent-id> --json --out foh-proof.json
|
|
66
|
+
foh test run --suite ./suite.yml --agent <agent-id> --json --out foh-test-report.json
|
|
67
|
+
foh agent replay --file ./transcript-export.json --json
|
|
68
|
+
foh bug improve --from-file foh-proof.json --out foh-improvement.json --json
|
|
69
|
+
```
|
|
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
|
+
|
|
78
|
+
`auth signup --web` opens the console signup page when possible and always
|
|
79
|
+
prints the fallback URL. `auth login --web` starts browser device
|
|
80
|
+
authorization, opens `/cli-auth`, waits for console approval, and stores the
|
|
74
81
|
returned short-lived token. Credential auth remains available as fallback.
|
|
75
82
|
|
|
76
83
|
`foh prove` produces a compact signed proof report across auth, org context,
|
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.15";
|
|
32644
32685
|
|
|
32645
32686
|
// src/commands/mcp-serve.ts
|
|
32646
32687
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -36437,8 +36478,13 @@ function inferReasonCode(artifact) {
|
|
|
36437
36478
|
}
|
|
36438
36479
|
return nonEmpty2(getPath2(artifact, "status"));
|
|
36439
36480
|
}
|
|
36440
|
-
function inferPromotionDecision(sourceType) {
|
|
36441
|
-
|
|
36481
|
+
function inferPromotionDecision(sourceType, reasonCode) {
|
|
36482
|
+
const reason = String(reasonCode || "").toLowerCase();
|
|
36483
|
+
if (sourceType === "external_agent_run") {
|
|
36484
|
+
if (reason.includes("exec_policy") || reason.includes("policy_blocked") || reason.includes("auth") || reason.includes("config")) return "fix_config";
|
|
36485
|
+
if (reason.includes("cli") || reason.includes("command") || reason.includes("flag")) return "fix_cli";
|
|
36486
|
+
return "fix_docs";
|
|
36487
|
+
}
|
|
36442
36488
|
if (sourceType === "knowledge_miss") return "fix_docs";
|
|
36443
36489
|
if (sourceType === "setup_failure" || sourceType === "proof_failure" || sourceType === "live_proof_failure") return "fix_config";
|
|
36444
36490
|
if (sourceType === "replay_failure" || sourceType === "runtime_miss") return "add_test";
|
|
@@ -36526,7 +36572,6 @@ function readSourceArtifact(path2) {
|
|
|
36526
36572
|
function buildImprovementPacket(input) {
|
|
36527
36573
|
const artifact = input.sourceArtifact ?? null;
|
|
36528
36574
|
const sourceType = parseEnum(input.sourceType, IMPROVEMENT_SOURCE_TYPES, "--source-type") ?? inferSourceType(artifact);
|
|
36529
|
-
const promotionDecision = parseEnum(input.promotionDecision, IMPROVEMENT_DECISIONS, "--recommendation") ?? inferPromotionDecision(sourceType);
|
|
36530
36575
|
const ids = collectIds(artifact, input.ids);
|
|
36531
36576
|
assertOrgBoundary(artifact, input.ids?.org_id);
|
|
36532
36577
|
const reasonCode = nonEmpty2(input.reasonCode) ?? inferReasonCode(artifact);
|
|
@@ -36538,6 +36583,7 @@ function buildImprovementPacket(input) {
|
|
|
36538
36583
|
statusCode: 400
|
|
36539
36584
|
});
|
|
36540
36585
|
}
|
|
36586
|
+
const promotionDecision = parseEnum(input.promotionDecision, IMPROVEMENT_DECISIONS, "--recommendation") ?? inferPromotionDecision(sourceType, reasonCode);
|
|
36541
36587
|
const evidenceSummary = redactString(
|
|
36542
36588
|
nonEmpty2(input.evidenceSummary) ?? nonEmpty2(getPath2(artifact, "summary")) ?? `Improvement candidate generated from ${sourceType} with reason ${reasonCode}.`
|
|
36543
36589
|
);
|
|
@@ -38276,7 +38322,8 @@ var TEXT_ARTIFACT_NAMES = /* @__PURE__ */ new Set([
|
|
|
38276
38322
|
"run.json",
|
|
38277
38323
|
"terminal-transcript.txt"
|
|
38278
38324
|
]);
|
|
38279
|
-
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;
|
|
38280
38327
|
var EMAIL_RE2 = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
38281
38328
|
var PHONE_RE2 = /(?<!\w)(?:\+?\d[\d\s().-]{7,}\d)(?!\w)/g;
|
|
38282
38329
|
function escapeRegExp(value) {
|
|
@@ -38303,6 +38350,9 @@ function matchesFor(pattern, text) {
|
|
|
38303
38350
|
const matches = text.match(pattern);
|
|
38304
38351
|
return matches ? matches.length : 0;
|
|
38305
38352
|
}
|
|
38353
|
+
function secretMatches(text) {
|
|
38354
|
+
return matchesFor(SECRET_RE2, text) + matchesFor(SECRET_QUERY_PARAM_RE, text);
|
|
38355
|
+
}
|
|
38306
38356
|
function redactionPatterns(input) {
|
|
38307
38357
|
return {
|
|
38308
38358
|
secret: SECRET_RE2,
|
|
@@ -38314,11 +38364,14 @@ function redactionPatterns(input) {
|
|
|
38314
38364
|
}
|
|
38315
38365
|
function redactExternalAgentArtifactText(text, input = {}) {
|
|
38316
38366
|
const patterns = redactionPatterns(input);
|
|
38317
|
-
let redacted = text
|
|
38367
|
+
let redacted = redactExternalAgentSecretText(text).replace(patterns.email, "[redacted_email]").replace(patterns.phone, "[redacted_phone]");
|
|
38318
38368
|
if (patterns.privateRepo) redacted = redacted.replace(patterns.privateRepo, "[redacted_private_repo_path]");
|
|
38319
38369
|
if (patterns.home) redacted = redacted.replace(patterns.home, "[redacted_home_path]");
|
|
38320
38370
|
return redacted;
|
|
38321
38371
|
}
|
|
38372
|
+
function redactExternalAgentSecretText(text) {
|
|
38373
|
+
return text.replace(SECRET_QUERY_PARAM_RE, "$1[redacted_secret]").replace(SECRET_RE2, "[redacted_secret]");
|
|
38374
|
+
}
|
|
38322
38375
|
function artifactFiles(runDir) {
|
|
38323
38376
|
if (!(0, import_fs12.existsSync)(runDir)) return [];
|
|
38324
38377
|
return (0, import_fs12.readdirSync)(runDir).map((name) => (0, import_path10.join)(runDir, name)).filter((path2) => {
|
|
@@ -38338,7 +38391,7 @@ function finding(kind, file2, count) {
|
|
|
38338
38391
|
function scanText(input) {
|
|
38339
38392
|
const patterns = redactionPatterns(input);
|
|
38340
38393
|
return [
|
|
38341
|
-
finding("secret_like_token", input.file,
|
|
38394
|
+
finding("secret_like_token", input.file, secretMatches(input.text)),
|
|
38342
38395
|
finding("private_repo_path", input.file, matchesFor(patterns.privateRepo, input.text)),
|
|
38343
38396
|
finding("local_home_path", input.file, matchesFor(patterns.home, input.text)),
|
|
38344
38397
|
finding("email_address", input.file, matchesFor(patterns.email, input.text)),
|
|
@@ -38478,6 +38531,12 @@ var CHILD_ENV_ALLOWLIST = [
|
|
|
38478
38531
|
"USERPROFILE",
|
|
38479
38532
|
"WINDIR"
|
|
38480
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
|
+
};
|
|
38481
38540
|
var ExternalAgentExecutorError = class extends Error {
|
|
38482
38541
|
reasonCode;
|
|
38483
38542
|
constructor(reasonCode, message) {
|
|
@@ -38500,6 +38559,12 @@ function buildCodexExecutorEnv(input) {
|
|
|
38500
38559
|
env[key] = value;
|
|
38501
38560
|
}
|
|
38502
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
|
+
}
|
|
38503
38568
|
env[EXTERNAL_AGENT_RUN_DIR_ENV] = input.runDir;
|
|
38504
38569
|
env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] = input.promptVersion;
|
|
38505
38570
|
env.FOH_CLI_SUPPRESS_BANNER = "1";
|
|
@@ -38666,7 +38731,6 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
38666
38731
|
"--skip-git-repo-check",
|
|
38667
38732
|
"--ephemeral",
|
|
38668
38733
|
"--ignore-rules",
|
|
38669
|
-
"--ignore-user-config",
|
|
38670
38734
|
"--sandbox",
|
|
38671
38735
|
"workspace-write",
|
|
38672
38736
|
"--full-auto",
|
|
@@ -38739,6 +38803,22 @@ function proofArtifactPasses(runDir) {
|
|
|
38739
38803
|
function readIfExists(path2) {
|
|
38740
38804
|
return (0, import_fs14.existsSync)(path2) ? (0, import_fs14.readFileSync)(path2, "utf8") : "";
|
|
38741
38805
|
}
|
|
38806
|
+
function redactSecretLikeFile(path2) {
|
|
38807
|
+
if (!(0, import_fs14.existsSync)(path2)) return;
|
|
38808
|
+
const original = (0, import_fs14.readFileSync)(path2, "utf8");
|
|
38809
|
+
const redacted = redactExternalAgentSecretText(original);
|
|
38810
|
+
if (redacted !== original) (0, import_fs14.writeFileSync)(path2, redacted, "utf8");
|
|
38811
|
+
}
|
|
38812
|
+
function redactSecretLikeOutputArtifacts(run) {
|
|
38813
|
+
redactSecretLikeFile(run.outputs.jsonl);
|
|
38814
|
+
redactSecretLikeFile(run.outputs.last_message);
|
|
38815
|
+
redactSecretLikeFile(run.outputs.stderr);
|
|
38816
|
+
}
|
|
38817
|
+
function copyCommandCaptureArtifacts(input) {
|
|
38818
|
+
const commandLog = (0, import_path12.join)(input.captureDir, "commands.ndjson");
|
|
38819
|
+
if (!(0, import_fs14.existsSync)(commandLog)) return;
|
|
38820
|
+
(0, import_fs14.writeFileSync)((0, import_path12.join)(input.runDir, "commands.ndjson"), (0, import_fs14.readFileSync)(commandLog, "utf8"), "utf8");
|
|
38821
|
+
}
|
|
38742
38822
|
function relativeArtifactName(path2) {
|
|
38743
38823
|
return (0, import_path12.basename)(path2);
|
|
38744
38824
|
}
|
|
@@ -38752,6 +38832,9 @@ ${stderr}`;
|
|
|
38752
38832
|
if (/need[^.\n]*(?:private|source)[^.\n]*repo|cannot[^.\n]*without[^.\n]*(?:private|source)[^.\n]*repo|clone[^.\n]*(?:private|source)[^.\n]*repo/i.test(combined)) {
|
|
38753
38833
|
return { status: "fail", reasonCode: "private_repo_assumption_detected" };
|
|
38754
38834
|
}
|
|
38835
|
+
if (/(?:blocked|rejected|declined) by policy|EXEC_POLICY_BLOCKED|command execution was rejected|shell commands were rejected/i.test(combined)) {
|
|
38836
|
+
return { status: "hold", reasonCode: "codex_exec_policy_blocked" };
|
|
38837
|
+
}
|
|
38755
38838
|
if (/browser|approve|approval|login|auth|sign in/i.test(combined) && !proofArtifactPasses(input.run.run_dir)) {
|
|
38756
38839
|
return { status: "hold", reasonCode: "auth_browser_approval_required" };
|
|
38757
38840
|
}
|
|
@@ -38816,11 +38899,11 @@ function buildExecutedRunArtifact(input) {
|
|
|
38816
38899
|
function spawnCodex(input) {
|
|
38817
38900
|
return new Promise((resolveRun) => {
|
|
38818
38901
|
const started = Date.now();
|
|
38819
|
-
const
|
|
38820
|
-
const child = (0, import_child_process4.spawn)(
|
|
38902
|
+
const commandInvocation = buildCommandInvocation(input.command, input.args);
|
|
38903
|
+
const child = (0, import_child_process4.spawn)(commandInvocation.command, commandInvocation.args, {
|
|
38821
38904
|
cwd: input.cwd,
|
|
38822
38905
|
env: input.env,
|
|
38823
|
-
shell:
|
|
38906
|
+
shell: false,
|
|
38824
38907
|
stdio: ["pipe", "pipe", "pipe"],
|
|
38825
38908
|
windowsHide: true
|
|
38826
38909
|
});
|
|
@@ -38860,15 +38943,24 @@ function spawnCodex(input) {
|
|
|
38860
38943
|
});
|
|
38861
38944
|
});
|
|
38862
38945
|
}
|
|
38946
|
+
function buildCommandInvocation(command, args) {
|
|
38947
|
+
if (process.platform === "win32" && command.toLowerCase().endsWith(".cmd")) {
|
|
38948
|
+
const codexEntrypoint = (0, import_path12.join)((0, import_path12.dirname)(command), "node_modules", "@openai", "codex", "bin", "codex.js");
|
|
38949
|
+
if ((0, import_fs14.existsSync)(codexEntrypoint)) return { command: process.execPath, args: [codexEntrypoint, ...args] };
|
|
38950
|
+
}
|
|
38951
|
+
return { command, args };
|
|
38952
|
+
}
|
|
38863
38953
|
async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
38864
38954
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38865
38955
|
const results = [];
|
|
38866
38956
|
const runnerCommand = options.runnerCommand || resolveCodexExecutionCommand();
|
|
38867
38957
|
for (const run of plan.runs) {
|
|
38868
38958
|
const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38959
|
+
const commandCaptureDir = (0, import_path12.join)(run.workspace_dir, ".foh-capture");
|
|
38960
|
+
(0, import_fs14.mkdirSync)(commandCaptureDir, { recursive: true });
|
|
38869
38961
|
const env = buildCodexExecutorEnv({
|
|
38870
38962
|
sourceEnv: options.env,
|
|
38871
|
-
runDir:
|
|
38963
|
+
runDir: commandCaptureDir,
|
|
38872
38964
|
promptVersion: run.prompt_version
|
|
38873
38965
|
});
|
|
38874
38966
|
const spawned = await spawnCodex({
|
|
@@ -38881,6 +38973,8 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
38881
38973
|
stderrPath: run.outputs.stderr,
|
|
38882
38974
|
timeoutMs: plan.timeout_minutes * 60 * 1e3
|
|
38883
38975
|
});
|
|
38976
|
+
copyCommandCaptureArtifacts({ captureDir: commandCaptureDir, runDir: run.run_dir });
|
|
38977
|
+
redactSecretLikeOutputArtifacts(run);
|
|
38884
38978
|
const artifactSafety = scanExternalAgentArtifacts({
|
|
38885
38979
|
runDir: run.run_dir,
|
|
38886
38980
|
privateRepoRoot: options.privateRepoRoot || plan.private_repo_root,
|