Files
dsh_ai1net_server/交付物/功能打包到基础插件-分步实施方案-20260926.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

51 KiB
Raw Permalink Blame History

功能打包到基础插件 · 分步实施方案(含逐棒验证与避坑)

  • 日期:2026-09-26
  • 上游判定:交付物/全功能承载判定-底层插件能否承-20260926.md §3.12(五块能进一个包;六件只剩三件)+ 交付物/AI1NET-三类节点结构调整可行性-20260926.md(承载两分)
  • 客户端侧依据:工作区 E:/ProgramData/AIProject/dsh-ai1net-desktop(只读引用,⛔ 不改其文件)
  • 编排方式:技能 dsh-auto-handoff-chain(规划棒 ↔ 执行棒交替 · 一棒一件事 · 收尾四件套)
  • 状态:规划件 —— 本文不出手改码、不部署;执行由后续棒次照单落地

§0 定位与四条硬边界

这件事是什么:把现在散在两处的功能(src/supervisor / web/routes / db / net/relay / im + 实例侧)收敛成一个可分发的基础插件包,让它成为「用户装官方 DSH + 一个包 = 全套能力」的那个包。

四条硬边界(⛔ 本方案全程遵守):

# 边界 为什么
1 ⛔ 不重造分发与更新机制 —— 平台侧共享包库 + 版本维度 + 三只只读口已上机;客户端侧拉取器已有对接单(docs/对接单_桌面端接入版本管理与包更新机制_20260926.md) 重造 = 两套指纹两套语义,必定打架(见 §4-C1)
2 ⛔ 不改官方 dsh 主程序与缓存(R2)—— 扩展只走官方插件/注入点 官方明写 developer preview「THERE WILL BE COMPATIBILITY-BREAKING CHANGES」⇒ 任何 fork 都会在下次升级时全量返工
3 ⛔ 不碰「包外面那三件」(起实例 / 取身份 / 把包装进去) 判定件 §3.12.5:那三件归部署方案与引导层,不在本方案的范围内
4 🔴 执行前必须先拿到用户确认(2026-09-26 13:19 用户明令:「现在只做方案 落地详细步骤和方案做好了 确认后在执行」) 方案阶段只出方案件与交接单;⛔ 任何棒次落码 / 部署前必须先交用户确认,⛔ 不许"自动接续"把执行棒带跑(见 §6.0)

§1 开工前必须先纠正的三条前提

⚠️ 这三条我上一轮(只读了平台仓库时)判断有误或不全,先纠正,否则方案会建在错前提上。

1.1 ✅ 官方桌面壳存在,且插件机制是同一套

我上一轮的说法 实际
「本仓库无 apps/、无桌面壳」 ⚠️ 只在平台仓库里成立 —— 官方壳在上游:deepseek-ai/deepseek-harness → apps/desktop(0.1.6-alpha.1)+ apps/desktop-host,private: true、未发 npm ⇒ 按官方源码直接构建,不 fork、零 patch

实测结论(客户端线 docs/S6_官方二次开发能力与桌面端扩展点实测_20260921.md):

官方明确支持二次开发,且是"一等公民"级设计;载体 = 插件(组合包 bundle)+ patch + profile;桌面端不是另一套,用的是同一套机制。 官方文档原文:「独立插件窗口获得结构化的列出、安装、移除、更新和更新检查操作」;「GUI 插件修改…使用相同的 staging、健康检查、激活与 rollback 路径」。

⛔ 我上一轮「桌面/服务器两条壳要分别实现」的说法要修正为:壳是两条,插件机制是一条。

1.2 ⚠️ 桌面端能跑平台,但没有多租户隔离 —— 不是"跑不了"

我上一轮的说法 实际(客户端线 CODEBUDDY.md §9 原文)
「setpriv/systemd-run 是 Linux 专有 ⇒ Windows 上这些代码路径不执行」 ⚠️ 那条链是 account 隔离档的依赖;客户端线走的是 soft 档 ⇒ setpriv/systemd-run 根本不在这条路上
「桌面机不能当承载」 ✅ 结论对,但理由要换 ⇒ 见下

🔴 实测原文两条:

  1. 「平台启动实例的方式在 Windows 上不成立:裸名 spawn('npm') → ENOENT(全局包是 .cmd 垫片);显式 .cmd → EINVAL;必须走 cmd.exe /c 或 shell: true(orchestrator.ts:663-665 现为不带 shell)」 ⇒ 🔴 这是全案唯一必须改平台代码的地方,也是客户端线的 M0 验收点。
  2. 「多人形态开关是零代码的(只配域名),但隔离档仍是 soft ⇒ 用户之间无隔离」

⇒ 🔴 准确说法:Windows 上能跑、能起实例;但 soft 档下用户之间没有隔离 ⇒ 所以桌面机只能当"主权承载"(自己的租户),不能当"公共承载"(别人的租户) —— 与 AI1NET-三类节点结构调整可行性 的判定同向,但现在有了实证理由。

1.3 ⭐ 桌面端没有 webServer —— 这是"一个包两端跑"的真正门槛

🔴 2026-09-28 加注(M6 归并的硬前提):本节不只是"一个包两端跑"的门槛 —— 它同样是 M6「设备接入」归并的硬前提。 依据(实测):垫片现状是 inject = ['connection', 'webServer'](靠 webServer.port 拿本机口,dsh-client/packages/device-shim/lib/index.js:61);而 ai1net 包要求 inject = ['connection'](dsh-plugin-ai1net/lib/index.js:102),且包内门禁 ①c 逐字断言这一行、并逐行断言代码里零 webServer 引用(dsh-plugin-ai1net/scripts/build.mjs:123 附近)。 ⇒ 归并必须先去掉 webServer 依赖:端口改包内配置常量 + 动态发现(与 §5.2 的上游改造是同一处改动,正好合并)。⛔ 不改 ⇒ 门禁红 + 桌面端逐字复现本节的 pending 现象。

实测(S6 §3,出处 apps/desktop-host/src/index.ts:326-343 + dsh-client-connection/lib/index.js:547-601):

