- 变更规模:新增 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/ 知识文件,按口径入库)
26 KiB
手机 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 判定(三句话)
- 🔴 一处重要更正:你说的「客户端插件」= DSH 客户端(桌面)上的插件。DSH 有完整插件宿主 ⇒ 垫片保持既有 DSH Host 插件形态,v1 的"改独立进程"撤销。
- ✅ 账号核对不用新做 —— 平台侧已完整具备(§3)。你要的"我的账号只能连我的客户端、访问我的 WorkBuddy",正是既有那一套:网络名与 hostId 从账号推导 + 台账属于本人 + 四道闸。
- ✅ "一个程序两角色"也是既有模式 —— 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)⇒ 写死必炸。
发现判据(三项同时成立):
- 监听者是
WorkBuddy.exe(或其后代); GET /返回 200 且正文含CodeBuddy Gateway/CodeBuddy Remote Control;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 进程树内 —— 两者不是同一进程树 ⇒ 垫片拿不到令牌。
⇒ 候选解法(⛔ 留给定归属的那一棒,本会话不代做、不定):
- 把"取令牌"那一半挪进 WorkBuddy 进程树(WorkBuddy 的 skill / hook / 它 spawn 的子进程都能继承该 env;本会话已实测继承成立)。优点:不落盘、不重开漏洞、令牌永远新鲜。缺点:垫片从一个"进程内插件"裂成两半,多一条本机 IPC。
- 由 WorkBuddy 侧主动把令牌交给垫片(需 WorkBuddy 侧存在承载点,⛔ 不改官方客户端 —— R2)。优点:形态最干净。缺点:目前无此承载点,须先确认扩展面。
- 落盘(写
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 那个进程树问题必须先定的原因。