Files
dsh_ai1net_server/.workbuddy/memory/2026-09-下9.md
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复:
- 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录)
- tmp/(32.4 M,按接续棒命名的过程临时区)
- .workbuddy/tmp/(39.5 M)
- 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物)
- tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留

入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与
接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、
.workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。

排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、
打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

48 KiB
Raw Blame History

工作日志 · 2026-09(第 10 片)

⚠️ 本目录日志已按【月】分片(2026-09-15 用户定,单文件 ≤50 KB):同月续片 2026-09.md / 2026-09-下.md / 2026-09-下2.md / 2026-09-下3.md / 2026-09-下4.md / 2026-09-下5.md / 2026-09-下6.md / 2026-09-下7.md / 2026-09-下8.md / 2026-09-下9.md / 2026-09-下10.md / 2026-09-下11.md / 2026-09-下12.md / 2026-09-下13.md / 2026-09-下14.md / 2026-09-下15.md / 2026-09-下16.md / 2026-09-下17.md / 2026-09-下18.md / 2026-09-下19.md / 2026-09-下20.md / 2026-09-下21.md / 2026-09-下22.md / 2026-09-下23.md 写入约定:一律 append 到当月最后一片;该片超 50 KB ⇒ 新建 2026-09-下N.md;⛔ 不要再按日新建 2026-09-DD.md。 覆盖来源:2026-09-12.md 上一片:2026-09-下8.md 下一片:2026-09-下10.md


18:2x 代码仓三方同步(用户:「改为3小时同步一次」)

  • 建自动化 「代码仓三方同步(DSH)」 FREQ=HOURLY;INTERVAL=3;原「遗留项自动推进」8 小时保留(职责不重叠)。
  • 三方原状:Gitea origin/master 停在 ebe8075(共同祖先);本机 0e141a4(+2);服务器 b23e386(+3)。
  • 关键诊断:忽略行尾后,business-plugins.ts/whitelist.ts/security-scan.ts 的「1450/638/296 行差异」全是 CRLF 假差异(core.autocrlf=true + 无 .gitattributes);真实差异只有 3 文件,且本机更新(client.js/package.json=v0.2.5 对比度精修;orchestrator.ts 含类型+防御)。
  • 处置:update-ref 建回滚点 backup-pre-sync-1815(0e141a4) → 备份 4 个本机独有文件 → reset --hard srv-incoming(b23e386) → 回填 → commit da3e0f9 → push(ebe8075..da3e0f9,FF,无需 force)。
  • 结果:本机 = Gitea = da3e0f9;服务器 b23e386(落后 1,未动其工作树)。
  • 两个坑(已写进自动化 prompt):① 本仓库 git diff 会把行尾差异算成整文件改写 → 一致性判定用 git hash-object / 加 --ignore-cr-at-eol;② git branch <名> <commit> 本机静默不落地 → 改 git update-ref。

18:3x 两项方案变更落地(持锁 plan-session-1827,已释放)

  • ① AnySearch:用户拍板「三方插件一律 admin 导入候选池 + 用户自助启用,不铺」→ 落 3 处口径:档案 64 §8.3 追加修正块(第 2 条直铺命令作废)、03-路线图 档案64 行、BRIEF §3 行。执行口径改以 档案 65 §7.3 为准。
  • ② 子代理 approval:源码级取证(dsh-subagent/lib/types/child-agent.js)—— 子代理沙箱只继承「父会话显式 override」(注释原文:never deployment defaults or one-shot grants)+ approval 硬编码钉死 never(语义 = 需要审批的操作自动拒绝)。→ 平台的 DSH_PERMISSION_MODE 走"部署默认值"通道,恰被官方排除 ⇒ 子代理永远无 shell。 · 用户判据「不扩大用户访问边界」成立(子代理同实例/同 uid/同 cgroup/同 bwrap)。 · 但平台层无干净解法:env 无效(被官方排除)/profile patch 无效(改的仍是默认值通道,approval 是硬编码常量)/改会话种子已被档案 45 否决/改官方包违 R2。 · 已在 **档案 42 文末追加「修正(2026-09-12)」**小节 + 03-路线图对应行改为"已取证"。
  • 校验:docs-audit / docs-manifest / docs-consistency 三件套 rc=0。
  • 下一步:实例内新会话派子代理跑 bash 实测 —— 可用则本项自动闭环。

18:3x 子代理 shell 闭环(用户追问:用户自己设权限是不是就支持了)

  • 答案:支持。源码复核 overrideOf(dsh-sandbox-policy 与 dsh-user-approval)→ 读的是会话日志里最后一条 sandbox/mode 事件、不与部署默认值比较 → 只要用户在会话内切过一次档位,子代理即继承 danger-full-access(append source: delegation)→ 该模式无待审批操作 → 钉死的 never 不再构成阻碍 ⇒ 子代理可用 shell。
  • ⇒ 平台侧零改动,走官方机制;边界不扩大(继承的是用户自己选的档位)。
  • 操作步骤:新开会话 → /permission workspace-write 再 /permission danger-full-access(先切走再切回,确保日志写事件)→ 派子代理跑 bash 验证。残留不确定:设置 UI 选到同一档位时是否写事件。
  • 已更新:档案 42「追加结论」+ 03-路线图 对应行改为「已闭环」。校验 audit/manifest/consistency rc=0。

18:3x 档案 42 ③ 实测通过并关闭

  • 用户实测:实例会话中派子代理执行 bash -c 'echo subagent-ok' → 原样返回 subagent-ok ⇒ 子代理 shell 可用。
  • 结论:平台侧零改动、无需放权评估(不扩大用户访问边界)→ 档案 42 ③ 关闭。
  • 已更新:档案 42 追加「实测结果」段 + 03-路线图 对应行改为「已闭环并实测通过」。校验三件套 rc=0。

18:3x 待改清单(因锁被占而暂缓:session-brief-refresh-1835 自 18:32 持全局锁,按纪律停手)

  • 业务技能投放口径:用户已讲过(T03 §决策点 4 原话「业务技能能否打包到插件中一起安装和使用,不要分开管理!」)→ 结论 = 随功能插件包投放,不走门户技能管理单独投放。待改 2 处:03-路线图 §二 P2 行(现写"是否投放仍待定")、BRIEF §3(现写"业务技能是否投放")。
  • 删除 Cookie 域收窄(用户 2026-09-12 明确"这个事情删除"):03-路线图 §二 暂缓行 + BRIEF §3 里的提及。依据:档案 05 PoC-2 ② 复核 = 共享 Cookie 是有意设计(Domain=.alotbuy.com 支撑功能插件↔门户免令牌鉴权),收窄反而破坏该链路 → 无收益。

