feat(run,ui): make the lane console usable — terminal focus, live-exit recovery, auto permission mode, model aliases

Terminal (issue #33):
- `convertEol` is gone from xterm. It rewrote every bare LF as CR+LF, so a
  tmux pane that scrolled (tmux emits a bare LF and keeps drawing at the
  column it was on) had the rest of that draw land at column 0, stamping a
  line's first characters into the left margin of the rows below — the torn
  left edge that only showed up once content scrolled.
- The attach carries the client's real size (`?cols=&rows=`, `sizeFromQuery`
  in `server/websocket.js`, bounded because it reaches a TIOCSWINSZ ioctl).
  `attach()` spawned `tmux attach-session` at a hardcoded 80x24 on every
  attach, and tmux sizes the window to the newest client, so `claude` redrew
  its whole UI at 80 columns until the first `resize` message landed.
- The terminal takes the keyboard on mount, so typing reaches tmux without
  clicking the pane first.
- A pane that ends (double Ctrl+C, `exit`, a crash) reports itself through a
  new `onExit` prop and `LaneConsolePane` runs `newRun()`, so the console
  returns to the setup form instead of leaving a dead pane behind a
  "New run" button.

Pane environment:
- `server/lib/desktop-env.js` forwards the graphical session's DISPLAY /
  WAYLAND_DISPLAY / XAUTHORITY / XDG_RUNTIME_DIR (from
  `systemctl --user show-environment`, this process's own values winning)
  into the pane. The server normally runs as a systemd user service with none
  of them, so `claude` there could not read the clipboard and answered "no
  image found in clipboard" on every image paste.

Run setup:
- `auto` is a permission mode and is now the default everywhere — allowlists,
  the `--permission-mode` fallback, the select, and the pane's initial state.
- The model picker sends the CLI's aliases (`fable`/`opus`/`sonnet`/`haiku`)
  instead of pinned ids. `claude --model` resolves an alias to the latest
  model itself, so a release like Fable 5.1 needs no edit here; pricing is
  unaffected because it keys off the resolved id the transcript reports.
- **Create/update readme file** moved from the Add-lane modal into the lane's
  Tools menu: creating a lane and writing its docs are separate decisions,
  and the second is worth re-running long after the first.
- The Workspace's no-lane state is a real banner; the lane strip (add tile
  included) renders only once a lane exists.

Refs #32, #33.

Tests: npm run test:server (1828 pass), client vitest (558 pass).
This commit is contained in:
2026-09-21 17:03:27 +07:00
parent 64017c6ca5
commit f77049c238
30 changed files with 579 additions and 204 deletions
+12 -5
View File
@@ -3390,7 +3390,7 @@ export interface CodexMarketplaceActionArgs {
// ─────────────────────────────────────────────────────────────────────────────
/** Maps 1:1 to the `claude --permission-mode` CLI flag. */
export type PermissionMode = "acceptEdits" | "default" | "plan" | "bypassPermissions";
export type PermissionMode = "auto" | "acceptEdits" | "default" | "plan" | "bypassPermissions";
/** Maps 1:1 to the `claude --effort` CLI flag; "" omits the flag (model default). */
export type EffortLevel = "" | "low" | "medium" | "high" | "xhigh" | "max";
/** "running" — a live tmux session exists; "gone" — it doesn't (killed,
@@ -3515,10 +3515,17 @@ export const RUN_EFFORT_CHOICES: EffortChoice[] = [
// configured default and the rest map to concrete `--model` values.
export const RUN_MODEL_CHOICES: ModelChoice[] = [
{ id: "", label: "Inherit from settings", hint: "Use whatever your settings.json model is" },
{ id: "claude-opus-5", label: "Opus 5", hint: "Highest capability" },
{ id: "claude-sonnet-5", label: "Sonnet 5", hint: "Balanced capability and speed" },
{ id: "claude-fable-5", label: "Fable 5", hint: "Fast output, same capability tier as Opus" },
{ id: "claude-haiku-4-5-20251001", label: "Haiku 4.5", hint: "Fastest, lightest" },
// Aliases, never pinned ids: `claude --model` documents `fable`/`opus`/
// `sonnet`/`haiku` as "an alias for the latest model", and the CLI resolves
// each one itself (2.1.278 maps fable -> claude-fable-5-1). A pinned id here
// is a list that silently goes stale every release and has to be edited by
// hand; an alias follows whatever `claude` was last updated to. Cost math is
// unaffected — pricing keys off the model the transcript reports, which is
// the resolved full id.
{ id: "fable", label: "Fable (latest)", hint: "Fast output, same capability tier as Opus" },
{ id: "opus", label: "Opus (latest)", hint: "Highest capability" },
{ id: "sonnet", label: "Sonnet (latest)", hint: "Balanced capability and speed" },
{ id: "haiku", label: "Haiku (latest)", hint: "Fastest, lightest" },
];
// Curated model list for Codex lanes - same shape as RUN_MODEL_CHOICES, sent