docs: spec and plan for codex lanes
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
# Codex lanes in the Workspace — design
|
||||
|
||||
Issue: [Smartgift-AI/Claude-Code-Monitor#10](https://git.smartgift.io.vn/Smartgift-AI/Claude-Code-Monitor/issues/10)
|
||||
— "Workspace | Thêm Codex vào trang Workspace".
|
||||
|
||||
## Goal
|
||||
|
||||
A lane can be driven by the `codex` CLI instead of `claude`, started and watched
|
||||
from the Workspace page exactly like today: same lane strip, same pipeline map,
|
||||
same tmux-backed terminal console, same run history.
|
||||
|
||||
## Decisions (from brainstorming)
|
||||
|
||||
1. Scope: run Codex in the lane's tmux pane. No ingestion of `~/.codex` history.
|
||||
2. The tool belongs to the **lane**, not to the run: one lane = one tool, picked
|
||||
in the "Add lane" modal, not changeable afterwards. One tmux session per lane
|
||||
stays `ccam-lane-<id>`.
|
||||
3. Codex run controls: model, sandbox, ask-for-approval, reasoning effort.
|
||||
4. Codex runs persist in `dashboard_runs` (new `tool` column) so Active runs and
|
||||
Run history show them next to Claude runs.
|
||||
5. The rest of the Workspace UI is unchanged.
|
||||
|
||||
## Data model
|
||||
|
||||
`server/db.js`, following the existing `ALTER TABLE lanes ADD COLUMN` pattern
|
||||
(idempotent, try/catch on "duplicate column"):
|
||||
|
||||
- `lanes.tool TEXT NOT NULL DEFAULT 'claude'` — `'claude' | 'codex'`.
|
||||
- `dashboard_runs.tool TEXT NOT NULL DEFAULT 'claude'`.
|
||||
|
||||
Both default to `'claude'`, so every existing row keeps today's behavior and no
|
||||
response shape changes for existing consumers — `tool` is an added field.
|
||||
|
||||
`server/lib/lanes.js` accepts `tool` on create (`ensureLane`, worktree create),
|
||||
validates it against `{claude, codex}`, and exposes it on the lane payload.
|
||||
`updateLane` never writes `tool` — a lane's tool is immutable, same spirit as
|
||||
"adopted lanes are not destroyable".
|
||||
|
||||
## Spawn
|
||||
|
||||
`server/lib/pty-run.js` — `buildArgv({tool, ...})` branches:
|
||||
|
||||
```
|
||||
claude (unchanged):
|
||||
claude --permission-mode <mode> --allow-dangerously-skip-permissions
|
||||
[--model M] [--effort E] [--resume SID] [prompt]
|
||||
|
||||
codex:
|
||||
codex [resume <uuid> | resume --last]
|
||||
[-m MODEL] [-s SANDBOX] [-a APPROVAL]
|
||||
[-c model_reasoning_effort=EFFORT] [prompt]
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `resume` is a **subcommand** for Codex, so it comes first, before the flags.
|
||||
`codex resume <uuid> [prompt]`; with no recorded id, `codex resume --last`.
|
||||
- Allowlists (reject with `EBAD*` like today, never pass through):
|
||||
- sandbox: `read-only | workspace-write | danger-full-access`
|
||||
- approval: `on-request | never`
|
||||
- effort: `low | medium | high | xhigh` (Codex has no `--effort` flag; it is
|
||||
passed as `-c model_reasoning_effort=<value>`)
|
||||
- The `--dangerously-bypass-approvals-and-sandbox` and
|
||||
`--dangerously-bypass-hook-trust` flags are never emitted.
|
||||
- `spawnRun` takes `tool` and records it on the run row. tmux session naming,
|
||||
create-or-adopt, and the shell-prompt send path are untouched.
|
||||
|
||||
## API
|
||||
|
||||
- `POST /api/lanes/ensure`, `POST /api/lanes/worktree`: accept optional `tool`.
|
||||
- `POST /api/lanes/:id/start`: accepts `sandbox` and `approval` in addition to
|
||||
today's `model`/`effort`/`permissionMode`/`resume`. The **server reads the tool
|
||||
from the lane row**, never from the request body, and ignores the fields that
|
||||
do not belong to that tool.
|
||||
- `GET /api/run/binary?tool=codex`: probes `codex` on PATH (`which`), same
|
||||
`{found, path}` shape. Default (no query) stays `claude`.
|
||||
|
||||
## UI
|
||||
|
||||
- `AddLaneModal`: a second segmented toggle "Claude Code | Codex" using the
|
||||
existing `Seg` component, next to the Repo/Worktree toggle.
|
||||
- `LaneCard` / `LaneStripCard`: a small tool badge, shown only for `codex` lanes
|
||||
so Claude lanes look exactly as before.
|
||||
- `RunSetup`: fields switch on the selected lane's `tool`.
|
||||
- claude: model, permission mode, effort (unchanged)
|
||||
- codex: model, sandbox, approval, reasoning effort
|
||||
- Codex lanes hide the Claude session picker (the sessions table holds Claude
|
||||
sessions only) and offer a single "Resume last" action mapping to
|
||||
`codex resume --last`.
|
||||
- The Start button's disabled state uses the per-tool binary probe.
|
||||
- Console, pipeline map, split view, run history: unchanged.
|
||||
- All new strings go through i18n, no literals.
|
||||
|
||||
## Known limitations (documented, not fixed here)
|
||||
|
||||
Codex does not emit this dashboard's Claude Code hooks, so for a Codex lane:
|
||||
|
||||
- Sessions, Analytics, and cost stay empty — nothing ingests Codex usage.
|
||||
- `server/lib/stage-detect.js` never paints the amber detected badge.
|
||||
- Pipeline stages still work when the agent calls `ccam stage` itself (the repo
|
||||
already ships `AGENTS.md` and `.codex/`).
|
||||
- Lane liveness for Codex lanes comes from the tmux pane plus `dashboard_runs`.
|
||||
|
||||
## Testing
|
||||
|
||||
- `npm run test:server`: Codex argv shape (fresh, resume-by-id, resume-last,
|
||||
effort mapping), allowlist rejections, `tool` persisted on lane and run,
|
||||
start route ignoring foreign fields.
|
||||
- `npm run test:client`: `RunSetup` field set per tool, `AddLaneModal` tool
|
||||
choice reaching the API call, screen snapshots reviewed and regenerated.
|
||||
- Docs to update: `docs/LANES.md` (tool per lane, Codex limitations),
|
||||
`docs/API.md` (new fields), `README` lane section if it lists lane fields.
|
||||
Reference in New Issue
Block a user