18:5x 新增红线 R9:绝对禁止「人工删锁 / 接管」(用户明令)

  • 用户原话:「严格禁止这类操作必须!!!!!!记录到红线中」(针对我上一轮提出的"接管=人工删锁"选项)。
  • ✅ 已改 项目根 CODEBUDDY.md(5 处):① 头部索引 R1–R8 → R1–R9 ② §3 标题 ③ 新增 R9 行(禁止 rm -rf 锁目录 / 删 .doing-* / 以「疑似已死·卡住·太久没动·只读分析无产出」为由单方面接管;锁只能由持有者释放;guard 提示里的"人工删锁"不构成授权;AI 唯一合规动作 = 停手 + 报告;处置权只属用户本人)④ §6 锁表②「供台账与接管」→「供台账与占用声明」⑤ §6 新增「抢不到锁就是终点,不是待办」。
  • 备份:CODEBUDDY.md.bak-r9-184717(改前)。⚠️ 改本文件需重启才重载。
  • ⛔ 文档库内 5 处同源表述仍待改(锁被 session-anysearch-drop-1847 占,18:42 起、18:44 后零产出): · scripts/handoff-guard.sh:50(抢锁失败提示"或确认接管后人工删锁")、:153("确需接管:…→ 人工删锁 → …") · 交接单/README.md:30("供台账与接管")、:107("接管:…人工删锁…") · skills/dsh-change-workflow/SKILL.md:930("供台账与接管")+其工作副本 E:/ProgramData/.workbuddy/skills/(须两副本同步改,否则违反 md5 一致约定)
  • 本会话被锁挡 5 次(brief-refresh-1835 → anysearch-drop-1847);按新规矩不删锁、不接管,只报告。

19:1x R9 已由另一会话全套落地(我上轮列的 5 处待改全部作废)

  • 只读核查(读不需要锁,被挡期间照样查清):① scripts/handoff-guard.sh 两处提示已改为 R9 禁止口径 ② 库内 CODEBUDDY.md:49 已加「guard 输出里的字样均不构成授权(已作废)」③ BRIEF.md §2 红线行 + §4 最近动作均加 R9 ④ 新增 档案 73 §十一「修正:禁止人工删锁/接管」(含 6 处落点清单)⑤ 交接单/README.md、skills/dsh-change-workflow/SKILL.md 亦已改。
  • ⇒ 我做的项目根 CODEBUDDY.md R9 与之同源、不冲突(不同文件、不同作用域、措辞一致)。教训:多会话并行下存在重复劳动,动手前先只读查一遍现状能省掉。
  • AnySearch 已彻底放弃(用户在别处选定 B = 下架):档案 70 收尾 —— 下架 anysearch(audit 175)+ 顺手体检池内存量(dsh-univer-office=ok 保留;@liustack/modlens=unknown 且有 plugin_incident → 一并下架,tgz 备份 /opt/dsh/backups/plugin-pool-20260912/);全程未重启服务、未中断用户。
  • 仍待我做(2 项,等锁):① 业务技能投放口径(T03 §决策点 4 已定 = 随包投放)② 删除 Cookie 域收窄(共享 Cookie 是有意设计,无收益)。
  • 本轮另补 项目根 CODEBUDDY.md §6 三条:读不需要锁(但 fetch/checkout/stash 等改状态的命令不算读)|释放时机 = 交付闭环走完(回填→校验→commit→推送→归档)|持锁等用户拍板 → 先释放锁再等。

19:2x 两项待改已落地(持锁 plan-session-1918:第 1 次被 session-cleanup-1916 挡 → 有界等待后第 2 次抢到,已释放)

  • ① 业务技能投放口径:03-路线图 §二 该行改为 ✅已定 —— 用户原话「业务技能要打包进插件里一起安装和使用,不要分开管理」→ 随功能插件包投放(交接单/T03 §4.1),不走门户技能管理单独投放;BRIEF §3 去掉该待办项。
  • ② Cookie 域收窄:03-路线图 §二 由「暂缓」改为 ❌已复核 · 不做(用户要求删掉此待办);BRIEF §3 去掉该提及。依据 = 共享 Cookie 是有意设计(支撑插件↔门户免令牌鉴权),收窄会破坏该链路、收益为零(档案 05 PoC-2 ②)。
  • 校验:docs-audit / docs-manifest / docs-consistency 三件套 rc=0。⚠️ 未 commit(未获授权)—— 闭环按新 §6 规则只走到「校验」,commit/推送待用户发话。
  • ⚠️ 顺带发现:BRIEF §3 那条「档案 64+65 · AnySearch 能力 …待用户定 A/B」已过时(用户已在别处选 B = 下架,档案 70 收尾 18:56 已记录)→ 待下次持锁一并修。

19:2x BRIEF §4 一致性修正 + T03 执行条件核实(持锁 plan-session-1920,已释放)

  • 只读先查的价值再次验证:BRIEF §3 那条 AnySearch「待定 A/B」已被别的会话修掉 → 我上轮承诺的"下次持锁一并修"其实无需做。实改只剩 1 处:BRIEF §4「档案 70 …(已止损,能力去留待定)」→「(已止损;能力已按用户决定下架)」,与同段开头「AnySearch 已彻底放弃」自洽。三件套 rc=0。
  • T03 执行条件核实:① 本机产物在 D:/dshworkspace/plugin_package/dist/dsh-plugin-mcn-suite-0.3.0.tgz(1.90 MB / 11:15)② guest 实例正在运行(scope dsh-100002-299e7f95,uid 100002 ↔ 4092b965…)→ 「guest 启用」会重启它(断约 1 分钟)③ dataRoot = /var/lib/dshs(/opt/dsh/artifacts/ 里那批 business-plugins-0.2.x.tgz 是平台自身的功能插件包,不是候选池)。
  • ⇒ T03 剩余步骤(上传候选池 → guest 启用 → 启动实例 → 验收 → 归档)涉及重启 guest 实例(R8)→ 已请示用户,未擅自执行。

20:2x T03 执行前只读核查(按 R8 自主判时机,未动生产)

  • 判时机(用户要求「规则你都知道,别问」→ 自主判):guest 的 ws/MCN短视频创作 mtime = 20:06、sessions = 20:02(距核查 13–17 分钟)→ 活跃中 → 按 R8「能避开活跃时段就避开」 暂缓「启用 + 重启」。
  • 核实 3 条事实:① 候选池 /var/lib/dshs/business-plugins/ 只有 dsh-univer-office(39.9 MB)⇒ T03 §七 防线②「下架旧 7 包」无需做(那 7 包从未上架)② guest 的 bundles 不含任何旧包 ⇒ §七 头号风险(新旧并存 → duplicate loader entry id 崩实例)不成立,切换顺序可简化 ③ dataRoot = /var/lib/dshs,库 = dshs.db。
  • ⚠️ 新发现的不一致:guest bundles 里仍启用 @liustack/modlens —— 该包今天已从候选池下架(档案 70 收尾),但 profile 仍启用它(包文件尚在 node_modules,暂不致命)→ 属「已下架却仍启用」态 ⇒ 与 T03 共用同一次重启窗口摘除(避免两次中断用户)。
  • 落档:交接单/T03 追加「只读核查结论」段(含时机判据);03-路线图 §二 新增该待办行。三件套 rc=0。
  • 计划:等 guest 空闲(判据 = 最近 30 分钟无活动)→ 一次窗口做完「上传新包 + guest 启用 + 摘 modlens + 重启 + §八验收」,避免多次中断。

