跳转到内容

架构

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,不会自动重跑,避免有副作用的操作执行两遍。客户端会提示用户确认后再重发