扩展点 Web 组合 桌面组合(实测)
webServer 具名路由 / fallback ✅ 有(激活即 listen) ❌ 服务根本不存在(壳内 host 走 fd 管道、不 listen)
/api/* 服务端逻辑 经 webServer ✅ ctx.connection.fetch.register({path, methods, fetch})
⚠️ 路径匹配 支持 exact / prefix 🔴 只支持精确路径,⛔ 无前缀匹配
页面级路由(新增一张页) ✅ register({kind:'exact'|'prefix'}) ❌ 无页面路由(/api → connection,其余全归 SPA dist)
浏览器端插件(client bundle) ✅ dsh.client + /plugins/* ✅ 同一套
index 注入 webserver/index-inject ✅ 同一事件(desktop-host/src/index.ts:191)

⇒ 🔴 所以"一个包"不是"写一遍就行",而是"必须只用两端都存在的扩展点子集":

可用交集 = { connection.fetch.register(精确路径) · dsh.client(浏览器端) · index 注入 }
⛔ 禁用   = { webServer.register · 前缀路由 · 页面级路由 }

§2 目标形态

        ┌──────────────── 一个发行包(组合包 bundle)────────────────┐
        │  模块① 多租户界面与请求   模块② 模型管理                  │
        │  模块③ 技能插件管理       模块④ 覆盖网络界面              │
        │  模块⑤ IM 插件侧         (+ client bundle:五个卡片)     │
        └───────────────────────────┬───────────────────────────────┘
                    同一份代码,两处加载(同一个 pluginId、同一个库)
        ┌───────────────────────────┴───────────────────────────────┐
        ▼                                                           ▼
  【实例内 / 桌面壳内】                                      【平台进程】
  · 全部业务逻辑                                             · 只接受"请求"
  · 依赖 = 扩展点交集                                        · 要写平台库的动作在这里
  · 桌面端 = 官方壳 + 本包;服务器端 = 官方 dsh + 本包          · ⚠️ 平台不是"实例"⇒ 装不了插件
                                                            (判定件 §3.12.3)

一个包、一个 pluginId、一个库(dshs_pl_<pluginId>),库内表按模块分段命名。


§3 分步实施方案

每步格式:目标 / 前置 / 步骤 / 🔴 验证(可复现)/ ⚠️ 坑 / 回滚 / 完成判据。 🔴 验证一律要"能变红" —— 不通过"负控"(故意改坏一处必须报红)的判据,不算判据。

S0 · 立验证地基(先立尺,再动手)

目标:让 S1 之后的每一步都有一键可复现的判据。⛔ 这一步不做,后面所有"验证"都是自说自话。

前置:无。不需要拍板、不需要改任何代码。

步骤:

  1. 取基线(两端各一条命令,结果落文件):
    • 平台侧:共享层包数与指纹(现读数为 4 个包,dsh-plugin-mcn-suite = 0.5.1 / 27f6c6d7… / 659 文件 / 2,018,751 B)
    • 客户端侧:本机 profile 的 bundle 清单(现为 4 个 bundle、无 @dsh-local/*)
  2. 建「插件四判据」冒烟脚本(用已有插件当靶子,⛔ 不用新包):装配 ✓ / 运行 ✓ / 取数 ✓ / 回调 ✓
  3. 建「浏览器级判据」脚本(客户端 UI 必须有这一关)
  4. 做负控:故意把靶子改坏一处 ⇒ 脚本必须报红;改回 ⇒ 必须报绿

🔴 验证:

  • 同一脚本连跑两次,结果一致(排除偶发)
  • 负控通过:改坏 ⇒ 红|改回 ⇒ 绿(这一条是 S0 的唯一完成判据)

⚠️ 坑:

  • ⚠️ dump-config 验不了运行时挂载(官方 "without booting" ⇒ 不跑 apply())⇒ 证据必须取活体 boot($B/home-adapt/logs/startup-*.log 有 fiber 级 outcome)
  • ⚠️ 启动日志只记失败条目(插件 logger.info 不在其中)⇒ 运行时挂载证据只能取 RPC 或 boot 失败面
  • 🔴 node --check +「client bundle 可组合」+「boot 本包零报错」三条全绿也证明不了客户端能加载 ⇒ UI 交付必须有浏览器级判据

回滚:删脚本(无副作用)。 完成判据:负控红/绿各一次,且连跑两次一致。


S1 · 定「包规格书」(不动一行代码)

目标:产出一份冷读者能照做的包规格书。这是后面所有棒次的共同输入。

前置:S0 完成。

步骤:

  1. 列模块:五个模块的名称、职责、边界(哪一块的哪一半在平台侧)
  2. 逐条列扩展点,每条必须标注两端是否都有:
    • ⛔ 出现任何 webServer / 前缀路由 / 页面级路由 ⇒ 当场改成 connection.fetch.register + 精确路径
  3. 列全部 API 精确路径(⛔ 不能靠前缀)—— 桌面端只认精确路径
  4. 定包名与库名:包名 ⇒ 库名 dshs_pl_<pluginId>,长度必须 ≤ 48、[a-z][a-z0-9_]{0,40}
  5. 定表名分段方案(库内表加 p_<pluginId>_ 前缀,模块靠后半段区分)
  6. 定迁移入口:只留一个入口、按模块分步执行(治"一个包一个库 ⇒ 一次迁移牵动全部")

🔴 验证:

  • 冷读者测试:把规格书给一个不参与本方案的会话读,问它「能不能照做」—— 不能 ⇒ 补
  • 逐条扩展点对表:与 §1.3 的表逐条比对,⛔ 零条落在"桌面不存在"那一列
  • 库名长度硬校验:用正则跑一遍(⛔ 别目测)

⚠️ 坑:见 §4-B1 / §4-D2。回滚:无(纯文档)。 完成判据:规格书里没有一条扩展点只在 Web 端存在。


S2 · 空包两端落地(最小可运行骨架)

目标:一个最小包(一个 client 卡片 + 一条 host 路由),两端都能装、都能出界面。

前置:S1 规格书完成。

步骤:

  1. 按官方三步做包:apply(ctx) 模块 → package.json 写 "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } → dsh plugin add
  2. host 半:ctx.connection.fetch.register({path:'/api/<name>/ping', methods:['GET'], fetch}),inject = ['connection'] 必写
  3. client 半:dsh.client + lazy-CJS 封包(⛔ 零顶层 import/export/require)
  4. 两端各起一次,看界面

🔴 验证(四层,缺一不算过):

层 怎么验 过了说明什么
语法 node --check ⚠️ 只说明语法
装进 profile 本机 profile 里出现该包 装配通
活体 boot startup-*.log 有 fiber 级 outcome 挂载通
浏览器级 真壳里出现卡片、JS 错误 0、无 Failed to load plugins ✅ 唯一能证明 UI 真的能用

⚠️ 坑:§4-A1(lazy-CJS)· §4-A2(inject 必须声明)· §4-A3(maxDepth 不可省)· §4-A4(静态注入是坏的)· §4-E2(改插件后必重启壳)。 回滚:从 profile 移除该包(⛔ 目录用 mv,不删)。 完成判据:两端都出现卡片;浏览器控制台 JS 错误 = 0。


S3 · 数据面(一个包一个库)

目标:包能建自己的库、跑迁移、被对账。

前置:S2 通过。

步骤:

  1. 声明数据面 ⇒ 平台侧建库 dshs_pl_<pluginId>(⛔ 顺序:先建库再投放)
  2. 迁移:模块分段、单入口分步执行
  3. 对账:pg_database 的 dshs_pl_* × 控制面台账
  4. 改声明后回读 yaml 实际值(⛔ 不回读 = 会漏落)

🔴 验证:

  • 建库 → 迁移 → 对账三轮全绿;库名匹配 ^dshs_pl_[a-z][a-z0-9_]{0,40}$
  • 负控:改一处声明不回读 ⇒ 必须能查出 yaml 与声明不一致(证明"回读"这一步真的在起作用)

⚠️ 坑:§4-D1 · §4-D2 · §4-D3 · §4-D4。 回滚:库用 mv 式子改名冻结,⛔ 不 DROP。 完成判据:三个模块的表都在同一个库里且能单独回滚。


S4 · 接进既有的分发与更新机制(⛔ 一行都别重造)

目标:包能被平台对账 + 增量拉取 + 原子换上,两端指纹逐字一致。

前置:S3 通过。⚠️ 本步依赖一个待拍板项(§6-P1:平台侧只读三口增认「设备身份」)—— 未拍板时只做能做的(手工主机密钥干跑 happy path)。

步骤:

  1. 照抄权威实现:src/worker/agent.ts 的 syncSharedLayerOnce() + pullOnePack()(纯 Node、无框架依赖)
  2. 实现四步:对账 → 取包 → 落地(原子换六动作)→ 回报
  3. 接进 profile 装配:依赖写 "<包名>": "link:<目录绝对路径>"
  4. 补 @deepseek-ai/* 软链(共享层里零个这些包,它们是宿主提供的 optional peer)
  5. 镜像目录绝对路径与平台侧逐字同形

🔴 验证:

  • 指纹逐字相同(⛔ 不是"大概一致");对账 diff 三个集合全空
  • 三条反例判据(⛔ 不验不算完成):
    1. 清单拿不到(断网/黑洞)⇒ 本地文件零改动(前后指纹一致)
    2. 单个包失败 ⇒ 其余包照常更新(不是整轮放弃)
    3. extra(本地多出来的包)⇒ 平台只告知,本地一个文件没被动过
  • 装配后:本机 profile 出现该包 ⇒ 实例页「能力管理(插件/技能)」栏开始出现

⚠️ 坑:§4-C1 – §4-C5(指纹算法不一致 ⇒ 永远 stale、每轮全量重拉;对账与下载共用超时;link: 目标两机同路径;软链缺失表现为"包在但加载不了")。 回滚:停掉拉取器即回滚(拉取失败本就不动本地文件 ⇒ 无数据风险)。 完成判据:三条反例判据全过 + 指纹逐字相同。


S5 · 第一块迁移:IM 插件侧(选它是因为它有现成模板)

目标:把 IM 的插件侧并入基础包。

前置:S4 通过。

为什么第一个做它:判定件 §3.12.3 / §3.12.6 —— IM 是今天唯一「装配 / 运行 / 取数 / 回调」四面齐全的 ⇒ 它就是模板,其余四块照它做。

步骤:

  1. 照 IM 的接入方式把插件侧搬进包(⛔ 是重写,不是 git mv)
  2. 补上残留两条:① 校验"插件是否真在该实例启用" ② 一条实例只该有一条常驻长轮询

🔴 验证:

  • IM 四条面(装配/运行/取数/回调)在新包里逐条复现
  • 回归:迁移前后,同一房间的消息收发行为一致(⛔ 用同一组输入比对)
  • 负控:故意让长轮询起两条 ⇒ 必须能检出

⚠️ 坑:§4-B2 · §4-C1。 回滚:切回旧实现(两套并存一段时间,⛔ 不删旧的)。 完成判据:四条面齐全 + 行为回归一致 + 残留两条已补。


S6 · 第二块:技能插件管理(含"更新-告知 / 自动")

目标:上传 → 检测 → 建库 → 启用 → 更新,全链进包。

前置:S5 通过。

步骤:

  1. 管理面(上传/检测/建库/启用)进包
  2. 「更新」四拆照判定件 §2.1:① 看见新版本 ✅ 包 ② 点更新按钮 ✅ 包(只到"请求")③ 把新包搬进来 → 走 S4 的拉取器(⛔ 不在包里自己搬)④ 「设置自动处理」→ 执行者必须在包外
  3. package.json 指向 yaml;自增 id ⛔ 不能照搬;UI ≤900px 必须独占

🔴 验证:

  • 全链 dry-run 一遍
  • "看见新版本"的判据:包只报"有差异",⛔ 不自己下结论(对齐既有纪律:「不知道」不是「已知」)
  • 「自动处理」开关在包外时,关掉执行者 ⇒ 行为必须停(证明执行者确实在包外)

⚠️ 坑:§4-D1 · §4-E1。 完成判据:更新链的每一步都能指出"谁在做",且执行者都在包外。


S7 · 第三块:模型管理

目标:配置 / 凭据 / 共享授权 / 界面进包。

前置:S6 通过。

🔴 验证:

  • 共享模型走管理员逐用户授权(users.shared_model_granted,v11 默认 0)—— 负控:未授权用户必须看不见
  • ⚠️ 用户卷在 worker 上时,写 home 必须走 UserFs;负控:直写 fs ⇒ 必须能检出"静默空操作"

⚠️ 坑:§4-D4(homedir() 必错)· §4-F3。 完成判据:模型配置在实例内可见、平台级凭据⛔不下发到客户端(R5 客户端专属红线)。


S8 · 第四块:覆盖网络界面(⚠️ 身份与隧道留在包外)

目标:看哪些区 / 选加入 / 跨区可见性 / 界面进包。入网凭据与隧道 ⛔ 不进。

前置:S7 通过。⚠️ 并且 F1.5(G1 序号 + G6 撤根)必须先有结论 —— 定稿原文「F1.5 是一切的前置」。

🔴 验证:

  • 界面能显示"有哪些区可加入" —— ⚠️ ⛔ 不可做成公开列表(拓扑信息即扩大暴露面),改法=凭邀请码;负控:无邀请码时必须看不到任何区
  • 跨区可见性跟随「用户 × 区」成员关系(已拍板);负控:无成员关系 ⇒ 不可见

⚠️ 坑:§4-F4(egressCidrs 语义)· §4-G1。 完成判据:包内零身份/隧道逻辑。


S9 · 第五块:多租户(包里只做界面 + 请求)

目标:租户列表 / 配额 / 成员 / 账单界面进包;"创建隔离实例"这个动作落平台进程。

前置:S8 通过。⚠️ 依赖 §6-P2(平台侧那一半怎么落)拍板。

🔴 验证:

  • 界面 + 请求在包内;动作在平台 —— 停掉平台侧 ⇒ 界面必须明确报"做不到",⛔ 不许假装成功
  • 配额相关:⚠️ 配额 384 MiB / V8 堆按宿主算 —— 负控:改宿主规格,租户侧看到的数必须跟着变

⚠️ 坑:§4-B3(平台不是"实例"⇒ 装不了插件)。 完成判据:包里没有任何直接写平台库的代码路径。


§3.10 落地详细步骤(S0–S9 逐步照做清单)

§3 说的是「每步要达成什么」,本节说的是「手怎么动」。 🔴 本节仍是方案,⛔ 不是执行授权 —— 全部动作须用户确认后才可开工(见 §6.0)。

3.10.1 先把「尺子」定死(S0 的产出规格)

后面九步的判据一律不许临时目测,只许调 S0 建出来的四把尺子:

尺子 名字 用法 绿 / 红怎么算
基线 v-baseline.sh 取两端基线(共享层包数+指纹 / 本机 profile bundle 清单),落文件 rc=0,且连跑两次逐字一致
插件四判据 v-plugin.sh <装配|运行|取数|回调> 拿已有插件当靶子,逐面判 rc=0 绿 / rc≠0 红
浏览器级 v-ui.sh 真壳起一次:出卡片 + JS 错误 0 + 无 Failed to load plugins rc=0 绿 / rc≠0 红
负控驱动 v-neg.sh <case> 改坏靶子一处 ⇒ 断言 v-plugin.sh 必须红 ⇒ 改回 ⇒ 断言必须绿 两个断言都成立才 rc=0

🔴 三条约定先立,否则后面全是扯皮:

  1. 退出码即结论:绿 = rc 0、红 = rc≠0。⛔ 不许拿"脚本打印了一行 OK"当绿。
  2. 只许测活体:「不 boot 就出结果」的路子(dump-config 类)测不出运行时挂载 ⇒ 证据必须来自活体 boot 日志的 fiber outcome 或一次真实请求。
  3. 落点 = 交付物/验证脚本/(与方案件同处,已在入库范围);⛔ 不放进文档库 07-scripts/(那里绑宿主 hook,动它影响全平台)。

3.10.2 S0 · 立验证地基

编号 动作 在哪做 产物 判据
S0-1 取平台侧基线(包数 + 每包指纹) 平台侧(只读) 交付物/验证脚本/baseline-平台-<日期>.txt 现值应为 4 包
S0-2 取客户端侧基线(profile bundle 清单) 本机 baseline-客户端-<日期>.txt 现值应为 4 bundle、无 @dsh-local/*
S0-3 写 v-plugin.sh(四面判据) 本机 脚本 拿已有插件跑 ⇒ 四面全绿
S0-4 写 v-ui.sh(浏览器级) 本机 脚本 拿已有插件跑 ⇒ 卡片出现、JS 错误 0
S0-5 写 v-neg.sh 并跑负控 本机 脚本 + 负控记录 改坏 ⇒ 红、改回 ⇒ 绿(各一次)
S0-6 连跑两次对比 本机 对比记录 两次输出逐字一致

失败怎么办:若 S0-3/S0-4 拿已有插件都跑不绿 ⇒ 🔴 立刻停,说明基线本身有问题,先修尺子再谈打包(⛔ 不许"先往下走、回头再修")。

3.10.3 S1 · 定包规格书

编号 动作 在哪做 产物 判据
S1-1 列五个模块的名称/职责/边界(标明哪一块的哪一半在平台侧) 文档 规格书 §1 冷读者能说出"这块归谁"
S1-2 逐条列扩展点,每条标"两端是否都有" 文档 规格书 §2 与 §1.3 表逐条比对
S1-3 列全部 API 精确路径 文档 规格书 §3 ⛔ 零条前缀
S1-4 定包名 → 库名,跑正则 文档 规格书 §4 匹配 ^dshs_pl_[a-z][a-z0-9_]{0,40}$ 且总长 ≤48
S1-5 定表名分段(p_<pluginId>_ + 模块后半段) 文档 规格书 §5 同库内任意两表不重名
S1-6 定唯一迁移入口(按模块分步) 文档 规格书 §6 能指出"只回滚某模块"走哪一步

失败怎么办:一旦出现 webServer / 前缀路由 / 页面级路由 ⇒ 当场改掉,⛔ 不许"先留着后面再说"。

3.10.4 S2 · 空包两端落地

编号 动作 在哪做 产物 判据
S2-1 按官方三步建最小包(apply(ctx) → package.json 写 bundle patch → dsh plugin add) 本机仓库 包骨架 v-plugin.sh 装配 绿
S2-2 host 半:注册一条精确路径路由,inject 必写 本机仓库 路由代码 v-plugin.sh 取数 绿
S2-3 client 半:dsh.client + lazy-CJS 封包(⛔ 零顶层 import/export/require) 本机仓库 卡片代码 封包契约门禁过
S2-4 两端各起一次 平台侧 + 本机壳 截图 / 日志 v-ui.sh 绿(两端各一次)

失败怎么办:卡片不出现 ↦ §4-A1|路由静默 404 ↦ §4-A2(inject 没声明)|启动报错 ↦ §4-A3/A4。

3.10.5 S3 · 数据面

编号 动作 在哪做 产物 判据
S3-1 声明数据面 本机仓库 声明文件 回读 yaml 实际值并与声明一致
S3-2 先建库 dshs_pl_<pluginId>(⛔ 不许先投放) 平台侧 库 库名匹配正则、总长 ≤48
S3-3 迁移:单入口、按模块分步 平台侧 表 三模块的表都在同一库
S3-4 对账:pg_database 的 dshs_pl_* × 控制面台账 平台侧 对账输出 两集合一致
S3-5 负控:故意改一处声明且不回读 本机 负控记录 必须能查出"yaml 与声明不一致"

失败怎么办:库没建上 ↦ §4-D5(顺序反了)|声明改了没落 ↦ §4-D3(没回读)。

3.10.6 S4 · 接进既有分发机制

编号 动作 在哪做 产物 判据
S4-1 照抄权威实现(syncSharedLayerOnce() / pullOnePack(),纯 Node) 本机仓库 拉取器 与权威实现逐段对得上
S4-2 实现四步:对账 → 取包 → 落地(原子换六动作)→ 回报 本机仓库 拉取器 happy path 跑通
S4-3 依赖写 link:<目录绝对路径> profile profile 依赖 两端路径逐字同形
S4-4 补 @deepseek-ai/* 软链 客户端侧 软链 缺软链的现象="包在但加载不了"
S4-5 三条反例判据:清单拿不到 / 单包失败 / extra 本机 三条记录 ⛔ 不验不算完成
S4-6 装进 profile 后看实例页能力栏 两端 截图 能力栏开始出现

失败怎么办:每轮全量重拉 ↦ §4-C1(指纹算法不一致)|超时怪 ↦ §4-C2(对账 20 s 与下载 300 s 必须分开算)|"不知道"被当成"一致" ↦ §4-C5。 ⚠️ 本步依赖待拍板项 P1 —— 未拍板时只做能做的(用主机密钥干跑 happy path)。

3.10.7 S5 · 第一块迁移(IM 插件侧 · 模板块)

编号 动作 在哪做 产物 判据
S5-1 照 IM 接入方式把插件侧重写进包(⛔ 不是 git mv) 本机仓库 模块⑤ 四条面(装配/运行/取数/回调)逐条复现
S5-2 补残留①:校验"插件是否真在该实例启用" 本机仓库 代码 未启用 ⇒ 必须拒
S5-3 补残留②:一个实例只允许一条常驻长轮询 本机仓库 代码 负控:故意起两条 ⇒ 必须检出
S5-4 回归:迁移前后,同一组输入的消息收发行为一致 两端 比对记录 同输入 ⇒ 同行为

失败怎么办:旧实现不删,两套并存一段时间(回滚 = 切回旧的)。

3.10.8 S6 · 第二块(技能插件管理)

编号 动作 在哪做 产物 判据
S6-1 上传/检测/建库/启用 进包 本机仓库 模块③ v-plugin.sh 四面绿
S6-2 「更新」四拆:①②进包(只到"请求");③ 搬包走 S4 拉取器;④ 执行者留包外 本机仓库 代码 能逐条指出"谁在做",且执行者在包外
S6-3 package.json 指向 yaml;自增 id ⛔ 不照搬;UI ≤900px 独占 本机仓库 代码 窄屏截图
S6-4 负控:关掉包外执行者 ⇒ 自动更新行为必须停 本机 负控记录 行为确实停

失败怎么办:改完没生效 ↦ §4-E1(测试跑的是构建产物,先 build)。

3.10.9 S7 · 第三块(模型管理)

编号 动作 在哪做 产物 判据
S7-1 模型配置/凭据/共享授权/界面 进包 本机仓库 模块② v-plugin.sh 绿
S7-2 共享模型走管理员逐用户授权(默认未授权) 本机仓库 代码 负控:未授权用户看不见
S7-3 写 home 一律走 UserFs 本机仓库 代码 负控:直写 fs ⇒ 能检出"静默空操作"
S7-4 验"平台级凭据 ⛔ 不下发客户端"(R5 红线) 两端 检查记录 客户端侧零平台凭据

失败怎么办:homedir() 写错 ↦ §4-D4。

3.10.10 S8 · 第四块(覆盖网络界面)

编号 动作 在哪做 产物 判据
S8-1 界面:看哪些区 / 选加入 / 跨区可见性 本机仓库 模块④ v-ui.sh 绿
S8-2 ⚠️ ⛔ 不做公开区列表(拓扑即暴露面)⇒ 改凭邀请码 本机仓库 代码 负控:无邀请码 ⇒ 看不到任何区
S8-3 跨区可见性跟随「用户 × 区」成员关系 本机仓库 代码 负控:无成员关系 ⇒ 不可见
S8-4 确认包内零身份/隧道逻辑 本机仓库 检查记录 搜不到身份/隧道代码路径

⛔ 本步的硬门:F1.5(G1 序号 + G6 撤根)必须先有结论 —— 定稿原文「F1.5 是一切的前置」。 失败怎么办:连不上 ↦ §4-F2(改走 unix socket)|egressCidrs 语义 ↦ §4-F3。

3.10.11 S9 · 第五块(多租户)

编号 动作 在哪做 产物 判据
S9-1 租户列表/配额/成员/账单 界面进包 本机仓库 模块① v-ui.sh 绿
S9-2 "创建隔离实例"这个动作留平台进程 平台侧 代码 包里搜不到写平台库的路径
S9-3 验"平台侧停掉 ⇒ 界面必须明确报做不到" 两端 负控记录 ⛔ 不许假装成功
S9-4 配额按宿主算的负控:改宿主规格 ⇒ 租户侧看到的数跟着变 平台侧 负控记录 确实跟着变

失败怎么办:想"在包里直接写平台库" ↦ §4-B3(平台不是"实例",装不了插件)。

3.10.12 每一步都要"停得住"(治"做一半烂在那里")

步 停下来时的状态 会不会留半成品
S0 只有几个脚本 不留(零副作用)
S1 只有一份文档 不留
S2 空包可卸 卸掉即回原状
S3 库空着 库用改名冻结,⛔ 不 DROP
S4 停拉取器即停 拉取失败本就不动本地文件
S5–S9 新旧并存 切回旧实现

⇒ 🔴 所以本方案允许"随时叫停":任何一步停下来,系统都还是能用的状态。这也是"确认后再执行"能成立的前提。


§4 避坑总表(这是本方案的第二主产出)

每条:现象 → 根因 → 后果 → 规避 → 证伪手段。全部来自两端实测,⛔ 没有一条是推测。

A 类 · 客户端 / UI(最容易"看着是绿的其实没生效")

# 现象 根因 后果 规避 证伪手段
A1 卡片根本不出现、页面报 Failed to load plugins 🔴 官方 client bundle 契约 = lazy-CJS 封包:形如 window.__ModuleLoader__.load({id, factory}),factory(require) 内 require('react')…,末尾 exports.apply/exports.inject;零顶层 import/export/require。手写 ESM 直交 ⇒ 浏览器按经典脚本加载 ⇒ SyntaxError: Cannot use import statement outside a module 🔴 node --check +「可组合」+「boot 零报错」三条全绿也证明不了能加载 按 lazy-CJS 封包写;scripts/build.mjs 的封包契约门禁必须过 浏览器级:真壳里出现卡片 + JS 错误 0 + 无 Failed to load plugins
A2 HTTP 路由静默 404 🔴🔴 cordis 的服务只能靠 inject 拿 —— ctx.connection 未在 inject 声明 ⇒ cannot get property "connection" without inject;ctx.reflect.get('connection', false)(官方注释 "without the inject requirement")返回 undefined 只写防御式 try/catch ⇒ 路由不挂、静默 404,看着像"没人调" inject = ['connection'] 必写(官方同类包 file-upload/ui-deliverables 全如此) 真发一次请求;⛔ 别只看"没报错"
A3 挂载抛错 maxDepth:'provider-managed' 不可省 整包挂不上 照官方样板写 活体 boot 有该条 fiber outcome
A4 ERR_MODULE_NOT_FOUND 静态注入是坏的 ⇒ 以动态为准 包在但加载不了 用动态注入 活体 boot
A5 改了却没生效 模块级常量在加载时读,不解热更新 白改一轮 改插件后必须重启壳 重启前后各取一次快照对比
A6 一个可选插件把壳搞得起不来 可选插件在服务缺失时抛错 ⇒ plugin tree failed to load: 1 entry did not activate 整棵插件树不加载 服务缺失时降级告警,⛔ 不抛错 故意注掉一个服务 ⇒ 其余插件仍活
A7 页面看着完全正常,但一调用全局就炸 把 JS/CSS 内联进 <script>/<style> 未转义 </script/</style/<!-- HTML 解析器提前闭合 ⇒ 全局从未建立 内联前一律转义;IIFE + 捕获阶段拦默认提交 浏览器控制台
A8 「登录接口 200,下一步却 401」 dsh-app: 是自定义协议 ⇒ Chromium 不落 Set-Cookie(cookie 存储只认 http/https/ftp/ws/wss) 看着像登录失败,其实是 cookie 被丢 壳内用显式凭据往返 抓响应头/请求头,⛔ 不看 cookie 存储
A9 登录成功后回到初始页 同 URL 整页导航会冲掉壳内现场 观感"登录完又回到起点" 改写成 <入口>#<标记> 走同文档片段 观察是否整页重载
A10 同一份脚本塞进同一文档跑第二次必崩 平台门户脚本体不幂等(经典脚本顶层写 const pwInput,而经典脚本共享全局词法环境)🔴 最危险的不是报错,是静默降级:form.addEventListener('submit') 没挂上 ⇒ 表单走默认提交 ⇒ 凭据进 URL 全新壳里第一次跑是好的 ⇒ 极易判成"偶发" body 内联脚本一律包 IIFE + 捕获阶段拦默认提交 挂载两次,看 URL 是否带 ?username=
A11 host 半判"不安全"并整体跳过注入 注入脚本正文里出现脚本闭合标签字面量 现象="插件 mounted=9 但壳里什么都没有" 注入脚本正文⛔ 不写闭合标签字面量 host 半日志

B 类 · 扩展点(一个包两端跑的门槛)

# 现象 根因 规避 / 证伪
B1 桌面端插件树加载失败:pending (waiting for service: webServer) 🔴 桌面端没有 webServer(壳内 host 走 fd 管道、不 listen) 桌面端必须用 connection;规格书逐条标注"两端是否都有"|🔴 这也是 M6 归并的硬前提(2026-09-28 加注):M6 的 host 半若带 webServer 依赖,桌面端会逐字复现本行现象;归并时端口一律取包内配置常量(⛔ 不读宿主服务),且包内门禁 ①c 会逐行抓 webServer
B2 API 明明注册了却 404 🔴 桌面端 只支持精确路径,⛔ 无前缀匹配 每条路由逐条列精确路径;证伪=逐条真发
B3 「放进一个包」落了地一半 🔴 平台不是"实例" ⇒ 装不了插件 ⇒ 要写平台库的动作只能加进平台进程本身 包里⛔ 不写直连平台库的路径;证伪=停掉平台侧,界面必须明确报做不到
B4 新增页面无处可挂 桌面端无页面路由 走同文档渲染

C 类 · 装配与分发

# 现象 根因 规避 / 证伪
C1 永远判 stale、每轮全量重拉 🔴 指纹算法两侧不一致(协议里的"指纹"是内容归一化后的哈希;⛔ 别用 md5 of tar / sha256 of file list 之类的近似物) 逐字同规则;抄 src/worker/agent.ts 的 syncSharedLayerOnce()/pullOnePack()
C2 2 MB 包跨云报 aborted due to timeout 对账与下载共用一个超时(对账 20 s / 下载 300 s、上限 900 s、最低速率 64 KiB/s —— 必须分开算) 两套超时分开;全部可用环境变量覆盖,⛔ 不写死
C3 装配失配 link: 目标路径不同 镜像目录绝对路径与平台侧逐字同形
C4 ERR_MODULE_NOT_FOUND("包在但加载不了"的假故障) 共享层里零个 @deepseek-ai/*(宿主提供的 optional peer) 必须补软链
C5 「不知道」被折算成「已知」 指纹拿不到时报/存了"没问题" 🔴 纪律:算不出指纹就报"算不出";传/存 null ⇒ 平台按 stale 处理,⛔ 绝不折算成"一致"
C6 失败一轮全放弃 / 删了不该删的 三条语义纪律 ① 清单取不到 ⇒ 本轮什么都不做 ② 单包失败 ⇒ 只记 failed,其余照常 ③ extra ⇒ 只告知,绝不删除

D 类 · 数据

# 现象 根因 规避 / 证伪
D1 迁移一失败五块全挂 一个包 = 一个 pluginId = 一个库 表名分段 + 单入口按模块分步 + 每模块可单独回滚
D2 库名非法 / 建不出来 库名形状 dshs_pl_ + [a-z][a-z0-9_]{0,40}、总长 ≤ 48 用正则硬校验,⛔ 别目测
D3 列限声明改了却没生效 列级 maxBytes 平台无强制点 ⇒ 唯一把关人 = 插件侧 COLUMN_MAX_BYTES 改声明后必回读 yaml 实际值
D4 写 home 静默空操作 homedir() 必错(实例 HOME=ws);用户卷在 worker 上时直写 fs = 静默空操作 写 home 必须走 UserFs;⛔ 不得 cp/mv/直写 fs 动 home
D5 顺序错导致白干 先建库再投放 写进步骤顺序

E 类 · 构建 / 运行

# 现象 根因 规避 / 证伪
E1 测试全过、运行时还是旧行为 测试跑的是构建产物 ⇒ 改源码不构建 = 新旧混跑,症状像"改动无效" 改完必 build;证伪=比对 src 与 lib 时间戳
E2 "改了却没生效"(UI 侧) 见 A5 改插件后必须重启壳
E3 脚本 top-level await 直接报错 .ts 被当 CJS 自研 tsx 脚本一律用 .mts
E4 EBUSY 同步派生被拦(execFileSync/spawnSync) 解包/校验用异步 spawn
E5 npm 起不来(Windows) 平台 orchestrator.ts:663-665 现为不带 shell ⇒ 裸名 ENOENT(.cmd 垫片)、显式 .cmd EINVAL 走 cmd.exe /c 或 shell: true;⚠️ 这是全案唯一必须改平台代码的地方
E6 client bundle 加载失败 exports.default 禁用(lazy-CJS 封包契约) 只导出 apply + inject(R3)

F 类 · 系统 / 沙箱 / 网络

# 现象 根因 规避
F1 🔴 所有实例全挂 改 bwrap 参数前没看目标机 bubblewrap 版本(47=0.4.0/106=0.11.0);--perms 属 0.5+ ⇒ 47 上 Unknown option 要权限用 --tmpfs;⚠️ smoke-isolation 不能作 account 模式验收
F2 插件连不上自己 spawn 的 gateway 插件被 nft 拒连 127.0.0.0/8 改走 unix socket
F3 私网里的目标连不上 egressCidrs 语义:empty = 0.0.0.0/0(except private ranges) 收敛 egress 前先确认目标不在私网
F4 实例「起不来」被误判成 OOM 🔴 先查属主/EACCES —— root 跑过 dsh --profile ⇒ 属主变 root ⇒ 崩溃循环/404 find <home> -user root -exec chown <uid>:<uid> {} + → 重启
F5 106 上实例必崩 users/ 权限必须 711(700 ⇒ 实例必崩) 装机时校验

G 类 · 流程 / 协作

# 规则 出处
G1 🔴 不抢锁 / 抢不到就停手;⛔ 绝不"人工删锁 / 接管"(R9) 平台 §6 / 客户端 §7
G2 ⛔ 绝不 taskkill /IM electron.exe(WorkBuddy 自己就是 Electron)⇒ 杀壳只能按端口定位 PID 客户端
G3 ⛔ launcher | head 会让壳静默退出 ⇒ 启动器不接管道 客户端
G4 清目录一律 mv 改名,⛔ 不删(safe-delete 护栏整会话累计、Node 侧无确认通道) 客户端
G5 ⛔ 不新发凭据体系、⛔ 不下发平台共享密钥、⛔ 不新增端口(走 443) 对接单 §8
G6 ⚠️ 别用 grep https?:// 判"有没有网络依赖" —— updater 的 feed 地址来自 app-update.yml,源码里一个 URL 都没有(该 grep 必然漏,实测漏过一次) 客户端 §9
G7 ⚠️ "离线可用"要防的是"挂起",不是"秒拒" —— 拿"没人监听的端口"(秒 ECONNREFUSED)去验离线是假验证;每一处对外调用都必须带上限等待 客户端 §9
G8 ⛔ 改 / 重启必须写在回执里 —— 别用"改了源码"冒充"改了运行态" 对接单 §7-⑩

§5 验证基建:判据分五层(每层都要能变红)

层 怎么验 能证明什么 ⛔ 不能证明什么
1 语法 node --check 语法合法 一切运行时行为
2 契约 scripts/build.mjs 封包契约门禁 封包格式对 浏览器能否加载
3 装配 profile 里出现该包 装配通 运行态挂载
4 运行态 活体 boot(startup-*.log 有 fiber outcome) 挂载通 UI 能否渲染
5 浏览器级 真壳出现卡片 + JS 错误 0 + 无 Failed to load plugins ✅ UI 真的能用 —

🔴 三层铁律:

  1. UI 类交付必须有第 5 层(node --check + 可组合 + boot 零报错三条全绿也证明不了)
  2. 每条判据都要做负控(改坏 ⇒ 必须红)—— ⛔ 不通过负控的判据不是判据
  3. 反例与正例同等重要 —— 光验 happy path 不算验过(对接单 §5 已明写三条反例判据)

§6 接续棒编排(规划棒 ↔ 执行棒交替)

6.0 🔴 第 0 号停点:用户确认门(2026-09-26 13:19 立)

用户原话:「现在只做方案 落地详细步骤和方案做好了 确认后在执行」。

⇒ 🔴 本线的棒分两类,开跑条件不同:

棒的类型 能不能自动开跑 依据
规划棒(只出方案件 / 交接单;不改码、不部署、不 ssh) ✅ 可以 属于"做方案"本身
执行棒(任何落码 / 部署 / 改配置 / 动服务器) 🔴 一律先交用户确认,⛔ 不许自动登记 用户明令"确认后再执行"

这条门禁怎么落地(三条,⛔ 缺一不可):

  1. 规划棒收官时 ⛔ 不登记执行棒;只把成果交用户确认,并把"下一棒"写进入口「⏭️ 本线下一项」+ 逐字标注「⏸ 等用户确认后才可立棒」。
  2. 用户确认后,才由用户或新会话立执行棒(⛔ 不预登记队列)。
  3. 执行棒自己的 prompt 里也要写:范围以用户确认过的口径为准,⛔ 不自行扩大。

⚠️ 为什么这条要写进方案、而不是只写在入口:本 §6.1 原本写着"①→⑧ 自动交替接力",照那样跑会自动把执行棒带起来 ⇒ 与用户明令冲突。已在第 38 棒的 automation prompt 里同步掐掉"登记下一棒"这一步。

6.1 棒次表

棒 类型 目标 产出 / 判据
① 规划棒 冻结包规格书(S1)+ 把 S0 的验证基建写成执行单 05-交接单/交接单_包规格书_20260926.md(8 段);判据=零条扩展点只在 Web 端存在
② 执行棒 S0 立验证地基 四判据脚本 + 浏览器级脚本;判据=负控红/绿各一次
③ 规划棒 按规格书出 S2+S3 执行单 交接单;判据=规格书与执行单逐条对得上
④ 执行棒 S2 空包两端落地 两端出现卡片、JS 错误 0
⑤ 执行棒 S3 数据面 建库→迁移→对账三轮绿 + 负控
⑥ 执行棒 S4 接进既有分发机制 指纹逐字相同 + 三条反例判据
⑦ 规划棒 S5–S9 一块一单(或合并成两单) 交接单
⑧ 执行棒 ×N S5 → S9 逐块迁移(一块一棒) 每块各自的验证 + 回归

🔴 上表的开跑门(按 §6.0):① 规划棒可自动开跑;②–⑧ 全是执行棒 ⇒ 每一棒都要先拿到你的确认,⛔ 不许上一棒自动带起来。⚠️ 也就是说:这张棒次表是"路线图",不是"排期"。

⚠️ 块间并行的判据:S5–S9 不要并行 —— 它们共用一个库、一套迁移入口(§4-D1)⇒ 域不重叠这条不成立。

6.2 每棒的固定纪律(照抄进 prompt)

第 0 步:跑 state.py 看状态(只读、免抢锁)。
第 1 步:preflight-lock.sh "<会话名>" <目标文件...> —— 【D】未归类 或【E】机制层非空 ⇒ rc=1 ⇒ 拒开工。
第 1b 步:handoff-guard.sh --claim-exec "<会话名>" --domains <域...>;抢不到 = 只报告并停(⛔ 不删锁,R9)。
第 2 步:读唯一执行依据 = 入口 §2「🎯 本轮动作」块点名的那份交接单。
收尾四件套(缺一即算未完成):释放锁(必带会话名)· 🔴 接续(规划棒:可登记"下一棒";**执行棒:⛔ 一律不登记**,见 §6.0)· 推进入口 §2 · 写当日日志。
约束:⛔ 不 commit / push;⛔ 不改官方 dsh 主程序(R2);⛔ 不重造分发机制;⛔ 不扩大单子范围。
成本纪律:批量活先写脚本;取证 ≤3 条命令;⛔ 不 Glob/Grep 全库摸底。

6.3 停点清单(⛔ 顺序不可颠倒)

序 停点 触发时怎么办
0 🔴 用户确认门(§6.0) 任何执行棒开跑前都要过;⛔ 规划棒也不许替用户跳过
1 P1(桌面客户端要不要有只读读包权 · §7) 撞上 ⇒ 停下问,⛔ 不许自动跨过
2 P2(平台侧那一半怎么落) 同上
3 F1.5(G1 序号 + G6 撤根) S8 的硬门 —— 定稿原文「F1.5 是一切的前置」
4 S1 待拍板(106 现存实例怎么处置) 带数据不可逆 ⇒ 必须先拍板

⇒ 🔴 一句话:规划棒可以自动跑;执行棒要过两道门 —— 用户确认门(0)+ 它撞上的具体停点。


§7 停点与待拍板

⛔ 顺序不可颠倒;本轮要你确认两件:C0(路线)+ P1(权限)(P2 与前一轮的 S1 同批,等 P1 定了再问)。

🔴 C0 —— 这份方案的整体路线,认不认?

要你定的是:后面要不要按这份方案往下走(S0 → S9,先立验证地基再动手,一块一棒)。

为什么需要你定:这是"做不做、按什么顺序做",属业务优先级,⛔ 不是技术取舍 ⇒ 我不自决。

A 案 · 认这份路线 (优点:S0 先立尺,后面每一步都有可复现、能变红的判据,不会出现"看着做完了其实没生效";一块一棒、每一步都停得住。 缺点:S0+S1 是纯准备,前期看不到画面,要等 S2 才第一次出界面。)

B 案 · 先只做 S0+S1,看效果再定 (优点:成本最低、零副作用零部署、不留半成品;看完规格书与判据再决定要不要继续,判断依据最足。 缺点:要分两次做决定。)

C 案 · 调整顺序或范围 (比如先做某一块、或砍掉某一块 —— 直接说改哪一处即可。)

我的倾向:B 起手,认可后再走 A(可推翻)。理由:S0/S1 不改一行代码、不碰服务器,做完你手上会多一份可执行的包规格书 + 一套能变红的判据;这时候再决定后面走不走,依据最足。

🔴 P1 —— 要不要让桌面客户端有权读取平台的插件包?

要你定的是:桌面上的 dsh 实例,要不要被允许从平台把插件包拉下来(只读,不写)。

为什么需要你定:这是权限面的扩大 —— 设备在此之前只能"入网",此后能读到平台的插件包内容。影响的是所有装了客户端的设备,不是一台机器;而权限一旦放开,收回要靠撤设备授权。

A 案 · 放开(只读) (优点:桌面本机实例终于能拿到平台业务插件,否则那个能力栏永远不出现;复用已有设备公钥校验,⛔ 不新发凭据、⛔ 不下发平台共享密钥、⛔ 不新增端口;三口全是只读,读不到别的用户的数据、写不了任何东西;可撤回 —— 撤回设备授权即失效。 缺点:可见面确实扩大了一小格(设备从"只能入网"变成"能读包库");需要平台侧改一处校验臂并重启控制面。)

B 案 · 不放开 (优点:可见面一格不扩,维持现状最小权限;⛔ 平台侧零改动。 缺点:桌面本机实例永远拿不到平台业务插件 —— 那个缺口长期存在,「一个包两端一致」这件事在客户端侧落不了地,等于一半的方案做不成。)

我的倾向:A(可推翻)。理由:这是小幅、可撤回、只读的扩展,而它换来的是"两端装同一个包"这个核心目标;⛔ 不应为了一格只读可见面放弃整个目标。


§8 出处

内容 出处
五块能否进一个包 · 三件留外面 · 平台进程装不了插件 交付物/全功能承载判定-底层插件能否承-20260926.md §3.12
承载两分(公共 / 主权) 交付物/AI1NET-三类节点结构调整可行性-20260926.md §3
插件一等公民 · 桌面扩展点对照 · webServer 不存在 · 精确路径 客户端 docs/S6_官方二次开发能力与桌面端扩展点实测_20260921.md §1–§5
Windows 起实例的唯一改动点 · 隔离档 soft · 离线防挂起 · updater 三重可容忍 客户端 CODEBUDDY.md §9
客户端红线(凭据不下发 / 环境变量白名单) 客户端 CODEBUDDY.md §4 R2·R5
lazy-CJS 封包契约 · 必须有浏览器级判据 客户端 .workbuddy/接续入口_desktop_20260925.md §2 ⑥ ⑪
分发四步协议 · 三条语义纪律 · 坑清单 10 条 · 边界 5 条 客户端 docs/对接单_桌面端接入版本管理与包更新机制_20260926.md
平台权威实现(可照抄) src/worker/agent.ts 的 syncSharedLayerOnce() / pullOnePack()
一插件一库 · 库名规则 · link: 装配 · 列级 maxBytes src/db/plugin-data/schema.ts · diff.ts · registry.ts
bwrap 版本 · nft loopback · egressCidrs · 属主/EACCES 平台 .workbuddy/memory/MEMORY.md 本机铁律 + src/supervisor/*
编排方法 技能 dsh-auto-handoff-chain