@f-o-h/cli 0.1.11 → 0.1.13
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 +57 -34
- package/dist/foh.js +515 -120
- 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.13`
|
|
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.
|
|
@@ -85,44 +85,67 @@ The CLI defaults to the production API at `https://api.frontofhouse.okii.uk`.
|
|
|
85
85
|
|
|
86
86
|
## External-Agent Eval Capture
|
|
87
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 batch \
|
|
93
|
-
--models openai/codex,anthropic/claude,cursor/agent \
|
|
94
|
-
--prompt-version blank-setup.v1 \
|
|
95
|
-
--json
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Run each returned launch command in a clean agent terminal:
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
foh eval external-agent run \
|
|
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 batch \
|
|
93
|
+
--models openai/codex,anthropic/claude,cursor/agent \
|
|
94
|
+
--prompt-version blank-setup.v1 \
|
|
95
|
+
--json
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Run each returned launch command in a clean agent terminal:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
foh eval external-agent run \
|
|
102
102
|
--model-provider openai \
|
|
103
103
|
--model-name codex \
|
|
104
104
|
--prompt-version blank-setup.v1
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
The command writes a versioned prompt, launches an instrumented shell, captures
|
|
108
|
-
FOH CLI commands into `commands.ndjson`, and finalizes `run.json` as an
|
|
109
|
-
`external_agent_run.v1` artifact when the shell exits.
|
|
110
|
-
|
|
111
|
-
For guarded programmable-runner planning:
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
foh eval external-agent execute \
|
|
115
|
-
--runner codex \
|
|
116
|
-
--batch test-results/external-agent-runs/<batch>/batch.json \
|
|
117
|
-
--dry-run \
|
|
118
|
-
--json
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
This writes `executor-plan.json`, creates intentionally empty clean workspaces
|
|
122
|
-
outside the private repo, validates the local Codex binary/help flags, and
|
|
123
|
-
prints exact `codex exec` commands without executing them.
|
|
124
|
-
|
|
125
|
-
|
|
107
|
+
The command writes a versioned prompt, launches an instrumented shell, captures
|
|
108
|
+
FOH CLI commands into `commands.ndjson`, and finalizes `run.json` as an
|
|
109
|
+
`external_agent_run.v1` artifact when the shell exits.
|
|
110
|
+
|
|
111
|
+
For guarded programmable-runner planning:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
foh eval external-agent execute \
|
|
115
|
+
--runner codex \
|
|
116
|
+
--batch test-results/external-agent-runs/<batch>/batch.json \
|
|
117
|
+
--dry-run \
|
|
118
|
+
--json
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This writes `executor-plan.json`, creates intentionally empty clean workspaces
|
|
122
|
+
outside the private repo, validates the local Codex binary/help flags, and
|
|
123
|
+
prints exact `codex exec` commands without executing them.
|
|
124
|
+
|
|
125
|
+
Before promoting run artifacts, scan and redact them:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
foh eval external-agent scan-artifacts \
|
|
129
|
+
--run-dir test-results/external-agent-runs/<batch>/<run-id> \
|
|
130
|
+
--private-repo-root <private-repo-root> \
|
|
131
|
+
--write-redacted \
|
|
132
|
+
--json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
After dry-run review, one controlled Codex run can be launched explicitly with
|
|
136
|
+
`--live`. Live mode is intentionally limited to one run per batch and finalizes
|
|
137
|
+
`run.json` even on timeout or non-zero exit:
|
|
138
|
+
|
|
139
|
+
```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
|
+
## Local Scenario Suites
|
|
126
149
|
|
|
127
150
|
`foh test run --suite <file>` runs deterministic widget-runtime checks for a
|
|
128
151
|
specific agent. The suite format supports reply text checks plus structured
|
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 =
|
|
6049
|
+
let _sch = resolve12.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
|
|
6076
|
+
function resolve12(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
|
|
6651
|
+
function resolve12(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:
|
|
6878
|
+
resolve: resolve12,
|
|
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((
|
|
10108
|
+
return await new Promise((resolve12) => {
|
|
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
|
-
|
|
10115
|
+
resolve12(defaultValue);
|
|
10116
10116
|
return;
|
|
10117
10117
|
}
|
|
10118
10118
|
if (!value && !allowEmpty) {
|
|
10119
|
-
|
|
10119
|
+
resolve12("");
|
|
10120
10120
|
return;
|
|
10121
10121
|
}
|
|
10122
|
-
|
|
10122
|
+
resolve12(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((
|
|
10130
|
+
return await new Promise((resolve12) => {
|
|
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
|
-
|
|
10144
|
+
resolve12(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
|
|
10153
|
+
return resolve12("");
|
|
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((
|
|
10422
|
+
return new Promise((resolve12) => setTimeout(resolve12, 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((
|
|
10960
|
+
return new Promise((resolve12) => setTimeout(resolve12, 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:
|
|
13999
|
-
rule = JSON.parse(
|
|
13998
|
+
const { readFileSync: readFileSync12 } = await import("fs");
|
|
13999
|
+
rule = JSON.parse(readFileSync12(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:
|
|
14599
|
+
const { writeFileSync: writeFileSync9 } = await import("fs");
|
|
14600
14600
|
const outputPath = opts.output ?? "tenant.yaml";
|
|
14601
|
-
|
|
14601
|
+
writeFileSync9(
|
|
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: mkdirSync7, writeFileSync:
|
|
16037
|
-
const { dirname: dirname5, resolve:
|
|
16038
|
-
const absolutePath =
|
|
16036
|
+
const { mkdirSync: mkdirSync7, writeFileSync: writeFileSync9 } = await import("fs");
|
|
16037
|
+
const { dirname: dirname5, resolve: resolve12 } = await import("path");
|
|
16038
|
+
const absolutePath = resolve12(outputPath);
|
|
16039
16039
|
mkdirSync7(dirname5(absolutePath), { recursive: true });
|
|
16040
|
-
|
|
16040
|
+
writeFileSync9(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((
|
|
30521
|
+
await new Promise((resolve12) => setTimeout(resolve12, 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((
|
|
30538
|
+
return new Promise((resolve12, 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
|
-
|
|
30616
|
+
resolve12(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((
|
|
30877
|
+
return new Promise((resolve12, reject) => {
|
|
30878
30878
|
if (signal.aborted) {
|
|
30879
30879
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
30880
30880
|
return;
|
|
30881
30881
|
}
|
|
30882
|
-
const timeoutId = setTimeout(
|
|
30882
|
+
const timeoutId = setTimeout(resolve12, 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((
|
|
31982
|
+
await new Promise((resolve12) => setTimeout(resolve12, 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((
|
|
32631
|
+
return new Promise((resolve12) => {
|
|
32632
32632
|
const json3 = serializeMessage(message);
|
|
32633
32633
|
if (this._stdout.write(json3)) {
|
|
32634
|
-
|
|
32634
|
+
resolve12();
|
|
32635
32635
|
} else {
|
|
32636
|
-
this._stdout.once("drain",
|
|
32636
|
+
this._stdout.once("drain", resolve12);
|
|
32637
32637
|
}
|
|
32638
32638
|
});
|
|
32639
32639
|
}
|
|
32640
32640
|
};
|
|
32641
32641
|
|
|
32642
32642
|
// src/lib/cli-version.ts
|
|
32643
|
-
var CLI_VERSION = "0.1.
|
|
32643
|
+
var CLI_VERSION = "0.1.13";
|
|
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((
|
|
32828
|
+
return await new Promise((resolve12) => {
|
|
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
|
-
|
|
32853
|
+
resolve12({
|
|
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
|
-
|
|
32869
|
+
resolve12({
|
|
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:
|
|
34779
|
-
|
|
34778
|
+
const { writeFileSync: writeFileSync9 } = await import("fs");
|
|
34779
|
+
writeFileSync9(
|
|
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:
|
|
34948
|
-
|
|
34947
|
+
const { writeFileSync: writeFileSync9 } = await import("fs");
|
|
34948
|
+
writeFileSync9(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:
|
|
34999
|
-
|
|
34998
|
+
const { writeFileSync: writeFileSync9 } = await import("fs");
|
|
34999
|
+
writeFileSync9(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
|
}
|
|
@@ -37710,7 +37710,7 @@ async function runSelf(args, apiUrlOverride) {
|
|
|
37710
37710
|
if (apiUrlOverride && !spawnArgs.includes("--api-url")) {
|
|
37711
37711
|
spawnArgs.push("--api-url", apiUrlOverride);
|
|
37712
37712
|
}
|
|
37713
|
-
return await new Promise((
|
|
37713
|
+
return await new Promise((resolve12, reject) => {
|
|
37714
37714
|
const child = (0, import_child_process2.spawn)(process.execPath, [process.argv[1], ...spawnArgs], {
|
|
37715
37715
|
stdio: "inherit",
|
|
37716
37716
|
env: {
|
|
@@ -37720,7 +37720,7 @@ async function runSelf(args, apiUrlOverride) {
|
|
|
37720
37720
|
}
|
|
37721
37721
|
});
|
|
37722
37722
|
child.once("error", reject);
|
|
37723
|
-
child.once("close", (code) =>
|
|
37723
|
+
child.once("close", (code) => resolve12(typeof code === "number" ? code : 1));
|
|
37724
37724
|
});
|
|
37725
37725
|
}
|
|
37726
37726
|
function shouldUseInteractiveHome(argv) {
|
|
@@ -38098,17 +38098,17 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
|
|
|
38098
38098
|
async function applyRepoUpdate(repoRoot) {
|
|
38099
38099
|
const scriptPath = (0, import_path9.join)(repoRoot, "scripts", "Install-FohCli.ps1");
|
|
38100
38100
|
if (process.platform === "win32") {
|
|
38101
|
-
return await new Promise((
|
|
38101
|
+
return await new Promise((resolve12, reject) => {
|
|
38102
38102
|
const child = (0, import_child_process3.spawn)(
|
|
38103
38103
|
"powershell",
|
|
38104
38104
|
["-ExecutionPolicy", "Bypass", "-File", scriptPath],
|
|
38105
38105
|
{ stdio: "inherit" }
|
|
38106
38106
|
);
|
|
38107
38107
|
child.once("error", reject);
|
|
38108
|
-
child.once("close", (code) =>
|
|
38108
|
+
child.once("close", (code) => resolve12(typeof code === "number" ? code : 1));
|
|
38109
38109
|
});
|
|
38110
38110
|
}
|
|
38111
|
-
return await new Promise((
|
|
38111
|
+
return await new Promise((resolve12, reject) => {
|
|
38112
38112
|
const child = (0, import_child_process3.spawn)(
|
|
38113
38113
|
"corepack",
|
|
38114
38114
|
["pnpm", "cli:install:global"],
|
|
@@ -38118,7 +38118,7 @@ async function applyRepoUpdate(repoRoot) {
|
|
|
38118
38118
|
}
|
|
38119
38119
|
);
|
|
38120
38120
|
child.once("error", reject);
|
|
38121
|
-
child.once("close", (code) =>
|
|
38121
|
+
child.once("close", (code) => resolve12(typeof code === "number" ? code : 1));
|
|
38122
38122
|
});
|
|
38123
38123
|
}
|
|
38124
38124
|
function shouldShowUpdateNotice(argv = process.argv) {
|
|
@@ -38254,18 +38254,149 @@ function registerUpdate(program3) {
|
|
|
38254
38254
|
}
|
|
38255
38255
|
|
|
38256
38256
|
// src/commands/eval.ts
|
|
38257
|
-
var
|
|
38258
|
-
var
|
|
38257
|
+
var import_fs15 = require("fs");
|
|
38258
|
+
var import_path13 = require("path");
|
|
38259
38259
|
var import_child_process5 = require("child_process");
|
|
38260
38260
|
|
|
38261
|
-
// src/lib/external-agent-
|
|
38261
|
+
// src/lib/external-agent-artifact-safety.ts
|
|
38262
38262
|
var import_fs12 = require("fs");
|
|
38263
38263
|
var import_path10 = require("path");
|
|
38264
|
+
var DEFAULT_MAX_BYTES_PER_FILE = 5 * 1024 * 1024;
|
|
38265
|
+
var TEXT_ARTIFACT_NAMES = /* @__PURE__ */ new Set([
|
|
38266
|
+
"codex-events.jsonl",
|
|
38267
|
+
"codex-exec.jsonl",
|
|
38268
|
+
"codex-last-message.md",
|
|
38269
|
+
"commands.ndjson",
|
|
38270
|
+
"executor-plan.json",
|
|
38271
|
+
"improvement-packet.json",
|
|
38272
|
+
"knowledge.json",
|
|
38273
|
+
"notes.md",
|
|
38274
|
+
"proof.json",
|
|
38275
|
+
"replay.json",
|
|
38276
|
+
"run.json",
|
|
38277
|
+
"terminal-transcript.txt"
|
|
38278
|
+
]);
|
|
38279
|
+
var SECRET_RE2 = /\b(?:Bearer\s+)?(?:sk|pk|xai|whsec|EAAN|ghp|gho|github_pat|npm_)[A-Za-z0-9_\-.]{12,}\b/gi;
|
|
38280
|
+
var EMAIL_RE2 = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
38281
|
+
var PHONE_RE2 = /(?<!\w)(?:\+?\d[\d\s().-]{7,}\d)(?!\w)/g;
|
|
38282
|
+
function escapeRegExp(value) {
|
|
38283
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
38284
|
+
}
|
|
38285
|
+
function pathVariants(path2) {
|
|
38286
|
+
const resolved = (0, import_path10.resolve)(path2);
|
|
38287
|
+
const slash = resolved.replace(/\\/g, "/");
|
|
38288
|
+
const backslash = resolved.replace(/\//g, "\\");
|
|
38289
|
+
return Array.from(new Set([
|
|
38290
|
+
resolved,
|
|
38291
|
+
slash,
|
|
38292
|
+
backslash,
|
|
38293
|
+
backslash.replace(/\\/g, "\\\\")
|
|
38294
|
+
].filter((entry) => entry.length > 3)));
|
|
38295
|
+
}
|
|
38296
|
+
function regexForLiteralVariants(values) {
|
|
38297
|
+
const parts = values.map((value) => escapeRegExp(value)).filter(Boolean);
|
|
38298
|
+
if (parts.length === 0) return null;
|
|
38299
|
+
return new RegExp(parts.join("|"), "gi");
|
|
38300
|
+
}
|
|
38301
|
+
function matchesFor(pattern, text) {
|
|
38302
|
+
if (!pattern) return 0;
|
|
38303
|
+
const matches = text.match(pattern);
|
|
38304
|
+
return matches ? matches.length : 0;
|
|
38305
|
+
}
|
|
38306
|
+
function redactionPatterns(input) {
|
|
38307
|
+
return {
|
|
38308
|
+
secret: SECRET_RE2,
|
|
38309
|
+
email: EMAIL_RE2,
|
|
38310
|
+
phone: PHONE_RE2,
|
|
38311
|
+
privateRepo: input.privateRepoRoot ? regexForLiteralVariants(pathVariants(input.privateRepoRoot)) : null,
|
|
38312
|
+
home: input.homeDir ? regexForLiteralVariants(pathVariants(input.homeDir)) : null
|
|
38313
|
+
};
|
|
38314
|
+
}
|
|
38315
|
+
function redactExternalAgentArtifactText(text, input = {}) {
|
|
38316
|
+
const patterns = redactionPatterns(input);
|
|
38317
|
+
let redacted = text.replace(patterns.secret, "[redacted_secret]").replace(patterns.email, "[redacted_email]").replace(patterns.phone, "[redacted_phone]");
|
|
38318
|
+
if (patterns.privateRepo) redacted = redacted.replace(patterns.privateRepo, "[redacted_private_repo_path]");
|
|
38319
|
+
if (patterns.home) redacted = redacted.replace(patterns.home, "[redacted_home_path]");
|
|
38320
|
+
return redacted;
|
|
38321
|
+
}
|
|
38322
|
+
function artifactFiles(runDir) {
|
|
38323
|
+
if (!(0, import_fs12.existsSync)(runDir)) return [];
|
|
38324
|
+
return (0, import_fs12.readdirSync)(runDir).map((name) => (0, import_path10.join)(runDir, name)).filter((path2) => {
|
|
38325
|
+
const stat = (0, import_fs12.statSync)(path2);
|
|
38326
|
+
return stat.isFile() && TEXT_ARTIFACT_NAMES.has(path2.split(/[\\/]/).pop() || "");
|
|
38327
|
+
}).sort();
|
|
38328
|
+
}
|
|
38329
|
+
function finding(kind, file2, count) {
|
|
38330
|
+
if (count < 1) return null;
|
|
38331
|
+
return {
|
|
38332
|
+
kind,
|
|
38333
|
+
file: file2,
|
|
38334
|
+
count,
|
|
38335
|
+
fatal: kind === "secret_like_token" || kind === "private_repo_path"
|
|
38336
|
+
};
|
|
38337
|
+
}
|
|
38338
|
+
function scanText(input) {
|
|
38339
|
+
const patterns = redactionPatterns(input);
|
|
38340
|
+
return [
|
|
38341
|
+
finding("secret_like_token", input.file, matchesFor(patterns.secret, input.text)),
|
|
38342
|
+
finding("private_repo_path", input.file, matchesFor(patterns.privateRepo, input.text)),
|
|
38343
|
+
finding("local_home_path", input.file, matchesFor(patterns.home, input.text)),
|
|
38344
|
+
finding("email_address", input.file, matchesFor(patterns.email, input.text)),
|
|
38345
|
+
finding("phone_number", input.file, matchesFor(patterns.phone, input.text))
|
|
38346
|
+
].filter(Boolean);
|
|
38347
|
+
}
|
|
38348
|
+
function scanExternalAgentArtifacts(options) {
|
|
38349
|
+
const runDir = (0, import_path10.resolve)(options.runDir);
|
|
38350
|
+
const privateRepoRoot = options.privateRepoRoot ? (0, import_path10.resolve)(options.privateRepoRoot) : void 0;
|
|
38351
|
+
const homeDir = options.homeDir || process.env.USERPROFILE || process.env.HOME;
|
|
38352
|
+
const maxBytes = options.maxBytesPerFile ?? DEFAULT_MAX_BYTES_PER_FILE;
|
|
38353
|
+
const files = artifactFiles(runDir);
|
|
38354
|
+
const findings = [];
|
|
38355
|
+
const redactedFiles = [];
|
|
38356
|
+
for (const file2 of files) {
|
|
38357
|
+
const stat = (0, import_fs12.statSync)(file2);
|
|
38358
|
+
if (stat.size > maxBytes) {
|
|
38359
|
+
findings.push({
|
|
38360
|
+
kind: "artifact_too_large",
|
|
38361
|
+
file: file2,
|
|
38362
|
+
count: 1,
|
|
38363
|
+
fatal: true
|
|
38364
|
+
});
|
|
38365
|
+
continue;
|
|
38366
|
+
}
|
|
38367
|
+
const text = (0, import_fs12.readFileSync)(file2, "utf8");
|
|
38368
|
+
findings.push(...scanText({ text, file: file2, privateRepoRoot, homeDir }));
|
|
38369
|
+
if (options.writeRedacted) {
|
|
38370
|
+
const redacted = redactExternalAgentArtifactText(text, { privateRepoRoot, homeDir });
|
|
38371
|
+
const out = `${file2}.redacted`;
|
|
38372
|
+
(0, import_fs12.writeFileSync)(out, redacted, "utf8");
|
|
38373
|
+
redactedFiles.push(out);
|
|
38374
|
+
}
|
|
38375
|
+
}
|
|
38376
|
+
const blockedReasonCodes = Array.from(new Set(findings.filter((entry) => entry.fatal).map((entry) => `artifact_contains_${entry.kind}`)));
|
|
38377
|
+
const ok = blockedReasonCodes.length === 0;
|
|
38378
|
+
return {
|
|
38379
|
+
schema_version: "external_agent_artifact_safety_report.v1",
|
|
38380
|
+
ok,
|
|
38381
|
+
status: ok ? "pass" : "blocked",
|
|
38382
|
+
reason_code: ok ? "external_agent_artifacts_safe" : "external_agent_artifacts_contain_blocked_content",
|
|
38383
|
+
scanned_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
38384
|
+
run_dir: runDir,
|
|
38385
|
+
files_scanned: files,
|
|
38386
|
+
redacted_files: redactedFiles,
|
|
38387
|
+
findings,
|
|
38388
|
+
blocked_reason_codes: blockedReasonCodes
|
|
38389
|
+
};
|
|
38390
|
+
}
|
|
38391
|
+
|
|
38392
|
+
// src/lib/external-agent-capture.ts
|
|
38393
|
+
var import_fs13 = require("fs");
|
|
38394
|
+
var import_path11 = require("path");
|
|
38264
38395
|
var EXTERNAL_AGENT_RUN_DIR_ENV = "FOH_EXTERNAL_AGENT_RUN_DIR";
|
|
38265
38396
|
var EXTERNAL_AGENT_PROMPT_VERSION_ENV = "FOH_EXTERNAL_AGENT_PROMPT_VERSION";
|
|
38266
|
-
var
|
|
38397
|
+
var SECRET_RE3 = /\b(?:Bearer\s+)?(?:sk|pk|xai|whsec|EAAN|ghp|gho|github_pat|npm_)[A-Za-z0-9_\-.]{12,}\b/gi;
|
|
38267
38398
|
function redactText(value) {
|
|
38268
|
-
return value.replace(
|
|
38399
|
+
return value.replace(SECRET_RE3, "[redacted_secret]");
|
|
38269
38400
|
}
|
|
38270
38401
|
function redactPath(value) {
|
|
38271
38402
|
let redacted = redactText(value);
|
|
@@ -38279,13 +38410,13 @@ function safeJsonLine(value) {
|
|
|
38279
38410
|
function getExternalAgentRunDir() {
|
|
38280
38411
|
const raw = process.env[EXTERNAL_AGENT_RUN_DIR_ENV];
|
|
38281
38412
|
if (!raw || !raw.trim()) return null;
|
|
38282
|
-
return (0,
|
|
38413
|
+
return (0, import_path11.resolve)(raw);
|
|
38283
38414
|
}
|
|
38284
38415
|
function recordExternalAgentCliInvocation(input) {
|
|
38285
38416
|
const runDir = getExternalAgentRunDir();
|
|
38286
38417
|
if (!runDir) return;
|
|
38287
38418
|
try {
|
|
38288
|
-
(0,
|
|
38419
|
+
(0, import_fs13.mkdirSync)(runDir, { recursive: true });
|
|
38289
38420
|
const args = input.argv.slice(2).map((arg) => redactText(String(arg)));
|
|
38290
38421
|
const record2 = {
|
|
38291
38422
|
schema_version: "external_agent_cli_command.v1",
|
|
@@ -38297,20 +38428,20 @@ function recordExternalAgentCliInvocation(input) {
|
|
|
38297
38428
|
json_requested: args.includes("--json"),
|
|
38298
38429
|
prompt_version: process.env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] || null
|
|
38299
38430
|
};
|
|
38300
|
-
(0,
|
|
38431
|
+
(0, import_fs13.appendFileSync)((0, import_path11.join)(runDir, "commands.ndjson"), safeJsonLine(record2), "utf8");
|
|
38301
38432
|
} catch {
|
|
38302
38433
|
}
|
|
38303
38434
|
}
|
|
38304
38435
|
function readCommandRecords(runDir) {
|
|
38305
|
-
const commandLogPath = (0,
|
|
38306
|
-
if (!(0,
|
|
38307
|
-
return (0,
|
|
38436
|
+
const commandLogPath = (0, import_path11.join)(runDir, "commands.ndjson");
|
|
38437
|
+
if (!(0, import_fs13.existsSync)(commandLogPath)) return [];
|
|
38438
|
+
return (0, import_fs13.readFileSync)(commandLogPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
|
|
38308
38439
|
}
|
|
38309
38440
|
|
|
38310
38441
|
// src/lib/external-agent-executor.ts
|
|
38311
|
-
var
|
|
38442
|
+
var import_fs14 = require("fs");
|
|
38312
38443
|
var import_os2 = require("os");
|
|
38313
|
-
var
|
|
38444
|
+
var import_path12 = require("path");
|
|
38314
38445
|
var import_child_process4 = require("child_process");
|
|
38315
38446
|
var CODEX_EXECUTOR_DENIED_ENV_PREFIXES = [
|
|
38316
38447
|
"SUPABASE_",
|
|
@@ -38375,14 +38506,14 @@ function buildCodexExecutorEnv(input) {
|
|
|
38375
38506
|
return env;
|
|
38376
38507
|
}
|
|
38377
38508
|
function normalizeForCompare(path2) {
|
|
38378
|
-
const resolved = (0,
|
|
38509
|
+
const resolved = (0, import_path12.resolve)(path2);
|
|
38379
38510
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
38380
38511
|
}
|
|
38381
38512
|
function isPathInside(childPath, parentPath) {
|
|
38382
38513
|
const child = normalizeForCompare(childPath);
|
|
38383
38514
|
const parent = normalizeForCompare(parentPath);
|
|
38384
|
-
const rel = (0,
|
|
38385
|
-
return rel === "" || !!rel && !rel.startsWith("..") && !(0,
|
|
38515
|
+
const rel = (0, import_path12.relative)(parent, child);
|
|
38516
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !(0, import_path12.isAbsolute)(rel);
|
|
38386
38517
|
}
|
|
38387
38518
|
function requireString(value, field) {
|
|
38388
38519
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -38391,10 +38522,10 @@ function requireString(value, field) {
|
|
|
38391
38522
|
return value;
|
|
38392
38523
|
}
|
|
38393
38524
|
function readBatch(batchPath) {
|
|
38394
|
-
if (!(0,
|
|
38525
|
+
if (!(0, import_fs14.existsSync)(batchPath)) {
|
|
38395
38526
|
throw new ExternalAgentExecutorError("external_agent_batch_not_found", `Batch file not found: ${batchPath}`);
|
|
38396
38527
|
}
|
|
38397
|
-
const parsed = JSON.parse((0,
|
|
38528
|
+
const parsed = JSON.parse((0, import_fs14.readFileSync)(batchPath, "utf8"));
|
|
38398
38529
|
if (parsed.schema_version !== "external_agent_batch_plan.v1") {
|
|
38399
38530
|
throw new ExternalAgentExecutorError("invalid_external_agent_batch", "Batch schema_version must be external_agent_batch_plan.v1.");
|
|
38400
38531
|
}
|
|
@@ -38423,11 +38554,14 @@ function resolveCodexProbeCommand() {
|
|
|
38423
38554
|
if (process.platform !== "win32") return "codex";
|
|
38424
38555
|
const appData = process.env.APPDATA;
|
|
38425
38556
|
if (appData) {
|
|
38426
|
-
const appDataShim = (0,
|
|
38427
|
-
if ((0,
|
|
38557
|
+
const appDataShim = (0, import_path12.join)(appData, "npm", "codex.cmd");
|
|
38558
|
+
if ((0, import_fs14.existsSync)(appDataShim)) return appDataShim;
|
|
38428
38559
|
}
|
|
38429
38560
|
return "codex.cmd";
|
|
38430
38561
|
}
|
|
38562
|
+
function resolveCodexExecutionCommand() {
|
|
38563
|
+
return resolveCodexProbeCommand();
|
|
38564
|
+
}
|
|
38431
38565
|
function validateCodexRunner(options) {
|
|
38432
38566
|
if (options.skipRunnerProbe) {
|
|
38433
38567
|
return { binaryChecked: false, requiredFlagsChecked: false };
|
|
@@ -38467,13 +38601,13 @@ function safeRunId(value) {
|
|
|
38467
38601
|
return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
38468
38602
|
}
|
|
38469
38603
|
function resolveWorkspaceRoot(input) {
|
|
38470
|
-
if (input.workspaceRoot) return (0,
|
|
38471
|
-
const batchStem = (0,
|
|
38472
|
-
const repoStem = (0,
|
|
38473
|
-
return (0,
|
|
38604
|
+
if (input.workspaceRoot) return (0, import_path12.resolve)(input.workspaceRoot);
|
|
38605
|
+
const batchStem = (0, import_path12.basename)((0, import_path12.resolve)(input.batchPath)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
|
|
38606
|
+
const repoStem = (0, import_path12.basename)((0, import_path12.resolve)(input.privateRepoRoot)).replace(/[^a-zA-Z0-9_.-]+/g, "-");
|
|
38607
|
+
return (0, import_path12.resolve)((0, import_os2.tmpdir)(), "foh-external-agent-workspaces", repoStem, batchStem);
|
|
38474
38608
|
}
|
|
38475
38609
|
function promptVersionFromPath(promptPath) {
|
|
38476
|
-
const raw = (0,
|
|
38610
|
+
const raw = (0, import_fs14.readFileSync)(promptPath, "utf8");
|
|
38477
38611
|
if (raw.includes("Do not assume access to the private source repository")) return "blank-setup.v1";
|
|
38478
38612
|
return "unknown";
|
|
38479
38613
|
}
|
|
@@ -38482,10 +38616,10 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
38482
38616
|
if (runner !== "codex") {
|
|
38483
38617
|
throw new ExternalAgentExecutorError("unsupported_external_agent_runner", `Unsupported runner: ${runner}`);
|
|
38484
38618
|
}
|
|
38485
|
-
const batchPath = (0,
|
|
38619
|
+
const batchPath = (0, import_path12.resolve)(options.batchPath);
|
|
38486
38620
|
const batch = readBatch(batchPath);
|
|
38487
38621
|
const runnerProbe = validateCodexRunner(options);
|
|
38488
|
-
const privateRepoRoot = (0,
|
|
38622
|
+
const privateRepoRoot = (0, import_path12.resolve)(options.privateRepoRoot || options.cwd || process.cwd());
|
|
38489
38623
|
const workspaceRoot = resolveWorkspaceRoot({ batchPath, workspaceRoot: options.workspaceRoot, privateRepoRoot });
|
|
38490
38624
|
if (isPathInside(workspaceRoot, privateRepoRoot)) {
|
|
38491
38625
|
throw new ExternalAgentExecutorError(
|
|
@@ -38493,17 +38627,17 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
38493
38627
|
`Workspace root must be outside the private repository. workspace=${workspaceRoot} repo=${privateRepoRoot}`
|
|
38494
38628
|
);
|
|
38495
38629
|
}
|
|
38496
|
-
(0,
|
|
38497
|
-
const batchDir = (0,
|
|
38630
|
+
(0, import_fs14.mkdirSync)(workspaceRoot, { recursive: true });
|
|
38631
|
+
const batchDir = (0, import_path12.resolve)(String(batch.batch_dir || (0, import_path12.resolve)(batchPath, "..")));
|
|
38498
38632
|
const timeoutMinutes = Number.isFinite(options.timeoutMinutes) && Number(options.timeoutMinutes) > 0 ? Number(options.timeoutMinutes) : 30;
|
|
38499
38633
|
const runs = batch.runs.map((run) => {
|
|
38500
38634
|
const runId = safeRunId(requireString(run.run_id, "runs[].run_id"));
|
|
38501
|
-
const runDir = (0,
|
|
38502
|
-
const promptPath = (0,
|
|
38503
|
-
const workspaceDir = (0,
|
|
38504
|
-
(0,
|
|
38505
|
-
(0,
|
|
38506
|
-
(0,
|
|
38635
|
+
const runDir = (0, import_path12.resolve)(requireString(run.run_dir, `runs[${runId}].run_dir`));
|
|
38636
|
+
const promptPath = (0, import_path12.resolve)(requireString(run.prompt_path, `runs[${runId}].prompt_path`));
|
|
38637
|
+
const workspaceDir = (0, import_path12.join)(workspaceRoot, runId);
|
|
38638
|
+
(0, import_fs14.mkdirSync)(workspaceDir, { recursive: true });
|
|
38639
|
+
(0, import_fs14.writeFileSync)(
|
|
38640
|
+
(0, import_path12.join)(workspaceDir, "README.md"),
|
|
38507
38641
|
[
|
|
38508
38642
|
"# FOH External-Agent Workspace",
|
|
38509
38643
|
"",
|
|
@@ -38519,8 +38653,12 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
38519
38653
|
runDir,
|
|
38520
38654
|
promptVersion: promptVersionFromPath(promptPath)
|
|
38521
38655
|
});
|
|
38522
|
-
const
|
|
38523
|
-
const
|
|
38656
|
+
const promptVersion = String(env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] || "unknown");
|
|
38657
|
+
const jsonlPath = (0, import_path12.join)(runDir, "codex-exec.jsonl");
|
|
38658
|
+
const lastMessagePath = (0, import_path12.join)(runDir, "codex-last-message.md");
|
|
38659
|
+
const stderrPath = (0, import_path12.join)(runDir, "codex-stderr.txt");
|
|
38660
|
+
const runPath = (0, import_path12.join)(runDir, "run.json");
|
|
38661
|
+
const artifactSafetyPath = (0, import_path12.join)(runDir, "artifact-safety.json");
|
|
38524
38662
|
const args = [
|
|
38525
38663
|
"exec",
|
|
38526
38664
|
"--cd",
|
|
@@ -38541,6 +38679,7 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
38541
38679
|
run_id: runId,
|
|
38542
38680
|
model_provider: String(run.model_provider || "unknown"),
|
|
38543
38681
|
model_name: String(run.model_name || "unknown-model"),
|
|
38682
|
+
prompt_version: promptVersion,
|
|
38544
38683
|
run_dir: runDir,
|
|
38545
38684
|
prompt_path: promptPath,
|
|
38546
38685
|
workspace_dir: workspaceDir,
|
|
@@ -38549,7 +38688,10 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
38549
38688
|
env_keys: Object.keys(env).sort(),
|
|
38550
38689
|
outputs: {
|
|
38551
38690
|
jsonl: jsonlPath,
|
|
38552
|
-
last_message: lastMessagePath
|
|
38691
|
+
last_message: lastMessagePath,
|
|
38692
|
+
stderr: stderrPath,
|
|
38693
|
+
run: runPath,
|
|
38694
|
+
artifact_safety: artifactSafetyPath
|
|
38553
38695
|
}
|
|
38554
38696
|
};
|
|
38555
38697
|
});
|
|
@@ -38578,12 +38720,219 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
38578
38720
|
};
|
|
38579
38721
|
}
|
|
38580
38722
|
function writeExternalAgentExecutorPlan(plan) {
|
|
38581
|
-
const path2 = (0,
|
|
38582
|
-
(0,
|
|
38583
|
-
(0,
|
|
38723
|
+
const path2 = (0, import_path12.join)(plan.batch_dir, "executor-plan.json");
|
|
38724
|
+
(0, import_fs14.mkdirSync)(plan.batch_dir, { recursive: true });
|
|
38725
|
+
(0, import_fs14.writeFileSync)(path2, `${JSON.stringify(plan, null, 2)}
|
|
38584
38726
|
`, "utf8");
|
|
38585
38727
|
return path2;
|
|
38586
38728
|
}
|
|
38729
|
+
function proofArtifactPasses(runDir) {
|
|
38730
|
+
const proofPath = (0, import_path12.join)(runDir, "proof.json");
|
|
38731
|
+
if (!(0, import_fs14.existsSync)(proofPath)) return false;
|
|
38732
|
+
try {
|
|
38733
|
+
const parsed = JSON.parse((0, import_fs14.readFileSync)(proofPath, "utf8"));
|
|
38734
|
+
return parsed.ok === true || parsed.status === "pass" || parsed.status === "passed";
|
|
38735
|
+
} catch {
|
|
38736
|
+
return false;
|
|
38737
|
+
}
|
|
38738
|
+
}
|
|
38739
|
+
function readIfExists(path2) {
|
|
38740
|
+
return (0, import_fs14.existsSync)(path2) ? (0, import_fs14.readFileSync)(path2, "utf8") : "";
|
|
38741
|
+
}
|
|
38742
|
+
function relativeArtifactName(path2) {
|
|
38743
|
+
return (0, import_path12.basename)(path2);
|
|
38744
|
+
}
|
|
38745
|
+
function classifyRun(input) {
|
|
38746
|
+
if (input.timedOut) return { status: "hold", reasonCode: "codex_runner_timeout" };
|
|
38747
|
+
if (!input.artifactSafetyOk) return { status: "fail", reasonCode: "external_agent_artifact_safety_blocked" };
|
|
38748
|
+
const lastMessage = readIfExists(input.run.outputs.last_message);
|
|
38749
|
+
const stderr = readIfExists(input.run.outputs.stderr);
|
|
38750
|
+
const combined = `${lastMessage}
|
|
38751
|
+
${stderr}`;
|
|
38752
|
+
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
|
+
return { status: "fail", reasonCode: "private_repo_assumption_detected" };
|
|
38754
|
+
}
|
|
38755
|
+
if (/browser|approve|approval|login|auth|sign in/i.test(combined) && !proofArtifactPasses(input.run.run_dir)) {
|
|
38756
|
+
return { status: "hold", reasonCode: "auth_browser_approval_required" };
|
|
38757
|
+
}
|
|
38758
|
+
if (input.exitCode !== 0) return { status: "hold", reasonCode: "codex_runner_nonzero_exit" };
|
|
38759
|
+
if (proofArtifactPasses(input.run.run_dir)) return { status: "pass", reasonCode: null };
|
|
38760
|
+
return { status: "hold", reasonCode: "external_agent_proof_artifact_missing" };
|
|
38761
|
+
}
|
|
38762
|
+
function buildExecutedRunArtifact(input) {
|
|
38763
|
+
const commands = readCommandRecords(input.run.run_dir);
|
|
38764
|
+
return {
|
|
38765
|
+
schema_version: "external_agent_run.v1",
|
|
38766
|
+
run_id: input.run.run_id,
|
|
38767
|
+
status: input.status,
|
|
38768
|
+
failure_reason_code: input.reasonCode,
|
|
38769
|
+
model_provider: input.run.model_provider,
|
|
38770
|
+
model_name: input.run.model_name,
|
|
38771
|
+
agent_shell: "codex-exec",
|
|
38772
|
+
workspace_type: "clean-no-repo-programmatic",
|
|
38773
|
+
prompt_version: input.run.prompt_version,
|
|
38774
|
+
prompt_path: "prompt.txt",
|
|
38775
|
+
started_at: input.startedAt,
|
|
38776
|
+
ended_at: input.endedAt,
|
|
38777
|
+
manual_intervention_count: 0,
|
|
38778
|
+
manual_interventions: [],
|
|
38779
|
+
environment: {
|
|
38780
|
+
os: process.platform,
|
|
38781
|
+
node_version: process.version,
|
|
38782
|
+
npm_version: null,
|
|
38783
|
+
foh_cli_version: CLI_VERSION,
|
|
38784
|
+
runner_exit_code: input.exitCode,
|
|
38785
|
+
runner_timed_out: input.timedOut,
|
|
38786
|
+
duration_ms: input.durationMs
|
|
38787
|
+
},
|
|
38788
|
+
public_entrypoints: [
|
|
38789
|
+
"https://frontofhouse.okii.uk",
|
|
38790
|
+
"https://frontofhouse.okii.uk/llms.txt",
|
|
38791
|
+
"https://frontofhouse.okii.uk/openapi.yaml",
|
|
38792
|
+
"npx --yes @f-o-h/cli@latest"
|
|
38793
|
+
],
|
|
38794
|
+
commands_run: commands.map((command) => command.command),
|
|
38795
|
+
docs_pages_used: [],
|
|
38796
|
+
artifacts: {
|
|
38797
|
+
terminal_transcript: relativeArtifactName(input.run.outputs.jsonl),
|
|
38798
|
+
command_log: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "commands.ndjson")) ? "commands.ndjson" : null,
|
|
38799
|
+
proof_bundle: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "proof.json")) ? "proof.json" : null,
|
|
38800
|
+
replay_packet: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "replay.json")) ? "replay.json" : null,
|
|
38801
|
+
knowledge_packet: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "knowledge.json")) ? "knowledge.json" : null,
|
|
38802
|
+
improvement_packet: input.status === "pass" ? null : "improvement-packet.json",
|
|
38803
|
+
notes: (0, import_fs14.existsSync)((0, import_path12.join)(input.run.run_dir, "notes.md")) ? "notes.md" : null,
|
|
38804
|
+
codex_last_message: relativeArtifactName(input.run.outputs.last_message),
|
|
38805
|
+
codex_stderr: relativeArtifactName(input.run.outputs.stderr),
|
|
38806
|
+
artifact_safety: relativeArtifactName(input.run.outputs.artifact_safety)
|
|
38807
|
+
},
|
|
38808
|
+
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}.`,
|
|
38809
|
+
next_commands: input.status === "pass" ? ["corepack pnpm eval:external-agent:runs:summary"] : [
|
|
38810
|
+
"foh eval external-agent scan-artifacts --run-dir <run_dir> --private-repo-root <private_repo_root> --write-redacted --json",
|
|
38811
|
+
"foh bug improve --from external-agent-run --file <run_dir>/run.json --out <run_dir>/improvement-packet.json --json",
|
|
38812
|
+
"corepack pnpm eval:external-agent:runs:summary"
|
|
38813
|
+
]
|
|
38814
|
+
};
|
|
38815
|
+
}
|
|
38816
|
+
function spawnCodex(input) {
|
|
38817
|
+
return new Promise((resolveRun) => {
|
|
38818
|
+
const started = Date.now();
|
|
38819
|
+
const useShell = process.platform === "win32" && input.command.toLowerCase().endsWith(".cmd");
|
|
38820
|
+
const child = (0, import_child_process4.spawn)(input.command, input.args, {
|
|
38821
|
+
cwd: input.cwd,
|
|
38822
|
+
env: input.env,
|
|
38823
|
+
shell: useShell,
|
|
38824
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
38825
|
+
windowsHide: true
|
|
38826
|
+
});
|
|
38827
|
+
const stdout = (0, import_fs14.createWriteStream)(input.stdoutPath, { flags: "w" });
|
|
38828
|
+
const stderr = (0, import_fs14.createWriteStream)(input.stderrPath, { flags: "w" });
|
|
38829
|
+
child.stdout.pipe(stdout);
|
|
38830
|
+
child.stderr.pipe(stderr);
|
|
38831
|
+
child.stdin.end(input.prompt);
|
|
38832
|
+
let timedOut = false;
|
|
38833
|
+
const timer = setTimeout(() => {
|
|
38834
|
+
timedOut = true;
|
|
38835
|
+
if (child.pid && process.platform === "win32") {
|
|
38836
|
+
(0, import_child_process4.spawnSync)("taskkill.exe", ["/pid", String(child.pid), "/t", "/f"], { stdio: "ignore" });
|
|
38837
|
+
} else {
|
|
38838
|
+
child.kill("SIGKILL");
|
|
38839
|
+
}
|
|
38840
|
+
}, input.timeoutMs);
|
|
38841
|
+
child.on("close", (exitCode) => {
|
|
38842
|
+
clearTimeout(timer);
|
|
38843
|
+
stdout.end();
|
|
38844
|
+
stderr.end();
|
|
38845
|
+
resolveRun({
|
|
38846
|
+
exitCode,
|
|
38847
|
+
timedOut,
|
|
38848
|
+
durationMs: Date.now() - started
|
|
38849
|
+
});
|
|
38850
|
+
});
|
|
38851
|
+
child.on("error", () => {
|
|
38852
|
+
clearTimeout(timer);
|
|
38853
|
+
stdout.end();
|
|
38854
|
+
stderr.end();
|
|
38855
|
+
resolveRun({
|
|
38856
|
+
exitCode: null,
|
|
38857
|
+
timedOut,
|
|
38858
|
+
durationMs: Date.now() - started
|
|
38859
|
+
});
|
|
38860
|
+
});
|
|
38861
|
+
});
|
|
38862
|
+
}
|
|
38863
|
+
async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
38864
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38865
|
+
const results = [];
|
|
38866
|
+
const runnerCommand = options.runnerCommand || resolveCodexExecutionCommand();
|
|
38867
|
+
for (const run of plan.runs) {
|
|
38868
|
+
const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38869
|
+
const env = buildCodexExecutorEnv({
|
|
38870
|
+
sourceEnv: options.env,
|
|
38871
|
+
runDir: run.run_dir,
|
|
38872
|
+
promptVersion: run.prompt_version
|
|
38873
|
+
});
|
|
38874
|
+
const spawned = await spawnCodex({
|
|
38875
|
+
command: runnerCommand,
|
|
38876
|
+
args: run.args,
|
|
38877
|
+
cwd: run.workspace_dir,
|
|
38878
|
+
env,
|
|
38879
|
+
prompt: (0, import_fs14.readFileSync)(run.prompt_path, "utf8"),
|
|
38880
|
+
stdoutPath: run.outputs.jsonl,
|
|
38881
|
+
stderrPath: run.outputs.stderr,
|
|
38882
|
+
timeoutMs: plan.timeout_minutes * 60 * 1e3
|
|
38883
|
+
});
|
|
38884
|
+
const artifactSafety = scanExternalAgentArtifacts({
|
|
38885
|
+
runDir: run.run_dir,
|
|
38886
|
+
privateRepoRoot: options.privateRepoRoot || plan.private_repo_root,
|
|
38887
|
+
writeRedacted: true
|
|
38888
|
+
});
|
|
38889
|
+
(0, import_fs14.writeFileSync)(run.outputs.artifact_safety, `${JSON.stringify(artifactSafety, null, 2)}
|
|
38890
|
+
`, "utf8");
|
|
38891
|
+
const runEndedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38892
|
+
const classification = classifyRun({
|
|
38893
|
+
run,
|
|
38894
|
+
exitCode: spawned.exitCode,
|
|
38895
|
+
timedOut: spawned.timedOut,
|
|
38896
|
+
artifactSafetyOk: artifactSafety.ok
|
|
38897
|
+
});
|
|
38898
|
+
const runArtifact = buildExecutedRunArtifact({
|
|
38899
|
+
run,
|
|
38900
|
+
startedAt: runStartedAt,
|
|
38901
|
+
endedAt: runEndedAt,
|
|
38902
|
+
status: classification.status,
|
|
38903
|
+
reasonCode: classification.reasonCode,
|
|
38904
|
+
exitCode: spawned.exitCode,
|
|
38905
|
+
timedOut: spawned.timedOut,
|
|
38906
|
+
durationMs: spawned.durationMs
|
|
38907
|
+
});
|
|
38908
|
+
(0, import_fs14.writeFileSync)(run.outputs.run, `${JSON.stringify(runArtifact, null, 2)}
|
|
38909
|
+
`, "utf8");
|
|
38910
|
+
results.push({
|
|
38911
|
+
run_id: run.run_id,
|
|
38912
|
+
status: classification.status,
|
|
38913
|
+
failure_reason_code: classification.reasonCode,
|
|
38914
|
+
exit_code: spawned.exitCode,
|
|
38915
|
+
timed_out: spawned.timedOut,
|
|
38916
|
+
duration_ms: spawned.durationMs,
|
|
38917
|
+
artifacts: run.outputs
|
|
38918
|
+
});
|
|
38919
|
+
}
|
|
38920
|
+
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38921
|
+
const failed = results.some((result) => result.status === "fail");
|
|
38922
|
+
const held = results.some((result) => result.status === "hold");
|
|
38923
|
+
const status = failed ? "fail" : held ? "hold" : "pass";
|
|
38924
|
+
return {
|
|
38925
|
+
schema_version: "external_agent_execution_batch_result.v1",
|
|
38926
|
+
ok: status === "pass",
|
|
38927
|
+
status,
|
|
38928
|
+
reason_code: status === "pass" ? "external_agent_execution_passed" : "external_agent_execution_needs_review",
|
|
38929
|
+
started_at: startedAt,
|
|
38930
|
+
ended_at: endedAt,
|
|
38931
|
+
runner: plan.runner,
|
|
38932
|
+
run_count: results.length,
|
|
38933
|
+
results
|
|
38934
|
+
};
|
|
38935
|
+
}
|
|
38587
38936
|
|
|
38588
38937
|
// src/commands/eval.ts
|
|
38589
38938
|
var DEFAULT_PROMPT_VERSION = "blank-setup.v1";
|
|
@@ -38604,13 +38953,13 @@ function defaultRunDir(modelName, promptVersion) {
|
|
|
38604
38953
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 23);
|
|
38605
38954
|
const safeModel = String(modelName || "unknown-model").toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
38606
38955
|
const safePrompt = String(promptVersion || DEFAULT_PROMPT_VERSION).toLowerCase().replace(/[^a-z0-9_.-]+/g, "-");
|
|
38607
|
-
return (0,
|
|
38956
|
+
return (0, import_path13.resolve)("test-results", "external-agent-runs", date4, `${safeModel}-${safePrompt}-${stamp}`);
|
|
38608
38957
|
}
|
|
38609
38958
|
function defaultBatchDir(promptVersion) {
|
|
38610
38959
|
const date4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
38611
38960
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 23);
|
|
38612
38961
|
const safePrompt = String(promptVersion || DEFAULT_PROMPT_VERSION).toLowerCase().replace(/[^a-z0-9_.-]+/g, "-");
|
|
38613
|
-
return (0,
|
|
38962
|
+
return (0, import_path13.resolve)("test-results", "external-agent-runs", date4, `batch-${safePrompt}-${stamp}`);
|
|
38614
38963
|
}
|
|
38615
38964
|
function safeSlug(value) {
|
|
38616
38965
|
return String(value || "unknown").toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
|
|
@@ -38638,14 +38987,14 @@ function inferShell(raw) {
|
|
|
38638
38987
|
}
|
|
38639
38988
|
function writePrompt(runDir, promptVersion) {
|
|
38640
38989
|
const prompt = PROMPTS[promptVersion] ?? PROMPTS[DEFAULT_PROMPT_VERSION];
|
|
38641
|
-
const path2 = (0,
|
|
38642
|
-
(0,
|
|
38990
|
+
const path2 = (0, import_path13.join)(runDir, "prompt.txt");
|
|
38991
|
+
(0, import_fs15.writeFileSync)(path2, `${prompt}
|
|
38643
38992
|
`, "utf8");
|
|
38644
38993
|
return path2;
|
|
38645
38994
|
}
|
|
38646
38995
|
function writeSession(runDir, session) {
|
|
38647
|
-
const path2 = (0,
|
|
38648
|
-
(0,
|
|
38996
|
+
const path2 = (0, import_path13.join)(runDir, "session.json");
|
|
38997
|
+
(0, import_fs15.writeFileSync)(path2, `${JSON.stringify(session, null, 2)}
|
|
38649
38998
|
`, "utf8");
|
|
38650
38999
|
return path2;
|
|
38651
39000
|
}
|
|
@@ -38696,7 +39045,7 @@ function buildRunArtifact(input) {
|
|
|
38696
39045
|
},
|
|
38697
39046
|
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}.`,
|
|
38698
39047
|
next_commands: status === "pass" ? ["corepack pnpm eval:external-agent:runs:summary"] : [
|
|
38699
|
-
`foh bug improve --from external-agent-run --file ${(0,
|
|
39048
|
+
`foh bug improve --from external-agent-run --file ${(0, import_path13.join)(input.runDir, "run.json")} --out ${(0, import_path13.join)(input.runDir, "improvement-packet.json")} --json`,
|
|
38700
39049
|
"corepack pnpm eval:external-agent:runs:summary"
|
|
38701
39050
|
]
|
|
38702
39051
|
};
|
|
@@ -38706,13 +39055,13 @@ function registerEval(program3) {
|
|
|
38706
39055
|
const external = evalCommand.command("external-agent").description("Capture clean external coding-agent setup attempts");
|
|
38707
39056
|
external.command("batch").description("Create a deterministic multi-model external-agent batch plan").option("--models <list>", "Comma-separated provider/model list", DEFAULT_BATCH_MODELS).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>", "Batch output directory").option("--json", "Output as JSON").action(async (opts) => {
|
|
38708
39057
|
const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
|
|
38709
|
-
const batchDir = (0,
|
|
39058
|
+
const batchDir = (0, import_path13.resolve)(String(opts.outDir || defaultBatchDir(promptVersion)));
|
|
38710
39059
|
const models = parseModelList(String(opts.models || DEFAULT_BATCH_MODELS));
|
|
38711
|
-
(0,
|
|
39060
|
+
(0, import_fs15.mkdirSync)(batchDir, { recursive: true });
|
|
38712
39061
|
const runs = models.map((model, index) => {
|
|
38713
39062
|
const runId = `${String(index + 1).padStart(2, "0")}-${safeSlug(model.provider)}-${safeSlug(model.name)}`;
|
|
38714
|
-
const runDir = (0,
|
|
38715
|
-
(0,
|
|
39063
|
+
const runDir = (0, import_path13.join)(batchDir, runId);
|
|
39064
|
+
(0, import_fs15.mkdirSync)(runDir, { recursive: true });
|
|
38716
39065
|
const promptPath = writePrompt(runDir, promptVersion);
|
|
38717
39066
|
const commandArgs = [
|
|
38718
39067
|
"eval",
|
|
@@ -38753,8 +39102,8 @@ function registerEval(program3) {
|
|
|
38753
39102
|
runs,
|
|
38754
39103
|
summary_command: `corepack pnpm eval:external-agent:runs:summary -- --root ${batchDir}`
|
|
38755
39104
|
};
|
|
38756
|
-
const batchPath = (0,
|
|
38757
|
-
(0,
|
|
39105
|
+
const batchPath = (0, import_path13.join)(batchDir, "batch.json");
|
|
39106
|
+
(0, import_fs15.writeFileSync)(batchPath, `${JSON.stringify(batch, null, 2)}
|
|
38758
39107
|
`, "utf8");
|
|
38759
39108
|
format(cliEnvelope({
|
|
38760
39109
|
schemaVersion: "external_agent_batch_plan_result.v1",
|
|
@@ -38774,8 +39123,8 @@ function registerEval(program3) {
|
|
|
38774
39123
|
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) => {
|
|
38775
39124
|
const status = normalizeStatus(opts.status);
|
|
38776
39125
|
const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
|
|
38777
|
-
const runDir = (0,
|
|
38778
|
-
(0,
|
|
39126
|
+
const runDir = (0, import_path13.resolve)(String(opts.outDir || defaultRunDir(opts.modelName, promptVersion)));
|
|
39127
|
+
(0, import_fs15.mkdirSync)(runDir, { recursive: true });
|
|
38779
39128
|
const runId = runDir.split(/[\\/]/).filter(Boolean).slice(-1)[0];
|
|
38780
39129
|
const promptPath = writePrompt(runDir, promptVersion);
|
|
38781
39130
|
const shell = inferShell(opts.shell);
|
|
@@ -38797,7 +39146,7 @@ function registerEval(program3) {
|
|
|
38797
39146
|
}
|
|
38798
39147
|
};
|
|
38799
39148
|
writeSession(runDir, session);
|
|
38800
|
-
(0,
|
|
39149
|
+
(0, import_fs15.writeFileSync)((0, import_path13.join)(runDir, "notes.md"), "# External Agent Run Notes\n\n", "utf8");
|
|
38801
39150
|
let shellExitCode = null;
|
|
38802
39151
|
if (opts.shell !== false) {
|
|
38803
39152
|
process.stdout.write(`
|
|
@@ -38819,8 +39168,8 @@ Exit the shell to finalize run.json.
|
|
|
38819
39168
|
shellExitCode = typeof result.status === "number" ? result.status : null;
|
|
38820
39169
|
}
|
|
38821
39170
|
const artifact = buildRunArtifact({ runDir, session, status, reasonCode: opts.reasonCode, shellExitCode });
|
|
38822
|
-
const runPath = (0,
|
|
38823
|
-
(0,
|
|
39171
|
+
const runPath = (0, import_path13.join)(runDir, "run.json");
|
|
39172
|
+
(0, import_fs15.writeFileSync)(runPath, `${JSON.stringify(artifact, null, 2)}
|
|
38824
39173
|
`, "utf8");
|
|
38825
39174
|
format(cliEnvelope({
|
|
38826
39175
|
schemaVersion: "external_agent_capture_result.v1",
|
|
@@ -38830,26 +39179,32 @@ Exit the shell to finalize run.json.
|
|
|
38830
39179
|
artifacts: {
|
|
38831
39180
|
run: runPath,
|
|
38832
39181
|
prompt: promptPath,
|
|
38833
|
-
commands: (0,
|
|
39182
|
+
commands: (0, import_path13.join)(runDir, "commands.ndjson")
|
|
38834
39183
|
},
|
|
38835
39184
|
nextCommands: artifact.next_commands,
|
|
38836
39185
|
extra: { run: artifact }
|
|
38837
39186
|
}), { json: Boolean(opts.json) });
|
|
38838
39187
|
});
|
|
38839
|
-
external.command("
|
|
38840
|
-
|
|
38841
|
-
|
|
38842
|
-
|
|
38843
|
-
|
|
38844
|
-
|
|
38845
|
-
|
|
38846
|
-
|
|
38847
|
-
|
|
38848
|
-
|
|
38849
|
-
})
|
|
38850
|
-
|
|
38851
|
-
|
|
38852
|
-
|
|
39188
|
+
external.command("scan-artifacts").description("Scan and redact external-agent run artifacts before they are promoted into improvement loops").requiredOption("--run-dir <path>", "External-agent run artifact directory").option("--private-repo-root <path>", "Private repository root that must not appear in artifacts").option("--write-redacted", "Write .redacted copies next to scanned artifacts").option("--json", "Output as JSON").action(async (opts) => {
|
|
39189
|
+
const report = scanExternalAgentArtifacts({
|
|
39190
|
+
runDir: String(opts.runDir),
|
|
39191
|
+
privateRepoRoot: opts.privateRepoRoot ? String(opts.privateRepoRoot) : process.cwd(),
|
|
39192
|
+
writeRedacted: Boolean(opts.writeRedacted)
|
|
39193
|
+
});
|
|
39194
|
+
format(cliEnvelope({
|
|
39195
|
+
schemaVersion: "external_agent_artifact_safety_result.v1",
|
|
39196
|
+
status: report.ok ? "pass" : "blocked",
|
|
39197
|
+
reasonCode: report.reason_code,
|
|
39198
|
+
summary: report.ok ? `External-agent artifacts are safe to promote (${report.files_scanned.length} file(s) scanned).` : `External-agent artifacts contain blocked content: ${report.blocked_reason_codes.join(", ")}`,
|
|
39199
|
+
artifacts: {
|
|
39200
|
+
report,
|
|
39201
|
+
redacted_files: report.redacted_files
|
|
39202
|
+
},
|
|
39203
|
+
nextCommands: report.ok ? ["foh bug improve --from external-agent-run --file <run.json> --json"] : ["Do not share or promote raw artifacts. Inspect .redacted copies and fix capture redaction before live rollout."]
|
|
39204
|
+
}), { json: Boolean(opts.json) });
|
|
39205
|
+
if (!report.ok) process.exitCode = 1;
|
|
39206
|
+
});
|
|
39207
|
+
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) => {
|
|
38853
39208
|
try {
|
|
38854
39209
|
const plan = createExternalAgentExecutorPlan({
|
|
38855
39210
|
batchPath: String(opts.batch),
|
|
@@ -38861,6 +39216,46 @@ Exit the shell to finalize run.json.
|
|
|
38861
39216
|
cwd: process.cwd()
|
|
38862
39217
|
});
|
|
38863
39218
|
const planPath = writeExternalAgentExecutorPlan(plan);
|
|
39219
|
+
if (opts.live) {
|
|
39220
|
+
if (plan.runs.length !== 1) {
|
|
39221
|
+
format(cliEnvelope({
|
|
39222
|
+
schemaVersion: "external_agent_execution_result.v1",
|
|
39223
|
+
status: "blocked",
|
|
39224
|
+
reasonCode: "external_agent_live_runner_single_run_only",
|
|
39225
|
+
summary: "Live programmable runner rollout is limited to one run at a time until artifact safety is proven.",
|
|
39226
|
+
artifacts: { plan: planPath },
|
|
39227
|
+
nextCommands: [
|
|
39228
|
+
"Create a one-model batch and rerun with --live."
|
|
39229
|
+
]
|
|
39230
|
+
}), { json: Boolean(opts.json) });
|
|
39231
|
+
process.exitCode = 1;
|
|
39232
|
+
return;
|
|
39233
|
+
}
|
|
39234
|
+
const result = await executeExternalAgentExecutorPlan(plan, {
|
|
39235
|
+
privateRepoRoot: plan.private_repo_root
|
|
39236
|
+
});
|
|
39237
|
+
const resultPath = (0, import_path13.join)(plan.batch_dir, "execution-result.json");
|
|
39238
|
+
(0, import_fs15.writeFileSync)(resultPath, `${JSON.stringify(result, null, 2)}
|
|
39239
|
+
`, "utf8");
|
|
39240
|
+
format(cliEnvelope({
|
|
39241
|
+
schemaVersion: "external_agent_execution_result.v1",
|
|
39242
|
+
status: result.status,
|
|
39243
|
+
reasonCode: result.reason_code,
|
|
39244
|
+
summary: `Controlled ${plan.runner} execution completed with ${result.status}.`,
|
|
39245
|
+
artifacts: {
|
|
39246
|
+
plan: planPath,
|
|
39247
|
+
result: resultPath,
|
|
39248
|
+
runs: result.results.map((run) => run.artifacts.run)
|
|
39249
|
+
},
|
|
39250
|
+
nextCommands: [
|
|
39251
|
+
...result.results.map((run) => `foh eval external-agent scan-artifacts --run-dir ${quoteArg(plan.runs.find((item) => item.run_id === run.run_id)?.run_dir || ".")} --private-repo-root ${quoteArg(plan.private_repo_root)} --write-redacted --json`),
|
|
39252
|
+
"corepack pnpm eval:external-agent:runs:summary"
|
|
39253
|
+
],
|
|
39254
|
+
extra: { result }
|
|
39255
|
+
}), { json: Boolean(opts.json) });
|
|
39256
|
+
if (!result.ok) process.exitCode = 1;
|
|
39257
|
+
return;
|
|
39258
|
+
}
|
|
38864
39259
|
format(cliEnvelope({
|
|
38865
39260
|
schemaVersion: "external_agent_executor_plan_result.v1",
|
|
38866
39261
|
status: "exported",
|