a4phone 通过内置的 dsh-hook Cordis 插件让 DeepSeek Harness(DSH)同样具备完整的手机远程交互能力:任务完成通知、AI 提问手机作答、权限请求手机审批、以及直接注入桌面会话的远程续聊a4p setup 检测到 DSH 环境时自动把插件挂载到 web profile,手机端无需额外订阅。本文章介绍 DSH 支持的事件覆盖、插件挂载机制、续聊实现与心跳检测。

1. DSH 支持概览

Hook DSH 事件 手机交互(外出模式)
任务完成 turn/endreason.kind === 'completed' 系统通知 + 手机推送(含 AI 最后输出)
AI 提问 ask_user_question 工具调用(tools/execute 拦截) 手机点选选项 / 文字自由作答
权限请求 approval/request 手机 Approve / Deny
远程续聊 文件队列 ~/.a4phone/dsh-jobs/ 手机发文字 → 注入桌面会话 → 回复推回手机

1.1 与 Claude Code / Codex 的差异

维度 Claude Code / Codex DSH
接入方式 Hook 命令(a4p hook Cordis 插件(dsh-hook)
事件来源 外部进程 Hook 协议 DSH 内部会话事件流
远程续聊 另起 headless 进程 进程内 agent.followup 注入当前会话
会话锁 Claude 独占锁 / Codex 写锁 无锁冲突
挂载方式 写 settings.json / config.toml cordis.patch.yml(热生效)
支持版本 全部版本 插件挂载自 1.1.6,续聊自 1.2.0

2. 插件挂载机制

2.1 自动挂载

a4p setup 检测到 DSH 环境(~/.dsh/profiles/web 目录存在)时,把本包 dsh/ 目录下的插件以 insert 形式写入 web profile 的 cordis.patch.yml

# ===== a4phone dsh-hook (auto-generated) =====
- insert:
    - id: dsh-hook
      name: "C:/ProgramMine/a4phone/dsh/lib/index.js"
# ===== end a4phone dsh-hook =====
  • 幂等可重复执行:已挂载相同路径时自动跳过
  • 若检测到旧版手动挂载(指向 C:\ProgramMine\dsh-hookid: dsh-hook),自动替换为本包路径
  • cordis.patch.yml 被 DSH 热监视(watchUserPatches),挂载即时生效,无需重启
  • 插件代码更新后建议重启 dsh web 使新代码生效

2.2 卸载

a4p uninstall 会同时移除该挂载(marker 包裹的插件块 + 旧版手动挂载块)。

3. 插件架构

dsh-hook 插件监听 DSH 的 Cordis 事件,与 a4phone 复用同一套配置与推送模块:

graph LR A[dsh web 进程] --> B[dsh-hook 插件
cordis 插件] B --> C[session/event
会话事件流] B --> D[tools/execute
工具执行拦截] B --> E[approval/request
审批请求拦截] B --> F[续聊服务
resume-service] C --> G[任务完成通知
+ AI 最后输出] D --> H[提问推送手机] E --> I[审批推送手机] F --> J[文件队列
req / resp] G --> K[ntfy.sh] H --> K I --> K J --> K K --> L[手机] style A fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style B fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style C fill:#d6eaf8,stroke:#1a6b8a,stroke-width:2px style D fill:#d6eaf8,stroke:#1a6b8a,stroke-width:2px style E fill:#d6eaf8,stroke:#1a6b8a,stroke-width:2px style F fill:#fdebd0,stroke:#b7950b,stroke-width:2px style G fill:#d5f5e3,stroke:#27ae60,stroke-width:2px style H fill:#d5f5e3,stroke:#27ae60,stroke-width:2px style I fill:#d5f5e3,stroke:#27ae60,stroke-width:2px style J fill:#fadbd8,stroke:#c0392b,stroke-width:2px style K fill:#ffecd6,stroke:#e67e22,stroke-width:2px style L fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px

3.1 事件日志

插件把每个 hook 事件写入 ~/.a4phone/dsh-logs/,按事件分类:

日志文件 记录内容
task-complete.jsonl 任务完成事件(会话、轮次、reasonKind)
question-asked.jsonl 提问工具调用(问题与选项)
permission-request.jsonl 审批请求与决策(approval/asked、approval/decided)

4. DSH 远程续聊

4.1 与 Claude Code / Codex 续聊的本质区别

DSH 续聊不另起进程,而是让 dsh web 进程内的插件直接把手机消息注入桌面上正在运行的同一个会话——手机消息与 AI 回复都会实时出现在桌面端会话里,手机与桌面看到同一段对话,且不存在会话锁冲突。

4.2 文件队列协议

graph LR A[a4p 守护进程
a4p listen] --> B[原子写入
req-<id>.json] B --> C[dsh web 进程
插件每秒扫描] C --> D[解析目标 agent] D --> E[agent.followup
注入用户消息] E --> F[await whenIdle
等待轮次结束] F --> G[提取最后
assistant 文本] G --> H[写 resp-<id>.json] H --> I[a4p 轮询取回] I --> J[推送回复到手机] C --> K[刷新心跳
dsh-heartbeat.json] style A fill:#fdebd0,stroke:#b7950b,stroke-width:2px style B fill:#fadbd8,stroke:#c0392b,stroke-width:2px style C fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style D fill:#d6eaf8,stroke:#1a6b8a,stroke-width:2px style E fill:#d5f5e3,stroke:#27ae60,stroke-width:2px style F fill:#d6eaf8,stroke:#1a6b8a,stroke-width:2px style G fill:#d5f5e3,stroke:#27ae60,stroke-width:2px style H fill:#fadbd8,stroke:#c0392b,stroke-width:2px style I fill:#fdebd0,stroke:#b7950b,stroke-width:2px style J fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style K fill:#fdebd0,stroke:#b7950b,stroke-width:2px

协议要点:

  1. 记录最近会话:插件在每次顶层会话 turn/end(completed)时写入 ~/.a4phone/last.jsonagent: "DSH"),与 Claude Code / Codex 的 Stop Hook 记录一致
  2. 请求:a4p 把手机文字原子写入 req-<id>.json(含目标 sessionId 与文本),插件每秒扫描
  3. 目标解析:优先使用请求指定的 sessionId(仍 live 的会话),否则兜底最近一个顶层会话
  4. 注入agent.followup() 把文字作为普通 user/message 写入桌面正在运行的会话,await agent.whenIdle() 等待轮次结束
  5. 回复提取:从会话事件日志中提取 followup 之后最后一条非空 assistant 文本作为回复
  6. 响应:写 resp-<id>.json,a4p 轮询取回并推回手机
  7. at-most-once:请求文件先删除再处理,进程崩溃不会导致同一条手机消息重复注入

