@prohost/cli 0.2.0 → 0.2.2

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/CHANGELOG.md CHANGED
@@ -4,6 +4,108 @@ Versions follow [semver](https://semver.org/). Publishing is automated: merging
4
4
  a version bump to `main` triggers `.github/workflows/npm-publish-cli.yml`, which
5
5
  builds via `prepack`, runs the suite, publishes, and tags `cli-v<version>`.
6
6
 
7
+ ## 0.2.2
8
+
9
+ Two agents paired to the same workspace behaved like different products: the
10
+ one on Claude Code remembered its threads and could look things up, the one on
11
+ Codex ran bare. And neither knew who its operator had hired it to be — the
12
+ persona configured in the ProhostAI agent profile never left the server.
13
+
14
+ - **Your agent gets its soul.** `agent.run_requested` may now carry
15
+ `system_prompt` — the standing instructions written in the agent's ProhostAI
16
+ profile — and `agent_title`. The instructions render at the very top of the
17
+ prompt, above memories and history, fenced off from the server-generated
18
+ sections so the agent can always tell where its operator stops talking. The
19
+ title renders alongside the name: `You are "Ada" (Guest Experience Lead)`. The
20
+ fence is followed by a line stating that what comes after is generated for
21
+ this run and describes what is actually true about it — a soul configures the
22
+ agent, it does not get to talk it out of reporting a lookup it never made.
23
+ Both fields are optional in both directions, so an older server behaves
24
+ exactly as before.
25
+ - **Attachments are visible.** `recent_messages[].attachments` and
26
+ `trigger_attachments` render as `[attached image "boiler.jpg": <url>]` inline
27
+ in the thread and as a list under the triggering message. An image-only
28
+ message — a guest who sends a photo of a broken boiler and nothing else —
29
+ used to be dropped entirely by the parser for having no text; it now survives
30
+ as the attachment it is, and the prompt says so rather than reporting the text
31
+ as missing. The agent is told plainly that a URL is not something it can see,
32
+ and to download one to a local file before opening it, because vision works on
33
+ local files and not on links.
34
+ - **The agent stops claiming it has no access to your machine.** A brain
35
+ running unsandboxed on a laptop that holds the checkout, the credentials and
36
+ the CLI was still answering "I don't have access to the codebase". Ungated
37
+ runs are now told they are on the operator's own machine, in their shell, and
38
+ to take a *quick* look — usual checkout locations, `gh auth status`, whether
39
+ the command is on PATH — before declaring a capability missing. A quick look,
40
+ not an audit: the run has a hard time cap. Under `--safe-tools` the licence is
41
+ withheld, because there the looking would itself sit waiting on an approval
42
+ nobody is there to give.
43
+ - **Codex gets the harness Claude Code has had since 0.2.0.** A `--exec` of
44
+ `codex exec` is now detected and shaped, with the same `--safe-tools` opt-out:
45
+
46
+ | | Claude Code | Codex |
47
+ |---|---|---|
48
+ | Structured output | `--output-format json` | `--json` (JSONL) |
49
+ | Session continuity | `--resume <id>` | `codex exec resume <thread>` |
50
+ | Tools | `--mcp-config` + `--strict-mcp-config` | `-c mcp_servers.prohost={…}` |
51
+ | Credential | `X-API-Key` in a `0600` file | `X-API-Key` via `env_http_headers` |
52
+ | Ungated by default | `--dangerously-skip-permissions` | `--yolo` |
53
+
54
+ Three things worth knowing about the Codex path. `--skip-git-repo-check` is
55
+ passed unconditionally, because the harness workspace is not a git repository
56
+ and Codex otherwise exits before the model is ever reached. `--yolo` is a
57
+ hidden alias of `--dangerously-bypass-approvals-and-sandbox` and passing both
58
+ spellings is a hard argument error, so a host who already wrote either one
59
+ never gets a second. And `resume` is appended *after* the host's own flags,
60
+ since `codex exec resume` accepts a strict subset of `codex exec`'s — a flag
61
+ that is legal before `resume` is rejected after it. When a resume is rejected
62
+ anyway, the run falls back to a fresh thread instead of failing the surface.
63
+ - **`--safe-tools` now removes a bypass you wrote yourself.** It used to only
64
+ decline to *add* one, so `--exec 'codex exec - --yolo' --safe-tools` ran fully
65
+ unsandboxed while the harness logged "tools are permission-gated" and the
66
+ prompt told the agent approvals would stop it. Since this README recommended
67
+ exactly that `--exec` until now, the flag is sitting in existing installs. A
68
+ safety switch that quietly does nothing is worse than not having one, so both
69
+ `--yolo` / `--dangerously-bypass-approvals-and-sandbox` (Codex) and
70
+ `--dangerously-skip-permissions` (Claude Code) are now stripped when
71
+ `--safe-tools` is passed. Note this covers the full bypass only —
72
+ `--full-auto` still auto-approves inside Codex's sandbox.
73
+ - **A Codex command carrying its own prompt is left alone.** `codex exec 'Be
74
+ terse'` keeps the plain stdin/stdout contract: Codex takes exactly one prompt
75
+ positional, so the `-` this harness appends would be a second one and the run
76
+ would fail outright with `unexpected argument '-'`. Standing instructions
77
+ belong in the agent's ProhostAI profile now, which is where `system_prompt`
78
+ comes from.
79
+ - **The Codex credential never touches the command line.** Codex can read an
80
+ MCP header out of the environment (`env_http_headers`), so the API key is
81
+ passed to the child that way rather than inside the `-c` override. A command
82
+ line is world-readable out of `ps`; a process environment is not. This makes
83
+ the Codex path slightly *safer* than the Claude one, which still has to keep
84
+ the key in a `0600` file on disk.
85
+
86
+ **Residual gaps on the Codex path**, both stated at start-up or in the prompt
87
+ rather than papered over:
88
+
89
+ - **No `--strict-mcp-config` equivalent.** Codex's `--ignore-user-config` was
90
+ the obvious candidate and does not work: it drops the `-c` overrides along
91
+ with the user config, taking the ProhostAI server with it. So a Codex run also
92
+ holds whatever MCP servers `~/.codex/config.toml` defines, with approvals
93
+ bypassed. The harness prints this at start-up rather than leaving an operator
94
+ to assume the Claude Code isolation applies.
95
+ - **Codex's MCP tool naming is unverified**, so the prompt names the server
96
+ (`registered as "prohost"`) instead of asserting a `mcp__prohost__*` pattern
97
+ it has not confirmed. The agent can read its own tool list; a wrong tool name
98
+ is the same failure as a wrong endpoint.
99
+ - **No permission-denial reporting.** Claude Code returns `permission_denials`
100
+ and the harness prints them; Codex's event stream has no equivalent, so a
101
+ refused tool stays as invisible on that path as it was before 0.2.0.
102
+
103
+ ## 0.2.1
104
+
105
+ - Prompt: tell brains with a ToolSearch tool that part of their toolkit
106
+ (notably WebSearch/WebFetch) is deferred and must be loaded before use, so
107
+ a paired agent stops telling customers it cannot browse the web when it can.
108
+
7
109
  ## 0.2.0
8
110
 
9
111
  The harness stopped being a one-shot prompt pipe. 0.1.x answered every mention
package/README.md CHANGED
@@ -87,7 +87,7 @@ in a team thread, a 1:1 DM, a task comment, a trigger — you'll see:
87
87
  Other agents, same command:
88
88
 
89
89
  ```bash
90
- npx @prohost/cli agent run --exec 'codex exec - --yolo'
90
+ npx @prohost/cli agent run --exec 'codex exec'
91
91
  npx @prohost/cli agent run --exec 'goose run -i -'
92
92
  npx @prohost/cli agent run --exec './my-agent.sh'
93
93
  ```
@@ -109,13 +109,14 @@ nothing is posted and the run is left open.
109
109
  2. The CLI verifies the event's HMAC signature. An event that doesn't verify is
110
110
  **not executed** — the whole point of the signature is that a run event makes
111
111
  your machine execute a command.
112
- 3. It builds a prompt — who your agent is, what it's replying to, the recent
113
- messages in the thread, anything the account remembers that bears on it, and
114
- the message that mentioned it — and runs your `--exec` command with that
115
- prompt on **stdin**.
112
+ 3. It builds a prompt — who your agent is, the standing instructions you wrote
113
+ in its ProhostAI profile, what it's replying to, the recent messages in the
114
+ thread, anything the account remembers that bears on it, and the message that
115
+ mentioned it — and runs your `--exec` command with that prompt on **stdin**.
116
+ Images and files on those messages appear as URLs the agent can download.
116
117
  4. Your command's **stdout** is the reply. It's posted into the conversation
117
118
  verbatim, so print the message and nothing else — no preamble, no reasoning.
118
- (Claude Code is the exception: see below.)
119
+ (Claude Code and Codex are the exceptions: see below.)
119
120
  5. The run is completed either way. A command that crashes, times out, or prints
120
121
  nothing reports `failed`; a run is never left hanging.
121
122
 
@@ -135,8 +136,9 @@ directory. Just choose it deliberately.
135
136
  ### If your agent is Claude Code
136
137
 
137
138
  When `--exec` starts with `claude` and asks for print mode (`-p` / `--print`),
138
- the CLI does three extra things. Every other command — Codex, goose, your own
139
- script — is left exactly alone.
139
+ the CLI does three extra things. Codex gets the same three through its own
140
+ flags — see "If your agent is Codex" below. Every other command — goose, your
141
+ own script — is left exactly alone.
140
142
 
141
143
  **It keeps the conversation.** Each run is a fresh process, so without this your
142
144
  agent meets every message as its first: someone answers its clarifying question
@@ -177,6 +179,54 @@ word of the command *and* print mode is asked for. Anything else — `npx claude
177
179
  redirect, or `&&` — keeps the plain stdin/stdout contract untouched. So
178
180
  `--exec 'sh -c "claude -p"'` is the way to opt out.
179
181
 
182
+ ### If your agent is Codex
183
+
184
+ When `--exec` starts with `codex` and uses the `exec` subcommand, the CLI does
185
+ the same three things it does for Claude Code, through Codex's own flags. The
186
+ detection is just as narrow: interactive `codex`, a wrapper script, a pipeline,
187
+ a command that already says `codex exec resume` / `codex exec review`, or one
188
+ carrying its own prompt (`codex exec 'Be terse'`) all keep the plain
189
+ stdin/stdout contract untouched. That last one matters: Codex takes exactly one
190
+ prompt positional, so the CLI cannot add its own alongside yours. Put standing
191
+ instructions in the agent's ProhostAI profile instead — they arrive at the top
192
+ of the prompt, ahead of everything else.
193
+
194
+ | | Claude Code | Codex |
195
+ | --- | --- | --- |
196
+ | Reply read from | `--output-format json` | `--json` (JSONL event stream) |
197
+ | Conversation kept with | `--resume <id>` | `codex exec resume <thread>` |
198
+ | Tools wired via | `--mcp-config` | `-c mcp_servers.prohost={…}` |
199
+ | API key travels in | `~/.prohost/mcp.json` (`chmod 600`) | the child's environment |
200
+ | Tools ungated by | `--dangerously-skip-permissions` | `--yolo` |
201
+
202
+ `--skip-git-repo-check` is also passed on every Codex run, and it is not
203
+ optional: your agent works in `~/.prohost/workspace`, which is not a git
204
+ repository, and Codex refuses to start there without it.
205
+
206
+ If you already pass `--yolo` (or `--dangerously-bypass-approvals-and-sandbox`)
207
+ in your own `--exec`, the CLI won't add a second one — passing both is an
208
+ argument error that would fail every run. `--safe-tools` works the same way it
209
+ does for Claude Code, and it *removes* a bypass flag you wrote yourself rather
210
+ than just declining to add one, so the gating it promises is real. (It covers
211
+ the full bypass only; `--full-auto` still auto-approves inside Codex's sandbox.)
212
+
213
+ **Two things Codex cannot do that Claude Code can**, both worth knowing before
214
+ you leave it running:
215
+
216
+ - **Your own MCP servers come along.** There is no Codex equivalent of
217
+ `--strict-mcp-config`. `--ignore-user-config` looks like one but drops the
218
+ `-c` overrides too, taking the ProhostAI server with them — so a Codex run
219
+ also holds whatever servers your `~/.codex/config.toml` defines, with
220
+ approvals bypassed. The CLI prints this at start-up so it isn't a surprise.
221
+ If that matters, keep `~/.codex/config.toml` free of servers you would not
222
+ hand to a teammate's message.
223
+ - **Refused tools stay invisible.** Claude Code reports what it was denied and
224
+ the CLI prints it; Codex's event stream has no equivalent.
225
+
226
+ Your ProhostAI API key is passed to Codex in the child process's environment
227
+ rather than in the `-c` override, because a command line is readable by anyone
228
+ on the machine via `ps` and a process environment is not.
229
+
180
230
  Runs are handled one at a time, and each run executes **at most once on this
181
231
  machine** — even across restarts. Started runs are recorded in
182
232
  `~/.prohost/runs.json` before the command launches, so a webhook redelivery
@@ -193,8 +243,11 @@ someone in your ProhostAI workspace asks. Be clear-eyed about what that means:
193
243
  - **The agent runs as you.** Same user, same filesystem, same SSH keys, same
194
244
  logged-in CLIs. It is not sandboxed.
195
245
  - **By default its tools are not gated.** With `--dangerously-skip-permissions`
196
- (the default for Claude Code) nothing pauses for confirmation. That is what
197
- makes it useful and it is also the whole risk.
246
+ (Claude Code) or `--yolo` (Codex) nothing pauses for confirmation. That is
247
+ what makes it useful and it is also the whole risk.
248
+ - **Its standing instructions come from the ProhostAI agent profile.** Whoever
249
+ can edit that profile is configuring a brain that runs unsandboxed on this
250
+ machine. Treat edit access to it as access to your laptop.
198
251
  - **The instructions come from other people.** The prompt carries a message a
199
252
  teammate wrote, plus thread history. Treat all of it as untrusted input — the
200
253
  same judgement you'd apply to any agent reading text from the internet. A
@@ -204,16 +257,17 @@ someone in your ProhostAI workspace asks. Be clear-eyed about what that means:
204
257
  What the harness does to bound this: the message reaches your command on
205
258
  **stdin only**, never the shell, so it cannot inject a command; runs execute at
206
259
  most once each; every event's signature is verified before anything runs; the
207
- agent works in `~/.prohost/workspace` rather than your code; and
208
- `--strict-mcp-config` keeps your own MCP servers out of the run.
260
+ agent works in `~/.prohost/workspace` rather than your code; and, on Claude
261
+ Code, `--strict-mcp-config` keeps your own MCP servers out of the run. Codex has
262
+ no equivalent — see "If your agent is Codex".
209
263
 
210
264
  What it does **not** do: sandbox the process. If that matters for your machine,
211
265
  run `agent run` inside a container or a dedicated user account, or use
212
266
  `--safe-tools` and accept that the agent will answer rather than act.
213
267
 
214
- Other agents: the CLI shapes no command but Claude Code's, so give yours its own
215
- equivalent flag — `codex --yolo` for Codex CLI, whatever your runner calls it —
216
- and apply the same judgement.
268
+ Other agents: the CLI shapes no command but Claude Code's and Codex's, so give
269
+ yours its own equivalent flag — whatever your runner calls it — and apply the
270
+ same judgement.
217
271
 
218
272
  ### `agent run` flags
219
273
 
@@ -227,7 +281,7 @@ and apply the same judgement.
227
281
  | `--machine` | OS hostname | Name shown next to the connection in ProhostAI. |
228
282
  | `--url` | paired value | Websocket endpoint override. Pairing stores the URL the server reports, so you should not normally need this. |
229
283
  | `--allow-unverified` | off | Run events whose signature can't be verified. See below. |
230
- | `--safe-tools` | off | Gate the agent's tools behind permission prompts instead of bypassing them. Claude Code only. Alias: `--no-skip-permissions`. |
284
+ | `--safe-tools` | off | Gate the agent's tools behind permission prompts instead of bypassing them. Claude Code and Codex only. Alias: `--no-skip-permissions`. |
231
285
 
232
286
  ### Keeping it running
233
287
 
@@ -307,7 +361,7 @@ Everything the CLI stores lives under `~/.prohost` (`$PROHOST_HOME` moves it):
307
361
  | --- | --- |
308
362
  | `agent.json` | Credentials. Delete to un-pair locally; revoke in ProhostAI to invalidate the key itself. |
309
363
  | `runs.json` | Runs already executed on this machine, so a redelivery can't run your agent twice. |
310
- | `sessions.json` | Conversation → Claude Code session, for continuity. Safe to delete; costs the agent its memory of open threads. |
364
+ | `sessions.json` | Conversation → Claude Code session / Codex thread, for continuity. Safe to delete; costs the agent its memory of open threads. |
311
365
  | `mcp.json` | Generated MCP config. Holds your API key — `chmod 600`. |
312
366
  | `workspace/` | Where your agent command runs by default. |
313
367
  | `agent.log` | Daemon output. |
@@ -14,13 +14,8 @@
14
14
  * session is unresumable, if the JSON is not what we expect — the run still
15
15
  * happens and still completes. These are enhancements that must fail soft.
16
16
  */
17
- /**
18
- * Name the ProhostAI MCP server is registered under, which is also what makes
19
- * its tools appear as `mcp__prohost__<tool>`. Referenced in the prompt, in the
20
- * `--allowedTools` pattern, and in the config file — keep them in sync from
21
- * here rather than by hand.
22
- */
23
- export declare const MCP_SERVER_NAME = "prohost";
17
+ import { MCP_SERVER_NAME } from './mcp.js';
18
+ export { MCP_SERVER_NAME };
24
19
  /**
25
20
  * What goes to `--allowedTools` so the ProhostAI tools are callable.
26
21
  *
@@ -17,13 +17,9 @@
17
17
  import { chmodSync, mkdirSync, writeFileSync } from 'node:fs';
18
18
  import path from 'node:path';
19
19
  import { prohostHome } from './credentials.js';
20
- /**
21
- * Name the ProhostAI MCP server is registered under, which is also what makes
22
- * its tools appear as `mcp__prohost__<tool>`. Referenced in the prompt, in the
23
- * `--allowedTools` pattern, and in the config file — keep them in sync from
24
- * here rather than by hand.
25
- */
26
- export const MCP_SERVER_NAME = 'prohost';
20
+ import { MCP_SERVER_NAME } from './mcp.js';
21
+ import { SHELL_CONTROL_OPERATORS, shellQuote } from './shell.js';
22
+ export { MCP_SERVER_NAME };
27
23
  /**
28
24
  * What goes to `--allowedTools` so the ProhostAI tools are callable.
29
25
  *
@@ -75,27 +71,6 @@ export function isClaudeExec(command) {
75
71
  return false;
76
72
  return words.some((word) => word === '-p' || word === '--print');
77
73
  }
78
- /** Pipes, lists, redirections, subshells — anything that makes "append to the end" wrong. */
79
- const SHELL_CONTROL_OPERATORS = /[|&;<>(){}`]|\$\(/;
80
- /**
81
- * Quote a value for a `shell: true` command line on this platform.
82
- *
83
- * Every value that goes through here is ours or the operator's — a session id
84
- * we stored, a path under `$PROHOST_HOME` — never event-derived text. It is
85
- * quoted anyway: a home directory with a space in it is ordinary, and "the
86
- * inputs are trusted" is the assumption that stops being true the first time
87
- * someone extends this.
88
- *
89
- * Windows needs its own branch. `shell: true` there runs `cmd.exe`, where a
90
- * single quote is an ordinary character rather than a quoting one — POSIX
91
- * quoting would hand Claude Code a config path with literal apostrophes around
92
- * it, and the tools would simply never load.
93
- */
94
- function shellQuote(value, platform) {
95
- if (platform === 'win32')
96
- return `"${value.replace(/"/g, '""')}"`;
97
- return `'${value.replace(/'/g, `'\\''`)}'`;
98
- }
99
74
  /**
100
75
  * Append the flags this harness needs to a Claude Code command.
101
76
  *
@@ -118,7 +93,20 @@ export function claudeCommand(base, flags = {}) {
118
93
  const platform = flags.platform ?? process.platform;
119
94
  const quote = (value) => shellQuote(value, platform);
120
95
  const skipPermissions = flags.skipPermissions !== false;
121
- const parts = [base.trim(), '--output-format', 'json'];
96
+ // `--safe-tools` removes the bypass rather than merely declining to add it.
97
+ // A host who wrote `--dangerously-skip-permissions` into their own `--exec`
98
+ // and *also* passed `--safe-tools` is contradicting themselves, and the only
99
+ // safe way to resolve that is in favour of the flag whose entire purpose is
100
+ // to gate — otherwise the harness announces "tools are permission-gated" over
101
+ // a run that bypasses every one of them.
102
+ const command = skipPermissions
103
+ ? base.trim()
104
+ : base
105
+ .trim()
106
+ .split(/\s+/)
107
+ .filter((word) => word !== '--dangerously-skip-permissions')
108
+ .join(' ');
109
+ const parts = [command, '--output-format', 'json'];
122
110
  if (skipPermissions)
123
111
  parts.push('--dangerously-skip-permissions');
124
112
  if (flags.sessionId)
@@ -0,0 +1,161 @@
1
+ /**
2
+ * The one place that knows anything about Codex CLI specifically.
3
+ *
4
+ * The sibling of `claude.ts`, and it exists for the same reason: `exec.ts` is
5
+ * deliberately agent-agnostic — prompt on stdin, reply on stdout — and that
6
+ * lowest common denominator costs a Codex run the two things it can actually
7
+ * have. A run has no memory of the previous one, and it has no tools for
8
+ * looking anything up in the workspace it is answering about. Until this
9
+ * module existed, a `codex` brain ran bare while a `claude` brain got both, so
10
+ * the same mention answered by two hosts produced visibly different work.
11
+ *
12
+ * Everything here was verified against a real `codex-cli 0.144.1` rather than
13
+ * taken from documentation; the surprises are called out where they are relied
14
+ * on. As in `claude.ts`, nothing in this module is load-bearing for
15
+ * correctness: if detection misses, if the session is unresumable, if the JSON
16
+ * is not what we expect — the run still happens and still completes.
17
+ */
18
+ /**
19
+ * Environment variable the API key is handed to Codex in.
20
+ *
21
+ * Codex resolves MCP headers from the environment at request time
22
+ * (`env_http_headers`), which is why the key travels this way rather than in
23
+ * the config override. The override is part of the command line, and a command
24
+ * line is world-readable out of `ps`; a child's environment is not. This is the
25
+ * one place the Codex path is meaningfully *safer* than the Claude one, which
26
+ * has to keep the key in a `0600` file on disk.
27
+ */
28
+ export declare const MCP_API_KEY_ENV_VAR = "PROHOST_MCP_API_KEY";
29
+ /**
30
+ * Is this exec command Codex CLI in non-interactive mode?
31
+ *
32
+ * A **heuristic**, and deliberately as narrow as {@link isClaudeExec}: it
33
+ * matches when the first word of the command is `codex` (or a path ending in
34
+ * `/codex`) *and* the command uses the `exec` subcommand (or its `e` alias).
35
+ * Both halves matter. Interactive `codex` has no `--json` and no `resume`
36
+ * subcommand, so shaping it would break a command that works today.
37
+ *
38
+ * Three things this refuses to touch, each because appending to it would do
39
+ * harm rather than nothing:
40
+ *
41
+ * * Commands with shell control operators. Flags are appended to the *end* of
42
+ * the string, so for `codex exec | jq .` they would land on `jq`.
43
+ * * `codex exec resume …` and `codex exec review …`. The host already chose a
44
+ * sub-subcommand; appending our own `resume` would produce two of them.
45
+ * * Anything after a `--` sentinel, which is the host saying "stop parsing".
46
+ * * A command carrying its own prompt, e.g. `codex exec 'Be terse'`. Codex
47
+ * takes exactly one prompt positional, so both the `-` this harness appends
48
+ * and a `resume <id>` would be a second one — `error: unexpected argument
49
+ * '-' found`, on a command that works today. Such a host keeps the plain
50
+ * contract; the place to put standing instructions is now the agent's
51
+ * ProhostAI profile, which arrives in the prompt itself.
52
+ */
53
+ export declare function isCodexExec(command: string): boolean;
54
+ /**
55
+ * The `-c` override that registers the ProhostAI MCP server.
56
+ *
57
+ * Codex takes a streamable-HTTP MCP server with **arbitrary** headers, which is
58
+ * what makes this possible at all: `codex mcp add --url` offers only
59
+ * `--bearer-token-env-var`, and a bearer token is the *wrong credential* for
60
+ * this endpoint — the server validates `Authorization: Bearer` as an OAuth
61
+ * access token, checks it first, and does not fall through to `X-API-Key` when
62
+ * it fails. A pairing hands out an `X-API-Key`. The `-c` path reaches the same
63
+ * config layer with none of that restriction.
64
+ *
65
+ * `env_http_headers` rather than `http_headers`: both work and both were
66
+ * verified against the running binary, but the former names an environment
67
+ * variable to read the value from instead of carrying the value itself, which
68
+ * keeps a live API key off a command line that anyone on the machine can read
69
+ * out of `ps`.
70
+ */
71
+ export declare function buildMcpOverride(mcpUrl: string): string;
72
+ export interface CodexFlags {
73
+ /** Session (Codex calls it a thread) to resume. Omitted for the first turn. */
74
+ sessionId?: string;
75
+ /** The paired MCP endpoint, when tools are being wired in. */
76
+ mcpUrl?: string;
77
+ /**
78
+ * Run with approvals and the sandbox bypassed. Default ON — see
79
+ * {@link codexCommand}. Set false by `agent run --safe-tools`.
80
+ */
81
+ skipPermissions?: boolean;
82
+ /** Quoting target. Defaults to the running platform; tests set it. */
83
+ platform?: NodeJS.Platform;
84
+ }
85
+ /**
86
+ * Append the flags this harness needs to a Codex command.
87
+ *
88
+ * Four things get added, and each one is load-bearing:
89
+ *
90
+ * `--json` turns stdout into a JSONL event stream, which is the only way to
91
+ * read the thread id back out (and so the only way sessions can continue).
92
+ *
93
+ * `--skip-git-repo-check` is not optional. The harness runs the agent in
94
+ * `$PROHOST_HOME/workspace`, which is not a git repository, and Codex refuses
95
+ * to start there — `Not inside a trusted directory and --skip-git-repo-check
96
+ * was not specified.` Without this flag **every** Codex run fails before the
97
+ * model is ever reached.
98
+ *
99
+ * `--yolo` mirrors Claude Code's `--dangerously-skip-permissions`, for the same
100
+ * reason: in a non-interactive run nobody is there to approve a command, so a
101
+ * sandboxed brain silently declines the work it was hired for. It is a hidden
102
+ * alias of `--dangerously-bypass-approvals-and-sandbox`, and passing both spells
103
+ * is a hard clap error (`cannot be used multiple times`) — so a host who already
104
+ * wrote either one must not get ours on top, or every run of theirs fails.
105
+ *
106
+ * `resume` goes **after** the host's own flags, not before them. `codex exec
107
+ * resume` accepts a strict subset of `codex exec`'s flags (no `-s`, `-C`,
108
+ * `--add-dir`, `-p`), and a flag placed after `resume` is parsed by `resume`.
109
+ * `codex exec -s workspace-write resume <id>` parses; `codex exec resume <id>
110
+ * -s workspace-write` is rejected. Appending keeps the host's flags where Codex
111
+ * still understands them.
112
+ */
113
+ export declare function codexCommand(base: string, flags?: CodexFlags): string;
114
+ /** What the harness got back from one Codex invocation. */
115
+ export type CodexOutput = {
116
+ ok: true;
117
+ text: string;
118
+ sessionId?: string;
119
+ } | {
120
+ ok: false;
121
+ error: string;
122
+ sessionId?: string;
123
+ };
124
+ /**
125
+ * Read the reply and the thread id out of `--json` stdout.
126
+ *
127
+ * Codex emits **JSONL**, not one object: `thread.started` (carrying
128
+ * `thread_id`), `turn.started`, an `item.completed` per item the turn produced,
129
+ * then `turn.completed`. Only `agent_message` items are the answer — reasoning
130
+ * and command items share the stream — and the last one is what a person would
131
+ * have seen at the end of the run.
132
+ *
133
+ * Lines that are not JSON are skipped rather than treated as corruption,
134
+ * because they are *expected*: an unreachable MCP server puts `ERROR
135
+ * rmcp::transport::worker: …` straight into this stream, and the run around it
136
+ * succeeds normally. This was observed, not guessed at.
137
+ *
138
+ * As in `parseClaudeOutput`, output with no reply in it is a **failure** rather
139
+ * than a fallback to posting the raw text. We asked for JSON; anything else
140
+ * means something went wrong that we do not understand, and the one thing that
141
+ * must not happen is that reaching a customer's conversation.
142
+ */
143
+ export declare function parseCodexOutput(stdout: string): CodexOutput;
144
+ /**
145
+ * Does this look like the resume having been rejected, rather than the agent
146
+ * having failed at its actual work?
147
+ *
148
+ * Two ways a Codex resume can fail where a fresh run would have worked: the
149
+ * thread is gone (rollout files get cleaned up and the ledger cannot know), or
150
+ * the host's own flags are ones `codex exec resume` does not accept, which clap
151
+ * reports as `unexpected argument`. Both must degrade to a fresh session
152
+ * instead of breaking that surface forever.
153
+ *
154
+ * The `thread.started` guard is what keeps this from firing on an ordinary bad
155
+ * turn. Codex echoes the thread id in that event on **success**, so the
156
+ * "output mentions the session id" heuristic — which is all `claude.ts` can
157
+ * do — would match a resumed run that started fine and then failed at its
158
+ * work, and retrying that runs the agent, and its tools, a second time for
159
+ * nothing.
160
+ */
161
+ export declare function looksLikeCodexResumeFailure(output: string, sessionId: string): boolean;