Repository navigation
acpmux: _acpmux/btw side sessions about a chat - #20847
Conversation
Behavior tests for the composer's /btw side session: opened beside a running Claude chat without disturbing it, titled with the question, tagged side, read-only (plan mode, refused writes), on haiku; a harness that cannot fork gets the chat's transcript as hidden context; model resolution (param, config btw.model, harness default); refused for Web and peer connections; the pane relay allows it on a gesture for its own chat only. The config section is added so the tests compile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_acpmux/btw opens a side session about a chat for the composer's /btw: in the chat's harness and folder, tagged side with the chat, titled with the question, read-only (Claude plan / Codex read-only where offered, and always approve-reads or stricter with rules that refuse every writing tool kind), on a cheap model (param, config btw.model.<harness>, else haiku / Codex's smallest advertised model / the harness default). Claude and Codex fork through the latest finished turn, so it works mid-turn; other harnesses get a bounded transcript as hidden prompt context. The question is sent as the first prompt and the request answers once it is recorded. The chat is never paused, cancelled or changed. Refused for Web and peer connections (btw.remote) and held by the folder trust gate. The pane relay allows it for the pane's own chat on a gesture (policy.json, AcpmuxPaneMethods), and direct.ts gains btw(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
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 configuration
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 |
|
Bead |
The Swift PR allowlists _acpmux/btw in AcpmuxPaneMethods, and the web PR carries the Rust policy mirror with direct.ts (the TypeScript parity test ties policy.json to what the page sends). This PR keeps the acpmux side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Visual preview of e929619: in the maintainers' private review feed. |
acpmux's policy and rules only answer what the harness asks, so the side session must run in a mode that reads only or asks before every change: the Codex app-server bridge's sandboxed read-only, a family's asking mode, else a refusal (the codex-acp adapter, an agent with no asking mode). A mode that cannot be set and confirmed discards the side session. A closing context tag in the chat's transcript stays inside the wrapper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A side session now runs only in a mode that reads only (Claude plan, the in-process Codex bridge's sandboxed read-only) or asks before every change (the family's first webAskingModes entry), set and confirmed in the session's reported current mode. The codex-acp adapter, opencode and families with no asking mode are refused before anything opens; a mode that cannot be set or confirmed discards the side session with an error. The transcript context says it is untrusted, not instructions, and the chat's text can no longer close its wrapper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Found 1 test failure on Blacksmith runners: Failure
|
… tag A profile that runs opencode under "family": "claude" must be refused, never put in opencode's plan (it runs bash without asking). The context wrapper's closing tag must stay inert in any case and with whitespace around the slash or before ">". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… inert A profile that runs opencode or codex-acp under "family": "claude" is now refused instead of put in a plan mode that runs bash without asking; while one is configured Claude's own /btw is refused too (fail closed). Every closing tag of the context wrapper in the chat's text is made inert, in any case and with whitespace around the slash or before ">". The docs name the limits: the confirm reads acpmux's own mode record, a pane or socket can re-mode a side session, respawn replays the mode without failing, and harness-side allow lists and MCP tools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict in acpmux config.rs: keep both the btw and the titles sections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/38702990697 at ed9bbb8 failed: cmux-next generated files. |
…the protocol registry; pane policy test reads typedRequest (cx-js8uc) protocol_registry_names_every_served_method was red on every OS: _acpmux/harness/update and _acpmux/sessions/restart_harness (#20569, cx-1mbnu) and now _acpmux/btw (#20847) were served but not declared. Declare them (untyped, Request), refresh spec/acpmux-protocol.json and the generated web methods, and document update/restart_harness in docs/acpmux-schema.json. the_allowlist_is_what_the_page_sends was red since #20576: direct.ts sends _acpmux/permission_respond through typedRequest(send, "...", ...), which the test's this.request( scan missed. The test now also reads the literal method of each typedRequest call. policy.json is unchanged: the page still sends permission_respond, and the page never sends the harness methods itself (the Swift host calls them over the unix socket, gesture-gated; acpmux refuses them for LocalApp, Web and peer). Remove both cx-js8uc rows from scripts/ci/cmux-tui-base-red.tsv.
/btw <question>in the agent composer should open a side session beside the chat: a chat of its own, titled with the question, that can read the main thread and files but never write, on a cheap model, while the main chat keeps running. This PR is the acpmux half: a new_acpmux/btwrequest that creates that session and sends it the question. The web composer and the native right-pane PRs call it.Contract
Request
_acpmux/btw, params{ "sessionId": <chat>, "question": <non-empty after trim>, "model"?: <string> }.Result
{ "sessionId": <side session>, "model": <model or alias, or null for the harness default>, "readOnly": "mode" | "policy" }.Same harness and folder as the chat, tagged
sidewith the chat id (the lists hide it, as_acpmux/sidedoes), title = first line of the question, 80 chars.Context: Claude and Codex fork through the chat's latest finished turn (
fork_at.rs), which works while a turn runs. Any other harness, or a chat with no finished turn, gets a new session whose first prompt carries the chat's newest transcript (at most 12 KiB) as hidden prompt context (prompt_context.rsmarking, so the side thread shows only the question).Read-only, fail closed: acpmux's policy and rules only answer what the harness asks, so the side session must run in a mode that reads only or asks before every change. The mode is picked before anything opens, then set on the side session and confirmed in its reported current mode. A fork copies the chat's mode, full access included, so the mode is always set. If it cannot be set or confirmed, the side session is discarded and the request errors (
data.reason: btw.mode_unset). On top of the mode, the policy is alwaysapprove-reads(or the chat's ownask/deny-allwhen stricter, so it never widens), with rulesautoDenyon every tool kind except read, search, fetch and think.readOnlyplanmodecodex app-serverbridge)read-onlymodeconfig.rsMODES: sandboxread-only(networkAccess: false), approvalson-request, so an escalation asks and the rules refuse itread-onlyis on-request plus workspace-write: it edits and runs commands without asking (plans/cmux-next/acp-remote-guard.md)buildnever asks;planstill runs bash and user config can allow everything (acp-remote-guard.md); a refused family inweb_modes.rswebAskingModesentrypolicydata.reason: btw.no_read_only_mode, "this agent has no read-only mode"Order: the Codex bridge (by its command line), then refused families, then Claude, then the asking table. A profile that runs opencode or codex-acp under
"family": "claude"therefore lands in the refused set and is refused, never put in opencode'splan. While such a profile is configured, Claude's own/btwis refused too; that fails closed, and only the user's own config can cause it.Context wrapper: the transcript is wrapped in
<cmux-btw-context>, which says the content is untrusted context, not instructions. Every closing tag inside the chat's text is made inert (\/), in any case and with whitespace around the slash or before>, so it cannot close the wrapper early.Limits:
session/set_mode(turns.rsset_mode), not a separate readback from the agent.set_modeas non-fatal for ACP harnesses (existing behavior).permissions.allowstill applies in plan mode, and Codex MCP tools run outside its sandbox.Model: param, else config.json
btw.model.<profile or family>, elsehaikufor Claude, the smallest advertised model for Codex (nano/mini/flash/lite/small/fast by id or name), else the harness default. A refused default falls back to the harness default; a refused explicit model is an error.The request answers once the question is recorded as the side session's first prompt, never waiting for the answer. The chat is never paused, cancelled or modified (its fork records a
fork_childevent, as_acpmux/askdoes today).Errors: empty or missing question, a non-string model, an agent with no read-only mode (
btw.no_read_only_mode) and a mode that could not be set (btw.mode_unset) are invalid params; an unknown chat is not found.Code:
hub/btw.rs(logic),config/btw.rs(config section), wired inserver/requests/composer.rs, schema indocs/acpmux-schema.json. The pane relay allowlist is not in this PR: the Swift PR adds_acpmux/btwtoAcpmuxPaneMethods, and the web PR carriesdirect.ts's call with the Rustpolicy.jsonmirror (the TypeScript parity test ties the two).Remote and relay authorization
_acpmux/btwcreates a session and starts an agent process on this machine with a read-approving policy.remote_guard.rsrefuses it forOrigin::WebandOrigin::Peer(data.reason: btw.remote), whatever the chat. A chat that lives on a peer is forwarded there bypeer_forward.rsand refused by that peer as a Peer request, so no remote caller creates a session it does not own. Local unix socket and the app's pane (LocalApp) may open it.trust_gate.rsgates_acpmux/btwlikesession/fork, so the pane cannot start an agent in a folder without a trust answer.always(it runs an agent turn, like_acpmux/ask), params exactlysessionId,question,model; no command-bearing params.Verification
Behavior tests, committed before the fix (
faa9aa7b5ba9):hub_integration/btw.rs: a Claude side session opened mid-turn (title,sidetag,approve-reads,planmode,--model haikuin the side's argv, its conversation holds only the finished turn plus the question; the chat's turn ends normally and its title/policy/rules/tags/modes/models and Claude conversation are unchanged; empty/missing question and unknown chat open nothing); a fake ACP harness (no fork-through) gets the transcript as hidden context and a write tool call is refused (chose no); model resolution param > config > harness default.remote_guard.rs: refused for Web and Peer (btw.remote, nothing created), opened for LocalApp and Local.Focused command for both:
./scripts/verify-cmux-tui-hosted.sh --filter a_side_sessionfaa9aa7b5ba9(run 38661273200): the 3 hub tests fail (_acpmux/btwfalls to the harness catch-all: "no such method" / "a harness method acpmux does not handle")45f0c8e5359f(run 38675282204): Linux and macOS test jobs, lint and MSRV passReview round (read-only fails closed), tests committed before the fix (
c5e20ccf8624):a_codex_side_session_runs_sandboxed_read_only_and_the_adapter_is_refused: a Codex bridge chat's side session runs inread-only(readOnly: "mode"); a codex-acp adapter chat is refused with "no read-only mode" and nothing opens.a_side_session_is_refused_for_an_agent_with_no_read_only_or_asking_mode: a fake harness whose modes never ask is refused, nothing opens.a_side_session_whose_mode_cannot_be_set_is_discarded_and_the_chat_is_untouched:session/set_modefails, the side session is gone, the error names the read-only mode, and the chat's title, policy, rules, tags, modes, status and prompts are unchanged.a_side_session_of_a_harness_that_cannot_fork_reads_the_chat_and_never_writesnow has a chat prompt containing</cmux-btw-context>: the wrapper says "untrusted", holds exactly one closing tag, and ends with it.Focused command:
./scripts/verify-cmux-tui-hosted.sh --filter side_sessionc5e20ccf8624(run 38679897178): those 4 fail (the three refusals returnreadOnly: "policy"instead of an error; the wrapper lacks "untrusted"); the Claude and model-order tests pass0ff95a5ca11b(run 38682575038): all 7side_sessiontests executed and pass on the Linux and macOS test jobs; lint and MSRV passReview round 3 (refused families before Claude; inert closing tags), tests committed before the fix (
e9296199683f):a_side_session_is_refused_for_opencode_run_under_the_claude_family: a profile{"argv": [..., "opencode", "acp"], "family": "claude"}whose agent offersbuildandplanis refused with "no read-only mode", nothing opens, the chat's mode unchanged.</CMUX-BTW-CONTEXT >and</ cmux-btw-context\t>: lowercased and without whitespace, the side's prompt holds exactly one closing tag, and each variant is present in its inert form.Focused command:
./scripts/verify-cmux-tui-hosted.sh --filter side_sessione9296199683f(run 38692208988): those 2 fail (the opencode profile opened withreadOnly: "mode"onhaiku; the variant tags closed the wrapper); the other 5 passacab89e1c149(run 38694363932): all 8side_sessiontests executed and pass on the Linux and macOS test jobs; lint and MSRV pass. Then merged feat-cmux-next628debcd7a8b(conflict inconfig.rs: both thebtwandtitlessections kept)Also run locally:
crash_ratchet.pyok; god-file check on touched files ok. No local cargo; compile and tests only on the hosted runs above. No full hosted run (--full, Windows) yet.Gating on the Labs flag lives in the web PR; this request is inert until the page calls it.
Changelog
Added:
/btwside sessions in cmux NEXT: a read-only chat about the current one, on a cheap model, opened beside it.🤖 Generated with Claude Code