1. 手机远程操控 AI Agent 这件事,到底解决了什么问题
第一次看到“一个手机就能远程操控所有 AI Agent”这个说法,我脑子里冒出来的第一个念头是:这不就是把终端塞进手机里吗?但真正动手折腾过 Claude Code、各种开源 Agent 框架之后,我才意识到事情没那么简单。手机远程操控 AI Agent 的核心价值,不是“用手机敲命令”,而是把 Agent 从工位上解放出来,让它变成一个随时在线、随时可指挥的“远程执行单元”。
先说清楚这个项目大概是什么。它本质上是一套“控制端 + 执行端”的架构:执行端跑在你家里或公司的电脑、服务器、甚至树莓派上,负责真正运行 AI Agent(比如 Claude Code、基于 Rust 写的 Agent、各种开源 Agent 框架);控制端就是你手里的手机,通过一个轻量的 Web 界面或者 App,把指令发过去、把结果收回来。GitHub 上这类开源项目最近冒出来不少,像 diplay、jizura 这类名字在热搜里反复出现,说明大家对“手机管 Agent”这件事的需求是真实存在的。
它能做什么?简单说,你在地铁上、在咖啡馆、在床上躺着,都能给家里的 Agent 派活:让它跑一段代码、整理一份文档、爬一批数据、生成一份报告。Agent 干完活,结果推回手机,你扫一眼就知道成没成。这解决的核心痛点是“Agent 必须有人盯着”——很多 Agent 任务跑起来要几分钟到几十分钟,你不可能一直坐在电脑前等,但你又怕它中途卡住、报错、或者跑偏。手机远程操控把“盯梢”这件事变成了“随时抽查”,效率提升是实打实的。
适合谁看?三类人。第一类是把 Claude Code 当日常生产力工具的人,你已经习惯了用 Agent 写代码、改 bug,但被“必须坐在电脑前”绑住了;第二类是折腾开源 Agent 的开发者,你想给自己的 Agent 加一个远程控制层,但不想从零造轮子;第三类是对 AI Agent 感兴趣但还没入门的新手,你想先看看“Agent 到底能干嘛”,手机操控这种低门槛入口正好适合你。
我自己的使用场景很典型:白天在公司用 Claude Code 处理代码任务,晚上回家不想开电脑,但有些任务又必须跑。以前的做法是远程桌面,体验极差,手机屏幕小、操作别扭、还容易断。换成手机远程操控 Agent 之后,我只需要在手机上发一句“把昨天那个脚本再跑一遍,结果发我”,剩下的交给执行端。这个体验差异,用过就回不去了。
提示:手机远程操控 Agent 的前提是执行端必须保持在线。如果你用的是笔记本,合盖休眠就会断连,建议用一台常开的小主机或者云服务器做执行端。
2. 整体架构怎么设计:控制端、执行端、通信层三件套
2.1 为什么是“控制端 + 执行端”而不是“全塞进手机”
很多人第一反应是:能不能直接在手机上跑 Agent?技术上不是完全不行,但体验会很差。AI Agent 尤其是 Claude Code 这类,依赖完整的运行环境、文件系统、网络请求能力,手机端要么跑不动,要么跑起来各种权限限制。更关键的是,Agent 的任务往往涉及大量文件读写和长时间运行,手机不适合做这种重活。
所以合理的架构一定是分离的:执行端负责“干活”,控制端负责“指挥”和“看结果”。这个思路和很多开源项目的设计是一致的,比如热搜里提到的 diplay、jizura,基本都是这个路子。执行端可以是一台 Ubuntu 服务器、一台 Mac mini、一台树莓派,甚至是你主力电脑上开的一个常驻进程。控制端就是一个网页或者 App,通过 WebSocket 或者 HTTP 长连接和执行端通信。
这样设计的好处有三个。第一,执行端算力不受限,你想跑多大的模型、多重的任务都行;第二,控制端极轻量,手机浏览器就能用,不需要装一堆依赖;第三,两者解耦,执行端挂了不影响控制端,控制端换设备也不影响执行端。
2.2 通信层选型:WebSocket 还是轮询
通信层是整个项目的命脉。我试过两种方案:HTTP 轮询和 WebSocket。轮询的实现最简单,控制端每隔几秒问一次“有结果了吗”,执行端有结果就返回。但问题是延迟高、请求量大,Agent 输出是流式的,轮询根本追不上。
WebSocket 是更合适的选择。它建立一条长连接,执行端有输出就主动推给控制端,延迟低、体验流畅。Claude Code 这类 Agent 的输出本身就是流式的,用 WebSocket 能把“打字机效果”完整还原到手机上,看起来就像 Agent 在你手机里跑一样。
具体实现上,执行端跑一个 WebSocket 服务,控制端连上去之后,双方约定一套简单的消息协议。比如:
{ "type": "command", "payload": "帮我整理一下今天的日志" }执行端收到后,把指令喂给 Agent,Agent 的输出再通过:
{ "type": "output", "payload": "正在处理日志文件..." }推回控制端。这套协议不需要复杂,够用就行。
2.3 执行端怎么接 Agent:进程调用还是 SDK 集成
执行端接 Agent 有两种方式。一种是进程调用,直接spawn一个 Claude Code 进程,把指令通过 stdin 传进去,stdout 读出来。这种方式最通用,任何命令行 Agent 都能接。另一种是 SDK 集成,如果 Agent 提供了编程接口,直接调用更可控。
我个人的选择是进程调用为主、SDK 为辅。原因是 Claude Code 这类工具本身就是命令行优先的,进程调用最贴近它的原生用法,不容易出幺蛾子。SDK 集成虽然优雅,但版本一变接口就可能不兼容,维护成本高。
进程调用有个坑要注意:Agent 的输出可能是带 ANSI 颜色码的,直接推到手机端会显示一堆乱码。解决办法是在执行端做一层清洗,把 ANSI 转义序列过滤掉,或者转成 HTML 标签。这个细节看起来小,但不处理的话手机端体验会很糟糕。
注意:执行端调用 Agent 进程时,一定要设置超时和资源限制。Agent 跑飞了把服务器内存吃满的情况我遇到过不止一次,加个
timeout和内存上限能救命。
3. 核心细节拆解:从指令下发到结果回传的完整链路
3.1 指令下发:怎么把一句话变成 Agent 能懂的活
手机端发出去的指令,不能直接原样丢给 Agent。原因很简单,Agent 需要上下文。比如你发“再跑一遍”,Agent 根本不知道“再”指的是什么。所以执行端需要维护一个会话状态,把历史指令和结果存起来,每次新指令都带上必要的上下文。
我的做法是在执行端维护一个轻量的会话管理器,每个会话有一个 ID,记录最近的 N 条指令和输出。手机端发指令时带上会话 ID,执行端把上下文拼好再喂给 Agent。这样即使用户在手机上只发了半句话,Agent 也能理解。
指令格式上,我建议用结构化的方式,而不是纯文本。比如:
{ "session_id": "abc123", "command": "重新运行昨天的数据清洗脚本", "context": { "last_task": "data_clean.py", "last_result": "success" } }这样执行端解析起来明确,不容易出错。
3.2 执行端任务调度:单任务还是队列
如果你只有一个 Agent,单任务模式就够了,来一个指令跑一个。但实际使用中,你很可能同时派多个活,这时候就需要队列。我试过最简单的做法:执行端维护一个 FIFO 队列,指令进来先排队,Agent 空闲了就取下一个。
队列的好处是避免并发冲突。Agent 任务往往涉及文件读写,两个任务同时跑很容易互相干扰。队列虽然牺牲了一点并发性,但稳定性提升明显。如果确实需要并发,可以起多个 Agent 实例,每个实例配一个队列,但这就复杂了,新手不建议一上来就搞。
队列还有一个隐藏好处:你可以看到“排队中”的状态。手机端显示“你的任务排在第 3 位”,比干等着不知道发生了什么要好得多。
3.3 结果回传:流式输出怎么在手机上优雅展示
Agent 的输出是流式的,手机端展示要处理好几个问题。第一是滚动,输出不断追加,页面要自动滚到底部,但用户手动往上翻的时候不能强制拉回来。第二是格式,Agent 输出里可能有代码块、列表、表格,手机端要能正确渲染。第三是长度,一次输出可能几千字,全塞进一个页面会卡,需要做分页或者虚拟滚动。
我的做法是:手机端用 Markdown 渲染输出,代码块单独处理,长输出分块加载。具体来说,执行端每收到一段输出就推给手机端,手机端追加到当前消息里,同时判断用户是否在底部,在底部就自动滚动,不在就不动。这个逻辑不复杂,但体验差异很大。
还有一个细节:Agent 跑完任务后,最好给一个明确的“完成”信号,而不是让用户猜。执行端在 Agent 进程退出后,推一条{"type": "done"}的消息,手机端收到后把状态从“运行中”改成“已完成”,用户一眼就知道可以看结果了。
3.4 断线重连:手机网络不稳定怎么办
手机网络切换(WiFi 转 4G、进电梯、地铁隧道)是常态,WebSocket 断线几乎必然发生。如果不处理,用户会看到“连接已断开”,然后一脸懵。
处理方案是执行端维护一个消息缓冲区,控制端重连后先拉取断线期间的消息。具体实现上,每条消息带一个递增的序号,控制端重连时带上自己收到的最大序号,执行端把之后的消息补发过去。这样即使断线几分钟,重连后也能看到完整输出。
这个机制我强烈建议加上,因为手机场景下断线太频繁了,没有重连补发的远程控制基本不可用。
提示:断线重连的缓冲区要有上限,比如只保留最近 1000 条消息,否则长时间断线会占满内存。
4. 实操过程:从零搭一套手机操控 Agent 的环境
4.1 执行端环境准备:Ubuntu 上的基础配置
我以 Ubuntu 22.04 为例,这是最常见的执行端环境。首先装好基础依赖:
sudo apt update sudo apt install -y python3 python3-pip nodejs npm git然后装 Claude Code。Claude Code 的安装方式最近变化比较快,我建议直接看官方文档,但核心步骤是:
npm install -g @anthropic-ai/claude-code装完之后验证一下:
claude --version能输出版本号就说明装好了。接下来是 Agent 的运行目录,我建议单独建一个工作区,不要和系统目录混在一起:
mkdir -p ~/agent-workspace cd ~/agent-workspace这个目录就是 Agent 干活的地方,所有文件读写都在这里,方便管理也方便清理。
4.2 控制服务搭建:一个最小的 WebSocket 服务
执行端需要一个 WebSocket 服务来接收手机指令。我用 Python 写一个最小实现,依赖websockets库:
pip install websockets然后写服务端:
import asyncio import websockets import subprocess import json async def handle_command(websocket): async for message in websocket: data = json.loads(message) cmd = data.get("command", "") # 调用 Claude Code process = await asyncio.create_subprocess_exec( "claude", "-p", cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd="/home/user/agent-workspace" ) # 流式读取输出 async for line in process.stdout: await websocket.send(json.dumps({ "type": "output", "payload": line.decode("utf-8", errors="ignore") })) await process.wait() await websocket.send(json.dumps({"type": "done"})) async def main(): async with websockets.serve(handle_command, "0.0.0.0", 8765): await asyncio.Future() asyncio.run(main())这段代码的核心逻辑是:收到指令,起一个 Claude Code 进程,把输出流式推回去。cwd指定工作目录,-p是 Claude Code 的 prompt 参数。
跑起来:
python3 server.py服务就监听在 8765 端口了。
4.3 手机端访问:一个极简的 HTML 页面
手机端不需要复杂的 App,一个 HTML 页面就够。核心是 WebSocket 连接和消息展示:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Agent 控制台</title> </head> <body> <div id="output"></div> <input id="input" placeholder="输入指令..."> <button onclick="send()">发送</button> <script> const ws = new WebSocket("ws://你的服务器IP:8765"); const output = document.getElementById("output"); ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === "output") { output.innerHTML += data.payload.replace(/\n/g, "<br>"); window.scrollTo(0, document.body.scrollHeight); } else if (data.type === "done") { output.innerHTML += "<br><b>[完成]</b><br>"; } }; function send() { const input = document.getElementById("input"); ws.send(JSON.stringify({command: input.value})); output.innerHTML += "<br><b>> " + input.value + "</b><br>"; input.value = ""; } </script> </body> </html>把这个 HTML 放到执行端的某个目录,用 Python 起一个静态服务:
python3 -m http.server 8080手机浏览器访问http://你的服务器IP:8080,就能看到控制界面了。
4.4 内网穿透与安全:让手机在外网也能连上
上面的方案只在同一局域网内可用。要在外网访问,需要做内网穿透。常见方案有 frp、ngrok 这类工具,配置思路是把执行端的 8765 和 8080 端口映射到公网。
但这里有个安全问题必须重视:一旦暴露到公网,任何人都可能连上你的 Agent。所以至少要加一层认证。最简单的做法是在 WebSocket 握手时校验 token:
async def handle_command(websocket, path): token = websocket.request_headers.get("Authorization") if token != "你的密钥": await websocket.close(1008, "Unauthorized") return # ... 正常处理手机端连接时带上这个 token。虽然简单,但能挡住绝大部分扫描。
注意:不要把 Agent 控制服务直接暴露在公网而不加认证。Agent 能执行任意命令,一旦被滥用后果很严重。至少加 token,有条件的话再加 IP 白名单。
4.5 参数调优:超时、并发、缓冲区怎么设
几个关键参数我踩过坑,这里直接给建议值:
| 参数 | 建议值 | 说明 |
|---|---|---|
| Agent 超时 | 300 秒 | 太短任务跑不完,太长卡死占资源 |
| 消息缓冲区 | 1000 条 | 断线重连补发用,太多占内存 |
| WebSocket 心跳 | 30 秒 | 检测连接是否还活着 |
| 并发任务数 | 1 | 新手先用单任务,稳定后再考虑并发 |
| 输出分块大小 | 4KB | 太大手机端渲染卡,太小请求频繁 |
这些值不是绝对的,但作为起点很稳。我一开始超时设了 60 秒,结果稍微大点的任务就被砍了,后来调到 300 秒才够用。
5. 常见问题与排查技巧实录
5.1 Agent 没反应:从进程到网络逐层排查
手机发了指令,Agent 没反应,这是最常见的问题。排查顺序应该是:先看执行端进程有没有起来,再看 WebSocket 有没有收到消息,最后看 Agent 本身有没有输出。
具体操作:在执行端ps aux | grep claude看进程在不在。如果不在,说明指令没传到执行端,检查 WebSocket 连接。如果在但没输出,可能是 Agent 卡住了,看它的日志。我遇到过 Claude Code 因为网络问题卡在初始化阶段,加个超时就能自动恢复。
还有一个隐蔽问题:工作目录权限。如果 Agent 没有工作目录的写权限,它会静默失败。检查一下ls -la ~/agent-workspace,确保当前用户有读写权限。
5.2 输出乱码:ANSI 转义序列的清洗
Agent 输出里经常带颜色码,比如\x1b[32m这种。手机端直接显示就是一堆乱码。解决办法是在执行端清洗:
import re ansi_escape = re.compile(r'\x1b\[[0-9;]*m') clean_output = ansi_escape.sub('', line)这个正则能去掉大部分 ANSI 转义序列。如果还有残留,可以再加一层过滤,只保留可打印字符。
5.3 连接频繁断开:心跳和重连策略
手机端 WebSocket 断线频繁,通常是两个原因:一是网络切换,二是服务端没有心跳。心跳的做法是客户端每隔 30 秒发一个 ping,服务端回 pong,超过一定时间没收到就重连。
重连策略我建议用指数退避:第一次断线 1 秒后重连,第二次 2 秒,第三次 4 秒,最多到 30 秒。这样既能快速恢复,又不会在服务端挂掉时疯狂重试。
5.4 常见问题速查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 手机连不上 | 防火墙/端口未开放 | 检查ufw status,开放对应端口 |
| 指令发出无响应 | WebSocket 未连接 | 手机端看连接状态,重连 |
| Agent 输出为空 | 工作目录权限问题 | chmod确保读写权限 |
| 输出乱码 | ANSI 转义序列 | 执行端正则清洗 |
| 任务卡死 | Agent 超时未设 | 加timeout参数 |
| 断线后丢消息 | 无缓冲区 | 加消息序号和补发机制 |
| 手机端卡顿 | 输出太长 | 分块加载,虚拟滚动 |
5.5 几个我踩过的坑
第一个坑是路径问题。执行端用subprocess调 Agent 时,工作目录默认是服务进程的目录,不是你想的那个。一定要显式指定cwd,否则 Agent 会在错误的地方读写文件。
第二个坑是编码问题。Agent 输出可能是 UTF-8,也可能是系统默认编码,解码时用errors="ignore"能避免崩溃,但会丢字符。更好的做法是统一用 UTF-8,执行端设置PYTHONIOENCODING=utf-8。
第三个坑是资源泄漏。Agent 进程如果没正常退出,会一直占着内存。我加了一个定时清理,超过一定时间还在跑的进程直接 kill。这个逻辑虽然粗暴,但很有效。
第四个坑是手机端输入法。手机输入中文时,回车键行为可能和预期不一样,导致指令提前发送。解决办法是在输入框上监听keydown,只有Shift+Enter才换行,单独Enter才发送。
6. 进阶玩法:让 Agent 主动汇报而不是等你问
基础版是“你问它答”,进阶版是“它主动汇报”。比如 Agent 跑完一个长任务,主动推一条消息到手机:“任务完成,结果如下”。这个体验比你自己去查要好得多。
实现方式是在执行端的任务完成回调里,主动发一条 WebSocket 消息。如果手机不在线,就存到离线消息队列,下次上线补发。这个机制加上之后,Agent 就从“工具”变成了“助手”。
再进一步,可以加定时任务。比如每天早上 9 点,Agent 自动跑一遍数据汇总,结果推到手机。你起床就能看到。这个用cron或者 Python 的schedule库都能实现。
还有一个玩法是多 Agent 协同。执行端起多个 Agent 实例,一个负责代码,一个负责文档,一个负责数据。手机端发指令时指定目标 Agent,各干各的。这个复杂度高一些,但思路是一样的:执行端管调度,控制端管指挥。
我自己最常用的场景是睡前派活。躺床上用手机发一句“把今天的代码提交整理成日报”,Agent 跑完推回来,我看一眼就睡。第二天早上再派一个“跑一遍测试”,结果到公司再看。这种碎片化利用时间的方式,是手机远程操控 Agent 最大的价值。
最后分享一个小技巧:手机端加一个“常用指令”快捷按钮,把高频指令存起来,点一下就发。比每次打字快得多。我存了“跑测试”“整理日志”“生成日报”三个,日常够用了。