fix(cx-gakp): make permission cards honest per harness

Merged
TE
22d ago
+1254-203

Summary

  • present a quiet, wrapped permission card with an expandable full command and human action label
  • preserve harness supplied allow_once, allow_always, and reject_once wording and show the exact persistent rule suggestion
  • focus the composer after the No / feedback action and keep shortcuts subtle

Changelog

Fixed: permission cards now match Claude and Codex approval semantics without clipping commands or using the old heavy pill treatment.

Proof

Gallery entry: agent-pane.bottom-stack

The gallery play checks trust-before-permission sequencing, one card, action labels, and stack geometry. The live gallery host was unavailable from this checkout while opening the PR; CI can render the same entry at the pushed head.

Option layout: variants and pick (head d6c67fd4)

A first in-app recording (2ffcc46a) showed equal-height tiles: Claude's 3-line "No" stretched "Yes" into an 80 px black block, and the steer button clipped as "Interr…". Three layouts, rendered from the bottom-stack gallery on the Linux builder (Chromium, DejaVu fonts; 420 light, 420 dark, 360 light):

A: numbered rows (picked)B: inline buttonsC: rows, solid primary

Pick A: it mirrors Claude Code's own prompt; the numbers are real (1-9 already answer while the card has focus); 28 px rows (41 px with the rule line) instead of 77 px tiles; a 12% accent fill reads the same in light and dark; the full rule stays readable. B strands the long No on its own line; C's solid bar is heavy again and cuts the rule at 360 px.
Interrupt: .acpmux-send{width:32px} beat the steer button's width:auto; it now sizes to its label and the Access/model chips shrink instead (whole at 300/360/420/600 px; test composer-menu-width "Interrupt shows whole…" red then green).
Claude's "don't ask again" persistence landed in #20591 (which superseded #20407) with the rule kept for the session only, so the Claude labels now say "this session". The in-app recording below is of that head.

In-app before/after

Real cmux NEXT DEV builds on a fleet capture mini, --showcase mock daemon, light appearance. Script: open the Claude chat that is waiting on a permission, expand the command, move the pointer over the options, click the No option, type into the composer, then switch to the Codex chat that is waiting on a permission and open the Access menu. The mini's host name is masked.

  • Before: feat-cmux-next b179607f (b179607f82c2, the PR's merge base; fleet job 03687e9b4c8c, mac builder)
  • After: PR head 2386acf8 (2386acf88a14; fleet job 40c1584eea31, mac builder). Its agent-pane pane.js was checked for this head's strings ("Yes, and don't ask again this session", "Granted access") before recording.

permission cards before/after

Full quality: · ·

Claude permission ask. Before: "Allow once (⌥⌘1) / Always allow / Deny (⌥⌘3)" black pills. After: numbered compact rows "1 Yes / 2 Yes, and don't ask again this session / 3 No, tell Claude what to do differently", the command in a block you can expand, and the Interrupt button shown whole.

Claude card before/after

Codex permission ask. After: the same numbered rows with the names the harness sent. The showcase mock sends "Allow / Always allow / Deny", so that is what shows.

Codex card before/after

After the No option, typed text goes into the composer on both sides:

after No, typing

Access menu on the Codex chat. After: a "Granted access" heading with the folder-trust grant and its remove button. Before: no grants section.

Access menu before/after

Command expanded (after): expanded

Seen in the recording:

  • After: "Interrupt" is shown whole next to its arrow on both chats. Before, it also showed whole, but the mode and trust chips overlapped ("Trusted this chat's…" over the lock). After, they do not overlap.
  • After: option rows are compact and numbered 1-3; "Yes" has a light grey fill. The 80 px black "Yes" block from the earlier recording is gone.
  • After: the Claude card says "Yes, and don't ask again this session".
  • After: while a turn runs, the effort chip next to the model icon shrinks to "M…" (Claude) and "H" (Codex) to make room for Interrupt. It shows "Medium" again once the turn ends.
  • After: with the command expanded, it shows three times: in the title ("Run bun run lint:ci"), in the summary line and in the expanded block.
  • After: the Codex card opens with its Deny row highlighted and its ⌥⌘3 hint shown. This looks like focus carried over from clicking No on the Claude card.

UNVERIFIED in app:

  • Claude's rule line ("Allow … for this session"). The mock sends no suggestions, so no rule line appears.
  • Codex's own option names. Real codex-acp names were not tested; the mock's generic names are shown.
  • Shortcut hints on pointer hover. The synthetic pointer (debug.mouse move) caused no hover state in the page.
  • Trust before permission ordering. The showcase mock has no folder-trust ask.

Capture note: the first after run hit the DEBUG main-thread watchdog inside the capture tool (debug.window_record creating a display link) and stopped before the Access menu. The run above is a clean rerun of the same build.


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


Summary by cubic

Rebuilds the permission flow so folder trust and chat-wide grants become per-grant records in the composer's Access menu, and the transcript shows only pending asks.

  • Claude suggestions and rule metadata map into localized labels (21 locales); "Yes" and "Yes, and don't ask again this session" apply only to single-option asks and state the session scope, while Codex keeps its own ACP names.
  • Only reject_* kinds deny: custom and kind-less options keep their agent-supplied names and get no deny shortcut.
  • The Access menu mounts even when only grants exist, under a "Granted access" heading, so folder trust and chat allow stay revocable.
  • Shows the full command in a click-to-expand block, adds per-option rule descriptions, and keeps action labels accessible via aria-label.
  • Renders options as Claude Code's numbered prompt (1-9 number keys, quiet fill on Allow once) with per-row height, and replaces the permission status trigger with the Access menu's compact grant count.
  • Makes the running action an explicit steer button sized to its label, reserves queue space below the stack, and scrolls a tall asks stack within a short pane.

Additional changes in this branch

  • Agent pane can stop a running Claude subagent or workflow via _acpmux/subagent_stop; sessions record their folder's git branch and HEAD commit at start.
  • Build identity moves to the cmux-build-identity leaf crate so source edits don't recompile cmux-remote.
  • ACPMUX_AGENT_LAUNCH=deny or CMUX_AGENTS_DISABLED=1 disables agents with a localized Home notice.
  • Nightly recovery initializes the ghostty-next submodule before resuming accepted submissions.