Skip to content

cmux NEXT: Allow once / Deny on agent chat approval banners (cx-kz6m0.2) - #20972

Merged
teamleaderleo merged 8 commits into
feat-cmux-nextfrom
feat-cmux-next-approval-actions
Oct 15, 2026
Merged

teamleaderleo merged 8 commits into
feat-cmux-nextfrom
feat-cmux-next-approval-actions

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

An agent chat that waits on a tool permission now gets a banner with Allow once and Deny (destructive), as classic's feed permission banners have. The banner body says what the agent asks to do (the tool call's title and command, secrets scrubbed) instead of the chat's last message. Parity gap row 2 of cmuxterm-hq#2099, bead cx-kz6m0.2.

How it answers:

  • When an agent chat's "needs input" notification arrives for a tab whose session runs on this Mac's acpmux, the app reads the chat's pending prompts (_acpmux/info) on a short connection proven as the person (the same initialize + _acpmux/person_prove handshake the feed bridge uses). The category is attached only when exactly one tool permission is pending and it offers allow_once and a reject option. Questions, several prompts, terminal tabs, and chats on another Mac keep the plain banner.
  • Allow once needs the Mac unlocked (.authenticationRequired). Each button re-reads the prompt and answers with _acpmux/permission_respond only while the same request (sha256, as the feed bridge checks) is pending. The decision-to-option mapping is now one function shared with AcpmuxPermissionFeedBridge. A prompt that ended or changed answers nothing and opens the tab.
  • Only banners this launch posted answer; one left in Notification Center from an earlier launch does nothing. A newer banner on the same tab retires the older one's buttons, so the app keeps at most one approval entry per tab.
  • The banner waits for the prompt read before it posts. If the tab was read meanwhile (focused, or answered in the chat) or a newer notification of it arrived, it posts nothing.
  • Where macOS hides previews (locked screen, Show Previews off), the approval category shows "An agent is waiting for your approval" instead of the command.
  • debug.notifications gains allow / deny actions and an approval field per banner, for dogfooding without clicking.

Not covered: terminal agents' hook approvals ("Claude needs approval", body "Bash"). NEXT's hooks only notify and nothing blocks on an answer, so there is no prompt to answer from a banner. That needs a blocking hook path in cmux-tui and is left for a follow-up.

Where the code lives: CmuxNextApp is size-capped (code-quality.md SW14), so the answerer, the person-proven acpmux line, the banner bookkeeping and category, and the prompt helpers the feed bridge shares (digest, request frame, shown command, option mapping), plus FeedSecretScrubber, are in CmuxNextAgentPane/Approvals/. The prompt helpers and the scrubber stay internal there; the feed bridge reaches them through extensions on the request dictionary, the offered options, String and Data (package-conventions-lint refuses all-static public enums). That target already holds the acpmux connection types. CmuxNextApp keeps the wiring in NotificationCenterService+Approvals.swift and DesktopNotifier, and ends 57 Swift lines smaller than its base. Package.swift is unchanged.

Verification

Fleet scripts/ci/run-swift-testing-suites.sh Packages/macOS/CmuxNext, focused on ApprovalBannerTests (boundary test: a daemon notification goes through NotificationCenterService.deliver, the test reads desktop.posted and the answers a stand-in answerer got):

  • Red, test plus empty seams at 8d46d0b (step bdb2f4b8c04b83008f424056): Suite ApprovalBannerTests failed after 0.013 seconds with 5 issues (no approval category, body is the last message, buttons answer nothing).
  • Green at 04c964e (step 32ce136bb3b15fb50135bb21): Suite ApprovalBannerTests passed after 0.011 seconds.
  • Green again at 04c964e (step 89d253e03c2487a618cd4920): Suite ApprovalBannerTests passed after 0.009 seconds, and every suite in the run passed.
  • Other suite failures in those runs (RapidWorkspaceCreateTests, ThemeEditsTests, ThemeLivePreviewTests, SidebarInsertWhileDisplayAsleepTests, BranchStateResourcesTests) differ between runs and touch none of these files.
  • Release compile scripts/cmux-next/check-release-compile.sh at 04c964e (step d18f6ef576562df80edd4be2): ** BUILD SUCCEEDED **.
  • check-concurrency, crash_ratchet, check-no-godfiles (--base), check-state-roots, check-system-blue, check-test-notification-centers, check-l10n: ok.

