拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

手机远程操控AI Agent:多会话统一管控与审批流实践

手机远程操控AI Agent:多会话统一管控与审批流实践

1. 这个标题到底在说什么事

先说结论:这个标题描述的是一个移动端统一管控层——把散落在不同终端、不同工具里的 AI Agent 会话,收敛到一个手机入口上做调度、查看和干预。核心不是"手机能跑 AI",而是"手机能当指挥台"。

我自己从 2024 年底开始密集用 Claude Code、Codex CLI、OpenCode 这几套东西,最难受的点从来不是模型能力不够,而是上下文切换成本太高。你在笔记本上开着一个 Claude Code 会话在改后端接口,同时在另一个终端里跑 Codex 在补测试用例,OpenCode 那边还挂着一个在重构前端组件。三个会话、三套配置、三种交互习惯,人一旦离开工位,整个链路就断了。手机远程操控这件事,解决的正是这个"人机分离"的断层。

它适合谁?三类人最需要:

  • 多 Agent 并行作业的重度用户:同时跑两三个 Agent 做不同任务,需要随时切进去看进度、补指令、掐掉跑偏的会话。
  • 经常离开工位但不想断线的人:开会、通勤、外出时想瞄一眼 Agent 跑到哪了,顺手回一句"继续"或者"停"。
  • 想把 Agent 当基础设施用的人:不是玩票,而是真的把 Agent 接进日常工作流,需要一套稳定的远程接入方案。

不适合谁?如果你只是偶尔用一次 AI 补个代码,那手机远程操控纯属给自己加戏,本地终端直接跑就完了。这套东西的价值随"并行会话数量"和"离开工位频率"线性上升。

下面我按"整体设计思路 → 核心细节 → 实操落地 → 踩坑排查"四块拆开讲,每一块都尽量给到能直接抄的配置和参数。

2. 整体设计与思路拆解

2.1 为什么是"手机当指挥台"而不是"手机跑 Agent"

这是整个方案里最关键的一个取舍,必须先讲清楚。

AI Agent 的运行本质是长时任务 + 高频工具调用 + 大上下文。Claude Code 一次会话动辄几十万 token 的上下文窗口,Codex 在跑测试的时候会反复读写文件系统,OpenCode 的 skill 机制还会拉起子进程。这些东西对算力、内存、文件系统权限的要求,手机端根本扛不住——不是性能问题,是架构问题。

所以正确的分层是:

层级部署位置职责
执行层本地工作站/服务器真正跑 Agent 进程,持有文件系统和工具权限
网关层本地或内网会话管理、鉴权、消息路由、状态同步
交互层手机发指令、看输出、做审批、切会话

手机只做交互层,这是唯一合理的分工。我见过有人试图在手机上直接跑 Agent 运行时,结果就是上下文一超就崩,工具调用权限根本给不了,纯属折腾。

2.2 三种主流接入路径的取舍

把手机和执行层连起来,业界常见三条路,各有适用场景:

路径一:终端复用型(tmux/screen + 移动 SSH 客户端)

最土但最稳。Agent 跑在 tmux 会话里,手机通过 SSH 客户端连上去 attach。优点是零额外开发,任何 CLI 型 Agent 都能用;缺点是交互体验原始,手机上敲命令很痛苦,而且没有结构化的会话管理。

路径二:自建网关型(TypeScript SDK + WebSocket)

用 TypeScript SDK 把 Agent 的输入输出包一层,起一个 WebSocket 服务,手机端用 PWA 或原生 App 连。这是标题里"一个手机操控所有 Agent"最贴近的实现方式。优点是统一入口、可定制、能加审批流;缺点是要自己写网关,维护成本不低。

路径三:现成中台型(AI Agent 中台产品)

直接用市面上已有的 Agent 中台,它们通常自带移动端。优点是开箱即用;缺点是绑定厂商,多 Agent 混合调度能力参差不齐,而且数据要过第三方。

我的建议是:先用路径一验证需求,确认自己真的需要高频远程操控后,再上路径二。路径三适合团队采购场景,个人玩家不划算。

2.3 核心设计原则:会话即资源

整个方案的设计哲学就一句话:把每个 Agent 会话当成一个可寻址的资源。

具体来说,每个会话要有:

  • 唯一 ID:不管是 Claude Code 的 session、Codex 的 conversation 还是 OpenCode 的 task,都要映射到一个统一 ID。
  • 状态机:idle / running / waiting-approval / done / error,手机端要能一眼看出每个会话卡在哪。
  • 输入队列:手机发的指令不能丢,要能排队,等 Agent 空闲时投递。
  • 输出缓冲:Agent 的输出要缓存最近 N 条,手机连上来能补看历史。

