SPB Git

Repository Public

codex-profile.md 11.7 KB 154 lines markdown

1# Codex profile ("Codex Remote")23The same code base runs two products. An instance serves exactly one **agent profile**, selected by4`CR_AGENT` (read by `apps/runtime` and `apps/server`; the web build is shared and learns the profile at5run time):67| | `CR_AGENT=claude` (default, unset) | `CR_AGENT=codex` |8|---|---|---|9| Product / agent name | Claude Remote / Claude Code | Codex Remote / Codex |10| Binary | `CR_AGENT_BIN`, else `CR_CLAUDE_BIN`, else `claude` | `CR_AGENT_BIN`, else `codex` (`CR_CLAUDE_BIN` ignored) |11| Default tmux socket (`CR_TMUX_SOCKET` unset) | `claude-remote` | `codex-remote` |12| Default data dir (`CR_DATA_DIR` unset) | `~/claude-remote-data` | `~/codex-remote-data` |13| Agent session command | `$SHELL -lc 'exec claude [--continue | --resume <id>]'` | `$SHELL -lc 'exec codex [resume --last | resume <uuid>]'` |14| Conversation id rule | `^[A-Za-z0-9][A-Za-z0-9-]{7,63}$` | UUID only |15| WAITING_FOR_INPUT signal | hooks `Notification`, `Stop` (settings.json) | `notify` program → `agent-turn-complete` → `Stop` |16| Back to RUNNING | hook `UserPromptSubmit` | first terminal input from a browser |17| Hook installation | `CR_INSTALL_AGENT_HOOKS=1` or `CR_INSTALL_CLAUDE_HOOKS=1` | `CR_INSTALL_AGENT_HOOKS=1` only |1819The definition is `packages/shared/src/agent.ts` (`AGENT_PROFILES`, capability flags, `Branding`).20With no new variable set, every default is the previous Claude behaviour.2122## Session kind: no new value, no migration2324`SessionKind` stays `'claude' | 'shell'` on the wire, in `RtSessionSpec` and in the database25(`terminal_sessions.kind`, CHECK `kind IN ('claude','shell')`). `'claude'` now means **"the26instance's agent"**: Claude Code on a Claude instance, Codex on a Codex instance. Reasons: the live27database has rows with `kind='claude'`, each instance has its own database so the value is never28ambiguous, and no migration or constraint change is needed. Only labels differ in the UI. The29column `claude_conversation_id` likewise holds the Codex session id on a Codex instance.3031## Verified facts about the Codex CLI (codex-cli 0.166.0, macOS arm64)3233Checked on the development laptop with the real binary and a temporary `CODEX_HOME`; no model call was made.3435- `CODEX_HOME` is honoured for configuration, authentication, sessions and MCP (`codex login status`36  answers "Not logged in" in an empty one while the real home is logged in).37- `codex --version` → `codex-cli 0.166.0`.38- `codex login status` prints on **stderr**: `Logged in using ChatGPT` (exit 0) / `Not logged in`39  (exit 1). The runtime reads stdout + stderr and keeps only a boolean and a label (`chatgpt`,40  `api_key`); the rest of the line (a masked key for API-key logins) is never returned.41- Resume: `codex resume [SESSION_ID]` ("Session id (UUID) or session name"), `codex resume --last`42  ("Continue the most recent session without showing the picker"; filtered by cwd unless `--all`).43  A free positional argument of `codex` / `codex resume` is a **prompt** that runs immediately.44- Approval / sandbox bypass flags present in `codex --help`: `--dangerously-bypass-approvals-and-sandbox`,45  `--dangerously-bypass-hook-trust`, `--approve-for-me`, `-s/--sandbox danger-full-access`,46  `-a/--ask-for-approval never`. Hidden aliases accepted by the parser (tested with `--help`):47  `--yolo`, `--not-so-yolo`. `--full-auto` does not exist in this version (still refused by name).48  Configuration overrides: `-c/--config key=value`, `--enable`, `--disable`, `-p/--profile`.49- Sessions ("rollouts"): `$CODEX_HOME/sessions/YYYY/MM/DD/rollout-<local time>-<uuid>.jsonl`. Line 1:50  `{"type":"session_meta","payload":{"id":<uuid v7>,"cwd":<abs path>,"originator":"codex-tui",51  "thread_source":"user","source":…,"base_instructions":…}}` (≈ 22 kB). User prompts are52  `response_item` records with `payload.type="message"`, `role="user"`,53  `content:[{"type":"input_text","text":…}]`; the first one is an injected `<environment_context>` block.54- MCP: `codex mcp list --json` (array; `transport.type` `stdio` | `streamable_http`; **`env` contains55  the values in clear**; `auth_status` `unsupported` | `not_logged_in` | `bearer_token` | `o_auth`),56  `codex mcp add <name> [--env K=V]… -- <command> [args…]`, `codex mcp add <name> --url <url>57  [--bearer-token-env-var VAR]`, `codex mcp get <name> [--json]` (unknown name: exit 1,58  `Error: No MCP server named '<name>' found.`), `codex mcp remove <name>` (unknown name: **exit 0**,59  `No MCP server named '<name>' found.`). `add` on an existing name **replaces it silently**. One60  scope (global, `config.toml`), no SSE, no custom headers from the CLI, no health check in `list`.61- `notify`: top-level key of `config.toml`, an argv array; the binary contains the legacy-notify62  payload fields `type` (`agent-turn-complete`), `thread-id`, `turn-id`, `cwd`, `client`,63  `input-messages`, `last-assistant-message`. Codex Desktop's "Computer Use" plugin sets this key64  itself (`notify = ["…/SkyComputerUseClient", "turn-ended"]` on the laptop).65- A hooks mechanism also exists (`codex features list`: `hooks  stable  true`; events in the binary66  include `UserPromptSubmit`, `Stop`, `SessionStart`, `PreToolUse`, …, configured in `hooks.json`),67  but hooks need a persisted **trust** decision made interactively (hence68  `--dangerously-bypass-hook-trust`). It is not used: installing it unattended would mean bypassing69  that trust.70- Skills: `$CODEX_HOME/skills/<name>/SKILL.md`, bundled ones in `$CODEX_HOME/skills/.system/`;71  the binary also references `.agents/skills` and `.codex/skills`. Instructions: `AGENTS.md`,72  `AGENTS.override.md`. No `$CODEX_HOME/prompts` custom-prompt directory is referenced by this version.7374## What the runtime does differently (`apps/runtime`)7576- **Launch** (`codex-args.ts`, `sessions.ts`). Accepted argument lists: none, `resume --last`,77  `resume <uuid>`. Refused with 400 `forbidden_flag`: every bypass flag above (any spelling78  containing `bypass` or `yolo`), `-c/--config`, `--enable`, `--disable`, `-p/--profile`, `--remote`.79  Refused with 400 `invalid_resume`: any other shape — in particular a free positional argument.80- **`GET /health`** gains `agent` and `agentHooks: {requested, status: not_requested | installed |81  conflict | error, detail}`.82- **`GET /claude/info`**: `codex --version` + `codex login status`; `configDir` = `$CODEX_HOME`;83  `agent: "codex"`.84- **`POST /claude/conversations`**: rollouts whose recorded `cwd` equals the requested directory85  (real or lexical path), newest first by mtime, at most 50. Bounded: 400 newest files examined,86  256 KiB read per file for the meta record, 4 MiB per listed session. Sub-agent threads87  (`thread_source` ≠ `user`) and `source: "exec"` runs are skipped.88- **`GET /skills`**: kind `skill` from `$CODEX_HOME/skills`, `~/.agents/skills`,89  `$CODEX_HOME/skills/.system` (source `plugin` = bundled with the CLI), and for a project90  `<root>/.agents/skills`, `<root>/.codex/skills`; kind `agent` for `AGENTS.md` /91  `AGENTS.override.md` (global and project). Never kind `command`. `POST /skills/op` works on skills92  only (user scope `$CODEX_HOME/skills`, disabled ones parked in `$CODEX_HOME/.disabled/skills`;93  project scope `<root>/.agents/`).94- **`/mcp`**: list through `codex mcp list --json` (scope always `user`, status `unknown`, or95  `needs_auth` when `auth_status` is `not_logged_in`; "Disabled" as `lastError`); env values, URL96  credentials and query strings are dropped. `add` refuses SSE, headers and project scope (40097  `unsupported`) and an existing name (409, checked first because the CLI would overwrite).98  `test` = `codex mcp get --json`: it only proves the server is configured.99- **Notify** (`codex-notify.ts`, `bin/cr-codex-notify`). With `CR_INSTALL_AGENT_HOOKS=1` the runtime100  sets `notify = ["<repo>/apps/runtime/bin/cr-codex-notify"]` in `$CODEX_HOME/config.toml` by a101  line-based edit of the top-level key (everything else is preserved byte for byte; file mode kept;102  atomic write). An existing `notify` that is not ours is **never replaced**: the file is left103  untouched, `/health` reports `agentHooks.status = "conflict"` and `/system/status` shows a104  DEGRADED `agent-hooks` component. The script is a no-op outside a Codex Remote session; inside it105  forwards the JSON (stdin of curl, token on fd 3) to `POST /hooks/claude`, where106  `agent-turn-complete` becomes `Stop` with `thread-id` as conversation id. The prompt and the107  assistant message contained in the payload are not kept.108109## What the server does differently (`apps/server`)110111- `GET /auth/status` → `branding: {agent, productName, agentName}` (public, both profiles).112  `GET /api/v1/health` → `{ok, version, agent: "codex"}` and `/system/status.claude` gains `agent`,113  `agentName` — **on a Codex instance only**: a Claude instance keeps its exact previous payloads114  (`{ok, version}`; absent `agent` ⇒ claude), which the existing test-suite pins; TOTP issuer, WebAuthn RP115  name and OpenAPI title use the product name. Cookie names are unchanged (different host).116- Arguments: `resume <uuid>` / `resume --last`; `resumeConversationId` and ids reported by the117  notify script must be UUIDs.118- **Input ends the wait.** Codex's notify has no "prompt submitted" event, so for profiles without119  a prompt-submit hook the terminal WebSocket proxy reports client → runtime **binary** frames120  (never their content) and the first one after `WAITING_FOR_INPUT` moves the session to `RUNNING`121  (`terminal_events` source `input`), once per waiting period. Control frames (resize, ping) do not122  count. This code path is not wired at all for the Claude profile.123- Static files: `/manifest.webmanifest` and `/icons/<file>` are answered from124  `<web export>/brand/codex/…` when that file exists (`static.ts`, `brandAsset`).125126## Web (`apps/web`)127128One build. `GET /auth/status.branding` decides the product; it is cached in `localStorage`129(`cr-branding`) and applied before paint by `/theme-init.js` (`<html data-agent="codex">`, document130title). Codex uses an emerald → teal → cyan accent (`html[data-agent='codex']` tokens in131`globals.css`, AA checked by `dev/check-contrast.mjs`). Capability flags hide what Codex does not132have (MCP project scope / SSE / headers / live status, command and sub-agent kinds).133134## Limitations / not verified135136- **No real Codex turn was run** (no paid call). That Codex 0.166 really invokes the legacy `notify`137  program at the end of an interactive TUI turn, and the exact payload it sends, are inferred from138  the binary and the documentation of the mechanism, not observed. The relay, the mapping and the139  state changes are tested with the real script and a payload of that shape.140- If `~/.codex/config.toml` on the node already has a `notify` (Codex Desktop's Computer Use sets141  one), the runtime does not install its own and the instance has **no WAITING_FOR_INPUT**. Remove142  the other entry by hand if the signal is wanted, then restart the runtime.143- "Input ⇒ RUNNING" is a heuristic: any keystroke counts (also one that only edits the prompt), and144  input typed directly in tmux outside the app is not seen.145- Approval prompts of Codex do not produce a notify event: such a session stays `RUNNING`.146- `codex resume --last` and `codex resume <uuid>` were validated against the CLI parser147  (`--help`) and launched without a recorded session only; resuming a real session was not exercised.148- The rollout format, the `mcp --json` schema and the `login status` wording are those of 0.166.0;149  the session filter (`thread_source`, `source`) is a heuristic based on four local rollouts.150- `codex mcp add --url` may start an OAuth login for servers that support it; the runtime runs it151  with no terminal and a 30 s timeout.152- The exported HTML carries the Claude Remote title and description until JavaScript runs.153- Codex `hooks.json` (which has a `UserPromptSubmit` event) is deliberately not used; see above.154
Files main
Menu

Theme