Review fixups, red then green with ApprovalBannerTests (three new cases: a read outlived by an acknowledge, a newer ask during a read, an older banner's buttons):

  • Red at 2f5efe1 (step bdf6fe00da27516b606efdb7): Suite ApprovalBannerTests failed after 0.018 seconds with 3 issues.
  • Green at f2721f4 (steps b81a6584555bdf1fe4b19278 and 2aa254edd299e889e5a75107): Suite ApprovalBannerTests passed, and every suite in both runs passed.
  • Release compile at f2721f4 (step f3f8e6ed209223e6ea562a86): ** BUILD SUCCEEDED **.

After the move, at b893776 (check-app-size.sh: ok, 113672 lines, 20 under the cap 113692):

  • Green twice (steps d8d59294a32b40474a2a6a86 and 1162006d98b89753bb52b53c): Suite ApprovalBannerTests passed. Other failures in those runs: SidebarInsertWhileDisplayAsleepTests (also red before this change), and one CloudBringBackTests or CloudMoveTests case per run, each a different test timing out on the same worker.
  • Release compile (step 8cc76f09baeae2769c048f33): ** BUILD SUCCEEDED **.
  • Pre-push checks, check-layers, check-module-resources and the freeze check pass.

After the lint fix, at ca386d5 (check-app-size.sh: ok, 113672 lines; lint-ios-package-conventions.sh: no ERROR lines):

  • Green (step de9ad588c557d96a73ae3ff6): Suite ApprovalBannerTests passed, and every suite in the run passed.
  • Release compile (step 160255c8408dcc48f8b513e3): ** BUILD SUCCEEDED **.

After merging feat-cmux-next with #20960 (finished-banner bursts), at f99c1fe (check-app-size.sh .: ok, 113665 lines, 70 under the cap 113735). Approvals are warnings and never join a burst; an acknowledge clears both:

  • Green (step b35665aa19b42530cb8eeb8f): Suite ApprovalBannerTests passed and Suite FinishedBannerBurstTests passed. ClassicShortcutsUpgradeTests and SettingsSchemaExportTests failed in that run; this branch touches no settings, shortcut or schema files.
  • Release compile (step 39adbfbaf193cdaaa11ebe18): ** BUILD SUCCEEDED **.

Not verified live: no capture of the banner's buttons on a real Mac yet; the acpmux connection path is exercised only by Release compile, not by a running acpmux.

Localization: three new strings (notifications.approval.allowOnce, notifications.approval.deny, notifications.approval.hiddenBody) with all 21 locales in CmuxNextApp's Localizable.xcstrings.

Base reds in cmux-next checks, not this change and not gating (coordinator): Motion tokens (ColorTransition.swift), Type scale (BrowserFitFrameView.swift), Package conventions lint (BuiltInAdBlockerSetting.swift). The compile queue check is stuck and does not gate; the fleet Release compile above covers it.

Changelog

  • Added: Agent chat approval notifications in cmux NEXT have Allow once and Deny buttons and say what the agent asks to run.

🤖 Generated with Claude Code

teamleaderleo and others added 3 commits October 15, 2026 00:44
Red: the banner posts without the approval category and its buttons answer
nothing yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…banner test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An agent chat on this Mac that waits on one tool permission now gets a
banner that says what it asks to do (tool title and command, scrubbed),
with Allow once and Deny (destructive). Each button re-reads the prompt
over a person-proven acpmux connection and answers it with
_acpmux/permission_respond only while the same request is pending; the
decision-to-option mapping is shared with the feed bridge. A prompt
that ended or changed opens the tab instead. debug.notifications gains
allow/deny actions and an approval field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 15, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot 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.

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 7238c39b-280c-4aa4-afe3-309b34bae307

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

all web/size checks green

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Not verified: macOS jobs did not run: compile and app tests were skipped on f99c1fe37a; ci-status is green without them.

CI passes on f99c1fe37a (run 38738233773 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

@blacksmith-sh

blacksmith-sh Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Found 1 test failure on Blacksmith runners:

Failure

Test View Logs
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

teamleaderleo and others added 5 commits October 15, 2026 02:30
Red: a banner whose prompt read outlived an acknowledge or a newer ask
still posts, and an older approval banner still answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uttons

A banner whose prompt read outlived an acknowledge or a newer
notification of its tab no longer posts; a newer banner on a tab retires
the older approval banner's buttons, so the map holds one entry per tab.
The approval category hides the command where macOS hides previews.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CmuxNextApp is size-capped (SW14). The answerer, the person-proven
acpmux line, the banner bookkeeping and category, the prompt helpers the
feed bridge shares (digest, frame, shown command, option mapping), and
FeedSecretScrubber move to CmuxNextAgentPane, which already holds the
acpmux connection types. CmuxNextApp keeps only the wiring and ends 57
lines smaller than its base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…public namespaces

package-conventions-lint refuses all-static public enums. The prompt
helpers and FeedSecretScrubber stay internal to CmuxNextAgentPane; the
app reaches them through extensions on the request dictionary, offered
options, String and Data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tions

Keeps #20960's finished-banner bursts beside the approval wiring: an
acknowledge reads both, and approvals (warnings) never join a burst.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Current head is green on the portable/static checks, but the target branch skipped the macOS compile and app/UI checks. I’m keeping this open until an exact-head macOS build exercises the approval banner in a narrow pane, including stale-prompt retirement, Allow once, Deny, reconnect, and focus restoration.

@teamleaderleo
teamleaderleo merged commit d5292fb into feat-cmux-next Oct 15, 2026
101 of 102 checks passed
@teamleaderleo
teamleaderleo deleted the feat-cmux-next-approval-actions branch October 15, 2026 14:58
@github-actions

Copy link
Copy Markdown
Contributor

cmux-next tour of f99c1fe37a5e37f69143432b9567b120bcbf885b (run)

The tour job failed before the tour ran (build or setup); the run log has the output.

The tour produced no media.

Tours are data in scripts/cmux-next/media/tours: add a step or a tour file to show your feature.

teamleaderleo added a commit that referenced this pull request Oct 15, 2026
… hq-1d) (#20933)

* test(agent pane): a chat's tools share the tab row behind chrome.tabs.rowTools

With the row offered (tabRow {height, trailingInset}), the chat header is the
row's height, ends its tools at the inset and reports their width with
pane.tabRowTools; without an offer the header is its own row and reports
nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(pane): chat tools in the tab row, a Debug toggle (no top bar)

Leo 2026-10-14, "why is there even a top bar". With the Debug setting
chrome.tabs.rowTools on (off by default), a chat pane's tab strip gives up
the width of the chat header's tools at its trailing end, left of the
pane's "...", and the chat page covers the strip's row: the strip and "..."
stay above it, and the page draws only its tools there, at the row's
height, with a short fade under the row. Off, nothing changes.

- CmuxNextDesign: PaneTabRowSharing, ChromeTunables.tabRowTools.
- PaneContentView+TabRow: the narrower strip frame, the row offered to
  the content, the agent name tag kept below the row.
- Agent pane: pane.tabRowTools {width} (decoded 0...2000, in
  AgentPageOps), the tabRow event (replayed on the page host, offered
  again after a page load), AgentPaneTabRow on the model. The side chat
  helper moves to AgentPaneSideChat to keep AgentPaneModel under 1000
  lines.
- Page: header/tabRow.ts; the header's data-tab-row mode; the summary card
  under the row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: import the tab row's modules publicly for AgentPaneView's conformance

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 6514bc7

- regenerated schemas/defaults/cmux-next.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38698999038

* fix: import AppKit publicly for the tab row conformance's CGFloat

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: track the tab row tools without state, so a relaunched chat keeps its draft

useTabRowTools kept the tools element in state; setting it on mount re-rendered
the pane while its first snapshot loaded, and a relaunched new chat lost its
kept draft (agent-pane-unsent-draft, red on fad20e4, green here).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor: keep the tab row's app code under the CmuxNextApp size cap

The zoom and setting checks move to AgentPaneTabRow; AgentPaneView's
conformance only forwards to it. The pane sets the content's tools callback
with each offer instead of following and unfollowing views, and the agent
tag's offset is inline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor: move the tab row's pane logic to CmuxNextDesign

Base grew to 3 lines under the CmuxNextApp cap. The strip's share of the row
and the offer move to a PaneTabRowSharing extension, AgentPaneView's witnesses
to a constrained extension in CmuxNextAgentPane, and the App keeps three
layout lines and the conformance on its existing Parking line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: a re-offered tab row gets the tools' width reported again

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: report the tab row tools' width for every offered row

A page that said ready again without reloading lost its width in the App,
and the re-offered identical row was deduped, so the tabs ran under the
tools. Also keep the agent tag's offset unchanged with the setting off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux + cmux-coderouter: the local router on Windows, owned by a kill-on-close job (cx-ncc.54)

The router's owner is one RouterOwner trait (daemon/router_owner.rs) with
one impl per platform, so daemon.rs only calls ensure_router at start and
release_router at shutdown:
- Unix (router_owner/fifo.rs): the cx-1rvy owner FIFO, moved unchanged out
  of daemon.rs (daemon.rs 1172 -> 949 lines).
- Windows (router_owner/job_object.rs): `router serve` starts suspended
  through CreateProcessW with a handle list (daemon/windows.rs spawn_this,
  shared with the daemon start) and resumes in a new kill-on-close job
  (crate::job::Job) the daemon holds, so Windows ends the router when the
  daemon ends, also on taskkill /F or a crash. A router that already
  answers belongs to no live daemon and is told to stop. Shutdown asks the
  router to stop, then closes the job.

cmux-coderouter: serve_with_admin(home, bind_admin) runs the router with an
admin listener its host binds (after router.lock is held and a stale socket
removed); admin_connection reads and writes any async halves. Unix `serve`
binds its unix socket (0600) through it.
The free tier's attest_provider peer check (18bae8e, cx-ds09i) needs the
whole socket, which the split halves no longer give: each accepted admin
connection now carries a free_tier::ProviderPeer read before the split (unix:
peer_cred uid and, on macOS, the audit token; cheap). provider_peer_allowed
takes it and runs the same checks (same user; on a Team-signed macOS build the
signed containing app) only when the connection asks to be the provider. Only
ProviderPeer::of_unix can pass; the Windows admin socket passes
ProviderPeer::refused, so Windows refuses attest_provider (providers are mac
and ios only). Unit tests: a refused peer never passes; a same-user socket
pair read at accept passes (Linux). The owner FIFO watch stays Unix;
on Windows `adopt` answers adopted false (no owner FIFO).

acpmux on Windows: `router serve` makes the router folder owner-only and
binds the admin socket as a cmux::local_socket listener with the daemon's
peer checks (server/windows_socket.rs router_admin); router_socket.rs
connects with connect_same_user.

Behavior tests (tests/windows_daemon.rs): `acpmux router serve` answers
status (build, pid, port) in an owner-only folder and exits 0 after
shutdown; a daemon started with ACPMUX_LOCAL_ROUTER=1 starts the router
with its build, and taskkill /F of the daemon's exact pid ends the router
within 1 s.

A router that already answers at Windows daemon start: the macOS rule
(cx-1rvy, kept after 03725dc) is confirmed, so the router is STOPPED,
not adopted. macOS adopts only a router that answers `adopted`, which needs
a live owner (a kept agent host holding the owner FIFO); a router with no
owner (no FIFO, or `stopping`) and no kept agent is told to `shutdown` and a
new one starts (it waits on router.lock). On Windows only the daemon owns
the router (its kill-on-close job) and daemon.lock allows one daemon, so a
router that answers at start has no live owner: same rule, stop it.
03725dc itself changes only the app's lease (it sets the upstream again
when the relay pid changes), so a replaced router gets its bearer again.
Release ignores `stop` on Windows (no kept agent hosts): accepted for now,
follow-up cx-xs01y (a shared job object if agent hosts come to Windows).

* acpmux: the Windows router admin socket binds again for up to 1 s (cx-ncc.54)

Review finding: a router of another build that just released router.lock
may still hold the old socket file for a moment; the bind then failed
with no output (the router's output goes to NUL). Remove and bind again,
bounded at 1 s.

* acpmux: router_owner blank lines as rustfmt wants them (cx-ncc.54)

Base reds: exact base 062b82b (landing 6 head, cmux-tui full run 38530387510). The gate excludes a red job only when that run has the same job red with the same error lines; expected: "valgrind-leak-check (startup)" and "valgrind-leak-check" (red on feat-cmux-next 12b1e29, run 38357900702; valgrind 0 errors). This landing changes only cmux-tui/crates/acpmux and cmux-tui/crates/cmux-coderouter. Windows VM proof (gpuitest, router e5cc1d8, same acpmux and cmux-coderouter code apart from tip changes in hub/ and tests): windows_daemon 11 passed, 0 failed, incl. a_killed_daemon_takes_its_router_with_it.

* acpmux: the Windows start tests daemon.lock with a read, not a lock (cx-ncc.54)

wait_for_winner (daemon/windows.rs) waits while another start's daemon
holds daemon.lock and has not bound its socket yet. It tested the lock
with File::try_lock, which took the lock for an instant when it was free:
a third start's daemon that tried the lock in that instant exited too.

Now a 1-byte read at offset 0 on a read-only handle tests the lock and
never takes it. ERROR_LOCK_VIOLATION (33) means another handle holds the
lock (LockFileEx over the whole range, as File::try_lock takes it); a read
or ERROR_HANDLE_EOF (38, std returns it as Ok(0)) means the lock is free.
Proven on the Windows VM (Windows Server 2022, NTFS, as gpuitest, folder
C:\build-wb\acpwin-probe, /tmp/acpwin/l4/probe/lock-probe.ps1): with a
holder in another process, an empty and a 1-byte daemon.lock both gave 33;
free, both gave 38. The outdated RISK comment is replaced.

* acpmux + cmux-coderouter: router review fixes (hq-84 P3, cx-ncc.54)

hq-84 approved the per-connection peer decision (c12e44a) with two P3
follow-ups, both done here:
- free_tier::ProviderPeer is neither Clone nor Copy: a peer read for one
  connection cannot be handed to another (serve_with_admin is public).
- The Windows router admin bind retry (server/windows_socket.rs) removes the
  old socket file only after the same check bind_unix makes: another user's
  socket is refused and never removed, a socket that still answers (a
  stopping router) is left until it goes, a stale file of our user is
  removed.
Also admin_connection takes the AdminConnection as one value, so the
clippy::too_many_arguments allow is gone.

* ci: cmux-tui-rust-check tests run under cargo-nextest; a failed test reruns once alone and passes as FLAKY (named), or stays red

cargo test ran the 361 test binaries one after another: 765 s of tests on rbx,
998 s on cmux9. nextest runs them at once: 55-60 s on rbx at 32 threads.
The full run's reds were load reds (a different set every run, each green
alone; cx-mqvyw, cx-t9m9q). A lower thread cap did not remove them (16
threads: 1 flaky in 2 runs, 99-109 s; 8 threads: 190 s). So a failed test
runs once more alone after the full run (2 s on rbx): a test that fails alone
stays red; one that passes alone is printed as gate-notice: FLAKY with its
name. Doc tests are their own step. The 4 Linux load-red skips go away (the
rerun covers them); protocol_ir_is_current is a base red (cx-ngrbq).

* ci: cmux-tui-rust-check reports the macOS PTY peak of the full run (cx-leo45)

* ci: rust-check review fixes: the PTY sampler has no stdout and dies with the step; nextest extracts beside its final path; rerun_notes

* webviews tests: stop the Vite a vp dev server started (#21062)

* test: stopping a vp dev server stops the Vite it started

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: browser tests stop the Vite their vp dev server started

SIGTERM to vp left its Vite child running; every dev-server test leaked
about 1.1 GiB per run, and on react-apps-check the leaks starved the dev
server files after a failing menu-focus into timeouts. stopDevServer stops
vp's children by parent pid, then vp, and keeps the server in the test's
process group so the runner's timeout still reaches it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the dev server warm check covers tests that load pages

dev-server-stop starts and stops vp dev without a browser, so it has
nothing to warm. Also read pgrep's output safely when it is missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cmux NEXT: Allow once / Deny on agent chat approval banners (cx-kz6m0.2) (#20972)

* test(cx-kz6m0.2): agent chat approval banners offer Allow once and Deny

Red: the banner posts without the approval category and its buttons answer
nothing yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cx-kz6m0.2): import NotificationSource's module in the approval banner test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(cx-kz6m0.2): Allow once and Deny on agent chat approval banners

An agent chat on this Mac that waits on one tool permission now gets a
banner that says what it asks to do (tool title and command, scrubbed),
with Allow once and Deny (destructive). Each button re-reads the prompt
over a person-proven acpmux connection and answers it with
_acpmux/permission_respond only while the same request is pending; the
decision-to-option mapping is shared with the feed bridge. A prompt
that ended or changed opens the tab instead. debug.notifications gains
allow/deny actions and an approval field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cx-kz6m0.2): approval banners skip a read or superseded ask

