Files
dsh_ai1net_server/dsh-server-docs/archive/交接单-已完成/T03-plugin_package整合为单插件并投放.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

41 KiB
Raw Blame History

T03 · 把 plugin_package 7 个自研插件整合为 1 个功能插件并投放

  • 日期:2026-09-12
  • 状态:✅ 已完成并归档(2026-09-13 18:0x) —— 投放链路全通(上传 → admin 装 → guest 启用 → 实例重启探活成功);主体验收通过。收尾记录见 §十

⛔ 2026-09-13 09:4x 现场取证:这一步已被试过,但被平台自动回滚了。 guest 在 07:42:52 通过「功能管理」提交了启用(POST /api/plugins/mine/apply,任务 c8ac2686539aeb7c), 随后实例 07:43–07:45 连崩 5 次(exitCode=134,栈 = V8 JsonParser/HeapAllocator ⇒ V8 堆打满 abort), 07:46 触发熔断,平台自动回滚(package.json.bak-rollback-20260913T0748)⇒ 现在 guest bundles 里没有 mcn-suite。 根因指向 --max-old-space-size=160 太紧(档案 74 的下调)+ guest cgroup 峰值已 382/384 MiB ⇒ 先把内存预算调上去(需重启窗口),再重试启用。详见 04-调整方案/74 文末「现场反证」。

原状态行:⏳ 待续做(2026-09-13 07:3x 复核更正) —— 步骤 1–9 已完成,且「上传候选池」亦已完成(现行池内 = dsh-plugin-mcn-suite @0.3.9,09-13 00:02 上传;admin 侧已装 0.3.9 且 host 面 3 个注册加载通过),剩:guest 侧在实例「功能管理」启用(POST /api/plugins/mine/apply —— 用户动作,平台不代劳)→ 重启实例 → §八验收 → 归档。2026-09-12 14:55 已释放占用锁(原 exec-session-B),接手者直接续做;产物与验收证据见 交接单/README.md §一 的 T03 行。

🔎 只读核查结论(2026-09-12 20:2x)—— 执行前的三个事实 ① 候选池里只有 dsh-univer-office(39.9 MB) ⇒ §七 防线②"下架旧 7 包"实际无需做:那 7 个包从未上架过候选池,不存在新旧并存。 ② guest 的 profile/web bundles = base / dsh-web-app / business-plugins / portal-entry / workspace-scoped-picker / @liustack/modlens / dsh-univer-office ⇒ 不含任何旧包 ⇒ §七 头号风险(新旧并存 → duplicate loader entry id 崩实例)在本单不成立,切换顺序可简化。 ③ 时机未到:guest 活动实测 —— ws/MCN短视频创作 mtime = 20:06、sessions mtime = 20:02(距核查 13–17 分钟)⇒ 判定活跃中,按 R8「能避开活跃时段就避开」 暂缓"启用 + 重启"。上传(零影响)与启用(断 guest ~1 分钟)同窗口做更安全(避免为"上传"单独占一次 op-lock),故整体等待空闲窗口(判据:最近 30 分钟无活动)。

⚠️ 同窗口须一并处理:guest 的 bundles 里仍挂着 @liustack/modlens —— 该包今天已从候选池下架(档案 70 收尾),但用户 profile 仍启用它(包文件尚在 node_modules/@liustack/modlens,故当前不致命)。这是"已下架却仍启用"的不一致态 ⇒ 与 T03 共用同一次重启窗口摘除(从 bundles 移除 + 重启),避免两次中断用户。

✅ 上线进展(2026-09-12 22:0x)

步 结果
① 首传 0.3.0 ❌ 被 T05 兼容性预检拒收(HTTP 409 compat_incompatible)—— 判据 @deepseek-ai/dsh-client-ui-sidebar@^0.1.0-rc.5 vs 平台 0.1.2-rc.1。根因是 semver 的 prerelease 规则:带 prerelease 的版本只能被「同 major.minor.patch 且带 prerelease」的比较器匹配,^0.1.0-rc.5 里没有 tuple (0,1,2) 的比较器 ⇒ 判不满足。不是误报,是声明写窄了(且 T05 16:35 才上线,而包 11:15 打的,单子当时没这道关)
② 修正 + 重传 ✅ HTTP 200,compat.level = ok、findings[] 空。改法:3 个 dsh-client-* peer 范围 → ^0.1.2-rc.1(跟随平台版本);版本号 0.3.0 → 0.3.1;npm pack 重打包 → md5 adcb134e… / 322 条目。内容与 0.3.0 逐项对齐:SKILL.md 18=18、lib/ 文件 19=19、web/ 1=1(条目数差异纯属 npm pack 不写目录条目)
③ 候选池 现 2 条(09-13 07:3x 复核):dsh-plugin-mcn-suite @0.3.9(09-13 00:02;admin 已装并验通,guest 未启用)+ dsh-univer-office @0.2.15(09-12 23:19,fork 版)|历史:曾为 @0.3.1 + @0.2.14,@liustack/modlens 与 anysearch 已下架
④ 剩余 guest 在实例「功能管理」里启用(POST /api/plugins/mine/apply —— 用户动作,平台不代劳)→ 重启 → §八 验收 → 归档。同窗口一并摘 @liustack/modlens 已消解(09-13 07:0x 实测:guest bundles 与 node_modules 均已无该包)

