# 手机 App 经 ai1net 覆盖网络操作 WorkBuddy 会话 · 架构与分工(2026-09-28) > 🔴 **本件已被取代(v1 → v2)** ⇒ 执行请以 **`手机App经覆盖网络操作WorkBuddy-架构定稿v2-20260928.md`** 为准。 > **本件作废的具体结论**:§3.3「垫片承载形态改为独立进程」**已撤销** —— 用户澄清「客户端插件」指 **DSH 客户端**上的插件(DSH 有完整插件宿主),故垫片**维持既有 DSH Host 插件形态**。 > 本件其余取证(复用清单、端口区间、gateway 端点、`forwardHeaders` 逻辑可复用)**仍然有效**,v2 已并入。 > **用户口径(原话)**:「通过 **dsh 平台的覆盖网络**去实现,功能做到**插件**中:**本地 dsh 客户端插件** + **dsh 平台插件**配合,让**手机 app 通过 ai1net 网络**使用」 > **性质**:架构定稿 + 分工 + 分步。落地按 §5 棒次执行。 > **与前一版关系**:本文件**取代**前一版的"连接方式待定" —— **D1 已定 = 走 ai1net 覆盖网络(方案 C)**。 --- ## 0 判定(三句话) 1. **能成,而且约 80% 是复用** —— 那条线(手机接入线)已经把"手机经中继看到桌面"整条跑通(配对 28 条判据全绿、设备入口真机 200/101),六个复用件**全部在位**(§2)。 2. **要新做的只有三处**(§3):把垫片的**上游**从"本机 DSH Web"换成"本机 WorkBuddy gateway"、把**凭据注入**从"DSH 签名 Cookie"换成"gateway 令牌"、把垫片的**承载形态**从"DSH Host 侧插件"改成"独立进程"(WorkBuddy 没有 DSH Host)。 3. 🔴 **一个必须先发现的事实**:gateway 端口**每次重启都会变**(实测 `50753 → 59914`)⇒ 垫片**必须动态发现**,⛔ 不能写死。 --- ## 1 目标架构(一条链,四段,复用三段) ``` 手机 App(Android 客户端) │ HTTPS 443(平台登录态) ▼ ① ai1net 平台(服务器节点 47)—— 【既有,零改动】 ├ 设备入口 /u//desk//* ← device-web.ts(202 行:五道闸 + 整站前缀转发) ├ 配对 overlay-pair(二维码六态) ← 28 条判据全绿 ├ 设备台账 overlay_devices + 准入四道闸 ← 既有 └ 覆盖网络中继 relay(wss 走 443,⛔ 不开端口)← 既有 ▲ 设备只拨出(无公网入站口) │ ② 用户电脑 —— 【既有守护 + 改造后的垫片】 ├ 节点守护 overlay-node-daemon.ps1(声明端口) ← 既有 └ 【设备侧垫片】只绑 127.0.0.1:20090 ← 既有范式,改上游 · Host → 127.0.0.1:(过 Host 校验) · 注入 gateway 凭据 ▼ ③ 本机 WorkBuddy gateway(127.0.0.1:<动态口>)—— 【既有,官方自带】 · GET /api/v1/jobs 会话列表 · GET /api/v1/jobs/{id}/stream ⭐ 实时尾随(SSE) · POST /api/v1/jobs/{id}/reply ⭐ 发送指令 ▼ ④ WorkBuddy 会话 —— 【既有,就是桌面那份会话库】 ``` **一句话**:手机走的是**你们已经跑通的那条路**,只是到了电脑里**换了最后一段的目标**(DSH Web → WorkBuddy gateway)。 --- ## 2 复用清单(⛔ 全部已核实存在,不重造) | # | 件 | 位置 | 状态 | |---|---|---|---| | ① | 设备侧垫片源码 | `E:/github/dsh-client/packages/device-shim` | ✅ 在位(含 `forwardHeaders` / `pair-page.js` / `oauth-dsh.js`) | | ② | 平台设备入口 | `D:/github/dsh_shenxian/src/web/routes/device-web.ts` | ✅ 202 行,**整站前缀转发**(一条前缀管全站) | | ③ | 配对五端点 | `src/web/routes/overlay-pair.ts` | ✅ 已部署,六态 | | ④ | 设备台账 + 四道闸 | `src/web/routes/overlay-device.ts` | ✅ | | ⑤ | 节点守护 | `E:/dsh-worker-dev/bin/overlay-node-daemon.ps1` | ✅ 含 `-Action install/uninstall/status` | | ⑥ | 节点配置(端口/凭据) | `E:/dsh-worker-dev/overlay/overlay-node.local.json` | ✅ `ports:[20090]` · `relayUrl=wss://ai1net.com/dshs-relay` | **关键既有约束(照抄,⛔ 别重新发明)**: - 中继**只连回环**:`factory(port,'127.0.0.1')`,⛔ 永不接受可路由地址。 - 声明端口必须在**中继允许区间**:47 生产实测 `--base 19000 --span 3000` ⇒ **19000..21999**。 - ⛔ **不能直接声明 WorkBuddy gateway 的口** —— 原因有二:① 它是随机口、可能落在区间外;② 区间内需要有**真实回环监听者**,而 gateway 的口不归我们管。⇒ **这正是垫片存在的理由**。 - 中继走 443(`wss://ai1net.com/dshs-relay`)⇒ **功能全可用、⛔ 不用开任何端口**。 --- ## 3 三处新做/改(本方案的真正工作量) ### 3.1 垫片上游改目标:DSH Web → WorkBuddy gateway | 项 | 现状(DSH 版) | 本方案(WorkBuddy 版) | |---|---|---| | 上游 | `127.0.0.1:`(实测 19387) | `127.0.0.1:`(**动态**,见 3.4) | | 端口来源 | DSH 注入的 `webServer` 服务 | ⛔ 拿不到 ⇒ **自行发现** | | 转发变换 | Host → `127.0.0.1:`;Origin 归一 | **同样需要**(gateway 也有 Host 校验 `hostValidation`) | ### 3.2 凭据注入改方式:DSH 签名 Cookie → gateway 令牌 - **DSH 版**:Cookie 是 **authority 绑定**的(名字 = `dsh-auth-`+sha256(authority),载荷里 authority 必须与 Host 逐字相等)⇒ 所以必须改写 Host。 - **WorkBuddy 版**:鉴权是 **`?password=<令牌>`** 或 Cookie,**不绑定 authority**。 - ✅ **有利**:Host 改写只为过 gateway 的 Host 校验,不带凭据语义 ⇒ 简单。 - 🔴 **约束**:鉴权中间件有**防爆破限流 `2 次/分钟` + `12 次/小时`** ⇒ **绝不能每次请求都带 query 密码**。 - **选定的做法**:垫片**首次**用 `?password=` 换到 **Cookie**,之后**全程复用 Cookie**(Cookie 不在限流口径内)。 - ⚠️ 备选做法(若 Cookie 方案不通):垫片维护一个"已认证会话",只对 `401` 响应重取 Cookie。 ### 3.3 垫片承载形态:DSH Host 插件 → 独立进程 - **现状**:垫片是 **DSH Host 侧插件**(`inject: ['connection','webServer']`),跑在 DSH Host 进程内 —— 因为它要从 `connection` 拿 DSH 凭据、从 `webServer` 拿端口。 - 🔴 **WorkBuddy 没有 DSH Host**,插件形态只有 `skill` / `mcp-app` ⇒ **垫片无法作为 WorkBuddy 插件运行**。 - ⇒ **改为独立进程**,由**既有节点守护拉起**(守护本来就管进程编排与端口声明)。 - ✅ 无损失:垫片本体只是"绑回环 + 反代 + 改头",与是否跑在 DSH 内**无关**;`forwardHeaders` 的逻辑**可原样复用**。 ### 3.4 🔴 动态发现 gateway 端口(**新增的必做项**) - 实测:重启后 `50753 → 59914` ⇒ **写死必炸**。 - **发现判据**(三项同时成立才算): 1. 监听者是 **`WorkBuddy.exe`**(或其后代进程); 2. `GET /` 返回 **200** 且正文含 `CodeBuddy Gateway` / `CodeBuddy Remote Control`; 3. `GET /api/v1/health` 返回 **401**(路由在、需鉴权)。 - 兜底:也可由用户敲 `/gateway status` 读出地址后填入配置(⛔ 该命令模型不能代敲)。 - ⚠️ **发现失败要 fail-closed**(不启、具名报错),⛔ 不许静默用上一次的端口。 --- ## 4 与既有那条线的差异表(一眼看清改了什么) | 维度 | 手机接入线(DSH 版 · 已跑通) | 本方案(WorkBuddy 版) | 判定 | |---|---|---|---| | 手机侧 | 浏览器 + 门户登录态 | **Android 客户端**(用户要求) | ⚠️ 换形态 | | 平台侧 | 设备入口 / 五道闸 / 台账 / 配对 / 中继 | **完全相同** | ✅ 零改动 | | 设备侧暴露 | 垫片只绑 `127.0.0.1:20090` | **相同** | ✅ 复用 | | 垫片上游 | 本机 DSH Web(19387) | **WorkBuddy gateway(动态口)** | ⚠️ 改 | | 垫片凭据 | DSH 签名 Cookie(authority 绑定) | **gateway 令牌 → Cookie** | ⚠️ 改 | | 垫片承载 | DSH Host 侧插件 | **独立进程**(守护拉起) | ⚠️ 改 | | 对外端口 | **0 个**(全走 443) | **相同** | ✅ 复用它已验的分层 | | 会话库 | 桌面 DSH | **桌面 WorkBuddy**(同一份) | ⚠️ 换目标 | --- ## 5 分工与步骤 ### 棒 0(前置 · 本会话,随时可做)—— 端口发现 + 只读实测 | # | 做什么 | 判据 | |---|---|---| | 0.1 | 写**端口发现**脚本(按 §3.4 三项判据) | 能稳定找到当前 gateway 口;⛔ 找不到即 fail-closed | | 0.2 | **U1 只读实测**:`/api/v1/jobs` 是否含**桌面正在聊的会话** | 有 ⇒ 本方案成立;无 ⇒ 只能操作 gateway 自起的会话(须回头改目标) | | 0.3 | 记录 `settings.gateway` / 令牌取得路径 | 令牌可得(用户敲 `/gateway status`) | ### 棒 1(设备侧 · **本地 dsh 客户端插件**)—— 垫片改造 | # | 做什么 | 判据 | |---|---|---| | 1.1 | fork `device-shim` 的 `forwardHeaders` 与反代骨架,**上游改 gateway** | `curl 127.0.0.1:<垫片口>/api/v1/jobs` 经垫片返回 JSON(不再 401) | | 1.2 | 凭据注入:首跳换 Cookie、之后复用 | 连续 50 次请求**无 429**(证明没在打限流) | | 1.3 | 改独立进程形态,写进节点守护的拉起清单 | 守护 `-Action status` 可见;停守护 ⇒ 端口撤 | | 1.4 | 端口声明:区间内新口(与 DSH 版**并存**,⛔ 不覆盖 20090) | relay `/status` 出现该端口 | ### 棒 2(平台侧 · **dsh 平台插件配合**)—— 零改动 + 一处确认 | # | 做什么 | 判据 | |---|---|---| | 2.1 | **确认** `device-web.ts` 的整站前缀转发对"非 DSH 后端"同样成立 | 经设备入口访问垫片,`/api/v1/jobs` 通 | | 2.2 | 若需**区分两条通道**(DSH / WorkBuddy),用**不同端口 + 不同设备行**区分,⛔ 不改判据 | 两条通道互不影响 | | 2.3 | 门户/接口层:让客户端能取到"我的设备"列表 | 客户端能列出可用设备 | > ⚠️ **本条与用户口径的偏差(需说明)**:按核实结果,**平台侧不需要新插件** —— `device-web.ts` 已是通用的整站前缀转发,准入四道闸与后端是谁无关。⇒ "**dsh 平台插件配合**"这一项实际是**零改动**。若用户期望的是"平台侧也出一个插件形态的东西"(如 M 系列模块),那是**另一种做法**(在插件里再包一层),**成本更高且无必要** —— 见 §7 待确认项。 ### 棒 3(手机侧 · Android 客户端)—— 已投对接单,**需按本方案更新** | # | 做什么 | 判据 | |---|---|---| | 3.1 | **更新对接单**:D1 从"隧道/局域网"改为"**走 ai1net 覆盖网络**"(§8 已做) | 对接单里连接方式段已改 | | 3.2 | 客户端只认**设备入口地址**(`https://<门户>/u//desk//…`)+ 平台登录态 | 手机打开即得会话列表 | | 3.3 | MVP:列表 / 历史 / **SSE 尾随** / 发送 / 重连 | 见对接单 §7 | ### 棒 4(联调) 真机端到端 + 断网重连 + **中继侧零协议改动**复核(判据:`src/net/relay/**` 无新增 op)。 --- ## 6 风险与红线 | # | 项 | 处置 | |---|---|---| | 1 | **gateway 端口会变** | §3.4 动态发现 + fail-closed | | 2 | **限流(2/min)** | 垫片换 Cookie 后复用;⛔ 客户端不许轮询(对接单已列为硬约束) | | 3 | **权限面** | gateway 能**执行任务、改会话** ⇒ 设备入口准入**必须保持四道闸 + 默认关**;⛔ 不新增共享密钥、⛔ 不把令牌落平台库 | | 4 | **WorkBuddy 侧无 Host 插件形态** | 垫片改独立进程(§3.3);⛔ 不要试图做成 WorkBuddy 插件 | | 5 | **两条通道并存** | 端口/设备行分开,⛔ 不动 DSH 那条已验通的 | | 6 | **`E:/github/dsh-client` 0 commit** | 动它之前**先入库**(回滚唯一保障) | | 7 | R5 只收窄 | 本方案**不开任何对外端口**、不新增暴露面 ⇒ ✅ 合规 | --- ## 7 待确认(一轮一问) **问题**:垫片(设备侧桥)放在哪个仓、由哪条线做? **为什么需要你定**:垫片现在的家在 `E:/github/dsh-client/packages/device-shim`(属**手机接入线**的客户端仓),而它的**上游目标要改成 WorkBuddy**(属本会话的宿主侧知识)。归属不同,动的人不同,回滚责任也不同。 - **候选 1 · 在 `dsh-client` 仓新建同源包**(如 `packages/wb-gateway-shim`),由**手机接入线**做 —— 与既有垫片同源、复用最顺、风格一致。**缺点**:要跨线交接,本会话只能出规格不能落地。 - **候选 2 · 由本会话另起一个独立小进程**,放在本工作区 —— 本会话可直接做完、闭环快。**缺点**:与既有垫片**两套代码**(同类逻辑两份),日后维护要记住两处。 - **候选 3 · 直接改既有 `device-shim` 支持"上游可配"**(一个包两种目标)—— 一份代码、最省。**缺点**:动的是那条线**已验通**的成果(28 条判据绿),有回归风险。 **倾向候选 3 的变体**:**不改既有行为**,只给它加一个"上游目标"配置项(默认仍是 DSH,⛔ 不改变现有语义)⇒ 一份代码、零回归、两条通道共用。若那条线不同意,退候选 1。 --- ## 附:本次取证读数 | # | 取证点 | 读数 | |---|---|---| | 1 | gateway 存活 | `127.0.0.1:59914` → **200**;重启前旧口 50753 → **502**(端口会变) | | 2 | 鉴权 | `/api/v1/health` → **401** | | 3 | 监听范围 | **仅 `127.0.0.1`** | | 4 | 中继区间(生产实测) | `--base 19000 --span 3000` ⇒ **19000..21999** | | 5 | 节点配置 | `ports:[20090]` · `relayUrl=wss://ai1net.com/dshs-relay`(走 443) | | 6 | 垫片现状 | DSH Host 侧插件;`inject:['connection','webServer']`;上游 = `webServer.port` | | 7 | 垫片关键变换 | `forwardHeaders(rawHeaders, authority, cookie)`:Host → authority、剥上游 set-cookie | | 8 | 平台设备入口 | `device-web.ts` 202 行,`/u//desk//…`,五道闸单一入口,**整站前缀转发** | | 9 | 六个复用件 | 全部 **EXIST** | | 10 | gateway 官方后门 | `CODEBUDDY_CODE_CORS_ORIGINS` 环境变量 + `settings.gateway.corsOrigins`(如需配 origin 用) |