Red: a banner whose prompt read outlived an acknowledge or a newer ask
still posts, and an older approval banner still answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cx-kz6m0.2): approval banners skip stale reads and retire older buttons

A banner whose prompt read outlived an acknowledge or a newer
notification of its tab no longer posts; a newer banner on a tab retires
the older approval banner's buttons, so the map holds one entry per tab.
The approval category hides the command where macOS hides previews.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(cx-kz6m0.2): approval banners live in CmuxNextAgentPane

CmuxNextApp is size-capped (SW14). The answerer, the person-proven
acpmux line, the banner bookkeeping and category, the prompt helpers the
feed bridge shares (digest, frame, shown command, option mapping), and
FeedSecretScrubber move to CmuxNextAgentPane, which already holds the
acpmux connection types. CmuxNextApp keeps only the wiring and ends 57
lines smaller than its base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(cx-kz6m0.2): prompt and scrubber helpers as extensions, not public namespaces

package-conventions-lint refuses all-static public enums. The prompt
helpers and FeedSecretScrubber stay internal to CmuxNextAgentPane; the
app reaches them through extensions on the request dictionary, offered
options, String and Data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* CmuxNext: reasoning menu drawn as a native menu (#20787)

* test: reasoning menu rows share one check gutter and Ultracode's note is one short line

The reasoning-claude gallery variant now offers Extra High on Opus, so its
menu shows Ultracode as its note promises.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CmuxNext: reasoning menu drawn as a native menu

Ultracode's two-line grey description becomes a short note beside its name
(Extra High + multi-agent, every locale), with the text as its tooltip. The
check moves into a fixed leading gutter shared by every row, so labels align
whether or not a row is checked, as in the access menu. Rows are one height;
section headers sit on the label column in the system's caption style; the
separator is inset to the rows with even spacing. The access menu's 1-4
numbers become secondary key-equivalent text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CmuxNext: reasoning menu rows and headers use logical padding for RTL

The asymmetric row padding and the header's gutter indent were physical
(left/right), so in Arabic the check gutter sat on the right while the
extra inset stayed on the left. Use padding-inline so both mirror.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: reasoning menu rows with Ultracode as its own On/Off section

The base now draws Ultracode as a switch section after the levels; the
one-line note sits on its On row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 8902b9d

- regenerated schemas/defaults/cmux-next.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38699733289

* CmuxNext: mark Ultracode's note tooltip as allowed (the menu may cut the note)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* cmux NEXT: /btw side sessions share one right pane; pane relay allows _acpmux/btw (#20826)

* test: side sessions share one right pane and the palette asks a side question

Behavior tests for /btw side sessions: _acpmux/btw goes out through the
pane relay like a prompt and its side session opens beside the chat;
later side sessions of a chat open as tabs in its one right pane; Ask a
Side Question… is a palette action that sends the page sideQuestion.
The tracker and view command are stubs here (old behavior), so these fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(cmux-next): /btw side sessions share one right pane; Ask a Side Question…

chat.sideChat (New side chat and /btw side sessions) opens a chat's later
side sessions as focused tabs in the pane its first one split off to the
right, instead of a new split each time (AgentSidePanes, one tracker per
chat session, opens one at a time like AgentSubagentPanes).

The pane relay allows _acpmux/btw (sessionId, question, model): session
scoped, gesture required like a prompt, and its reply's sessionId joins
the pane's scope so chat.sideChat may open it.

Ask a Side Question… (agentPane.sideQuestion) is a palette action for a
focused agent chat, with no default key; it sends the page sideQuestion,
which the page ignores while its side sessions gate is off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cmux-next): regenerate action exports for Ask a Side Question…