这四条做到了,手机端就是一个纯粹的"会话列表 + 详情页"结构,逻辑非常干净。做不到,就会变成一堆 if-else 的泥潭。

3. 核心细节解析与实操要点

3.1 会话抽象层怎么设计

这是整个网关的核心。我用 TypeScript SDK 写的时候,抽象层大概长这样:

interface AgentSession { id: string; agentType: 'claude-code' | 'codex' | 'opencode'; status: 'idle' | 'running' | 'waiting-approval' | 'done' | 'error'; cwd: string; createdAt: number; lastActivityAt: number; outputBuffer: OutputChunk[]; pendingInputs: string[]; } interface OutputChunk { seq: number; timestamp: number; type: 'stdout' | 'stderr' | 'tool-call' | 'approval-request'; content: string; }

关键点在于outputBuffer用序号 + 环形缓冲,而不是简单数组。原因:手机端网络不稳定,重连后要能告诉服务端"我最后收到的是 seq=1024",服务端从 1025 开始补发。用数组的话,你没法高效地做增量同步。

环形缓冲大小我一般设 2000 条,超过就丢最老的。实测下来,2000 条足够覆盖手机端断线 5-10 分钟的补发需求,再长的话用户也不会去翻。

3.2 三种 Agent 的接入差异

Claude Code、Codex、OpenCode 虽然都是 CLI 型 Agent,但接入方式差别不小,这里必须分开讲。

Claude Code:它的交互是 TUI 型的,直接 pipe 输出会丢格式。我的做法是用--print模式跑非交互任务,或者用它的 SDK 模式。如果你要保留 TUI 交互,就得走 PTY(伪终端),用node-pty起一个伪终端,把输出流原样转发。PTY 方案的好处是能完整保留颜色和进度条,坏处是输出里混着大量 ANSI 转义码,手机端要额外做清洗。

Codex:Codex CLI 的会话管理相对清晰,它有自己的 conversation ID。接入的时候我建议直接用它的非交互模式,把每次调用当成一次独立的 request-response,会话状态由网关层自己维护。这样比去解析它的 TUI 输出稳定得多。

OpenCode:OpenCode 的 skill 机制是它的特色,但也是接入的难点。skill 执行过程中会拉起子进程,输出是异步的。我的处理方式是把 skill 调用当成一个独立的 output stream,用type: 'tool-call'标记,手机端单独渲染成一个可折叠的卡片。

注意:三种 Agent 的输出格式差异很大,千万不要试图写一个"万能解析器"。正确做法是每种 Agent 写一个 adapter,统一输出成上面定义的OutputChunk格式。adapter 层是脏活累活,但隔离好了,上层逻辑就干净了。

3.3 审批流的实现要点

Agent 干活最怕的就是它自作主张删文件、改配置。手机远程操控的一个核心价值就是随时能审批。

审批流的实现要点:

  1. 拦截点:在 Agent 执行危险操作前拦截。Claude Code 有 permission 机制,Codex 有 approval mode,OpenCode 有 skill 权限声明。要利用它们原生的拦截能力,而不是自己 hook 文件系统。
  2. 推送:拦截后,网关把审批请求推给手机,状态置为waiting-approval。
  3. 响应:手机端 approve/reject,网关把结果回传给 Agent。
  4. 超时:必须设超时,我一般设 5 分钟。超时默认 reject,宁可让 Agent 停下,也不能让它带着未审批的操作继续跑。

这里有个坑:审批请求的推送必须走独立通道,不能混在普通输出流里。因为普通输出流可能被大量日志淹没,审批请求一旦被淹没,Agent 就卡死了。我用的是单独的 WebSocket 频道,或者退一步用系统级推送。

3.4 鉴权与安全边界

手机远程操控,安全是绕不开的。几个硬性要求:

  • 传输加密:WebSocket 必须走 wss,不能裸 ws。
  • 设备绑定:手机端首次连接要配对,配对后发一个长期 token,token 绑定设备指纹。
  • 操作审计:所有从手机端发起的指令都要落日志,包括时间、指令内容、结果。
  • 危险操作二次确认:删除、force push、改配置这类操作,手机端要二次确认。

我自己的做法是网关只监听内网地址,手机通过内网访问。如果确实需要外网访问,那必须上完整的鉴权体系,不能图省事。

