Files
dsh_ai1net_server/.workbuddy/memory/2026-09-下10.md
T

298 lines
47 KiB
Markdown
Raw Normal View History

# 工作日志 · 2026-09(第 11 片)
> ⚠️ **本目录日志已按【月】分片**(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-13.md
> **上一片**:`2026-09-下9.md` **下一片**:`2026-09-下11.md`
---
## 22:3x 用户截图「仍是列表」→ 定位(**符合预期**:实例比新包早启动 17 分钟)
- 用户 22:34 截图「功能管理」仍是列表(但**数据已是新的**:`dsh-plugin-mcn-suite v0.3.2` ✓)。
- **定位三条**:① 磁盘包 = **0.2.6**、含 `gridTemplateColumns` ✓(新代码在位)② admin 实例当时是 **22:12 启动的那个**(用户试装那次),**早于我 22:29 部署 17 分钟** ⇒ 实例内存里加载的是**旧 bundle** ✓ ③ 复核时 admin/guest 两个实例**均已 inactive**(已回收)。
- **机制确认**:client bundle 由**实例进程**提供 —— `/plugins/??<pkgs>&rev=<hash>` 是**实例侧**路由(从平台侧请求同一路径返回 **404 Route not found**)⇒ **改 client bundle 必须重启实例才生效**。
- **下一步**:用户再访问一次实例(**建议硬刷新 Ctrl+Shift+R**,防浏览器缓存)→ 实例冷启动即加载 0.2.6 → 可见卡片;顺便可在卡片里启用 mcn-suite 0.3.2。
## 22:4x 「还是列表」深挖 → 结论:**没改错地方**(是实例/缓存时序)
- 用户质疑「是不是改错地方了」→ 全面核对三条证据:
① **全盘扫同名 `client.js`**:**只有 0.2.6 那份含卡片代码**(两个用户 profile 的 `node_modules/.pnpm/@dsh-local+business-plugins@file+..+..+.dsh-stage+business-plugins-0.2.6.tgz/.../lib/client.js` 命中 1);0.1.0–0.2.5 的 8 份历史副本全部命中 0。
② `readlink -f` 确认 profile 的 `node_modules/@dsh-local/business-plugins` **软链到的正是 0.2.6 那份** ⇒ **实例读的就是新代码** ✓
③ ⚠️ **易误判点(记下来)**:服务器 `/opt/dshs/poc/business-plugins/lib/client.js` 是**旧**的 —— 那只是**源码副本**,**不是实例读取路径**;去那里 grep 会误判成"没改"。
- **实例生命周期核对**:`POST https://alotbuy.com/api/dsh/enter`(注意:`/api/dsh/*` 在**门户域**,实例子域没有)→ `{"status":"running","port":44193}`;实际 scope = **`dsh-114801-22f3c08d.scope`(新 id)**,旧 id `0c033dbe` 已停 ⇒ **当前实例加载的就是 0.2.6**。
- **未完成**:`/plugins/??<pkgs>&rev=` 的 URL 形态没试对(单包 + `&token=` 组合均 404)⇒ **没能直接抓到「HTTP 返回新版」**;结论建立在「磁盘命中 + 实例新进程」两条证据上。
- **下一步**:请用户用**无痕窗口**访问(排除浏览器缓存)。
## 22:5x **决定性验证**:位置对 ✓ 服务端对 ✓ ⇒ 是**浏览器缓存**(且发现平台 `rev` 缺陷)
- 按项目约定用 `-L --compressed -c/-b jar -H "Accept: text/html"` 取到实例页 HTML(10068 B)→ 抠出真实 bundle URL(HTML 里 `&` 被转义成 `&amp;`,**还原后请求**):
**`HTTP=200 size=3,792,974`**、**`gridTemplateColumns` 命中 1**(新版卡片代码在内)、**「功能管理」命中 2**(确认就是该 section)。
- ⇒ **三条证据闭合**:① **位置没错** —— 就是「设置 → 功能管理」那个 bundle(`@dsh-local/business-plugins`)② **服务端返回的是新版** ③ **磁盘也是新版**(唯一有效副本,软链到 `.pnpm/…0.2.6…`)。
- ⇒ **用户看到旧版 = 浏览器缓存**(不是改错地方、不是部署失败)。
- ⚠️ **发现平台缺陷(值得修)**:资源 URL 的 **`rev` 参数没随包更新变** —— 0.2.5→0.2.6 之后仍是 **`rev=03857f16ea9c`**(与 22:33 日志完全一致)⇒ 浏览器 URL 不变、**有理由继续命中共缓存**。**建议**:`rev` 应基于**包内容 / 版本**计算(当前依据不明)。
- 下一步:用户用**无痕窗口**访问(排除缓存)。
## 23:0x `rev` 深挖结论:**服务端给的是新版;是浏览器缓存**;`rev` 在官方包、平台修不了
- **rev 的来源**(dsh 官方包,只读):`dsh-client-modules/lib/index.js` → `rev = revision ?? framedHash("combo", [sourceBytes, sourceMap])`;注释「sha1 content hash shortened to 12 hex chars」「Versioned code is immutable; mismatched revisions are **rejected instead of serving newer bytes**」。⇒ **rev 由 dsh 官方生成,平台不得改(R2)**。
- **排除的两个嫌疑**:① nginx 的 `expires 30d` **只作用于 `location ~* ^/assets/`**(`/plugins/` 走 `location /`,无缓存头)② `profiles/web/.dsh-module-fallback` 是**空目录**(只有个空 node_modules);`.dsh-biz-plugins.log` 只记 `apply pid=2` 时间戳,非加载日志。
- **决定性事实**:我按实例页 HTML 里的真实 URL 请求该 bundle(还原 `&amp;`)→ **HTTP 200 / 3,792,974 B / 「功能管理」命中 2 / `gridTemplateColumns` 命中 1** ⇒ **服务端返回的就是新版**。
- ⇒ **根因 = 浏览器缓存**:页面 URL 的 `rev` 仍是 `03857f16ea9c`(与 22:33 一致)→ 浏览器认为缓存有效 → **不重新请求**。这也解释了"硬刷新前仍是旧的"。
- **提给用户的两个选项**:A(0 成本、推荐)无痕窗口 / 清站点数据 → 立刻可见;B(治本、较重)平台代理层给 `/plugins/` 加 `Cache-Control: no-cache` → 以后 bundle 更新自动生效,**代价 = 改平台代码 + build + 重启 `dshs`(全体在线用户断约 10 秒)**。
## 23:0x **治本完成**:平台加 `Cache-Control: no-cache` + 机制固化到 `06-工作台UI规范 §7`
- **用户要求**:「需要治本 所有UI修改相关的任务都要使用这个机制,不然太难排查了」。
- **① 平台侧实现**:`src/supervisor/proxy.ts` 响应头组装处新增 —— 对实例 `/plugins/`(与 `/assets/`)统一加 **`Cache-Control: no-cache`**(允许缓存但**每次回源校验**)。**已 build + 部署 + 重启 `dshs`**(23:04:08;全站断约 10 秒,用户已同意治本)。
- **② 验证**:`/plugins/…` 响应头**确带 `cache-control: no-cache`** ✓(对照 `/assets/` 被 nginx `expires 30d` 覆盖成 `max-age=2678400` —— 但 `/assets/` 是 dsh 构建产物、**文件名带 hash**,30d 合理,**无需处理**)。
- **③ 决定性验证**:实例重启后抓实例页 HTML → **`rev` 由 `03857f16ea9c` 变为 `fa213c11e8aa`** ⇒ 内容确实换新、URL 随之变化 ⇒ **浏览器会自动重新拉,用户无需清缓存/换无痕**。
- **④ 机制固化**(用户明确要求):`06-工作台UI规范.md` 新增 **§7「UI 改动的生效链路与验收」**(生效链路四关 / 平台侧机制 / 三段式验收 / **四个常见误判**);项目根 `CODEBUDDY.md §2` 加对应指针。
- 备份:服务器 `proxy.ts` / `proxy.js` 的 `.bak-cachefix-2305`。现场:两把锁已释放、临时脚本已清、三件套 rc=0。
## 23:2x admin 启用插件失败 → 根因「**插件漏声明 xlsx 依赖**」→ 修 0.3.3 并**真实验证通过**
- **现象**:用户「用admin账号测试启动插件还是有问题」。
- **取证(journalctl 原文)**:`failed to import loader entry mcn-suite (dsh-plugin-mcn-suite): **Cannot find package "xlsx**` imported from `…/lib/host/mcn/imports.js` → 实例 `exitCode: 1` + `[crash-restart]` ⇒ **崩溃重启**(与档案 70 anysearch 同型:**插件加载失败 → 全或无 → 实例起不来**)。
- **根因**:`imports.js` 首行 `import XLSX from "xlsx"`,但整合包 `package.json` **未声明该依赖**(dependencies 只有 react/react-dom)。**全包 import 扫描确认:外部依赖仅缺 `xlsx`**(react/react-dom 已声明;`@deepseek-ai/dsh-llm` 由平台提供)。
- **修法**:`dependencies` 补 `"xlsx": "^0.18.5"`;版本 **0.3.2 → 0.3.3**;重打包 → 上传 **HTTP 200 / replaced:true**。
- **止损**:admin 的 `bundles`/`dependencies` 摘掉坏的 mcn-suite(第一次 python 因 ssh 引号失败未生效 → 改 heredoc 脚本后成功)⇒ 实例不再崩。
- **★真实验证(用户已被坑两次,故先验)**:装上 0.3.3 + 加进 bundles + 拉起实例 → 日志 **`[mcn-suite] loaded` / `host 面已注册: mcn, mcn-schedule, voxemw-cloud` / `skills ensured: 装 [email protected](新装)`**,**无 crash-restart、无 failed to import** ⇒ **通过** ✓;验后**恢复原状**(bundles 回 5 个)。
- **遗留小瑕疵**:插件内部 `console.log` 的版本字符串仍写 `v0.3.0`(不影响功能)。
- ⚠️ **教训**:ssh 内联 `python3 -c` 的引号两次把改动吞掉(第一次止损静默失败)→ **凡带引号的远程改动一律「本地写脚本 → scp → 执行」**(档案 70 已记过,本次又犯)。
## 23:2x 用户问「为什么要删除」→ 认错并修复:`rm -rf node_modules/<pkg>` 留下孤儿包
- **我的操作**:试装验证 0.3.3 后为「恢复原状」,清理时用了 `pnpm remove` + **`rm -rf` 兜底**。
- **错在哪**:`rm -rf node_modules/dsh-plugin-mcn-suite` **只删包目录、不删依赖树** ⇒ 留下 **`xlsx` 的 7 个子依赖**(adler-32 / cfb / codepage / crc-32 / frac / ssf / wmf)在顶层 node_modules,与 `.modules.yaml` 记录**不一致**。不致命(实例启动正常)但状态脏。
- **为什么会加 `rm -rf` 兜底**:之前 `pnpm remove` 报 `CANNOT_REMOVE_MISSING`(那时 package.json 已被恢复、里面没这条依赖)⇒ 我误以为「remove 用不了」而手删。**正确动作是 `pnpm install`**(按 package.json 自愈,pnpm 自己 prune)。
- **修复**:以 admin 身份跑 `pnpm install` → **`Packages: -15`**(自动清掉 15 个孤儿)→ 顶层 node_modules 只剩 `@dsh-local` ✓、bundles 5 个 ✓。
- **顺带确认**:admin 实例**启动正常**(`dsh-114801-cdb5d7c0.scope active` + `dsh web: http://127.0.0.1:46221/`),下午那次崩溃是 xlsx 缺依赖所致,与本次删除无关。另:23:23:43 有一次平台服务重启(**用户手动**点的,成功)。
- **已固化**:项目根 `CODEBUDDY.md §7` 补「**插件安装 / 卸载 / 清理一律走 pnpm;⛔ 禁 `rm -rf node_modules/<pkg>`**」+ 本次实测后果与正确动作。
## 23:3x 「Failed to load plugins」→ 根因「整合包没按包名注册自己」;修 client 注册 + host 同步 + 工作空间统一 mcn-task(0.3.5)
- **用户给的报错**:`bundle …dsh-plugin-mcn-suite/client.js… **loaded without registering "dsh-plugin-mcn-suite" via __ModuleLoader__.load**`。
- **根因①(致命)**:整合包的 `client.js` 是把 6 个原包 client **原样拼接**的,那些 `window.__ModuleLoader__.load({id:"dsh-plugin-mcn",…})` 注册的是**各自的原包 id** ⇒ **本包 id 从未注册**。而 dsh 按**包名**找 ⇒ 直接拒载。
- **修法①**:改 `scripts/build-mcn-suite.cjs` 加**整合包注册层** —— 6 处 load 改写为 `window.__mcnSuiteCollect({...})`(收集),再追加本包的 `load({id:"dsh-plugin-mcn-suite", factory})`,其 factory **驱动 6 个子包 factory** 并把 `inject` 取并集(硬编码兜底 runtime+sidebar)。自检:`整合包 load = 1 / 子包 collect = 6 / 注册 id = 7` ✓
- **根因②**:整合包的 **host 面是 T03 时手工复制的旧副本**(停在 10:36,原包已到 23:33)⇒ 改原包 host 代码**不会进包**(本次白打一次包)。**修法②**:build 脚本增加 `[build-host]` 段,每次从原包同步 `lib/*.js` 到 `host/{mcn,schedule,voxemw}/`,**排除 `client.js` 与 `*.bak*`**。
- **顺带完成用户新需求**:**「MCN 工作台所有功能调用 AI 会话的工作空间统一叫 `mcn-task`」** —— 改 `lib/index.js` **13 处**:6 处 `join(resolveCreativeCwd(), "<旧名>")`→`"mcn-task"`、5 处 `reg.create(dir, …)`、4 处 `setTitle(…)`、1 处 `cwd:`。
- **版本 0.3.5**:三重验证 —— ① `client.js` 含本包注册 =1 ✓ ② `host/mcn/index.js` 含 `mcn-task` =14 ✓ ③ 依赖含 `xlsx` =1 ✓;上传 HTTP 200;**更新 admin 已装**(pnpm add 指向候选池新 tgz)→ 0.3.5 ✓;**重启 admin 实例**验证 → `[mcn-suite] loaded` + 3 个 host 面注册 + `skills ensured` ✓ **无 crash、无 failed to import**。
- **待用户确认**:浏览器侧 client 面(刷新后不应再报 Failed to load plugins)。
- 备注:插件内部 `console.log` 仍打印 `v0.3.0`(纯文案,未随包版本更新)。
## 23:4x 第二轮报错「pending (waiting for services: …)」→ 根因「inject 写成了数组,应为函数」→ 0.3.6
- **用户给的第二个报错**:`web boot: 1 entry did not activate` + `dsh-plugin-mcn-suite: **pending (waiting for services: @deepseek-ai/dsh-client-runtime, @deepseek-ai/dsh-client-ui-sidebar)**`。
- **根因**:dsh client bundle 的 **`inject` 是函数**(原包写法 `inject: () => ({})`,返回「要注入的服务映射」);我在整合包注册层里**硬编码成了数组** `["@deepseek-ai/dsh-client-runtime", …]` ⇒ dsh 把它理解成「在等这些服务」⇒ 条目**永远 pending**。
- **修法**:改为 `inject: function () { … }` —— 遍历子包,`typeof m.inject === "function" ? m.inject() : m.inject` 取并集后返回**对象**;并去掉硬编码常量。
- **0.3.6**:上传 HTTP 200 / `replaced:true` / `compat: ok`;更新 admin → **0.3.6**(`inject 是函数`=1、`mcn-task`=14 ✓);重启实例 → `[mcn-suite] loaded` + 3 个 host 面注册 + `dsh web:` ✓、**无 failed to import**。
- **待用户确认**:刷新后 client 面(应不再 pending)。
- ⚠️ **教训(值得进规则)**:给 dsh 写 client bundle 时,**`inject` 必须是函数**(`() => ({})`),**不是数组**;数组会被当成「等待的服务名」而永久 pending —— 这个坑**服务端日志完全看不出来**,只在浏览器里暴露。
## 23:5x 「工作区统一 mcn-task」第二批:修 2 处 Windows 路径 fallback(0.3.7 / 0.3.8)
- **用户追问①**:「为什么还是会创建 解析任务 / 计划任务 两个分组」。
- **根因(第二批)**:**两个包各有一份独立的**「读 workspace.json 取根工作区」实现,**两处都读错路径 + fallback 都是 `D://dshworkspace`**:
· `dsh-plugin-mcn/lib/config.js` 的 `resolveCreativeCwd()`
· `dsh-plugin-mcn-schedule/lib/index.js` 的 `resolveWorkspaceRoot()`
两者都用 `homedir()/.dsh/storages/workspace.json`(**实际在 `$DSH_HOME/storages/`**)⇒ 永远读不到 ⇒ 走 fallback `"D://dshworkspace"` ⇒ **Linux 下被当成目录名**。
- **用户追问②**:「工作区目录里面有个 d://dsworkspace 明显不对 这个是之前在windows上的路径」—— 正是同一根因(用户自己看出来 ✓)。
- **修法**:两处都改为 **多路径尝试**(`$DSH_HOME/storages/` → `homedir()/storages/` → `homedir()/.dsh/storages/`)+ **fallback 改为 `homedir()`**(实例内 HOME 已由平台指向 ws)。版本 0.3.7(改 config)→ 0.3.8(补 schedule)。
- **进展**:0.3.8 部署后 **`mcn-task → ws/mcnworkspace/mcn-task`** ✓ **新路径正确出现**。
- **残留**:`计划任务 → ws/D://dshworkspace/计划任务`、`mcn-task → ws/D://dshworkspace/mcn-task` **仍在实例启动时被重建** ⇒ **源码里已搜不到 `D://dshworkspace`**(两处都改完 ✓)⇒ 判定为**旧 workspace 记录/目录被恢复**(非源码问题)→ 待清 `ws/` 下的带盘符目录 + 重清注册表。
- 期间踩坑:`pnpm add file:` 首次上传 HTTP 400(内联命令引号问题)→ 改脚本后 HTTP 200;**再次验证「带引号的远程操作必须写脚本」**。
## 23:5x 工作区统一 mcn-task —— 收尾状态(**新路径已生效,旧记录残留未清净**)
- **✅ 已生效**:`mcn-task → ws/mcnworkspace/mcn-task`(新代码路径正确 ✓);`ws/D://dshworkspace` 目录已删、`.node-compile-cache` 与 `ws/.cache` 已清、实例已重启。
- **✗ 残留**:注册表里仍有 `mcn-task → ws/D://dshworkspace/mcn-task`(旧路径),以及 `计划任务` 分组。
- **关键判据(本轮最重要的一条)**:清理动作是在**拉起实例之前**执行的,当时输出 `剩余: [mcnworkspace, mcntimo, mcn-task, mcn-task]` ⇒ **那两条旧的不是在启动时被重建的**,而是**历史记录本身没被我的过滤条件删掉**(过滤用了 `"D:////////dshworkspace" not in path`,在 heredoc 里经多层转义后没能匹配上)。
- **⇒ 结论修正**:**不是「实例用旧代码重建」,而是「旧注册记录没清干净」**。代码侧两处 fallback 已修完(源码 grep `D://dshworkspace` 已为空 ✓)。
- **下一步(明确)**:按 **path 精确匹配**(不做字符串转义,改从 JSON 解析后 `endswith`/`in` 判断并打印命中项)重清一次;**或**最直接:**用户在实例里手动删掉「计划任务」分组**(UI 一秒)。
- **今日代价提示**:这一轮为追这个 bug 连续打了 0.3.5→0.3.8 四个包;根因是「两处独立的 Windows 路径 fallback + 旧注册记录」。
## 23:5x 「插件已启用但看不到 MCN 工作台入口」→ 定位到两处,**暂停等明天**
- **已确认正常**:host 面 `[mcn-suite] loaded` + 3 个 host 面注册;侧边栏入口是 **client 面**注册的(`ctx.slots.inject("sidebar.footer.action", …)`,见 client.js 3042/3053 行)。
- **⚠️ 发现缺陷①(我引入)**:生成的 `client.js` 里有**两份注册层** —— 第 5522 行还是旧的 `var inject = ["@deepseek-ai/…"]`(数组),文件末尾才是新的 `inject: function()`。原因:构建脚本的替换只匹配**行首** `window.__ModuleLoader__.load({`,把「上一轮生成时已有的旧注册层」也一并当成子包收集了 ⇒ **新层驱动旧层、旧层再驱动 6 个子包**(重复注册)。**修法**:生成前先剔除文件里已有的注册层标记(或改为「不追加、就地改写」),并在自检里断言「注册层恰好 1 份」。
- **⚠️ 疑问②(待浏览器验证)**:`inject` 返回 `{}`(原包写法就是 `inject: () => ({})`)是否足以让 `ctx.slots` 可用 —— 若不够,侧边栏注册会失败、入口不显示。**只有浏览器控制台能判定**(host 日志看不到)。
- **下一步(明天)**:① 修构建脚本的注册层重复 → 重打包;② 让用户**硬刷新**看控制台是否仍有 `Failed to load plugins` / `pending`;③ 若 inject 语义不足,改为**按 T03 单子原设计**在 `package.json` 的 `dsh.client.inject` 里声明 `[runtime, sidebar]`(当前已有 ✓)并核对 dsh 对 client `inject` 的期望形态。
- **暂停理由**:已连续打 0.3.5→0.3.8 四个包、跨 5 小时;继续在低质量状态下改会引入新问题(用户偏好里明确「方案规划与落地执行分开、状态不好不硬推」)。
## 23:47 恢复机制全景梳理 + 现场发现(会话:恢复机制展示)
- 用户问「能否检测用户是否回到 dsh 页面 / 自动恢复进程状态,体验不自然」→ 逐场景梳理线上恢复机制(代码 + 生产日志双向核对)。
- **机制现状(四层,出处见代码/档案)**:
- **浏览器注入 `__dshRecover`**(proxy.ts L56-287):`visibilitychange/focus/pageshow` → 跨域探活 `/api/dsh/status`(15s 节流)→ `recover()`;识别 404+`not_running`(fetch `clone().text()` / XHR `responseText`);401+`/api/` → `expire()`→`reload()`;请求挂起 ≥3s → 覆盖层(`SLOW_MS=3000`)。
- **代理 `proxy.ts`**:导航 404 → 302 `/wake.html?next=`(不阻塞);非导航 404 → `launch()` + `waitForLaunchTokenForUser(20000)` + 重新 resolve + 转发;实例侧 401 导航 → 302 带新 token;实例侧 401 XHR → 取新 `dsh-auth-*` 覆盖 cookie **重放一次** + 回写 Set-Cookie;门户侧 401 导航 → 302 login.html。
- **编排器**:崩溃 → 指数退避(1s→30s)重启;`crashStableMs=60s` 稳定即重置;10 分钟 5 次熔断;插件加载失败**自动摘除** + 清计数;cap 4 LRU + 7 天 TTL。
- **重要事实纠正**:`/etc/dshs.env` **未设** `IDLE_TTL` / `MAX_IDLE_INSTANCES` → 生效值 = **7 天 TTL + cap 4**。全历史 `idle-reap` **仅 1 次**(09-09)→「空闲回收」基本从未发生;`instance-restart` **106 次** ⇒ 实例中断主因是崩溃/被停,**注入脚本文案「实例已休眠(空闲回收)」归因不准**。
- **⛔ 现场发现(只报告,未处置)**:admin 实例(uuid `cce6d1cd` / uid 114801)**23:18 起每 2–4 分钟重启一次**(23:37:45 / 23:41:06 / 23:45:29 / 23:46:45 / 23:48:45 / 23:50:50…)。每次 scope 均为 `Succeeded`(**干净退出**,非 OOM/ABRT);`crash-restart` 事件**无 exitCode 字段**(`exitCode = code ?? undefined`)。23:18:10 曾因 `Cannot find package 'xlsx'`(`dsh-plugin-mcn-suite/lib/host/mcn/imports.js`)插件树加载失败并熔断。⇒ 用户体感「不自然」的直接来源 = **恢复流程被反复触发**(同期日志 `/api/dsh/status`×19、`POST /api/dsh/enter`×7、`GET /`×10)。
- **机制缺口(新发现)**:`crashStableMs=60s` **小于**崩溃周期(2–4 分钟)⇒ 每次崩溃前都「稳定过」→ 退避步数/streak 被重置(attempt 恒 1、delayMs 恒 1000)→ **周期性死亡永不熔断**,恢复流程可以无限反复弹。
- 未改任何文件;未抢锁;未 commit。
- **⚠️ 更正(同日 23:58,同一会话自查)**:上条「周期性死亡永不熔断」**表述有误**。实测 `stableTimers` 到点只 `crashStreak.delete()`(**只清 streak,不清 `crashHistory`**)→ 窗口计数照常累积,**熔断会触发**(生产日志 23:50:50 已到 `restartsInWindow:5`)。真正的缺口是:熔断后 `mains.delete` + `resetCrashState()`,而用户下一次 `launch()/enter`(L127)又 `resetCrashState()` → **计数清零、重新给足 5 次预算** ⇒ 崩溃循环可**无限期重复**(每轮 ≈10 分钟),且熔断**只写 stderr 日志、无告警**。
## 23:54 「回到页面看不到拉起提示」根因定位(用户澄清后的追加排查)
- **用户澄清**:不自然之处 = **回到页面从来没看到过「拉起实例」的提示**(不是嫌流程慢)。
- **验证 1 · 注入脚本确实在页面上**(R4 临时会话 → 用完即删,删除 2 条 `poc-curl2` 历史残留):
`curl -s -L --compressed -c jar -b jar -b "sid=$TOKEN" -H "Accept: text/html,application/xhtml+xml" https://admin.alotbuy.com/`
→ `http=200 size=9994`,`__dshRecover=7 / __dshAssist=6 / visibilitychange=1 / not_running=3 / SLOW_MS=2`。**注入正常,脚本在跑。**
- **验证 2 · 注入会被跳过的旁证**:全历史 `[inject-recovery] skip: content-encoding=gzip` **11 次**,最近两次 **09-12 22:40:25 / 23:26:55**(正落在崩溃循环期间)→ 这些页面加载**没有自愈脚本**,是次要但真实的洞。
- **★ 根因(代码级,决定性)**:注入脚本 `probe()` 只在 `j.running === false` 时才 `recover()`;而 `running` 来自 `dsh.ts` 的 `alive(status)` —— **`alive()` 把 `starting` 也算活着**(`status !== crashed/stopped/failed`)。
又实测每次崩溃后 **新 scope 在同一秒或 1 秒内就起来**(23:37:46→23:37:46、23:41:06→23:41:07、23:45:29→23:45:30、23:46:45→23:46:46、23:48:45→23:48:46、23:50:50→23:50:51)。
⇒ **用户切回标签页时实例几乎总是 `starting`/`running` → `probe()` 判定「没问题」→ 不提示、不恢复。** 用户看到的"死页面"完全没有任何反馈 —— 与他的描述完全吻合。**这不是概率问题,是逻辑条件选错了判据。**
- **次要根因**:即便 `recover()` 真触发,覆盖层只活 **600 ms** 就 `location.replace(wake.html)`;而实例已在跑时 wake.html 的 `enter` **立即返回 url** → 过渡页一闪而过。两处叠加 ⇒ 提示即便存在也肉眼不可见。
- **修复方案(已定,未实施;需 R8 窗口)**:改 `proxy.ts` 注入脚本 —— ① 判据从「进程在不在跑」改为「页面还能不能连上实例」(新增同源轻探针,404/405 时**降级回旧判据**以免官方升级误报);② 提示可见:切回页面先亮顶部轻提示,判定脱节后升级为**不自动消失**的覆盖层;③ 恢复**就地**(自己调 enter 拿新 URL + 保留路径),不再跳门户域;④ 文案去掉「空闲回收」归因。改动面 1 个源文件 + build + **重启 dshs(断在线用户数秒)**。
- 未改任何代码;未抢锁;未 commit。
## 00:0x 「看不到 MCN 工作台入口」—— 逻辑层已用模拟验证全对,**卡在浏览器侧**
- **修正上一条的误判**:client.js 里第 5522 行的 `var inject = [...]` 只是我留下的**未使用变量**(无害),**不是"两份注册层"**。
- **读官方源码确认契约(`dsh-client-modules`)**:
· `package.json` 的 `dsh.client.inject` = **字符串数组 = 依赖的包名**(`index.js:145` `optionalStringArray(pkgName, "dsh.client.inject", decl.inject)`);
· client bundle `factory()` 返回的 `inject` = **函数**(cordis 服务注入,返回服务映射);
· 二者**不是一回事** —— 之前报 `waiting for services: @deepseek-ai/…` 就是把这个函数写成了包名数组(0.3.6 已修)。
- **★用 node 模拟 `__ModuleLoader__` 跑真实 client.js 的结果(决定性)**:
· `load()` 调用 **1 次**(只有整合包)✓;
· 收集到 **6 个子包**(`dsh-plugin-mcn` / `mcn-schedule` / `voxemw-cloud` / `douyin-accounts` / `douyin-account-detail` / `douyin-video-detail`)✓;
· 整合包 factory 返回 `{inject, apply}`、`inject` 是**函数**返回 `{}`、`apply` 是函数 ✓。
· ⚠️ 「apply 期间 slots.inject 调用 0 次」**不能作为结论** —— 模拟里 6 个子包 factory 都因假 `require` 缺 React 运行时抛错(`Cannot set properties of undefined (setting plugin)`),是 **mock 不够真**,非真实故障。
- **⇒ 结论:代码侧(注册层 / inject 形态 / 子包收集)已全对,剩下的是浏览器运行时的真实行为。**
- **下一步(唯一缺的一步)**:在管理员浏览器里 **F12 → Console** 看真实报错;若不再有 `Failed to load plugins`,则问题在「入口注册的 slot 位置/可见条件」,需对着 `sidebar.footer.action` 的实际渲染确认。
- 期间尝试用 `agent-browser` 直接开实例页(需实例 token)→ 取 token 时 `node -e` 引号再次失败,未完成(**第 3 次踩同一个坑:一律写脚本,不要内联**)。
## 23:57「现在改」→ 抢锁失败,按 R9 停手(改动已备好)
- 用户答复「现在改」。执行第一步 `--claim-exec` **抢锁失败**:**两把锁都被占**
- `.exec-lock`:占用者 **`uv-deploy-2317`**(09-12 23:17 起,未声明单号)
- `/opt/dsh/state/.op-lock/univer-fork-deploy`:占用者同人;摘要 **univer-fork-deploy**;影响面 **「覆盖候选池 dsh-univer-office.tgz(0.2.14→0.2.15)+ guest 实例重装重启」**
- **按 R9 停手**:不得人工删锁/接管,未动代码仓库、文档库、服务器。
- **顺带核到的实况**(只读):近 1 小时 **admin(114801) 换了 17 个 scope(= 被重启 17 次)**,guest(100002) 3 次 —— 与我 23:47 报的「admin 每 2–4 分钟重启一次」一致,**可能与该部署会话有关**(时间吻合:锁 23:17 → admin 23:18 起崩溃循环),但 op-lock 摘要写的是 guest,**需该会话自证**。
- **改动已备好(未落地)**:`_patch77/inplace-recover-patch.md`(放在**受保护根之外**,含 5 处精确编辑的 old/new、落地步骤、回滚、红线)。锁一释放即可机械落地。
- 要点:① 判据从「进程在不在跑」→「页面能不能连上实例」(新增同源探针 `GET /`,2.5s 超时;404/405 → `unknown` 降级回旧判据,绝不误报)② 顶部轻提示条 + 不自动消失的覆盖层(`__dshConnBar`)③ **就地恢复**(自己调 `/api/dsh/enter` + `withPath()` 保留路径,不再跳门户域)④ 去掉「空闲回收」归因文案。
- 未 commit。
## 00:0x **★用户贴控制台报错 → 一句话定位根因 → 0.3.9 修好**
- **用户给的报错(决定性)**:`[mcn-suite] 子包 apply 失败: Error: **cannot get property "slots" without inject**` at `Module.apply (client.js:3042)`。
- **根因(读子包真身)**:`dsh-plugin-mcn/lib/client.js` 的写法是 ——
`const inject = ["slots", "sessions"]; function apply(ctx){ ctx.slots.inject("sidebar.footer.action", …) } exports.inject = inject;`
⇒ **`inject` 是「要注入的服务名」数组**,元素是 `slots` / `sessions` 这类**服务名**。
- **我错了两次(都在这一处)**:
· 写**包名**数组 `["@deepseek-ai/dsh-client-runtime", …]` → `pending (waiting for services: @deepseek-ai/…)`;
· 改成**函数** `inject: function(){ return {} }` → `cannot get property "slots" without inject`(ctx.slots 拿不到,入口注册失败 ⇒ **看不到 MCN 工作台入口**)。
· **正解** = **子包 inject 的并集**(`["slots","sessions",…]`,起点空数组 `var inject = []` + 合并 6 个子包)。
- **0.3.9 已部署**:上传 200 / 候选池 0.3.9 / 已装 0.3.9 / `var inject = []` 命中 1 / 子包 `["slots","sessions"]` 命中 2 / 实例重启 `[mcn-suite] loaded` + 3 host 面 ✓。
- **教训(已进规则)**:dsh client bundle 的 `inject` 是**服务名数组**,与 `package.json` 的 `dsh.client.inject`(**包名**数组)**同名不同义** —— 这是本次连续打 5 个包的根因。
- **另一条**:**浏览器控制台的报错文本是这类问题唯一的定位线索**(服务端日志全程显示正常)——用户贴的那两行,比我之前所有服务端排查都有效。
## 00:03(09-13)「继续修改」→ 锁仍未释放;原地做完两项只读预验证
- 复抢锁:**仍失败**,`uv-deploy-2317` 自 23:17 持有 `.exec-lock` + `/opt/dsh/state/.op-lock/univer-fork-deploy`(已 46 分钟)。按 R9 继续不动代码仓库/文档库/服务器。
- **预验证 1 · CORS 通过(本方案前提)**:`OPTIONS https://alotbuy.com/api/dsh/enter`,`Origin: https://admin.alotbuy.com` → `204` + `allow-origin: https://admin.alotbuy.com` + `allow-credentials: true` + `allow-headers: Content-Type`;无 cookie 的 POST → `401` 且带 CORS 头。⇒ 注入脚本跨域调 `enter` 合法可用。
- **预验证 2 · `[inject-recovery] skip: content-encoding=gzip` 系假警报**:两次跳过(09-12 22:40:25 / 23:26:55)的上下文均为 `[proxy-auth-replay] GET /` + `remoteAddress` = **47.77.182.89(服务器本机)**/CF 回源;即**我们自己的验证 curl**(默认 `Accept: */*` 不含 `text/html` → 代理不覆盖 `accept-encoding: identity` → 上游 gzip → 跳过注入)。真实浏览器导航必带 `text/html`(三件套 curl 实测 `__dshRecover=7`)。⇒ **不需要为此改动**,草案四项编辑已足够。
- 草案已更新:`_patch77/inplace-recover-patch.md`(新增 §5 预验证、§6 仍未解决的项)。
- 未改任何受保护资源;未 commit。
## 00:07(09-13)用户问「uv-deploy-2317 没看到这个会话」→ 查明身份
- **`uv-deploy-2317` 不是 UI 会话标题**,是那个会话 23:17 抢锁时**自己填的 `ME`**(本项目约定 `<主题>-<HHMM>`)。锁文件路径:`dsh-server-docs/交接单/.exec-lock/OWNER`(三行:OWNER / 开始 / 在做)。
- **从共享日志(同一份 `2026-09-12.md`)还原出它的轨迹**:23:17 先做 **univer-office 投放**(op-lock 摘要 `univer-fork-deploy`:候选池 0.2.14→0.2.15 + guest 重装重启),**23:2x 起一路在做 `dsh-plugin-mcn-suite` 插件**(0.3.2→0.3.9,最后一条 00:0x「用户贴控制台报错 → 0.3.9 修好」)。
⇒ **它极可能就是当前仍在交互的那个「插件调试」会话**(用户正往里贴控制台报错),按 `uv-deploy-2317` 这个名字当然找不到。**锁是活的,不是孤儿。**
- **同时解开上一个悬案**:admin 实例 1 小时内 17 次重启 = **插件会话每升一版就重启实例验证**(23:2x 那次 `Cannot find package 'xlsx'` 是 0.3.2 漏声明依赖,0.3.3 已修)。**与平台回收机制无关**,与本补丁无关。
- 释放命令(**只应由用户本人执行**;会话仍在跑时释放会破坏互斥):
`bash scripts/handoff-guard.sh --release-exec` | `ME="uv-deploy-2317" bash scripts/op-lock.sh release univer-fork-deploy`
- 草案 §6 已更新。
## 00:10-00:20(09-13)**档案 77 落地完成**:回到页面自检 + 就地恢复(恢复过程可见化)
- **用户授权解锁**("看不到还有别的会话执行,批准解锁")→ 按项目章程用 `--release-exec` + `FORCE=1 release univer-fork-deploy` 处置(**用户明确点头,非 AI 单方面接管**);随后以 `recover-inplace-77` 重占两把锁。解锁前抽查:近 6 分钟无文件写入。
- **改动**(唯一文件 `D:/github/dsh_shenxian/src/supervisor/proxy.ts`,5 处编辑,全在注入脚本 `SESSION_RECOVERY_JS`):
① `poke()` 探针(同源 `GET /`,`redirect:'manual'`+`no-store`+**2.5s 超时**;200→ok,404/405→**unknown 降级回旧判据**,其余→bad)
② `probe(withNotice)`:门户口 `running:false` 直接恢复,否则 `poke()` bad 才恢复
③ 顶部轻提示条 `#__dshConnBar`(`ensureBar/showBar/hideBar`,**最少显示 900ms**)
④ `recover()` **就地恢复**:跨域 `POST /api/dsh/enter` + `withPath()` 保留 pathname/hash → `location.replace()`;取不到则 reload
⑤ 触发细分:`visibilitychange` + `leftAt`(**离开 ≥20s** 才亮提示条);`focus` 静默;`pageshow(persisted)` 带提示
⑥ 文案:去掉「空闲回收」归因;`expire()` 文案改「正在重新连接你的工作区…」
- **验证(全绿)**:
- 本地 `npm run build` rc=0;模板字符串**反引号 0 / `${` 0**(长度 6604→10634)
- 编译产物抽 `SESSION_RECOVERY_JS`/`SESSION_ASSIST_JS` → `new Function()` **双通过**
- **CORS 前提实测**:`OPTIONS /api/dsh/enter` + `Origin: https://admin.alotbuy.com` → `204` + allow-origin + allow-credentials + allow-headers: Content-Type
- 传前 `diff` 服务器版 = 本地版(**0 行差异**,排除带上别人的在途改动);传后 `md5` 双向一致、`CR 行数=0`
- 服务器 `npm run build` rc=0 → `lib/` 含新标记;`systemctl restart` → active / 门户 200 / **孤儿 scope 0**
- **端到端**:临时会话 `enter` 200 + 实例 `running` → 三件套 curl 取实例页 → `__dshConnBar`/`function poke`/`withPath`/`rawFetch`/`leftAt`/「正在检查工作区连接」/「工作区正在恢复」**全命中**;旧文案 0;临时会话 `deleted_sessions=2`
- 回滚点 `/opt/dsh/backups/proxy.ts.bak-20260913-0012`
- **归档**:`04-调整方案/77-回到页面自检与就地恢复-恢复过程可见化.md`(占号 `.lock-77`,已释放)+ `INDEX.md` 加行 + `BRIEF.md` 改「回到实例页面」现行事实行 + 最近动作。四件套:`docs-audit` rc=0 / `docs-manifest` 刷新 / `docs-consistency` rc=0。
- **文档推送**:只推**我本次碰过的 4 个**(77-*.md / INDEX.md / BRIEF.md / docs-manifest.json),chmod 600;复跑对账 125 一致。
⚠️ **如实报告**:`BRIEF.md` 的本地版**混有其它会话未推的编辑**(T03 候选池状态、待办精简等),随本次一并推上服务器;剩余 **6 内容不一致 + 3 仅本地**(74/75/76、03-路线图、交接单/README、交接单/T03、06-UI规范、42、67)**属其它会话积压,我未动**。
- **未 commit / 未 push**(用户未要求);本机代码仓与文档库现有未提交项。
# 2026-09-13 工作日志(append-only)
## 06:50–07:15 会话 `a2665dd3`:接管「参考决策方法逐条判断处理」会话的任务(**只读 + 配置修复**)
- **起因**:用户改了工作区路径(`D:\AI技能\aliyun-dsh-server` → `E:\ProgramData\AI技能\aliyun-dsh-server`,D: 旧路径已不存在),要求继续上一会话的任务。
### 一、目标会话身份已查明
| 项 | 值 |
|---|---|
| 会话 id | `b58f0293-d8a6-497d-94d8-af9665553c75` |
| 原始标题 | 「查看dsh项目还有哪些任务待确认和处理」 |
| 09-12 **22:55 被自动改名** | 「**参考决策方法逐条判断处理**」 |
| 生命周期 | 09-12 17:50 建 → 最后活动 **09-13 00:09**(≈6.3 小时) |
- ⚠️ **本机拿不到该会话转录**。桌面版会话内容**只在云端(EdgeSync)**;`~/.workbuddy/projects/` 最后一份 jsonl 停在 **09-12 07:00**,之后全部缺席。
- **能查到的**:元数据在 `~/.workbuddy/workbuddy.db`(`sessions` 表,但**未含本会话**,桌面版该库已静态化);标题/创建/重命名/最后活动在 `~/.workbuddy/logs/<日期>/edge-sync.log`。
- ⇒ 复原任务只能靠「共享日志 `2026-09-12.md` + 文档库 + 服务端只读实测」三条腿。
### 二、✅ 修复一个阻断级问题:hooks 路径仍指 D:(本次实测确诊)
- 现象:任何 **Write/Edit 都被拒**,错误 = `D:\miniconda3\python.exe: can't open file 'D:\AI技能\...\lock-guard-hook.py'`(D: 已不存在)。
- 根因:`~/.workbuddy/settings.json` 的 hooks 两条 command 都是**绝对路径**,工作区搬迁后失配。
- 修法(**只改路径串,未动结构**):`sed -i 's#D:/AI技能/aliyun-dsh-server#E:/ProgramData/AI技能/aliyun-dsh-server#g'`;备份 `settings.json.bak-pathfix-0655`。
- 校验:JSON 顶层键 5 个(`sandbox/claw/enabledPlugins/autoLaunchDesired/hooks`)、`hooks` = `PreToolUse` + `SessionStart` 均在;写探针文件成功。
- ⛔ **【该结论已被 09-13 07:05 实测推翻——见本文件下方「hooks 语义定论」:hook 命令实为「会话启动时快照」;当时"改完就能写"是被一支临时目录联接掩盖的假象】**
- **当时的★结论(修正 09-12 的悬案)**:**hooks 配置是"每次调用现读",不是启动快照** —— 改完**立即生效、无需重启**;同时 `.workbuddy/lock-hook.log` 有 `SessionStart` / `PreToolUse-deny` 行 ⇒ **桌面版确实加载 hooks**(首次正面证据)。
- ⚠️ 但钩子**只校验「有没有人持锁」,不校验「是不是你」**:只要有任一锁存在,任何会话都能写受保护根。
- ⚠️ **副作用(新发现,未处置)**:无锁时**自动化会话**(cwd = `D:\github\dsh_shenxian`)的 Write/Edit 同样被拒 —— `lock-hook.log` 03:21 / 06:22 两次 deny(`.workbuddy/memory/2026-09-13.md`)⇒ 「代码仓三方同步」自动化若需提交会被钩子挡住。**待用户裁定:给自动化留白名单 or 入 `disableAllHooks` 之外的例外**。
### 三、⛔ 未动文档库:全局执行锁被活会话持有
- `交接单/.exec-lock/OWNER` = **`回到页面实测-0647`**(09-13 06:51 起),对应会话 `3c12a818`(用户同批开的另一个窗口:继续「检测用户回到dsh页面」)。
- 证据:近 20 分钟它已改 `BRIEF.md`/`INDEX.md`/`README.md`/`03-路线图`/`04-73`/`04-77`/`06-UI规范`/`docs-manifest.json`/`scripts/*`,并正在跑浏览器实测(`_patch77/shots/`:`0-初始.png`/`A-顶部提示条.png`/`B-恢复后.png`/`B-恢复覆盖层.png`)。
- ⇒ 按 **R9 / §6**:**未抢锁、未改文档库与代码仓、未动服务器**(只做只读 ssh)。
### 四、服务端只读实测(07:01–07:05,`bt-server` 端口 32022)
| 项 | 实测值 |
|---|---|
| 候选池 | `dsh-plugin-mcn-suite.tgz` = **0.3.9**(09-13 00:02)|`dsh-univer-office.tgz` = **0.2.15**(09-12 23:19,**fork 版**) |
| admin(uid 114801) | `bundles` **含 `dsh-plugin-mcn-suite`(0.3.9 已装)**;univer **未**在 bundles |
| guest(uid 100002) | `bundles` **无 mcn-suite**(⇒ **T03 的"guest 启用"仍未做**);**含 `dsh-univer-office` 0.2.15**(同源代理改造版已上线) |
| guest node_modules | **无 `@liustack/modlens` 残留** |
| op-lock(服务器侧) | **空闲**(仅 README) |
| 实例内存 | guest **208 MiB** / admin **210 MiB**(上限 384)⇒ 堆限 160 的治理有效 |
| 服务/实例 | `dshs` active;两个实例 scope 均 running |
### 五、发现的「账实不符」(**只报告,未改**)
1. `交接单/README §一` T03 行仍写「池内 = `0.3.2`」→ 实际 **0.3.9**;`BRIEF §3` 同。
2. `03-路线图 §二` P1「**摘除 guest 仍在启用的 `@liustack/modlens`**」→ **实际已完成**(guest bundles 与 node_modules 均已无)。
3. `03-路线图 §二` 档案 64/AnySearch 行仍与「已彻底放弃(下架)」并存(BRIEF 已改)。
4. 档案 76(univer 平台适配)标注状态与「fork 版 0.2.15 已进 guest bundles」的差异 —— 需核该档案是否已含"端到端 400 未闭环"的修正。
### 六、本次落盘
- 本日志 + `MEMORY.md` 三处更新(状态表新增「路径迁移」「钩子已实证生效」两行;代码仓/文档库未提交项按 07:0x 复核刷新;§三 改造文档路径改 E:)。
- 探针文件(`_hooktest.txt` / `dsh-server-docs/_gate_probe.md`)已清理。
- **未 commit / 未 push / 未 scp**(按 §4,用户未发话)。
---
## 06:47–07:10 会话 `3c12a818`(锁名 `回到页面实测-0647`):续做「检测用户回到dsh页面」+ 档案 77 浏览器端实测 + 路径全面归位
> ⚠️ 与上一节(`a2665dd3`)**同时在跑**:它 06:5x 也去修 hooks 路径(`sed` 改 settings.json);我 06:52 先备份、06:53 建了目录联接。两边都改 settings.json 的**同一处**,结果一致(最终 = E: 路径),无相互破坏(我只做精确 Edit,未整段覆盖)。
### 一、档案 77 唯一未闭环项 → **已关闭:浏览器端实测 10/10 全绿**
- 手段:本机真 Chrome(无头,`playwright-core` 驱动)+ **临时会话**(`mksess.cjs`,用完即删 `deleted_sessions=2`),**未重启服务 / 未停实例 / 未改任何源文件**。
- 结果:① 注入落位全中(`__dshConnBar`/`poke`/`withPath`/`LEFT_MS`),旧文案「空闲回收」= 0;② **离开 21 s 回来 → 顶部条「正在检查工作区连接…」出现、约 1 s 后自动撤除、未误亮覆盖层**;③ **拦截实例侧探针使其失败 → 覆盖层「工作区正在恢复,请稍候…」→ `POST /api/dsh/enter` 200 → 就地跳回、仍在 `admin.alotbuy.com`(未跳门户域)**。
- 证据截图:`_patch77/shots/`(4 张)。结论已回填 `04-调整方案/77` 新增 **§九**(关闭 §四 的 L5)。
- **首次跑的两个 ❌ 是测量脚本的 bug,不是产品问题**:① 提示条等待器在 21 s 等待**之前**启动、被自身 6 s 超时耗光;② 网络过滤写 `hostname.endsWith('.alotbuy.com')` **漏掉裸域 `alotbuy.com`**(`enter` 在门户裸域)→ 误报"没调 enter"。
- 环境坑:playwright 装在托管工作区;ESM 不认 `NODE_PATH` → 用 `createRequire`;自带 chromium 版本对不上(期望 1210 / 本机 1234)→ 改 `channel:'chrome'`;**脚本里给 Node 传 bash 风格 `/e/...` 路径会在当前盘根建 `E:\e\...`**(已删)。
### 二、hooks 语义定论(**含一次自我纠错**)
- 起点:工作区一搬,`settings.json` 里 hooks 的**绝对路径失配** → hook 报错 → 本机**所有会话** Write/Edit 全被拒(fail-closed)。为不把活断在手上,先建 `D:/AI技能 → E:/ProgramData/AI技能` **目录联接**救急,同时**误以为 hooks 是「启动时快照」**。
- **实测结论(2026-09-13 07:05,决定性)**:**hook 命令是「会话启动时快照」**。证据链:本会话 **06:47 启动**(当时配置里仍是 D: 路径)→ **06:55 把配置改成 E:** → **07:05 拆掉目录联接后,Write/Edit 报的仍是旧路径 `D:/AI技能/...`** ⇒ **改配置对已在跑的会话无效**。
- ⚠️ **纠错**:中途(07:0x)我曾写成「hooks 每次调用现读、无需重启」,并据此宣布「目录联接多余」——**错**。真相是**联接在 06:53–07:05 期间存在**,把"改完就能写"伪装成了现读;同机另一会话(`a2665dd3`)独立得出同一错论,可见**这就是该误判的成因**。
- **正确处置**:改完 hook 路径 → **完全重启 WorkBuddy**(关窗 ≠ 退出)或**新开会话**。**应急兜底**:本钩子**不拦 Bash**(有意留的安全阀)⇒ 本次余下的文件写入即走 shell 完成。
- 已回填:`CODEBUDDY.md §6`、`MEMORY.md §五`、`04-调整方案/73`(① 条 + 文末「修正」节)、`03-路线图与待办.md`。
### 三、活路径归位到 E:(本次共改 11 个文件;历史档案按「只增不改」保留原值)
| 类别 | 文件 |
|---|---|
| 配置 / 脚本(**必须**) | `~/.workbuddy/settings.json`(hooks×2)、`dsh-server-docs/scripts/lock-guard-hook.py`(`DSH_DOCS_ROOT` 默认值)、`scripts/docs-sync-check.sh` + `dsh-server-docs/scripts/docs-sync-check.sh`(`DOCS_LOCAL_DIR` 默认值)、`dsh-server-docs/scripts/extract-user-voice.py`(目录名示例) |
| 活文档(现行事实) | `README.md`、`INDEX.md`、`06-工作台UI规范.md`(权威源路径×3)、`04-调整方案/73`(hook 安装片段×5 + 新增「修正」节)、`BRIEF.md`(新增本机工作区行)、`03-路线图与待办.md`(新增「工作区迁移 D:→E:」记录) |
| 记忆 | 项目 `MEMORY.md`(改造文档路径)、用户级 `~/.workbuddy/USER.md`(本机工作区) |
- **未改**:`01-规划与架构`、`04-调整方案/11`、`04-调整方案/12`、`archive/` 各篇、历史日志 —— 写于迁移前,属历史(BRIEF/03-路线图 已各加一句"历史 D: 路径 = 迁移前旧值,勿照抄")。
- 代码仓 `D:\github\dsh_shenxian` **未动**(不在 `AI技能` 之下)。
### 四、文档库收尾
- 四件套:`docs-audit` rc=0 | `docs-manifest` 刷新 | `docs-consistency` rc=0 | 对账。
- **只推本次碰过的 11 个文件**到 `/opt/dsh/docs`(档案 600 / README 644)→ 复跑对账:**一致 123 → 127**。
- ⚠️ 如实报告:其余 **4 不一致 + 3 仅本地** 是**其它会话积压**(`交接单/T03`、`04-42`、`交接单/README`、`04-67`;`04-74/75/76` 仅本地)——本次未动。
- 未 commit / 未 push。
### 五、留给用户 / 下一棒
1. 历史档案正文里的 `D:\AI技能\...` 未逐处改写(按约定);**要连正文一起改,说一声**(约 6 个历史文件)。
2. 档案 77 **§八 遗留 1**(崩溃熔断可无限重来 + 无告警)仍待另开档案。
3. `a2665dd3` 报的 4 条「账实不符」(T03 池内版本 0.3.2→实际 0.3.9 等)未处置。