docs: sync CLAUDE.md and READMEs with codex lanes and the tmux run surface
Add the codex-lane facts and doc-sync marker to CLAUDE.md, document the per-lane tool picker in README/README.vi, and bring server/README.md and client/README.md's API and routing tables up to date with the tmux-backed run endpoints and the /workspace route.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# Claude Code Working Guide
|
||||
|
||||
_Last synced: d884d19 (2026-08-26)._ A change that makes any statement in this file, `AGENTS.md`, or a README wrong — a moved path, a new or changed extension point, a retired rule — updates that file in the same change-set and bumps this marker.
|
||||
|
||||
## Project mission
|
||||
- Maintain a reliable local-first dashboard for Claude Code session monitoring.
|
||||
- Preserve real-time behavior (hooks -> API -> SQLite -> WebSocket -> UI).
|
||||
@@ -33,6 +35,7 @@ A **lane** is a durable unit of parallel agent work — one working directory, m
|
||||
- Liveness: a silent **watcher** (stage matching `/watch|poll/`) is dead after `LANE_DEAD_SEC` seconds (default 300); a silent **idle** lane is at rest, not dead.
|
||||
- **Never `rm -rf` a lane.** Destructive lane operations (`reset`, `remove`, `purge`) go through `server/lib/worktree.js`/`server/lib/lanes.js`, never a raw filesystem delete — that's what keeps the three-check destroy guard and the DB bookkeeping in lockstep.
|
||||
- **Never build a git command as a shell string.** Lane git operations use `execFile("git", [...args])` with an explicit argument array (see `server/lib/worktree.js`'s `git()` helper), never a concatenated/interpolated string passed to a shell.
|
||||
- **A lane's `tool` is a lane fact, immutable, and never read from a request.** `lanes.tool` (`claude` | `codex`, default `claude`) is set by `createLane` only — it is deliberately absent from `PATCHABLE`, so no PATCH can flip the CLI under a live pane. Every spawn path (`POST /api/lanes/:id/start`, `POST /api/run`) takes the tool from the lane row and ignores whatever the body says; the run panel's Codex-only fields (`sandbox`, `approval`, `resumeLast`) are validated against the fixed allowlists in `server/lib/pty-run.js` before they reach an argv, and the bypass flags (`--dangerously-bypass-approvals-and-sandbox`, `--dangerously-bypass-hook-trust`) are never emitted. `GET /api/run/binary?tool=` resolves the probed binary name from that same allowlist — the query string reaches `which`.
|
||||
- **Adopted lanes are not destroyable.** A lane with `kind === "adopted"` may never have its worktree reset or removed; "removing" one only drops the dashboard's own record of it. This is enforced in code (`assertDestroyable`'s first check) — do not add a path that bypasses it.
|
||||
|
||||
See `docs/LANES.md` for full guide: stage reporting, custom templates, lane actions, and the five node states.
|
||||
|
||||
@@ -17,10 +17,11 @@ Internal build — all rights reserved.
|
||||
- **Stage detection.** The stage is inferred from the tool stream, so a session
|
||||
that never calls `ccam stage` still shows progress — rendered dashed amber and
|
||||
never as done, because an inference is not evidence.
|
||||
- **Run Claude from the browser.** A real terminal (tmux + a real PTY,
|
||||
rendered with xterm.js) attached to a lane's directory — the exact TUI you'd
|
||||
see locally, fully interactive, resumable, and attachable from a real
|
||||
terminal too via `ccam lanes shell`.
|
||||
- **Run Claude Code or Codex from the browser.** A real terminal (tmux + a real
|
||||
PTY, rendered with xterm.js) attached to a lane's directory — the exact TUI
|
||||
you'd see locally, fully interactive, resumable, and attachable from a real
|
||||
terminal too via `ccam lanes shell`. Which CLI it starts is the lane's own
|
||||
setting.
|
||||
- **Analytics (in-dashboard tab), alerts, and a workflow view**, plus an MCP server and a CLI.
|
||||
|
||||
## Requirements
|
||||
@@ -101,6 +102,13 @@ ccam lanes add --cwd /path/to/repo --title "My feature" # adopt
|
||||
ccam lanes add --repo /path/to/repo --slug my-feature # managed worktree
|
||||
```
|
||||
|
||||
Every lane also carries the CLI that drives it — `claude` (the default) or
|
||||
`codex` — picked in the Add-lane modal and fixed for the life of the lane. A
|
||||
Codex lane's run panel offers Codex's own sandbox, approval and reasoning-effort
|
||||
choices, and resumes with `codex resume --last`. Codex emits none of this
|
||||
dashboard's hooks, so a Codex lane records its runs but stays absent from
|
||||
Sessions, Analytics and stage detection.
|
||||
|
||||
The declared stage comes from `ccam stage`. The inferred stage comes from tool
|
||||
events and expires after `DETECTION_TTL_MS` (default 5 minutes), so a lane can
|
||||
move backwards between work sessions. Detection never writes the declared stage,
|
||||
|
||||
+13
-3
@@ -18,9 +18,12 @@ Internal build — all rights reserved.
|
||||
session không bao giờ gọi `ccam stage` vẫn hiển thị progress — render dạng
|
||||
viền chấm màu hổ phách (amber) và không bao giờ hiện `done`, vì suy luận
|
||||
không phải bằng chứng.
|
||||
- **Chạy Claude từ trình duyệt.** Spawn session trong thư mục của lane, stream
|
||||
output, gửi follow-up, resume session cũ bất kỳ.
|
||||
- **Analytics, alert, Kanban và workflow view**, cộng thêm MCP server và CLI.
|
||||
- **Chạy Claude Code hoặc Codex từ trình duyệt.** Một terminal thật (tmux + PTY
|
||||
thật, render bằng xterm.js) gắn vào thư mục của lane — đúng cái TUI bạn thấy ở
|
||||
máy, tương tác đầy đủ, resume được, và attach được từ terminal thật qua
|
||||
`ccam lanes shell`. Chạy CLI nào là thiết lập riêng của từng lane.
|
||||
- **Analytics (tab trong dashboard), alert và workflow view**, cộng thêm MCP
|
||||
server và CLI.
|
||||
|
||||
## Yêu cầu
|
||||
|
||||
@@ -99,6 +102,13 @@ ccam lanes add --cwd /path/to/repo --title "My feature" # adopt
|
||||
ccam lanes add --repo /path/to/repo --slug my-feature # managed worktree
|
||||
```
|
||||
|
||||
Mỗi lane còn mang theo CLI điều khiển nó — `claude` (mặc định) hoặc `codex` —
|
||||
chọn trong modal Add lane và cố định suốt vòng đời của lane. Lane Codex có panel
|
||||
chạy với các lựa chọn riêng của Codex (sandbox, approval, reasoning effort) và
|
||||
resume bằng `codex resume --last`. Codex không bắn hook của dashboard này, nên
|
||||
lane Codex vẫn ghi lịch sử chạy nhưng không xuất hiện ở Sessions, Analytics và
|
||||
stage detection.
|
||||
|
||||
Stage khai báo (declared) đến từ `ccam stage`. Stage suy luận (inferred) đến
|
||||
từ tool event và hết hạn sau `DETECTION_TTL_MS` (mặc định 5 phút), nên lane có
|
||||
thể lùi lại giữa các work session. Detection không bao giờ ghi đè stage khai
|
||||
|
||||
+8
-3
@@ -449,11 +449,13 @@ graph TB
|
||||
Feed["/activity (redirect)"]
|
||||
Workflows["/workflows"]
|
||||
CcConfig["/cc-config (redirect)"]
|
||||
Run["/run"]
|
||||
Lanes["/lanes (redirect)"]
|
||||
Run["/run (redirect)"]
|
||||
Workspace["/workspace"]
|
||||
Settings["/settings (tabs: dashboard | claude-code | ...)"]
|
||||
NF["/* (NotFound)"]
|
||||
|
||||
Root --> Dashboard & Sessions & Detail & Feed & Workflows & CcConfig & Run & Settings & NF
|
||||
Root --> Dashboard & Sessions & Detail & Feed & Workflows & CcConfig & Lanes & Run & Workspace & Settings & NF
|
||||
|
||||
style Dashboard fill:#3B82F6
|
||||
style Detail fill:#3B82F6
|
||||
@@ -479,7 +481,10 @@ function App() {
|
||||
<Route path="workflows" element={<Workflows />} />
|
||||
{/* legacy path - Claude config now lives in a Settings tab */}
|
||||
<Route path="cc-config" element={<Navigate to="/settings?tool=claude-code" replace />} />
|
||||
<Route path="run" element={<Run />} />
|
||||
{/* legacy paths - lanes and runs are one page now */}
|
||||
<Route path="lanes" element={<Navigate to="/workspace" replace />} />
|
||||
<Route path="run" element={<Navigate to="/workspace" replace />} />
|
||||
<Route path="workspace" element={<Workspace />} />
|
||||
<Route path="settings" element={<SettingsHub />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Route>
|
||||
|
||||
+13
-10
@@ -651,22 +651,25 @@ Reads — and carefully gated mutations for low-risk text-file artifacts — for
|
||||
| `PUT` | `/api/cc-config/file` | Create or overwrite a text-file artifact (skills/agents/commands/output-styles/memory). Body: `{ scope, type, name?, content }`. Auto-backs-up if file exists. Atomic temp + rename. 256 KB cap. Per-project file-based memory is also editable via `{ scope: "auto-memory", type: "auto-memory", project, name }` — backups land under `<memory-dir>/.cc-config-backups/auto-memory/`, and an invalid project slug returns `EBADPROJECT` |
|
||||
| `DELETE` | `/api/cc-config/file` | Backup-then-delete a text-file artifact. Skill dirs are backed up whole before recursive removal |
|
||||
|
||||
### Run Claude (`/api/run`)
|
||||
### Run a lane's CLI (`/api/run`)
|
||||
|
||||
HTTP surface for spawning and supervising `claude` subprocesses from the dashboard. Every route enforces a same-origin / loopback-Origin guard against browser CSRF.
|
||||
HTTP surface for the tmux-backed terminal run: one session per lane, named `ccam-lane-<laneId>`, running the CLI that lane declares (`claude` or `codex`). There is no in-memory handle map — every read is computed from `tmux list-sessions`, so a session killed out-of-band self-corrects on the next call. Every route enforces a same-origin / loopback-Origin guard against browser CSRF.
|
||||
|
||||
| Method | Path | Description |
|
||||
| -------- | ----------------------------- | ----------- |
|
||||
| `GET` | `/api/run` | List handles + `maxConcurrent` + `activeCount` |
|
||||
| `GET` | `/api/run/binary` | Probe whether `claude` is on `PATH` |
|
||||
| `GET` | `/api/run` | `{ items }` — every live run handle, recomputed from tmux |
|
||||
| `GET` | `/api/run/history?limit=&laneId=` | Persisted run history from `dashboard_runs` (default 50, max 500, newest first); each row carries `isLive` and the `tool` it was spawned with. `laneId` narrows to one lane |
|
||||
| `GET` | `/api/run/binary[?tool=]` | Probe whether the CLI is on `PATH`. `?tool=codex` probes Codex; omitted or `?tool=claude` probes Claude; anything else is `400 EBADTOOL`. Returns `{ found, path }` |
|
||||
| `GET` | `/api/run/tmux` | `{ available }` — whether the `tmux` binary exists |
|
||||
| `GET` | `/api/run/cwds` | Suggested cwds (dashboard, home, recent from sessions) |
|
||||
| `GET` | `/api/run/files?cwd=…&q=…` | Fuzzy file search inside `cwd` for the Run page's `@`-file autocomplete. Skips `node_modules`, `.git`, `dist`, `build`, `.next`, `.cache`, `coverage`, `vendor`, etc. Cwd is required and must exist; results are capped and ranked by basename match |
|
||||
| `POST` | `/api/run` | Spawn. Body: `{ prompt, mode, cwd?, model?, permissionMode?, resumeSessionId?, effort? }`. `effort` (`low`/`medium`/`high`) maps to `--effort`. When `resumeSessionId` is set in conversation mode, `prompt` may be empty — the spawner skips the initial stdin write and `claude --resume` idles until the client POSTs a follow-up to `/api/run/:id/message`. Spawner always passes `--output-format stream-json --verbose --include-partial-messages` for character-by-character streaming. Concurrency is effectively uncapped by default (ceiling 10000, override with `RUN_MAX_CONCURRENT`) — the terminal TUI has no cap and neither does the dashboard; the ceiling is sanity-only to prevent fork-bomb footguns |
|
||||
| `POST` | `/api/run/:id/message` | Send follow-up turn (conversation mode only). Body: `{ text }` |
|
||||
| `GET` | `/api/run/:id` | Handle state. `?envelopes=1` includes the in-memory envelope log for re-attach |
|
||||
| `DELETE` | `/api/run/:id` | Stop (SIGTERM → SIGKILL after 5 s) |
|
||||
| `GET` | `/api/run/files?cwd=…&q=…` | Fuzzy file search inside `cwd` for the run panel's `@`-file autocomplete. Skips `node_modules`, `.git`, `dist`, `build`, `.next`, `.cache`, `coverage`, `vendor`, etc. Cwd is required and must exist; results are capped and ranked by basename match |
|
||||
| `POST` | `/api/run` | Create-or-adopt the lane's tmux session. Body: `{ laneId, cwd, initialPrompt?, model?, permissionMode?, effort?, resumeSessionId? }`. The CLI comes from the lane row, never from the body. An existing session whose pane sits at a shell prompt gets the argv typed into it; a pane already running a program is adopted unchanged |
|
||||
| `GET` | `/api/run/:id` | One run handle, or 404 once its tmux session is gone |
|
||||
| `DELETE` | `/api/run/:id` | Kill the tmux session |
|
||||
|
||||
WebSocket message types added: `run_stream` (parsed stream-json envelope, including `stream_event` deltas from `--include-partial-messages`), `run_status` (status transitions), `run_input_ack` (stdin write confirmed), and `cc_config_changed` (broadcast by `lib/cc-watcher.js` on `fs.watch` events under `~/.claude/` and by `routes/cc-config.js` after every successful PUT/DELETE — debounced at 500 ms, payload `{ source: "dashboard"|"fs", action?, scope?, type?, name?, paths? }`).
|
||||
Codex-only spawn options (`sandbox`, `approval`, `resumeLast`) are accepted by the lane action `POST /api/lanes/:id/start`, not by `POST /api/run` — see [`docs/API.md`](../docs/API.md).
|
||||
|
||||
The pane itself is not JSON: its bytes stream over `/ws-pty/:runId` as binary WebSocket frames. The JSON WebSocket surface adds `cc_config_changed` (broadcast by `lib/cc-watcher.js` on `fs.watch` events under `~/.claude/` and by `routes/cc-config.js` after every successful PUT/DELETE — debounced at 500 ms, payload `{ source: "dashboard"|"fs", action?, scope?, type?, name?, paths? }`).
|
||||
|
||||
### Import History
|
||||
|
||||
|
||||
Reference in New Issue
Block a user