手机回复 WorkBuddy 会话 · 架构与实施方案(2026-09-28)
目标(用户原话):让用户能通过手机对 WorkBuddy 中对话进行回复。
约束:结合现有 ai1net 架构 —— 服务器节点(+插件)/客户端(+插件),复用既有能力,⛔ 不重造。
性质:本文件 = 规划(架构 + 详细方案)。落地按 §7 分阶段棒次执行,每棒独立验收。
证据分级:【已验】有实测读数 | 【源码在】代码在位未跑 | 【待实测】机制在位、行为未验证 —— ⛔ 交付时不得把后两类写成已验。
0 判定(先给结论)
一句话架构:手机在平台门户作答 ⇒ 平台 IM 内核 ⇒ 覆盖网络反隧道 ⇒ 设备侧桥 ⇒ WorkBuddy 钩子把答案交给模型。
三个关键结论:
- 能做成,且不需要给 WorkBuddy 打补丁、不需要开任何公网入站口。 WorkBuddy 的钩子契约支持
permissionDecision: deny + permissionDecisionReason(模型据此重写/继续)与 additionalContext(注入上下文)——【已验】本机已装的钩子正在用这两个字段,WorkBuddy 自身 CLI 内分别命中 96/70/42 处。
- 主要工作量在「客户端侧桥」,不在服务器侧。 平台侧 IM 房间/消息/面板(带可点按钮)/插件桥端点全部已存在【源码在】;手机侧只需一个新页面。
- 有一条硬约束必须先拍板:若该房间启用 IM 的 E2EE 档位,设备侧桥(非人端)读不到明文 —— 这正是 IM 线当初记下的三代价之一「agent 代答须重做」。见 §3 D5。
1 现状资产盘点
1.1 直接复用(⛔ 不重造)
| 资产 |
载体 |
证据 |
| 覆盖网络中继 + 设备只拨出 |
src/net/relay/**;设备节点 overlay-node-daemon.ps1 |
【已验】真机端到端已跑通(设备声明端口 → 中继端点 → 平台解析 → 打回垫片) |
| 配对仪式(二维码) |
src/web/routes/overlay-pair.ts + web/pair.html |
【已验】六态、28 条判据全绿;生产 pair.html/overlay-pair.js 已部署 |
| 设备台账与准入 |
overlay_devices + device-web 四道闸 |
【已验】入口 200 + WS 101;无会话 401、他人设备 404 |
| 用户级开关模式 |
users.device_web_enabled(默认 0)+ DSHS_DEVICE_WEB 运维急停 |
【已验】S5 已上线,26/26 判据 |
| IM 房间/成员/消息 |
src/im/** + /api/im/rooms/** |
【源码在】消息收发/成员/在线态齐全 |
| IM 面板 + 动作(可点按钮) |
/api/im/rooms/:id/panels[/:panelId/actions] |
【源码在】interactive = 有 onAction;写入口唯一,actorId 取会话不取 body |
| IM 插件桥(双向长轮询) |
/api/im/plugins/{register,out/message,out/frame,bridge,bridge/pull,bridge/result} |
【源码在】M5 已实现插件侧客户端(含每插件令牌 + 实例级单例长轮询闸) |
| agent 触发与上下文内核 |
src/im/agent-bridge.ts(370 行) |
【源码在】形态 A 能力机器人 / 形态 B 组员代理(autoReply+agentRef)、四条硬约束 |
| 问答数据模型 |
决策库(options / ai_recommendation / user_*) |
【已验】权威 PG dsl-pg,已落 135 行;SQLite 兜底 |
| 钩子桥基础设施 |
settings.json.hooks + decision_bridge.py |
【已验】四事件已挂、实测生效 |
1.2 需要新增(少量)
| 新增 |
归属 |
说明 |
| M6「移动会话面」模块 |
服务器节点 · ai1net 插件 |
手机端问答台账 + 开关 + 面板注册(表前缀 p_ai1net_mobile_) |
移动会话页 web/mobile.html |
服务器节点 · 平台 |
复用 pair.html 的登录态与版式;手机唯一入口 |
设备侧桥 wb-mobile-bridge |
客户端 · 设备 |
本地回环口 + 平台长轮询 + 下行队列(本方案主体工作量) |
| WorkBuddy 钩子分支 |
客户端 · WorkBuddy |
在既有钩子桥上加"手机代答"分支(默认关) |
1.3 明确不做
⛔ 不新造第二套取址/中继协议 | ⛔ 不给 relay 加协议 op | ⛔ 不把设备升格为 worker | ⛔ 不引入 AA 式云中转 | ⛔ 不改官方 dsh/WorkBuddy 主程序。
2 架构
2.1 三层拓扑
2.2 两条通路(本方案的全部数据流)
通路 A · 下行(会话 → 手机):让我在手机上看得见"它在等我"
通路 B · 上行(手机 → 会话):把答案交回模型
🔴 一句话判据:通路 A 让手机看见,通路 B 让手机说话;两者都只经既有面,⛔ 不新增对外服务。
3 关键设计决策(含取舍 · 可推翻)
| # |
决策 |
选了什么 |
理由 / 代价 |
| D1 |
承载 |
走 IM 房间承载,⛔ 不做"手机反代 WorkBuddy 界面" |
WorkBuddy 是 Electron 外壳、无对外 web 面(承不承载得起反代:不能)⇒ 手机看的是平台渲染的会话视图,不是 WorkBuddy 的 UI。代价:视图要做一份,⛔ 不追求"逐像素同桌面" |
| D2 |
设备侧载体 |
独立守护进程(扩展设备节点)为主;MCP app 作可选第二形态 |
守护不依赖 WorkBuddy 进程存活、钩子只需本地回环调用(毫秒级,远低于钩子超时);与既有 device shim 同构。代价:多一个要装的东西 |
| D3 |
触发形态 |
形态 B 组员代理(autoReply+agentRef),⛔ 不用形态 A bot |
代理不占成员位、借用户本人身份回写 ⇒ 与"用户的会话"语义一致,且复用 agent-bridge 既有四条硬约束(禁自激/预算/静默期/排队) |
| D4 |
答案送达路径 |
两段式:实时窗口内用阻断式;超时转队列式 |
钩子有超时上限【待实测具体值】⇒ 不能无限阻塞等待。超时后钩子仍返回 deny + reason「已推送到手机,请先暂停该动作」⇒ 会话不被卡死(这是关键:⛔ 不能为了手机把桌面会话挂住) |
| D5 |
E2EE 档位 |
该房间不启用 E2EE(走平台可见档) |
设备侧桥是非人端,加密房读不到明文;IM 线原决策已记此代价「agent 代答须重做」。代价:该房消息对平台可见 ⇒ 若不可接受,则须走"设备侧持钥+棘轮持久化"(另立一棒,成本显著更高) |
| D6 |
默认状态 |
默认关(users.mobile_reply_enabled = 0),用户自己开 |
沿用 S5 已验证的开关模式;关闭时手机入口整体不存在 ⇒ 可见面零扩大 |
| D7 |
与桌面弹窗的关系 |
默认不拦弹窗;仅在开"手机代答"且命中窗口时才接管 |
阻断式会 deny 掉原生弹窗(用户回桌面看不到)⇒ 必须由开关显式授权,⛔ 不静默抢答 |
4 服务器节点(+插件)侧
4.1 平台进程(47 / 106)
| 项 |
内容 |
状态 |
| IM 内核 |
房间 / 成员 / 消息 / 面板动作 |
复用,⛔ 零改动 |
| 设备台账 + 四道闸 |
overlay_devices + deviceWebDecision |
复用,⛔ 零改动 |
| 配对 |
overlay-pair 五端点 |
复用;新增一对「答题码」语义(见 4.3) |
| 新增静态页 |
web/mobile.html(移动会话页) |
新(复用 pair.html 登录态/版式) |
| 新增用户字段 |
users.mobile_reply_enabled(默认 0,v19 迁移) |
新(照 device_web_enabled 的 D 档流程) |
4.2 ai1net 插件 · 新增 M6「移动会话面」
- 一个包一个库:仍
dshs_pl_ai1net;表名前缀 p_ai1net_mobile_(模块分段,⛔ 不与 M1–M5 混)。
- 精确路径路由(⛔ 全精确,无前缀/通配;沿用两端交集纪律):
| 方法 |
路径 |
干什么 |
| GET |
/api/ai1net/mobile/pending |
本设备待答问题列表(只回当前用户本人的设备) |
| POST |
/api/ai1net/mobile/answer |
提交答案(只收一次,带 nonce) |
| GET |
/api/ai1net/mobile/state |
开关 + 桥在线态 + 队列深度(给手机页首屏) |
- 卡片:M6 出 1 张卡(
plugins.item 槽位),显示「手机代答」开关 + 桥在线 + 今日问答数。
- 面板注册:M6 向 IM 注册一个交互面板(
onAction),手机上的「选项按钮」即此面板的 action。
4.3 答案的一次性语义(⛔ 防重放)
沿用 overlay-pair 的码语义:每个待答问题带 一次性 nonce(128 bit CSPRNG),答案写入即烧码;重复提交 ⇒ 409;过期 ⇒ 410。判据:同 nonce 二次提交必拒。
5 客户端(+插件)侧
5.1 设备侧桥 wb-mobile-bridge(本方案主体)
| 职责 |
设计 |
判据 |
| 本地服务口 |
只绑 127.0.0.1(沿用 device shim 纪律);⛔ 不暴露整机 |
ss -lntH 只见回环;外部连不上 |
| 平台长轮询 |
走 M5 既有插件桥(/api/im/plugins/bridge 长轮询 + /pull + /result);每插件令牌 fail-closed |
取不到令牌 ⇒ 不发任何请求 |
| 答案队列 |
本地落盘队列(带 nonce + 过期时间),供钩子按序取用 |
队列可回读、可对账;过期项具名丢弃 |
| 下行上报 |
会话事件(含待答题干/选项)→ 平台 IM |
平台侧可见即判通 |
| 生命周期 |
随设备节点守护起停(复用 -Action install/uninstall) |
停守护 ⇒ 平台立刻"离线"(具名状态,⛔ 不静默) |
5.2 WorkBuddy 钩子分支(在既有钩子桥上增,⛔ 不改其原有行为)
| 事件 |
原行为(保留) |
新增分支 |
SessionStart |
注入提问规范 |
+ 向桥注册本会话(拿到会话键) |
PreToolUse ^AskUserQuestion$ |
提问规范门 + 落库 |
+ 若开关开:上报待答 → 短窗等待 → 命中则 deny + permissionDecisionReason="用户已在手机上作答:<选项>(<理由>)—— 这是答复,不是拒绝" |
UserPromptSubmit |
逐轮识别 + 回收决策 |
+ 若队列有未取用答案 ⇒ additionalContext 注入 |
SessionEnd |
未答收口 |
+ 桥侧会话下线 |
🔴 两条硬纪律(沿用既有钩子桥的教训):
- fail-open —— 桥不可达/超时/异常 ⇒ 按原逻辑放行,⛔ 绝不因为手机把桌面会话弄坏;
- 留痕先于一切 —— 每次取用/放弃都写审计流水,⛔ 否则"没调用"与"静默失败"无法区分(该桥已因此踩过坑)。
6 数据面(插件库 dshs_pl_ai1net)
| 表 |
关键列 |
用途 |
p_ai1net_mobile_questions |
question_id · device_id · session_key · stem · options_json · state · nonce · expires_at |
待答问题 |
p_ai1net_mobile_answers |
question_id · actor_user_id · choice · text · via · answered_at |
作答(actor_user_id 恒取会话) |
p_ai1net_mobile_outbox |
seq · device_id · kind · payload_json · state |
下行待投递队列 |
- ⛔ 包内零数据库动作(不 import 驱动、不拼 SQL)—— 沿用
DB-03 §二 红线;建库在平台侧,先建库再投放。
- 列级
maxBytes 唯一把关人仍是包内 data-limits.js;改声明必回读两处。
7 分阶段实施(每棒做完即停,判据可机器验)
| 棒 |
目标 |
验收判据(可机器验) |
| S1 |
设备侧桥骨架:本地回环口 + IM 插件注册 + 长轮询 |
注册 200;长轮询有包;ss 只见 127.0.0.1;取不到令牌 ⇒ 具名拒绝 |
| S2 |
下行通路:钩子上报 → 手机可见 |
手机页出现该会话待答项;题干/选项与桌面弹窗一致(逐字比对) |
| S3 |
上行·作答:手机作答 → 模型收到 |
会话继续且模型回复/动作体现用户所选;同 nonce 二次提交 409;过期 410 |
| S4 |
上行·续聊:手机自由发言 |
注入生效(additionalContext 或 automation 起新会话),且不打断正在跑的任务 |
| S5 |
开关 + 台账 + 手机页收口 |
开关关 ⇒ 入口与阻断整体不存在(可见面零扩大);开关开 ⇒ 全链 200/101 |
| S6 |
真机 + 回归 |
真手机端到端一次;既有线零回归(配对 28 条 · S5 26 条 · 既有入口无变化) |
🔴 每棒的"完成"= 交付门禁通过(⛔ 改完/build 过/已 commit 都不算交付)。
8 安全与权限(R5:本方案只收窄,不扩大)
| 面 |
处置 |
| 设备侧暴露 |
只绑回环;⛔ 不新开公网入站口(复用 443);端口窗口沿用 20000..20999 |
| 平台准入 |
复用四道闸(平台开关 / 登录用户本人 / 台账 active+desktop / relay 有在线口) |
| 身份键 |
actorId 恒取会话,⛔ 不取 body 自报值(沿用既有纪律) |
| 凭据 |
长期设备凭据仍只落设备侧;手机从不持有设备凭据(沿用配对线的 G5 结论) |
| 一次性 |
每个答案带 nonce,写即烧(§4.3) |
| 默认状态 |
全链默认关;开启 = 用户显式动作(D6) |
| 内容可见性 |
🔴 本节最需注意:走平台可见档(D5)⇒ 会话正文对该房间平台侧可见。这是本方案唯一的"面扩大",必须由用户拍板 |
9 回滚(每项都有独立回滚点)
| 变更 |
回滚 |
| 钩子新增分支 |
摘钩子(既有 install_hooks --rollback 幂等)或设 DSH_DECISION_OFF=1 秒级急停 |
| 用户开关 |
users.mobile_reply_enabled 置 0 ⇒ 入口与阻断立即消失 |
| 设备侧桥 |
设备节点守护 -Action uninstall;桥进程停止 ⇒ 平台显示离线 |
| 平台静态页/路由 |
删 mobile.html + 撤回路由(生产 lib-bak-* 兜底) |
| 插件 M6 |
模块级回滚(表分段 ⇒ 一次迁移牵动不了 M1–M5) |
10 未验证项与风险(⛔ 不得当成已定)
- 钩子超时的实际上限【待实测】—— 决定前置窗口 T1 能开多长;若上限很短 ⇒ 直接以 D4 的队列式为主。
deny 语义对模型的真实影响【待实测】—— 模型可能把 deny 读成"工具被拒"而非"用户答复"。缓解:reason 里写明"这是答复不是拒绝",并在 S3 用真机验收;⛔ 不靠猜。
- 阻断式会吞掉桌面弹窗(D7)—— 产品取舍,已用开关收敛;⚠️ 若用户同时在桌面点,存在双答竞速 ⇒ 需"先到先得 + 另一方收到具名失效"。
- MCP app 作第二载体的进程生命周期【未公开】—— 采用与否以实测为准。
- WorkBuddy 升级可能改钩子契约 —— 红线 R1:⛔ 不自动升级;契约变更须走独立"测试→评估→修复"。
- 决策库遗留缺陷会污染问答对(该库自报:同题重复识别、答复错配 2 条)—— 若 M6 复用其口径,须先排除,⛔ 否则答案可信度受损。
- 权威库不自起(
dsl-pg 实测处于 Exited 状态)—— 若复用决策库,须纳入探活。
11 边界(⛔ 不重造清单,交付时逐条自查)
- ⛔ 不新造取址 / 中继协议 / 第二套配对 / 第二套 IM 内核(全部已在)。
- ⛔ 不让设备成为"被外部直接连入"的一方(设备只拨出;D8 用户已拍板"先不开端口")。
- ⛔ 不把设备升格为平台 worker(沿用既有决定:设备形态 =「带端口的终端」)。
- ⛔ 不在服务器上 build(
/opt/dshs/src 陈旧,会盖坏产物)。
- ⛔ 不引入 AA 云中转(用户已定 B 方案:用自己的覆盖网络)。
- ⛔ 不跨线并行改
aliyun-dsh-server / ai1net-dsh-desktop(各线在活动)。
附录:本次取证读数(可复跑)
| # |
取证点 |
读数 |
| 1 |
strings app.asar | grep hookSpecificOutput/permissionDecision/additionalContext |
96 / 70 / 42 —— 契约字段存在 |
| 2 |
本机已装钩子 decision_bridge.py |
用 permissionDecision:deny+permissionDecisionReason 打回残缺提问;用 additionalContext 注入常驻规矩 ⇒ 契约在跑 |
| 3 |
settings.json hooks |
四事件(SessionStart / PreToolUse ^AskUserQuestion$ / UserPromptSubmit / SessionEnd) |
| 4 |
src/web/routes/im.ts |
房间/成员/消息/panels/panels actions/插件桥(register·out·bridge·pull·result)/gateway/presence/stats 全在位 |
| 5 |
src/im/agent-bridge.ts |
370 行;形态 A bot / 形态 B 组员代理;四条硬约束(禁自激·预算·静默期·房间级限速排队) |
| 6 |
overlay-pair + pair.html |
六态、28 条判据全绿、生产已部署 |
| 7 |
users.device_web_enabled + S5 |
用户级开关模式已验证(默认 0) |
| 8 |
E:/dsh-worker-dev/bin/overlay-node-daemon.ps1 |
设备节点守护在位(含 -Action install/uninstall/status) |
| 9 |
dsh-plugin-ai1net M5 |
插件侧 SDK 已实现(每插件令牌 + 单例长轮询闸) |