Files
dsh_ai1net_server/交付物/手机App经覆盖网络操作WorkBuddy-架构与分工-20260928.md
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

209 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 手机 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/<uid>/desk/<hostId>/* ← 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:<gateway口>(过 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:<webServer.port>`(实测 19387) | `127.0.0.1:<gateway口>`(**动态**,见 3.4) |
| 端口来源 | DSH 注入的 `webServer` 服务 | ⛔ 拿不到 ⇒ **自行发现** |
| 转发变换 | Host → `127.0.0.1:<port>`;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/<uid>/desk/<hostId>/…`)+ 平台登录态 | 手机打开即得会话列表 |
| 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/<uid>/desk/<hostId>/…`,五道闸单一入口,**整站前缀转发** |
| 9 | 六个复用件 | 全部 **EXIST** |
| 10 | gateway 官方后门 | `CODEBUDDY_CODE_CORS_ORIGINS` 环境变量 + `settings.gateway.corsOrigins`(如需配 origin 用) |