| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
Add agent-chat: browser-surface chat UI for any coding agent (#7258) * Add agent-chat: browser-surface chat UI for any coding agent Bun server + single-file web UI rendered in a cmux browser surface. Adapters normalize claude (stream-json), codex (app-server JSON-RPC, one thread per session), pi (rpc), and ACP agents (opencode, gemini) into one event schema. Page background/palette resolve from the Ghostty config at serve time; transparent splits follow background-opacity. cmux-chat CLI opens chats as workspace tabs; a config workspaceCommand exposes New Agent Chat in the palette. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Resolve terminal theme via ghostty +show-config Hand-parsing the config picked the wrong theme (last theme line; ghostty resolves the first). Ask the bundled ghostty binary for the fully resolved config instead, with manual parsing as fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: solid html bg (no terminal bleed-through) + composer redesign - html now paints the same solid theme bg as body in opaque mode, so a terminal surface behind the webview can't composite through the transparent document root (server sets --bg-html; transparent only in transparent mode). - Replace the provider pill row with a composer card: integrated toolbar with a provider dropdown (colored dot + name), a cwd chip, an auto-approve toggle, and a send button. Chat reply box gets a matching card + send button and auto-grow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * agent-chat: rebuild frontend as React + Base UI components Replace the hand-rolled vanilla dropdown/menu with Base UI (@base-ui-components/react), the component library the cmux web app uses. Provider picker = Select, working-directory editor = Popover, auto-approve = Switch, all themed with the resolved Ghostty colors (Base UI ships unstyled). The server bundles src/main.tsx with Bun.build on startup and serves it as /app.js; the HTML shell injects theme CSS vars and loads /app.css. Streaming, markdown, tool chips, and one-page-per-session routing are preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * agent-chat: capability-driven option controls + keyboard shortcuts for every provider Adapters now declare SessionOptions (model, effort/thinking, fast mode, permission/plan/session mode, approvals, sandbox) and emit options/commands events; the React UI renders them generically with Base UI controls in both composer and chat, plus / and $ command autocomplete and a keymap-table-driven shortcut set (Shift+Tab mode cycle, Ctrl+P model cycle, Ctrl+T effort, Ctrl+F fast, Ctrl+Shift+M plan, Esc interrupt, Ctrl+/ help overlay). Per provider: claude uses correlated control requests (list_models/set_model, set_permission_mode, set_max_thinking_tokens, apply_flag_settings for effort/fastMode, interrupt instead of SIGINT, slash_commands from init); codex uses model/list with per-model efforts and service tiers, turn/start overrides, skills/list for $, and turn/steer mid-turn; pi uses id-correlated RPC (get_available_models/set_model, set_thinking_level, get_commands); ACP maps session modes + opencode configOptions/set_config_option and available_commands_update. ACP startup is single-flight per session so refresh-at-creation and the first prompt share one agent process. New test/options.e2e.ts exercises option fetch, set (asserting a confirming options event), and prompt-after-change for codex/pi/opencode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: minimal Claude Code-style status row + provider brand icons Replace the chip/switch options toolbar and status strip with one flat footer row in composer and chat: provider mark, spark+model label, icon-only fast-mode bolt, signal-bars effort/thinking, mode glyph shown only when non-default, folder+cwd, shield auto-approve, and a ··· overflow menu for the remaining selects (codex approvals/sandbox). Controls get dark Base UI tooltips with KEYMAP-derived shortcut glyphs. Provider dots become brand icons (Anthropic starburst, OpenAI knot, opencode mark, pi, Gemini sparkle) with the colored dot kept as fallback for unknown ids. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: first-class harness switching, live cached model catalogs, real provider icons Harness (provider) select is now interactive in the chat view too: picking a different harness returns to the composer with cwd and draft preserved. User-modified start options are keyed per harness (localStorage agentui.opts.<provider>) and sanitized against the provider catalog both client- and server-side, so a claude model id can never leak into a codex session. Model catalogs are derived live from the installed binaries (claude list_models probe, codex model/list, pi get_available_models, opencode ACP configOptions) behind a per-provider server cache with 10-min stale-while-revalidate and startup warming; warm claude sessions emit the full model list in their first options event, so new models appear without shipping UI changes. Effort never shows "off": adapters tag options with role (effort vs thinking-budget), claude keeps one inline effort control with thinking tokens in the overflow, pi drops "off" from choices and normalizes an off default to minimal once at session start, codex filters off-like efforts. Provider icons come from the repo's Assets.xcassets/AgentIcons (served at /icons/<provider>, path-validated, dark variant for codex): the server advertises stat-verified icon URLs in hello and the UI renders stateless background-image spans, fixing the Base UI trigger re-render bug that left img/onLoad-based icons permanently invisible. Gemini keeps the drawn sparkle; unknown providers fall back to the colored dot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: turn action row with safe forking, per-model gating, borderless chrome Replace the turn stats footer with a Claude Code-style action row on every completed turn: duration when known, copy button, and a menu with the full stats plus Fork chat where the harness supports it. Forking never touches the source conversation: claude respawns with --resume --fork-session and pins the fork to its own session id, codex uses thread/fork, and pi spawns the fork's own process with --fork <source session file> then pins respawns to the fork's file (sending pi's fork RPC to the source process would have rewound it). fork.e2e proves both sides: the source keeps its context and the fork answers from shared history. Claude effort choices and the fast-mode toggle now follow the selected model's list_models metadata (Fable exposes no fast mode), with corrective apply_flag_settings when a model switch clamps a value. No hardcoded model lists remain. Chrome polish from dogfood: no bold anywhere, composer heading removed, borders dropped from cards, bubbles, popovers, and the overflow group divider, and scrollbar-gutter: stable prevents scrollbar layout shift. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: grayscale font smoothing to match terminal rendering Default subpixel smoothing makes light-on-dark text look heavy and fuzzy next to Ghostty's grayscale-antialiased glyphs; -webkit-font-smoothing: antialiased + -moz-osx-font-smoothing: grayscale on body aligns the two. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: unified searchable picker, one approvals surface, @ files, installed-harness detection One approvals surface per harness replaces the redundant global auto-approve shield: claude uses its permission-mode select (acceptEdits default), codex its approvals+sandbox selects with reverse-approval answers keyed off the live policy, ACP keeps an adapter-declared toggle (its real mechanism), pi shows nothing; REST/CLI autoApprove still maps to those defaults. The provider and model selects merge into one t3-style searchable picker (cmdk inside our popover, minimal styling): groups per installed harness, items from the live catalogs, selection sets harness and model together, uninstalled harnesses listed with copyable install commands, and providers with cold catalogs stay reachable via a default item. Installed detection stats each binary; Bun.which gets the prepended PATH explicitly since it ignores runtime env mutations under launchd. Claude context window follows t3's mechanism: base+[1m] catalog pairs collapse into one model with a 200k/1M select resolving the [1m] suffix at set_model, fully derived. Composer/chat gain @ file references (git ls-files or bounded walk, cached), Ctrl+N/P menu navigation, configurable Ctrl+J (agentChat.keys.ctrlJ in cmux.json), proportional effort bars, and type-to-focus on every screen. Session cwd is validated everywhere, fixing the misleading posix_spawn ENOENT when a persisted working directory disappeared. Start rejections alone surface in the composer banner; catalog probe failures stay quiet, and command catalogs are TTL-cached per provider+cwd so page loads no longer spawn probe processes for inactive harnesses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: t3-layout picker, curated version-gated catalogs, typecheck gate, menu keyboard nav The harness/model picker copies t3code's layout: fixed popover with a provider rail (installed harnesses, dimmed not-installed entries with copyable install commands, hidden while searching), an integrated borderless search that autofocuses on open, flat model rows with scroll fades, and full dialog/tablist accessibility. Ctrl+N/P and Ctrl+J/K navigate every cmdk surface through one shared keymap; Ctrl+J still inserts a newline when no popup is open and Ctrl+K is reserved. Claude models use t3's curated list (Fable 5 through Haiku 4.5, clean names, sonnet-5 default, no Default pseudo-entry) gated by the installed CLI version, failing open when the version is unknown and showing too-old models disabled with upgrade messages; binary-reported extras union in after alias normalization and dedupe. The launchd bug that hid gated models is fixed by passing env explicitly to every spawn (Bun.spawn, like Bun.which, ignores runtime PATH mutations). Context 200k/1M resolves the [1m] suffix at spawn/set_model. Gemini gets a curated def-level model list applied via --model, with mid-session changes restarting the ACP process; a stub-ACP e2e covers preseed and restart paths after fixing an out-of-scope def reference and a spawn/report model mismatch. bun run check now typechecks the whole app including tsx (this gate would have caught the def bug bun build bundled silently), model labels normalize casing via one slug-shape prettifier (GPT-5.4 Mini, not gpt-5.4-mini), popover dead space from stable scrollbar gutters is gone (overlay scrollbars appear only while scrolling), and the codex overflow menu is restyled to standard rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: Base UI tooltips with delay grouping and animated entry All chrome hints go through one HintTooltip primitive under a single app-root Tooltip.Provider, so the first hover waits ~500ms but moving between controls while the group is warm shows the next tooltip instantly, with no hand-rolled timers. Entry animates via Base UI's data-starting-style/data-ending-style states: fade plus a side-aware 3px slide with transform-origin from the anchor, 120ms in and 80ms out. Disabled model rows get a wrapper span so their upgrade-reason tooltip still fires despite the disabled button swallowing hover. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: bind loopback and require trusted Origin/Host on the sidecar Bun.serve defaulted to 0.0.0.0, exposing the agent control plane on all interfaces, and even on loopback any web page could drive the WS API (browser CSRF) or POST /api/sessions. Bind 127.0.0.1 explicitly, reject requests whose Host is not loopback:port (DNS rebinding), and reject WS upgrades and session-creating POSTs whose Origin is present but not the sidecar's own origin. Requests without an Origin header (CLI/curl/Bun clients) stay trusted. Also in server.ts: emit a done event when adapter.send rejects so the UI gets its turn boundary, cache the frontend bundle by default (CMUX_AGENT_UI_DEV=1 opts into per-request rebuilds), and cap retained session transcripts at 5000 events with a truncation marker. * agent-chat: answer codex approvals per protocol generation The adapter replied to every server->client request with {decision: approved|denied}. Verified against 'codex app-server generate-json-schema': that shape is only correct for v1 execCommandApproval/applyPatchApproval; the v2 item/commandExecution|fileChange/requestApproval methods expect accept/decline, and other requests (permission profiles, tool user input) expect entirely different payloads. v1 params also carry conversationId rather than threadId, so the session lookup never matched and v1 approvals were always denied even with auto-approve on. Dispatch per method, look sessions up by threadId or conversationId, and decline unsupported request methods with a JSON-RPC error instead of a malformed result. Also: single-flight thread/start so concurrent first sends cannot spawn two threads for one chat, and a 30s initialize timeout that kills the app-server process (rejecting all pending requests) instead of blocking every codex session forever on a hung startup. * agent-chat: never approve ACP permissions via fallback; serialize prompts; bound startup session/request_permission fell back to options[0] when no reject-kind option existed, which silently approves tools that only offer allow options even when auto-approve is off; answer with the spec's cancelled outcome instead. Serialize session/prompt per session since ACP has no steer and most agents reject overlapping turns. Give startup (initialize + session/new) a 30s timeout that kills the process so a hung agent cannot leave the session stuck in running with nothing to cancel. * agent-chat: close claude turns on process exit and reapply options after respawn A claude process dying mid-turn emitted an error but no done event, leaving the chat stuck mid-stream without a turn footer; emit done on both exit paths. When ensureProc spawns a replacement process, initialApplied stayed true so runtime option changes applied via control messages (thinking, effort, fast mode) were silently lost; reset the flag on respawn and reapply values that drifted from spawn defaults. Also flush the NDJSON TextDecoder at stream end so a trailing multi-byte sequence is not dropped. * agent-chat: drop frames that declare a non-2.0 jsonrpc version Per review-corpus guidance on exact JSON-RPC version matching: ignore stdio frames that explicitly declare a different jsonrpc version instead of routing them through response/request handling. Frames without the field still pass (some agents omit it). * agent-chat: honor ctrlJ mode in composer menus; bound cwd catalogs The Ctrl+J menu-next binding ignored the agentChat.keys.ctrlJ config: with the default "newline" mode, pressing Ctrl+J while a /, $, or @ composer popup was open moved the menu selection instead of inserting a newline. Tag the binding with the existing (previously unused) ctrlJMode field and filter it in menuActionForKey for composer menus; standalone overlay pickers keep the binding since they have no newline semantics. Also cap the cwd-keyed command/file catalogs at 64 settled entries so a long-lived sidecar chatting across many worktrees cannot grow them unboundedly. * agent-chat: bound codex JSON-RPC requests at 30s The shared app-server request helper had no per-request timeout, so a dropped response (server alive but silent) left callers awaiting forever and turns stuck in running. No codex RPC is long-lived by protocol (turn completion arrives as a notification), and the claude and pi adapters already bound their requests the same way. Timeout clears the pending entry and rejects with the method name; send()'s catch path then emits error/done and returns the session to idle. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 3 个月前 |