4. 实操过程与核心环节实现

4.1 环境准备与依赖安装

先列一下我这套方案的实际依赖:

# 基础运行时 node --version # 需要 >= 20 pnpm --version # 用 pnpm 管理依赖,比 npm 快 # Agent 本体 npm install -g @anthropic-ai/claude-code npm install -g @openai/codex # OpenCode 按官方文档安装 # 网关依赖 pnpm add ws node-pty uuid pnpm add -D typescript @types/node @types/ws

node-pty是编译型依赖,装的时候容易出问题。如果编译失败,先确认python3和make在 PATH 里,macOS 上还要装 Xcode Command Line Tools。这个坑我踩过两次,每次都是环境缺东西。

4.2 网关服务的核心代码

网关的主循环逻辑,我简化成可读版本:

import { WebSocketServer } from 'ws'; import { spawnAgent } from './adapters'; const sessions = new Map<string, AgentSession>(); const wss = new WebSocketServer({ port: 8080 }); wss.on('connection', (ws, req) => { const token = extractToken(req); if (!verifyToken(token)) { ws.close(4001, 'unauthorized'); return; } ws.on('message', async (raw) => { const msg = JSON.parse(raw.toString()); switch (msg.type) { case 'list-sessions': ws.send(JSON.stringify({ type: 'session-list', sessions: [...sessions.values()].map(toSummary), })); break; case 'attach': attachSession(ws, msg.sessionId, msg.lastSeq); break; case 'input': enqueueInput(msg.sessionId, msg.content); break; case 'approve': resolveApproval(msg.sessionId, msg.approvalId, msg.approved); break; } }); });

attachSession是关键,它负责把lastSeq之后的输出补发给手机:

