Files
dsh_ai1net_server/.workbuddy/memory/2026-09-下17.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

46 KiB
Raw Blame History

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

⚠️ 本目录日志已按【月】分片(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-13.md 上一片:2026-09-下16.md 下一片:2026-09-下18.md


20:35–20:5x · R2 视觉层第 2 轮:「系统管理」改按门户源码照抄(business-plugins 0.3.3)

触发:用户第 2 次反馈「点击卡片后的弹窗样式还是没变呢,说了要用之前门户的里面的对应样式」。

查明两个真因(都不是猜)

  1. 上一步改动没出厂:poc/business-plugins/lib/client.js mtime 20:33 > 最新产物 business-plugins-0.3.2.tgz mtime 20:17。
  2. 更要命:上一版只有「用户管理」1 个弹窗是门户表格;另外 4 个(候选池/运行时/存储/当前实例)仍是裸 div + 内联样式 ⇒ 点开即「毛坯房」。
  3. 方法论错误:上一轮抄的是 06-工作台UI规范 的抽象条目(§4.5/§4.7),用户要的是 web/portal.html 里已跑着的实际组件。

改法(读门户源码,逐值对应)

元素 门户来源 落地类名
卡片网格/卡片 .nav-grid / .nav-card(r10, 18px 16px, hover 上浮-2px + 蓝边 + 0 4px 12px rgba(47,111,237,.12)) .pa-grid / .pa-card
计数胶囊 .pg-tab .pg-cnt .pa-cnt
弹窗表格(5 个全改) .table-wrap + table.tbl(th 8px10px/600, td 7px10px, 行 hover 浅蓝) .pa-wrap + .pa-tbl
徽章 / 按钮 .badge 四色 / .btn-sm(+.danger) .pa-badge / .pa-sm
弹窗头 / 标题 / 副标题 .card-h / .page-title / .page-sub .pa-card-h / .pa-hd / .pa-sub
弹窗外壳 门户无弹窗 ⇒ .card(r10/18px) + design.css --shadow .pa-overlay + .pa-modal
表格空态 门户 emptyRow() .pa-tbl td.pa-empty-cell

仅颜色走 dsh token --dsw-*(须随 dsh 主题);字号/间距/圆角/动效与门户逐值一致。新增 zh/en 词典键:pa.colName/colVer/colDesc/colItem/colValue/colUsed/detail/close。

三层验收(这次不只看单测)

  1. 单测:scripts/verify-platform-admin-section.mjs 新增「结构层」8 项断言(含「5 个弹窗全部 = 门户表格」)→ 全绿。
  2. 实装:md5 5800ba4f77e1c786063aed8a91f9f4b4 三处一致(本地产物 / admin profile / guest profile),旧标记 pa-go/.pa-n{ = 0。
  3. 端到端:mksess.cjs 临时会话 → 实例首页 200 → 模块 URL rev=fd084d6aa33a → 取回 bundle 4.26 MB:pa-empty-cell 3 / pa-card-h 4 / pa-overlay 2 / pa-cnt 3 / pa-grid 3 / pa-tbl 10 处,旧标记 0;临时会话已删(1 行)。

缓存不是原因:src/supervisor/proxy.ts:383 已对 /plugins/、/assets/ 下发 Cache-Control: no-cache,且模块 URL 自带内容 rev=<sha1>。

顺手清掉一个自作的地雷(如实记录)

打包脚本第一次因断言锚点写错(description 不以 v0.2.8 开头)在写文件前抛错,但我已先跑了 npm pack ⇒ 生成了一个内容是新版、版本号仍写 0.3.2 的本地 tgz。已删除该文件,改用正确锚点重新升到 0.3.3(服务器侧 business-plugins-0.3.2.tgz 是真正旧版,未受影响)。

交付

  • 产物 business-plugins-0.3.3.tgz;铺发 node /opt/dshs/scripts/ensure-biz-plugins.cjs --all --restart ⇒ admin bundles=6 / guest bundles=7。
  • 代码仓提交 b9540a8 → 已 push origin master;文档库提交 f1a0da1(档案 82 §10.7)→ 已 push origin main;档案 82 已 tar 管到 /opt/dsh/docs,双端 md5 456a2a55c3648ab760359c15d803cbfa 一致。
  • 顺带修校验脚本自身缺陷:text() 树遍历 depth > 12 会把 表格>tbody>tr>td>文本 截断(表头有、行文本丢)→ 放宽到 20。

⚠️ 收尾状态(给下一次接手)

  • 并行会话在跑:技能归档副本同步时抢锁失败 —— 占用者 univer-e2e-confirm(20:48 起)。按 R9 已停手。
  • 未完成(因抢不到锁):skills/dsh-change-workflow/SKILL.md 的归档副本 + 服务器镜像未同步(工作副本已改好,见下)。
  • 文档库 docs-sync-check.sh 当前 141 一致 / 2 不一致,两个都属并行会话在途:docs-manifest.json、skills/dsh-opensource-release/SKILL.md(另有档案 76 md 本地已改但服务器已同步 ⇒ 已一致)。刻意不碰。

技能更新(工作副本已改,待补归档副本)

dsh-change-workflow 改了 4 处:

  1. 工具口径纠偏:技能原写「agent-browser 是死路,别再试」与用户 2026-09-13 明令(「只允许 agent-browser(默认) / browser-harness;Playwright 全面禁止」)冲突 ⇒ 已改为「用户口径优先,旧实测降为背景」。
  2. 阶段 4 新增第 7 条:交付前必问「这份改动出厂了吗」(源文件 mtime vs 最新 tgz mtime 的一秒自检 + 假产物陷阱)。
  3. 阶段 4 新增第 8 条:视觉验收三层(单测 class / 实装 md5 / 端到端取 bundle)。
  4. 阶段 4 新增第 9 条:「参考门户样式」= 抄门户源码实际取值,不抄规范抽象条目(⛔ docs-status-sync.sh 已不存在 等阶段 5 过期内容一并修正)。

20:5x · 档案 76 §六(端到端确认)已补 + 一次自己的操作失误(已修)

  • 第四轮后再补一节「§六 端到端确认」:dock active:false → true(浏览器侧闭环)、turnTail 的 -10 占位者即本插件卡片、旧回合不回溯、以及「硬刷新会取消在途 client-platform 查询」这条平台级现象。四件套 rc=0。
  • ⚠️ 我的一次失误(必记):scp <两个文件> bt-server:/opt/dsh/docs/04-调整方案/ —— 多文件 + 单目标目录 = 两个都落进那个子目录,于是 docs-manifest.json 被误放进 04-调整方案/,对账立刻报出「仅服务器(待拉取)」。⇒ 本库有子目录结构,scp 必须逐个文件、各自带正确相对目录(文档库 CODEBUDDY 的同步链路一节已写明,是我没照做)。已 rm 误放副本 + 复对账(只剩另一会话的 skills/dsh-opensource-release/SKILL.md 待它自己同步)。

21:0x · 收尾:沉淀新技能 + 清掉三把技能的归档漂移

  • 新增第 7 把技能 dsh-plugin-diagnose(工作副本 ~/.workbuddy/skills/ + 归档副本 + 镜像 /opt/dsh/docs/skills/,三方 md5 61bd08b7… 一致,目录 700 / 文件 600)。内容 = 插件三层归属(host / client / 网关·原生绑定)+ 三把尺子:inject 差集(客户端半边静默挂死)、glibc 直测(原生绑定两类对策)、产物插探针(打包顺序废掉垫片);附 8 条实测踩坑与平台侧事实。
  • 清掉三把技能的归档漂移(记忆里挂着的历史遗留):dsh-change-workflow(995 vs 958 行)/ dsh-decision-method(491 vs 435)/ dsh-feature-first(284 vs 239)—— 工作副本为准同步到归档与镜像,三方 md5 一致。 方向安全性先验证过:归档独有行只有 3–5 行且全是过时残留(引用已删的 docs-status-sync.sh、「文档只写服务器不落本地」、已被推翻的「agent-browser 是死路」)⇒ 不是「归档更新」。
  • 对账回收:只剩 1 项不是我的(skills/dsh-opensource-release/SKILL.md,另一会话改了未同步)。

21:00–21:2x · MCN 插件摘除「计划任务」+ 查明 mcn-task 会话分组真因

触发:用户问「为什么 admin 会话中一会儿就多出一个 mcn-task 会话分组 哪里来的」→ 接指令「把那个插件的计划任务删除」。

真因(源码级证据,不是猜)

mcn-task 不是平台的东西,是 dsh-plugin-mcn-suite 自己建的工作区分组(名字是 09-12 用户定的统一名)。两个创建点:

# 位置 机制
A lib/host/schedule/index.js ensureTaskWorkspace() 挂在 apply(ctx) 上 ⇒ 每次实例冷启动都跑(进程内缓存随进程消失)⇒ 用户删掉分组后下次启动又回来。有按 title 查重,不会重复建,但会「复活」
B lib/host/mcn/index.js 291/405/511/613(视频解析/改写/审稿/人设) mkdirSync 后直接 reg.create(dir,"mcn-task"),没先查重(只有进程内缓存兜底)+ 建完 setTitle("mcn-task") 强制同名 ⇒ 只要 dir 变了就再建一个同名分组

dir 为什么会变:config.js 的 resolveCreativeCwd() 每次调用重读 workspace.json、取「路径最浅」的工作区当根 ⇒ 用户动工作区 ⇒ 根漂移 ⇒ dir 变。 现场(admin):ws/mcnworkspace/mcn-task(1 会话) + ws/mcntimo/mcn-task(0 会话) = 同名 2 个、路径不同 —— 正是「一会儿多一个」的形态。

本轮改动(0.3.9 → 0.3.10,只做用户要的那一项)

lib/index.js(HOST_PARTS 去掉 mcn-schedule + import;顺带修 VERSION 常量长期写 0.3.0 的笔误)| lib/client.js(不再注册 mcn-schedule 入口 ⇒ 工具栏按钮靠 scheduleEntry && 自动消失;删「更多功能」里未守卫的「自动任务」;整块删 248 行子包客户端段,SchedulePage/mcnSched_* 清零)| lib/host/schedule/ 整目录删除|tests/smoke.mjs 删用例+重编号|README.md 功能页 5→4|cordis.patch.yml 注释|package.json。

验证(三层,全通过)

  1. 实装:admin/guest 均 0.3.10;lib/index.js md5 919efdd9…、lib/client.js md5 f1a3cf7c… 与本地产物完全一致;lib/host 无 schedule;HOST_PARTS=[mcn, voxemw-cloud];客户端子包 5、入口注册 0。
  2. 端到端:冷启动日志 [mcn-suite] loaded v0.3.10(host 面 2 个:mcn, voxemw-cloud)、无任何 mcn-schedule 行;/mcn/schedule/list → 404;mcn-task 分组数 冷启动前 2 → 后 2(不再自动新增);下发 bundle(rev 9136d5916c17)dsh-plugin-mcn-schedule 0 处 / mcnSched_ 0 处。
  3. 基线校验(本次新增纪律,已写进技能):把线上 0.3.9 tgz 拉下解包与本地源码全量比对 ⇒ 除本轮 4 文件外逐字节一致 ⇒ 「要改的 == 正在跑的」。⚠️ 我当时是先改后验,顺序错了(结论好但风险窗口白开)。

⚠️ 未解决(已写进 dsh-plugin-dev/notes/2026-09-13-mcn-suite摘除计划任务与mcn-task分组真因.md)

  1. B 处仍会新增同名 mcn-task —— 三种走法各有取舍(合并进一个会打乱各功能独立工作目录;各给独立标题违背 09-12「统一名」;只加按 title 查重仍有目录语义问题)⇒ 需产品决策,未擅自改。
  2. 既有缺陷(非本轮引入):/mcn/api/accounts → 500 unable to open database file。日志该句最早 09-12 23:37:52,今天 21:10(部署前)已累计 81 次。线索:DB_PATH=join(homedir(),".dsh","mcn-plugin.db"),而磁盘上实存的是 <home>/mcn-plugin.db(旧路径少一层 .dsh)。
  3. lib/client.js 线上是混用行尾(CR=5222/LF=5547),本地纯 LF ⇒ 整文件 md5 必变(无语义影响)。

其它

  • 回滚件:/var/lib/dshs/business-plugins/.bak/dsh-plugin-mcn-suite-0.3.9.tgz;升级脚本留档 E://ProgramData//AI技能//aliyun-dsh-server//.workbuddy//tmp//upgrade-mcn-suite.cjs。
  • ⚠️ 该插件目录未被 git 跟踪(上层仓 D://dshworkspace 里是 ?? ./)⇒ 改它没有 git 兜底。
  • 技能归档副本仍未同步:dsh-change-workflow 现在共 2 处未同步(20:5x 那 4 条 + 21:2x 的基线校验条),因锁被 r2-portal-align 占用。

21:0x–21:3x · 会话 r2-portal-align:「系统管理」弹窗按门户 6 功能页重做(business-plugins 0.3.3 → 0.3.4)

触发(用户原话):「怎么设置系统管理里面还是老样子呢,和之前门户那个功能名称、页面样式完全不一样呢,你要不要把之前那个 portal 页面打开看看,我要点开弹窗里面展示的内容是和 portal 点击功能后那种详细的表格和功能」+「弹窗页面需要多大多大,不用做的太小」。

先核事实(不是没出厂):实例 profile 解析 @dsh-local/business-plugins → .dsh-stage/business-plugins-0.3.3.tgz,version = 0.3.3 ⇒ 第 2 轮(视觉)确实已生效。「还是老样子」的真因 = 第 2 轮只改了视觉层,没动信息架构:管理项仍是自造的 5 项只读摘要(用户管理/候选池/运行时/存储/当前实例),而门户是 6 个功能页 ⇒ 名字与表格都对不上。

改造(poc/business-plugins/lib/client.js,1624 → 2218 行):

  • 新增 PA_PAGES(6 页,每页 {icon,title,desc,sub,html(),init($,root)},html ≡ 门户 renderXxx()、init ≡ 门户 initXxx())+ PA_GROUPS(← 门户 renderHome() 的 sec1 服务 / sec2 管理)+ PortalPage(dangerouslySetInnerHTML 注门户原文 + useEffect 跑门户 init)。
  • 三处机械改写:document.getElementById → 局部 $(弹窗子树内查,避免与 dsh 面板同名 id 撞车);fetch → paReq(跨子域 + credentials:include);location.hash 路由 → 弹窗内页签状态。
  • 词典 pa.* 91 → 225 条(zh/en 一一对应,新增断言把关),弹窗内 182 条文案逐字取自门户。
  • CSS:门户页面级组件逐条译为 .pa-*(.card→.pa-box、.btn*→.pa-btn*、.page-head→.pa-phead、.pg-tabs/.pg-tab/.pg-cnt→.pa-tabs/.pa-tab/.pa-tcnt、.dsh-bar/.dot→.pa-dshbar/.pa-dot、.pathbar/.crumb→.pa-pathbar/.pa-crumb、.upload-row/.hint→.pa-row/.pa-hint、.key-row→.pa-keyrow、.wl-*→.pa-wl*、.empty→.pa-empty、.toast→.pa-toast)。
  • 弹窗尺寸:min(1440px,96vw) × min(92vh,1040px)(对齐门户功能页 1440),头固定 = 门户 .page-head(← 返回 + 标题 + 副标题)+ 关闭。
  • ⚠️ 口径变更:推翻档案 82 §一 的「只读子集 + 写操作回跳门户」⇒ 现为就地全量管理(CORS 白名单已含 GET/POST/DELETE/OPTIONS)。

顺带发现(未动手,只报告):

  1. 🐛 门户 web/portal.html 的 readAsBase64() 缺 await(String(new Promise(…)) 恒得 "[object Promise]")⇒ 门户三个上传入口(文件 / 技能 zip / 插件 tgz)实际传的是空数据。弹窗侧(0.3.4)已改为真 Promise,门户侧未动。
  2. ⚠️ 档案 82 自身严重重复:# 82 · R2 管理面就地化 标题 6 次、§一–§八 重复 6 份(1937 行里大半是重复)⇒ 待单独立项去重。
  3. 门户文件夹行没有任何可点提示(无手型/无颜色)⇒ 弹窗补 .pa-dir(否则文件树没法导航)。

出厂与铺发:

  • dsh-local-business-plugins-0.3.4.tgz(39,098 B;包内 4 条目,无 .tgz/无 .bak)。⚠️ 打包前删掉 lib/client.js.bak-* —— .npmignore 不排除 .bak。
  • scp → /opt/dsh/artifacts/business-plugins-0.3.4.tgz → ssh bt-server 'cd /opt/dshs && node scripts/ensure-biz-plugins.cjs --all --restart' ⇒ admin/guest 均 0.3.4,bundles=6;已停 0 个 scope(本来就没跑,下次访问自动拉起)。
  • sha256 三方一致:本机 = artifacts = 两实例 .dsh-stage = 15cbec22e342…;实例内 lib/client.js 含 PA_PAGES.files/plugins/runtime、function PortalPage、pa-box×9 / pa-tabs×3 / pa-dshbar×4。

验收:node scripts/verify-platform-admin-section.mjs 全绿(含新增的「zh/en 键集一一对应」「占位符键一致」「6 功能页同名 + 6 弹窗外壳 + 各页表头/按钮文本」);node --check lib/client.js 通过;docs-audit.py rc=0。 ⚠️ 本轮踩到的验收盲区:功能页正文走 dangerouslySetInnerHTML,class 只存在于字符串里 ⇒ 只看 React 节点的 classes() 扫不到 ⇒ 新增 clsAll()(同时扫节点与内联 HTML)。写这类断言时别再踩。

档案:04-调整方案/82-…md 追加 §十一(含口径变更表、落地清单、两处有意偏离、未动手的三项)。注意该文件本身重复严重(见上)。

21:2x · 同会话续做:官方插件列表拉高(0.3.4 → 0.3.5)

用户:「系统设置 插件管理里面官方插件列表 高度可以增加,距离底部 100PX 就行」。

  • 改前:.pa-wrap + 内联 style="max-height:420px"(我在 §十一 里为规避弹窗高度硬套的固定值)。
  • 改后:新增 .pa-plist{max-height:max(280px, min(calc(92vh - 470px), 580px))}。依据写进 CSS 注释:弹窗高 min(92vh,1040px),表上方固定消耗 ≈470px,表下方还有按钮行 + 结果区 + 内边距 ⇒ 反推使列表底部距弹窗底部约 100px;280px 下限防小屏压扁、580px 上限对应弹窗触顶。
  • ⚠️ 门户原值 calc(100vh - 520px) 是整页布局取值,弹窗内不适用,别照抄。
  • 出厂 business-plugins-0.3.5.tgz(39,544 B)→ scp artifacts → ensure-biz-plugins.cjs --all --restart ⇒ admin/guest 均 0.3.5;sha256 1d29683770b4… 本机 = artifacts = 两实例 .dsh-stage 三方一致;实例内 lib/client.js:654 含 .pa-plist{…}。
  • 验收:verify-platform-admin-section.mjs 全绿(CSS 断言已扩到 .pa-plist);node --check 通过;防假产物自检:源 21:21:41 < 产物 21:21:58 ✅。
  • 档案 82 追加 §11.7。

21:40–22:0x · 会话 user-byok-keys:模型密钥开放给用户自助配置(两层密钥)(档案 85)

需求(用户原话):「能否把配置模型密钥的功能开放给用户自己配置,参考原本 dsh 模型设置功能,用户自己配置模型自己用」+补充「admin 设置的 用户也可以用,和用户设置的区分开就行」。

先取证(关键:数据层本来就是 per-user,卡住的是路由与注入):

  • src/web/server.ts:66-78 改前 = 「统一 KEY 模式」,resolveApiKey = async (_userId) =>(下划线参数名 = 故意忽略),永远注入 admins[0] 那把;注释原文「用户不可自配」。
  • src/web/routes/auth.ts 的 4 个 /api/me/keys* 路由全部 requireAdmin。
  • 但 credential_vault(user_id, key_name, secret_ref, enabled) + getEnabledCredentialKeyRef(userId) 天生 per-user ⇒ 不用新建体系,放开 + 加回落即可。
  • 线上现状(db 实测):只有 admin/deepseek-main/enabled=1;guest 一行都没有 ⇒ guest 一直靠回落 admin 在跑。

改动:

  • server.ts:resolveApiKey(userId) 两级 —— ① 用户自己的启用 key → ② 回落 admin 的「平台共享密钥」(抽 decryptRef() 复用解密 + 损坏兜底)。
  • auth.ts:4 路由 requireAdmin → requireAuth;新增 keySourceOf(userId) / sharedKeyInfo(userId)(含 ownerIsMe)/ refreshAfterKeyChange(userId, role)(admin ⇒ restartAllMains();其他人 ⇒ 只 restartMain(自己))。
  • client.js:新增 MyKeysSection + settings.section id: my-keys, order 103,无条件注册(所有用户可见);词典 +20 个 mk.* 键;复用门户同款 .pa-* 样式。
  • web/portal.html:keys 页语义改名「平台共享密钥(未自配密钥的用户默认使用;仅管理员可改)」+ 空态改写。
  • verify-platform-admin-section.mjs:注册数 2→3(admin)/ 非 admin 2(含 my-keys);新增 mk 分区断言。

部署:平台 tsc → 传 src/web/{server.ts,routes/auth.ts}(转 LF)→ 服务器 npm run build → systemctl restart dshs(两次,均 active);web/portal.html 直传;插件 0.3.7 铺发(sha256 16bb37dbcc13… 本机=artifacts=两实例三方一致)。回滚件 /opt/dsh/backups/{server,auth}.ts.bak-20260913-byok。

验收:npm run verify 全绿;真环境端到端(R4 合规:临时 session 直插、用完即删)——guest GET /api/me/keys 200 + {"keys":[],"effective":"shared","shared":{name:"deepseek-main",owner:"admin",ownerIsMe:false}}(改前应是 403);未登录 401;POST 假 key ⇒ effective 变 own;DELETE ⇒ 回落 shared ✅。清理已确认:临时 session 0 条、vault 只剩 admin 那行。

🔴 本轮踩到的并行冲突(已写进档案 85 §八 + MEMORY.md §七): 我 21:40 抢到锁后开工,21:50 pack 时才发现另一个会话已把版本改成 0.3.6(「内存条读真实配额」,档案 84)并已铺发。后果:① client.js 里两方改动混在一起;② 我 pack 的 0.3.6 是我的内容,而 ensure-biz-plugins.cjs 按文件名判版本 ⇒ 输出「已是 0.3.6 → 跳过」,实例里留的其实是旧内容 ⇒ 同号不同内容。 处置:拒绝同号 ⇒ 提升为 0.3.7 重铺(0.3.6→0.3.7 实测生效);出厂前跑全量 npm run verify(含它挂的 verify-mem-model.mjs)把两方改动一起把关,全绿才发。 教训:抢到锁 ≠ 工作区干净;升版本号前先确认该号没被用过;铺发后必须回读实例内版本 + sha256 三方比对。

文档:新建 04-调整方案/85-模型密钥开放给用户自助配置-两层密钥.md(原子占号 85;84 已被并行会话占用);档案 82 追加 §11.7(列表高度)。四件套全过(audit rc=0 / manifest 含 85 / consistency ✓);镜像已推我改的 3 个文件(82-…、85-…、docs-manifest.json),未碰 skills/dsh-opensource-release/SKILL.md(别人的在途,对账仍报它 1 处不一致)。


22:0x–22:2x · 会话 official-models-open:改用官方「模型」页(不仿制) —— 85 §九 修正

用户追加要求:「参考 dsh 原始的配置密钥页面格式 去开发页面,那里面是可以选模型厂家的」+「界面交互都要和官方的一摸一样」。

判断:「一模一样」只有用本体才能满足 ⇒ 方案从"仿制一个多厂家页"改为直接放开官方 ui-settings-models。

调研(读官方包,只读不改):

  • 包 = @deepseek-ai/dsh-client-ui-settings-models(在官方 dsh 的 node_modules 里);实体是 ProviderEditor(一家一张卡:write-only API key + 折叠的「自定义设置」= baseURL + DeepSeek 模型目录)+ CustomProviderCard(声明官方目录外的厂家:OpenAI 兼容网关,必填 endpoint/protocol/≥1 模型)。
  • 数据走 remote.llm/settings/credentials(dsh 内部通道,非外网)⇒ 补丁注释里"provider 目录在此环境不可用"的旧判断已不成立。
  • 🔴 两处硬约束(决定性):① dsh-credentials-local 头注释写明 inherited process environment (read-only, wins) > $DSH_HOME/.credentials.yaml > … ⇒ env 永远赢;② 它的 write() 有 assertUnshadowed():env 里存在同名 ref 时,用户在模型页保存直接抛错("supplied read-only by the launching environment")⇒ 只要平台注入 env,用户就被锁死不能自配 DeepSeek key。

方案落地:

  1. ensure-role-profile-patch.cjs:不再禁用 ui-settings-models(保留 plugins/inventory/cordis 的禁用),并加"旧块含 models 禁用 ⇒ --force 时升级"的判据。
  2. src/web/server.ts:resolveApiKey(userId) 语义整体换掉 —— 不再注入 env(恒返回 null),改为 spawn 时把「平台共享密钥」预置进 $DSH_HOME/.credentials.yaml 的 refs: 段(新增 ensureRefInCredentials():只在无该 ref 时写 / 只在 version: 1 上插入 / 写前备份 / 写完 chown 给 home 属主 / 认不出的布局宁可不动 / 失败退回 env 保底)。
  3. 撤掉「我的密钥」分区(插件 0.3.8,删组件+注册+20×2 条 mk.* 词典,脚本精确删四处并修相邻逗号)。

🔴🔴 本轮真事故(如实记录):第一版把备份写成 $DSH_HOME/.credentials.yaml.bak-platform(root 属主 600)⇒ dsh 用 chokidar watch 整个 home ⇒ EACCES: permission denied, watch … ⇒ 实例直接退出 + 崩溃循环(attempt 1→5),guest 约 1 分钟不可用。修复:① 立即删该文件止血(实例自愈恢复);② 备份改落 /opt/dsh/backups/;③ 顺手扫了 home 里 root 属主的文件。教训已写进 MEMORY.md §七。

验证(逐条实测):patch 里 ui-settings-models 0 处;用户操作 /api/session/modelCatalog 200;凭据文件被正确预置 refs.DEEPSEEK_API_KEY(原 records: 完整保留、属主=实例 uid);实例进程 env 里 DEEPSEEK_API_KEY 0 条(改前 1 条);实例稳定 0 崩溃;备份落 /opt/dsh/backups/;抹掉 refs 再 spawn ⇒ 又被正确重新预置(写入分支闭环)。临时 session 残留 0、临时脚本已清。

制品:插件 0.3.8 已铺发 admin/guest;后端 src/web/server.ts 已部署 + 服务器 npm run build + 重启;角色补丁已应用。回滚件 /opt/dsh/backups/{server,auth}.ts.bak-20260913-byok。

22:2x · 用户要求「提交项目最新文件」⇒ 已提交并推送

  • 代码仓 eb50ca2(f6d4ad9..eb50ca2 master,已 push):7 个文件全是我的 —— ensure-role-profile-patch.cjs、src/web/{server.ts,routes/auth.ts}、web/portal.html、poc/business-plugins/{package.json,lib/client.js}、scripts/verify-platform-admin-section.mjs。(并行会话的内存改动已由它自己提交为 f6d4ad9,工作区里没有别人的残留。)
  • 文档库 bc3a176(0606f28..bc3a176 main,已 push):只加我改的两个 —— 04-调整方案/82-…md(§十一 + §11.7)、04-调整方案/85-…md(新建,含 §九修正)。
  • ⚠️ 文档库仍留着「别人的」未提交在途,未动:04-调整方案/76-…md、docs-manifest.json、skills/{dsh-change-workflow,dsh-decision-method,dsh-feature-first,dsh-opensource-release}/SKILL.md、skills/dsh-plugin-diagnose/(新增目录)。依据:git show --stat 0606f28 显示别人上次提交只带自己那 1 个文件 ⇒ 惯例 = 只提交自己那份。
  • 步骤:先 git status 分区归属 → git add <逐个文件>(不用 -A)→ git diff --cached --name-only 复核 → commit → push。锁 commit-final 内完成,完工已释放。

22:30–22:5x · 会话 admin-multi-user-svc:admin 跨用户实例管理 + 两处改名(档案 86,插件 0.3.10)

用户三条要求(原话):①「admin 的 系统管理 中的服务管理只管得了自己的,看不到用户的服务,需要都能管理才行」②「这个功能名称 改为实例管理」③「密钥管理 改为模型管理」。

取证:平台所有服务/文件 API 都是 request.user.id(desktop.ts 头注释原文 "one user can never address another user's files")⇒ admin 只能管自己。但底层 UserFs / Spawner 每个方法都以 userId 为首参 ⇒ 卡点只在路由层。

做法:

  • 新开一组 /api/admin/users/:id/{fs/tree,fs/create,fs/upload,dsh/status,dsh/launch,dsh/stop}(新文件 src/web/routes/admin-user-ops.ts,全 requireAdmin + targetOr404)。没给既有路由加 userId 参数 —— 那会把"越界判断"混进普通用户每天走的路径,一处写漏就是任意读他人文件;隔离式新增风险面为 0。
  • 抽共用函数并导出(dsh.ts):alive / dshUrl / launchForUser / statusForUser / sendBreakerOpen,原 /api/dsh/launch、/api/dsh/status 改为调它们(行为不变)—— 避免"两处副本必然漂"。
  • 插件「实例管理」页加**「用户」下拉**(/api/admin/users 填充,默认选自己),切换后文件树/启停/打开全跟着走。
  • 改名同步:插件词典(zh/en)+ 卡片说明 + 副标题(files 副标题改为「按用户查看工作区、启动 / 停止其 DSH 实例」)+ 门户 CRUMB_TITLES 与首页卡片。

🔴 部署踩坑(已写进 MEMORY.md §六):第一次服务器构建报 TS2339: Property 'quotaInfo' does not exist on type 'Spawner'。真因 = quotaInfo 是档案 84 加进 Spawner 的接口成员(已提交进 git),但当初部署档案 84 时漏传了 src/supervisor/spawner.ts(diff 仅那 7 行)⇒ 服务器源码落后于 git。补传后干净通过。

教训:「服务器能跑」≠「服务器源码 == git」;浅克隆 + 逐文件 scp 会静默落后,改动依赖别人已提交的符号时,先 grep 服务器上有没有它。

验证(真环境):admin 读他人文件树 → 200 + 真实 entries;读他人实例状态 → 200 {running:false,…,url:"https://guest.alotbuy.com/"};未登录 → 401。插件 admin/guest 均升 0.3.10,实例内 client.js 含「实例管理/模型管理」3 处、svcUser 切换器 2 处。verify-platform-admin-section.mjs 全绿(新增「实例管理页含用户切换器」断言)。探针 session 残留 0。

编号修正:代码注释里原写「档案 87」,但 86 才是空号 ⇒ 批量改回 86(5 处),并因此升版本 0.3.9 → 0.3.10(同号不同内容是禁忌,见 §七)。 文档:新建 04-调整方案/86-admin跨用户实例管理-两处改名.md(原子占号 86);四件套全过;镜像已推 86-…md + docs-manifest.json。 未提交(§4:用户未要求)—— 在途:src/web/routes/{dsh.ts,admin-user-ops.ts,server.ts}、web/portal.html、poc/business-plugins/{package.json,lib/client.js}、scripts/verify-platform-admin-section.mjs、04-调整方案/86-…md。


21:2x · 查明「功能管理里显示 388 多」= 前端自估模型与平台配额两套表从未对齐

用户问:「实例内存大小是不是调整过了,为什么功能设置中还是显示 388 多」

事实(实测)

项 平台侧(决定真实 MemoryMax) 插件前端(「功能管理」显示的那个数)
基线 BASE_MEM_MB = 160 MEM_BASE_MIB = 285
dsh-univer-office 512 64
dsh-plugin-mcn-suite 128 39
合成规则 clamp(160 + Σ, 384, 1024) 285 + Σ,且把 384 当硬上限判超限
上限 384 只是下限,真实可到 1024 MEM_LIMIT_MIB = 384(写死,档案 58 时代的旧值)

实测 MemoryMax(字节 → MiB):guest(uid 100002,装 univer) 704643072 = 672(=160+512 ✓)|admin(uid 114801,装 mcn-suite) 402653184 = 384(=clamp(160+128)=384,踩下限 ✓)。 heapMbFor():两组都= min(256, 配额−96) = 256。

388 怎么来的:池里恰好 2 个插件(实调 /api/plugins/business 确认),前端表都有值 ⇒ 全勾 = 285+64+39 = 388。而 MIC 侧同一情形 = 160+512+128 = 800。 ⇒ 那条「⚠ 将超出上限」是误报:384 在平台侧早已只是下限。

根因(一句话)

「单实例上限」这个事实被复制到了两处(编排器 instanceMemMb() / 插件 MEM_LIMIT_MIB),R1 把它从写死 384 改成动态 384–1024 时只改了编排器;插件那张成本表(2026-09-13 另一路实测口径)也一直没同步。

附带确认

/api/dsh/status 不返回任何内存字段(只有 running / instance{id,port,status,restarts} / watchdog / breaker / url)⇒ 插件拿不到真实配额,只能自估,这才是它自建表的根本原因。

拟定修法(未执行:平台仓被 r2-portal-align 占锁,按 R9 停手)

  • 主修:/api/dsh/status 增补 memMb(= instanceMemMb(profileDir))与 heapMb —— 编排器已算好,只是透出来,零新增逻辑;前端以真实配额为 100% 基准,读不到才退回估算并明确标注「预估值」。
  • 兜底:即使不读 status,也把前端表/规则对齐平台(base 160 / univer 512 / mcn 128 / clamp 384–1024)。因为前端拿到的 enabled 就是本实例 bundles ⇒ 能算出与平台完全相等的值(不再是估计)。
  • 现状 MEM_BASE_MIB=285 的口径(含 148 个官方插件)与平台 BASE_MEM_MB=160(只算 dsh 基座)是两种不同量法,不能简单取一个覆盖另一个 —— 这也是为什么首选「读真实值」而不是「把表抄过去」。

21:2x–21:4x 会话 48a9c14c:检查最新方案状态(全程只读)

锁:全局锁被 mem-model-fix(09-13 21:23 起)持有 → 在「统一内存配额口径(/api/dsh/status 暴露真实值 + 前端读真值)」→ 按 §6/R9 未抢锁、未改任何文件。

★一次我自己的误判与当场纠正(值得记的教训)

  • 我实测发现 guest bundles 不含 dsh-plugin-mcn-suite(且 MemoryMax 仍是 672 而非装入后的 800),而 03-路线图 §二 记着「T03 …guest 启用已成功(任务 c20739a6dbe8975c:success/restarted=true;配额自动 672→800)」⇒ 我据此判定为「账实不符」,并去追查 21:22 那次 ensure-biz-plugins.cjs --all 重写 profile 是否把它冲掉了。
  • 用户当场澄清:「那是我手动禁用的」 ⇒ 既不是回归、也不是账实不符,是用户在产品面的主动操作(实例「功能管理」启用/禁用本就是用户自助面)。
  • 教训(写给自己):遇到「文档说 A、实测说 B」,可能原因有三层 —— ① 文档滞后 ② 会话事故/回归 ③ 用户主动操作(启用/禁用、候选池上下架、删文件…)。先想到第③层,再下"账实/回归"的结论;否则会把人家的正常操作报成系统故障。平台有大量用户自助面,这类"看起来不该变的状态"本就随时可被用户改。

线上实测快照(2026-09-13 21:3x,只读)

项 admin(uid 114801 / cce6d1cd…) guest(uid 100002 / 4092b965…)
bundles(6 个) dsh-base · dsh-web-app · portal-entry · business-plugins · workspace-scoped-picker · dsh-plugin-mcn-suite dsh-base · dsh-web-app · business-plugins · portal-entry · workspace-scoped-picker · dsh-univer-office
business-plugins 0.3.5 0.3.5
portal-entry 0.5.2 0.5.2
其它 dsh-plugin-mcn-suite = 0.3.10(21:10 池内版) dsh-univer-office = 0.2.27
MemoryMax 402,653,184 = 384 MiB(=clamp(160+128)) 704,643,072 = 672 MiB(=160+512)
  • R3 标识迁移已实际落地:/opt/dshs、/var/lib/dshs、/etc/dshs.env、dshs.service 全部存在且为现行(实例数据根 = /var/lib/dshs/users/<uuid>/;不是 /opt/dsh-server-login 与 /var/lib/dsh-server-login,那两个已不存在)。
  • 候选池 /var/lib/dshs/business-plugins/:dsh-plugin-mcn-suite.tgz(0.3.10,1,944,704 B @21:10)、dsh-univer-office.tgz(0.2.27,42,430,328 B @19:44)。
  • .dsh-stage/:两实例都已铺 dsh-plugin-mcn-suite.tgz(0.3.10 @21:10)与 business-plugins-0.3.5.tgz(@21:22)⇒ 包铺到位 ≠ 已启用,启用态看 bundles。
  • 服务器 op-lock 空闲;dshs.service active;两实例 scope 均 running。

待执行清单现状

  • 交接单 §一:只剩 T01(⏳ 待执行,决策点 1 已定 = A 扩展 business-plugins,无阻塞)。T02–T05 全部归档。
  • 03-路线图 §二:档案 81 重构总纲的 R2/R4/R5(R0/R1/R3 已完成;R2 代码已完、待收尾)|档案 78 遗留 L1(需维护窗口)|档案 27 暂缓(3 处 P0)|档案内挂起一批(57 / 59 / 66 / 68 / 38b / 15 / 32)|P3 门户下载入口|触发式(dsh 升级回归 / 会话 GC)。
  • 本机 git:末次提交 f1a0da1(档案 82 §10.7);未提交 8 项 = 04-76、04-82、docs-manifest.json、4 个技能 SKILL.md + 未跟踪 skills/dsh-plugin-diagnose/。

21:2x–21:4x · 已修:「388 MiB」内存误报(档案 84)

用户授权执行我的方案。锁当时被 r2-plist-height 占着 ⇒ 等它释放后抢到(未抢锁、只等)。

改了什么

平台侧(把已算好的真值透出来,零新增计算): src/supervisor/spawner.ts 加可选 quotaInfo?(照 breakerInfo 模式)| src/supervisor/orchestrator.ts 实现 quotaInfo()(走同一个 instanceMemMb()/heapMbFor())| src/web/routes/dsh.ts 的 /api/dsh/status 返回 quota:{memMb,heapMb}。

插件侧(0.3.5 → 0.3.6):常量/规则逐项对齐编排器(160/384/1024/96 + univer 512 + mcn 128),新增 rawMiB()/quotaOf()/heapMiBFor() 同构;load() 并行读 status 并作为事实行显示「实际配额 · V8 堆」;超限判据改「原始需求 > 硬顶 1024」(旧版拿 clamp 后的值判 ⇒ 既误报又永不触发);未进成本表的插件不再显示 ≈0 MiB 徽章;文案 上限→硬顶。

钉死(本次关键):新增 scripts/verify-mem-model.mjs —— 解析两侧常量/成本表/clamp 算式/接线点逐项交叉断言,接进 npm run verify;反向验证过(把插件基线改回 285 ⇒ 报错 exit 1,还原即全绿)。根因是「同一事实两处副本」,所以修法必须包含「让它不能再漂」。

验证

build 零报错 | verify 全绿(含新增 15 项断言)| 服务端 /opt/dshs/lib/... 两文件 md5 与本机编译产物一致(差异已核:orchestrator 正好我的 19 行;dsh.js 我的 3 行 + 1 行他人旧注释)| GET /api/dsh/status → quota:{"memMb":384,"heapMb":256} 与实测 MemoryMax=402653184 吻合 | 两 profile 实装 0.3.6、client.js md5 86bc8a46… 与本地产物一致、旧符号 MEM_LIMIT_MIB 0 处 | 下发 bundle(rev=a8eb3ecf1edb)含新模型。 修复后界面应为:当前 384 MiB → 勾选后 800 MiB + 实际配额 384 MiB · V8 堆 256 MiB,状态余量充足,无红色误报。

🔴 并发撞车(本次最该记住的)

poc/business-plugins/lib/client.js 在我作业期间被并行会话连续改了两轮(0.3.4「系统管理改为门户 6 功能页」、0.3.5「插件列表高度」,0.3.5 已部署)。因每次 Edit 前都重读文件,我的改动叠在其上、未覆盖;但我以为版本是 0.3.3、实际已是 0.3.5 ⇒ 第一次 npm pack 打出「内容是别人 0.3.5 + 我的修改、版本号仍写 0.3.5」的同名假产物(已删),改判 0.3.6。 ⇒ 新纪律:动手前先 git status + 查最新 tgz 版本号,版本取「当前值 +1」,不要用记忆里的值。

提交与收尾

代码仓 f6d4ad9 → push master(只提交我的 5 个文件:3 个 src + 新脚本 + 根 package.json);档案 84-实例内存配额口径统一-消灭388误报.md,文档库 0606f28 → push main,已 tar 管到 /opt/dsh/docs 且双端 md5 093346e1… 一致;四件套 audit rc=0 / consistency ✓;锁已释放。 ⚠️ poc/business-plugins/lib/client.js 与 poc/business-plugins/package.json 刻意未提交 —— 里面有并行会话已上线但未 commit 的 0.3.4/0.3.5,留给他们的批次一起提交;同时这也是一处风险:这 3000+ 行改动目前只在工作树里,没有 git 兜底。


22:2x · 普通用户权限审计(用户要求确认) + 修回一次「整包覆盖」事故

一、权限审计结论:没有被放开(四层证据)

  1. 静态:我 21:4x 的提交 f6d4ad9 共 5 文件 / +128 −1,新增行里没有任何鉴权判断;唯一路由改动是加 quota 一个只读字段,且取的是 quotaInfo(request.user!.id) —— 按调用者自己,没有可传的 userId 参数。
  2. 动态实测(临时会话,用完即删):
    身份 /api/dsh/status /api/plugins/business /api/admin/{users,storage,domains,runtime}
    无 cookie 401 401 401
    guest(role=active) 200(只有自己的 quota) 403 403(全家族)
    admin 200 200 200
  3. quota 隔离:admin {memMb:384} / guest {memMb:672} —— 各是自己实例的值,无越权读取。
  4. 鉴权中间件未被改动:那次 22:16 的整包覆盖虽然也覆盖了 lib/web/middleware/authn.js 等,但与本机 HEAD 构建 md5 逐字节一致(authn / fs-guard / rate-limit / security-scan 四个都是),即覆盖的是同一份已审鉴权代码。
  5. 实例内 UI 门禁仍在(并行会话重写过「系统管理」分区,特意复查):verify-platform-admin-section.mjs 的 ✓ 非 admin 视角下只注册 1 个分区(功能管理;不含系统管理) 仍通过;guest 实装的 client.js 里 role !== "admin" 门禁 3 处、/api/auth/me 判角色 3 处均在。

二、🔴 事故:我的内存修复被「整包 lib/ 覆盖」抹掉(已修回)

现象:22:2x 复验时 /api/dsh/status 不再有 quota 字段(keys 退回 5 个)。 诊断:服务器 /opt/dshs/lib/supervisor/orchestrator.js 与 lib/web/routes/dsh.js 的 md5 恰好等于我 21:33 备份的旧版 md5,mtime = 22:16,lib/** 有一整批文件同时在 22:16 被改、服务 22:16:32 重启 ⇒ 某个并行会话做了一次「整个 lib/ 全量覆盖」,用的是不含我 21:4x 提交的陈旧构建。 处置:锁空闲 ⇒ 抢锁 → 备份 22:16 版到 /opt/dshs/.bak-20260913-2216/ → 重传 2 文件(md5 一致)→ 重启 → 复验 admin 384 / guest 672 回来 → 释放锁。 残余影响很小:插件 0.3.6+ 有兜底 —— 读不到 quota 时用 quotaOf(rawNow) 本地算(表已对齐且被 verify-mem-model.mjs 钉死),数值仍正确,只是事实行文案降级成「估算值」。

三、新增纪律(已写进 dsh-change-workflow)

全量部署 lib/ 之前必须先在 HEAD 上 npm run build —— 否则会把别人的已提交改动整体回退(本次:整批文件统一 mtime + 服务重启时间就是指纹)。 判据:grep -c <你上次加的符号> /opt/dshs/lib/... 或直接 md5 对账;部署后立刻复验上次刚验收过的东西(本次正是靠这个发现的)。 另:「已部署」≠「已提交」(并行会话 0.3.5 已上线却未 commit),所以不能拿服务器状态当版本事实源。

四、附带观察

并行会话的插件已推进到 0.3.8(0.3.5→0.3.6→0.3.7→0.3.8),两 profile 均 0.3.8;我的内存修复仍在其中(MEM_BASE_MIB=160 / MEM_MAX_MIB=1024 / 成本表 512+128 / 读 status 6 处 / 旧符号 0 处)⇒ 插件侧没有被他们改掉。


23:20 「模型设置」lane 交接单 —— 递单受阻(规划会话)

任务:为用户 09-13 22:5x 那轮「平台自建模型设置页」写交接单(把后端已改部分登记,防新会话重复劳动)。

做了什么(全部只读 + 非受保护路径写入):

  1. 清掉「官方页为何不可用」的落盘错位:用户以为该结论在档案 86,实际 86 是「admin 跨用户实例管理 + 两处改名」,全文没有该内容;真结论只存在于 ensure-role-profile-patch.cjs 的工作树注释(DISABLE_MODELS_BLOCK / 新增 ADMIN_MODELS_BLOCK):官方模型页要 Host settings 镜像,isLoopback = transport.ownsHost || pageLocation===undefined || isLoopbackHostname(page),平台是远程域名访问 ⇒ 三条皆不成立 ⇒ persistence 降 memory ⇒ 页面必报「加载提供方目录失败」。⇒ 代码注释里的「档案 86 §九」是笔误,应为 87。
  2. 摸清后端缺口(git diff + grep):adapter.ts 新增/改了 5 项接口(listEnabledCredentialKeys / toggleCredentialKey / getSharedModelEnabled / setSharedModelEnabled / setCredentialKey 签名 +baseUrl,models);repo.ts 已实现前两项 + 签名,缺 getSharedModelEnabled/setSharedModelEnabled(grep = 0);sqlite.ts/pg.ts 一项未动(mtime 停在 15:39)⇒ tsc 必报「类未实现接口」。
  3. 挖出一处新口径引入的正确性缺陷(本单最重要):getEnabledCredentialKeyRef 是 SELECT secret_ref … WHERE user_id=? AND enabled=1,无 ORDER BY;老逻辑靠「写入时 SET enabled=0 全关」保证唯一,而本轮已删掉互斥(repo.ts:254-256 注释「不再互斥」)⇒ 多条目启用后任取一条,keySourceOf()(auth.ts:144,147)与 resolveApiKey()(server.ts:145)会飘。必须重定义为「已启用的内置 DeepSeek 条目」。
  4. 产出交接单草案:E:\ProgramData\AI技能\aliyun-dsh-server\.workbuddy\draft\T06-模型设置页-多厂家条目可开关.md(8 段齐全,含 4 个待补项 A/B/C 与唯一未取证项 = 自定义厂家的 route/protocol/models 命名规则)。
  5. 压缩本库记忆:MEMORY.md 16,494 B → 10,201 B(已超注入上限被截断,按「只记会变的状态」重写)。

为什么没落盘(关键):--claim-exec 抢锁失败 —— 占用者 rebuild-my-keys,开始 09-13 22:57,未声明单号;而 src/db/{adapter,repo,schema}.ts 的 mtime = 23:11(抢锁之后仍在写)⇒ 同一 lane 的活会话。按 R9 + 交接单/README.md §一:抢不到 = 停手 + 报告用户,⛔ 不得删锁/接管 ⇒ 草案只落非受保护路径(.workbuddy/ 不在两处受保护根内 ⇒ 无需锁)。

待用户处置:① 让原会话自己收尾(上下文最全,推荐)或 ② 释放锁后由新会话落盘 交接单/T06-…md + 回填 §一 + 跑四件套。