fix(ios): show Mac handoff for Cloud requests the phone cannot answer (HQ-14)
HQ #6236 / brief 14 — Cloud requests should have an honest phone handoff
Starting ref: manaflow-ai/cmux@6c3fe3648ba4b44e7bf38c4982a665ec2d5d5712 (feat-cmux-next, 2026-11-03). Research and owner follow-up: HQ #6236, Cloud permission owner #30584, atomic VM mode owner #30588.
Why
The Cloud cloud-acpmux adapter emits _acpmux/permission_pending to the iPhone, yet cloud_acp.rs supports only read/prompt/cancel/detach; it refuses _acpmux/permission_respond and any question-answer call, deliberately avoiding transfer of Mac approval authority to a VM. The shared iPhone chat view nevertheless draws Face ID Allow, Deny, and Reply instead controls; questions have enabled Answer and Decline. A person away from their desk sees a usable-looking decision that the transport refuses.
This isolated change
- Explicit
AgentChatStore.canAnswerPendingRequests(defaults true for existing/local Mac chats; false only when binding a Cloud chat); any programmatic answer/decline andReply insteadgo through that guard. - The Cloud permission bar and transcript show Open this Cloud chat on your Mac to answer; Cloud questions show their real prompt and option labels read-only. Local Mac answer UX, signed Face ID flow and Cloud text prompt/cancel stay as they were.
- Add
cloudPermissionRequiresMacHandoffSwift Testing regression with the existing mock adapter: attempts to allow/deny/reply instead retain the pending request and never queue a replacement prompt.
Scope: iPhone UI and shared chat-store presentation policy only. No VM-mode policy, auth, Rust hostd, entitlement, or server changes. This is a short-term disclosure; implementing an actual Cloud-scoped approval receipt belongs to #30584, with its atomic VM authorization dependency #30588. Avoid relabeling this as remote permission support.
Verification and explicit limits
- Source-reviewed the original code and the changed code against exact NEXT head; five edited files, one new regression test. Tests are committed but have not run in this GitHub-only session. This runner has Swift on Linux, but no Xcode/iOS simulator/paired phone. No real APNs, multi-Mac, physical gesture, or Cloud VM approval observed.
- Required before merge: compile/run
CmuxMobileAgentChatTestson an authorized iOS simulator and visually verify the two Cloud pending cards; verify a local Mac chat still offers signed Allow/Deny and its question form. - Manual negative acceptance:
cloud-acpmuxreceives a pending permission/question, phone shows the exact content but no actionable answer; opening the same session on Mac permits policy-compliant decision and phone history updates on replay. Revoke/change account, background/resume and re-tap an old push; no previously authenticated request is actionable in another account.
This PR intentionally stays draft for native validation and owner review.
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes the iPhone chat UI showing dead-end decision controls for Cloud requests that the transport refuses to answer. Cloud chats now display a read-only "Open this Cloud chat on your Mac to answer" notice for pending permissions and questions, while local Mac chats keep their existing signed Allow/Deny and question-answer flow.
- Adds
AgentChatStore.canAnswerPendingRequests(defaults true; false when binding a Cloud chat) and guards all programmatic answer/decline/reply-instead paths behind it. - Adds a Swift Testing regression asserting Cloud permission attempts keep the request pending and never queue a replacement prompt.