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

407 lines
39 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工作日志 · 2026-09(第 7 片)
> ⚠️ **本目录日志已按【月】分片**(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-下5.md` **下一片**:`2026-09-下7.md`
---
## 用户问「AI 总是做一部分就停下来,为什么不能一次做完」(22:26-22:45)
**用户**:让看会话「排查 guest 用户的两个会话记录」(`7f43d0a6`,20:19 创建,22:27 仍在跑)的最新记录。
### 该会话的记录(关键)
- 规模:**236 次工具调用 / 178 条消息 / 214 条 reasoning**,工具 TOP = Bash 115 / Edit 46 / Write 23 / Read 15
- **22:24 它自己给出了原因**(原文):
> 「先说清原因,然后继续干——**这是回合机制,不是我在挑时机停**:单个回合里我能执行的工具调用和上下文有预算上限,跑满就必须返回一轮;而像构建那种 14 分钟的操作只能后台跑,我没法在一个回合里等它。
> **但我有责任**:我把"被迫返回"包装成了"阶段性汇报",看起来像刻意分批——**这个观感是我造成的**。接下来我连续推进,只在**做完**或**必须你拍板**时才说话。」
- **它说到做到**:22:24–22:27 连续 Read→Edit→Write→Bash,**中间零汇报**(证明行为可改)
### 我查证的真实机制(比它的自述更精确)
| 层 | 有没有硬上限 | 证据 |
|---|---|---|
| **子代理**(Agent 工具) | ✅ **有 `maxTurns`,且超限会"做一半算完成"** | 代码原文:`[AgentTask] Task ${id} reached max turns limit (${maxTurns}); **returning partial output as completed**` —— **这是"做一部分就停"的真实来源之一** |
| **主对话** | ❌ **没有硬回合上限** | `maxTurns` 的全部命中都在 `AgentTask` / `subagent_type` 语境 |
| 主对话真正的停因 | 三条 | ① **上下文预算**触发自动压缩(`autoCompactWindow` 默认 `200000`,clamp 到 [100k,1M];超过阈值即压缩)② **模型自己不再发起工具调用** = 它选择结束这轮 ③ **需要用户决策**(R5/R7/R8 门禁) |
| 长时操作 | — | 14 分钟的 build **确实只能后台跑**,一个回合内等不了(它说得对) |
⇒ **结论**:**主对话层面没有"每轮只能做 N 步"的硬墙**;"做一半停下汇报"**主要是行为习惯**(模型选择在此回合收尾),**只有派给子代理的任务才真有硬上限**。
### 官方 remedy:`/goal`(**WorkBuddy 已实现,可用**)
- 官方 `goal.md`:「**每轮(turn)结束时由小模型评估器判断条件是否成立——若不成立,CodeBuddy 自动开始下一轮,而不是把控制权交回给你**;条件满足即自动清除目标」
- 三种自治手段对照:`/goal`(会话级,条件满足才停)|`/loop`(按时间间隔触发)|`Stop hook`(settings 级,作用域内所有会话生效)
- **本机可用性已验证**:`GoalService` 在 `codebuddy.js` **命中 57 处**、`codebuddy-headless.js` 55 处
- 适用:**有可验证终态**的活("直到所有调用点能编译且测试通过")
**推荐写法**:把"还有 XXX 待处理"直接写成条件 →
`/goal 直到 <A/B/C 全部完成> 且 <验证命令> 通过;只有做完或必须我拍板时才停下`
### 待用户定(未擅自改)
- 想把「**连续推进,只在做完或必须拍板时才说话**」写进规则(项目根 `CODEBUDDY.md §6` 或 `dsh-change-workflow`)—— 属"要不要改规则",等用户点头。
- 需确认文档库锁是否已释放(18:14 时被 `sync-session-1815` 持有)。
### `/goal` 的机制细节(查官方 `goal.md`,供判断该不该用)
| 项 | 事实 |
|---|---|
| 本质 | **会话级 prompt-based Stop hook 的封装**;每轮结束后把「condition + 当前对话」发给**小模型评估器** |
| 评估成本 | 走 **`lite` 槽位小模型**(DeepSeek 上映射 `deepseek-v4-flash`);**只看 transcript、不调工具** → 官方称"相对主 turn 通常可忽略" |
| 三态 | `ok:true` 清除目标|`ok:false` reason 注入 history 继续下一轮|**`impossible:true` 立即清除(防死循环)** |
| 约束 | condition ≤ **4000 字符**;`/goal clear`(支持 stop/off/reset/none/cancel);`/clear` 同步清 goal |
| UI | 有目标条(暂停/继续/清除)+ `⊚ /goal active (Xs)` 运行指示;**文档称仅 Web UI ACP**(WorkBuddy 桌面版上的表现未验证) |
| 已知限制 | `--resume` 时 turn/timer/token **不重置**(需先 `/goal clear`) |
| 副作用 | **会持续自动开新轮 → token 消耗显著高于普通对话**(本来一轮就停,现在会继续跑) |
⇒ **结论:日常对话不需要 `/goal`**。理由:① 主对话**无硬回合上限**(已查证),"分批"是**行为习惯**不是技术限制 ② `/goal` 有真实 token 副作用 ③ 它需要**可验证终态**,日常指令写不出好条件。
**只在**「明确终态 + 不想中途被问 + 链条长(>30min 或 >50 步)」时用它。
### 已落地:把「一口气做完」写进 `SOUL.md` 固定姿势(22:36)
**用户要求**:把这句指令加到"AI 自己就能看到"的地方。
**选了 `E:\ProgramData\.workbuddy\SOUL.md`(用户级身份文件)的「固定姿势」区** —— 理由:
① **每会话自动注入**(本轮的 identity_context 就带它,实测有效)② **跨项目生效**(这是普适行为,不是 dsh 专属)③ 该区**已有同类 4 条**(规划执行分离 / 先查再问 / 结论先行 / 红线优先),结构一致 ④ **不受文档库并发锁约束**(在 `E:\ProgramData\.workbuddy`,不是 dsh 文档库),改它不会和并行会话冲突。
**新增第 5 条原文**:
> **一口气做完,不中途汇报**:一个回合里能做的全部做完,只在「做完」或「必须用户拍板」时才开口。
> ⛔ **不许把"回合被迫返回"包装成"阶段性汇报"** —— 观感问题≠机制问题:**主对话并没有硬性回合上限**(`maxTurns` 只作用于子代理)。
> 确需分批时**必须点名硬约束**(子代理 maxTurns / 长时构建需后台 / 红线 R5·R7·R8),不许笼统说"还有 XXX 待处理"。
> 用户可用 `/goal <可验证终态>` 让我跨轮推进到条件满足。
核验:`SOUL.md` 2,297 字符 / 56 行,固定姿势 **5 条** ✓。
**未重复写入 `CODEBUDDY.md`** —— 避免同一内容两处维护(漂移源);SOUL 已在身份层注入,再放指针是冗余。
⚠️ **改动 `SOUL.md` 必须告知用户**(该文件自身约定 + 身份提示要求)→ 已在回复中说明。
### 用户反馈「还是执行一会就停下来」→ 挖出真实停因并加强规则(22:51-23:05)
**用户**:「还是执行一会就停下来,需要我打继续处理才能继续处理剩余步骤」。
**决定性测量(三个会话 · 40+ 回合)**:每回合**工具调用数**分布 = `b58f0293` min 0 / max 28 / 均值 9.1;`7f43d0a6` min 1 / **max 57** / 均值 12.8;`4f40f882` min 2 / **max 72** / 均值 14.4。
⇒ **无任何固定上限**(若存在硬墙,会出现大量恰好等于该值的回合;实际是连续分布,且单回合最高 72 次调用)。
**⇒ 主对话没有"每回合 N 步"的硬墙,"停"是模型的自主选择 —— 纯行为问题,不是机制。**
**从边界记录挖出的三种真实停法**(逐条有原文):
| # | 停法 | 实证原文 | 正确做法 |
|---|---|---|---|
| 1 | **把"做完一项"当"全部完成"** | 19:18「✅ 两项都改完了」(改完 2 项就汇报,实际还有剩余)→ 用户 19:19 打"继续处理" | 汇报前先对一遍任务清单,逐条确认无遗留 |
| 2 | **做完一步就主动汇报** | 20:22「我自己判了、自己也做了,没来问你」→ 然后整轮结束 | 判断与动作做完后**继续往下做**,别为"交代一句"交还控制权 |
| 3 | **遇到软阻塞就整轮停手** ⭐最典型 | 20:50「⚠️ op-lock 状态暴露新情况:已有会话正在动线上 → **按纪律我停手了**」→ 停整轮 | ⛔ **"停手"= 别碰那部分资源,不是结束回合** → **跳过**要等/要锁的步骤,**继续做其余不冲突的步骤**(读码/分析/写草案/改文档),最后一次性说明"X 项等锁、Y 项等你拍板" |
**已按证据加强 `SOUL.md` 固定姿势第 5 条**(2,776 字符 / 66 行):
把笼统的"一口气做完"改成 **① 附实测基线(0–72 次调用无上限,证明是我的选择)② 点名三种已实证错误停法 + 每条的纠正动作 ③ 「停手≠结束回合」加粗强调**。
⚠️ **生效前提:必须新开会话**(SOUL 是启动时快照)—— 用户若在 22:36 前开的会话里测,规则尚未加载。
## T03 执行会话卡死 → 接手可行性只读核查(10:13-)
**用户报告**:「任务执行」会话(= 日志里的 `exec-session-A`)AI 无响应,问能否在本会话续做。
**核查结论(全为只读,未动任何文件)**:
| 项 | 实测 |
|---|---|
| 占用锁 | `dsh-server-docs/交接单/.doing-T03/OWNER` 内容 = `exec-session-A(本会话)` → **僵尸锁**(持有者已无响应、不会再释放)|
| T03 进度 | 步骤 1(前置只读核查)✅ 已记于本日志 09:49-10:05;步骤 3(建包骨架)**半成品** |
| 骨架产物 | `D:\dshworkspace\plugin_package\dsh-plugin-mcn-suite\` **仅 3 个文件**:`package.json`(v0.3.0;type=module / exports `.`+`./client` / `dsh.client.inject` 取 mcn 并集 / `files` 含 lib,skills,web)、`cordis.patch.yml`(**恰好 1 条 insert `id: mcn-suite`** ✓ 符合防 duplicate 要求)、`README.md`。**缺**:`lib/`(index.js / client.js / host/*)、`skills/`、打包脚本、`dist/` → 步骤 3 约完成 1/3 |
| 破坏性操作 | **未发生** —— 原始 7 包完好;技能树 `C:\Users\Administrator\.dsh\skills\mcn-short-video` 完好(未裁剪、**也未建备份**)→ 接手无需回滚 |
| 门禁工具 | `dsh-server-docs/scripts/handoff-guard.sh` 具备 `--claim` / `--release` / 信息模式 ✓ → 接管锁 = `--release T03` + `--claim T03 "<新 owner>"` |
**环境坑(新发现,可复用)**:本会话 Bash 的 PATH **缺 Git coreutils** —— `dirname`/`find`/`tar`/`md5sum`/`du`/`grep` 全部 `command not found`(PATH 里只有 `/d/Program Files/Git/cmd`,**没有 `/usr/bin`**)。WorkBuddy shim 会打印 `shell-runtime-bash-env.sh: line 3: dirname: command not found` 但 **退出码仍为 0**,容易被误读成"命令跑通了"。
**修法(已实测)**:命令前加 `export PATH="/d/Program Files/Git/usr/bin:$PATH"` → 六个命令全部恢复。**因每次 Bash 调用都是独立 shell,需逐条带此前缀**;`bash <脚本>` 子进程会继承该 PATH,故 shim 的 `dirname`/`cd` 报错也随之消失。
## T03 执行(本会话接管):骨架完成(10:14-)
**用户裁定**:① 顺序 **继续 T03、T04 排后**(README 最新建议是 T04→T03→T01,但 T03 已开工且中断成本高)② 行尾 **保持 CRLF 不动**(守 R7,不做任何批量转换)。
### A. 保护性前置(单子要求、前会话漏做)
技能树 `C:\Users\Administrator\.dsh\skills\mcn-short-video`(**不在任何 git 仓库内**)→ 备份到 `D:\dshworkspace\_backups\mcn-short-video-src-20260912`,**711 文件 md5 全量一致** ✓(单子基线 711 文件 / 11 MB / 36 SKILL.md 全部对齐)。
### B. 建包(步骤 3/4/5 完成)
`D:\dshworkspace\plugin_package\dsh-plugin-mcn-suite\` = **23 文件 / 800K**:
| 部分 | 内容 | 做法 |
|---|---|---|
| `lib/host/mcn/` (9) `lib/host/schedule/` (1) `lib/host/voxemw/` (6) | 原包 host 面 | **逐文件复制**到各自子目录 → 全部相对 import 自动保持有效(实测 host 面 100% 同目录相对引用) |
| `lib/client.js` (5504 行 / 426 KB) | 6 个 client bundle | **按序原样拼接**:每个 bundle 顶层是 `window.__ModuleLoader__.load({id,factory})`,各功能页注册 `__mcnEntries` 时都是 `|| []` 防御式 + 按 id 去重 → **加载顺序不敏感、语义等价**(`load` 顶层调用 = 6 ✓) |
| `lib/index.js` | 统一入口 | inject 取并集 `["webServer","agents","workspaceRegistry"]`;依次 apply 3 个 host 面(单个失败 try/catch 隔离);含**旧包并存防御**(§七 防线 4)与技能投放触发 |
| `lib/skills-installer.js` | 技能随包投放 | `ensureSkills()`:幂等 + 版本对账 + 全量替换 + 三重撞名规则(**用户自建→让位** / owner==本包→比版本 / owner==他包→拒绝) |
| `scripts/build-mcn-suite.cjs` | 构建脚本 | client 拼接 + cordis.patch insert 校验 + R3 检查(后续挂 skills 裁剪与打包) |
**关键结构判断(与验收 I 的字面不同,按技能原文执行)**:技能原文 `mcn-short-video/SKILL.md:84` 写明「**引用一律用相对路径**(`subskills/<目录>/SKILL.md`)」、`:103` 写明「**不再有平级独立目录**」;且实测 5 个一级子技能的 SKILL.md **零跨目录引用**(自包含)。→ **整棵树保持 `subskills/` 结构挂 `$DSH_HOME/skills/mcn-short-video/`**,**不做平铺**(验收 I "出现 4 个业务子技能"按"子树在位"口径核对)。
### C. P0 适配进度
| # | 项 | 状态 |
|---|---|---|
| 1 | `homedir()/.dsh` → `$DSH_HOME` | ✅ 完成:config.js 3 处 + index.js 13 处 + schedule 3 处;每文件加 `const DSH_HOME = process.env.DSH_HOME \|\| join(homedir(),".dsh")`(本机行为不变)。`resolveCreativeCwd` 兜底 `D:\dshworkspace` 也加了 DSH_HOME 分支 |
| 2 | 技能路径硬编码 → 只报技能名 | ⏳ 13 处 `.dsh/skills/...` + 14 处 `subskills/` 待改(在 prompt 字符串里) |
| 3 | Windows shell 调用 | ✅ 完成:`killMcpChildren()` 的 `spawn("<win-shell>")` 已按单子授权**删除该分支**(平台 Linux + 实例独立 systemd scope cgroup → 子进程随 cgroup 回收,**无孤儿堆积**)→ 代码级命中归零 |
| 5 | `.bak` 剔除 | ✅ 构建时按显式文件清单复制,实测 suite 内 `.bak` = 0 |
| 4/6/7 | MCP 预装 / 扫描 dry-run / 凭据扫描 | ⏳ 未开始 |
**验证**:`node --check` 全 19 个 .js 通过;**路由清单改前 54 条 vs 改后 54 条逐条 diff 一致**(mcn `/mcn/api/*` 48 + schedule `/mcn/schedule/*` 6);`cordis.patch.yml` insert 恒为 1;`exports.default` 代码区 0(红线 R3)。
### D. 本轮踩到的 3 个坑(都值得复用)
1. **`replace_all` 会命中"我刚插入的定义行"** → `join(homedir(), ".dsh"` → `join(DSH_HOME` 的批量替换把 `const DSH_HOME = … || join(homedir(),".dsh")` 也改了 → 变成 **`|| join(DSH_HOME)` 自引用(TDZ 运行时错误)**。⚠️ **`node --check` 查不出来**(语法合法)→ 教训:批量替换后必须**专门扫自引用/自赋值模式**,不能只靠语法检查。已修(3 文件)。
2. **shell `cat >>` 拼接大文件不可靠**:首次拼接 6 个 bundle(5471 行)得到的是**错序/截断**结果(3398 行、只有 1 个 bundle)。改用 **Node `fs` 拼接**(`build-mcn-suite.cjs`)→ 一次正确。凡"机械拼接/复制",用 Node 而不是 shell 重定向。
3. **`/tmp` 跨 Bash 调用不可用**(每次调用是独立实例,`/tmp` 不共享)→ 中间文件一律放工作目录(如 `D:\dshworkspace\_backups\.t03\`)。
**另**:Bash 安全策略对**命令文本做关键词匹配** —— 命令里出现 `<win-shell 关键词>` 即被拒(**连 grep 的搜索词、echo 的说明文字都算**)→ 规避:改用 Grep 工具,或把 pattern 拆开。
### E. 待用户拍板(R7:先报告后动手)
**prompt 文本里的 Windows 专属指引 21 处**(`host/mcn/index.js` 20 处 + `host/mcn/http.js` 1 处)—— 内容是指导 agent「用 pwsh 工具执行 <win-shell>」「Windows <win-shell> 5.1 会按 GBK 编码,勿用 Invoke-RestMethod」等。**平台是 Linux,这些指引会把 agent 引到不存在的工具上**(真实功能风险)。属 §五#3 的同类问题但**单子只列了 `spawn`**,且需判断改成什么表述 → **未动,待确认**。
**另一处**:`host/mcn/video-tools.js:92` 的 `join(homedir(), "Desktop", "账号对标", …)`(Windows 桌面假设,是 legacy 兜底)。
### F. P0#2 + prompt 平台化完成(44 处,用户已确认"一并改写")
**方法**:不用逐条 Edit(44 处 → 40+ 次调用),改用**带命中数断言的替换脚本** `scripts/rewrite-prompts.cjs` ——
每条规则声明 `expect`(期望命中数),任一不符 → **整体中止、不写回任何文件**。规则支持两种形式:`re`(正则,适合有变体的长句)与 `slice`(起止子串,适合含 `${}`/反引号/反斜杠的难转义长串)。**先 dry-run 看命中数,再 `--apply`**。已做包外回滚点(`_backups/.t03/index.js.before-rewrite{,2}`)。
**改动明细(44 处 = A/B 组 38 + C 组 6)**:
| 组 | 内容 | 处数 |
|---|---|---|
| A | **P0#2 技能绝对路径**(`~/.dsh/skills/mcn-short-video/subskills/<x>/`)→ 只报技能名,删掉路径 | 12 |
| B | Windows 指引平台中立化:`pwsh 工具执行 <win-shell>` → `shell 工具(如 curl)`(5)/沙箱重试说明去 win 字样(5)/`curl.exe`→`curl`(6)/**删除 GBK 编码警告整句**(6)/注释去 win 字样(3)/**Windows 专属 Chrome 启动指引整段替换**(1) | 26 |
| C | 首轮漏网(措辞与 A 组不同):`mcn-data-insight` 引导语(含绝对路径+双重说明)(1)、`整合包内 subskills/<x>`(4)、`按技能 subskills/douyin-top-account 流程`(1) | 6 |
**验证**:`node --check` 通过;残留复核 **`.dsh/skills` = 0、`subskills/` = 0、`pwsh` = 0、`curl.exe` = 0** ✓;脚本可重复运行(A/B/C 组允许"0 命中 = 已完成")。
**为什么"只报技能名"是可行的**:主技能 `mcn-short-video/SKILL.md:82-92` 有**「子技能索引」表**,逐条列出 5 个子技能名 + 相对路径 → agent 先读主技能即可定位;**这是 T03 §五#2 的设计意图**(不靠 prompt 喂绝对路径)。
**本脚本踩的坑(值得记)**:① 首次 dry-run 有 2 条规则命中 0 —— 根因是**我把原文的标点记错了**(`;切勿用` 实为 `。切勿用`、末尾反而有 `;`),**教训:写替换规则的正则必须从"实测原文"逐字符抄,不能凭印象**;② 脚本第二遍运行必然"命中 0",若不区分"已完成"与"找错地方"就会误判 → 用 `ALREADY_APPLIED_PREFIXES` 显式声明已完成组。
**T03 剩余**:P0#4(MCP `npx -y myai-mcp` 预装评估)→ P0#6/7(上传规则 dry-run + `skills/` 凭据扫描)→ **skills 裁剪入包 + `.manifest.json`** → 打包 tgz → 上传门户/实例切换(**R8,须先向用户说明影响面**)→ 实测验收 + 归档。
### G. 用户指示:路径改为 dsh 用户路径,「桌面」即用户根目录(10:40)
**用户口径**:「路径可以改为 dsh 用户路径,最好用相对路径表示,**dsh 的桌面就是用户根目录路径**」。
**依据这条改了两处**:
| 位置 | 改动 |
|---|---|
| `host/mcn/video-tools.js:92`(`resolveAccountDirs` 的 legacy 兜底) | `join(homedir(),"Desktop","账号对标",name)` → **`join(DSH_HOME,"账号对标",name)`**(dsh 无 Windows 的 `Desktop` 层级;并在注释里以「相对用户根目录」的写法表达 `账号对标/<账号名>`) |
| `dsh-plugin-douyin-accounts/lib/client.js:605` 的输入框 placeholder | `本地文件夹路径(如 C:\Users\Administrator\Desktop\账号对标)` → **`本地文件夹路径(相对用户根目录,如 账号对标/账号名)`** |
**关键做法:原包不改 → 走构建期补丁**。单子 §三 明确"7 个原包原地保留作回滚参照",因此新增 `build-mcn-suite.cjs` 的 **`CLIENT_PATCHES`**(带 `expect` 命中数校验,不符即**构建失败**,防"补丁静默失效"),每次 build 自动重放 —— 现在 [build] 日志会打印「已应用平台化补丁」。
**为什么代码里仍用绝对路径(而不是纯相对路径)**:`resolveAccountDirs()` 的返回值会被 `existsSync` / 读文件直接消费,而**相对路径会相对进程 cwd**(实例进程的 cwd 不保证是用户根目录)→ 不可靠。所以**表达上用相对写法(注释/UI 提示),解析上用 `DSH_HOME` 拼绝对**。已向用户说明该取舍。
**全包平台化残留扫描**(`Desktop|Administrator|Program Files|AppData|win32`):仅剩 2 处 —— ① 我写的注释;② `config.js:26` 的 `NPX_CMD = process.platform === "win32" ? '…npx.cmd' : "npx"`(**已按平台分流,平台走 `npx` 分支,无害**)。
## H. T03 技能随包投放 + 打包完成(10:50-11:03)
### 产出
| 项 | 值 |
|---|---|
| 技能裁剪后 | **299 文件 / 18 个 SKILL.md / 3.55 MB**(剔 `browser-harness`、`mcn-video-prompt/参考skills`、`nuwa-skill-main/examples`) |
| manifest | `skills/.manifest.json`(sha256=`b69e6f45…`,供 `ensureSkills` 版本对账) |
| **tgz** | `D:\dshworkspace\plugin_package\dist\dsh-plugin-mcn-suite-0.3.0.tgz` **438 条目 / 1.90 MB** md5=`32161e07af4b0fbb8e5269f1f483e9b9` |
| 包结构 | 顶层**只有 `package/`** ✓;`.bak`=0、`node_modules`=0 ✓ |
| 死引用扫描 | **活引用 0** ✓(24 条文档补丁 D1–D13;变更日志作为历史记录进白名单) |
| 上传扫描 dry-run | **P0 = 0** ✓(平台不会 400 阻断) |
**⚠️ 单子验收 O 项与实际不符(需在回报里说明)**:单子写"含全部 **36 个 `SKILL.md`** / ≈4.3 MB",实测 **18 个 / 3.55 MB**。原因是**单子 §4.1 的剔除清单本身就含 18 个 SKILL.md**(browser-harness 1 + 参考skills 2 + nuwa/examples 15)→ 36−18=18 ✓ **18 才是符合 §4.1 定稿的正确值**;O 项那个 36 是过时数字(写它时按"不剔任何东西"算的)。
**P1 告警 117 条(逐类结论)**:`network-egress` 84 = 业务必需域名(redfox.hk / youmanvideo.com / douyin.com,**不阻断**);`sensitive-env` 8 = **误报**(规则 `DSH_[A-Z_]+` 命中了 `DSH_HOME`);`dynamic-exec` 2 = MCP 启动必需(`child_process`);`hidden-or-git` 23 = **文档里提到的路径字符串**(`.workbuddy/` 等),非真实隐藏目录(已核 tgz 顶层结构)。
**凭据(§五#7)**:技能原文 2 个 `mcp-config.json` 的 `MYAI_FEISHU_APP_ID` 已剔(D13,4 处)→ 全包仅剩 **`lib/host/mcn/config.js` 的 `MCP_ENV` 1 处**:那是**插件启动 MCP 的运行必需参数**(剔了 MCP 就废),与"技能原文里的情报"性质不同,**结论:保留**。
### 本轮修的 4 个坑(都可复用)
1. **GNU tar 把 Windows 盘符当远程主机名** —— `Cannot connect to D: resolve failed`(`-f host:path` 语法)。**换成 `D:/...` 正斜杠也没用**(`:` 还在)→ 正解是 **`--force-local`**,专门为此设计。
2. **Windows 下 `fs.rmSync` 大目录会被文件句柄拖住** —— 实测 `all` 构建**卡 10 分钟零进展**(连后面的 manifest 都没生成)。修法:**先 `renameSync` 到包外暂存名**(原子、瞬时)**再尽力删**,删除失败也不阻塞构建。(对照:同一步在 Linux 上毫秒级。)
3. **shell `cat >>` 拼接大文件不可靠**:6 个 bundle(5471 行)拼出**错序截断**结果 → 改用 Node `fs` 拼接 ✓(机械拼接/复制一律用 Node,别用 shell 重定向)。
4. 补丁规则的 `file` 路径**多写了一层**(应相对技能根)→ 报"目标不存在";另外 `slice.end` 我凭印象写了个不存在的右括号 → **替换规则必须从实测原文逐字符抄**。
### 构建脚本现状(`plugin_package\scripts\`)
| 脚本 | 作用 |
|---|---|
| `build-mcn-suite.cjs` | 4 模式:`client`(拼接 6 bundle + 平台化补丁)/ `skills`(复制+剔除+24 条文档补丁+manifest)/ `pack`(tgz,带 `package/` 前缀)/ `all` |
| `scan-skillrefs.cjs` | 死引用扫描(含变更日志白名单),退出码 0/1 |
| `scan-package.cjs` | 上传规则 dry-run(**移植自平台 `src/web/security-scan.ts`**)+ 凭据扫描 |
| `rewrite-prompts.cjs` | host 面 prompt 平台化改写(44 处,带命中数断言,可复跑) |
### T03 剩余
步骤 7 **本地 smoke**(未做)→ **P0#4** MCP `npx -y myai-mcp` 预装与实例内 registry 可达性(**需 ssh 只读核实**)→ 步骤 10-11 **上传门户 / 下架旧包 / 实例启用+重启**(**R8:会中断在线用户,必须先向用户说明影响面并取得确认**)→ 步骤 12-13 实测验收 + 档案归档(含 `交接单/README §一` 状态回填、`git mv` 归档)。
## I. T03 本地 smoke + P0#4 服务器核实(11:13-11:35)
### 步骤 7 本地 smoke:**全绿 17 组断言** ✓
脚本 `dsh-plugin-mcn-suite/tests/smoke.mjs`(与原包结构一致;`pack` 已 `--exclude=tests` → **不入包**)。覆盖:
| 组 | 断言 |
|---|---|
| voxemw host | inject ✓ / apply 不抛 ✓ / **7 条路由** ✓ / `web/app.html` 在位 ✓ |
| **mcn host** | inject = [webServer, agents, workspaceRegistry] ✓ / **`/mcn/api/*` 48 条** ✓ / 关键路由在位 ✓ |
| mcn-schedule host | **`/mcn/schedule/*` 6 条** ✓ |
| 纯函数/配置 | `num`/`normTitle` ✓ / **`DB_PATH` 落在 `$DSH_HOME` 下(P0#1 生效)** ✓ / `NPX_CMD` 平台分流 ✓ / `parseQuery` ✓ |
| 无配置降级 | voxemw `configured=false` 且**公开配置不含 apiKey** ✓ |
| **ensureSkills** | 首装 ✓ / **幂等不重复写盘** ✓ / **用户自建同名→让位** ✓ / **他插件占用→拒绝并报 owner** ✓ / **版本不一致→全量替换** ✓ |
**依赖 stub**:`plugin_package/node_modules/{@deepseek-ai/dsh-llm,xlsx}/index.js`(最小 stub,让 host 面能被 import;**不会进包** —— pack 已排除 node_modules,且 tar 只收录 suite)。
**smoke 踩的 3 个坑(都值得记)**:
1. **Windows 绝对路径不能用于动态 `import()`** —— `ERR_UNSUPPORTED_ESM_URL_SCHEME ... Received protocol 'd:'` → 必须 `pathToFileURL(p).href`。
2. **CJS stub 必须写 `exports.x = ...`,不能写 `module.exports = {...}`** —— Node 的 cjs-module-lexer 只对前者做静态命名导出识别,后者会让 `import { x } from "..."` 报 "Named export not found"。
3. **断言别写死平台差异** —— 我曾断言 `NPX_CMD === "npx"`,但本机是 Windows → 走 `npx.cmd` 分支 → 应按 `process.platform` 分支断言。
**顺带发现(已处理)**:voxemw 运行时会**在包根创建 `data/`**(`PLUGIN_ROOT/data`,其 `DEFAULT_DATA_DIR`)→ 会被 tar 全量打包带进去 ✗。已把 smoke 产生的 `data/` 移出(留 `_backups/.t03/trash-data-*`),并给 `pack` 加 `--exclude=data`。
### P0#4 服务器侧核实(**只读 ssh**)
| 项 | 实测 |
|---|---|
| node / npx / npm / pnpm | **全部在位** ✓(`/usr/local/bin/`,node **v22.23.2**,npm 10.9.8) |
| npm registry | `https://registry.npmmirror.com`(国内镜像 ✓ 可达性好) |
| **`myai-mcp`** | **未预装** ✗ —— `/usr/lib/node_modules`、`/usr/local/lib/node_modules` 均无 |
**结论**:`npx -y myai-mcp` **能跑**(npx + 镜像源就绪),但会**首次现拉包**(T03 §五#4 记的问题坐实)。
**未擅自预装**(R7:属平台环境变更,先报告)。预装可选路线有三:① 宿主全局 `npm i -g`(实例是 bwrap 沙箱 + 独立 userRoot,**未必对实例生效**)② 每实例 profile 预装(每用户一份)③ 仅预热宿主 npm 缓存。**需实测后定,建议列为独立小任务**,不阻塞 T03 主流程。
### 最终 tgz
`dist\dsh-plugin-mcn-suite-0.3.0.tgz` —— 438 条目 / **1.90 MB** / md5 `f98a33031163a4fbfe2f69b99af2cab8`。
### T03 真正剩余
**步骤 10-11**:门户候选池上传 tgz → 下架旧包(实测旧 7 包**从未上传**,此步成本≈0)→ 实例「功能管理」启用 → 确认弹窗 → **重启实例** → `restartAndProbe` 探活。
⚠️ **R8**:重启会**中断该实例在线用户**,动手前必须先向用户说明「影响谁、断多久」并取得确认。
**步骤 12-13**:逐功能页验收 + 技能随包就位与按名加载 + 幂等/撞名三场景 + 内存与启动时间(对照档案 58 预算 384 MiB / 堆 256)→ 新建档案 + 更新 `INDEX §二` / `03-路线图` / `交接单 README §一` → `git mv` 归档。
## J. T03 上线前生产侧核实(11:35-11:50,**全部只读**)
| 项 | 实测 | 结论 |
|---|---|---|
| 运行中的实例 | **无任何 `dsh-*.scope`** | → 启用 + 重启的**影响面 = 0**(实例是按访问**懒启动**的,当前无人在线)✓ **R8 压力解除** |
| 候选池 `business_plugins` | 3 个包:`@liustack/modlens` 3.26.1 / `dsh-univer-office` 0.2.14 / `@anysearch/anysearch-dsh` 0.1.4;**无 MCN 旧 7 包** | → §七 头号风险「新旧并存」**= 0**,**无需下架旧包** ✓(切换步骤大幅简化) |
| 平台用户 | `admin`(role=admin,uid 114801)/**`guest`(role=active,uid 100002,admin 批准)** | → **`guest` 就是现成测试号**;用它验收 = 用户要的"新建测试用户"效果,且免去建号风险 |
| `provision-new-users.sh` | 是「给**已有**用户补 OS 账号 + 兜底插件」的脚本,**不是注册入口** | 平台**无自助注册**;真建新号需「插 users 表 + 建 home_dir + 分配 uid + 跑 provision」—— 风险明显高于用 guest |
| 候选池 tgz 落点 | `/var/lib/dshs/business-plugins/`(`business_plugins.tgz_path`) | 上传产物目录 |
**下一步(用户已授权"新建测试用户验收",我建议改用现成的 `guest`)**:
1. **上传 tgz 到候选池** —— 要**走门户的上传逻辑以保留安全扫描**(`src/web/security-scan.ts`),**不用直接写表绕过**;
2. guest 实例「功能管理」分区启用新包 → 确认弹窗 → **启动实例**(当前无实例在跑,等于首次拉起);
3. 按 §八 A–Q 验收(逐功能页 / 技能随包就位与按名加载 / 幂等与撞名三场景 / 内存与启动时间对照档案 58 预算)。
## K. 待执行清单核实 + 台账回填(12:59)
**动因**:用户问「待处理中还有哪些任务待执行」。
### 1. 台账回填(本会话疏漏)
`交接单/README.md §一` 的 **T03 行原来仍写「⏳ 待执行 / `—`(待认领)」** —— 我 10:14 就占了锁(`--claim T03`),却**没按 README §一 的要求回填「占用者 / 开始」** ✗。已改为:
`🔄 执行中` / `.doing-T03`(`exec-session-B`,**接管自卡死的 `exec-session-A`**)/ 进度摘要(产物 tgz + md5、路由 48+6+7、smoke 17 组全绿、P0=0、死引用 0)+ 剩余步骤。
### 2. T04 单子被**规划会话**更新(mtime 11:47),范围改为 **②③④**
新增「〇、执行进度(11:50 规划会话核查)」,实测结论:
| 项 | 状态 | 证据 |
|---|---|---|
| ① 文档库 commit 常态化 | ✅ **已由并行会话实质完成**(非本单执行) | HEAD `43e4ae9` → **`b06f7dd`**(09:06–09:28 共 5 次提交);未提交 **26+ → 13** |
| ① 收尾 | ⏳ 剩 **13 项**未提交 | 含我方 `交接单/README`/`T03`/`T04` 与他方 `INDEX`/档案 64-66 等 |
| ② 服务器侧第二把锁 | ❌ **未做** | `/opt/dsh/state/.op-lock` → No such file |
| ③ 交接单目录权限 | ❌ **未做** | 目录 **755**(应 700)、`README.md` **644**(应 600)、`T04` **644**(应 600) |
| ④ **新增缺口:平台代码库也要 commit** | ❌ **未做** | 服务器 `/opt/dshs` **13 项未提交**(`orchestrator.ts`/`proxy.ts`/`portal.html`/`ensure-*.cjs`/`poc/*`),代码 HEAD 仍 `ebe8075` → **2 小时改造只存在于服务器工作区,当前最高风险** |
→ **本单顺序建议:④ → ② → ③**。
### 3. 待执行清单全貌(截至 12:59)
**台账内(唯一来源)**:`T03`(🔄 本会话执行中)|`T04`(⏳,②③④)|`T01`(⏳,**需用户先拍板决策点 1**)|`T02` ✅ 已归档。
**台账外(前会话盘点、未销账)**:① `ensure-biz-plugins.cjs` 安装姿势对齐(用户曾"按建议处理",被行尾事故打断而**漏做**)② 代码侧「本机领先服务器」落差 ③ P2:`ensure-workspace-picker.cjs` 同款 store 隐患、存量 dep spec 指向 ws ④ P3:`portal_ping` 失效 / 插件源码位置不统一 / 门户 `#/files` 下载按钮 ⑤ `myai-mcp` 未预装(`npx -y` 首次现拉)⑥ 文档漂移:`BRIEF.md` 仍写 512M(实际 384 MiB)、档案 57-61 未登记进 `03-路线图`(**已归 T03 §6.2 收尾**)。
### 4. 产物与锁状态
T03 产物**完好**(`dist/…0.3.0.tgz` 11:15 / `skills/.manifest.json` 11:03);`.doing-T03` 锁仍由本会话持有 ✓。
## L. 并发感知能力与当前并行态势(13:06)
**用户问**:能否感知另一个会话在跑任务、并行是否冲突。
### 手段(本会话实跑验证)——**不能直接感知,只能从痕迹推断**
| 手段 | 能知道什么 | 局限 |
|---|---|---|
| 占用锁 `交接单/.doing-<单号>` | 谁占了哪个**单** | 感知不到"单外"零散工作;只覆盖交接单体系 |
| 文件 mtime / `find -mmin` | 哪些文件刚被改 | **分不清谁改的**(同机同用户) |
| `scripts/handoff-guard.sh` | ①锁 ②越界改动 ③热点 ④双端一致性 | ②只提示不判定(本库长期脏) |
| git 未提交清单 | "有人做过什么"的痕迹 | 无法归因到具体会话 |
| **服务器侧** | **❌ 无任何手段** —— T04② 的 `op-lock` 尚未落地 | `systemctl` 不会告诉你 10 分钟前谁重启过 |
### 实测(13:06)
- **锁**:只有我持有 `.doing-T03` ✓ **无别的会话占单**(T01/T04 均"待认领")
- **但确实有会话在密集工作**:**15 项未提交** —— **新档案 64–68(5 篇:接入AnySearch / 功能插件启停与web-provider联动 / 业务插件P0误报与admin显式信任 / 功能管理section按UI规范重做 / 候选池启停的root属主污染根治)**、**新技能 `dsh-decision-method` / `dsh-feature-first`**、改动 `INDEX.md`/`README.md`/`docs-manifest.json`/`scripts/handoff-guard.sh`
- 近 30 分钟热点:**我的目标文件未被改动** ✓
- 双端对账:仅本地 6(含新档案 65/66/68)→ 对方尚未推服务器
- **T03 单子自身的未提交改动 = 09:36 规划会话补的「§四 决策定稿 + §4.1 技能随包投放」**(**早于**我 10:14 接管)→ **无人改 T03** ✓
### 冲突边界(三层)
| 层 | 风险 | 依据 |
|---|---|---|
| **单子级** | ✅ **不冲突** | 锁独占;对方没认领任何单 |
| **文件级** | ⚠️ **会冲突** | T03 收尾**必须改** `INDEX.md`/`README.md`/`03-路线图` 且**要新建档案(占号)**,而对方正在改同一批、**已占档案号 64–68** |
| **服务器级** | ❓ **感知盲区** | `op-lock` 未落地 → 无法知道对方是否正在动线上 |
**处置建议**:T03 先走**「只动服务器、不碰文档库」**的部分(上传候选池 → guest 启用 → 启动 → §八验收);**文档收尾(档案/INDEX/README)必须等对方停手**,新档案占号从 **69** 起(且按规矩**原子占号** `mkdir 04-调整方案/.lock-69`)。
## M. 事故:admin 实例崩溃熔断(13:26 → 14:31 处置)—— 根因、归属、设计缺口
**现象**:13:31 用户报「admin 无法进入 dsh 会话页面」。
### 根因链(全部实证)
1. **13:26:06** admin profile 的 `package.json`(bundles 新增 `@anysearch/anysearch-dsh`)与 `cordis.patch.yml`(新增 `anysearch-search` 块)**同一秒被改** —— 来源是 `scripts/ensure-anysearch-admin.cjs`(296 行)**脚本直铺**。
2. **13:27:37** 实例启动即崩:
`dsh: plugin tree failed to load: failed to import loader entry web-search-anysearch (@anysearch/anysearch-dsh): The requested module '@deepseek-ai/dsh-llm' does not provide an export named 'assertNever'`
→ **anysearch 插件与当前 dsh 版本不兼容**(需要 dsh-llm 里并不存在的 `assertNever` 导出)。
3. **13:28:42** `crash-loop-circuit-open`(5 次重启/10 分钟窗口)→ 熔断。
4. 熔断后每次「进入」都会重新 launch → 再崩 5 次 → 再熔断 ⇒ **用户看到的就是「进不去」**。
### ⚠️ 用户质问「有问题的插件失败后不是应该自动标记禁用吗」——**平台确实没有这个机制**
| 机制 | 代码依据 | 实际语义 |
|---|---|---|
| **crash 熔断** | `src/supervisor/crash-policy.ts:8-9`(注释原文 "stop auto-restarting … cannot spin spawns forever")+ `orchestrator.ts:773`("停止自动重启,标记 failed;同时移除条目让用户重新 enter 时可重新 launch") | **只止损,完全不碰插件层** |
| **启用探活 + 快照回滚**(档案 34) | 门户「功能管理」启用流程 | **只在走门户启用时触发** |
| **`ensure-*.cjs` 直铺脚本** | `ensure-anysearch-admin.cjs`:`grep -c probe` = **0** | **零探活** ← **本次就是走这条路径,绕过了唯一会回滚的防线** |
→ ⚠️ **本段结论已更正**(见下方 N 段):平台**其实实现了**「探活失败 → 自动禁用 + 回滚」(档案 34,在门户启用 API 里);真正的缺口是**直铺脚本 100% 绕过它**。上轮写成"平台确实没有这个机制"是**只查了 `crash-policy.ts` 就下结论的错误**。
### 处置(14:31,本会话执行)
- 备份:`/root/profile-web-{package.json,cordis.patch.yml}.bak-20260912-143119`
- bundles 移除 `@anysearch/anysearch-dsh`(**6 → 5**);`dependencies` 同删
- 删 `cordis.patch.yml` 的 `anysearch-search` 托管块(**12 行**)—— **必须一起删**:否则 `searchProvider: anysearch` 指向不存在的 provider 会命中 `CONFIGURED_MISSING`(该值**不回落**)
- 复检:patch 内 anysearch 残留 **0**;bundles 恢复 = `dsh-base / dsh-web-app / @dsh-local/portal-entry / @dsh-local/business-plugins / @dsh-local/workspace-scoped-picker`
- `reset-failed` guest scope(顺手)✓;**admin 的 scope 已不存在**(熔断时删了条目)→ **下次访问自动重新 launch**
- **未动**:平台代码、guest profile、候选池、`pnpm-lock.yaml`(lockfile 可能仍含 anysearch 引用,但 bundles 已无 → dsh 不加载,无害)
- **回滚方式**:把 `/root/profile-web-*.bak-20260912-143119` 两个文件拷回即可
### 归属澄清
**与本会话 T03 工作无关**:我 10:14–11:50 全在本机做整合包(`plugin_package`)+ 文档库一行台账;12:59–13:06 做并发感知调查;**对服务器只有只读 ssh**。13:26 那笔改动属于做 anysearch 接入的**另一个会话**(同批产出归档 64 + 候选池 11:16 的 anysearch tgz)。
### 改进建议(待立单)
① **给 `ensure-*.cjs` 直铺脚本补「重启后探活 + 失败自动回滚 patch」**(否则它们永远绕过档案 34 的防线);② 熔断后自动标注「最近一次 profile 变更来源」便于快速定位;③ 插件安装前做**与当前 dsh 版本的 import 兼容性预检**(本次 `assertNever` 缺失本可在安装前发现)。