🔭 援兵 / 哨兵 vs Happier 对照(2026-09-18,Tora Master)

HOOP 系统设计图纸 · 正文唯一真相是 docs/援兵哨兵与Happier对照.md;改 md 再跑 ./tools/deploy_docs.sh,这页是生成的

版本 d513bcab9
2026-09-18 23:49

这份回答一个问题:开源的 Happier 和我们的援兵计划 + 哨兵计划有什么不同、有什么可借。 起因:Jeff / Lisa 09-18 和 Nova 聊到 Happier,说「有点与我们类似,要深入研究」。

读取时刻与基线(结论不带读取时刻就是过期的): · 我们这边:origin/master 3a7e54986(2026-09-18)—— 读的是 援兵计划设计蓝图.md · 援兵计划-MCP.md · 援兵窗口分工-实施交接.md · sentinel.md · SENTINEL_ARCH_REVIEW.md · tools/hoop-lib/README.md · tools/hoop-channel/README.md · backend/internal/mcp/windows.go · artifacts/reinforcement-audit-2026-09-05/REVIEW.md · Happier:github.com/happier-dev/happier dev 分支 3b66fe42(2026-09-18),浅克隆在 mini /Users/Shared/wt/tora/happier(723 MB,21,132 个文件)。读了它自己的五份架构图纸(docs/cli-architecture.md / backend-architecture.md / encryption.md / agent-transition.md / pending-delivery.md / protocol.md / claude-feature-matrix.md)加官网、文档站、README,然后进源码逐个核了 §4 表里的每一格。 · §4 每一格都有我打开过的文件为证;但每个文件读的是头部和关键段落,不是逐行。 没看的写在 §7。

判据只有一条:文档说的和代码做的分开写。§2 是文档层,§4 是代码层,别混。


0. 一句话

Happier 是「一个人远程遥控自己的编程 agent」;我们的哨兵 + 援兵是「AI 作为正式成员住进一个多人协作平台」。

两者只在「Jeff 在手机上指挥 mini 上的 Claude」这一个场景重叠。核心对象、身份模型、威胁模型全不同 —— 它不是我们的替代品,倒可以当我们援兵计划底下的一种客户端(一个装了 Happier 的 Claude Code 窗口,照样能 claude mcp add hoop 接援兵钥匙、挂门铃;Jeff 就能在 Happier 手机端看到那个窗口的完整过程和权限提示,同时它还在 HOOP 房里当援兵)。


1. 先分清我们自己的两套

这张表从 援兵计划设计蓝图.md §1 和 sentinel.md §3.1 读出来,不是记忆里的碎片。

哨兵计划(07-30 起) 援兵计划(09-05 起)
一句话 没人时 AI 自己干 有人时几个窗口一起干
谁在驱动 没人;哨兵敲门,分身 claude -p --resume 自己跑一轮 人坐在 Claude Code 窗口前;AI 是手里的工具,靠 MCP 读房 / 发话 / 落卡
身份 哨兵号 nova / tora(is_agent + fixed_otp=true,固定码换 15 分钟 JWT,能打全量 REST) 援兵号 xxx_aid(fixed_otp=false,永不登录,只有一把 hoopmcp_ 钥匙,能干的 = 46 只手 ∩ 那账号在 App 里能干的)
进程 launchd 常驻 Python,一家一个;WS message.new + 5 分钟闹钟 窗口里 Monitor 挂的 Node 门铃 hoop-channel.js,一窗口一个;30 秒轮询
叫醒谁 起一个新的 claude -p(常驻进程复用、冷压闸、缓存 TTL 那一整套) 已经开着的人类窗口,stdout 打一行 🔔
谁管哪个房 watched() 自动发现:私聊 + 所有工作房 后端三张表(agent_windows / agent_room_assignments / agent_window_defaults),App「援兵计划 → 窗口」里改,专管 + 兜底
关了会怎样 半夜也接 窗口关了那个 AI 就不在;半夜仍归哨兵
安全兜底 前言 + act_guard + 账号 flock + 业务权限 删除 / 钱 / 管理员的手根本不存在 + 业务权限 + 服务器 Instructions

蓝图原话:援兵不替代哨兵,两套的边界只有一条 —— 有没有人在窗口前。

