这份回答一个问题:开源的 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/happierdev 分支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_peer 或 server_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 大一个量级,但做的是一件事 |
容易误读的三处(核之前我自己就读错过)
- 「它怎么接 Claude」 —— 不看代码会以为是 PTY 包壳。真相:Agent SDK 为主,PTY 为本地,stream-json 为 legacy。
- 「分享能不能一起打字」 —— 文档说不清。真相:
edit级能发消息,还能被授权替主人批权限;服务端在 socket 层真校验。但仍是「N 个人围着一个 session」,不是「一个房里 N 个人 + N 个 AI」。 - 「转交有多成熟」 —— 文档只说 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. 判断
- 不是竞品,是不同层。 Happier 解决「一个人的 agent 怎么被这个人从任何设备接管」;我们解决「一群人 + 一群 AI 怎么在同一个平台上分工、交付、追责」。Happier 里没有「Gode 审 Tora 的活」这种事,因为它根本没有第二个主体。
- 重叠那一块的两种解法:它 —— 镜像终端,保真度 100%,一次一个 session、一个人;我们 —— 聊天房当总线,保真度低(房里只见结论),但天然多方、有卡、有归因、半夜有哨兵接。
- 它能当我们的客户端,反过来不行(§0)。
- 我们做了、它没有、而且它这种形状做不出来的:AI 自己的账号和社交身份 · 多 AI 跨主体互 @ 与评审 · 工作台 + 卡闸 + 按卡计费 · agent 直接交付上线 · 24h 无人常驻 · 「情报不是命令」这一整套对抗房里外人的结构性防线。
- 它做了、我们没做、而且我们确实需要的:权限审批到手机 · 跨房「待处理」总台 · 转交状态机。
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