feat(mobile): ACP chat on mobile with a Cursor-style UI, behind acp-chat
Summary
- Mobile shows and drives ACP (chat-v3) sessions that desktop opens, next to terminal sessions, behind the
acp-chatflag. - The chat screen is redesigned after Cursor mobile: plain agent text, grey prompt pills, folded "N steps" activity, a queue docked above the composer, and native composer controls for the permission mode and Stop.
- Fixes the red-screen crash when rendering chat markdown (react-native-worklets 0.10 renamed the Babel option that streamdown relies on).
Why / Context
Chats that desktop opens over ACP have no PTY, so mobile, which built its session lists from terminals, never showed them. Once they appeared, rendering a reply crashed: react-native-streamdown runs remend on a worklet runtime, and its README's workletizableModules option no longer exists in worklets 0.10.1. The plugin ignored it, captured remend as a remote function, and calling it there threw.
How It Works
- Listing:
useHostTerminalsadds live chats (fromlistSessions, which now reportslive) to the terminal rows, only withacp-chaton. The flag is in the query key so chats appear as soon as it loads. - Transcript (
ChatSessionView,ChatRowView):- Agent replies render as markdown, with copy and branch buttons under the last one in a group.
- Each run of thinking and tool calls folds into one grey "N steps" line that opens an Activity sheet. Failed tools say "Failed".
- Queue: docked above the composer, one line per prompt with steer and delete. The transcript stays pinned to the bottom when it grows.
- Header: a sessions button (the Sessions sheet gains a + to start one), and a
cpubutton only while background tasks or subagents run, opening a sheet with Stop per task. - Native composer (Swift,
modules/composer):ComposerModeMenuis a SwiftUIMenu/Pickerbeside +, shown while the composer is expanded.- Send turns into Stop while the agent works and the draft is empty.
- New props:
modeOptions,selectedModeId,onModeSelect,canStop,onStop. The mode change is optimistic and rolls back if the host refuses it.
- State sharing: each chat publishes its modes, tasks and stop to its own entry in
activeChatStore, keyed by session, which the header and composer read. - Babel:
importForwarding: { moduleNames: ["remend"] }replacesworkletizableModules.
Manual QA Checklist
Checked on the iOS 26.3 simulator against production with a real host:
- Chat markdown renders with no worklets red screen
- Live chats appear on Home and open into the chat UI; with the flag off, none appear
- Prompt pills, plain agent text, grey inline code, copy and branch row
- Thinking and tool runs fold into "N steps"; tapping opens the Activity sheet
- Header sessions button opens the Sessions sheet;
cpubutton appears on a chat with a running background task - Composer: mode button beside + (expanded only), send shows as Stop on a running chat
- Mode popover selection, Stop press, queue steer and delete, and an approval card were not exercised end to end (they act on real agents)
Testing
bunx tsc --noEmit -p apps/mobile(no errors in changed files; the knownpackages/authbetter-auth type errors remain)bunx biome checkon the changed pathsbun test apps/mobile/screens/(authenticated)(68 pass), including newgroupActivity/groupPositionstestsbun run check:i18n(all locales complete)- Native:
xcodebuildDebug simulator build of the dev client
Known Limitations
- The native build needs Xcode 26.4+. On Xcode 26.3 (Swift 6.2.4) Expo's
expo-modules-jsi57.1.1 does not compile. I built locally with three patches tonode_modulesthat are not part of this PR. - Messages already on screen keep their old markdown colours until the chat remounts (the native markdown view caches styling).
Risks / Rollout
- Risk: chat UI changes are gated by
acp-chat. The composer's new props are optional, so terminal sessions and the Home composer are unchanged with the flag off. Starting a chat from Home now runs the session bootstrap in the background, so the Home composer frees immediately. - Rollout: needs a new native build (Swift composer changes) before the composer controls show. On older builds the props are ignored.
- Rollback: turn off
acp-chat, or revert the PR.
Summary by cubic
Adds ACP chat sessions to mobile, behind the acp-chat flag. Chats desktop opens over ACP have no PTY, so mobile never showed them; they now appear on Home and in session strips and open into a new Cursor-style chat screen. With the flag off, behavior is unchanged. The chat logic mobile and desktop each kept copies of now lives once in @superset/chat, with no behavior change on either app.
New chat screen
- Agent replies render as plain markdown with copy and branch actions under the turn's last reply; prompts show as grey pills; thinking and tool runs fold into "N steps" rows that open an Activity sheet.
- The queue docks above the composer with steer and delete; approvals render inline; the header gains a sessions button and a background-tasks button while tasks run. Terminal sessions keep their tab strip when the flag is on.
- The native Swift composer adds a permission-mode menu beside + and turns send into Stop while the agent works. Mode changes are optimistic and roll back if the host refuses them, and switching to a terminal clears the menu.
chat-runtime'slistSessionsnow reports whether each session is live, so mobile only lists chats still backed by an agent. On older hosts without that, the probe is batched ten at a time, up to fifty, so running chats behind closed ones still list.- Branching keeps the original chat open. Starting a chat from Home no longer holds the Home composer while the session boots; images go as attachments, and a phone-started chat that never appears says the host hasn't answered.
- On hosts 1.37.0+, a new chat goes through
agents.runwithsurface: "chat", so the host launches it and sends the first prompt. Older hosts ignoresurfaceand would start a terminal, so they keep the phone-side launch, which also runs when the create timed out at the relay but may have landed. - Fixes the crash rendering chat markdown:
react-native-worklets0.10 droppedworkletizableModules, soremendusedimportForwardinginstead. Approval details expand past 600 characters and show the proposed edit.
Rollout
- Needs a new native build (Swift composer changes) before the composer controls show; older builds ignore the new props.
- The native build requires Xcode 26.4+; on Xcode 26.3
expo-modules-jsidoes not compile (worked around locally with uncommittednode_modulespatches).
Summary by CodeRabbit
- New Features
- Added mobile chat sessions alongside terminal sessions, including chat creation, resuming ended sessions, and visibility in workspace activity.
- Added chat transcripts with message and tool activity, approval requests, queued prompts, and controls for background tasks.
- Added composer options for selecting chat modes and stopping active work, plus support for chat attachments.
- Added localized chat and agent interface text across supported languages.
- Improvements
- Chat sessions can be managed from workspace controls, with clearer activity and task status.
- Chat messages refresh correctly when their display styling changes.