@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 +102 -0
- package/README.md +71 -17
- package/dist/agent/claude.d.ts +2 -7
- package/dist/agent/claude.js +17 -29
- package/dist/agent/codex.d.ts +161 -0
- package/dist/agent/codex.js +369 -0
- package/dist/agent/contract.d.ts +62 -3
- package/dist/agent/contract.js +66 -3
- package/dist/agent/mcp.d.ts +23 -0
- package/dist/agent/mcp.js +23 -0
- package/dist/agent/prompt.d.ts +11 -0
- package/dist/agent/prompt.js +97 -4
- package/dist/agent/run.js +77 -19
- package/dist/agent/shell.d.ts +23 -0
- package/dist/agent/shell.js +27 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
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
|
|
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,
|
|
113
|
-
|
|
114
|
-
the
|
|
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
|
|
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.
|
|
139
|
-
|
|
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
|
-
(
|
|
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
|
|
215
|
-
equivalent flag —
|
|
216
|
-
|
|
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. |
|
package/dist/agent/claude.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
*
|
package/dist/agent/claude.js
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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;
|