4.3 心跳检测

插件每次轮询刷新 ~/.a4phone/dsh-heartbeat.json{ alive: true, ts })。a4p 据此快速判断 dsh web 是否在运行

  • 心跳超过 10 秒视为插件未运行,续聊请求快速失败并给出提示,而不是干等 30 分钟超时
  • 续聊轮询期间周期复查心跳,插件中途死亡立即中断并提示

4.4 续聊前提

  • dsh web 正在运行,且已挂载新版 dsh-hook 插件(a4p setup 自动挂载,插件代码更新后需重启 dsh web
  • 未检测到 dsh web 时续聊快速失败并给出提示

5. 任务完成推送去重

续聊轮次的"任务完成"推送已自动去重:插件检测到会话正在被续聊服务驱动(inflight),跳过重复通知,回复统一由 a4p 推回手机。

6. 常见问题

问:DSH 插件挂载后需要重启 DSH 吗?

答:不需要。cordis.patch.yml 被 DSH 热监视,挂载即时生效;但插件代码更新后建议重启 dsh web 使新代码生效。

问:DSH 模式切换(a4p out / a4p home)需要重启 DSH 吗?

答:不需要。插件在每次 tools/executeapproval/request 事件时实时读取 ~/.a4phone/mode.json 判断当前模式,与 Claude Code / Codex 的 Hook 机制一致,切换即时生效。

问:手机端需要额外订阅 DSH 的话题吗?

答:不需要。DSH 插件复用与 Claude Code / Codex 相同的 a4phone 话题与模式,手机端同一订阅即可收到 DSH 的推送。

问:DSH 续聊会另起进程吗?

答:不会。DSH 续聊直接发生在桌面正在运行的会话上,手机与桌面看到同一段对话,且无会话锁冲突。