| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
Fix Claude bridge branch and resume failures (#7882) (#7900) * test: cover inherited Claude bridge sessions * Fix Claude bridge session identity leakage * Fix OMC Claude session identity inheritance * Harden independent Claude launch boundaries * Generate Claude launch environment policy * Preserve Claude Teams respawn trust * Run Claude launch policy test in CI * Preserve Claude auto-naming trust context | 2 个月前 | |
agent-chat: virtualization scroll anchoring, measured-height disclosure, picker polish (#7650) * Scroll anchoring for virtualized transcript; picker row gap; smoother disclosure Re-measurements above the viewport compensate scrollTop in the same frame (adaptive row estimates shrink initial error); rows suppress re-measure while their disclosure animates and measure once at the end; activity rows get a capped-stagger entry animation; model picker rows gain a 1px seam. Adds resize-anim-lab.html: ten instrumented height animation approaches for side-by-side feel testing. * Remove per-row entry fade/translate from disclosure open The height reveal alone carries the animation; the staggered child fade felt wrong in dogfood. The lab's entry toggle now defaults off. * Disclosure animates measured height via WAAPI Replaces the grid-template-rows track animation after side-by-side testing in the resize-anim lab (user picked measured height). Open measures scrollHeight and animates 0->H committing height:auto on finish; close animates back and unmounts after; a mid-flight toggle cancels and continues from the current computed height. Remounts of already-open rows stay instant and reduced motion stays instant. * Live ghostty re-theme, no composer shadow, menu opt-out flip, disclosure races The sidecar watches the ghostty config and resolved theme sources, re-resolves through +show-config, and broadcasts a theme event that clients apply as CSS variables live (shiki colors are variable-driven so code re-themes too). The composer card loses its shadow. The plus menu appends the built-in Agent chat item even with a custom contextMenu (explicit newWorkspaceMenu:false or explicit inclusion are the only overrides). Disclosure finish remeasures through an unguarded event path so cached row heights can't go stale, and interrupt cleanup pins the mid-flight height before cancel so reopen continues instead of snapping (fixes the 110px->149px jump seen in CDP). * Anchor scroll compensation on the first visible index at measure time virtualFirstVisibleIndex derives the anchor from current scrollTop and offsets, so compensation covers exactly the rows above the viewport top: overscan rows above compensate, visible rows do not, and scroll-up mounts keep compensating against estimates. * Anchor strictly past exact row-bottom scroll positions When scrollTop equals a row's cumulative bottom edge that row is fully above the viewport; anchoring on it exempted its own resizes from compensation. * cmux pushes its resolved theme to the agent-chat sidecar AgentChatThemeSync builds the theme payload from cmux's own resolution (GhosttyApp runtime colors + full ANSI palette) and POSTs it to the sidecar's /api/theme when the action runs and on config-reload and background-change notifications, debounced and gated on recent health. The sidecar treats the pushed theme as authoritative (last writer wins), broadcasts it live, and its file fallback now also applies the managed override from cmux's app-support config with appearance-based light/dark selection — closing the +show-config divergence. The theme watcher poll now only stats mtimes and re-arms watchers only when the source list changes; recurring resolves use async spawn. * AgentChatThemeSync: import CmuxFoundation for GhosttyBackgroundBlur * Theme validator treats omitted nullable keys as null Swift's synthesized Codable drops nil optionals from the JSON; the exact-key check was 400ing every push from configs with an empty font-family. Unexpected keys are still rejected. * Allow opacity 0 in theme payloads (legal ghostty background-opacity) * Theme sync survives restarts and pushes unconditionally Observers install at app startup (not only on the create action), each notification resolves the current agentChat.url from the config store and POSTs with the short timeout (no health-freshness gate — a refused loopback connection is cheap), nullable payload fields encode explicit JSON nulls, and non-2xx responses are logged. * Theme sync cleanups: clock debounce, os Logger, nonisolated payload; opacity-0 and sparse-payload server test * Root-anchor the theme endpoint like healthURL A path in agentChat.url passed health checks but 404'd every theme POST because themeURL preserved the path prefix while the sidecar serves /api/theme at the origin root. * Push the theme once at launch Observers alone never fire after a relaunch with an unchanged config, so a running sidecar kept its file-derived theme until the next change; applicationDidFinishLaunching schedules the debounced push (init-time start() is too early for resolved config state). * Move the launch-time theme push into AgentChatThemeSync The 4-line AppDelegate addition tripped the frozen file's hard-cap budget; the sync now observes NSApplication.didFinishLaunching itself (or schedules immediately when already running), which also puts the behavior in the file that owns it. * Hue-aware accent selection; tooltips stack above popovers Accent candidates (ANSI 4,12,6,14,5,13,2,10) are filtered to blue/cyan/ violet hues so themes like Monokai that remap blue to orange fall through to their cyan; a neutral foreground mix is the last resort and an explicit accent in the cmux payload overrides. Tooltip positioners get a dedicated layer above menus/popovers. * Document agent-chat-accent and live theme updates in the README * Drop self-removal from the one-shot launch observer didFinishLaunching posts once per process; the mutable capture tripped the sendable-closure warning budget. * cmux theme pushes inherit the file-configured accent override The Swift payload has no accent field, so every push stored accent:null and silently disabled the documented agent-chat-accent override whenever cmux is running (always, since the launch push). | 2 个月前 | |
agent-chat: virtualization scroll anchoring, measured-height disclosure, picker polish (#7650) * Scroll anchoring for virtualized transcript; picker row gap; smoother disclosure Re-measurements above the viewport compensate scrollTop in the same frame (adaptive row estimates shrink initial error); rows suppress re-measure while their disclosure animates and measure once at the end; activity rows get a capped-stagger entry animation; model picker rows gain a 1px seam. Adds resize-anim-lab.html: ten instrumented height animation approaches for side-by-side feel testing. * Remove per-row entry fade/translate from disclosure open The height reveal alone carries the animation; the staggered child fade felt wrong in dogfood. The lab's entry toggle now defaults off. * Disclosure animates measured height via WAAPI Replaces the grid-template-rows track animation after side-by-side testing in the resize-anim lab (user picked measured height). Open measures scrollHeight and animates 0->H committing height:auto on finish; close animates back and unmounts after; a mid-flight toggle cancels and continues from the current computed height. Remounts of already-open rows stay instant and reduced motion stays instant. * Live ghostty re-theme, no composer shadow, menu opt-out flip, disclosure races The sidecar watches the ghostty config and resolved theme sources, re-resolves through +show-config, and broadcasts a theme event that clients apply as CSS variables live (shiki colors are variable-driven so code re-themes too). The composer card loses its shadow. The plus menu appends the built-in Agent chat item even with a custom contextMenu (explicit newWorkspaceMenu:false or explicit inclusion are the only overrides). Disclosure finish remeasures through an unguarded event path so cached row heights can't go stale, and interrupt cleanup pins the mid-flight height before cancel so reopen continues instead of snapping (fixes the 110px->149px jump seen in CDP). * Anchor scroll compensation on the first visible index at measure time virtualFirstVisibleIndex derives the anchor from current scrollTop and offsets, so compensation covers exactly the rows above the viewport top: overscan rows above compensate, visible rows do not, and scroll-up mounts keep compensating against estimates. * Anchor strictly past exact row-bottom scroll positions When scrollTop equals a row's cumulative bottom edge that row is fully above the viewport; anchoring on it exempted its own resizes from compensation. * cmux pushes its resolved theme to the agent-chat sidecar AgentChatThemeSync builds the theme payload from cmux's own resolution (GhosttyApp runtime colors + full ANSI palette) and POSTs it to the sidecar's /api/theme when the action runs and on config-reload and background-change notifications, debounced and gated on recent health. The sidecar treats the pushed theme as authoritative (last writer wins), broadcasts it live, and its file fallback now also applies the managed override from cmux's app-support config with appearance-based light/dark selection — closing the +show-config divergence. The theme watcher poll now only stats mtimes and re-arms watchers only when the source list changes; recurring resolves use async spawn. * AgentChatThemeSync: import CmuxFoundation for GhosttyBackgroundBlur * Theme validator treats omitted nullable keys as null Swift's synthesized Codable drops nil optionals from the JSON; the exact-key check was 400ing every push from configs with an empty font-family. Unexpected keys are still rejected. * Allow opacity 0 in theme payloads (legal ghostty background-opacity) * Theme sync survives restarts and pushes unconditionally Observers install at app startup (not only on the create action), each notification resolves the current agentChat.url from the config store and POSTs with the short timeout (no health-freshness gate — a refused loopback connection is cheap), nullable payload fields encode explicit JSON nulls, and non-2xx responses are logged. * Theme sync cleanups: clock debounce, os Logger, nonisolated payload; opacity-0 and sparse-payload server test * Root-anchor the theme endpoint like healthURL A path in agentChat.url passed health checks but 404'd every theme POST because themeURL preserved the path prefix while the sidecar serves /api/theme at the origin root. * Push the theme once at launch Observers alone never fire after a relaunch with an unchanged config, so a running sidecar kept its file-derived theme until the next change; applicationDidFinishLaunching schedules the debounced push (init-time start() is too early for resolved config state). * Move the launch-time theme push into AgentChatThemeSync The 4-line AppDelegate addition tripped the frozen file's hard-cap budget; the sync now observes NSApplication.didFinishLaunching itself (or schedules immediately when already running), which also puts the behavior in the file that owns it. * Hue-aware accent selection; tooltips stack above popovers Accent candidates (ANSI 4,12,6,14,5,13,2,10) are filtered to blue/cyan/ violet hues so themes like Monokai that remap blue to orange fall through to their cyan; a neutral foreground mix is the last resort and an explicit accent in the cmux payload overrides. Tooltip positioners get a dedicated layer above menus/popovers. * Document agent-chat-accent and live theme updates in the README * Drop self-removal from the one-shot launch observer didFinishLaunching posts once per process; the mutable capture tripped the sendable-closure warning budget. * cmux theme pushes inherit the file-configured accent override The Swift payload has no accent field, so every push stored accent:null and silently disabled the documented agent-chat-accent override whenever cmux is running (always, since the launch push). | 2 个月前 | |
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> | 2 个月前 | |
Over-the-air model catalog for agent-chat (cmux-owned models.dev) (#7751) * Over-the-air model catalog: cmux.dev endpoint + layered sidecar consumption GET /api/agent-models serves a versioned, ETag'd, CDN-cached catalog from checked-in data (deploying the site is the update mechanism); the sidecar fetches it with revalidation, a 1h TTL, disk-persisted last-good payload for offline, strict schema validation, and a WS model-catalog event so open pickers refresh live. Merge precedence per provider: remote catalog, then binary-reported models, then the built-in curated lists only when nothing was ever fetched. Selected ids pass through verbatim to the CLIs so day-one models work without any update; minVersion gates keep unsupported entries disabled with the upgrade hint. * Address catalog review findings; scope files-changed git queries to the cwd ACP option merges preserve the active selection and refreshed defs reach open pickers; defaultModel is validated and gated entries never auto-select; remote metadata wins over binary-reported entries for codex; refreshIfStale rejections are caught. git status/diff for files-changed scope to the session cwd subtree (a scratch session no longer scans the whole dirty worktree). * Carry codex effort/tier metadata through the catalog; merge remote models on refresh failure Day-one remote-only codex models keep their reasoning and tier options instead of falling back to hardcoded defaults, and a failed provider refresh broadcasts the fallback merged with the downloaded remote models rather than the stale pre-OTA snapshot. | 2 个月前 | |
Fix Claude bridge branch and resume failures (#7882) (#7900) * test: cover inherited Claude bridge sessions * Fix Claude bridge session identity leakage * Fix OMC Claude session identity inheritance * Harden independent Claude launch boundaries * Generate Claude launch environment policy * Preserve Claude Teams respawn trust * Run Claude launch policy test in CI * Preserve Claude auto-naming trust context | 2 个月前 | |
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> | 2 个月前 | |
Over-the-air model catalog for agent-chat (cmux-owned models.dev) (#7751) * Over-the-air model catalog: cmux.dev endpoint + layered sidecar consumption GET /api/agent-models serves a versioned, ETag'd, CDN-cached catalog from checked-in data (deploying the site is the update mechanism); the sidecar fetches it with revalidation, a 1h TTL, disk-persisted last-good payload for offline, strict schema validation, and a WS model-catalog event so open pickers refresh live. Merge precedence per provider: remote catalog, then binary-reported models, then the built-in curated lists only when nothing was ever fetched. Selected ids pass through verbatim to the CLIs so day-one models work without any update; minVersion gates keep unsupported entries disabled with the upgrade hint. * Address catalog review findings; scope files-changed git queries to the cwd ACP option merges preserve the active selection and refreshed defs reach open pickers; defaultModel is validated and gated entries never auto-select; remote metadata wins over binary-reported entries for codex; refreshIfStale rejections are caught. git status/diff for files-changed scope to the session cwd subtree (a scratch session no longer scans the whole dirty worktree). * Carry codex effort/tier metadata through the catalog; merge remote models on refresh failure Day-one remote-only codex models keep their reasoning and tier options instead of falling back to hardcoded defaults, and a failed provider refresh broadcasts the fallback merged with the downloaded remote models rather than the stale pre-OTA snapshot. | 2 个月前 | |
agent-chat: turn summaries, virtualization, gallery, option UI polish + new-workspace menu entry (#7610) * 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: thinking indicator and pending states A shimmering "Thinking" indicator renders at the transcript tail whenever the session is running without visible output — immediately on send, between tool phases, switching to "Reasoning" under a streaming thinking block, and yielding to tool spinners — with an elapsed counter after 3s and an "esc to interrupt" hint. The state derives from a pure function over session status and the folded block tail (unit-tested phase matrix); the elapsed timer keys on block phase transitions so reasoning deltas don't reset it. Claude's 1-2 minute first-token latency no longer looks like a hang. Composer submits guard against double-send and dropped sends: sendRaw reports whether the message left the socket, submit only enters the pending state (and clears the draft) when it did, and a reconnect epoch reset un-wedges any pending state stranded by a socket death. Fork failures now send an op-tagged error to the requesting socket (plus the transcript event), so the fork menu item recovers instead of spinning forever; a fork-failure e2e locks the wire contract. The harness icon pulses while running, fork shows a pending spinner, and all animations are keyframe-only with prefers-reduced-motion fallbacks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: native text-editing keys pass through; option shortcuts move to Ctrl+Shift Plain Ctrl+letter combos are never intercepted while an editable element has focus and no popup is open — a policy branch ahead of the keymap lookup, so macOS's native editing set (Ctrl+K kill-line, Ctrl+A/E, Ctrl+D/H, Ctrl+F/B/N/P, Ctrl+T, Ctrl+Y) always reaches the field and future bindings can't shadow it. Option shortcuts rebind to Ctrl+Shift (M model cycle, P picker, T effort, F fast, L plan); popup navigation keys are unchanged. Unit assertions pin the policy branch, and tooltips, help overlay, and README derive from the keymap table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: turn summaries, virtualization, unified pickers, gallery, animations Rounds 25-41 of the option-UI iteration: component decomposition (src/components/), turn summaries with 3-level progressive disclosure, transcript virtualization, files-changed trees with per-file diffs, shiki highlighting driven by the ghostty palette, configurable fonts, tooltips with delay grouping, lazy composer start, selection/cursor policy, split+gzipped bundles, and a /gallery mock page for visual QA. Round 40/41: tabular-nums on incrementing numbers, SVG chevron disclosure carets, turn-action time right-aligned, grid-rows expand/collapse animation with reduced-motion fallback, fixed-width fast toggle (no layout shift). * Add agent chat to the new-workspace menu Built-in 'Agent chat' item in the new-workspace context menu, wired through cmux.json (agentChat.url/startCommand) with section ordering (customFirst/cloudFirst), localized en+ja, schema + docs updated. * agent-chat: fix disclosure open animation; duration sits next to turn actions DisclosureMotion's grid wrapper is now mounted in both states (children still render lazily only while open or exiting), so an open toggle transitions grid-template-rows from a painted 0fr instead of mounting already-open; virtualized remounts of expanded rows stay instant. Turn-action duration moves from the far right edge to immediately after the copy/overflow buttons. * Split agent-chat config code into dedicated files to satisfy Swift length budgets CmuxAgentChatConfig.swift, CmuxAgentChatConfigTests.swift, ContentView+AgentChatCommandPalette.swift, Workspace+AgentChat.swift, and CmuxSurfaceTabBarBuiltInAction+Codable.swift take the round's additions out of the four over-budget files; budgets ratcheted down to the new counts. * Address review findings: asset cache default-on, diff path containment, per-render fonts, CSS font escaping, agentChat config resolves as a unit - buildBundles caches by default again (CMUX_AGENT_UI_DEV=1 opts out); the merge had inverted the condition so every asset request reran Bun.build - get-file-diff resolves the requested path against the session cwd and rejects anything escaping it (not just leading ../) - agentChat.fonts re-resolve per renderPage via a small TTL cache - cssFontFamily quotes each family and strips control chars/semicolons/braces - CmuxAgentChatConfiguration.resolved adopts a local agentChat block as a unit instead of field-wise local ?? global (url from one config no longer runs the other config's startCommand); tests cover the four combinations - server-utils tests for cache reuse, containment, sanitization * Move agent-chat actions off AppDelegate.swift; gate local startCommand behind project trust AppDelegate+AgentChat.swift takes the 197-line action block so AppDelegate.swift no longer grows past the 900-line hard cap; the Workspace switch case is offset to net zero. CmuxAgentChatConfiguration now carries its config source, and a local-config startCommand routes through CmuxConfigExecutor's project automation trust prompt before first execution; global config keeps direct launch. * Address second autoreview pass: bounded highlight cache, untracked-file diffs, complete agentChat schema, stable virtual measure refs - ChatMarkdown htmlCache is a capped LRU instead of unbounded module state - fileDiff produces a real diff for untracked files (no-index vs /dev/null) and the client renders an explicit empty state instead of loading forever - cmux.schema.json declares agentChat.fonts and agentChat.keys.ctrlJ with localized descriptions (en+ja), matching what the server reads - useVirtualTurns caches per-index measure callbacks so streaming renders no longer recreate ResizeObservers for every visible row * Address review pass 3 and bot round 2 Server: gitOutputWithCodes streams with a real byte cap and 10s deadline (kills the child at either limit); start/fork/get-file-diff errors are sanitized before reaching the transcript (raw error logged server-side); get-file-diff replies with an op-tagged error for stale sessions/paths; done waits for files-changed (750ms bound); asset/CSS builds are single-flight; font values strip angle brackets so </style> cannot end the inline style element. Client: replies queued during lazy start instead of dropped; diff panel renders explicit error/empty states; activity key for unknown tail blocks carries the length prefix so the elapsed timer resets. Swift: fonts/keys-only local agentChat blocks no longer mask the global server config (source tagging follows the actual startCommand origin); local start commands launch from the project root; the new-workspace menu item hides when browser surfaces are disabled, matching the palette gate. * Fix stale file-diff cache; agent chat menu item honors action opt-out Client diff cache keys by session + files-changed revision so a later turn touching the same path re-requests instead of showing the old diff; loaded checks use key existence to keep explicit empty/error states. The built-in menu item now goes through the same resolution model as other workspace actions: newWorkspaceMenu:false hides it and a custom ui.newWorkspace.contextMenu supersedes the default append. * Preserve intermediate assistant segments within a turn groupTurns moves earlier assistant blocks into the ordered activity stream when later activity or prose follows, so prose emitted before a tool call renders in chronological position inside the expanded turn instead of being overwritten; the final segment stays the primary answer. Gallery fixture covers prose -> tool -> final. * Convert CmuxAgentChatConfigTests to Swift Testing * Address review pass 7 and bot round 3 Server: done waits for files-changed inside the git deadline (no wall-clock fallback) and idle status broadcasts after done; git output truncation is metadata so '[truncated]' never appears as a file entry. Client: the turn grouper demotes an assistant block only when a later assistant exists, so late file events can no longer hide the final answer; streaming code renders plain and highlights once on completion with a byte-bounded cache; pre-session start failures return to the composer with the prompt and a sanitized error; virtualizer observer teardown happens in layout-effect cleanup, not render. Swift: health probe targets the URL origin's /healthz; the menu opt-out resolves once in CmuxConfigStore.loadAll instead of reparsing config on every menu open. * Turn-generation guards for deferred finalization; idempotent start on reconnect Deferred done/idle carry a per-session turn generation: a newer prompt drops the stale idle flip, and duplicate finalization is dropped once the turn has its footer. WS start is idempotent by requestId (short TTL), the client resends a pending start with the same id on reconnect, and a bounded timeout fails a stranded pending start into the composer error path. * Adapters route steer-vs-new-turn from their own turn state; browser gate in shared agent-chat action codex and pi track an explicit active turn and route follow-ups from it, so a prompt sent during the deferred done/idle window starts a new turn instead of steering a completed one (claude/acp audited: no status-keyed routing). performNewAgentChatAction refuses when browser surfaces are disabled so configured entrypoints cannot bypass the setting. * Bind turn generation at send; scope user-echo dedupe to optimistic ids; expire start requests Each turn carries the generation allocated at sendPrompt through to its finalization (queued ACP follow-ups no longer misattribute footers); duplicate-user suppression only drops the expected optimistic echo by request id; startRequests self-expire after the TTL on success and failure. * pi: tear down the turn on standalone error A pi error event without a following agent_end now clears the active turn, emits the footer through the shared finalization (idempotent, no duplicate on a late agent_end), and returns the session to idle so the next send starts a new turn instead of steering a dead one. * Attribute files-changed to the turn via a turn-start baseline; schema constrains agentChat.url sendPrompt captures the cwd's dirty state (porcelain paths + batched git hash-object signatures, bounded by FILES_LIMIT and the git deadline); turn-end reports only paths whose state changed during the turn, falling back to all-dirty with a marker if the baseline failed. agentChat.url gains pattern ^https?:// matching the runtime contract. * Retire per-turn baselines in finally; capture baseline before dispatching the prompt No-files, error, and timeout paths all delete the turn's baseline, and the prompt waits (3s bound) for the baseline so a fast agent's first edits cannot be filtered out of the Files Changed block. * Drop cross-turn files-changed dedupe The turn-start baseline already scopes the block to files changed during the turn, so an identical stats list from a later turn is a real edit; the session-wide key was suppressing it. * Bound baseline capture end-to-end before prompt dispatch Race the whole capture (git calls plus the stat/hash loop) against one deadline so a huge dirty worktree delays the send by at most ~3.5s and attribution degrades to the marked fallback. * claude: classify process exit from explicit active-turn state Send increments and result clears activeTurns; stdout-close and exit handlers decide crash-vs-clean from it, never sess.status, so a normal exit during the deferred files-changed window no longer reports a false mid-turn failure. * Hard deadline on done-side file attribution Race filesChangedEvents against DONE_FILES_TIMEOUT_MS+500 end to end; a wedged worktree yields a turn with no files block instead of a session stuck running after the agent finished. * Allowlist get-file-diff to reported paths; bound turn baselines get-file-diff only serves paths a files-changed event reported for that session, refusing unreported untracked files (.env) with an op-tagged error; turnBaselines retains at most the newest 4 generations so steered follow-ups cannot accumulate signatures indefinitely. * Structural attribution generations: steers do not allocate baselines; abortable deadlines Adapters report steer-vs-new to sendPrompt so only new-turn sends push an attribution generation; done finalization reads the explicit active generation queue, never the mutable latest counter; dirty-state collection checks one real deadline through the git flow and the stat loop so timed-out work stops instead of racing on; per-session attribution is serialized. * Prefer the emitting turn's async-context generation for done finalization A fast queued follow-up's completion no longer finalizes as the stale queue head, which dropped its footer as a duplicate and left the session running. * Carry the turn generation explicitly on done events sendPrompt hands the generation to the adapter; each adapter stores it in its explicit turn state and the emitted done carries it (stripped before store/broadcast). emitDoneAfterFiles uses only the carried value: no AsyncLocalStorage, no queue-head fallback, no mutable counter, so long-lived readers reused across turns cannot leak stale generations. Forks rebuild the diff allowlist from copied history. * Dispatch prompts immediately; baseline captures concurrently Conscious tradeoff closing the review loop: gating adapter.send on the dirty-baseline capture cost up to ~3.5s per message in large dirty repos. Sends now go out immediately; completion still awaits the stored baseline promise, and files the agent edits inside the capture window may be absorbed into the baseline (cosmetic files-changed omission, documented in code). * Complete attribution across adapters; guard the agent-chat action claude tracks queued stream-json prompts as separate FIFO turns and process close finalizes every in-flight generation; codex fork state clears activeGeneration; get-file-diff requires the socket to be subscribed to the session; the Agent Chat action is single-flight per window and a failed startCommand launch skips the 10s health poll. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 2 个月前 | |
agent-chat: turn summaries, virtualization, gallery, option UI polish + new-workspace menu entry (#7610) * 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: thinking indicator and pending states A shimmering "Thinking" indicator renders at the transcript tail whenever the session is running without visible output — immediately on send, between tool phases, switching to "Reasoning" under a streaming thinking block, and yielding to tool spinners — with an elapsed counter after 3s and an "esc to interrupt" hint. The state derives from a pure function over session status and the folded block tail (unit-tested phase matrix); the elapsed timer keys on block phase transitions so reasoning deltas don't reset it. Claude's 1-2 minute first-token latency no longer looks like a hang. Composer submits guard against double-send and dropped sends: sendRaw reports whether the message left the socket, submit only enters the pending state (and clears the draft) when it did, and a reconnect epoch reset un-wedges any pending state stranded by a socket death. Fork failures now send an op-tagged error to the requesting socket (plus the transcript event), so the fork menu item recovers instead of spinning forever; a fork-failure e2e locks the wire contract. The harness icon pulses while running, fork shows a pending spinner, and all animations are keyframe-only with prefers-reduced-motion fallbacks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: native text-editing keys pass through; option shortcuts move to Ctrl+Shift Plain Ctrl+letter combos are never intercepted while an editable element has focus and no popup is open — a policy branch ahead of the keymap lookup, so macOS's native editing set (Ctrl+K kill-line, Ctrl+A/E, Ctrl+D/H, Ctrl+F/B/N/P, Ctrl+T, Ctrl+Y) always reaches the field and future bindings can't shadow it. Option shortcuts rebind to Ctrl+Shift (M model cycle, P picker, T effort, F fast, L plan); popup navigation keys are unchanged. Unit assertions pin the policy branch, and tooltips, help overlay, and README derive from the keymap table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: turn summaries, virtualization, unified pickers, gallery, animations Rounds 25-41 of the option-UI iteration: component decomposition (src/components/), turn summaries with 3-level progressive disclosure, transcript virtualization, files-changed trees with per-file diffs, shiki highlighting driven by the ghostty palette, configurable fonts, tooltips with delay grouping, lazy composer start, selection/cursor policy, split+gzipped bundles, and a /gallery mock page for visual QA. Round 40/41: tabular-nums on incrementing numbers, SVG chevron disclosure carets, turn-action time right-aligned, grid-rows expand/collapse animation with reduced-motion fallback, fixed-width fast toggle (no layout shift). * Add agent chat to the new-workspace menu Built-in 'Agent chat' item in the new-workspace context menu, wired through cmux.json (agentChat.url/startCommand) with section ordering (customFirst/cloudFirst), localized en+ja, schema + docs updated. * agent-chat: fix disclosure open animation; duration sits next to turn actions DisclosureMotion's grid wrapper is now mounted in both states (children still render lazily only while open or exiting), so an open toggle transitions grid-template-rows from a painted 0fr instead of mounting already-open; virtualized remounts of expanded rows stay instant. Turn-action duration moves from the far right edge to immediately after the copy/overflow buttons. * Split agent-chat config code into dedicated files to satisfy Swift length budgets CmuxAgentChatConfig.swift, CmuxAgentChatConfigTests.swift, ContentView+AgentChatCommandPalette.swift, Workspace+AgentChat.swift, and CmuxSurfaceTabBarBuiltInAction+Codable.swift take the round's additions out of the four over-budget files; budgets ratcheted down to the new counts. * Address review findings: asset cache default-on, diff path containment, per-render fonts, CSS font escaping, agentChat config resolves as a unit - buildBundles caches by default again (CMUX_AGENT_UI_DEV=1 opts out); the merge had inverted the condition so every asset request reran Bun.build - get-file-diff resolves the requested path against the session cwd and rejects anything escaping it (not just leading ../) - agentChat.fonts re-resolve per renderPage via a small TTL cache - cssFontFamily quotes each family and strips control chars/semicolons/braces - CmuxAgentChatConfiguration.resolved adopts a local agentChat block as a unit instead of field-wise local ?? global (url from one config no longer runs the other config's startCommand); tests cover the four combinations - server-utils tests for cache reuse, containment, sanitization * Move agent-chat actions off AppDelegate.swift; gate local startCommand behind project trust AppDelegate+AgentChat.swift takes the 197-line action block so AppDelegate.swift no longer grows past the 900-line hard cap; the Workspace switch case is offset to net zero. CmuxAgentChatConfiguration now carries its config source, and a local-config startCommand routes through CmuxConfigExecutor's project automation trust prompt before first execution; global config keeps direct launch. * Address second autoreview pass: bounded highlight cache, untracked-file diffs, complete agentChat schema, stable virtual measure refs - ChatMarkdown htmlCache is a capped LRU instead of unbounded module state - fileDiff produces a real diff for untracked files (no-index vs /dev/null) and the client renders an explicit empty state instead of loading forever - cmux.schema.json declares agentChat.fonts and agentChat.keys.ctrlJ with localized descriptions (en+ja), matching what the server reads - useVirtualTurns caches per-index measure callbacks so streaming renders no longer recreate ResizeObservers for every visible row * Address review pass 3 and bot round 2 Server: gitOutputWithCodes streams with a real byte cap and 10s deadline (kills the child at either limit); start/fork/get-file-diff errors are sanitized before reaching the transcript (raw error logged server-side); get-file-diff replies with an op-tagged error for stale sessions/paths; done waits for files-changed (750ms bound); asset/CSS builds are single-flight; font values strip angle brackets so </style> cannot end the inline style element. Client: replies queued during lazy start instead of dropped; diff panel renders explicit error/empty states; activity key for unknown tail blocks carries the length prefix so the elapsed timer resets. Swift: fonts/keys-only local agentChat blocks no longer mask the global server config (source tagging follows the actual startCommand origin); local start commands launch from the project root; the new-workspace menu item hides when browser surfaces are disabled, matching the palette gate. * Fix stale file-diff cache; agent chat menu item honors action opt-out Client diff cache keys by session + files-changed revision so a later turn touching the same path re-requests instead of showing the old diff; loaded checks use key existence to keep explicit empty/error states. The built-in menu item now goes through the same resolution model as other workspace actions: newWorkspaceMenu:false hides it and a custom ui.newWorkspace.contextMenu supersedes the default append. * Preserve intermediate assistant segments within a turn groupTurns moves earlier assistant blocks into the ordered activity stream when later activity or prose follows, so prose emitted before a tool call renders in chronological position inside the expanded turn instead of being overwritten; the final segment stays the primary answer. Gallery fixture covers prose -> tool -> final. * Convert CmuxAgentChatConfigTests to Swift Testing * Address review pass 7 and bot round 3 Server: done waits for files-changed inside the git deadline (no wall-clock fallback) and idle status broadcasts after done; git output truncation is metadata so '[truncated]' never appears as a file entry. Client: the turn grouper demotes an assistant block only when a later assistant exists, so late file events can no longer hide the final answer; streaming code renders plain and highlights once on completion with a byte-bounded cache; pre-session start failures return to the composer with the prompt and a sanitized error; virtualizer observer teardown happens in layout-effect cleanup, not render. Swift: health probe targets the URL origin's /healthz; the menu opt-out resolves once in CmuxConfigStore.loadAll instead of reparsing config on every menu open. * Turn-generation guards for deferred finalization; idempotent start on reconnect Deferred done/idle carry a per-session turn generation: a newer prompt drops the stale idle flip, and duplicate finalization is dropped once the turn has its footer. WS start is idempotent by requestId (short TTL), the client resends a pending start with the same id on reconnect, and a bounded timeout fails a stranded pending start into the composer error path. * Adapters route steer-vs-new-turn from their own turn state; browser gate in shared agent-chat action codex and pi track an explicit active turn and route follow-ups from it, so a prompt sent during the deferred done/idle window starts a new turn instead of steering a completed one (claude/acp audited: no status-keyed routing). performNewAgentChatAction refuses when browser surfaces are disabled so configured entrypoints cannot bypass the setting. * Bind turn generation at send; scope user-echo dedupe to optimistic ids; expire start requests Each turn carries the generation allocated at sendPrompt through to its finalization (queued ACP follow-ups no longer misattribute footers); duplicate-user suppression only drops the expected optimistic echo by request id; startRequests self-expire after the TTL on success and failure. * pi: tear down the turn on standalone error A pi error event without a following agent_end now clears the active turn, emits the footer through the shared finalization (idempotent, no duplicate on a late agent_end), and returns the session to idle so the next send starts a new turn instead of steering a dead one. * Attribute files-changed to the turn via a turn-start baseline; schema constrains agentChat.url sendPrompt captures the cwd's dirty state (porcelain paths + batched git hash-object signatures, bounded by FILES_LIMIT and the git deadline); turn-end reports only paths whose state changed during the turn, falling back to all-dirty with a marker if the baseline failed. agentChat.url gains pattern ^https?:// matching the runtime contract. * Retire per-turn baselines in finally; capture baseline before dispatching the prompt No-files, error, and timeout paths all delete the turn's baseline, and the prompt waits (3s bound) for the baseline so a fast agent's first edits cannot be filtered out of the Files Changed block. * Drop cross-turn files-changed dedupe The turn-start baseline already scopes the block to files changed during the turn, so an identical stats list from a later turn is a real edit; the session-wide key was suppressing it. * Bound baseline capture end-to-end before prompt dispatch Race the whole capture (git calls plus the stat/hash loop) against one deadline so a huge dirty worktree delays the send by at most ~3.5s and attribution degrades to the marked fallback. * claude: classify process exit from explicit active-turn state Send increments and result clears activeTurns; stdout-close and exit handlers decide crash-vs-clean from it, never sess.status, so a normal exit during the deferred files-changed window no longer reports a false mid-turn failure. * Hard deadline on done-side file attribution Race filesChangedEvents against DONE_FILES_TIMEOUT_MS+500 end to end; a wedged worktree yields a turn with no files block instead of a session stuck running after the agent finished. * Allowlist get-file-diff to reported paths; bound turn baselines get-file-diff only serves paths a files-changed event reported for that session, refusing unreported untracked files (.env) with an op-tagged error; turnBaselines retains at most the newest 4 generations so steered follow-ups cannot accumulate signatures indefinitely. * Structural attribution generations: steers do not allocate baselines; abortable deadlines Adapters report steer-vs-new to sendPrompt so only new-turn sends push an attribution generation; done finalization reads the explicit active generation queue, never the mutable latest counter; dirty-state collection checks one real deadline through the git flow and the stat loop so timed-out work stops instead of racing on; per-session attribution is serialized. * Prefer the emitting turn's async-context generation for done finalization A fast queued follow-up's completion no longer finalizes as the stale queue head, which dropped its footer as a duplicate and left the session running. * Carry the turn generation explicitly on done events sendPrompt hands the generation to the adapter; each adapter stores it in its explicit turn state and the emitted done carries it (stripped before store/broadcast). emitDoneAfterFiles uses only the carried value: no AsyncLocalStorage, no queue-head fallback, no mutable counter, so long-lived readers reused across turns cannot leak stale generations. Forks rebuild the diff allowlist from copied history. * Dispatch prompts immediately; baseline captures concurrently Conscious tradeoff closing the review loop: gating adapter.send on the dirty-baseline capture cost up to ~3.5s per message in large dirty repos. Sends now go out immediately; completion still awaits the stored baseline promise, and files the agent edits inside the capture window may be absorbed into the baseline (cosmetic files-changed omission, documented in code). * Complete attribution across adapters; guard the agent-chat action claude tracks queued stream-json prompts as separate FIFO turns and process close finalizes every in-flight generation; codex fork state clears activeGeneration; get-file-diff requires the socket to be subscribed to the session; the Agent Chat action is single-flight per window and a failed startCommand launch skips the 10s health poll. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 2 个月前 | |
agent-chat catalog: direct cmux.com default, fast initial-fetch retry (#7846) * Catalog: default to cmux.com directly; retry failed initial fetches quickly cmux.dev 301s to cmux.com, adding a hop to every revalidation; and with no cached payload a failed first fetch now retries within a minute instead of waiting out the full TTL on built-in fallbacks. * Restore vendor/bonsplit pointer (stale worktree pointer swept into the previous commit) | 2 个月前 | |
agent-chat: dynamic-port + path-token serving, app-owned sidecar launch (#7723) * Serve agent-chat on the diff-viewer contract: dynamic port + path token, app-owned launch The sidecar accepts CMUX_AGENT_CHAT_TOKEN/PORT=0/STATE_FILE: every route and the WS require the unguessable token path segment (missing token 404s; /healthz stays tokenless), an ephemeral bind writes {port,pid,protocolVersion} atomically to the state file, and asset/WS URLs are prefix-relative. cmux (flag-gated) generates the token, injects the env into the trusted startCommand launch, discovers the port from the state file, health-checks, opens the tokened URL, and theme pushes follow; an explicitly configured agentChat.url keeps the old fixed-URL behavior as the advanced escape hatch. Fixed public localhost:7739 is no longer reachable in app-owned mode. * Launcher hardening from review Owned mode never probes or opens the default URL (a lingering legacy sidecar on 7739 can no longer bypass the tokened launch); the test seam moved behind @testable; state-file discovery polls off the main actor; launch state lives in a lock-boxed owner instead of a new singleton; state files use one stable per-instance path with a stale sweep at init. * State file carries launch identity; state store is an instance type The launcher injects CMUX_AGENT_CHAT_LAUNCH_ID and the sidecar echoes it into state.json; discovery accepts only its own launch's file, so a stale state file from a previous sidecar can never mismatch tokens. The state-file store takes its URL and FileManager at init instead of being a static namespace. * Annotate optional closure result types the compiler could not infer * Review nits: bare token path routes to root, nonisolated state model, createFile result checked * Owned launch failures never open the legacy URL Availability carries an optional browserURL; owned-mode failures return nil so the action beeps and posts a launch-failed notification anchored to the focused workspace instead of opening the untokened placeholder URL. Localized en+ja. * Keep AppDelegate+AgentChat under the 500-line tracking threshold * Owned-mode wrapper passthrough; dead-session clear; per-launch state files cmux-chat execs the server directly when CMUX_AGENT_CHAT_STATE_FILE is set (env flows through; no fixed-port probe, no URL opening); theme pushes clear the owned session on connection-level failure so the next action relaunches; state files are per-launch (state-<launchId>.json) with pattern-aware sweeping so concurrent cmux instances cannot clobber each other's discovery. * Disambiguate stateFileURL(launchId:) references after the per-launch refactor | 2 个月前 | |
Fix Claude bridge branch and resume failures (#7882) (#7900) * test: cover inherited Claude bridge sessions * Fix Claude bridge session identity leakage * Fix OMC Claude session identity inheritance * Harden independent Claude launch boundaries * Generate Claude launch environment policy * Preserve Claude Teams respawn trust * Run Claude launch policy test in CI * Preserve Claude auto-naming trust context | 2 个月前 | |
Over-the-air model catalog for agent-chat (cmux-owned models.dev) (#7751) * Over-the-air model catalog: cmux.dev endpoint + layered sidecar consumption GET /api/agent-models serves a versioned, ETag'd, CDN-cached catalog from checked-in data (deploying the site is the update mechanism); the sidecar fetches it with revalidation, a 1h TTL, disk-persisted last-good payload for offline, strict schema validation, and a WS model-catalog event so open pickers refresh live. Merge precedence per provider: remote catalog, then binary-reported models, then the built-in curated lists only when nothing was ever fetched. Selected ids pass through verbatim to the CLIs so day-one models work without any update; minVersion gates keep unsupported entries disabled with the upgrade hint. * Address catalog review findings; scope files-changed git queries to the cwd ACP option merges preserve the active selection and refreshed defs reach open pickers; defaultModel is validated and gated entries never auto-select; remote metadata wins over binary-reported entries for codex; refreshIfStale rejections are caught. git status/diff for files-changed scope to the session cwd subtree (a scratch session no longer scans the whole dirty worktree). * Carry codex effort/tier metadata through the catalog; merge remote models on refresh failure Day-one remote-only codex models keep their reasoning and tier options instead of falling back to hardcoded defaults, and a failed provider refresh broadcasts the fallback merged with the downloaded remote models rather than the stale pre-OTA snapshot. | 2 个月前 | |
agent-chat: virtualization scroll anchoring, measured-height disclosure, picker polish (#7650) * Scroll anchoring for virtualized transcript; picker row gap; smoother disclosure Re-measurements above the viewport compensate scrollTop in the same frame (adaptive row estimates shrink initial error); rows suppress re-measure while their disclosure animates and measure once at the end; activity rows get a capped-stagger entry animation; model picker rows gain a 1px seam. Adds resize-anim-lab.html: ten instrumented height animation approaches for side-by-side feel testing. * Remove per-row entry fade/translate from disclosure open The height reveal alone carries the animation; the staggered child fade felt wrong in dogfood. The lab's entry toggle now defaults off. * Disclosure animates measured height via WAAPI Replaces the grid-template-rows track animation after side-by-side testing in the resize-anim lab (user picked measured height). Open measures scrollHeight and animates 0->H committing height:auto on finish; close animates back and unmounts after; a mid-flight toggle cancels and continues from the current computed height. Remounts of already-open rows stay instant and reduced motion stays instant. * Live ghostty re-theme, no composer shadow, menu opt-out flip, disclosure races The sidecar watches the ghostty config and resolved theme sources, re-resolves through +show-config, and broadcasts a theme event that clients apply as CSS variables live (shiki colors are variable-driven so code re-themes too). The composer card loses its shadow. The plus menu appends the built-in Agent chat item even with a custom contextMenu (explicit newWorkspaceMenu:false or explicit inclusion are the only overrides). Disclosure finish remeasures through an unguarded event path so cached row heights can't go stale, and interrupt cleanup pins the mid-flight height before cancel so reopen continues instead of snapping (fixes the 110px->149px jump seen in CDP). * Anchor scroll compensation on the first visible index at measure time virtualFirstVisibleIndex derives the anchor from current scrollTop and offsets, so compensation covers exactly the rows above the viewport top: overscan rows above compensate, visible rows do not, and scroll-up mounts keep compensating against estimates. * Anchor strictly past exact row-bottom scroll positions When scrollTop equals a row's cumulative bottom edge that row is fully above the viewport; anchoring on it exempted its own resizes from compensation. * cmux pushes its resolved theme to the agent-chat sidecar AgentChatThemeSync builds the theme payload from cmux's own resolution (GhosttyApp runtime colors + full ANSI palette) and POSTs it to the sidecar's /api/theme when the action runs and on config-reload and background-change notifications, debounced and gated on recent health. The sidecar treats the pushed theme as authoritative (last writer wins), broadcasts it live, and its file fallback now also applies the managed override from cmux's app-support config with appearance-based light/dark selection — closing the +show-config divergence. The theme watcher poll now only stats mtimes and re-arms watchers only when the source list changes; recurring resolves use async spawn. * AgentChatThemeSync: import CmuxFoundation for GhosttyBackgroundBlur * Theme validator treats omitted nullable keys as null Swift's synthesized Codable drops nil optionals from the JSON; the exact-key check was 400ing every push from configs with an empty font-family. Unexpected keys are still rejected. * Allow opacity 0 in theme payloads (legal ghostty background-opacity) * Theme sync survives restarts and pushes unconditionally Observers install at app startup (not only on the create action), each notification resolves the current agentChat.url from the config store and POSTs with the short timeout (no health-freshness gate — a refused loopback connection is cheap), nullable payload fields encode explicit JSON nulls, and non-2xx responses are logged. * Theme sync cleanups: clock debounce, os Logger, nonisolated payload; opacity-0 and sparse-payload server test * Root-anchor the theme endpoint like healthURL A path in agentChat.url passed health checks but 404'd every theme POST because themeURL preserved the path prefix while the sidecar serves /api/theme at the origin root. * Push the theme once at launch Observers alone never fire after a relaunch with an unchanged config, so a running sidecar kept its file-derived theme until the next change; applicationDidFinishLaunching schedules the debounced push (init-time start() is too early for resolved config state). * Move the launch-time theme push into AgentChatThemeSync The 4-line AppDelegate addition tripped the frozen file's hard-cap budget; the sync now observes NSApplication.didFinishLaunching itself (or schedules immediately when already running), which also puts the behavior in the file that owns it. * Hue-aware accent selection; tooltips stack above popovers Accent candidates (ANSI 4,12,6,14,5,13,2,10) are filtered to blue/cyan/ violet hues so themes like Monokai that remap blue to orange fall through to their cyan; a neutral foreground mix is the last resort and an explicit accent in the cmux payload overrides. Tooltip positioners get a dedicated layer above menus/popovers. * Document agent-chat-accent and live theme updates in the README * Drop self-removal from the one-shot launch observer didFinishLaunching posts once per process; the mutable capture tripped the sendable-closure warning budget. * cmux theme pushes inherit the file-configured accent override The Swift payload has no accent field, so every push stored accent:null and silently disabled the documented agent-chat-accent override whenever cmux is running (always, since the launch push). | 2 个月前 | |
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> | 2 个月前 | |
agent-chat: turn summaries, virtualization, gallery, option UI polish + new-workspace menu entry (#7610) * 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: thinking indicator and pending states A shimmering "Thinking" indicator renders at the transcript tail whenever the session is running without visible output — immediately on send, between tool phases, switching to "Reasoning" under a streaming thinking block, and yielding to tool spinners — with an elapsed counter after 3s and an "esc to interrupt" hint. The state derives from a pure function over session status and the folded block tail (unit-tested phase matrix); the elapsed timer keys on block phase transitions so reasoning deltas don't reset it. Claude's 1-2 minute first-token latency no longer looks like a hang. Composer submits guard against double-send and dropped sends: sendRaw reports whether the message left the socket, submit only enters the pending state (and clears the draft) when it did, and a reconnect epoch reset un-wedges any pending state stranded by a socket death. Fork failures now send an op-tagged error to the requesting socket (plus the transcript event), so the fork menu item recovers instead of spinning forever; a fork-failure e2e locks the wire contract. The harness icon pulses while running, fork shows a pending spinner, and all animations are keyframe-only with prefers-reduced-motion fallbacks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: native text-editing keys pass through; option shortcuts move to Ctrl+Shift Plain Ctrl+letter combos are never intercepted while an editable element has focus and no popup is open — a policy branch ahead of the keymap lookup, so macOS's native editing set (Ctrl+K kill-line, Ctrl+A/E, Ctrl+D/H, Ctrl+F/B/N/P, Ctrl+T, Ctrl+Y) always reaches the field and future bindings can't shadow it. Option shortcuts rebind to Ctrl+Shift (M model cycle, P picker, T effort, F fast, L plan); popup navigation keys are unchanged. Unit assertions pin the policy branch, and tooltips, help overlay, and README derive from the keymap table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent-chat: turn summaries, virtualization, unified pickers, gallery, animations Rounds 25-41 of the option-UI iteration: component decomposition (src/components/), turn summaries with 3-level progressive disclosure, transcript virtualization, files-changed trees with per-file diffs, shiki highlighting driven by the ghostty palette, configurable fonts, tooltips with delay grouping, lazy composer start, selection/cursor policy, split+gzipped bundles, and a /gallery mock page for visual QA. Round 40/41: tabular-nums on incrementing numbers, SVG chevron disclosure carets, turn-action time right-aligned, grid-rows expand/collapse animation with reduced-motion fallback, fixed-width fast toggle (no layout shift). * Add agent chat to the new-workspace menu Built-in 'Agent chat' item in the new-workspace context menu, wired through cmux.json (agentChat.url/startCommand) with section ordering (customFirst/cloudFirst), localized en+ja, schema + docs updated. * agent-chat: fix disclosure open animation; duration sits next to turn actions DisclosureMotion's grid wrapper is now mounted in both states (children still render lazily only while open or exiting), so an open toggle transitions grid-template-rows from a painted 0fr instead of mounting already-open; virtualized remounts of expanded rows stay instant. Turn-action duration moves from the far right edge to immediately after the copy/overflow buttons. * Split agent-chat config code into dedicated files to satisfy Swift length budgets CmuxAgentChatConfig.swift, CmuxAgentChatConfigTests.swift, ContentView+AgentChatCommandPalette.swift, Workspace+AgentChat.swift, and CmuxSurfaceTabBarBuiltInAction+Codable.swift take the round's additions out of the four over-budget files; budgets ratcheted down to the new counts. * Address review findings: asset cache default-on, diff path containment, per-render fonts, CSS font escaping, agentChat config resolves as a unit - buildBundles caches by default again (CMUX_AGENT_UI_DEV=1 opts out); the merge had inverted the condition so every asset request reran Bun.build - get-file-diff resolves the requested path against the session cwd and rejects anything escaping it (not just leading ../) - agentChat.fonts re-resolve per renderPage via a small TTL cache - cssFontFamily quotes each family and strips control chars/semicolons/braces - CmuxAgentChatConfiguration.resolved adopts a local agentChat block as a unit instead of field-wise local ?? global (url from one config no longer runs the other config's startCommand); tests cover the four combinations - server-utils tests for cache reuse, containment, sanitization * Move agent-chat actions off AppDelegate.swift; gate local startCommand behind project trust AppDelegate+AgentChat.swift takes the 197-line action block so AppDelegate.swift no longer grows past the 900-line hard cap; the Workspace switch case is offset to net zero. CmuxAgentChatConfiguration now carries its config source, and a local-config startCommand routes through CmuxConfigExecutor's project automation trust prompt before first execution; global config keeps direct launch. * Address second autoreview pass: bounded highlight cache, untracked-file diffs, complete agentChat schema, stable virtual measure refs - ChatMarkdown htmlCache is a capped LRU instead of unbounded module state - fileDiff produces a real diff for untracked files (no-index vs /dev/null) and the client renders an explicit empty state instead of loading forever - cmux.schema.json declares agentChat.fonts and agentChat.keys.ctrlJ with localized descriptions (en+ja), matching what the server reads - useVirtualTurns caches per-index measure callbacks so streaming renders no longer recreate ResizeObservers for every visible row * Address review pass 3 and bot round 2 Server: gitOutputWithCodes streams with a real byte cap and 10s deadline (kills the child at either limit); start/fork/get-file-diff errors are sanitized before reaching the transcript (raw error logged server-side); get-file-diff replies with an op-tagged error for stale sessions/paths; done waits for files-changed (750ms bound); asset/CSS builds are single-flight; font values strip angle brackets so </style> cannot end the inline style element. Client: replies queued during lazy start instead of dropped; diff panel renders explicit error/empty states; activity key for unknown tail blocks carries the length prefix so the elapsed timer resets. Swift: fonts/keys-only local agentChat blocks no longer mask the global server config (source tagging follows the actual startCommand origin); local start commands launch from the project root; the new-workspace menu item hides when browser surfaces are disabled, matching the palette gate. * Fix stale file-diff cache; agent chat menu item honors action opt-out Client diff cache keys by session + files-changed revision so a later turn touching the same path re-requests instead of showing the old diff; loaded checks use key existence to keep explicit empty/error states. The built-in menu item now goes through the same resolution model as other workspace actions: newWorkspaceMenu:false hides it and a custom ui.newWorkspace.contextMenu supersedes the default append. * Preserve intermediate assistant segments within a turn groupTurns moves earlier assistant blocks into the ordered activity stream when later activity or prose follows, so prose emitted before a tool call renders in chronological position inside the expanded turn instead of being overwritten; the final segment stays the primary answer. Gallery fixture covers prose -> tool -> final. * Convert CmuxAgentChatConfigTests to Swift Testing * Address review pass 7 and bot round 3 Server: done waits for files-changed inside the git deadline (no wall-clock fallback) and idle status broadcasts after done; git output truncation is metadata so '[truncated]' never appears as a file entry. Client: the turn grouper demotes an assistant block only when a later assistant exists, so late file events can no longer hide the final answer; streaming code renders plain and highlights once on completion with a byte-bounded cache; pre-session start failures return to the composer with the prompt and a sanitized error; virtualizer observer teardown happens in layout-effect cleanup, not render. Swift: health probe targets the URL origin's /healthz; the menu opt-out resolves once in CmuxConfigStore.loadAll instead of reparsing config on every menu open. * Turn-generation guards for deferred finalization; idempotent start on reconnect Deferred done/idle carry a per-session turn generation: a newer prompt drops the stale idle flip, and duplicate finalization is dropped once the turn has its footer. WS start is idempotent by requestId (short TTL), the client resends a pending start with the same id on reconnect, and a bounded timeout fails a stranded pending start into the composer error path. * Adapters route steer-vs-new-turn from their own turn state; browser gate in shared agent-chat action codex and pi track an explicit active turn and route follow-ups from it, so a prompt sent during the deferred done/idle window starts a new turn instead of steering a completed one (claude/acp audited: no status-keyed routing). performNewAgentChatAction refuses when browser surfaces are disabled so configured entrypoints cannot bypass the setting. * Bind turn generation at send; scope user-echo dedupe to optimistic ids; expire start requests Each turn carries the generation allocated at sendPrompt through to its finalization (queued ACP follow-ups no longer misattribute footers); duplicate-user suppression only drops the expected optimistic echo by request id; startRequests self-expire after the TTL on success and failure. * pi: tear down the turn on standalone error A pi error event without a following agent_end now clears the active turn, emits the footer through the shared finalization (idempotent, no duplicate on a late agent_end), and returns the session to idle so the next send starts a new turn instead of steering a dead one. * Attribute files-changed to the turn via a turn-start baseline; schema constrains agentChat.url sendPrompt captures the cwd's dirty state (porcelain paths + batched git hash-object signatures, bounded by FILES_LIMIT and the git deadline); turn-end reports only paths whose state changed during the turn, falling back to all-dirty with a marker if the baseline failed. agentChat.url gains pattern ^https?:// matching the runtime contract. * Retire per-turn baselines in finally; capture baseline before dispatching the prompt No-files, error, and timeout paths all delete the turn's baseline, and the prompt waits (3s bound) for the baseline so a fast agent's first edits cannot be filtered out of the Files Changed block. * Drop cross-turn files-changed dedupe The turn-start baseline already scopes the block to files changed during the turn, so an identical stats list from a later turn is a real edit; the session-wide key was suppressing it. * Bound baseline capture end-to-end before prompt dispatch Race the whole capture (git calls plus the stat/hash loop) against one deadline so a huge dirty worktree delays the send by at most ~3.5s and attribution degrades to the marked fallback. * claude: classify process exit from explicit active-turn state Send increments and result clears activeTurns; stdout-close and exit handlers decide crash-vs-clean from it, never sess.status, so a normal exit during the deferred files-changed window no longer reports a false mid-turn failure. * Hard deadline on done-side file attribution Race filesChangedEvents against DONE_FILES_TIMEOUT_MS+500 end to end; a wedged worktree yields a turn with no files block instead of a session stuck running after the agent finished. * Allowlist get-file-diff to reported paths; bound turn baselines get-file-diff only serves paths a files-changed event reported for that session, refusing unreported untracked files (.env) with an op-tagged error; turnBaselines retains at most the newest 4 generations so steered follow-ups cannot accumulate signatures indefinitely. * Structural attribution generations: steers do not allocate baselines; abortable deadlines Adapters report steer-vs-new to sendPrompt so only new-turn sends push an attribution generation; done finalization reads the explicit active generation queue, never the mutable latest counter; dirty-state collection checks one real deadline through the git flow and the stat loop so timed-out work stops instead of racing on; per-session attribution is serialized. * Prefer the emitting turn's async-context generation for done finalization A fast queued follow-up's completion no longer finalizes as the stale queue head, which dropped its footer as a duplicate and left the session running. * Carry the turn generation explicitly on done events sendPrompt hands the generation to the adapter; each adapter stores it in its explicit turn state and the emitted done carries it (stripped before store/broadcast). emitDoneAfterFiles uses only the carried value: no AsyncLocalStorage, no queue-head fallback, no mutable counter, so long-lived readers reused across turns cannot leak stale generations. Forks rebuild the diff allowlist from copied history. * Dispatch prompts immediately; baseline captures concurrently Conscious tradeoff closing the review loop: gating adapter.send on the dirty-baseline capture cost up to ~3.5s per message in large dirty repos. Sends now go out immediately; completion still awaits the stored baseline promise, and files the agent edits inside the capture window may be absorbed into the baseline (cosmetic files-changed omission, documented in code). * Complete attribution across adapters; guard the agent-chat action claude tracks queued stream-json prompts as separate FIFO turns and process close finalizes every in-flight generation; codex fork state clears activeGeneration; get-file-diff requires the socket to be subscribed to the session; the Agent Chat action is single-flight per window and a failed startCommand launch skips the 10s health poll. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 2 个月前 |
cmux-agent-ui
MVP of the "UI mode" for cmux: a web chat surface (initial composer + chat view) rendered in cmux's existing browser surface, backed by any coding agent CLI. In production cmux launches the sidecar on a discovered loopback port and opens the unguessable token-prefixed URL it reports.
Run
Three entrypoints, all landing on the same server:
- CLI (
cmux-chat, symlinked into~/.local/bin):cmux-chatopens a composer as a new workspace tab;cmux-chat -p codex fix the testsstarts the chat immediately;--splitopens in the current workspace instead;--no-openprints the URL. It auto-starts the server if needed. - Command palette:
Cmd+Shift+P→ "New Agent Chat". Wired via~/.config/cmux/cmux.json(actions.agent-chat→workspaceCommand"Agent Chat" with a browser-surface layout), cmux's designed extension point, so no app build. When this productizes it becomes a built-in palette command in the cmux repo. - Server runs as a cmux sidecar. Manual dev run:
bun server.ts(defaults tohttp://127.0.0.1:7739with no token). Production launchers setCMUX_AGENT_CHAT_PORT=0,CMUX_AGENT_CHAT_TOKEN=<unguessable>, andCMUX_AGENT_CHAT_STATE_FILE=<path>; after bind the server atomically writes{"port":..., "pid":..., "protocolVersion":1}so cmux can openhttp://127.0.0.1:<port>/<token>/.
One page = one session: / is the composer, /s/<id> a chat. When CMUX_AGENT_CHAT_TOKEN or --token is configured, every HTTP route, static asset, API route, and WebSocket upgrade except /healthz must be under /<token>/...; missing or wrong tokens return 404. There is deliberately no in-page session list or header; each chat is its own cmux workspace tab (page title = first prompt), so cmux's sidebar is the session list.
Model catalog
The sidecar fetches the model catalog from https://cmux.dev/api/agent-models (CMUX_AGENT_MODELS_URL overrides it for development), revalidates it with ETags after a one-hour TTL, and caches the last-good response at ~/.cache/cmux-agent-chat/models.json for offline startup. Refreshes happen in the background; changed catalogs are pushed to open pages so model pickers update without reloading.
Remote entries define the offered model order, labels, descriptions, defaults, context metadata, fast-mode support, and Claude minimum-version gates. Models reported only by the installed binary are appended. The built-in Claude and Gemini lists are used only until a remote payload has been fetched or loaded from disk. Model IDs are passed to provider CLIs verbatim, including remote-only IDs, so newly released models work without a sidecar update.
Theming
The server resolves the terminal's colors from ~/.config/ghostty/config (theme file from ~/.config/ghostty/themes or the cmux/Ghostty app bundle, explicit background/foreground overrides, palette = N=#rrggbb ANSI colors, selection-background, cursor-color, background-opacity, blur) and injects them as CSS variables at serve time, so the page paints with the terminal background on first frame. Syntax highlighting maps token colors to the injected Ghostty ANSI palette (--ansi-*), so code colors track the active terminal theme without rebuilding the client bundle. /api/theme exposes the resolved values. Splits opened by cmux-chat --split use browser.open_split with transparent_background: true plus ?transparent=1, so the body is rgba(bg, background-opacity) and Ghostty transparency/blur shows through. Workspace-tab chats (palette, default CLI) are solid theme-bg because cmux workspace layout definitions don't carry a transparency flag yet; adding transparent to CmuxSurfaceDefinition in cmux would close that gap. Theme changes apply live (the server watches the config and cmux pushes its resolved theme on reload/appearance changes). The accent color is picked hue-aware from the palette (blue/cyan/violet candidates first); set agent-chat-accent = #rrggbb in the ghostty config to override it explicitly.
Agent-chat also reads optional font settings from ~/.config/cmux/cmux.json:
{
"agentChat": {
"fonts": {
"sansFamily": "-apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif",
"baseSize": 14,
"monoFamily": "Berkeley Mono",
"codeSize": 12.5,
"codeLineHeight": 1.5
}
}
}
Defaults: body text uses the system sans stack at 14px; code uses Ghostty's font-family and font-size when resolvable, otherwise the built-in monospace stack at 12.5px; code line-height defaults to 1.5.
Smoke test every provider end to end (spawns real agents):
bun test/e2e.ts # or: bun test/e2e.ts codex pi
Architecture
The frontend is a small React app built with Bun and styled to match the
terminal. Dropdowns and controls use @base-ui-components/react (Base UI),
the same component library the cmux web app uses: Select for the provider
picker and Popover for the working-directory editor and overflow menus.
Base UI ships unstyled, so every part is themed with the resolved Ghostty
colors. The server bundles src/main.tsx with Bun.build on startup and
serves it as /app.js; the HTML shell injects the theme CSS variables and
loads app.css relative to the current sidecar prefix.
browser surface (React app: src/*.tsx + Base UI)
│ WebSocket (common event schema)
server.ts (Bun): session manager, replayable event log per session
│
adapters/: normalize each provider into AgentEvent
claude.ts persistent `claude -p --input/output-format stream-json`
codex.ts shared `codex app-server` (JSON-RPC), one thread per session
pi.ts persistent `pi --mode rpc`
acp.ts generic ACP (JSON-RPC/NDJSON over stdio) client → opencode, gemini, …
The UI only knows AgentEvent (types.ts): user, delta, assistant, thinking, tool-start/end, status, done, error, meta. Sessions live in server memory with a full event log, so any client (reload, second browser, future native surface) can subscribe and replay.
How this covers every agent provider
Two adapter families are enough, and family 2 is a single implementation:
- Native stream-JSON/JSON-RPC CLIs. Claude Code (
--output-format stream-json), Codex (app-server, the JSON-RPC server its IDE extension uses), pi (--mode rpc), cursor-agent and amp have the same shape. Each needs a ~100-line adapter because event names differ, but they all reduce to the same event set: text deltas, tool start/end, turn done. Use a native adapter when the native protocol carries things ACP doesn't yet (Claude permission modes/hooks, Codex thread/turn model and approvals). - ACP (Agent Client Protocol, agentclientprotocol.com). One generic client (
adapters/acp.ts) speaks initialize → session/new → session/prompt, renderssession/updatenotifications, and answers reverse requests (session/request_permission). That single file already runs opencode (opencode acp) and gemini (gemini --acp), and gets claude (@zed-industries/claude-code-acp), goose, marimo, and future agents for free. ACP is the long-term contract: it's the protocol Zed drove, adapters keep appearing, and it standardizes exactly the hard parts (permissions, fs proxying, tool call lifecycle, plans).
Capability differences are absorbed by the schema, not the UI:
| provider | transport | streaming | tools visible | multi-turn | permissions |
|---|---|---|---|---|---|
| claude | persistent stdio | deltas | yes | persistent proc | permission mode |
| codex | app-server JSON-RPC | deltas | yes | thread per session | approvals + sandbox options |
| opencode | ACP persistent stdio | deltas | yes | ACP session | auto-approve toggle for request_permission |
| gemini | ACP persistent stdio | deltas | yes | ACP session | auto-approve toggle (--yolo at start) |
| pi | persistent stdio | deltas | yes | persistent proc | none (always executes) |
Runtime options are declared by adapters as SessionOption[] and replayed as
options events. React renders the schema generically; provider-specific logic
stays in adapters.
| provider | option | mechanism |
|---|---|---|
| claude | model | control_request list_models / set_model; default omits model to reset |
| claude | mode | set_permission_mode (default, acceptEdits, plan, bypassPermissions, dontAsk, auto) |
| claude | thinking | set_max_thinking_tokens (0, 4096, 16384, 32768) |
| claude | effort, fast | apply_flag_settings with {effortLevel} / {fastMode}; verified accepted by Claude 2.1.201 |
| codex | model | model/list; stored on the session and applied through turn/start / thread/settings/update |
| codex | effort | selected model's supportedReasoningEfforts |
| codex | fast | selected model service tier whose id/name/description advertises fast/priority |
| codex | approvals, sandbox | approvalPolicy and sandboxPolicy turn/thread overrides |
| codex | mode | real collaborationMode/list + collaborationMode setter; app-server requires experimentalApi capability |
| codex | skills | skills/list, emitted as $ commands |
| opencode/gemini ACP | auto-approve | local autoApprove toggle answers session/request_permission; gemini also maps the start value to --yolo |
| pi | model | RPC get_available_models / set_model {provider, modelId} |
| pi | thinking | RPC set_thinking_level |
| pi | commands | RPC get_commands, emitted as / commands |
| opencode/gemini ACP | model, mode | session/new configOptions / ACP modes; opencode setter is session/set_config_option (session/set_config is not supported by 1.17.13) |
| opencode/gemini ACP | commands | available_commands_update, emitted as / commands; opencode accepts slash command prompt text and routes it to session.command |
Keyboard shortcuts are defined once in src/keymap.ts and the status row plus
keyboard handler both call the same setOption path:
| shortcut | action |
|---|---|
Shift+Tab |
cycle mode-like option |
Ctrl+Shift+M |
cycle model |
Ctrl+Shift+P |
open model select |
Ctrl+Shift+T |
cycle thinking/effort |
Ctrl+Shift+F |
toggle fast mode |
Ctrl+Shift+L |
toggle plan mode |
Esc |
interrupt running turn, else close popup/overlay |
Ctrl+/ or ? on an empty input |
shortcut help overlay |
ArrowDown / Ctrl+N |
next item while a /, $, or @ popup is open |
ArrowUp / Ctrl+P |
previous item while a /, $, or @ popup is open |
Enter / Tab |
accept the selected popup item |
Ctrl+J |
insert newline by default; set agentChat.keys.ctrlJ to "menu" in ~/.config/cmux/cmux.json to make it next-item while a popup is open |
When focus is in a text input and no popup is open, plain Ctrl+<letter>
combinations are left to the native macOS text editor bindings.
Typing @ at a token start opens the same popup UI for file references. The
server lists git-tracked/untracked files for the cwd when possible and falls
back to a capped directory walk outside git repositories.
Adding a provider is either one registry entry (ACP-speaking: id + cmd) or one small adapter file (bespoke stream-JSON). Nothing in the UI changes.
Known env quirks (this machine)
- gemini: Google now blocks Gemini Code Assist for individuals (
IneligibleTierError, migrate-to-Antigravity). ACP handshake works; auth fails upstream. Registry keeps it; it errors fast in the UI. - claude: TTFT is ~1-2 min on this machine when an
ANTHROPIC_API_KEY/proxy auth source is active; the UI streams fine once tokens start.
What the real cmux feature needs beyond this MVP
- A native
AgentChatSurface(or a pinned browser surface type) with the composer as the new-workspace view; the server becomes a cmux-owned daemon keyed by workspace, sessions persisted to disk (each provider already has resume: claude--resume, codex thread ids, ACPsession/load). - Permission requests routed to native cmux dialogs/notifications instead of the auto-approve toggle; ACP already models this, and claude gets it via
--permission-prompt-toolor the ACP adapter. - Attach chat sessions to the workspace's terminal/worktree (cwd = worktree, show diffs via
cmux-diff), and a "open in terminal" escape hatch that resumes the same session in the provider's TUI (claude --resume <id>,codex resume <thread>). - Provider registry in
~/.config/cmux/agents.jsonso users can add any ACP/stream-JSON agent without code.