a4phone 的远程续聊让你直接向手机订阅的通知话题发送文字,即可与最近一次会话(Claude Code / Codex / DSH)继续对话——AI 回复自动推回手机,形成完整的远程对话闭环。续聊由后台守护进程(a4p listen)监听主话题驱动,也支持 a4p resume 手动续聊。本文章介绍续聊的完整机制、三种 Agent 的实现差异、积压合并与 Codex 会话 fork。
1. 功能概述
| 维度 | 说明 |
|---|---|
| 入口 | 手机向主话题 {topic} 发送文字;或电脑端 a4p resume 内容 |
| 驱动方式 | 后台守护进程 a4p listen(推荐)或手动命令 |
| 续聊目标 | 最近会话(last.json 记录,按 Agent 自动选择续聊方式) |
| 回复回传 | AI 回复经 ntfy 推送回手机主话题 |
| 超时控制 | resumeTimeout(默认 30 分钟) |
| 版本要求 | 续聊主话题机制自 1.1.5 起,DSH 续聊自 1.2.0 起 |
1.1 续聊闭环
发送文字] --> B[守护进程
a4p listen] B --> C[读取最近会话
last.json] C --> D{会话 Agent} D -->|Claude Code| E[claude --resume
--continue -p] D -->|Codex| F[codex exec resume
-o 捕获回复] D -->|DSH| G[文件队列交给
dsh web 插件] G --> H[agent.followup
注入桌面会话] E --> I[AI 回复写入会话] F --> I H --> I I --> J[回复推回手机] J --> A style A fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style B fill:#fdebd0,stroke:#b7950b,stroke-width:2px style C fill:#d6eaf8,stroke:#1a6b8a,stroke-width:2px style D fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style E fill:#ffecd6,stroke:#e67e22,stroke-width:2px style F fill:#ffecd6,stroke:#e67e22,stroke-width:2px style G fill:#fadbd8,stroke:#c0392b,stroke-width:2px style H fill:#d6eaf8,stroke:#1a6b8a,stroke-width:2px style I fill:#d5f5e3,stroke:#27ae60,stroke-width:2px style J fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px
2. 使用方式
2.1 手机端续聊(守护进程)
- 启动续聊守护进程(后台无窗口运行):
a4p listen # 后台运行
a4p listen --status # 查看状态
a4p listen --stop # 停止
-
手机 ntfy App 已订阅主话题
{topic}(a4p setup生成,扫描二维码即可),向该话题发送任意文字。 -
守护进程收到消息后自动续聊最近会话,AI 回复推回手机。日志写入
~/.a4phone/daemon.log。
不需要额外的续聊话题——你订阅的通知话题本身就是对话通道。
2.2 电脑端手动续聊
无需守护进程时,也可在电脑上手动续聊:
a4p resume 帮我总结一下刚才的改动
2.3 查看最近会话
a4p last
输出最近会话的会话 ID、工作目录、来源 Agent 与时间。
3. 各 Agent 的续聊实现
3.1 Claude Code
claude --resume <会话ID> --continue -p
headless 运行,手机文字通过 stdin 作为下一条用户消息注入,stdout 捕获回复。
3.2 Codex
codex exec resume <会话ID> -o <临时文件> -
headless 运行,stdin 作为消息,-o 把最后一条回复写入文件后由 a4phone 读取回推。若会话被窗口占用(thread-store conflict),自动 fork 成新线程续聊(见第 5 节)。
3.3 DSH(DeepSeek Harness)
不另起进程:a4p 把手机文字原子写入 ~/.a4phone/dsh-jobs/req-<id>.json,dsh web 进程内的 dsh-hook 插件扫描队列后,用 agent.followup() 把消息直接注入桌面上正在运行的同一个会话,等待轮次结束后提取回复写入 resp-<id>.json,a4p 轮询取回并推回手机。手机消息与 AI 回复实时出现在桌面会话里,手机与桌面看到同一段对话。详见DSH支持与插件挂载。
4. 积压合并
一轮续聊最长可达 resumeTimeout(默认 30 分钟),期间手机连续发来的多条消息若逐条排队,每条都会触发一个独立续聊轮次,内容会被无限期延迟。a4phone 的积压合并机制把忙时到达的消息合并为一个批次:
- 当前轮结束后一次性处理整批(任意数量积压最多只多产生一个续聊轮次)
- 批次内容按到达顺序拼接,保证手机内容一定能送达 AI
- 批次持久化到
~/.a4phone/pending-batch.json,守护进程重启/崩溃后自动恢复,不丢消息 - 批次一旦交给续聊管线即删除持久化文件(at-most-once),避免同一条消息重复注入会话
进行中] B[手机消息 2] --> E[积压队列
合并批次] C[手机消息 3] --> E D -->|本轮结束| F[一次性续聊
整个批次] E --> F F --> G[回复推回手机] G -->|下一轮| E style A fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style B fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style C fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style D fill:#ffecd6,stroke:#e67e22,stroke-width:2px style E fill:#fdebd0,stroke:#b7950b,stroke-width:2px style F fill:#d5f5e3,stroke:#27ae60,stroke-width:2px style G fill:#fadbd8,stroke:#c0392b,stroke-width:2px
5. Codex 会话 fork
Codex 会话同一时间只能被一个进程持有写锁(thread-store conflict)。若续聊时原会话仍被 Codex 窗口占用,a4phone 自动把会话 fork 成新线程续聊:
- 定位原会话的 rollout 文件(
~/.codex/sessions/**/rollout-*-<id>.jsonl) - 复制内容并替换为新生成的线程 ID,更新时间戳使其成为最新会话
- 在 fork 出的新会话上执行续聊,并更新
last.json记录
原会话(含原窗口)毫发无损、可继续使用,手机对话在 fork 上继续,多轮对话天然成立——不需要关闭原窗口。
6. 过时消息过滤
守护进程按消息时间戳过滤:如果新会话开启时间晚于手机消息的发送时间,则丢弃该消息,避免旧消息被注入到新会话中。会话切换后,早于新会话创建时间的积压消息不会污染新会话。
7. 常见问题
问:续聊期间 AI 提问或请求权限怎么办?
答:续聊期间守护进程自动临时切换为外出模式,续聊回合内的提问/审批仍推送到手机,结束后恢复原模式,形成完整闭环。
问:Claude Code 续聊前需要先关闭原会话吗?
答:需要。Claude Code 会话同一时间只能被一个进程占用,--resume 续聊前请先结束终端里仍在运行的原会话;Codex 会话被占用时 a4phone 会自动 fork 新线程续聊,无需关闭原窗口。
问:手机发来的消息会重复注入吗?
答:不会。批次处理采用 at-most-once 语义(取批即删持久化文件),DSH 续聊请求文件也是先删除再处理,进程崩溃不会导致同一条消息重复注入。
问:续聊超时会怎样?
答:超过 resumeTimeout(默认 30 分钟)自动中断,超时前已生成的回复仍会推回手机。
举手提问