Files
dsh_ai1net_server/交付物/手机App经覆盖网络操作WorkBuddy-架构定稿v2-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

26 KiB
Raw Permalink Blame History

手机 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(不启 + 具名报错),⛔ 不许沿用上次端口。
  • 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 那个进程树问题必须先定的原因。