From scripts/measure/export-action-surfaces.sh (fleet light step) on 004a08b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: the pane relay refuses the inline side question _acpmux/ask

/btw now starts a side session (_acpmux/btw); the page no longer sends
_acpmux/ask and the Rust policy drops it (web PR). The relay still
passes it here, so this fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cmux-next): the pane relay no longer allows _acpmux/ask

The page sends _acpmux/btw for /btw now and the Rust pane policy drops
_acpmux/ask (web PR), so the Swift allowlist, its session scope, gesture
rule and params entry drop it too and the two lists stay mirrored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* revert(cmux-next): drop the Ask a Side Question… palette action for now

Review: it shows for every NEXT user and does nothing while the page's
side sessions gate is off, the default. It returns with the Labs flag
follow-up, gated by requires on labs.sideSessions. Removes its catalog
group, handler, strings, AgentPaneView.askSideQuestion (no other caller),
its test, and its action exports (back to the base copies; regenerated
on the fleet to confirm).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: a chat whose side tabs all closed keeps no side-pane tracker

AgentSidePanes keeps one tracker per chat session for good today, so
this fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cmux-next): drop side-pane trackers whose side tabs all closed

AgentSidePanes.chat drops idle trackers of other chats with no side tab
still open (read through the latest open's pane lookup), so the
registry does not keep one per chat that ever had a side session. The
test layout's pane lookup holds it weakly, as the app's does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(cmux-next): side-pane placement lives in CmuxNextAgentPane, not the App

The App size cap fails when a change adds lines to CmuxNextApp. The side
pane's single placement closure (AgentSidePanes.open(_:chat:pane:place:))
moves into CmuxNextAgentPane, and the App reuses openBeside, which now
focuses a side session it adds as a tab, so CmuxNextApp does not grow.
AcpmuxPaneMethods.swift stays at its 400-line budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cmux-next): /btw and Ask a Side Question… follow Debug Settings' Side sessions