⚠️ 给接手人的两条提醒

  1. 上传前必过兼容性预检(POST /api/plugins/business 会 409);trust 只用于"已人工确认后放行",别拿它盖住声明问题。
  2. peer 版本范围必须跟随平台 dsh 版本(当前 0.1.2-rc.1)—— 平台升版时同步改,否则 prerelease 规则会再次拒收。插件源码并不 import 这些包(只靠 dsh.client.inject 注入点),所以放宽范围无功能风险。
  • 触发:用户「规划 D:\dshworkspace\plugin_package 整合成一个插件,可打包后由 admin 整体上传到 dsh 服务器插件,用户可以启用进行使用」
  • 关联:档案 16(插件三层归属 + 投放/启停流程)、27(MCN 插件平台化评估,P0 清单来源)、34(启用探活 + 快照回滚)、36/38(铺开链路)、41(技能启停 API)、58(实例内存配额)、60(分区改名「功能管理」)、19 §C6 + 41(上传安全扫描)
  • 规划会话边界:本单由规划会话产出,只读普查了本机 D:\dshworkspace\plugin_package(未 ssh、未改码)

一、目标

把 D:\dshworkspace\plugin_package 下的 7 个自研插件整合成 1 个功能插件包:admin 在门户一次性上传候选池 → 用户在实例设置「功能管理」分区启用一次 → 重启实例 → 全部功能可用。

做完的判定:一个全新用户启用该整合包并重启后,侧边栏入口 + 工作台各功能页逐个可用,journald 无 duplicate loader entry id、无崩溃循环;并且业务技能随包自动就位(见 §4.1)—— $DSH_HOME/skills/ 出现 mcn-short-video 与 4 个业务子技能,用户不需要另外上传或启用技能。

二、现状(2026-09-12 只读普查结论)

包 loader id 体积 client 行 host 行 角色
dsh-plugin-mcn mcn-nav 945K 3045 3095 + 9 模块 宿主壳(唯一声明 dsh.client.inject=[runtime, sidebar])
dsh-plugin-douyin-accounts douyin-accounts 55K 666 9(空壳) 功能页
dsh-plugin-douyin-account-detail douyin-account-detail 67K 877 9(空壳) 功能页
dsh-plugin-douyin-video-detail douyin-video-detail 39K 496 9(空壳) 功能页
dsh-plugin-mcn-schedule mcn-schedule 54K 253 483 功能页 + 定时任务
dsh-plugin-social-workbench social-workbench 37K 150 218 外部服务守护(Windows 本地)
dsh-plugin-voxemw-cloud voxemw-cloud 77K 134 233 + 6 模块 + web/app.html 云 API 面板

2.1 结构性结论(这是"必须整合"的真正理由)

