- 变更规模:新增 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/ 知识文件,按口径入库)
51 KiB
功能打包到基础插件 · 分步实施方案(含逐棒验证与避坑)
- 日期: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 根本不在这条路上 |
| 「桌面机不能当承载」 | ✅ 结论对,但理由要换 ⇒ 见下 |
🔴 实测原文两条:
- 「平台启动实例的方式在 Windows 上不成立:裸名
spawn('npm')→ENOENT(全局包是.cmd垫片);显式.cmd→EINVAL;必须走cmd.exe /c或shell: true(orchestrator.ts:663-665现为不带 shell)」 ⇒ 🔴 这是全案唯一必须改平台代码的地方,也是客户端线的 M0 验收点。 - 「多人形态开关是零代码的(只配域名),但隔离档仍是
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 之后的每一步都有一键可复现的判据。⛔ 这一步不做,后面所有"验证"都是自说自话。
前置:无。不需要拍板、不需要改任何代码。
步骤:
- 取基线(两端各一条命令,结果落文件):
- 平台侧:共享层包数与指纹(现读数为 4 个包,
dsh-plugin-mcn-suite=0.5.1/27f6c6d7…/ 659 文件 / 2,018,751 B) - 客户端侧:本机 profile 的 bundle 清单(现为 4 个 bundle、无
@dsh-local/*)
- 平台侧:共享层包数与指纹(现读数为 4 个包,
- 建「插件四判据」冒烟脚本(用已有插件当靶子,⛔ 不用新包):装配 ✓ / 运行 ✓ / 取数 ✓ / 回调 ✓
- 建「浏览器级判据」脚本(客户端 UI 必须有这一关)
- 做负控:故意把靶子改坏一处 ⇒ 脚本必须报红;改回 ⇒ 必须报绿
🔴 验证:
- 同一脚本连跑两次,结果一致(排除偶发)
- 负控通过:改坏 ⇒ 红|改回 ⇒ 绿(这一条是 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 完成。
步骤:
- 列模块:五个模块的名称、职责、边界(哪一块的哪一半在平台侧)
- 逐条列扩展点,每条必须标注两端是否都有:
- ⛔ 出现任何
webServer/ 前缀路由 / 页面级路由 ⇒ 当场改成connection.fetch.register+ 精确路径
- ⛔ 出现任何
- 列全部 API 精确路径(⛔ 不能靠前缀)—— 桌面端只认精确路径
- 定包名与库名:包名 ⇒ 库名
dshs_pl_<pluginId>,长度必须 ≤ 48、[a-z][a-z0-9_]{0,40} - 定表名分段方案(库内表加
p_<pluginId>_前缀,模块靠后半段区分) - 定迁移入口:只留一个入口、按模块分步执行(治"一个包一个库 ⇒ 一次迁移牵动全部")
🔴 验证:
- 冷读者测试:把规格书给一个不参与本方案的会话读,问它「能不能照做」—— 不能 ⇒ 补
- 逐条扩展点对表:与 §1.3 的表逐条比对,⛔ 零条落在"桌面不存在"那一列
- 库名长度硬校验:用正则跑一遍(⛔ 别目测)
⚠️ 坑:见 §4-B1 / §4-D2。回滚:无(纯文档)。 完成判据:规格书里没有一条扩展点只在 Web 端存在。
S2 · 空包两端落地(最小可运行骨架)
目标:一个最小包(一个 client 卡片 + 一条 host 路由),两端都能装、都能出界面。
前置:S1 规格书完成。
步骤:
- 按官方三步做包:
apply(ctx)模块 →package.json写"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }→dsh plugin add - host 半:
ctx.connection.fetch.register({path:'/api/<name>/ping', methods:['GET'], fetch}),inject = ['connection']必写 - client 半:
dsh.client+ lazy-CJS 封包(⛔ 零顶层import/export/require) - 两端各起一次,看界面
🔴 验证(四层,缺一不算过):
| 层 | 怎么验 | 过了说明什么 |
|---|---|---|
| 语法 | 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 通过。
步骤:
- 声明数据面 ⇒ 平台侧建库
dshs_pl_<pluginId>(⛔ 顺序:先建库再投放) - 迁移:模块分段、单入口分步执行
- 对账:
pg_database的dshs_pl_*× 控制面台账 - 改声明后回读 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)。
步骤:
- 照抄权威实现:
src/worker/agent.ts的syncSharedLayerOnce()+pullOnePack()(纯 Node、无框架依赖) - 实现四步:对账 → 取包 → 落地(原子换六动作)→ 回报
- 接进 profile 装配:依赖写
"<包名>": "link:<目录绝对路径>" - 补
@deepseek-ai/*软链(共享层里零个这些包,它们是宿主提供的 optional peer) - 镜像目录绝对路径与平台侧逐字同形
🔴 验证:
- 指纹逐字相同(⛔ 不是"大概一致");对账
diff三个集合全空 - 三条反例判据(⛔ 不验不算完成):
- 清单拿不到(断网/黑洞)⇒ 本地文件零改动(前后指纹一致)
- 单个包失败 ⇒ 其余包照常更新(不是整轮放弃)
extra(本地多出来的包)⇒ 平台只告知,本地一个文件没被动过
- 装配后:本机 profile 出现该包 ⇒ 实例页「能力管理(插件/技能)」栏开始出现
⚠️ 坑:§4-C1 – §4-C5(指纹算法不一致 ⇒ 永远 stale、每轮全量重拉;对账与下载共用超时;link: 目标两机同路径;软链缺失表现为"包在但加载不了")。
回滚:停掉拉取器即回滚(拉取失败本就不动本地文件 ⇒ 无数据风险)。
完成判据:三条反例判据全过 + 指纹逐字相同。
S5 · 第一块迁移:IM 插件侧(选它是因为它有现成模板)
目标:把 IM 的插件侧并入基础包。
前置:S4 通过。
为什么第一个做它:判定件 §3.12.3 / §3.12.6 —— IM 是今天唯一「装配 / 运行 / 取数 / 回调」四面齐全的 ⇒ 它就是模板,其余四块照它做。
步骤:
- 照 IM 的接入方式把插件侧搬进包(⛔ 是重写,不是
git mv) - 补上残留两条:① 校验"插件是否真在该实例启用" ② 一条实例只该有一条常驻长轮询
🔴 验证:
- IM 四条面(装配/运行/取数/回调)在新包里逐条复现
- 回归:迁移前后,同一房间的消息收发行为一致(⛔ 用同一组输入比对)
- 负控:故意让长轮询起两条 ⇒ 必须能检出
⚠️ 坑:§4-B2 · §4-C1。 回滚:切回旧实现(两套并存一段时间,⛔ 不删旧的)。 完成判据:四条面齐全 + 行为回归一致 + 残留两条已补。
S6 · 第二块:技能插件管理(含"更新-告知 / 自动")
目标:上传 → 检测 → 建库 → 启用 → 更新,全链进包。
前置:S5 通过。
步骤:
- 管理面(上传/检测/建库/启用)进包
- 「更新」四拆照判定件 §2.1:① 看见新版本 ✅ 包 ② 点更新按钮 ✅ 包(只到"请求")③ 把新包搬进来 → 走 S4 的拉取器(⛔ 不在包里自己搬)④ 「设置自动处理」→ 执行者必须在包外
- 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 |
🔴 三条约定先立,否则后面全是扯皮:
- 退出码即结论:绿 =
rc 0、红 =rc≠0。⛔ 不许拿"脚本打印了一行 OK"当绿。 - 只许测活体:「不 boot 就出结果」的路子(
dump-config类)测不出运行时挂载 ⇒ 证据必须来自活体 boot 日志的 fiber outcome 或一次真实请求。 - 落点 =
交付物/验证脚本/(与方案件同处,已在入库范围);⛔ 不放进文档库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 真的能用 | — |
🔴 三层铁律:
- UI 类交付必须有第 5 层(
node --check+ 可组合 + boot 零报错三条全绿也证明不了) - 每条判据都要做负控(改坏 ⇒ 必须红)—— ⛔ 不通过负控的判据不是判据
- 反例与正例同等重要 —— 光验 happy path 不算验过(对接单 §5 已明写三条反例判据)
§6 接续棒编排(规划棒 ↔ 执行棒交替)
6.0 🔴 第 0 号停点:用户确认门(2026-09-26 13:19 立)
用户原话:「现在只做方案 落地详细步骤和方案做好了 确认后在执行」。
⇒ 🔴 本线的棒分两类,开跑条件不同:
| 棒的类型 | 能不能自动开跑 | 依据 |
|---|---|---|
| 规划棒(只出方案件 / 交接单;不改码、不部署、不 ssh) | ✅ 可以 | 属于"做方案"本身 |
| 执行棒(任何落码 / 部署 / 改配置 / 动服务器) | 🔴 一律先交用户确认,⛔ 不许自动登记 | 用户明令"确认后再执行" |
这条门禁怎么落地(三条,⛔ 缺一不可):
- 规划棒收官时 ⛔ 不登记执行棒;只把成果交用户确认,并把"下一棒"写进入口「⏭️ 本线下一项」+ 逐字标注「⏸ 等用户确认后才可立棒」。
- 用户确认后,才由用户或新会话立执行棒(⛔ 不预登记队列)。
- 执行棒自己的 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 |