AionUi 手机端代理 CLI 架构研究
AionUi 手机端代理 CLI 架构研究
更新时间:2026-07-07
结论
当前安卓 App 的主线应继续采用 AionUi mobile/ 的 Expo Router + React Native UI 架构,但协议和行为语义不能只看 mobile 目录的简化实现,还要以 AionUi 桌面客户端的 chatLib、消息合并规则、工具调用抽象为准。
换句话说:
- UI 骨架参考
AionUi/mobile。 - 消息协议参考
AionUi/packages/desktop/src/common/chat/chatLib.ts。 - 工具调用展示参考
AionUi/packages/desktop/src/common/chat/normalizeToolCall.ts和移动端ToolCallBlock。 - 本项目的差异点放在运行时:AionUi mobile 连接远端 WebUI/host;本项目在手机本地通过 Termux daemon 启动和代理 OpenCode、Gemini CLI、Codex CLI 等。
工程优先级可以概括为一句话:界面先像 AionUi mobile,行为先像 AionUi desktop client,运行时改成本地 Termux daemon。
AionUi 技术选型
AionUi 主仓库是 Electron 桌面客户端 + WebUI + Expo mobile 的组合。需要注意的是,AionUi 的手机端位于仓库根目录 mobile/,不在 npm workspace 的 packages/* 内;桌面端和 web-host 等包在 packages/* 内。
桌面端:
- React 19 + React DOM。
- Electron + electron-vite。
- Arco Design、UnoCSS、自定义主题变量。
- 主进程负责安装、启动、数据库、WebUI、更新、宠物窗口、渠道集成等。
- renderer 通过 IPC/WebSocket 风格桥接消费统一消息模型。
移动端:
- Expo 55、React Native 0.83、React 19。
expo-router作为页面路由。@react-navigation/bottom-tabs、drawer/screen 生态。axios负责 REST。- 原生
WebSocket负责长连接。 expo-secure-store保存 host/port/token。react-native-markdown-display渲染助手消息。@shopify/flash-list已引入,但当前核心聊天页仍主要使用FlatList。app.config.ts使用 portrait、automatic theme、expo-router、expo-secure-store、expo-dev-client、expo-camera。
移动端不是直接复用桌面 React 组件,而是复用协议、状态结构和信息架构,再用 React Native 重写展示层。这也是本项目应采用的路径。
客户端优先参考原则
AionUi mobile 的 src/utils/messageAdapter.ts 文件头明确说明它是 desktop src/common/chatLib.ts 的简化移植,目的是避免 Metro 解析 desktop import chain。因此本项目不能把 mobile adapter 当成完整协议源,而应该采用分层参考:
- UI 信息架构参考
mobile/
手机端的 Tabs、Chat Drawer、Files Drawer、连接页、新建会话弹窗、workspace/mode/model/file picker、消息气泡、工具折叠摘要,都应优先沿用 mobile/ 的交互和布局。
- 消息模型参考 desktop
chatLib.ts
desktop 的 chatLib.ts 定义了完整 TMessage 类型和关键语义,包括 tips.error 结构化错误、thinking、available_commands、created_at、hidden、文本 replace、ACP tool call sanitize/merge、plan session_id 等。这些是本项目 mobile adapter 应逐步追平的协议面。
- 工具调用抽象参考 desktop
normalizeToolCall.ts
desktop 把 tool_group、acp_tool_call、tool_call 统一成 NormalizedToolCall,包含 key/name/status/description/input/output/truncated/messageId/conversationId/imagePath。本项目应优先复刻这个中间层,而不是在 ToolCallBlock 里继续堆每个 CLI 的私有分支。
- 运行时替换为本地 daemon
AionUi mobile 是远程连接已有 host;本项目是手机本地运行 CLI。所以 ConnectionContext -> bridge -> ChatContext 的形状可以复用,但 host 实现应落到 127.0.0.1 Termux daemon,再由 daemon 管理 CLI 进程、会话、权限、文件和取消。
AionUi Mobile 核心链路
移动端通信链路:
ConnectionContext
-> configureApi(host, port, token)
-> wsService.configure/connect
-> bridge.request/on
-> ChatContext / ConversationContext / FilesTabContext
-> MessageAdapter
-> ChatScreen / MessageBubble / ToolCallBlock桥接协议:
客户端请求:{ name: "subscribe-{key}", data: { id, data } }
服务端响应:{ name: "subscribe.callback-{key}{id}", data: result }
服务端推送:{ name: "chat.response.stream", data: IResponseMessage }
心跳:ping / pong关键事件:
database.get-user-conversationsdatabase.get-conversation-messagescreate-conversationremove-conversationupdate-conversationchat.send.messagechat.stop.streamchat.response.streamconfirmation.addconfirmation.updateconfirmation.removeconfirmation.listconfirmation.confirmacp.get-available-agentsacp.probe-model-infoconversation.get-workspaceget-file-by-dirread-fileget-image-base64
这些 API 名称本身就是我们本地 Termux daemon 的兼容目标。UI 不应该直接关心某个 CLI 的进程细节。
UI 设计要点
AionUi mobile 的第一屏不是营销页,而是工作台式聊天客户端:
- 底部 Tab:聊天、文件、设置。
- 聊天页:会话侧栏 + 当前聊天流 + 输入栏。
- 新建会话:先选 agent,再选择 workspace、mode、model、文件。
- 消息类型:文本、提示、状态、权限确认、工具调用、计划。
- 工具调用:默认折叠,显示状态图标、标题、摘要,展开后展示参数或结果。
- Codex/OpenCode/Gemini 等差异应被压缩到统一消息模型里。
当前移动端 UI 已覆盖的高价值对象:
texttipsagent_statustool_calltool_groupacp_permissionacp_tool_callcodex_permissioncodex_tool_callplan
桌面端还具备但 mobile 简化不足的对象:
thinking- 结构化错误
tips.error replace流式覆盖语义hidden消息created_at严格历史排序available_commands- 更完整的工具归一化与文件变更汇总
这些是后续需要补齐的 UI/adapter 差距。
客户端实现链路对照
AionUi mobile 的聊天页当前链路是:
ChatScreen
-> useChat()
-> useProcessedMessages(messages)
-> FlatList
-> MessageBubble / ToolCallSummary
-> ToolCallBlock / ConfirmationCard / MarkdownContent
-> ChatInputBar这条链路适合手机端继续使用,但需要补强三个点:
ChatContext不只接收内容流,也要接收并维护会话级元数据,例如thought、contextUsage、available commands、model info。useProcessedMessages应从“连续工具消息批量折叠”升级为“基于 normalized tool call 的稳定摘要层”。MessageBubble应补齐 desktop 中的消息类型,而不是只渲染 text/tips/status/permission/plan。
AionUi desktop 的消息处理链路更完整:
IResponseMessage
-> transformMessage()
-> TMessage
-> composeMessage()
-> mergeTextMessageContent / mergeAcpToolCallContent
-> renderer components这里最值得迁移的是合并语义,而不是桌面组件本身。尤其是文本 replace、hidden、created_at、ACP tool update sanitize、plan session merge,这些会直接影响流式响应、历史恢复和工具状态是否稳定。
工具调用主线
现阶段最容易失控的是工具调用 UI,因为 OpenCode、Gemini、Codex、ACP/MCP 的事件形状不同。AionUi desktop 已经给出更好的解法:先归一化,再展示。
建议本项目建立移动端 NormalizedToolCall 层:
type NormalizedToolCall = {
key: string;
name: string;
status: 'pending' | 'running' | 'completed' | 'error' | 'canceled';
description?: string;
input?: string;
output?: string;
truncated?: boolean;
messageId?: string;
conversationId?: string;
imagePath?: string;
};对应策略:
- daemon adapter 负责把 CLI 私有事件转成 AionUi-compatible event。
- message adapter 负责保持
TMessage合并正确。 - normalized tool call 层负责把不同工具消息统一成展示 VO。
ToolCallSummary和ToolCallBlock只消费 normalized VO,减少 CLI 分支。
这样后续增加 MCP tool call、shell command、file diff、web search、image output 时,不会每次都改聊天页主结构。
本项目架构路径
本项目不应把手机端做成“远程控制 AionUi 桌面端”的附属 App。用户目标是手机本地运行 OpenCode、Gemini、Codex 等 CLI,因此主架构应为:
React Native App
-> WebSocket bridge
-> Local Termux daemon
-> CLI backend adapters
-> OpenCode serve/API
-> Gemini CLI process
-> Codex CLI JSONL process
-> future ACP/MCP adapters
-> normalized AionUi-compatible events
-> Mobile UI rendering边界划分:
- UI 层只处理 AionUi 风格消息,不处理 CLI 私有格式。
- daemon 层负责启动、探测、会话存储、权限确认、文件系统、进程取消。
- backend adapter 层负责把 OpenCode/Gemini/Codex 的输出转换为统一事件。
- message adapter 层负责把
IResponseMessage转成TMessage并合并流式更新。
当前实现对齐情况
当前 aicliui-app 已经基本采用 AionUi mobile 的结构:
- Expo Router + React Native。
ConnectionContext、ConversationContext、ChatContext、WorkspaceContext。bridge.request/on协议。- 本地
127.0.0.1:43117daemon。 - Termux bootstrap。
- OpenCode、Gemini CLI、Codex CLI 后端。
- Codex JSONL 的
command_execution、web_search、file_change、todo_list已被转换到移动端消息。 - OpenCode/Gemini 的模型、mode、工具更新、文件附件已有基础链路。
acp_context_usage已进入ChatContext,并可按 desktopContextUsageIndicator的阈值和 token 格式在移动端展示。
主要差距:
- mobile 的
messageAdapter仍是 AionUi mobile 简化版,没有追平桌面chatLib的完整语义。 codex_tool_call类型仍有未细分对象,例如mcp_tool_call。- 工具调用 summary 仍偏轻,未完全对齐桌面端
normalizeToolCall的输入/输出/截断/图片/文件变更抽象。 - 权限确认需要继续按桌面端的 edit/exec/mcp 分类增强文案和按钮策略。
- 文件工作区 UI 已有基础,但还需要与工具调用产生的文件变更、预览、diff 串起来。
AionUi 进一步研究结论
本轮重新检查后,可以更明确地区分三条参考线:
AionUi/mobile是手机端 UI 和信息架构主参考,但它不是完整 runtime 客户端。AionUi/packages/desktop/src/renderer是客户端行为主参考,尤其是会话 runtime、send box、slash command、消息合并和权限确认。AionUi/packages/desktop/src/common/adapter/ipcBridge.ts与httpBridge.ts是 bridge contract 主参考,它们把 renderer 的ipcBridge.conversation.*调用映射到后端 REST/WS。
因此,本项目的安卓端不应只“复刻 AionUi mobile 目录”,而应采用:
移动端 UI:参考 AionUi/mobile
客户端状态机:参考 AionUi desktop renderer hooks/context
协议/消息语义:参考 AionUi desktop common chat + adapter
本地 runtime:替换为 Termux daemon + CLI adapters这个判断很关键,因为 AionUi mobile 当前更像远程 WebUI companion client;而本项目要做的是手机本地 agent runtime client。
Slash command 主动查询链路
AionUi desktop 的 slash command 不是只靠流式 available_commands 事件。它还有一条主动查询链路:
ensureConversationRuntime(conversation_id)
-> ipcBridge.conversation.getSlashCommands.invoke({ conversation_id })
-> GET /api/conversations/:id/slash-commands
-> mapAcpCommandsToSlashCommands()
-> useSlashCommandController()
-> SlashCommandMenu / SendBox主动查询的原因是:available_commands 可能在 agent warmup 阶段通过 WebSocket 推送,此时前端监听器未必已经挂上;如果只依赖流事件,进入会话后 slash 菜单可能为空。
本项目应复刻这个客户端语义,但 route 名称落在本地 bridge 上:
ChatContext.loadConversation(conversation_id)
-> bridge.request('conversation.get-slash-commands', { conversation_id })
-> Termux daemon / packages daemon
-> active backend adapter.getSlashCommands()
-> mapAvailableCommandsToSlashCommands()
-> ChatInputBar slash menu这条链路已经比单纯监听 available_commands 更接近 AionUi desktop client,也更适合 OpenCode 这类本地 server 可以主动枚举 commands 的 backend。
落地策略:
- OpenCode:优先查询本地
opencode serve的/api/command,失败后降级/command。 - Gemini/Codex:当前本项目以 one-shot CLI/JSONL 方式运行,暂不硬编码 TUI slash commands,先返回空数组。
- 后续 ACP adapter:如果握手或 runtime catalog 提供
available_commands,再映射到同一SlashCommandItem。
本轮落地更新
本轮已在 aicliui-app 中补齐一个小竖切:
packages/daemon的CliAgentAdapter增加可选getSlashCommands()。- 默认 daemon route 增加
conversation.get-slash-commands。 - OpenCode client 增加命令列表查询和宽松响应解析。
apps/mobile/src/context/ChatContext.tsx在打开会话时主动拉取 slash commands。apps/mobile/src/services/termuxDaemonSource.ts同步补齐内嵌 Termux daemon route。- 新增 daemon 和 mobile 测试,覆盖主动查询链路。
这一步的价值不在于 slash 菜单本身,而在于确认本项目的实现方向:移动端 UI 保持 AionUi mobile 风格,核心客户端行为逐步向 AionUi desktop 对齐,本地 daemon 负责适配各 CLI runtime。
后续优先级
- 先固定协议兼容层
把 Termux daemon 对齐到 AionUi bridge API;新增 CLI 时只新增 backend adapter,不改 UI 协议。
- 追平 desktop
chatLib的消息语义
优先补 created_at、hidden、replace、thinking、tips.error、available_commands、plan session_id/sessionId 兼容。这些是稳定聊天流和历史恢复的底座。
- 做统一工具调用模型
参考 AionUi desktop normalizeToolCall.ts,在 mobile 中建立统一的 tool summary 数据结构,避免每个 CLI 都堆一套 UI 分支。
- 补齐 Codex 特有事件
下一步优先实现 mcp_tool_call 的专门映射,让 server/tool、arguments、result、error 都能在 UI 中稳定展示。
- 强化文件和 diff 工作流
把工具调用中的文件变更、工作区文件树、文件预览、diff 展示连接起来。这是从“聊天壳”变成“手机端 agent 工作台”的关键。
- 最后再扩展多 Agent/Team
AionUi 的 Team Mode 很有价值,但本项目前期应先把单 agent 的本地运行、权限、工具、文件链路打穿。Team Mode 需要稳定的会话隔离、并发进程和权限队列后再做。
开发原则
- 复用 AionUi mobile 的信息架构和交互模式。
- 复用 AionUi 桌面端的消息模型和工具抽象语义。
- 不把 UI 和 CLI 私有协议耦合。
- 所有 CLI 输出先进入 daemon adapter,再转成 AionUi-compatible event。
- 每个后端能力按垂直切片落地:事件解析、持久化、UI 展示、测试一起完成。