function attachSession(ws, sessionId, lastSeq) { const session = sessions.get(sessionId); if (!session) return; const missed = session.outputBuffer.filter(c => c.seq > lastSeq); for (const chunk of missed) { ws.send(JSON.stringify({ type: 'output', sessionId, chunk })); } // 注册实时推送 session.subscribers.add(ws); }

这段逻辑看着简单,但lastSeq的语义一定要想清楚:它是"客户端已收到的最大序号",补发从lastSeq + 1开始。我第一版写成>= lastSeq,结果每次重连都重复收到最后一条,调试了半天。

4.3 手机端的最小实现

手机端我不建议一上来就写原生 App,用 PWA 最快。核心就三个页面:

会话列表页:拉list-sessions,渲染成卡片,每张卡片显示 Agent 类型、状态、最后活动时间、最近一行输出。

会话详情页:attach 到具体会话,渲染输出流,底部一个输入框。输出流要做虚拟滚动,不然几千条输出直接卡死。

审批页:收到approval-request时弹出,显示操作详情,两个按钮。

PWA 的好处是可以加到主屏幕,用起来跟原生 App 差不多,而且不用过应用商店。缺点是后台推送能力弱,iOS 上尤其明显。如果你对推送实时性要求高,那还是得原生。

4.4 参数调优的实际记录

几个我实测下来比较重要的参数:

参数我的取值说明
outputBuffer 大小2000 条覆盖 5-10 分钟断线补发
审批超时5 分钟超时默认 reject
WebSocket 心跳30 秒低于 30 秒手机端耗电明显
输出批量推送100ms 聚合避免高频小包,手机端省电
重连退避1s/2s/4s/8s 上限 30s指数退避,避免风暴

输出批量推送这个特别重要。Agent 输出是高频的,如果每条都推一次 WebSocket,手机端电量掉得飞快。我加了 100ms 的聚合窗口,把窗口内的输出合并成一个包推,实测电量消耗降了一半以上。

5. 常见问题与排查技巧实录

5.1 典型问题速查表

现象可能原因排查方向
手机连不上网关网络不通/端口未监听/鉴权失败先 curl 网关健康检查接口,再看 token
输出乱码ANSI 转义码未清洗检查 adapter 的清洗逻辑
会话状态卡在 runningAgent 进程僵死看进程树,检查是否有子进程未回收
审批请求收不到推送通道被日志淹没确认审批走独立通道
重连后输出重复lastSeq 语义错误检查补发逻辑的边界条件
手机端卡顿输出流未做虚拟滚动检查渲染层

5.2 几个我踩过的坑

坑一:PTY 输出里的 ANSI 码

Claude Code 的 TUI 输出里混着大量 ANSI 转义码,直接推到手机端就是一堆[38;5;242m之类的乱码。我一开始想用正则全清掉,结果把有用的颜色信息也清了。后来改成用strip-ansi库,只清控制码保留文本,效果好很多。但进度条这类依赖光标移动的 UI,清完之后就变成一堆重复文本,这个只能靠 adapter 层做特殊处理。

坑二:Agent 进程的僵尸子进程

Codex 跑测试的时候会拉起子进程,如果网关只 kill 主进程,子进程会变成僵尸。我的做法是用进程组,spawn 的时候设detached: true,kill 的时候用process.kill(-pid)杀整个组。这个坑不踩一次根本想不到。

坑三:手机端后台断连

iOS 的 PWA 在后台几分钟就会被系统挂起,WebSocket 直接断。用户切回来的时候要能自动重连并补发。我的做法是监听visibilitychange事件,切回前台立即重连,配合lastSeq补发。Android 上稍微好一点,但也不能指望后台常驻。

坑四:多设备同时 attach

我一开始没考虑多设备,结果手机和平板同时 attach 同一个会话,输出就乱了。后来改成每个会话维护一个 subscriber 集合,输出广播给所有 subscriber,输入则加锁串行化。多设备场景其实挺常见的,值得提前设计。

5.3 独家避坑技巧

技巧一:给每个会话加一个"心跳探针"

Agent 有时候会静默卡死,状态还是 running 但实际不动了。我的做法是每 60 秒检查一次会话的lastActivityAt,如果超过 5 分钟没活动且状态是 running,就标记为疑似卡死,手机端显示黄色警告。用户可以选择强制重启会话。

技巧二:输出流做语义分层

不要把 Agent 的所有输出平等对待。我分了四层:普通日志、工具调用、审批请求、错误。手机端默认只显示工具调用和错误,普通日志折叠起来。这样信息密度高很多,用户一眼能看到重点。

技巧三:会话快照

每个会话定期(比如每 5 分钟)存一个快照,包括当前状态、最近输出、工作目录。网关重启后能从快照恢复,不用让用户重新起会话。这个功能在网关需要升级重启的时候特别有用。

技巧四:输入预校验

手机端发的指令,网关收到后先做一轮预校验,比如检测是否包含危险命令模式。这不是为了拦截,而是为了在手机端弹出二次确认。实测下来,这个预校验拦住了我好几次手滑。

6. 关于并发和扩展性的一点经验

热词里有个"ai agent 怎么扛并发",这个问题在这套架构里同样存在。手机端可能同时操控十几个会话,网关要能扛住。

我的经验是:瓶颈不在 WebSocket 连接数,而在 Agent 进程的资源占用。一个 Claude Code 会话吃几百 MB 内存很正常,十几个会话就是几个 GB。所以并发上限本质上是机器资源上限,网关层反而是轻量的。

真要提升并发,方向有两个:一是会话池化,把空闲会话挂起,需要时再唤醒;二是把 Agent 跑在容器里,用容器做资源隔离和限制。前者实现简单但恢复慢,后者隔离好但运维复杂。我个人倾向容器方案,因为 Agent 跑飞了不至于把整台机器拖垮。

TypeScript SDK 这边,单进程扛几百个 WebSocket 连接没问题,但如果要上量,建议用 cluster 模式多进程,每个进程管一批会话,前面挂一个路由层。不过说实话,个人使用场景下,单进程足够,别过度设计。

7. 我个人的一些实际体会

这套东西我从 2025 年初开始折腾,中间重构过两次。最大的体会是:别追求大而全,先把一个 Agent 的远程操控跑通,再扩展。

我第一版就想同时支持三种 Agent,结果 adapter 层写了一堆半成品,哪个都不好用。后来砍掉两个,只做 Claude Code,把会话管理、审批流、断线重连这些基础能力打磨扎实,再逐个加 Codex 和 OpenCode,反而快得多。

另一个体会是手机端的交互设计比后端难。后端逻辑再复杂,好歹有明确的输入输出。手机端要在小屏幕上呈现 Agent 的复杂状态,信息架构稍微没设计好,用户就懵了。我的做法是极度克制,默认只显示最关键的信息,细节全部折叠,用户主动展开才看。

最后分享一个小技巧:给会话加"标签"功能。你可以给每个会话打标签,比如"后端重构""测试修复""文档",手机端按标签分组显示。会话一多,没有标签根本找不到哪个是哪个。这个功能实现成本极低,但用起来幸福感提升巨大。

返回列表