它不是 7 个平行插件,而是「1 个宿主壳 + 6 个功能页」。

  • dsh-plugin-mcn 注册了 25+ 条 /mcn/api/* HTTP 路由、inject = ["slots","sessions"]、在 sidebar.footer.action 注册唯一入口,并提供两个全局扩展点:
    • window.__mcnEntries —— 功能页注册表(工作台首屏图标网格读它渲染,源注释写明"外部插件注册的功能入口,即插即用")
    • window.__mcnNav —— 页内路由(open(entryId, params) / openView(id) / back())
  • 其余 6 个包全靠往 __mcnEntries 注册 + 被 __mcnNav.open() 打开;三个 douyin 包的 host 面只有 9 行空壳,注释原文写着"数据复用 dsh-plugin-mcn 服务端注册的 /mcn/api/* 路由"。
  • ⇒ 拆开的状态下"逐个启用"本身就违背设计:缺了 mcn,其余 5 个只是死链;反过来只启用 mcn 则功能残缺。整合是回归正确形态,不只是图省事。
  • 附注:dsh-plugin-mcn 没有 defineTool / tools.register(grep 实证 = 0)→ 它是 UI + HTTP 插件,不给 agent 注册工具;agent 侧能力只来自技能与 prompt。

2.2 打包链路现状:不存在,要新建

7 个包均无 scripts 字段、无 build 脚本、无 package/ 目录、无 tgz 产物;lib/*.js 是直接维护的产物(client.js 顶部自带 var module = {exports:{}} 的 bundle 外壳)。→ 整合包必须新建打包脚本。

2.3 需要一并处置的残留

dsh-plugin-mcn/lib/ 内有 2 个 .bak(index.js.bak-20260906、index.js.bak3-20260906)→ 会被打进包、加重体积并可能触发上传扫描告警。docs/ui-check-home-fix/(1.6M)与插件无关,不进包。

三、范围

区 对象 动作
新建 D:\dshworkspace\plugin_package\dsh-plugin-mcn-suite\(7 个原包原地保留作回滚参照) 整合包源码
新建 整合包内 skills/(技能原文 + .manifest.json,来源 C:\Users\Administrator\.dsh\skills\mcn-short-video,11 MB / 711 文件) 随包投放的业务技能,按 §4.1 取舍表挑选(不整包照搬)
新建 打包脚本(scripts/build-mcn-suite.cjs 或等价) 产出 dsh-plugin-mcn-suite-<ver>.tgz(tgz 内需 package/ 前缀,档案 25/28 教训)
改 整合包内的 host/client 代码 §五 的 P0 适配
平台侧 门户候选池 上传新包 + 下架旧 7 包(防并存,见 §七)
文档 新建档案 + INDEX §二 + 03-路线图 + 交接单/README §一 归档

明确不动:❌ dsh 官方包与缓存(红线 R2)❌ 平台自建三 bundle(portal-entry / business-plugins / workspace-scoped-picker)❌ /opt/dsh 权限 ❌ 7 个原包源码(保留)❌ 不新增平台侧权限能力(不触发 R5)

四、决策点(已由用户 2026-09-12 拍板)

用户口径:「A 按照最佳方案处理;B 按照你的建议处理 —— 业务技能能否打包到插件中一起安装和使用,不要分开管理!;C、D 按照最优方案处理」

# 决策 定稿
1 dsh-plugin-social-workbench 进不进包 ✅ 不进包(采纳建议 A)。实测是 Windows 本地服务守护:EASEL_DIR 默认 D:\dshworkspace\Easel、可执行文件 .venv/Scripts/easel.exe、探针打 127.0.0.1:7860/:18789;平台实例里 ① 该 exe 不存在 ② 127.0.0.1 已被档案 39 封(实测 BLOCKED)→ 必然不可用。该入口在平台不投放,原包源码保留
2 dsh-plugin-voxemw-cloud 进不进包 ✅ 进包。凭据全程走 env(无硬编码 ✓、getPublicConfig 已剥离 apiKey ✓),平台实例无该 env → 入口显示「未配置」,不崩
3 整合后的入口形态 ✅ A=保留 __mcnEntries 机制、多入口并列(工作台首屏图标网格),client 面行为与现状一致
4 业务技能投放 —— 用户明确要求 打进插件包、一起安装使用、不分开管理 ✅ 改为「技能随包投放」,实现见 §4.1。这推翻了规划会话原先"走门户技能管理分开投"的建议,据此重设计
5 包名与版本 dsh-plugin-mcn-suite v0.3.0。版本号必须每次递增:同名同版本 tgz 即使内容变了,pnpm 也复用旧包(档案 18 踩坑 1)

4.1 技能随插件包投放(用户要求:不分开管理)

为什么可行:技能发现位在实例内 —— dsh discoverRoot 只扫 $DSH_HOME/skills(档案 41 §B 实测:启用/禁用靠目录 rename 即可即时生效、无需重启);而插件的 host 面代码就跑在实例内、以该用户 uid 运行 → 插件完全有能力自己把技能装到位,不需要平台技能管理面参与。

机制(建议,执行会话按此实现):整合包内置 skills/(技能原文)+ skills/.manifest.json(技能名 → 版本 / 文件数 / sha256)。host 面在每次实例启动调用 ensureSkills():

  1. 读 .manifest.json;对每个技能比对 $DSH_HOME/skills/<name>/.installed.json 记录的版本;
  2. 幂等:版本一致 → 跳过(不重复写盘);
  3. 版本不一致 → 全量替换(先删后放,旧版已删除的文件一并清除 —— 与档案 11「apply 全量替换」同语义);
  4. 不覆盖用户自建的同名技能(目录存在但无 .installed.json)→ 跳过 + 写日志 + UI 提示"被同名技能占用";
  5. 写加载标记 [mcn-suite] skills ensured: [email protected], …(回归看日志即可,档案 26 §建议)。

备选(更省事,但必须先实测):若 dsh 的 skill 扫描认得目录软链,可改用软链 <DSH_HOME>/skills/<name> → <profile>/node_modules/dsh-plugin-mcn-suite/skills/<name>,升级插件即升级技能、零对账。风险:symlink 是否被扫描/读盘正常要实测(档案 39 的教训:symlink 行为必须 realpath 级实测,别假设)。→ 执行会话先试软链,不行再退回"复制 + 版本对账"。

投哪些 / 不投哪些 —— 按「引用驱动」裁剪,不按"看着像资料"裁(用户 2026-09-12 定「参考资料不需要」;规划会话随后实测引用关系,发现三类"像资料"的其实是被引用的功能件,故按下表执行):

内容 体积 引用实测 定稿
主技能 mcn-short-video(SKILL.md + references/ + references-add/ + scripts/ + 帮助文档.md) ~1.3 MB — ✅ 投
子技能 mcn-dou-analysis / mcn-script-review / mcn-video-prompt / mcn-data-insight 主体 主体 插件 prompt 正是引用这 4 个 ✅ 投
mcn-data-insight/ —— 它本身是"技能集索引",下挂 12 个二级子技能(douyin-top-account(140K) / douyin-account-diagnosis(136K) / douyin-rise-ranking(76K) / douyin-hot-trend(72K) / douyin-ai-feed / douyin-prohibited-word / playlet-douyin-feed / douyin-weekly-surge / douyin-works-crawler / douyin-content-surge / douyin-daily-hot / douyin-search,全部带 SKILL.md) 869 KB 插件的「榜单更新」任务依赖其中的 douyin-top-account;workshop.js 的赛道白名单同源 ✅ 整块投(这是"技能树"的第二层,不是参考资料;find -maxdepth 容易漏它,核对时注意层级)
mcn-dou-analysis/references/样例/(模板_旧梦留声机账号设定卡.svg 19 KB + 参考_旧梦留声机_账号设定卡_长图版.png 994 KB) 992 KB references/feature/06_生成账号设定卡片.md:67 写明"动手前必须先读"这两份 → 功能件,不是资料 ✅ 保留。用户说的"参考资料不需要"不覆盖这一项;若你仍要剔,则必须同改那条指令,否则留下死引用
nuwa-skill-main/ 主体(去掉 examples/) 104 KB / 11 文件 被引为"主方法论":feature/03_提炼账号设定.md:81、references/人设卡生成方法.md:186 ✅ 保留
nuwa-skill-main/examples/(trump-perspective/ 英文研究文档等) ≈ 2.4 MB 人设卡生成方法.md:187-188 引了 examples/mrbeast-perspective/(未引 trump-perspective) ❌ 剔。精确做法:只剔未被引的 trump-perspective/;若要连 mrbeast-perspective 一起剔,必须同改那两行引用
mcn-video-prompt/参考skills/(seedance2.0-prompt-skill、script-writing-studio 上游全文) 2.0 MB 实测 0 引用(grep 命中 0)→ 纯资料 ❌ 剔 ✓ —— 用户"参考资料不需要"指的就是这类
browser-harness/(整包 Python + 自带 venv + docs/png) 2.3 MB 多篇 feature 要求"浏览器优先"(feature/01_获取账号信息.md:12、feature/02_获取视频.md:10)+ 主 SKILL.md 有专章(L88/105-112) ❌ 剔目录,但必须同步改文档:写明「平台无浏览器(档案 38a 实测 Playwright 装不起来)→ 改走 MCP / 其它路径」,否则留下死引用,agent 会像档案 55 那样反复撞墙
scripts/mcp-config.json 16 KB 插件 MCP 依赖(功能必需部分 = URL) ⚠️ 剔 MYAI_FEISHU_APP_ID(cli_a84d0a47f93e1013)、保留 URL(maiya-trans.youmanvideo.com);再按 §五#7 过扫描

裁剪后体积预期:11 MB → ≈4.3 MB(剔掉 6.7 MB:参考skills 2.0 + browser-harness 2.3 + nuwa/examples 2.4)。 ⚠️ 投的是整棵技能树,不是一个技能:全树 36 个 SKILL.md / 711 文件(主技能 1 + 一级子技能 5 + mcn-data-insight 下 12 个二级子技能 + 更深层的参考技能目录)。核对层级时别用浅 find(规划会话首轮就是这么误判 douyin-top-account "缺失"的)。

裁剪必须机械校验(新增,防"剔完留死引用"):剔完跑一次死引用扫描 ——

grep -rIn -E "参考skills|browser-harness|nuwa-skill-main/examples/trump|参考_旧梦留声机" <整合包>/skills/ \
  | grep -v -E "已移除|无浏览器|不再使用"

期望:只剩"已移除 / 已改走 X"这类说明行(即上面要求改写的指令),没有一条指向已删文件的活引用。

⚠️ 与共享技能层互斥:本单走"插件内投"后,不要再把 mcn-short-video 投到平台共享技能层(bundledSkillDir,档案 10/40)—— 两条通道同名会按 rank 抢,行为不可预测。详见 §七。

禁用 / 卸载语义(取舍,已定):插件被禁用(disabled: true)时不删已装技能(用户可能正在用),只在下次启用时按版本对账;卸载插件会留下技能残留 → 记为已知残留(清理走实例内「我的技能」或目录手工删)。

4.2 技能落地方式(装到哪)与撞名规则(2026-09-12 用户提问定稿)

问题一:装到 $DSH_HOME/skills/,还是"就在插件内目录使用"?

关键事实:dsh 的技能扫描根是一个固定集合(个人 ~/.dsh/skills rank300 / 项目级 .dsh/skills rank100 / .agents/skills rank200 / 平台注入的 bundledSkillDir,档案 10 §Skill 装载机制 / 档案 41 §B),插件包目录(profile/node_modules/<pkg>/skills/)不在其中。 ⇒ "就在插件内目录使用"意味着 agent 无法按技能名加载(skill 机制发现不了),只能靠 prompt 给绝对路径让它自己读 SKILL.md。

方案 能否按技能名加载 副本/维护 备注
A 复制到 $DSH_HOME/skills/<name> ✅ 能 每用户一份 ≈4.3 MB;需版本对账 最简单确定;用户能在「我的技能」看到(但归插件管)
B 软链 $DSH_HOME/skills/<name> → 插件包内目录 ✅ 能(前提:实测 dsh 跟随 symlink) 零副本;升级插件即升级技能,零对账 副作用:用户在「我的技能」里删/禁 → 断链或删链,需处理;必须先实测(档案 39 教训:symlink 行为必须 realpath 级实测,不许假设)
C 不落扫描根,prompt 给"包内绝对路径"(插件运行时用 import.meta.url 算出) ❌ 不能 零副本零对账 兜底方案:agent 用不了 skill 名;用户不可见、不可管理;每次 prompt 都要注入路径

→ 定稿:B 优先(一条实测命令即可确认);B 不通退回 A;C 仅作最终兜底。

问题二:同名技能怎么处理(三层,按优先级)

场景 处理 理由
插件技能 ↔ 用户自建同名 让位:不覆盖,跳过 + 日志 + UI 提示"被你的同名技能占用" 用户的东西优先;档案 41 已有"同名 409"的先例,不静默覆盖
插件技能 ↔ 平台共享技能层同名 互斥:本单不投共享层;若共享层已有同名,插件让位并提示 共享层 rank 更高会压住个人层,静默失败比明说更糟(§4.1 互斥条)
插件技能 ↔ 另一个插件同名 owner 标记 + 先到先得 + 冲突报错:每份由插件装的技能写 .installed.json(owner: <包名>@<版本>、skillVersion、sha256);安装前查目标目录 —— 无标记=用户自建→让位;owner=自己→按版本对账;owner=别的插件→拒绝写 + 明确提示 多插件共存时行为可预测、冲突可见;不建议给技能名加命名空间前缀(要改所有 prompt,且用户看到怪名字)

判定顺序(实现时按此写):目标目录不存在 → 装;存在且 .installed.json.owner == 本包 → 比版本,不同则全量替换;存在但无 .installed.json → 用户自建,让位;存在且 owner == 其他包 → 拒绝 + 报错。

五、平台化 P0 清单(不修则"传上去也不能用")

# 问题 实测位置 改法
1 homedir()/.dsh 硬编码 —— 平台实例的 DSH 家目录是 $DSH_HOME,不是 ~/.dsh mcn/lib/config.js:6,28,68;mcn/lib/index.js:733,771,865,881,935,964,978,1124,1167,1496,1565,1630,1969,2143,2840;mcn-schedule/lib/index.js:11,27,84 统一 const DSH_HOME = process.env.DSH_HOME → join(DSH_HOME, …)。注意三处子路径:storages/workspace.json、sessions/、profiles/,都在 DSH_HOME 下
2 技能路径硬编码 ~/.dsh/skills/mcn-short-video/subskills/<x>/ mcn/lib/index.js 多处:1255、1565、1798、1813、1834、2078、2313、2364、2491、2546、2984、2986… 改成只报技能名(mcn-dou-analysis 等),由 agent 自行解析(档案 27 P0-2);技能随包投放(见 §4.1),prompt 里不再出现任何绝对路径
3 spawn("powershell", …) 杀进程树 = Windows only mcn/lib/index.js:919(两个 .bak 同位置) 平台是 Linux → 改 process.kill(-pid, …) / pkill -P,或直接删掉该清理分支(评估残留影响)
4 MCP spawn(npx -y myai-mcp) 每次现拉包 mcn/lib/mcp.js:21、config.js:14 平台侧预装成包(档案 27 P1),并确认实例内 npm registry 可达(档案 38a:仅云元数据端点被封,其余放行但只记日志)
5 .bak 混在 lib/ mcn/lib/index.js.bak-20260906、index.js.bak3-20260906 打包前剔除(构建脚本过滤 + files 白名单)
6 上传扫描可能误伤(P0 阻断 / P1 告警) 整包 打包后先本地 dry-run src/web/security-scan.ts 的规则(P0:/etc/shadow、base64 解码执行、nc -e、/dev/tcp 反弹、fork 炸弹、给系统二进制加 setuid;P1:child_process、vm/eval/new Function、敏感 env、超长 base64、外部网络、隐藏目录)。已知 child_process(mcn spawn、social-workbench spawn)只会触发 P1 告警、不阻断,但要确认整包 P0 = 0。注意技能原文里有大量代码示例/脚本,P0 风险比插件本身高
7 技能原文里的凭据 / 平台情报(随包投放新增) mcn-short-video/scripts/mcp-config.json(实测含 MYAI_FEISHU_APP_ID=cli_a84d0a47f93e1013 + 私域 URL maiya-trans.youmanvideo.com)、scripts/MCN_CYLG_API.py(命中 key/token 模式,需逐个核对)、subskills/browser-harness/.env.example 入包前对 skills/ 整目录做凭据扫描:`grep -riE "api[_-]?key
8 插件 prompt 里的技能路径层级不符(2026-09-12 复核,更正规划会话先前的误判) 插件 prompt 写的是 subskills/douyin-top-account(mcn/lib/index.js:3085、3086;mcn/lib/workshop.js:9,22),真实路径是 subskills/mcn-data-insight/subskills/douyin-top-account/(少了一层)。技能本身完整存在(36 个 SKILL.md / 711 文件)—— 规划会话首轮的"缺失"结论是搜索深度不够(-maxdepth 5 差一层)导致的误判,已更正 无需补文件;并入 #2 一起修:所有 prompt 改成只报技能名(douyin-top-account),由 agent 自己解析 → 层级问题自然消失。打包前必跑:grep -rn "subskills/" lib/ 逐条核对是否与技能实际层级一致

六、步骤(每步自带验证)

  1. 前置只读核查:git -C <平台代码> log -1(取当前 HEAD 备写档案)/ ls /opt/dsh/artifacts/(看版本命名与 package/ 前缀)/ 门户 #/plugins 候选池现有 7 包的实际状态(是否有人已启用)。 验证:拿到"候选池里有哪几个、谁在用"的事实清单。
  2. 决策与技能完整性:§四 五个决策点已定稿;另需向用户确认 §五#8 —— douyin-top-account 子技能的去向(补进包 / 已废弃改 prompt / 走 MCP)。该项未确认前不要打包。
  3. 建包骨架:package.json(name dsh-plugin-mcn-suite、version、type: module、exports {".", "./client"}、dsh.bundle.patch、dsh.client.inject = mcn 的 [runtime, sidebar] 并集)+ cordis.patch.yml(只 1 条 insert:id: mcn-suite)+ lib/{index.js, client.js, host/*}。 验证:node --check 全通过;cordis.patch.yml 里 insert 条目数 = 1(防 duplicate,档案 25)。
  4. 移植 host 面:把 mcn 的 9 个模块 + mcn-schedule + voxemw(+决策 1 的 social-workbench) 收进 lib/host/,保持路由前缀不变(/mcn/api/*、/voxemw/api/*)→ client 面零改动。 验证:路由清单 diff:改前 25+ 条 vs 改后逐条一致(只增不减)。
  5. 合并 client 面:一个 client.js 内注册全部 __mcnEntries 条目;inject 取并集;确认无 exports.default(红线 R3,实测 7 个原包全部合规 ✓,合并时别引入)。 验证:grep -c "exports.default" = 0。
  6. P0 适配(§五 1–5)+ 按需加加载标记:console.log("[mcn-suite] loaded …")(把回归从"看 UI"变成"看日志",档案 26 §建议)。 验证:grep -n "homedir()" lib/**/*.js = 0;grep -n "powershell" = 0;grep -rn "\.dsh/skills" = 0。
  7. 本地 smoke:参考 dsh-plugin-voxemw-cloud/tests/smoke.mjs 的写法,跑一遍 host 面(路由注册、DB 建表幂等、无 env 时降级不崩)。 验证:smoke 全绿;无 env 情况下进程不抛。
  8. 打包:产出 dist/dsh-plugin-mcn-suite-<ver>.tgz,内含 package/ 前缀,不含 .bak / node_modules。 验证:tar tzf 清单人工核对(顶层只有 package/);体积与 du 对照;md5sum 记档。
  9. 扫描 dry-run:按 §五#6 规则跑一遍 → P0 必须为 0;P1 告警逐条判断是否误报。
  10. 上传:admin 门户 #/plugins 上传 tgz → 进候选池(默认禁用)。产物不要复制进用户 ws(档案 28 平台污染教训)。 验证:候选池列表出现该包 + 版本号正确。
  11. 切换(关键,防并存):① 先在候选池下架旧 7 包 ② 用户实例「功能管理」启用新包 ③ 确认弹窗 → 重启实例 ④ 走 restartAndProbe 探活(档案 34)。 验证:§八 C–F。
  12. 实测验收 + 资源:逐功能页点开;用 scripts/probe-instance-mem.cjs 量私有内存与启动时间(档案 58 预算:MemoryMax 384 MiB / 堆 256)。 验证:§八 D、G。
  13. 归档:新建档案(原子占号 mkdir 04-调整方案/.lock-<NN>)→ 更新 INDEX §二 / 03-路线图 / 交接单/README §一 → 本单 git mv 到 archive/交接单-已完成/。 验证:docs-audit.py 退出码 0。