Red first: with agent.sideSessions off (the default) the palette has no
Ask a Side Question… and the page's header state says side sessions are
off; with it on the action shows in a focused agent chat, the page is
told, turning it off hides the action again without reopening, and
running the action sends the page the sideQuestion command (prefill).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(cmux-next): Side sessions (/btw) Debug Settings toggle gates /btw and Ask a Side Question…

agent.sideSessions (Debug Settings > Pages, "Side sessions (/btw)", off
by default, DEV and NIGHTLY) replaces the page's side-question
experiment. The page reads it as sideSessions in the chat header state
(pane.tabState and the headerState push); the header watch observes the
toggle, so a change reaches open chats without reopening them.

Ask a Side Question… returns (from 161e75f) in the palette only,
no key, requiring the new sideSessions context fact, which
ActionRegistry.publishSideSessions() keeps in step with the toggle, so it
is hidden while the toggle is off. Its handler shares one loop with
Switch Model… and Search Files…, so CmuxNextApp does not grow; the
AgentPaneView and AgentPaneModel god types do not grow either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cmux-next): regenerate action exports for Ask a Side Question…

From export-action-surfaces.sh on fleet step 82e608fc0e5b3b3b3c3708a7
(light, aws-m4pro-9) at c8dbb22.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cmux-next): regenerate action exports on the merged head

