fix(ios): show Mac handoff for Cloud requests the phone cannot answer (HQ-14)

Draft
TE
3d ago
+123-23

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 and Reply instead go 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 cloudPermissionRequiresMacHandoff Swift 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 CmuxMobileAgentChatTests on 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-acpmux receives 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
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.