cmux-next: agent question card like the Claude app's (Needs your input pill, ⌘↩, Esc returns to the composer)
Summary
When an agent asks a question (Claude Code's AskUserQuestion, Codex user input, Grok or Cursor asks), the cmux-next agent pane now shows it the way the Claude app does, as Leo asked on 2026-10-19:
- An amber Needs your input pill sits above the card. Its chevron folds the card away and back, and the person's picks survive the fold.
- The question is the card's header strip. Options are radio rows (checkbox rows for multi-select), each with a bold label and a muted multi-line description, divided by hairlines. Other turns into an inline text field.
- Skip and Submit sit at the bottom right. Submit shows a ⌘↩ hint.
- A chat waiting on the person shows an amber Needs input label in the session sidebar, beside the existing glyph.
Keyboard:
- The card takes the keyboard when it arrives if nothing else has it or the composer is empty, so no click is needed first. A half-written prompt keeps the keyboard.
- ↑/↓ move between options, Enter picks, and 1–9 choose directly.
- ⌘↩ submits. With nothing chosen yet, it answers the highlighted row the way Enter does.
- Esc gives the keyboard back to the composer and never answers. Inside the Other field, Esc only leaves the field. Skip declines the ask.
- One click on the last single-choice question sends it at once, as in Codex and Claude Code.
- If acpmux refuses a reply, the card shows the error and its controls work again for a retry.
No new path: this reworks the existing QuestionCard in the agent pane. acpmux already normalizes every harness's ask into a permission with _meta.acpmux.question. The reply still goes chat.permission → _acpmux/permission_respond → agent, unchanged.
The native AgentQuestionCardView (CmuxNextAgentQuestion) is still unused. Home has no question producer or custom-row seam yet (plans/cmux-next/agent-questions.md §5–6), and agent tabs render through this web pane.
Skip declining follows the plan's §7 decision: a hidden pending ask blocks the turn. Esc does not decline; plans/cmux-next/agent-questions.md §6 and §7 now record that, the refused-reply retry and the single-click submit.
Testing
webviews/test/agent-question-card.test.ts(new) drives the agent pane preview in Chromium on the sharedpending-singlefixture. It checks four things:- the pill and the header;
- fold and unfold keep the pick;
- ↓ then ⌘↩ sends
chat.permission {permissionId, optionId: "allow_once", answers: {"Which auth method should the API use?": "API keys"}}; - Esc sends nothing and gives the keyboard to the composer, Skip sends the
reject_oncedecline, and an Other answer typed after a digit key is sent with ⌘↩.
- Red/green with
CMUX_BROWSER_TESTS=1 bun test test/agent-question-card.test.ts:- red at 9bfc6bb800e: 0 pass / 4 fail;
- green at 2b0ad35db75: 4 pass.
- The agent end of the round trip:
cargo test -p acpmux --test hub_integration questionspassed 4/4 on this branch.questions_incomplete_answers_are_refused_and_the_ask_staysanswers with the sameanswersshape and asserts the fake agent received{"Which one?": "A"}. tsc --noEmit,vp fmt --checkandvp lintpass on the changed files.check:type-scale,check:layers,check:system-blueandcheck:text-focuspass, and the type-scale baseline is lowered by 9 because the card now uses--text-*tokens. These suites also pass: ui-rules, hairline-rims, theme-colors, stylesheets, session-rail, tooltip-rules, row-focus, selected-row-accent and hover-row-sweep.- The whole
bun testrun on this Linux VM has failures in unrelated files whether or not this change is applied. They pass when those files run alone, so they come from cross-file pollution in the single-process run. - Screenshots: from the
agent-pane.question-cardgallery entry, captured by the Linux gallery advisory run (run 39341076275, head1425c5dcc1fd) in the app's real theme (Apple System Colors, dark and light), so the pill shows its amber. Not verified: a capture in a tagged macOS build. The fleet build of2b0ad35passed (cmux-ci job80496fca94e0e6a1d048bbe3, tagqcard), but no capture mini was usable. Whether the ⌘↩ answer reaches the agent in the app is also not checked live; the acpmux hub question tests cover that reply path.
Localization: one new key, question.needsInput ("Needs your input"), in all 21 agent-pane locales (needs_review, the same state as the card's other strings). generated/strings.json is regenerated, and gen-strings --check agentPane passes. The session row reuses the existing sidebar.markInput.
Fixup (agent, per Leo)
Leo decided that Esc must not decline. Four commits on top of 3ab5658d2999:
- Esc on the card sends nothing. Outside the Other field it gives the keyboard to the composer and keeps the answers so far. The card does not claim the key, so the pane's own listeners still see it. The exception is the floating chat, where an Esc nobody claimed would hide the panel, so the card claims it there. Skip is the only decline.
- A refused
chat.permissionno longer locks the card. The card showsquestion.replyFailed("Couldn't send your answer. Try again.", in all 21 locales,needs_review) with the host's reason, then takes input again.gen-strings --check agentPanepasses. - One click on the last single-choice question still submits at once, and a test now pins it.
plans/cmux-next/agent-questions.md§6 and §7 record the three rules above.
Red/green with CMUX_BROWSER_TESTS=1 bun test test/agent-question-card.test.ts:
- red at
f0d0d47ce3d5: 8 pass / 2 fail (Esc answered and was claimed; a refused reply left the card locked). Green at05985df7bbca: 10 pass. - red at
1c176bf49c95(floating-chat Esc, from the review): 1 fail. Green at851cc5b1f9ce: 11 pass.
Observed / Not run
- Observed: the browser test above on Linux Chromium;
prepush-gate.pyhas no new violations;scripts/web-lint.shfails only on files this PR does not touch (settings and markdown formatting, findPill.css layers, Swift text-focus), the same as on the branch before. - Not run: a tagged macOS build, so the floating chat and a live acpmux refusal are not checked in the app.
Changelog
Changed: An agent's question in the agent pane now shows a "Needs your input" pill over a radio-option card; ⌘↩ submits, Esc returns to the composer, and a refused answer can be retried
Proof
Gallery advisory captures (Linux Chromium, agent-pane.question-card, width 420):
| Dark | Light |
|---|---|
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Checklist
- Behavior changes have added or updated tests, or Testing says why not
- UI, settings, menu, schema, help-text or user-facing docs change: localization audited, and the result is stated above
- Reviewed with a subagent before merge (cmux-review), and all bot and human review comments resolved
🤖 Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Reworks the agent pane's question card to match the Claude app's AskUserQuestion card: an amber "Needs your input" pill over a card with radio (or checkbox) option rows, an inline Other field, and Skip/Submit with a ⌘↩ hint. Esc no longer declines an ask — it returns focus to the composer and keeps the answers given so far (Skip is the only decline) — and a reply the host refuses now shows its error on the card and takes input again for a retry instead of locking the card.
- The card takes the keyboard on arrival when nothing has it or the composer is empty (the emptiness check reads the editor's text, so a restored draft keeps the keyboard); ↑/↓ and 1–9 pick options, ⌘↩ submits, and one click on the last single-choice question sends it.
- Esc is left to the pane's own listeners, except in the floating chat, where the card claims it so an unanswered Esc does not hide the panel.
- The pill's chevron folds the card and keeps picks and Other text; each ask is keyed so a new ask arrives unfolded, and a chat waiting on the person shows an amber "Needs input" label in the session sidebar.
- Adds
question.needsInputandquestion.replyFailedin all 21 agent-pane locales, a gallery entry rendering the card on the sharedCmuxAgentQuestionfixtures, and a Chromium test driving the pane preview and recording thechat.permissioncall it hands the host.