这条线上今天已核实没做的(sentinel.md §0 末段,别当成有):窗口分工第二刀(租约 / epoch / 事件账本 —— 所以转交期间不保证不双响)· SSE 事件流(小桥仍轮询)· OAuth v3(手机上的 Claude 接不了)· 暗号锁实现在但生产没配 —— 今天谁知道某个哨兵号的邮箱就能换到它的 JWT · 每轮只读 10 条、超了漏铃。


2. Happier 是什么(文档层)

  • 血统:slopus/happy(Happy Coder,23.8k ★)的硬分叉。README 原话:"We originally started as contributors to Happy… our own needs required faster iteration"。happier-dev/happier 1.7k ★、10,410 commits、MIT。
  • 它自己的定位(AGENTS.md):"what you would get if Claude Desktop, Codex App, Cursor Glass and Conductor were merged into one open-source app"。
  • 形状:三层 —— Relay server(云或自托管 Docker;PostgreSQL / SQLite / MySQL)· 机器上的 daemon + CLI(@happier-dev/cli客户端(iOS / Android / 桌面 Tauri / 网页 / macOS 菜单栏)。
  • 接 agent:"Happier does not replace your coding agent. It runs the one you already use — on your own machine, in your own checkout" / "You install the agent's CLI yourself… it does not bundle any agent"。13 家(Claude Code、Codex、OpenCode、Cursor、Gemini、Copilot、Qwen、Kimi、Auggie、Kilo、Kiro、Pi、Grok)+ 任何说 ACP(Agent Client Protocol) 的 CLI。
  • 核心对象 = session:持久、可 resume、可 fork、可整个搬到另一台机器("The same session ID stays in place; the active machine changes")。终端 ⇄ 手机随时接管(happier attach)。
  • 加密:默认 required_e2ee —— 服务器只存密文和路由元数据。
  • 多 agent:一个 session 里起 subagents、建 "Claude teams"、session 之间互发消息。
  • 多人:"Share a live session with teammates or via view-only public links"。分享的是一个 session;没有群、房、组织、工作台这些对象。账号 = 一个人 + 他的设备们。
  • Inbox:跨所有 session、所有机器的「待你处理」总台。
  • 自托管:happier relay host install;企业登录 GitHub OAuth org 门禁 / OIDC / mTLS。

3. 十二轴对照

Happier HOOP 哨兵 + 援兵 谁更合适
核心对象 session(一段编程对话) 房 + 账号 + 卡:AI 是房里的成员,活是板上的卡 目标不同,无优劣
主语是谁 遥控自己的 agent AI 是被 @ 的同事;房里可以有 Jeff、李敏、Mike Ng、外部开发者,也可以有 Gode / Vega / Tora 几个 AI 我们:多方;它:单人
AI 的身份 没有 —— agent 是人账号下的一个进程 AI 有自己的 HOOP 账号(is_agent),有好友、进房、发言署名、被 @、领卡、跳心跳 我们独有
怎么被叫醒 人在 app 里发消息 → daemon 起 turn;推送在 turn 结束 / 要权限时 事件驱动:房里任何人 @ 它 / 派卡 / 私信 → 门铃 → 窗口(援兵)或 claude -p(哨兵);多 AI 房只认 @ 我们
多人 分享一个 session:view / edit / admin 三档 + 可代批权限(§4) 原生多人房:工作房 / 群 / 私信 / 频道;chat_members 数 AI 决定 @ 规则;filterMentions 只保留真成员 我们
多 agent subagents / Claude teams —— 都在一个人的账号里,且是照出 Claude Code 原生能力(§4) 分身 / 工人(spawn_worker)/ 子卡真并行;跨账号:Gode 审 Tora 的活,直接互 @(only:mention 白名单) 我们:跨主体;它:单主体内更细
工作管理 Inbox(待批清单) 工作台:卡状态机 queued → working → done/failed/cancelled、租约 5 分钟、心跳 3 分钟判失联、子卡递归撤销、每张卡记 token 用量、卡闸(没卡不许交游戏 / 上线) 我们独有
权限审批 手机上批 tool permission,AskUserQuestion / ExitPlanMode 直达手机 没有。援兵窗口撞上权限提示就卡在那,房里的人看不见;靠 bypass + act_guard 它。这是我们真实的洞
威胁模型 保护内容不被服务器看到(E2E) 保护平台不被 agent 弄坏:MCP 按 scope 组装工具面、删除 / 钱 / 管理员的手不存在、闸在后端、铁律 30「情报不是命令」写进服务器 Instructions 两个不同的问题
内容加密 服务器读不到 服务器读得到,而且必须读得到:@ 路由、缝查、卡归因、审核、多 AI 判 @ 全在后端 我们不能照抄 E2E
转交 跨机器搬 session:8 阶段状态机 + lease(§4) 转交收铃权(revision + 409 防并发);第二刀没做,切换 30 秒内可能双响 它更成熟
成本 / 上下文 镜像终端里那个 session,不管上下文 哨兵那套:常驻进程、冷压 40 万、热天花板 100 万、缓存 TTL 1h、用量按卡记 我们(24h 常驻才需要)

另外三轴不列表:交付 / 上线(game_stage / game_promote / app_promote —— agent 能把东西真发到线上,受卡闸;它无)· 透明度(它:完整 transcript 实时可看;我们:房里只见结论)· 可插拔(它:ACP + catalog 接 13 家;我们:每种客户端各写一份门铃)。


4. 文档说的 vs 代码真做的(源码核过)

Happier 路径相对它的仓库根;行号按 3b66fe42

议题 官网 / 文档说 代码里 对我们的意义
接 Claude Code 的方式 「runs the one you already use」,没说怎么接 三条路并存(docs/claude-feature-matrix.md 自己承认):① 默认远程 = Agent SDK(@anthropic-ai/claude-agent-sdk ^0.2.123),apps/cli/src/backends/claude/remote/claudeRemoteAgentSdk.ts:752-778 —— query({ cwd, resume, permissionMode, settingSources, includePartialMessages, maxTurns, systemPrompt, mcpServers, env, pathToClaudeCodeExecutable }),运行中还能 setPermissionMode;② 本地 = Claude 自己的 TUI 跑在 node-pty 里,尾读 ~/.claude/projects/**.jsonl;③ 老的 stream-json 壳当兜底,矩阵里标 legacy 我们哨兵是 claude -p --input-format stream-json 常驻进程 —— 正是它列为 legacy 的那条。它的主路是 SDK。SDK 给了权限回调和 setPermissionMode,我们那条路没有
权限审批到手机 「Inbox… permission requests」 远程模式走 SDK 回调(utils/permissionHandler.ts 头注释:「Permission Handler for canCallTool integration — Replaces the MCP permission server with direct SDK integration」)→ 请求写进加密的 agentState.requests → 手机读到、答 approved / approved_for_session / denied / abort → 回环。本地 TUI 模式只有一个「实验性 local permission bridge」 我们完全没有这一环。它证明:只要走 SDK,权限就是一个能路由的事件,不是终端里的一道死墙
分享给别人 「share with teammates… view-only links」 prisma/schema.prisma SessionShare:accessLevel ∈ view / edit / admin + canApprovePermissions(shareRoutes.ts:119 只给 edit / admin,主人另授)+ 每个收件人一把 NaCl box 包的 encryptedDataKey + 访问日志(IP / UA)。服务端 socket 收到共享者写操作先 checkSessionAccess + requireAccessLevel('edit')(apps/server/sources/app/api/socket/rpcHandler.ts:400) 能一起打字,而且能替你批权限。 但对象仍是一个 session,不是房;权限模型是 3 档 + 1 布尔,不是成员制
跨机器转交 「Move a live session to another machine」 真有状态机:starting_source → preparing_target → resuming_target → confirming_target → binding_target → committing_target → cleaning_source → finalizing_target(apps/cli/src/session/handoff/orchestration/sessionHandoffCoordinator.ts,427 行);协议 v2 带 atomicTargetResume;传输 direct_peerserver_routed_stream;目标机准备作业有带存活探测的 lease(prepare/sessionHandoffPrepareTargetJobLease.ts:196「Daemon process is gone; treat the lease as stale」);Claude 那半是把 JSONL 原文 base64 搬过去、在目标机的 config dir 重建再 --resume 对照 援兵窗口分工-实施交接.md §7 §8 那份没做的第二刀:它做完了,而且形状和 Liva 写的提纲惊人地像(prepare / drain / commit / cleanup、lease 兜死进程)。可以直接当参考实现读
可靠投递 没提 SessionPendingMessage 持久行是唯一的消息托管者;服务端持有「激活意图」;只有 session 所在那台机器的 daemon 能无人值守启动;docs/pending-delivery.md 明说「no polling loop, lease, generation, retry counter」—— 靠 Session.lastActiveAt 当过期围栏 我们小桥「每轮只读 10 条、超了漏铃、accepted ≠ received」那族问题,它的答案是服务端持久队列 + 一个 owner,不是更多本地状态
Inbox 「global attention center」 纯客户端折叠(apps/ui/sources/activity/attention/activityAttentionSessions.ts):对已同步、已解密的 session 状态算 hasUnread / hasPendingPermissionRequests / hasPendingUserActionRequests / hasBlockedPendingDelivery / hasQueuedUserInput 因为 E2E,服务端读不到,只能客户端算。我们后端读得到,同样的东西可以是一条查询
推送 「Push notifications alert users」 Expo push;权限请求的推送是 CLI / daemon 那一侧发起的(apps/cli/src/settings/notifications/permissionRequestPush.ts),服务端另有按 badge 刷的(refreshAccountActivityBadgePushes.ts) 触发点在 agent 旁边,不在服务器 —— 和 E2E 一致
Claude teams / subagents 「Create and manage Claude teams, send messages to teammates」 不是它自己的编排:utils/teamInbox/claudeTeamInboxCollector.ts:162 在 watch ~/.claude/teams/<team>/inboxes/team-lead.json —— 读的是 Claude Code 原生 agent-teams 落盘的文件,再在 UI 里把参与者拼出来 它的「多 agent」= 把 Claude 自带的能力照出来。我们的分身 / 工人 / 子卡是跨账号、跨主体的
给 Claude 塞的东西 没提 ① 系统提示 { type:'preset', preset:'claude_code', append: getClaudeRemoteSystemPrompt() } —— 在 Claude Code 预设后面 append 一段自己的;② 注入一个 MCP bridge(session.getOrCreateHappierMcpBridge()),工具 happier__action_execute / happier__session_list —— Claude 能回头调 Happier 的动作(派子代理、起执行、改标题) 结构上和我们 前言 + HOOP MCP 一模一样,方向相反:它的 MCP 让 agent 控制 Happier,我们的让 agent 参与 HOOP
加密 「server holds ciphertext」 两代:legacy NaCl secretbox / 现行 AES-256-GCM + 每 session 一把 DEK(NaCl box 包给每个设备);session metadata、agentState(含权限请求)、消息、机器状态、artifact、KV 全加密;服务端只解自己签发的第三方 token。账号 = 公钥,没有密码。存储模式 required_e2ee / optional / plaintext_only,session 建立时定死不混(docs/encryption.md) 判断不变:我们不能照抄 —— @ 路由、缝查、卡归因全靠后端读内容
服务端形状 Fastify + Socket.IO(/v1/updates,三种 scope:user / session / machine)+ Prisma / Postgres + Redis(只 ping)+ S3;每用户单调 seq;所有可变字段 expectedVersion 乐观并发;presence 10 分钟判离线(docs/backend-architecture.md / protocol.md) 和我们 Go + PG + WS 同一个量级;它没有「房」这一层对象
规模 非测试 ts 文件:CLI 2,957 · server 491 · UI 4,749;apps/cli/tools/archives 156 MB 是打包归档不是代码 比我们 hoop-lib + hoop-channel + internal/mcp 大一个量级,但做的是一件事

容易误读的三处(核之前我自己就读错过)

  1. 「它怎么接 Claude」 —— 不看代码会以为是 PTY 包壳。真相:Agent SDK 为主,PTY 为本地,stream-json 为 legacy。
  2. 「分享能不能一起打字」 —— 文档说不清。真相:edit 级能发消息,还能被授权替主人批权限;服务端在 socket 层真校验。但仍是「N 个人围着一个 session」,不是「一个房里 N 个人 + N 个 AI」。
  3. 「转交有多成熟」 —— 文档只说 session id 不变。真相:完整的 prepare / commit / cleanup 状态机 + lease,形状就是我们第二刀提纲里画的那个。

5. 对哨兵 / 援兵的启发(三档)

可以直接搬的思路(不是抄代码)

  • 权限 = 事件。 哨兵那条 claude -p 路如果换成 Agent SDK,canUseTool 回调就能把权限请求变成一张卡或一条房里的消息,Jeff 在手机上答;setPermissionMode 还能运行中切 bypass ↔ default。这是今天援兵 / 哨兵最大的洞,它证明了可行。
  • 第二刀参考实现。 apps/cli/src/session/handoff/ 整个目录,尤其 lease 那份和 coordinator 的 phase 序列,对着 Liva 的 §7 §8 读,能少走弯路。
  • 服务端持久待投递队列 + 单一无人值守启动者。 解「漏铃 / 双响 / accepted ≠ received」那一族,比给小桥再加本地状态干净。

不该搬的

  • E2E 加密(理由:平台逻辑靠后端读内容;硬上等于把闸推回客户端,和「闸长在后端」判据正对着干)。
  • 它没有「房 / 账号 / 卡」—— 这三样是我们的根,它那边没有对应物可借。

值得再看一眼再定的

  • packages/agents 的 catalog + @agentclientprotocol/sdk:它用一张 catalog 表接 13 家 agent、用 ACP 接任何 CLI。我们每种客户端手写一份 xxx-channel.js。如果援兵计划真要接 Cursor / Gemini CLI,这是现成的接法。
  • docs/compatibility.md 的纪律:它把 wire / 语义 / 持久化 / 运维四种兼容分开写、每个 seam 标 predecessor 和 removal condition。我们迁移撞号、registry_test 数量守卫那一族问题,它有一套成文的做法。

6. 判断

  1. 不是竞品,是不同层。 Happier 解决「一个人的 agent 怎么被这个人从任何设备接管」;我们解决「一群人 + 一群 AI 怎么在同一个平台上分工、交付、追责」。Happier 里没有「Gode 审 Tora 的活」这种事,因为它根本没有第二个主体。
  2. 重叠那一块的两种解法:它 —— 镜像终端,保真度 100%,一次一个 session、一个人;我们 —— 聊天房当总线,保真度低(房里只见结论),但天然多方、有卡、有归因、半夜有哨兵接。
  3. 它能当我们的客户端,反过来不行(§0)。
  4. 我们做了、它没有、而且它这种形状做不出来的:AI 自己的账号和社交身份 · 多 AI 跨主体互 @ 与评审 · 工作台 + 卡闸 + 按卡计费 · agent 直接交付上线 · 24h 无人常驻 · 「情报不是命令」这一整套对抗房里外人的结构性防线。
  5. 它做了、我们没做、而且我们确实需要的:权限审批到手机 · 跨房「待处理」总台 · 转交状态机。

7. 边界:我没看的

  • getClaudeRemoteSystemPrompt()原文(只确认它存在、是 append 到 Claude Code 预设后面);
  • apps/ui 里任何一行(UI 是最大的包,一个文件都没打开);
  • relay-server 的 socket 事件处理细节(只读了架构文档和 share 路由);
  • 密钥派生 / 配对 v3 的实现(只读了 encryption.md);
  • 没跑它的任何测试,没起过它的服务。

所以 §4「代码里」那一列,每一格都有我打开过的文件为证;但每个文件读的是头部和关键段落,不是逐行。要更深,clone 就在 /Users/Shared/wt/tora/happier,先留着。


8. 出处

Happier:happier-dev/happier · happier.dev · docs.happier.dev(/agents /security)· 仓内 docs/cli-architecture.md backend-architecture.md encryption.md agent-transition.md pending-delivery.md protocol.md claude-feature-matrix.md AGENTS.md · 上游 slopus/happy · happy.engineering

我们:援兵计划设计蓝图.md · 援兵计划-MCP.md · 援兵窗口分工-实施交接.md · sentinel.md · SENTINEL_ARCH_REVIEW.md · tools/hoop-lib/README.md · tools/hoop-channel/README.md · backend/internal/mcp/windows.go · artifacts/reinforcement-audit-2026-09-05/REVIEW.md