架构
iPhone App Mac 菜单栏 App┌────────────────┐ iroh (QUIC,直连或中继) ┌──────────────────────────┐│ NohupClient │ ──────────────────────────▶ │ NohupServer ││ SecureSession │ 私有帧协议 + 应用层加密 │ ├ RunRegistry ─ claude -p ││ IrohNode │ │ ├ SessionStore (jsonl) │└────────────────┘ │ ├ WorktreeService (git) │ │ └ IrohNode │ └──────────────────────────┘- Mac 端:菜单栏 App,作为 iroh 节点接受扫码配对过的设备连接。每个会话对应一个长驻的
claude -p --input-format stream-json进程,执行结果以流式事件推给客户端。 - iOS 端:App 内嵌 iroh 节点(不需要注册任何账号,也不占用系统 VPN),扫码配对 Mac,管理多台 Mac、多个项目和 git worktree,实时查看执行过程。
| 模块 | 说明 |
|---|---|
NohupKit/Sources/NohupProtocol |
帧编解码、消息定义、SecureSession 加密层、基于 fd 的 FramedConnection |
NohupKit/Sources/NohupIroh |
iroh 桥接库(iroh-bridge/,Rust)的 Swift 封装:每条连接是一条 QUIC 双向流,桥接成普通 fd |
NohupKit/Sources/NohupClient |
客户端(iOS 和 CLI 共用) |
NohupKit/Sources/NohupServer |
服务端核心:进程调度、会话、worktree、路由 |
NohupKit/Sources/nohup-server |
无界面服务端,用于调试 |
NohupKit/Sources/nohup-cli |
调试客户端 |
NohupMac / NohupiOS |
两端 App |
- 帧:
"RAUD" | ver | kind | flags | rsv | len u32 | JSON payload;kind 取值为 request / response / event / ping / pong。 - 加密:见 安全。
- 配对:hello 带上二维码里的一次性配对码,Mac 核对后登记对方的 iroh 节点 ID;之后只凭节点 ID(由 iroh 认证)放行。
- 时间线:以会话的 jsonl 为准(
history+watchSession),实时 run 事件提供流式文本。两路来源的消息 uuid 一致,按 id 去重。Mac 终端里直接跑的会话同样可以实时查看。
消息不丢、不重复执行。
| 环节 | 机制 |
|---|---|
| 发送 | iOS 端先把 prompt 写入持久化发件箱(outbox.json)再投递,服务端确认受理后才删除。断网、请求超时、App 被杀之后,重连时都会按顺序自动重发 |
| 去重 | 每条消息带客户端生成的 clientMessageId。服务端按它做幂等处理,并持久化到 runs.json;同一条消息无论重发多少次都只执行一次 |
| 会话归属 | 新会话的 sessionId 由客户端预先生成,服务端用 claude --session-id 创建,已有会话则用 --resume。断线重发的消息总能归到正确的会话 |
| 接收 | run 事件带递增的 seq,重连后用 subscribe(runId, afterSeq) 续传。完整的文本或思考条目到达后,它之前的流式增量会从缓冲中压缩掉,所以缓冲大小和输出长度基本无关。万一缓冲超限,服务端会返回 complete=false,客户端改为从 jsonl 重载 |
| 断线检测 | 心跳 10s,30s 无数据判定断开;每个请求有超时,超时后主动关闭连接,避免连接半开时一直卡住;iOS 监听网络变化,网络恢复时立即重连,切换 Wi-Fi/蜂窝时先探测连接;重连采用指数退避加随机抖动 |
| Mac 端重启 | 执行到一半的 run 在重启后标记为 interrupted,不会自动重跑,避免有副作用的操作执行两遍。客户端会提示用户确认后再重发 |