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/ 知识文件,按口径入库)
This commit is contained in:
1 parent
30b46dbd0c
commit
c1b5e4d966
735 files changed
+153192
-2415
No files matched your search
@@ -0,0 +1,318 @@
|
||||
# 手机 App 经 ai1net 覆盖网络操作 WorkBuddy · 架构定稿 v2(2026-09-28)
|
||||
|
||||
> ## 🔴 状态块(2026-09-29 加):**本件已被取代,降为过程档案**
|
||||
> ⇒ **权威件 = `交付物/手机App经覆盖网络操作WorkBuddy-架构定稿v3-20260929.md`**(各线按 v3,⛔ 别照本篇抄)。
|
||||
> 取代原因(均为源码/实测):本件 **§5.3「首跳 `?password=` 换 Cookie」整段作废**(`?password=` 在受保护路径永远失效,正解=`x-access-token`);**§5.2 三判据不足**(本机并存 3 个 `WorkBuddy.exe` 且共用口令 ⇒ 必须加"父链"判据);**§5.1「Origin 归一」不足**(必须**剥掉** Origin,否则写操作一律 403);并新增 v2 完全没写的**会话侧硬约束**(会话按工作目录限定、"活会话"语义、往任意会话发要走 ACP)。
|
||||
> ⛔ 正文按机制**不动**(保留取证原样)。
|
||||
|
||||
|
||||
> **用户口径(原话)**:「通过 **dsh 平台的覆盖网络**去实现,功能做到**插件**中:**本地 dsh 客户端插件** + **dsh 平台插件**配合,让**手机 app 通过 ai1net 网络**使用」
|
||||
> +「**平台插件和客户端插件是一个程序**,作用除了覆盖网络通讯,还有个作用是**账号核对**,这样才能**我的账号连我的客户端、访问我的 workbuddy**」
|
||||
> **性质**:**v2 定稿**(取代 v1)。落地按 §6 棒次执行。
|
||||
> **v1 已被本版更正的**:v1 把"客户端插件"误读为 **WorkBuddy 插件**,据此得出"垫片必须改成独立进程"。**该结论撤销** —— 见 §1。
|
||||
|
||||
---
|
||||
|
||||
## 0 判定(三句话)
|
||||
|
||||
1. 🔴 **一处重要更正**:你说的「客户端插件」= **DSH 客户端(桌面)上的插件**。DSH **有完整插件宿主** ⇒ 垫片**保持既有 DSH Host 插件形态**,v1 的"改独立进程"**撤销**。
|
||||
2. ✅ **账号核对不用新做 —— 平台侧已完整具备**(§3)。你要的"我的账号只能连我的客户端、访问我的 WorkBuddy",正是既有那一套:**网络名与 hostId 从账号推导 + 台账属于本人 + 四道闸**。
|
||||
3. ✅ **"一个程序两角色"也是既有模式** —— ai1net 插件本就是「同一包、客户端侧走 `dsh.profile.bundles`/平台侧走 `link:`」(§4)。
|
||||
|
||||
⇒ **净新增只有两处**(v1 说三处,现减为一处撤销 + 两处保留):垫片**上游**改指 WorkBuddy gateway + **凭据注入**改方式。
|
||||
|
||||
---
|
||||
|
||||
## 1 更正说明(v1 → v2)
|
||||
|
||||
| 项 | v1 的结论 | v2 更正后 | 依据 |
|
||||
|---|---|---|---|
|
||||
| 「客户端插件」指谁 | 误读为 **WorkBuddy 插件** | **DSH 客户端插件**(用户原话「本地 dsh 客户端插件」) | 用户口径 |
|
||||
| 垫片承载形态 | 改为**独立进程**(因"WorkBuddy 无插件宿主") | **维持 DSH Host 插件**(⛔ 不改造) | DSH 有完整插件宿主;`device-shim` 已是该形态且已验通 |
|
||||
| 改动数量 | 三处 | **两处**(上游 + 凭据) | 承载形态那条撤销 |
|
||||
|
||||
⚠️ **但 v1 那条观察仍然成立且重要**:WorkBuddy 侧**确实没有插件宿主**(只有 skill / mcp-app)⇒ 所以**插件装在 DSH 客户端侧**、WorkBuddy 只当**本地被访问对象**(它自带 gateway 已够)。这正是你的设计意图,比我 v1 的方案更干净。
|
||||
|
||||
---
|
||||
|
||||
### 1.1 术语:什么是「垫片」(shim)
|
||||
|
||||
> 这一节是后补的 —— 投递方自己曾把「客户端插件」误读为 WorkBuddy 插件(见上表),根因就是**术语没先讲清**。⇒ 后续棒请先读本节。
|
||||
|
||||
**一句话**:夹在两个**对不上**的东西之间、让它们能接上的**薄中间层**(工程借用词,与硬件无关)。
|
||||
|
||||
在本方案里 = 一个跑在电脑上的小程序,**只做"转接"、不做业务**:
|
||||
|
||||
| # | 卡在哪 | 垫片怎么办 |
|
||||
|---|---|---|
|
||||
| ① | 中继**只肯连回环地址**(代码写死 `'127.0.0.1'`,原文「永不接受可路由地址」),且**只认它从平台拿到的那个端口**,端口还须落在允许区间(生产实测 `19000..21999`) | 垫片**占一个区间内的固定口、真监听**(现用 `127.0.0.1:20090`) |
|
||||
| ② | 目标服务的口**不归我们管、还会变**(DSH Web 19387 动态分配;WorkBuddy gateway 实测 `50753 → 59914`) | 垫片**对外固定,对内自己去找**(本方案需新增"动态发现",见 §5.2) |
|
||||
| ③ | 目标服务**要它自己的凭据**,且凭据**不落盘**(DSH 的 token 是进程内随机值,只出现在启动链接里) | 垫片**在进程内拿到并注入**;并改写 `Host` / `Origin` 过对方的栅栏,否则一律 401/403 |
|
||||
|
||||
**「薄」的含义**:⛔ 不处理业务 · ⛔ 不存数据 · ⛔ 不做权限判断 —— 只**转发 + 改几个请求头**。越薄,故障面越小。
|
||||
|
||||
**为什么不能省**(两条都被实测否掉):
|
||||
- 把目标服务的口**直接**声明给中继 ⇒ ❌ 不行(口不归我们管;且凭据对不上)。
|
||||
- 用**外部脚本**代替垫片 ⇒ ❌ 不行(凭据**只在进程内存里**,外部拿不到)。
|
||||
|
||||
⇒ 这也是它**必须是"进程内插件"而非独立脚本**的根本原因 —— 而「进程内」在 DSH 侧成立(DSH 有插件宿主),在 WorkBuddy 侧不成立(只有 skill / mcp-app)。
|
||||
🔑 **所以本方案的落点就是**:垫片**留在 DSH 客户端插件**里,它去访问的**目标**换成 WorkBuddy gateway。
|
||||
|
||||
## 2 目标架构(v2)
|
||||
|
||||
```
|
||||
手机 App(Android 客户端)
|
||||
│ HTTPS 443(平台登录态)
|
||||
▼
|
||||
① ai1net 平台(47)—— 【既有,零改动】
|
||||
├ 账号核对(下方 §3 全链) ← 决定"我的账号 ↔ 我的客户端"
|
||||
├ 设备入口 /u/<uid>/desk/<hostId>/* ← device-web.ts(202 行,五道闸 + 整站前缀转发)
|
||||
├ 配对 overlay-pair(六态 · 28 条判据绿)
|
||||
└ 覆盖网络中继(wss 走 443,⛔ 不开端口)
|
||||
▲ 设备只拨出
|
||||
│
|
||||
② 用户电脑
|
||||
├ 【客户端插件 · 与平台插件同一程序】
|
||||
│ ├ 覆盖网络通讯:复用平台仓自己的客户端入口(`lib/net/relay/main.js --client`)
|
||||
│ ├ 本地监听:只绑 127.0.0.1:20090(DSH Host 侧插件形态,⛔ 不改)
|
||||
│ └ 凭据注入:改用 WorkBuddy gateway 令牌(🔴 改动 2)
|
||||
│ · 上游改指 127.0.0.1:<gateway 动态口>(🔴 改动 1)
|
||||
│ · Host 改写 → 过 gateway 的 hostValidation
|
||||
│ └ 持有设备凭据(账号核对链路的落点)
|
||||
▼
|
||||
③ WorkBuddy gateway(官方自带)→ ④ WorkBuddy 会话(桌面那份)
|
||||
```
|
||||
|
||||
**★ "一个程序"怎么成立**:客户端侧与平台侧是**同一个包**,只是装配位置不同(§4)。客户端侧多一层"本机承接"职责(监听 + 注入),平台侧多一层"账号核对 + 准入"职责。
|
||||
|
||||
---
|
||||
|
||||
## 3 🔴 账号核对:平台侧已完整具备(⛔ 不用新做)
|
||||
|
||||
你要的「**我的账号连我的客户端、访问我的 WorkBuddy**」,逐环对应如下 —— **每一环都已存在**:
|
||||
|
||||
| # | 环节 | 实现 | 位置 |
|
||||
|---|---|---|---|
|
||||
| 1 | **登录态** | 门户会话 ⇒ `request.user.id` | `src/web/routes/auth.ts`(698 行) |
|
||||
| 2 | **网络名由账号推导** | `network = tenantNetworkOf(userId)` | `src/net/relay/device-grant.ts` |
|
||||
| 3 | **hostId 由账号推导** | `hostId = deviceHostIdOf(userId, nodeKey)` | 同上 |
|
||||
| 4 | **凭据签发绑账号** | `issueDeviceGrant({db, **userId**, nodeKey})`,落**四处**(返回值/registry/台账/relay 密钥表) | `device-grant.ts#issueDeviceGrant`(**唯一实现**) |
|
||||
| 5 | **准入闸 · 属于本人** | 台账 `overlay_devices` **属于本人** 且 `status='active'` 且 `kind='desktop'` | `device-web.ts:14` 第③闸 |
|
||||
| 6 | **用户级开关** | `getDeviceWebEnabled(userId)` —— **默认关** | `device-web.ts:92` |
|
||||
| 7 | **停发即失效** | 台账置 `revoked` ⇒ 再签被拒("⛔ 不能续一次签就复活") | `instance-credential.ts:256` |
|
||||
| 8 | **配对方向有利** | 由**已登录的手机**批准 ⇒ 归属由**手机的会话**确定;`approve` 请求体**只有 code**,`userId` 恒取 `request.user.id` ⇒ **"替别人签发"结构上不可能** | `overlay-pair.ts` |
|
||||
|
||||
🔑 **一句话**:**网络名与 hostId 都是从 `userId` 推导出来的** —— 这在结构上就保证了"**我的账号只能连到我的那台客户端**"(原文注释:「提权在结构上不可能」)。
|
||||
|
||||
⇒ **结论:账号核对是既有能力。** 你把它列入插件的作用是对的(它确实是这条链路的核心作用之一),但**它不需要新写** —— 新写的只是"客户端侧承接 WorkBuddy"那一段。
|
||||
|
||||
---
|
||||
|
||||
## 4 "一个程序两角色"= 既有模式(原文照录)
|
||||
|
||||
既有 ai1net 插件的 README 原文:
|
||||
|
||||
> **两端怎么装它(同一套机制)**
|
||||
> ① **客户端(桌面壳)**:profile 的 `package.json` 里 `dsh.profile.bundles` 含 `@dsh-local/ai1net`;
|
||||
> ② **平台侧(实例)**:同一个键 + `dependencies` 里一条 `link:` 指向共享只读包库
|
||||
> (平台侧协议见 `src/supervisor/plugin-assembly.ts`,⛔ 本包不重造分发机制)。
|
||||
|
||||
⇒ 你要的"一个程序"**已经有现成装配协议**(`link:` + `plugin-assembly.ts`)。本方案照它走,⛔ 不新造分发机制。
|
||||
|
||||
---
|
||||
|
||||
### 4.1 🔴 归属定案:垫片 = ai1net 包的**一块功能**(2026-09-28 用户拍板)
|
||||
|
||||
**用户口径**:垫片就是 ai1net 插件中的一个功能。
|
||||
**核实结论**:**方向对,但现状不是** —— 垫片现在是一个**独立插件包**(`@dsh-client/device-shim`,自带 `cordis.patch.yml` + `package.json` 里的 `dsh.bundle.patch`,与 ai1net 包**各装各的**)。
|
||||
⇒ **定案:归并** —— 把垫片的 host 半并入 `@dsh-local/ai1net`,作为**第 6 块模块**,保留其 `apply()` 与 `forwardHeaders()`;独立包 `@dsh-client/device-shim` 退役。装载仍走 `dsh.profile.bundles`(§4),⛔ 不新造分发机制。
|
||||
|
||||
**归并要顺带解决三件(①② 是硬阻碍,均已取证)**:
|
||||
|
||||
| # | 事项 | 实测 / 依据 |
|
||||
|---|---|---|
|
||||
| ① | 规格书 §五 A1 只列 **M1–M5 五块**(多租户 / 模型管理 / 技能插件管理 / **覆盖网络界面** / IM 插件侧),**没有"设备接入"** | 规格书 §五 A1 原文;`dsh.data.yaml` 模块段只有 `tenant/model/plugin/net/im` ⇒ 归并**必须带一条规格修订**(加模块 + 表段,建议 `net_dev_*`;无持久化需求则⛔ 不建表) |
|
||||
| ② | 🔴 **`inject` 不兼容** | 垫片现为 `inject = ['connection','webServer']`(靠 `webServer.port` 拿本机口);而 ai1net 包 E1 明文「`inject = ['connection']` **必写**」,且分步实施方案 §1.3 / B1 已记「**桌面端没有 `webServer`**」⇒ 归并**必须去掉 `webServer` 依赖**(端口改自身配置 + 动态发现 —— 与 §5.2 是同一处改动,正好合并) |
|
||||
| ③ | ✅ **不碰那条红线** | 规格书 M4 行写「「取身份」(入网凭据)⇒ **引导层,⛔ 不进包**」。核实:入网凭据 = `nodeKey / grant / grantSig`(`DSHS_OVERLAY_NODE_KEY_FILE` / `..._GRANT_FILE`),由**节点守护 + 平台**在拿;垫片持的是**本机服务的会话凭据**(`dsh-auth-*` 签名 Cookie)⇒ **两类凭据、不是一回事**,归并**不触红线** |
|
||||
|
||||
**代价(如实登记)**:归并要动**两处别人的成果** —— 插件线的 `S1` 冻结正文(加模块)与客户端线的 `device-shim`(迁出);且 `E:/github/dsh-client` **0 commit** ⇒ 迁之前**必须先入库**(§8 第 5 条)。
|
||||
**收益**:一个包、一个 pluginId、一个库、一套装载机制(与 §4 完全一致);两条通道(DSH / WorkBuddy)共用**同一个模块**,以后改一处即可。
|
||||
|
||||
---
|
||||
|
||||
## 5 净新增:只有两处
|
||||
|
||||
### 5.1 【改动 1】垫片上游:DSH Web(19387)→ WorkBuddy gateway(动态口)
|
||||
|
||||
| 项 | 现状(已验通) | 本方案 |
|
||||
|---|---|---|
|
||||
| 上游地址 | `127.0.0.1:<webServer.port>` | `127.0.0.1:<gateway 口>` |
|
||||
| 端口来源 | DSH 注入 `webServer` | **动态发现**(下方 5.2) |
|
||||
| Host 改写 | → `127.0.0.1:<port>` | **同样需要**(gateway 有 `hostValidation`) |
|
||||
| Origin 归一 | 需要(DSH 有 Host/Origin 栅栏) | **同样需要** |
|
||||
|
||||
✅ 既有 `forwardHeaders(rawHeaders, authority, cookie)` **逻辑可原样复用**,只换 authority 的值。
|
||||
|
||||
### 5.2 【必须新增】动态发现 gateway 端口
|
||||
|
||||
🔴 实测:**gateway 端口每次重启都变**(`50753 → 59914`)⇒ 写死必炸。
|
||||
**发现判据**(三项同时成立):
|
||||
1. 监听者是 **`WorkBuddy.exe`**(或其后代);
|
||||
2. `GET /` 返回 **200** 且正文含 `CodeBuddy Gateway` / `CodeBuddy Remote Control`;
|
||||
3. `GET /api/v1/health` 返回 **401**(路由在、需鉴权)。
|
||||
⇒ 找不到 **fail-closed**(不启 + 具名报错),⛔ 不许沿用上次端口。
|
||||
|
||||
### 5.3 【改动 2】凭据注入:DSH 签名 Cookie → WorkBuddy gateway 令牌
|
||||
|
||||
- DSH 版:Cookie **authority 绑定**(名字 = `dsh-auth-`+sha256(authority))⇒ 必须改写 Host。
|
||||
- WorkBuddy 版:鉴权是 **`?password=<令牌>`** 或 Cookie,**不绑 authority**。
|
||||
- 🔴 **约束**:鉴权中间件有**防爆破限流 `2 次/分钟` + `12 次/小时`** ⇒ **绝不能每请求带 query 密码**。
|
||||
- **选定做法**:垫片**首跳**用 `?password=` 换 **Cookie**,之后**全程复用 Cookie**(Cookie 不在该限流口径内)。
|
||||
|
||||
---
|
||||
|
||||
## 6 分工与棒次
|
||||
|
||||
| 棒 | 归属 | 做什么 | 判据 |
|
||||
|---|---|---|---|
|
||||
| **棒 0** | 本会话 | ① 端口发现脚本(§5.2 三判据)② **U1 只读实测**:`/jobs` 是否含桌面正在聊的会话 ③ 记录令牌取得路径 | 发现稳定;U1 定论 |
|
||||
| **棒 1** | **ai1net 包 · 客户端半**(§4.1 归并后) | 上游改指 gateway;凭据换 Cookie 复用;Host/Origin 改写沿用既有;**去掉 `webServer` 依赖** | `curl 127.0.0.1:20090/api/v1/jobs` 经垫片返回 JSON;连续 50 次**无 429** |
|
||||
| **棒 2** | **ai1net 包 · 平台半**(同一包,装法见 §4) | ⚠️ 见 §7 G-B —— **准入判定零改动**;本棒实际是**装同一包 + 开关登记**(走既有 `link:` 协议) | 经设备入口访问垫片通;两条通道(DSH / WorkBuddy)互不影响 |
|
||||
| **棒 3** | Android 客户端 | 按对接单 MVP(列表/历史/SSE 尾随/发送/重连) | 对接单 §7 |
|
||||
| **棒 4** | 联调 | 真机端到端 + 断网重连 + 中继零协议改动 | 判据:`src/net/relay/**` 无新增 op |
|
||||
|
||||
---
|
||||
|
||||
## 7 两个缺口(新发现 · 需处理)
|
||||
|
||||
| # | 缺口 | 说明 |
|
||||
|---|---|---|
|
||||
| **G-A** | 🔴 **DSH 桌面客户端的 profile 已随 `E:\ProgramDSH` 消失** | 现 `C:/Users/Administrator/.dsh/profiles/` 只剩 `rescue` / `web`,**无 `desktop`**;而垫片要挂在 `desktop` profile 上 ⇒ **客户端插件目前无处可挂**。须先恢复 `desktop` profile(或确认新的挂载点)。⚠️ **§4.1 归并后此缺口照旧**(换的是包名,不是挂载点)。 |
|
||||
| **G-B** | ⚠️ **"平台插件"这一角色按核实是零改动** | `device-web.ts` 用通用反代(前缀之后原样转发),**准入四道闸与后端是谁无关** ⇒ **准入判定**零改动、⛔ 不需要为新功能改代码。✅ **按你的口径对齐**:平台侧那一「配合」角色由**同一个 ai1net 包的 host 半**在实例内承担(装法见 §4);本阶段它只需「开关 + 对账」,MVP 用平台侧既有 `device_web_enabled` 即可、**零界面**。若日后要「我的设备」自助界面 ⇒ 那是 **M4(覆盖网络界面)的延伸**,属插件线后续棒、**不属本方案**。 |
|
||||
|
||||
---
|
||||
|
||||
## 8 红线与风险
|
||||
|
||||
| # | 项 | 处置 |
|
||||
|---|---|---|
|
||||
| 1 | gateway 端口会变 | §5.2 动态发现 + fail-closed |
|
||||
| 2 | 限流 2/min | 换 Cookie 后复用;客户端 ⛔ 不轮询 |
|
||||
| 3 | 权限面 | gateway 能**执行任务、改会话** ⇒ 准入四道闸保持 + 默认关;⛔ 不新增共享密钥、⛔ 令牌不落平台库 |
|
||||
| 4 | 两条通道并存 | 端口/设备行分开,⛔ 不动 DSH 那条已验通的 |
|
||||
| 5 | `E:/github/dsh-client` **0 commit** | 动之前**先入库**(回滚唯一保障) |
|
||||
| 6 | R5 只收窄 | 本方案**不开任何对外端口**、不新增暴露面 ⇒ ✅ 合规 |
|
||||
|
||||
---
|
||||
|
||||
## 9 归属定案(原「待确认」项已由用户拍板)
|
||||
|
||||
> 🟢 **2026-09-28 用户拍板:垫片就是 ai1net 插件中的一个功能。** ⇒ 「放哪个仓」不再是开放问题:**⛔ 不新建独立包、⛔ 不跨线共用**,改为**归并进 `@dsh-local/ai1net`**(§4.1)。原先三个候选(加配置项 / 新建同源包 / 另起进程)**全部作废**。
|
||||
|
||||
**随之而来的三件必办(无一项需要再拍板)**:
|
||||
|
||||
| # | 办什么 | 归属 |
|
||||
|---|---|---|
|
||||
| 1 | **规格修订**:规格书 §五 A1 加第 6 块模块(建议「设备接入」,表段 `net_dev_*`),并在 M4 那行「取身份不进包」后补一句边界定义(**入网凭据 ≠ 本机服务凭据**) | 插件线(S1 正文;本会话只出条款草案) |
|
||||
| 2 | **垫片改造**:上游改指 WorkBuddy gateway + 凭据换 Cookie 复用 + **动态发现端口** + **去掉 `webServer` 依赖** | 插件线(迁入后)/客户端线(迁出前须先入库) |
|
||||
| 3 | **恢复挂载点**:`desktop` profile(§7 G-A) | 客户端线 |
|
||||
|
||||
### 9.1 规格修订条款草案(可直接并入规格书 §五 A · 交插件线)
|
||||
|
||||
**A1 表增一行**:
|
||||
|
||||
| **M6** | **设备接入**(=原「垫片」) | 本机回环反代:占**区间内固定口**、承接中继来连、把**本机服务凭据**注入转发请求(并改写 `Host` / `Origin`);**上游目标可配置**(DSH Web / WorkBuddy gateway) | 「取身份」(入网凭据 `nodeKey/grant/grantSig`)⇒ **引导层,⛔ 不进包**(同 M4 口径) | 1 |
|
||||
|
||||
**A1 表下补一句边界定义**(防后人误读):
|
||||
|
||||
> M4 / M6 的「取身份不进包」管的是**入网凭据**(平台签发、由节点守护持有);M6 持有并注入的是**本机服务的会话凭据**(本机私产)。**两类凭据不同源、不同生命周期** —— ⛔ 不得因"都是凭据"就把 M6 判为越界。
|
||||
|
||||
**表名分段**:M6 **默认不建表**(纯转发、无状态)。若日后确需(如设备-目标映射),在既有 `net_*` 段下**加子段** ⇒ `p_ai1net_net_dev_*`,⛔ **不新开第 6 个模块段**(保持 `tenant / model / plugin / net / im` 五段不变)。
|
||||
|
||||
**⚠️ 一条必须显式授权的例外(否则与 A2 打架)**:A2 写「凡想到加服务端路由 / 加路径前缀 / 加一张页面 ⇒ **当场作废**」,而 M6 的动作是**在 `127.0.0.1` 上绑一个端口并监听** —— 这既不是路由、也不是页面,属 **E1–E3 之外的 Node 能力**。⇒ 规格须为 M6 **单列一条授权**:
|
||||
|
||||
> 「M6 得在 `127.0.0.1` 绑**一个**端口(默认 `20090`;须落在中继允许区间内、排除中继自身口),⛔ 不得绑可路由地址、⛔ 不得开多口。」
|
||||
|
||||
**这一条不写清,M6 就是违规插件。**
|
||||
|
||||
**须同步加注的两处既有文字**:① A2 的 E1 行「`inject = ['connection']` 必写」→ 加注「**M6 同此,⛔ 不得 inject `webServer`**(桌面端无此服务)」;② 分步实施方案 §1.3 / B1 的「桌面端没有 `webServer`」→ 加注「这也是 **M6 归并的硬前提**」。
|
||||
|
||||
---
|
||||
|
||||
## 附:v2 本次取证读数
|
||||
|
||||
| # | 取证点 | 读数 |
|
||||
|---|---|---|
|
||||
| 1 | 账号核对链路 | `auth.ts` **698 行** · `overlay-pair.ts` **578 行** · `overlay-device.ts` **99 行** · `tenants.ts` **362 行** |
|
||||
| 2 | 凭据签发唯一实现 | `device-grant.ts#issueDeviceGrant`(落**四处**);`instance-credential.ts` 明写"⛔ 不复制那段判据" |
|
||||
| 3 | 归属由账号推导 | `network = tenantNetworkOf(userId)`;`hostId = deviceHostIdOf(userId, nodeKey)` ⇒ 原文注「提权在结构上不可能」 |
|
||||
| 4 | 四道闸第③条 | 「台账 `overlay_devices`:**设备属于本人** 且 `status='active'` 且 `kind='desktop'`」 |
|
||||
| 5 | 停发语义 | 台账 `revoked` ⇒ 再签被拒(`instance-credential.ts:256`) |
|
||||
| 6 | 一程序两角色 | ai1net 插件 README §「两端怎么装它(同一套机制)」:客户端 `dsh.profile.bundles` / 平台 `link:` |
|
||||
| 7 | DSH 客户端 | `E:/github/dsh-desktop-0.1.7rc2` EXIST;但 `~/.dsh/profiles/` **只有 `rescue`/`web`,无 `desktop`** ⇒ 缺口 G-A |
|
||||
| 8 | 既有 daemon 复用方式 | `overlay-node-daemon.ps1` 头注:「reuses the platform repo's OWN client entry, read-only: `node <Repo>/lib/net/relay/main.js --client`」+「Zero new protocol code」 |
|
||||
|
||||
---
|
||||
|
||||
## 10 棒 0 实测读数(2026-09-28 20:5x · 本会话交付)
|
||||
|
||||
> §6 把棒 0 定为「本会话」的活:① 端口发现脚本 ② U1 只读实测 ③ 记录令牌取得路径。**三项均已交付**。
|
||||
> 交付件:`交付物/棒0-网关端口发现-20260928.mjs`(+ 逻辑夹具在 `tmp/wb-phone/_棒0逻辑夹具.mjs`)。
|
||||
> 🔴 **棒 1–4 不属本会话**(见 §6 归属列),本会话只出件 + 派活。
|
||||
|
||||
### 10.1 ① 端口发现脚本(交付 · 可复跑)
|
||||
|
||||
| 项 | 读数 |
|
||||
|---|---|
|
||||
| 正向 | `node 棒0-网关端口发现-20260928.mjs` ⇒ **端口 52954 · pid 47964(WorkBuddy.exe) · `GET /` 200 含 «CodeBuddy Gateway» · `/api/v1/health` 401 · exit=0** |
|
||||
| 纯逻辑夹具 | **PASS=10 / FAIL=0**(netstat 解析 5 条 + 判据① 5 条,含"父链成环不死循环") |
|
||||
| fail-closed 负例 | ① netstat 空 ⇒ `no-loopback-listener` ② 去掉 52954 ⇒ `fingerprint-mismatch` ③ 名字表空 ⇒ `not-workbuddy-process`(**判据① 不放宽**)—— 三条都 exit=3 |
|
||||
|
||||
**两条设计要点(都是实测逼出来的)**:
|
||||
- 🔴 **判据① 单靠进程名不够** —— 回环上**另有 3 个 `WorkBuddy.exe` 监听口**(`18488` · `59889` · `59890`)⇒ 必须三判据同时成立,否则会误认。
|
||||
- 🔴 **受限环境逃生口**:实测在 WorkBuddy 的 AI 沙箱里 **Node spawn `netstat`/`tasklist` 恒 `EBUSY`**(而同一沙箱 **Python 正常**)⇒ 脚本支持 `--netstat-file/--tasklist-file/--parents-file` 喂入外层抓好的文本。⛔ 下棒若在受限环境跑,别把 `EBUSY` 误判成"机器上没有 gateway"。
|
||||
|
||||
### 10.2 ② U1 定论(🔴 这条修正对接单 §3.1)
|
||||
|
||||
| 端点 | 读数 | 结论 |
|
||||
|---|---|---|
|
||||
| `GET /api/v1/jobs` | 200 → `{data:{jobs:[]}}` —— **空** | 🔴 **桌面正在聊的会话不在 jobs 里** |
|
||||
| `GET /api/v1/jobs/events` | 200(SSE,无即时应答体) | 同上,无实例可订 |
|
||||
| `GET /api/v1/sessions?cwd=*` | 200 → **16 条**,字段 `id`/`cwd`/`name` | ✅ 含活会话 |
|
||||
| `GET /api/v1/sessions/live` | 200 → `{sessionId, writerOccupied:true}` = **桌面当前会话** | ✅ 「操作现有会话」成立 |
|
||||
| `GET /api/v1/sessions/{id}/history` | 200 → `{sessionId,name,requests:[{userInput,finalReply}]}` · **requests=3**,与本会话 3 次发言**逐数吻合** | ✅ 历史覆盖到最新一轮 |
|
||||
| `GET /api/v1/sessions/{id}/replay` | 200 | ✅ 在 |
|
||||
|
||||
⇒ 🔴 **对接单 §3.1 的「主推一套」(jobs 系列)在本机上操作不了现有会话**;**必须走它列的"备选一套"(sessions 系列)**。棒 3(Android 客户端)请按 **sessions 一套**实现,⛔ 别按 jobs 写。
|
||||
(附带更正对接单 §2-②:那里写的 `127.0.0.1:59914` 已失效,端口实测为 `52954`。)
|
||||
|
||||
### 10.3 ③ 令牌取得路径 —— 并由此暴露 v2 §5.3 的一个前提不成立
|
||||
|
||||
**取得路径(实测)**:令牌 = **`CODEBUDDY_GATEWAY_PASSWORD`**,由 `workbuddy-server` **进程内惰性生成**(32B base64url、模块单例),只经 `gatewaySecretEnv()` 注入**它 spawn 的子进程 env**。
|
||||
|
||||
| 事实(实测) | 读数 |
|
||||
|---|---|
|
||||
| 作用域 | **User = 空 · Machine = 空 · 仅 Process** ⇒ ⛔ 不是持久变量,WorkBuddy 每次重启即换 |
|
||||
| 落盘 | ⛔ **刻意不落盘**(源码注释:该 secret 存在就是为了"同机其他进程无法读取"——`/api/v1/*` 曾是未鉴权本地 RCE) |
|
||||
| `settings.json` | ⛔ **无 `gateway` 键** ⇒ 用户敲的 `/gateway token` **实测未落盘**(该子命令语义=*regenerate*,且 env 优先级**高于** `settings.gateway.password`) |
|
||||
| 谁能读到 | 只有 **WorkBuddy 进程树内**的进程(实测:本会话的 Bash 工具继承到了;旧 bridge.py 从资源管理器启动则**读不到**,恒 401) |
|
||||
|
||||
🔴 **v2 §5.3 需修订**:§5.3 说「垫片**在进程内**拿到并注入」—— 这句话在 **DSH 路线**成立(DSH 令牌就在 DSH Host 进程里,而垫片正跑在那),但**在本路线的目标上不成立**:垫片跑在 **DSH Host 进程**内,而 WorkBuddy 令牌在 **WorkBuddy 进程树**内 —— **两者不是同一进程树 ⇒ 垫片拿不到令牌**。
|
||||
|
||||
⇒ **候选解法(⛔ 留给定归属的那一棒,本会话不代做、不定)**:
|
||||
|
||||
1. **把"取令牌"那一半挪进 WorkBuddy 进程树**(WorkBuddy 的 skill / hook / 它 spawn 的子进程都能继承该 env;本会话已实测继承成立)。**优点**:不落盘、不重开漏洞、令牌永远新鲜。**缺点**:垫片从一个"进程内插件"裂成两半,多一条本机 IPC。
|
||||
2. **由 WorkBuddy 侧主动把令牌交给垫片**(需 WorkBuddy 侧存在承载点,⛔ 不改官方客户端 —— R2)。**优点**:形态最干净。**缺点**:目前无此承载点,须先确认扩展面。
|
||||
3. **落盘**(写 `token.txt`)。**优点**:改动最小。**缺点**:🔴 **等于把厂商刚修掉的本地 RCE 面重新打开**,且每次重启要重写 ⇒ ⛔ 不建议。
|
||||
|
||||
### 10.4 派活(§6 棒次的落地指向 · ⛔ 本会话不代做)
|
||||
|
||||
| 棒 | 归属线 | 派活内容 | 卡点 |
|
||||
|---|---|---|---|
|
||||
| 棒 1 | **插件线 / 客户端线**(垫片改造) | 上游改指 gateway + 凭据换 Cookie 复用 + 动态发现(**直接复用 10.1 的脚本**)+ 去掉 `webServer` 依赖 | 🔴 **先解 §10.3 的进程树问题**,否则凭据注入无处取;且 **G-A(`desktop` profile 已随 `E:\ProgramDSH` 消失)仍在** —— 实测 `~/.dsh/profiles/` 只有 `rescue`/`web` |
|
||||
| 棒 2 | 插件线(平台半) | ⚠️ §7 G-B:准入**零改动**,本棒=装同一包 + 开关登记 | 依赖棒 1 |
|
||||
| 棒 3 | **anywhere 线(Android)** | 按对接单 MVP 五件事实现 | 🔴 **改用 sessions 一套**(见 §10.2) |
|
||||
| 棒 4 | 联调 | 真机端到端 + 断网重连 | 依赖 1–3 |
|
||||
|
||||
📌 **一处环境事实(供各棒判读)**:**DSH 桌面客户端当前没在跑** —— `19387`(DSH Web)与 `20090`(垫片)**均无监听**,且无 electron 进程。⇒ 两条通道里,DSH 那条的**承载进程不在**,这也是 §10.3 那个进程树问题必须先定的原因。
|
||||
|
||||
Reference in new issue
Block a user