The textual merge of action-surfaces.json went stale; regenerated by
export-action-surfaces.sh on fleet step 3384e925ec431e91b8a90d09 at
986fed6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cmux-next): name the sideSessions context fact for the control bridge

RegistryReachabilityTests runs every action with every named context fact;
the bridge's own name table lacked sideSessions, so Ask a Side Question…
stayed unavailable there (fleet suites at 47266ba).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cmux-next): ActionCatalog.xcstrings is base's plus Ask a Side Question…

The textual merge rewrote 27 of base's lines; this is base's catalog with
only action.agentPane.sideQuestion added (no key or value lost).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(cmux-next): regenerate action exports on the merged head

export-action-surfaces.sh on fleet step 715bded4f5ecea4562b53200 at
000d2fd; the textual merge of the exports went stale again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at c6ec002

- regenerated plans/cmux-next/action-surfaces.json
- regenerated plans/cmux-next/actions.md

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38724924351

* next: move Ask a Side Question… to a follow-up; keep the Side sessions toggle

The palette action, its context bit, catalog strings and action exports
leave this PR (plans/cmux-next exports are frozen). The Debug Settings
toggle and the header-state plumbing that gates /btw stay, with a header
test for the off and on states.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: safe-push <safe-push@cmux.local>
lawrencecchen added a commit that referenced this pull request Oct 16, 2026
…es its lines

#20972 (d5292fb) started a main-actor Task per acpmux line and decoded
every reply on the main actor (main-actor work ratchet, baseline 0). Each line
now decodes on the line's queue into an id and its result, and the replies
reach the main actor through MainActorLineBatch (at most one drain pending;
the close after the last line). MainActorLineBatch moves from
CmuxNextAgentActivity to CmuxNextCompat so CmuxNextAgentPane can use it
without a new package dependency; the AgentActivityLineConnection helper
stays in CmuxNextAgentActivity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant