Repository navigation
cmux-next agent pane: Vite dev server in Debug builds, and ~/.config/cmux/agent-pane hot reload - #16433
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository: manaflow-ai/cmux/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
3 issues found across 18 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="webviews/src/agent-session/acpmux/README.md">
<violation number="1" location="webviews/src/agent-session/acpmux/README.md:7">
P2: This says a missing or invalid layout is skipped, but the host sends `{}` to `configure` instead. Document that behavior or avoid calling `configure` when no valid layout exists.</violation>
<violation number="2" location="webviews/src/agent-session/acpmux/README.md:7">
P2: This describes `registry.js` as a host script without warning that it runs in the pane's page context and can access the daemon token. State that it is trusted code so users do not run untrusted registry scripts.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Tests/CmuxNextAgentPaneTests/AgentPaneCustomizationTests.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Tests/CmuxNextAgentPaneTests/AgentPaneCustomizationTests.swift:96">
P2: The injection marker is never observed: `run(_:)` returns only `calls`, so `window.pwned = 1` can execute while these assertions still pass. Make the payload append a marker to `calls` so the existing count assertion detects execution.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| Rows are measured before paint with [Pretext](https://github.com/chenglou/pretext) using the named `Helvetica Neue` font. Prepared markdown blocks are cached by row id, content version, and text. Layout stores exact tops and heights in typed arrays and finds the visible range with binary search. React mounts only that range; row components are memoized by id and content version, so a streaming update replaces one row. | ||
|
|
||
| User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components; Swift watches the files and replays them to the page. | ||
| User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components. cmux-next watches the directory (next to `cmux.json`, so `CMUX_NEXT_CONFIG_FILE` moves it) and pushes changes to open panes: it evaluates `registry.js` as host script, separately so a broken registry cannot stop the theme, then calls `applyCustomization({themeCSS, layout})`, which replaces the user stylesheet (an empty one after `theme.css` is deleted) and hands `layout.json` to the registry's `configure`. A file that is missing or invalid is skipped and the others still apply. |
There was a problem hiding this comment.
P2: This says a missing or invalid layout is skipped, but the host sends {} to configure instead. Document that behavior or avoid calling configure when no valid layout exists.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/README.md, line 7:
<comment>This says a missing or invalid layout is skipped, but the host sends `{}` to `configure` instead. Document that behavior or avoid calling `configure` when no valid layout exists.</comment>
<file context>
@@ -4,6 +4,17 @@ Swift is the WKWebView host only. On `ready`, the versioned host bridge returns
Rows are measured before paint with [Pretext](https://github.com/chenglou/pretext) using the named `Helvetica Neue` font. Prepared markdown blocks are cached by row id, content version, and text. Layout stores exact tops and heights in typed arrays and finds the visible range with binary search. React mounts only that range; row components are memoized by id and content version, so a streaming update replaces one row.
-User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components; Swift watches the files and replays them to the page.
+User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components. cmux-next watches the directory (next to `cmux.json`, so `CMUX_NEXT_CONFIG_FILE` moves it) and pushes changes to open panes: it evaluates `registry.js` as host script, separately so a broken registry cannot stop the theme, then calls `applyCustomization({themeCSS, layout})`, which replaces the user stylesheet (an empty one after `theme.css` is deleted) and hands `layout.json` to the registry's `configure`. A file that is missing or invalid is skipped and the others still apply.
The virtualized DOM cannot provide selection or find across rows that are unmounted. The v1 pane keeps cmux find in the host, which can ask the direct client to page and mount a matching row in a future action. The preview harness is the iteration path: `cd webviews && bun run preview:dev` for Vite hot reload, or `bun run preview:build`, which writes static files to `webviews/dist/acpmux-agent-session-preview/`.
</file context>
| User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components. cmux-next watches the directory (next to `cmux.json`, so `CMUX_NEXT_CONFIG_FILE` moves it) and pushes changes to open panes: it evaluates `registry.js` as host script, separately so a broken registry cannot stop the theme, then calls `applyCustomization({themeCSS, layout})`, which replaces the user stylesheet (an empty one after `theme.css` is deleted) and hands `layout.json` to the registry's `configure`. A file that is missing or invalid is skipped and the others still apply. | |
| User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components. cmux-next watches the directory (next to `cmux.json`, so `CMUX_NEXT_CONFIG_FILE` moves it) and pushes changes to open panes: it evaluates `registry.js` in the page context, separately so a broken registry cannot stop the theme, then calls `applyCustomization({themeCSS, layout})`, which replaces the user stylesheet (an empty one after `theme.css` is deleted) and passes the layout to the registry's `configure` as `{}` when `layout.json` is missing or invalid. Missing or unreadable files do not block other updates. |
| Rows are measured before paint with [Pretext](https://github.com/chenglou/pretext) using the named `Helvetica Neue` font. Prepared markdown blocks are cached by row id, content version, and text. Layout stores exact tops and heights in typed arrays and finds the visible range with binary search. React mounts only that range; row components are memoized by id and content version, so a streaming update replaces one row. | ||
|
|
||
| User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components; Swift watches the files and replays them to the page. | ||
| User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components. cmux-next watches the directory (next to `cmux.json`, so `CMUX_NEXT_CONFIG_FILE` moves it) and pushes changes to open panes: it evaluates `registry.js` as host script, separately so a broken registry cannot stop the theme, then calls `applyCustomization({themeCSS, layout})`, which replaces the user stylesheet (an empty one after `theme.css` is deleted) and hands `layout.json` to the registry's `configure`. A file that is missing or invalid is skipped and the others still apply. |
There was a problem hiding this comment.
P2: This describes registry.js as a host script without warning that it runs in the pane's page context and can access the daemon token. State that it is trusted code so users do not run untrusted registry scripts.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/README.md, line 7:
<comment>This describes `registry.js` as a host script without warning that it runs in the pane's page context and can access the daemon token. State that it is trusted code so users do not run untrusted registry scripts.</comment>
<file context>
@@ -4,6 +4,17 @@ Swift is the WKWebView host only. On `ready`, the versioned host bridge returns
Rows are measured before paint with [Pretext](https://github.com/chenglou/pretext) using the named `Helvetica Neue` font. Prepared markdown blocks are cached by row id, content version, and text. Layout stores exact tops and heights in typed arrays and finds the visible range with binary search. React mounts only that range; row components are memoized by id and content version, so a streaming update replaces one row.
-User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components; Swift watches the files and replays them to the page.
+User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components. cmux-next watches the directory (next to `cmux.json`, so `CMUX_NEXT_CONFIG_FILE` moves it) and pushes changes to open panes: it evaluates `registry.js` as host script, separately so a broken registry cannot stop the theme, then calls `applyCustomization({themeCSS, layout})`, which replaces the user stylesheet (an empty one after `theme.css` is deleted) and hands `layout.json` to the registry's `configure`. A file that is missing or invalid is skipped and the others still apply.
The virtualized DOM cannot provide selection or find across rows that are unmounted. The v1 pane keeps cmux find in the host, which can ask the direct client to page and mount a matching row in a future action. The preview harness is the iteration path: `cd webviews && bun run preview:dev` for Vite hot reload, or `bun run preview:build`, which writes static files to `webviews/dist/acpmux-agent-session-preview/`.
</file context>
| User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components. cmux-next watches the directory (next to `cmux.json`, so `CMUX_NEXT_CONFIG_FILE` moves it) and pushes changes to open panes: it evaluates `registry.js` as host script, separately so a broken registry cannot stop the theme, then calls `applyCustomization({themeCSS, layout})`, which replaces the user stylesheet (an empty one after `theme.css` is deleted) and hands `layout.json` to the registry's `configure`. A file that is missing or invalid is skipped and the others still apply. | |
| User customization files live in `~/.config/cmux/agent-pane/`: `theme.css`, `layout.json`, and `registry.js`. Registry components must provide a static `measure(row, width)` function returning a Pretext-based height. Components without it use post-mount measurement and scroll anchoring. `registry.js` can register or replace message, tool, edited-files, permission, and composer-chip components. cmux-next watches the directory (next to `cmux.json`, so `CMUX_NEXT_CONFIG_FILE` moves it) and pushes changes to open panes: it evaluates `registry.js` in the page context, where it can access the daemon token, so only trusted scripts should be used; a broken registry cannot stop the theme. It then calls `applyCustomization({themeCSS, layout})`, which replaces the user stylesheet (an empty one after `theme.css` is deleted) and passes the layout to the registry's `configure` as `{}` when `layout.json` is missing or invalid. Missing or unreadable files do not block other updates. |
| } | ||
|
|
||
| @Test func themeTextCannotEscapeTheCall() throws { | ||
| let hostile = #"a::after { content: "\"}); window.pwned = 1; //" }"# + "\n</script>\u{2028}" |
There was a problem hiding this comment.
P2: The injection marker is never observed: run(_:) returns only calls, so window.pwned = 1 can execute while these assertions still pass. Make the payload append a marker to calls so the existing count assertion detects execution.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Tests/CmuxNextAgentPaneTests/AgentPaneCustomizationTests.swift, line 96:
<comment>The injection marker is never observed: `run(_:)` returns only `calls`, so `window.pwned = 1` can execute while these assertions still pass. Make the payload append a marker to `calls` so the existing count assertion detects execution.</comment>
<file context>
@@ -0,0 +1,111 @@
+ }
+
+ @Test func themeTextCannotEscapeTheCall() throws {
+ let hostile = #"a::after { content: "\"}); window.pwned = 1; //" }"# + "\n</script>\u{2028}"
+ let customization = AgentPaneCustomization(themeCSS: hostile)
+ let calls = try run(customization)
</file context>
| let hostile = #"a::after { content: "\"}); window.pwned = 1; //" }"# + "\n</script>\u{2028}" | |
| let hostile = #"a::after { content: "\"}); calls.push("injected"); //" }"# + "\n</script>\u{2028}" |
The pane's page now comes from an AgentPaneSource (the bundled file, or a loopback Vite dev server), and AgentPaneCustomization carries the ~/.config/cmux/agent-pane/ files to the page. Both are stubs here: the dev-server override is ignored, its origin is never trusted, and the customization reads and pushes nothing, so the new suites fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
A Debug or tagged build launched with CMUX_NEXT_AGENT_PANE_DEV_URL=http://127.0.0.1:<port>/ loads the pane from that loopback dev server (bun run dev:agent-pane, port 4176) instead of the bundled page, so TypeScript edits hot-reload in the running app. The handshake still comes from Swift, so the dev page talks to the real acpmux daemon. AgentPaneSource.resolve takes allowsDevServer, which the App sets only under #if DEBUG: Release loads the bundled file whatever the environment says. The override must be http on 127.0.0.1 or localhost with an explicit port and no credentials; anything else keeps the bundled page. Navigation and the handshake trust only that exact origin. reload.sh forwards the variable into the tagged launch environment, and the pane README documents the loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
Ports #16042's customization hot reload. AgentPaneCustomizationWatcher watches theme.css, layout.json and registry.js next to cmux.json (so CMUX_NEXT_CONFIG_FILE moves them) with ConfigFileWatcher, reads them off the main actor, drops unchanged results, and sets the value on every agent pane; new panes get the current value. It runs while any agent tab has a view. AgentPaneView pushes a change at once, and replays it after a load and when the page asks for the handshake (its bridge and registry exist by then). registry.js is evaluated as its own host script, since the bundled CSP has no unsafe-eval, so a broken registry no longer stops the theme; then applyCustomization({themeCSS, layout}) runs. A missing file is skipped instead of failing the push, a deleted theme.css sends "" and clears the style, and layout.json must be a JSON object. The page hands layout to the registry's configure. The bundled page is rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
b4e16a2 to
f772804
Compare
…clarations The pane re-evaluates registry.js on every load, handshake and file change. As a global classic script, a second run of a file with a top-level const, let or class throws a SyntaxError (duplicate variable) before registering anything. Checked in the system jsc: the raw script registers once and then throws. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
Each replay of registry.js now runs it inside a fresh function, as #16042's indirect eval did, so top-level const, let and class declarations don't collide with the previous run. The README says the file is replayed and should be idempotent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
|
Subagent review at f772804: REQUEST_CHANGES. Release safety and dev-origin trust check out. The blocker: |
|
Subagent review at 86b8e98: APPROVE.
Non-blocking:
|
…ext-agent-pane-devloop # Conflicts: # Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/Resources/agent-pane/index.html
|
Merging. 0f720a9 is the approved head 86b8e98 with feat-cmux-next (#16426) merged in. The only conflict was the generated agent-pane/index.html, rebuilt with |
Summary
Changing the agent pane's React code used to mean rebuilding the bundle and the app. Now a Debug or tagged build can load the pane from a local Vite dev server, so TypeScript edits show up in the running app in about a second:
Dev server mode:
AgentPaneSource.resolve(environment:bundledPage:allowsDevServer:)accepts only anhttpURL on 127.0.0.1 or localhost, with an explicit port and no credentials. Anything else falls back to the bundled page.AgentPaneSource, so the pane trusts only the dev server's exact scheme, host and port.AgentTabStorepassesallowsDevServer: trueonly under#if DEBUG, so Release still loads nothing but the bundled file.ready, so the dev page talks to the real acpmux daemon.scripts/reload.shforwards the variable through itsenv -ilaunch.webviews/vite.config.acpmux-pane.mjsand a devindex.htmlwith a dev CSP. The README has the full loop.Customization hot reload (ported from #16042):
theme.css,layout.jsonandregistry.jsin~/.config/cmux/agent-pane/are watched withConfigFileWatcherand pushed into every open agent pane. The directory followsCMUX_NEXT_CONFIG_FILE.theme.cssclears the style, and an invalidlayout.jsonis ignored.registry.js: it is evaluated by the host as its own script instead of the page'seval, which the bundled page's CSP blocks.registry.jsruns with page privileges, including access to the daemon token, the same trust as in feat: add React acpmux agent pane #16042, since it's the user's own config file. Any local process on the dev port would also receive the token; the README says so.Follows #16231.## Verification
AgentPaneSourceTests(includingreleaseIgnoresTheDevServerOverride) andAgentPaneCustomizationTests, plus updatedAgentPaneNavigationTests. They're meant to fail at ba855a4 (stubs) and pass at head.registryWithDeclarationsReplaysInTheSamePagewas added at b3a745f, after review found that replayingregistry.jsas a global script throws on top-levelconst/class. The systemjscconfirms the semantics: the raw script registers once and then throws SyntaxError, while the function-wrapped form from 86b8e98 registers on both runs. Not run yet: this Mac doesn't build cmux, and the cmux-nextswift testlane is currently red for unrelated reasons, so the red and green results are unproven.nx-devloop-v1at b4e16a2 is running. Dogfooding follows: New Agent Chat with the dev server, edit a row component, and a hot-reload oftheme.css.bun run test(254 pass), typecheck andlint:cipass.build-agent-pane-web.sh --checkandbuild-webviews-app.sh --checkpass, so neither build picks up the new dev entry.bun run dev:agent-paneserved all 24 modules with 200.test_reload_local_backend_mode.sh,test_reload_build_only_keeps_tagged_app.sh,test_reload_shared_derived_data.sh,test_reload_tag_lock.pyandtest_ci_reload_build_cache_keys.pypass.Changelog
none
🤖 Generated with Claude Code
https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Debug and tagged builds can now load the agent pane from a local Vite dev server so TypeScript edits hot-reload in the running app, and customization files in
~/.config/cmux/agent-pane/hot-reload into open panes. Release builds still load only the bundled page.Dev server
CMUX_NEXT_AGENT_PANE_DEV_URL=http://127.0.0.1:4176/withbun run dev:agent-pane;reload.shforwards the variable, and only Debug builds honor it.httpon 127.0.0.1 or localhost with an explicit port, and the pane's bridge, navigation, and crash reload trust only that exact origin.Customization hot reload
theme.css,layout.json, andregistry.jsnext tocmux.json(soCMUX_NEXT_CONFIG_FILEmoves them) are watched and pushed to every open pane.registry.jsis evaluated as its own host script, bypassing the bundled page's CSP that blocked the previouseval; each replay runs in a fresh function scope so top-levelconst,let, andclassdeclarations don't collide, and a broken registry no longer stops the theme.theme.cssclears the style, and an invalidlayout.jsonis ignored.Written for commit 0f720a9. Summary will update on new commits.