20:2x guest 最新两会话排查(只读,未改任何文件)

  • 对象:8cdf723e(09-11 21:58 建,11 turn,最后事件 20:13)与 eda867a0(09-12 20:02 建,1 turn/45 工具,20:11 结束)。两会话档位均 danger-full-access/never ✓(档案 55 的 P1 未复发)。
  • P0 根因(实锤)= 实例 OOM:20:11:10 kernel: oom-kill:constraint=CONSTRAINT_MEMCG, oom_memcg=/system.slice/dsh-100002-299e7f95.scope, task=node,pid=355329,uid=100002 → 平台 crash-restart exitCode 137 → 20:12:11 stable。两个会话的工具调用在同一时刻(20:11:05 / 20:11:09)被中断,用户 20:12:12 问「是报错了吗」。
    • 当前仍在临界:MemoryCurrent=400175104 / MemoryMax=402653184 = 99.4%(新实例才跑 10 分钟);dsh-instance-mem.log 显示 uid 100002 峰值长期贴顶 382/384 MiB(02:35Z 起);近 3 天该实例 crash-restart ≥7 次(09-11 23:56 连崩 5 次 exitCode 1;09-12 00:19 / 01:28 / 09:37 / 20:11)。
    • NODE_OPTIONS=--max-old-space-size=256 已正确注入(档案 58 修复在位)→ 说明 256 MiB V8 堆 + 实例其余开销仍撑不住 384 MiB cgroup 上限。
  • P1 = dsh-univer-office 与本平台 loopback 封锁天然不兼容:nft table ip dsh_egress 有 meta skuid 100000-199999 ip daddr {47.77.182.89, 127.0.0.0/8, 172.17.0.1, 172.18.16.212} tcp ... reject with tcp reset(计数 159)。插件需「bundled Gateway」监听 127.0.0.1:9080+ 走 loopback HTTP → 实例内 curl=000 / python=Connection refused / node fetch failed(RST 而非超时)。
    • 后果:univer_new 报 bundled Gateway did not become ready within 10000ms×3;抖音爆款视频榜_2026-09-12.univer 全盘 find 无此文件(从未落盘);前端 /univer-api/state 从 20:08:08 起持续 400,已 153 次且仍在刷。
    • ⇒ 档案 71 静态预检判 ok 没错,但检测不出运行时架构冲突(预检盲区)。
    • ⇒ agent 反复起 gateway(9081/9082/9099 多进程)可能正是 20:11 OOM 的推手。
  • 附带:DNS 类失败 2 次(ENOTFOUND lg.racknerd.com、api.bgpview.io),外部站点侧,非平台问题。
  • 方法沉淀:取证路径 = /var/lib/dshs/users/<uid>/home/sessions/--var-lib-...-ws-<工作区>--/<session-id>/session.jsonl.zstd(不是档案 55 写的 users/<uid>/home/sessions,uid 4092b965…=guest、cce6d1cd…=admin)。
  • ⚠️ 工具缺陷(未修,待授权):dsh-server-docs/scripts/sess-list-presets.mjs 首行注释缺 /** 起始符 → node 直接 SyntaxError: Unexpected token '*',该脚本当前不可用。按 R7 未擅自修改文档库。
  • ⚠️ 与 T03 计划的关联:20:2x 那条定的「等 guest 空闲再重启」窗口,guest 此刻仍活跃且刚 OOM 崩过 → 内存贴顶是重启窗口的额外风险项。

20:3x guest 内存归因 + 隔离复现实验(只读 + /tmp 隔离,未碰生产)

  • 内存构成实测(cgroup v1:/sys/fs/cgroup/memory/system.slice/dsh-100002-af3f3fa9.scope/):
    • usage_in_bytes 382 MiB / max_usage_in_bytes 384 MiB = limit(曾精确打满)
    • memory.stat:rss 363.7 MiB(95.4%)+ cache 仅 17.8 MiB ⇒ 几乎全不可回收,内核 OOM killer 无路可退
    • rss_huge 82 MiB(THP 放大);smaps:Anonymous 372404 kB、Pss_Anon 363.7 MiB、[heap] 段 63.9 MB
    • 内存非恒定贴顶,而是 316~384 MiB 波动(清理后实测降到 316)→ 波峰正好在死亡线
  • 插件账本(隔离 cgroup 实测,纯只读 import):node 空跑 56 MiB → +dsh-univer-office = 121 MiB(该插件 +65 MiB) → +libsql 125 MiB。⚠️ univer 磁盘 168 MB(含 exchange-node-binding-linux-x64-gnu 29 MB、engine-formula-rust-binding 10 MB 等原生绑定);node_modules 总览还有 puppeteer-core/chromium-bidi/libsql/zod×2。
  • 对照:dsh-instance-mem.log(时间戳为 UTC!)显示 admin(uid 114801,未装 univer)峰值 233 MiB vs guest(uid 100002)峰值 382 MiB → 差 149 MiB,其中 65 MiB = univer 加载实测值。
  • 三条隔离实验(systemd-run -p MemoryMax=402653184,跑完即回收,全程未碰生产):
    1. 插件账本:同上,未撞墙(125 MiB)。
    2. JS 堆压力 + --max-old-space-size=256 → FATAL ERROR: Reached heap limit(Mark-Compact 254 MB),systemd code=dumped/status=ABRT ⇒ 死亡路径 A = V8 堆限自崩。
    3. 纯堆外分配(Buffer) → kernel: oom-kill:constraint=CONSTRAINT_MEMCG, task=node + code=killed/status=9/KILL ⇒ 死亡路径 B = 内核 OOM SIGKILL,与 20:11 真实事故记录逐字同构。
  • 结论:不是单一"坏东西",而是配额本身零余量 —— V8 老生代上限 256 MiB(占配额 2/3)+ 插件 65 MiB + dsh 本体/堆外 ~50 MiB + 页缓存 18 MiB ≈ 386 MiB > 384 MiB 上限。两条死亡路径都实测复现,且都不产生可读日志(一条 ABRT、一条 SIGKILL)。
  • 宿主容量:物理内存仅 1.83 GB(MemTotal 1915896 kB),available 924 MiB,swap 1 GB 已用 194 MB → 每实例 384 MiB ×2 + orchestrator ≈ 用满。这是容量问题,不是 bug。
  • 生产实例全程 active,未重启、未中断用户;实验 unit 无残留,/tmp/memsim 与本地 _tmp_memsim 已清理。

20:4x 内存治理方案调研(只读,未改任何配置)

  • 关键发现①:平台已预留 env 覆盖点,改 V8 堆限零代码改动 src/supervisor/orchestrator.ts:402 → NODE_OPTIONS: process.env.DSH_INSTANCE_NODE_OPTIONS ?? '--max-old-space-size=256' ⇒ 运维只需在 /etc/dshs.env(service 的 EnvironmentFile=)加一行 DSH_INSTANCE_NODE_OPTIONS=...;改 .env 只需 systemctl restart(不必 daemon-reload)。 ⚠️ 副作用:该 env 被实例内所有 node 子进程继承(用户自己跑 node 也被限),非 node 任务(python/ffmpeg)不受影响。
  • 关键发现②:档案 58 的设计假设与实测严重脱节(源码注释自述,这是定靶心的依据)
    • 设 256 时的依据:「dsh 实测老生代仅 27~30 MiB,三倍余量足够」→ 现在实测被会话数据填到 250 MiB,低估近 9 倍
    • 配额依据:「384 − 145 = 239 MiB 留给用户任务」→ 实测 dsh 自身吃到 363.7 MiB,留给用户任务只剩 ~20 MiB(用户跑 python/ffmpeg 立刻爆)
    • 另有 NODE_COMPILE_CACHE=<home>/.node-compile-cache(削 code range 88 MiB 冷启动开销)
    • 注释亦载明:dsh 是 223 包 / 717 JS 文件 / 21.2 MB 源码,私有内存里 88 MiB 是 [anon](V8 code range)
  • 配额硬编码在代码里:orchestrator.ts:625 -p MemoryMax=384M;改它要走「改码 → npm run build → drain + 重启」(注释里给了回滚口径)。
  • 方案结论(待用户拍板,本轮未执行):① 压 DSH_INSTANCE_NODE_OPTIONS=--max-old-space-size=160(零代码,省 ~90 MiB)② guest 在「功能管理」卸 dsh-univer-office(省 65 MiB + 消除 /univer-api/state 无限 400)③ 配额 384→512 暂不动(宿主 1.87 GB,2 实例即 1.02 GB,风险大于收益)④ 加内存阈值告警(现状贴顶是静默的)。①②合计可把峰值从 383 压到 ~230 MiB。长期解法 = 升宿主内存(花钱,须用户决策)。
  • 两个动作都要重启/重载,会断在线实例约 4 秒(T05 实测口径)→ 按 R8 已向用户报明,等窗口。

21:0x 用户拍板「先做 1、4」→ 已落地并实测验证

  • 锁:全局执行锁 + 服务器侧操作锁(instance-mem-tune);完工反序释放(先 op-lock 后 exec-lock)✓
  • 改动 1(零代码):/etc/dshs.env 追加 DSH_INSTANCE_NODE_OPTIONS=--max-old-space-size=160 → systemctl restart dshs。走的是平台预留覆盖点(orchestrator.ts:402),未碰官方 dsh。
    • 实测:orchestrator 进程 env 已读到;实例 NODE_OPTIONS=--max-old-space-size=160、v8 heap_size_limit=163M ✓
    • 效果:实例 rss 363.7 → 195.5 MiB(−46%),cgroup 总量 382 → 274 MiB(余量 ~110)
    • 验证方式(R4 合规):自建临时 session 直插(不走登录、不踢用户)调 POST /api/dsh/enter 拉起实例 → 验证 → [cleanup] 已删除 1 条
  • 改动 2:instance-mem-sample.cjs 加阈值告警(85% × 连续 3 次 → WARN 前缀 + ⚠ 标注;未达连续则标 (高位 N%,x/3))+ tooSoon 保护(<60s 的重复触发只保持计数,防手动连跑虚高);cron 每小时 → 每 10 分钟(否则 3 小时才告警,抓不到分钟级 OOM)
  • 档:新增 04-调整方案/74-实例内存治理-V8堆限下调与阈值告警.md;INDEX.md 登记 04-74;三件套(audit/manifest/consistency)rc=0。未 scp 到 /opt/dsh/docs、未 commit(按 §4 等用户发话)
  • ⚠️ 部署陷阱(已写入项目根 CODEBUDDY.md §7 第 5 条):本机 D:\github\dsh_shenxian 的 core.autocrlf=true ⇒ 工作树 CRLF,服务器是 LF;直接 scp 会把 CRLF 带进生产 → 必须 tr -d '\r' 先转,且只转本次要传的那一个文件。另注:grep -c $'\r' 在 git bash 里会误报(od -c 才准)
  • 技能:新增 dsh-instance-diagnose(三层定位 / cgroup 三件套 / 两条死亡路径 / 隔离复现 / 6 条踩坑)

21:0x 追测:「谁占了内存」的重要纠正(会话数据 vs 加载成本)

  • 整个会话(2546 事件、11 turn)的全部字符串只有 0.9 MB(最大单串 40820 字符,说明 dsh 对工具输出有截断)⇒ 对话内容根本不是内存来源,"减会话长度/web_fetch"几乎无用
  • 空载实例(零会话,仅 enter 后)smaps 拆解 = 285 MiB:匿名 214.0(V8 堆/匿名数据 155.9 + [heap] 58.1 + JIT 2.2)+ 文件映射 69.1(其中 node 本体 59.8)+ 其他
  • ⇒ 占用主体 = 「dsh 本体 + 148 官方插件 + 业务插件」的加载(模块对象与源码),不是操作。配 --max-old-space-size=160 后 V8 按"许可"预留到接近上限,故匿名映射仍 ~214 MiB
  • ⚠️ 两个实验设计坑(下次别踩):① systemd-run 起服务不继承调用者 env → 必须 --setenv=NODE_OPTIONS=...,否则堆限根本没生效(用 v8.getHeapStatistics().heap_size_limit 自证)② 同质字符串会被引擎优化('x'.repeat(N)+i 走 cons string 惰性拼接,不复制前缀)→ 实测"投喂 23 GB 只涨 140 MB"的假数据。结论:内存类模拟实验不可靠,优先用真实数据统计

21:1x 插件内存成本逐个实测(推翻"装得多就占得多")

  • 方法:隔离 cgroup(MemoryMax=600M)+ node --expose-gc,逐个 import guest 的插件测增量(.cjs 不支持顶层 await → 必须 .mjs)
  • 结果(rss 增量):dsh-univer-office +65.1 MiB | libsql +7.8 | 平台自研 ×3(@dsh-local/portal-entry、workspace-scoped-picker、business-plugins)= 0.0 MiB | puppeteer-core 0.0 MiB
  • ⇒ 成本由"装了什么"决定,不是"装了几个":一个重型插件 ≈ 无穷多个轻量插件。平台自研 3 个合计 0 MiB(写法是轻的),是正面样板
  • puppeteer-core = 0 MiB 是"懒加载有效"的实证(只 import 主入口、不启动浏览器)
  • univer 为什么重:lib/index.js = 单文件 bundle 5400+ 行 / 6 MB,顶层静态 import 拉起重型依赖(连 @puppeteer/browsers 都在,一个表格插件带浏览器依赖),全文件仅 2 处动态 import ⇒ 优化空间在「改懒加载」,但它是第三方的(要 fork 或提上游)
  • guest 的 bundles 实际清单(profiles/web/package.json):@deepseek-ai/dsh-base(148 官方插件基座)+ dsh-web-app + 3 个 @dsh-local/* + dsh-univer-office。注:@liustack/modlens 只在 node_modules 残留、不在 bundles(已禁用)
  • ⚠️ 插件的 pnpm remove(禁用)必须重启实例才真释放 —— Node 模块缓存不卸载
  • 已把上述实测数据与两条实验坑补进技能 dsh-instance-diagnose

21:3x 用户报「继续检查 guest 最新对话还是有问题」→ 已定位

  • 用户的问题 = univer 打不开:会话 eda867a0(21:28 最后更新)里,用户 21:24「再试试呢」→ agent 重试 univer_new 又失败(bundled Gateway did not become ready within 10000ms);21:27「如何打开 univer」→ agent 深挖源码后转向用 python 产出 xlsx+docx(21:25 已生成 reports/抖音爆款视频表_2026-09-12.xlsx + 日报.docx + 4 图表)
  • ★ 独立验证出的根因 = 架构级不兼容(配置不可修),两条独立阻碍:
    1. Host→Gateway:实例内 node 连不上自己起的 127.0.0.1:9080(nft dsh_egress 对 skuid 100000-199999 → 127.0.0.0/8 是 reject with tcp reset)
    2. Browser→Viewer:源码硬编码 gateway = http://127.0.0.1:${port}、viewerUrl = ${gateway}/?file=...,client 拿它当 iframe src ⇒ 浏览器去用户自己电脑的 127.0.0.1 找 Viewer ⇒ 即使解封 loopback 也打不开
    • ⇒ 该插件的 Viewer 假设「浏览器与实例同机」(本地部署),与托管多租户平台根本不兼容 → 治理结论:候选池下架 / 用户禁用(顺带省 65 MiB)
  • 内存现状(跑会话后):cgroup 370 / 384 MiB(余量仅 14);smaps 对比空载:V8 堆/匿名 155.9 → 275.5(+120)、文件映射 69.1 → 110.1 ⇒ 只要跑了活,V8 堆就涨到"许可上限"附近。但全程无崩溃、无 OOM ✓ —— 堆限 160 的防崩作用已验证
  • 服务级 20:53 后零 crash-restart ✓
  • 技能 dsh-instance-diagnose 已更新:① NODE_OPTIONS 更正为 160(含档案 74 与覆盖方法)② 新增「已知与本平台不兼容的插件:dsh-univer-office」整节(含两条阻碍的机理与验证命令、替代路径)
  • 排查用会话副本已清理;全轮只读,未改生产

21:5x 用户拍板「把托管友好性作为插件评估与改造的一个点」→ 已立档(档案 75)

  • 新增档案 04-调整方案/75-插件评估新增托管友好性与资源成本两个维度.md(📋 只立标准,未改预检脚本)+ INDEX.md 登记;三件套 rc=0;未 commit、未 scp docs
  • 维度 A 托管友好性(H1–H4):核心判据一句话 = 「插件的一切对外交互,是否都能走平台已有的那一条入口」(浏览器侧只用相对路径;实例侧不依赖 loopback 网络服务)
    • H2 = 阻断级:client bundle 里出现绝对 URL(http:// 字面量)= 会交给浏览器 = 托管平台必失效且平台侧无法补救(univer 死因)
    • H1 硬编码 loopback / H3 自建监听=警告;H4 交付 URL 由谁生成为人工研判
    • 检测复用档案 71 的 walkJs 静态扫描框架,不另起一套
  • 维度 B 资源成本:加载 rss 增量,>30 MiB 标注 / >60 MiB 强提示(univer 实测 +65.1;自研 ×3 = 0.0 是正面基线)
  • 改造规范 R-a~R-e(自研/改造插件必守):① 对外入口唯一(挂 Host 的 webServer,同域相对路径)② 内部通信走 IPC(unix socket / stdio),不用 TCP loopback ③ 不新增监听端口 ④ 重型依赖懒加载 ⑤ 交付浏览器的地址一律由 Host 生成且为相对路径
  • 与既有档案关系:是档案 71 的新增维度(沿用其分级语义);与 70(预检盲区致事故)同属系统性补盲;与 74 的资源成本同源
  • 技能 dsh-instance-diagnose 的「已知不兼容插件」节已加指针指向档案 75(不复制内容,保持单一来源)
  • 落地(待排期):预检脚本加 H1/H2 静态扫描 + H2 命中判 blocked + 静态体积提示 → 属生产代码变更,须另立交接单(规划与执行分离);T03 的 MCN suite 起按 R-a~R-e 验收

22:0x 用户问「现在可以改造这个插件了吗」→ 可行性已验证(三个门槛全过)

  • 门槛① 源码可得 ✅:已 clone 上游到 D:\github\dsh-univer-office-src(浅克隆,395 文件 / 216 个 ts / 15 MB,含 src/、packages/、scripts/、4 个 tsconfig、test/)。仓库 github.com/dream-num/dsh-univer-office(走代理 10800)。⚠️ 候选池里的 tgz 只有产物无源码(files 只发 lib/artifacts/docs/skills,构建脚本都没发布)→ 改造必须回到上游
  • 门槛② 依赖可装 ✅(原本最大不确定项):.npmrc 把 @univer-cli/@univerjs/@univerjs-pro 三个 scope 全指向私有 registry https://insider-npm-registry.univer.work/,且仓库未配 authToken;实测该 registry 公开可读(包元数据 HTTP 200,返回正常 npm 元数据)→ 无需 token 即可装
  • 门槛③ 改动点定位 ✅(TypeScript 源码,模块化清晰:src/{gateway-app,host,client,viewer-app,viewer-support,render-machine,workers,shared})
    类 位置 要点
    ① src/gateway-app/server.ts:11 / :136 const LOOPBACK_HOST = '127.0.0.1' + server.listen(port, LOOPBACK_HOST, …) → 需支持 unix socket
    ② src/gateway-app/main.ts:16-17、src/host/processes/gateway/gateway-process.ts:29、protocol.ts:20 origin 构造 http:// / ws://127.0.0.1:${port} → 需可配置
    ③ src/host/provider/gateway-univer-service.ts:538 viewerUrl: ${gateway}/?file=… → 需改相对路径
    ④ (新写) Host 侧新增反向代理(/univer-api/viewer/* → gateway),含 WebSocket upgrade 转发 ← 真正的工作量在这
  • 工作量诚实评估:①②③ 合计不到 10 行;④「代理层含 WS」是新功能(且要穿 nginx → orchestrator 两跳)。构建链重:build = lib+worker+gateway+render+viewer 五个产物,150+ devDependencies(vite 8 / ts 7 / tailwind 4 / insiders 快照依赖)
  • 已启动:pnpm install --frozen-lockfile(prepare 会自动触发 build)→ 这是决定改造成立与否的最后 gate(构建不出来 = 改造不成立)。本机环境:node v22.22.2 ✓ / pnpm 11.21.0(源要求 11.23.0)/ D 盘余 1.2 T ✓
  • 待定(要用户拍板):若继续,须先定「fork 放哪 / 谁维护 / 上游更新怎么合」——这是长期承诺

22:1x 用户说「开整」→ 构建验证通过,改造已开工

  • ✅ 构建 gate 通过(决定性):pnpm install --frozen-lockfile 成功,prepare 自动跑完 build(5 个产物)——✓ built in 10.16s、built ...\artifacts\viewer、Done in 14m 45.1s。只有 INEFFECTIVE_DYNAMIC_IMPORT 良性警告(i18n locale chunk 优化提示)。⇒ 改造成立
    • 产物:artifacts/{gateway.cjs 57M, unit-content-worker.mjs 30M, viewer/, render-machine/} + lib/{index.js 5M, client.js 1.1M}
    • 我的改动已验证编译进产物:gateway.cjs 里 UNIVER_COLLAB_GATEWAY_SOCKET ×1、socketPath ×12
    • 增量构建快(rollup 10 秒级),首跑 14m45s 主要在装依赖
  • 已改 3 个文件(gateway 侧监听层,均向后兼容):src/gateway-app/server.ts(LOOPBACK_HOST 常量 → 新增 socketPath/host 选项,listen() 支持 unix socket)、main.ts、gateway-entry.ts(真入口,读 UNIVER_COLLAB_GATEWAY_SOCKET)
  • ★ 关键发现①(简化方案的钥匙):gateway 全部 API 在 /uf/<base64url(univerfile)> 前缀下(main.ts 自带端点清单),WS 是 /uf/<enc>/universer-api/comb/connect;Viewer 加载后按绝对路径请求 /uf/* ⇒ 代理规则两条即可:/uf/** + /univer-api/viewer/**
  • ★ 关键发现②(坑):Node 内置 fetch 不支持 unix socket(无 socketPath)⇒ 健康检查 protocol.ts:4 与 GatewayClient 都要改。但改造面比预想小:host 侧 fetch 仅 5 处,且已有集中封装 src/host/adapters/gateway/client.ts(89 行,只用 response.json()/ok/status 三个能力) ⇒ 在 GatewayClient 内加一条 unix 分支(http.request({socketPath,…}) 包最小 Response-like)即可覆盖大部分
  • 剩余改动清单:④ launcher.ts 传 socket env | ⑤ gateway-process.ts:29 origin 构造 | ⑥ protocol.ts:2,20 健康检查 | ⑦ gateway-univer-service.ts:538 viewerUrl → 相对路径 | ⑧ adapters/gateway/client.ts 加 unix 分支 | ⑨ host/webServer/router.ts 新增代理层(/uf/** + viewer)
  • 代码在 D:\github\dsh-univer-office-src(未打包、未碰生产)

22:2x 改造阶段 1 代码完成(①~⑨),构建通过

  • 新增 4 个模块:shared/gateway-socket.ts(socket 路径解析 + unix:<path> 端点标识 unixOrigin/parseUnixOrigin)、shared/unix-http.ts(HTTP over unix socket 最小客户端,只覆盖 ok/status/text,零新依赖)、shared/viewer-paths.ts(浏览器侧路径常量,避免 service→webServer 依赖)、host/webServer/viewer-proxy.ts(流式反向代理,去掉 hop-by-hop 头)
  • 改动:launcher(传 UNIVER_COLLAB_GATEWAY_SOCKET)|gateway-process(端点按传输打标)|protocol(健康检查 + probeGateway 支持 socket)|adapters/gateway/client(加 unix 分支,一处覆盖绝大部分调用)|router(viewer 代理分支)|plugin(/uf 注册)|gateway-univer-service(viewerUrl 与 base 均改相对路径)
  • ★ 关键设计决策(易踩):dsh 的 webServer 是前缀匹配但无「最长优先」 ⇒ /univer-api 会吃掉 /univer-api/viewer/... ⇒ viewer 代理必须放进现有 router 的 dispatch 内,单独注册会被 404 截胡;/uf 与之不冲突,可独立注册
  • ★ 踩坑:只 grep viewerUrl: 会漏 —— 另有 const base = ${gateway}/?file= 派生 openUrl/worktreeUrl/mergeUrl(全是交给浏览器的 URL)。改完产物内旧拼接残留 = 0
  • 构建:✓ built in 13.69s rc=0;产物含 createGatewayViewerProxy×3、parseUnixOrigin×4、VIEWER_PAGE_PATH×3、UNIVER_DSH_GATEWAY_SOCKET×1
  • 剩余:① 3 处散点 fetch(render-source-operations.ts:147、resource-operations.ts:86、worker 侧 1 处)socket 模式下也要改 ② WS 代理(阶段 2):dsh webServer.registerUpgrade 是精确路径匹配,而 gateway 的 WS 在 /uf/<enc>/univeriser-api/comb/connect(含动态段)⇒ 需按文件动态注册(ctx.effect 可增删)③ 自带 test:host/test:integration 冒烟 ④ 打包 → 候选池 → guest 启用 → 端到端验收

22:4x ★ socket 传输在真实 Linux 验证通过(改造核心假设成立)

  • 方法(不碰生产):构建产物(lib + artifacts + test,36 MB 压缩)传到服务器 /tmp/uv-verify,用 guest 实例已装的 node_modules 做只读符号链接补原生依赖(libsql 等)
  • 结果(unix socket 模式):[dsh-univer-gateway] listening on unix:/tmp/uv-verify/gw.sock;GET / → 200,且 <title>Univer</title>=true、<div id="app"></div>=true(这正是原先失败的健康检查判据,在 socket 上成立);GET /uf/AAAA → 400(路由可达)。TCP 对照同样通过
  • ⚠️ Windows 限制(本机测不了 socket 的原因):本机 AF_UNIX 返回 EACCES —— 用纯 node:net 也复现 ⇒ 平台限制,与改动无关;UNIVER_DSH_GATEWAY_SOCKET 链路本身通(gateway 收到路径并尝试监听)
  • TCP 回归:test/integration-smoke.mjs 在 Win 与 Linux 均 OK(18 项:new/status/Unit/import/export/screenshot/print-pdf/resources/worktree lifecycle…)⇒ 改动未破坏原功能
  • 清理:服务器 /tmp/uv-verify、本地临时文件已删;生产完好(guest 实例 active、依赖正常)
  • ⏳ 待拍板(R8):端到端验证必须部署到 guest 实例(启停 = 重启实例、断在线)—— 这是唯一需要用户点头的一步
  • 仍待做:① WS 代理(阶段 2,按文件动态注册 registerUpgrade)② 边缘功能 socket 支持(截图资产、worker snapshot)

23:1x 改造全部完成(代码 + 验证 + 打包),仅剩部署验收待拍板

  • WS 代理(阶段 2)完成:createGatewayUpgradeProxy(转发 upgrade:重建 101 响应头 + 双向 pipe + 失败降级)|viewerSocketPaths(fileKey)(两条:/uf/<enc>/universer-api/comb/connect、/uf/<enc>/events)
    • 注册机制:dsh registerUpgrade 是精确路径且 gateway 的 WS 路径含动态段 ⇒ 按文件懒注册(router /univer-api/state → onViewerFileOpened 回调 → plugin 内 Map 去重 + ctx.effect 统一 dispose)
  • 统一传输入口 shared/gateway-request.ts:requestGateway(endpoint, path, init) 按 endpoint 前缀选 socket/TCP ⇒ client.ts 与 render-source-operations.ts(截图资产)均改用它,消除重复分支;unix-http.ts 扩展 headers.get/json()/arrayBuffer()/signal
  • 未做(已标注为已知限制):worker 侧(unit-content-worker,协同/快照类)仍走 TCP —— 其内部 12 个 URL 用标准 fetch 拼接,改造面大且属边缘;导出 xlsx 走 Host 的 GatewayClient,不受影响
  • 验证全绿:构建 rc=0 | 回归 integration-smoke OK(18 项功能)| Linux socket 复验通过(GET /→200 且 Univer 标记 true;TCP 对照同)| 版本升 0.2.15、pnpm pack 产物 /d/tmp/dsh-univer-office-0.2.15.tgz(41 MB)
  • 清理:服务器 /tmp/uv-verify 等 + 本地临时文件已删;生产完好(guest 依赖正常)
  • ⏳ 唯一待拍板:部署到 guest 实例做端到端验收(R8:启停 = 重启实例、断在线)

23:2x 用户「确认执行」→ 已部署到 guest,但端到端仍差一步

  • ✅ 投放成功:POST /api/plugins/business(admin,base64 传 tgz)→ version 0.2.15、replaced:true、compat.level=ok(223 平台包全匹配)、无 P0 命中(trustedOverride:false);仅 network-egress 警告(来自 README/assets 里的 URL 字符串,非阻断)
  • ⚠️ 发现平台缺陷(重要):guest 侧「禁用→启用」失败(安装失败,请重试或联系管理员)。根因:business-plugins.ts 的 pnpm add 缺 -w ⇒ ERR_PNPM_ADDING_TO_ROOT(profile 内有 pnpm-workspace.yaml,必须显式 --workspace-root)。且该路由 runPnpmAs 用 stdio:'pipe' 吞掉 pnpm 的 stderr ⇒ journal 里查不到真实原因。手动 pnpm add -w 成功(5.5s,装到 0.2.15,属主正确 100002)
  • ✅ 平台侧配置完成:orchestrator.ts 的 baseEnv 增加透传(DSH_INSTANCE_UNIVER_SOCKET → 实例内 UNIVER_DSH_GATEWAY_SOCKET,不设则保持原 TCP 行为);npm run build 通过;/etc/dshs.env 加 DSH_INSTANCE_UNIVER_SOCKET=auto;重启服务后 orchestrator 已读到;实例 env 实测注入成功(UNIVER_DSH_GATEWAY_SOCKET=auto)
  • ✅ 组件级验证全过:实例内 os.tmpdir()=/tmp → socket 路径 /tmp/dsh-univer-gateway-<pid>.sock(103 字节,未超 108 上限);在实例命名空间内(nsenter)用插件真实产物跑 gateway → listening on unix:/tmp/…sock,socket 文件生成 ⇒ 改造在实例内确实有效
  • ⚠️ 端到端仍未通过:用户浏览器 23:24:10 请求 /univer-api/state → 23:24:18 返回 400(8.2s);实例当前无 gateway 子进程、/tmp 下无 .sock。400(非 500)指向校验类错误码(INVALID_* / SESSION_SCOPE_*),8.2s 说明确曾尝试启动
  • ⏳ 下一步:请用户在实例里再触发一次 univer 操作(23:24 那次实例刚重建 ~23:24:04,插件加载可能未就绪);若仍 400,开 UNIVER_DSH_GATEWAY_DEBUG=1 取 gateway stderr
  • 另注:同期间发现 admin 实例 crash-loop(dsh-plugin-mcn-suite 缺 xlsx 依赖,crash-loop-circuit-open)—— 与本次改造无关,属 T03 遗留问题

20:4x T03 推进到「上传前一刻」—— 按纪律停手(线上有别的会话在动)

  • 窗口条件已满足:guest 实例 systemctl is-active dsh-100002-299e7f95.scope = inactive(已停)+ 最后活动 20:06(41 分钟前)⇒ 零中断窗口 ✓
  • T03 执行路径已完全摸清(本轮收获,下次直接可用): · 上传 = POST /api/plugins/business(admin),body = {filename, file:<base64 tgz>, trust?:{confirmed,reason}};cookie 名 sid;端口 3080;UPLOAD_BODY_LIMIT = 180 MB ✓(1.9 MB 包绰绰有余);P0 命中且无 trust → 409 fail-closed。 · 启用 = POST /api/plugins/mine/apply(用户自己发)⇒ 按刚固化的规则「用户在实例里自己启用」,这一步不该我代劳。 · 摘 modlens = 改 guest profile/web/package.json 的 bundles 列表(零中断,因实例已停)。
  • ⛔ 停手原因:bash scripts/op-lock.sh status 显示 🔴 instance-mem-tune(占用者 mem-opt-2046,20:47 起)→ 线上有会话正在动生产 ⇒ 按纪律停手;已 release t03-upload-pool 撤掉我刚占的位。
  • ⚠️ 发现 op-lock 的设计缺陷:它按「操作名」占位(锁根下一个操作一个子目录)⇒ 两个会话占不同名的操作都会成功 ⇒ 它拦不住「并行动生产」(不像全局执行锁那样真正互斥)。本次即「占位成功但线上已有人」—— 只能靠人主动看 status,机制本身不设防。建议改为「单锁位 + 占用者字段」,或 claim 时发现已有任意占位即默认拒绝(--force 才可覆盖)。待下次改文档时补进 交接单/README §六。

21:0x T03 上传候选池 —— 被 T05 兼容性预检拦住(HTTP 409 compat_incompatible)

  • 条件全满足(op-lock 空、guest inactive、59 分钟无活动)→ 占 op-lock → scp tgz(md5 f98a3303… 与单子一致 ✓)→ mksess.cjs 造 admin 会话 → curl POST /api/plugins/business。
  • 判据(服务端回显):{"kind":"dep-range","pkg":"@deepseek-ai/dsh-client-ui-sidebar","detail":"平台为 0.1.2-rc.1,插件要求 ^0.1.0-rc.5(不满足)"}。
  • 原因(semver 的 prerelease 规则,不是误报):带 prerelease 的版本只能被「同 major.minor.patch 且带 prerelease」的比较器匹配 → 0.1.2-rc.1 的 tuple (0,1,2) 在 ^0.1.0-rc.5(= >=0.1.0-rc.5 <0.2.0)内没有对应比较器 → 判不满足。插件声明过严(作者意图「≥0.1.0-rc.5」,但 ^ 写在 prerelease 上不等价)。
  • 根因(时间差):T05 兼容性预检 16:35 才上线,而 T03 的包是 11:15 打的 → T03 单子没预料到这道新关卡。
  • 副作用 = 零:候选池未变(池内仍只 dsh-univer-office @0.2.14);临时 admin session 已删(deleted sessions=1 ✓);服务器与本机临时文件已清;op-lock 已释放(无人占用 ✓)。
  • 待定处置:(a) 改插件 package.json 依赖声明放宽到能匹配 0.1.2-rc.1 → 重打包 → 重传(推荐,不掩盖问题);(b) 人工确认实际兼容后上传带 trust:{confirmed:true,reason} 放行(快,但掩盖声明问题)。下一步:先只读验证「插件实际用了 sidebar 的哪些 API / 平台 0.1.2-rc.1 是否提供」再定。
  • 经验:op-lock.sh release 必须带同一个 ME=,否则被按 R9 拒绝(本次踩 1 次,属正确保护)。

22:2x T03 安装失败 → 根因定位 + 修复 + 重传完成(用户报障「安装失败」)

  • 现象:用户在「功能管理」启用 mcn-suite → 失败。取证:apply 请求 200 → 实例被拉起(22:12:57 dsh web: http://…)→ 但 node_modules 无包、bundles 无、日志无 mcn 痕迹、profile mtime 被改 ⇒ 平台 install 阶段失败并回滚。
  • 复现(mksess + POST apply + 轮询 task):#failed | 失败 ERR: 安装失败,请重试或联系管理员 —— 阶段停在「正在安装 1 个插件…」,不是探活阶段;⚠️ 平台把真实错误泛化吞掉了(违反 A10「失败面要留证据」)。
  • ★真实根因(手动跑平台同姿势 pnpm 才逼出来): ERR_PNPM_NO_MATCHING_VERSION No matching version found for @deepseek-ai/dsh-client-runtime@>=0.1.2-rc.1 <0.2.0-0 ⇒ 这些 @deepseek-ai/dsh-client-* 不在公开 npm registry 上(平台用的是本地那份 0.1.2-rc.1),而 pnpm 会去 registry 解析 peer 依赖 → 解析不到 → 装不上。这与「预检拒收」是两层不同问题:改范围只过了预检,装的时候照样卡。
  • 修法:package.json 增加 peerDependenciesMeta:4 个 peer 全标 optional: true(准确表达「由平台提供、不走 npm」);版本 0.3.1 → 0.3.2;npm pack 重打包(1,951,946 B)。
  • 验证:以 admin 身份手动 pnpm add 新包 → ✅ 成功(+ dsh-plugin-mcn-suite 0.3.2,2.9s)→ 随后清理残留(pnpm remove 报 CANNOT_REMOVE_MISSING(package.json 已恢复、无该依赖)→ rm -rf 兜底)→ bundles 回到平台 5 个 ✓
  • 上传:HTTP 200、replaced: true、compat.level=ok;候选池 = dsh-plugin-mcn-suite @0.3.2 + dsh-univer-office @0.2.14。
  • 现场:服务器与本机诊断脚本/临时包全部清理;admin profile 已复核一致(node_modules 无残留、deps 正常);op-lock 已释放。
  • ⚠️ 建议修平台缺陷:apply 失败时应回显 pnpm 的真实 stderr(现在吞成"安装失败,请重试或联系管理员",导致必须手动复现才能定位)。
  • 待办:请用户再试一次启用(0.3.2 已就绪)。另:用户新提「功能管理列表改卡片、一行 2 个」→ 新需求,待做。

22:3x 「功能管理」插件列表 → 卡片形式(一行 2 个)v0.2.6(用户要求「一起改了吧」)

  • 改动(poc/business-plugins/lib/client.js,4 处纯渲染层):① 新增内层网格容器 display:grid; gridTemplateColumns:repeat(2, minmax(0,1fr)); gap:10px(单独包一层,不直接改外层 wrap —— 它同时装标题/搜索/按钮/提示等纵向块;插件项收集进 cards[] 再统一 push)② 每项由「行」改「卡片」:padding 12px 14px、borderRadius 12px、background: var(--dsw-alias-bg-layer-1,#fff)、常显 1px 边框(原非错误项为 transparent)③ hover 改按规范 §4.5:border-color: brand-primary + box-shadow 0 4px 12px rgba(47,111,237,.15)(原为换背景色)④ 卡片内 alignItems: flex-start(复选框/徽章顶部对齐,长说明不撑歪行高)。
  • 规范依据:06-工作台UI规范 §4.5(卡片 r12 + hover 主色扩散影)、§2.4、§2.3;颜色仍走 --dsw-*(section 嵌在 dsh 面板内)。
  • 验证:node --check 语法 OK(556 行);版本 0.2.5 → 0.2.6;npm pack → dsh-local-business-plugins-0.2.6.tgz(产物名带 dsh-local- 前缀,部署脚本要的是 business-plugins-*.tgz → scp 时改名)。
  • 部署:/opt/dsh/artifacts/business-plugins-0.2.6.tgz → node scripts/ensure-biz-plugins.cjs --all(未加 --restart,不主动打断在线实例)→ admin/guest 均确认 0.2.6、包内含改动 ✓;op-lock 已释放。
  • 生效条件:client bundle 在实例启动时加载 ⇒ 用户需重启自己的实例才看得到。
  • 未做:浏览器渲染验证(本机无 Chromium)→ 待用户目视验收。
  • 落档:档案 67 追加「v0.2.6 卡片化」小节。