6.1 技能随包的额外步骤(并入上面第 3、6、8、10 步,不另起编号)

  • 建包阶段(并入第 3 步):按 §4.1 引用驱动裁剪(不是"看着像资料就删")→ 放整合包 skills/<name>/;生成 skills/.manifest.json(名称 / 版本 / 文件数 / sha256);剔完跑 §4.1 的死引用扫描。 验证:du -sh skills/ ≈ 4.3 MB(而不是 11 MB);tar tzf 确认没有 参考skills/、browser-harness/、nuwa-skill-main/examples/trump-perspective/;确认有 mcn-dou-analysis/references/样例/(含 994 KB 长图)、nuwa-skill-main/SKILL.md、mcn-data-insight/subskills/douyin-top-account/(这三块都是被引用的功能件,不能剔)。
  • 改码阶段(并入第 6 步):实现 ensureSkills()(§4.1 五步)+ 加载标记;把 mcn/lib/index.js 的 13+ 处技能绝对路径改成技能名。 验证:grep -rn "\.dsh/skills" lib/ = 0;grep -rc "ensureSkills" lib/ ≥ 1。
  • 打包阶段(并入第 8 步):skills/ 整体进包(package.json 的 files 白名单必须包含 skills)。 验证:tar tzf dist/*.tgz | grep -c "package/skills/" ≈ 技能文件总数(数百)。
  • 上线阶段(并入第 10 步):上传 → 用户启用 → 重启 → 按 §八 J/K 验技能就位。

6.2 收尾顺带(D 组:两处文档漂移 —— 用户已定「并进 T03 收尾」)

  • BRIEF.md:20:scope(512M / 150% / TasksMax 128) → 384M,并补 NODE_OPTIONS=--max-old-space-size=256 与 NODE_COMPILE_CACHE(档案 58 实测)。
  • 03-路线图与待办.md:头部仍是 09-11 21:20 合并版、档案 57-61 完全未登记 → 补登记 + 刷新头部状态行;BRIEF §3 待办同步。 验证:grep -n "384M" BRIEF.md 命中;grep -nE "档案 (60|61)" 03-路线图与待办.md 命中(写成 (60|61) 而不是字符类:档案\s*6[01] 这种写法会被 docs-audit.py 的引用正则匹配成裸旧号 6 → 永久悬空,T02 已踩过一次)。

七、风险与红线

🔴 头号风险:新旧包并存 → 实例崩 若某用户同时在位"旧 7 包"与"新整合包",会出现:/mcn/api/* 路由重复注册、sidebar.footer.action 重复注册、__mcnEntries 重复条目;严重时 duplicate loader entry id → 崩溃循环(档案 25 有真实前例,靠档案 20 熔断才拦住,但用户会看到死页面)。四道防线必须全做:

  1. 新包用全新 loader id(不复用 mcn-nav 等 7 个旧 id);
  2. 门户候选池下架旧 7 包(或标 deprecated,并在包描述写明替代关系);
  3. 切换按 §六 步骤 11 的顺序执行(先禁用旧 → 再启用新);
  4. 新包 host 面加防御:启动时检测旧 id 是否同时在位 → 命中则拒绝启动并写明确日志(宁可这个包不工作,也不要崩实例)。

其余

  • 内存/启动(档案 58):合并后单实例加载模块更多,必须实测,超预算就按需懒加载或砍 social-workbench。
  • 网络(档案 38a):实例出网只记日志不拦(元数据端点已封)→ MCP npx、voxemw 云调用可达性要用实测确认,别假设。
  • 技能同名互斥(§4.1):插件投的技能、平台共享技能层、用户自建技能三方可能同名 → ① 本单不投共享技能层;② 用户自建同名时插件跳过并提示;③ 若日后要改投共享层,必须先撤掉插件内投放。
  • 技能随包的体积成本:约 1.5–2 MB × 每用户一份(用户已明确"不分开管理",接受该冗余);日后要省可改软链方案(§4.1 备选)。
  • 红线:R2 不改官方 dsh 包(只走 bundle + patch)✓;R3 client 禁 exports.default ✓;R5 本单不扩大平台权限 ✓;不自动升级 dsh ✓;未获明确授权不 commit / push。

八、验收

# 命令 / 动作 期望
A tar tzf dist/*.tgz | head ;md5sum 顶层只有 package/;无 .bak、无 node_modules;md5 记档
B 上传扫描 dry-run P0 = 0;P1 逐条有结论
C 门户 #/plugins 上传 进入候选池、默认禁用、版本号正确
D 全新用户:启用 → 确认弹窗 → 重启 侧边栏入口出现;工作台首屏功能入口逐个可打开
E journalctl -u dshs --since '10 min ago' | grep -E "mcn-suite|duplicate" 有 [mcn-suite] loaded;无 duplicate loader entry id;无崩溃重启
F 启用探活(档案 34) 探活通过;失败时快照回滚不误伤好插件
G scripts/probe-instance-mem.cjs 私有内存 + 启动时间在档案 58 预算内(写实测值)
H 候选池 旧 7 包已下架/标 deprecated;不存在"新旧同时可启用"的状态
I 技能随包就位(§4.1) 全新用户启用 + 重启后:ls $DSH_HOME/skills/ 出现 mcn-short-video 与 4 个业务子技能;journalctl 有 [mcn-suite] skills ensured: …
J 技能可用(agent 侧) 实例内让 agent 按技能名加载(如「调用 mcn-dou-analysis」)→ 能解析到,无需给绝对路径
K 幂等 / 不覆盖 再重启一次 → 技能不被重复写(.installed.json 版本一致则跳过);人为放一个同名自建技能 → 插件跳过并提示,不覆盖
L 凭据(§五#7) skills/ 凭据扫描:真密钥 0 命中;app id / 私域域名的处置有明确结论并写进档案
M D 组文档(§6.2) BRIEF.md 已更正为 384M;档案 57-61 已在 03-路线图 登记
N 裁剪后引用完整性(§4.1 死引用扫描) 只剩「已移除 / 已改走 X」的说明行;无一条指向已删文件的活引用(browser-harness 相关的"浏览器优先"指令必须已改写)
O 包体积与技能树 skills/ ≈ 4.3 MB(已剔 6.7 MB);含全部 36 个 SKILL.md(主技能 + 5 一级子技能 + mcn-data-insight 12 个二级子技能);保留了 references/样例/(992 KB)、nuwa-skill-main/SKILL.md、mcn-data-insight/subskills/douyin-top-account/
P 技能落地方式(§4.2) 按 B(软链)先实测;不通则用 A(复制)。两种都必须能被 skill 机制按名加载(不是靠 prompt 给绝对路径)
Q 撞名处理(§4.2) 用户自建同名 → 让位并提示 ✅;平台共享层同名 → 互斥并提示 ✅;别的插件已占 → 拒绝写并报出 owner ✅(验证:人为造这三种场景各跑一次)

九、回滚

  • 包级:候选池删新包 → 重新上架旧 7 包 → 用户逐个启用 → 重启(这是为什么原包源码与 tgz 都要留档)。
  • 实例级:走档案 34 的 restartAndProbe 快照回滚;或直接禁用新包重启。
  • 本单不改平台代码 → 无平台侧回滚;若为铺开改了 ensure-* 脚本,按 .bak-<ts> 还原 + 重启。

十、回报格式(执行会话填)

## T03 执行回报
- 决策 1–5 选定:<逐条,谁确认的>
- 整合包:name/version = ?  体积 = ?  路由数 25+N  入口数 = ?
- P0 适配:homedir() 残留 0 ✅ / powershell 残留 0 ✅ / .dsh/skills 硬编码 0 ✅ / .bak 已剔 ✅
- 打包与扫描:tgz md5 = ?  P0 = 0  P1 = ?(逐条结论)
- 上传与切换:候选池版本 ?  旧包下架 = 是/否  启用+重启 ✅  探活 ✅
- 实测:私有内存 ? MiB / 启动 ? s(对照档案 58 预算)
- 验收:A–I 逐项 ✅(未过项写现象)
- 证据:<journald loaded 标记 + duplicate 检查输出 + 内存采样>
- 偏离:<…;无则写"无">

十一、不在本单

  • 业务技能内容本身的维护/改版(只做"投放到位")
  • voxemw 云链路接线(档案 12 的 M1 后段)
  • Easel / social-workbench 的平台化改造(决策 1 选 C 才需要,另立单)
  • 平台「功能管理」分区的其它 UI 调整(档案 57/60 已闭环)

九、验收结果(2026-09-13 09:5x · 只读取证,服务器实测)

口径:§八 逐项核对;能只读证的全跑了,需要浏览器/造场景的明确标出。

# 项 结果 实测证据
A 包结构 ✅ 池内 tgz md5 2ff1a2fa775891085949d50452394fa0 / 1,953,267 B / v0.3.9;顶层仅 package/;node_modules 0、.bak 0
B 上传扫描 ✅(P0) [upload-scan] blocked=0 warnings=118(warnings 逐条结论未做)
C 候选池上传 ✅ 池内 dsh-plugin-mcn-suite @0.3.9(09-13 00:02)
D 启用→重启→入口 ⚠️ 未验(需浏览器逐个点开入口) —
E 加载日志 ✅ [mcn-suite] loaded v0.3.0(host 面 3 个:mcn, mcn-schedule, voxemw-cloud);duplicate loader entry id = 0;同期无崩溃
F 启用探活 ⚠️ 未单独验(但平台已自动回滚过一次 ⇒ 该链路是活的,见 07:42–07:46 事故) —
G 内存 ⚠️ 偏紧 guest 峰值 382 / 上限 384 MiB;admin 峰值 321 / 384(档案 74 采样)
H 旧 7 包下架 ✅ 池内只剩 mcn-suite + univer-office 两个;无新旧并存
I 技能随包就位 ✅ 落点 = <home>/skills/mcn-short-video(4.6 MB,含 4 个一级子技能);journal [mcn-suite] skills ensured …
J agent 按技能名加载 ⚠️ 未验(需实例内跑一次 agent) —
K 幂等/不覆盖 ✅ skills ensured: 跳过 [email protected](版本一致)
L 凭据扫描 ⚠️ 未验 —
M D 组文档 ✅ BRIEF.md 已是 384M(档案 58 收尾时改)
N 裁剪后引用完整性 ⚠️ 未验(需跑死引用扫描) —
O 包体积与技能树 ⚠️ 口径待核 实测 4.4 MB / 18 个 SKILL.md(主 1 + 一级 4 + mcn-data-insight 下 13);§八 原文期望 36 个 SKILL.md ⇒ 差异需确认为"包被裁剪"还是"口径过时"
P/Q 软链落地 / 三种撞名场景 ⚠️ 未验(需造场景) —

结论:主体已通过(8 项实证);未验的 6 项(D/J/L/N/P·Q)都属"需浏览器或需造场景",可以另找时间补,不阻塞投放。

⚠️ 一条必须记的事实(guest 那次尝试):2026-09-13 07:42:52 guest 在「功能管理」提交启用 → 实例 07:43–07:45 连崩 5 次(exitCode=134,栈 = V8 JsonParser/HeapAllocator ⇒ V8 堆打满)→ 07:46 熔断 → 平台自动回滚(package.json.bak-rollback-20260913T0748)⇒ 现在 guest bundles 里没有 mcn-suite。 根因指向 --max-old-space-size=160 太紧(档案 74)+ guest cgroup 峰值贴顶 ⇒ 详见 04-调整方案/74 文末「现场反证」。 admin 侧自 09-13 00:02 起一直启用且各项实证通过(上表 A/E/I/K 均取自 admin)。


十、收尾记录(2026-09-13 18:0x)

本轮补齐的 gap

项 结果
guest 启用(原「剩余」第 1 条,单子长期卡点) ✅ 已完成 —— POST /api/plugins/mine/apply {id: dsh-plugin-mcn-suite, enabled: true} → 任务 c20739a6dbe8975c → status=success / stage=完成 / restarted=true;启用后 bundles 含 dsh-plugin-mcn-suite ✓
容量前置(07:4x 崩溃的根因) ✅ 已解除 —— 档案 79 两处 P0 已修;R1-④ 动态配额生效(guest 装前 672 MiB → 装后 800 MiB)⇒ 「V8 堆打满 abort → 自动回滚」不再复现
§八 L 凭据扫描 ✅ 通过 —— ak_+32hex 0、sk- 0、app_id= 0、*.alotbuy.com 0;125 处命中均为变量名/文档说明;唯一 32+hex 是 .manifest.json 的文件哈希
§八 O 包体积与技能树 ✅ 通过(口径澄清) —— SKILL.md 实测 18 个;§八 原文期望「36」系笔误(主 1 + 一级 4 + mcn-data-insight 下二级 13 = 18,与文字描述完全吻合)。包总 4.27 MB / skills 3.55 MB
§八 P 技能落地形态 ✅ 通过 —— admin 实例 skills/mcn-short-video = 实体目录(18 个 SKILL.md / 4.6 MB)⇒ 走 §4.2 的 A(复制)兜底路径
§八 N 死引用扫描 ⚠️ 未定判 —— 自动扫描出 63 处「包内路径对不上」,但抽样 8 个里 5 个是误报(引用省略 references/ 前缀,文件其实在);内容基本齐全(16 个 scripts/ 目录、27 个 .py、263 个 .md,知识库 12 类齐全)⇒ 不能判不合格,需人工逐条读;不阻塞投放
§八 D / J / Q ⏸ 未验(需浏览器或造场景);按 §九 口径 不阻塞投放

归档

  • 本单主体目标已达成 ⇒ 按流程移入 archive/交接单-已完成/,并在 INDEX.md §四 与 交接单/README.md §一、03-路线图与待办.md §二 三处同步状态。
  • 遗留(可选,不阻塞):D(实例内入口逐个点开)/ J(agent 按技能名加载)/ Q(三种撞名场景)/ N(死引用逐条人工判定)。