a4phone 的远程续聊让你直接向手机订阅的通知话题发送文字,即可与最近一次会话(Claude Code / Codex / ZCode / 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 起,ZCode 续聊自 1.6.0 起

1.1 续聊闭环

graph LR A[手机向主话题
发送文字] --> 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 -->|ZCode| K[zcode.cjs --prompt
--resume --json] D -->|DSH| G[文件队列交给
dsh web 插件] G --> H[agent.followup
注入桌面会话] E --> I[AI 回复写入会话] F --> I K --> 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 K fill:#f9e79f,stroke:#b7950b,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 手机端续聊(守护进程)

  1. 启动续聊守护进程(后台无窗口运行):
a4p listen          # 后台运行
a4p listen --status # 查看状态
a4p listen --stop   # 停止
  1. 手机 ntfy App 已订阅主话题 {topic}a4p setup 生成,扫描二维码即可),向该话题发送任意文字。

  2. 守护进程收到消息后自动续聊最近会话,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>.jsondsh web 进程内的 dsh-hook 插件扫描队列后,用 agent.followup() 把消息直接注入桌面上正在运行的同一个会话,等待轮次结束后提取回复写入 resp-<id>.json,a4p 轮询取回并推回手机。手机消息与 AI 回复实时出现在桌面会话里,手机与桌面看到同一段对话。续聊失败(心跳过期、超时、插件中途退出)会把失败原因推回手机。详见DSH支持与插件挂载

3.4 ZCode(1.6.0 起)

node zcode.cjs --prompt <手机消息> --resume <会话ID> --json --mode yolo --cwd <目录> --no-color

a4phone 自动探测 ZCode 桌面端内置的 CLI(resources/glm/zcode.cjs),headless 续聊指定会话,从 JSON 输出的 response 字段取回复推回手机。前提是 ZCode 桌面端已安装(环境变量 ZCODE_WINDOWS_APP_INSTALL_DIR 或常见安装位置探测)。

模型自动跟随:ZCode 的模型与 provider 由桌面 App 管理(~/.zcode/v2/config.json),headless 续聊需要 ~/.zcode/cli/config.json 里有显式模型配置。a4phone 在每次续聊前从该会话的 rollout 记录读取实际使用的模型,并从注册表同步对应 provider 定义到 ~/.zcode/cli/config.json——你在桌面端切换模型后,续聊自动跟随切换后的模型;a4p setup 会先写入一个默认模型配置(注册表中第一个自定义 provider)。

ZCode 续聊要求最近会话的 rollout 记录可定位(会话曾实际产生过模型请求);若无法确定模型,会明确报错并提示先在桌面端运行该会话。

4. 积压合并

一轮续聊最长可达 resumeTimeout(默认 30 分钟),期间手机连续发来的多条消息若逐条排队,每条都会触发一个独立续聊轮次,内容会被无限期延迟。a4phone 的积压合并机制把忙时到达的消息合并为一个批次:

  • 当前轮结束后一次性处理整批(任意数量积压最多只多产生一个续聊轮次)
  • 批次内容按到达顺序拼接,保证手机内容一定能送达 AI
  • 批次持久化到 ~/.a4phone/pending-batch.json,守护进程重启/崩溃后自动恢复,不丢消息
  • 批次一旦交给续聊管线即删除持久化文件(at-most-once),避免同一条消息重复注入会话
graph LR A[手机消息 1] --> D[续聊轮次
进行中] 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 成新线程续聊:

  1. 定位原会话的 rollout 文件(~/.codex/sessions/**/rollout-*-<id>.jsonl
  2. 复制内容并替换为新生成的线程 ID,更新时间戳使其成为最新会话
  3. 在 fork 出的新会话上执行续聊,并更新 last.json 记录

原会话(含原窗口)毫发无损、可继续使用,手机对话在 fork 上继续,多轮对话天然成立——不需要关闭原窗口。

6. 过时消息过滤

守护进程按消息时间戳过滤:如果新会话开启时间晚于手机消息的发送时间,则丢弃该消息,避免旧消息被注入到新会话中。会话切换后,早于新会话创建时间的积压消息不会污染新会话。

7. 常见问题

问:续聊期间 AI 提问或请求权限怎么办?

答:续聊期间守护进程自动临时切换为外出模式,续聊回合内的提问/审批仍推送到手机,结束后恢复原模式,形成完整闭环。

问:Claude Code 续聊前需要先关闭原会话吗?

答:需要。Claude Code 会话同一时间只能被一个进程占用,--resume 续聊前请先结束终端里仍在运行的原会话;Codex 会话被占用时 a4phone 会自动 fork 新线程续聊,无需关闭原窗口;ZCode 续聊走 headless CLI,无会话锁冲突。

问:ZCode 续聊需要什么前置条件?

答:ZCode 桌面端已安装(自动探测 zcode.cjs),且 ~/.zcode/cli/config.json 已写入显式模型配置——a4p setup 会自动写入默认配置,之后每次续聊还会按会话实际模型自动同步;桌面端切换模型后无需手动处理。

问:手机发来的消息会重复注入吗?

答:不会。批次处理采用 at-most-once 语义(取批即删持久化文件),DSH 续聊请求文件也是先删除再处理,进程崩溃不会导致同一条消息重复注入。

问:续聊超时会怎样?

答:超过 resumeTimeout(默认 30 分钟)自动中断,超时前已生成的回复仍会推回手机。