Files

328 lines
47 KiB
Markdown
Raw Permalink Normal View History

# 接续包 · 会话机制合并技能包(任务 2/3/4 + 归档)— 2026-10-01
> ## §0 状态(**最新行在最上**)
> | 时间 | 会话 | 发生了什么 |
> |---|---|---|
> | 2026-10-01 20:0x–20:1x | `会话协作看板调整与自检-20261001`(**机制层 ⇒ 全局独占锁**) | ✅ **① 看板按最新架构调整(常驻口径回归)**:`assets/board.html`/`scripts/board.py`/`scripts/board_ext.py` 三处把 09-30 的「钩子按需唤起/跑完即退/不是常驻进程/没有启动-停止这回事」全部改回 **常驻投递** 口径(用户 10-01 定案「**协作与投递一直运行(常驻)**」+「**定时任务的方案已经废弃了**」);`prog_by` **不再猜调用方**(戳上分不出常驻还是钩子)⇒ 只报轮次名;帮助文本补 **四类会话 + 接续=形态**。⚠️ **易漏点**:看板扩展件**不是**从包里读的(`board.py` 按配置读 **`<工作区>/.workbuddy/collab/board_ext.py`**)⇒ 只改包内**不生效**,已 `cp` 同步、两份 md5 一致;`board.html` 相反、由包内提供即生效。<br>✅ **三步验证全绿**:语法 / 渲染桩 **FAIL 0/45**(双目标样本 0/48)/ 几何 **FAIL 0/7**。<br>✅ **② 用协作机制跑「检查会话协作是否运行正常」**(`goalctl declare` ⇒ `collabd --once` ⇒ `--tick`):**结论=机械部分可用、投递链不通**。可用:两轮 `rc=0`、目标/类别接线正确、digest/queue/TO-MAIN 都能生成、整包自测 **PASS 39/FAIL 0**。不通:**`--tick` 判 `deliver=target-deaf`**(登记主会话 `f8a792ab-…` 不响应;投递台账最后一条 **08:55** ⇒ 停了 ~11 小时)|**常驻投递未拉起**(`--supervise` 没跑、`guard.stop` 挂着)|**声明目标 ≠ 产生工作**(队列 0 件、digest「没有可派的任务」)|**台账不随目标切换**(新目标下仍反馈旧类别的 M5/M6/M7,关键路径写「M5 → M6」)|**验收判据不绑目标**(`declare` 换名时旧判据原样留着 ⇒ 新目标**一声明就被判"全过"**,实测 20:09)。<br>✅ **顺手修掉 4 个真缺陷**:① `preflight-lock.sh` **未登记技能包** ⇒ 改技能包一律误判【D】未归类、提示"先定域"(正解是"独占")⇒ **改技能包开不了工**(本轮开工即撞)⇒ 补 `'\.workbuddy/skills/'`(**包内+库内两处同源**,`bash -n` 均过、行尾各保持);② `goalctl.py` 工作区按 `__file__` 推层级 ⇒ 不带环境变量**静默指向 `…/.workbuddy/skills`** ⇒ 改 `DSH_COLLAB_WS`→`DSH_WS_ROOT`→cwd,落空才回落并**醒目告警**;③ 同文件加**静默陷阱告警**(换目标名不给 `--kpi` ⇒ 提示旧判据会留下);④ `state.py` 抢锁路径提示 `scripts/` **不存在**(实际 `07-scripts/`)。<br>🔴 **途中揪出并修掉 1 个真缺陷**:`tmp/render-check.mjs` 的 `BOARD` 还指向**已退役已归档**的 `skills/multi-session-collab/` ⇒ 唯一的看板回归工具一跑就崩。⚠️ **结构性遗留(未动)**:看板回归工具住在 `tmp/`(按规矩要清的草稿区)⇒ 清掉后"改完看板怎么验"这条路就断了。<br>⚠️ **一处自伤已修正**:`board_ext.py` 文案里嵌了 **ASCII 双引号** ⇒ 截断字符串 ⇒ `--manifest` 报**语法失败 1**(⚠️ 而 `--verify` 仍全绿 —— **覆盖面不同**,`--manifest` 才是语法闸)。<br>✅ **验收**:`--manifest` **34 文件 / 0 失败**|`--verify` **全绿**|`board_ext.py`/`goalctl.py` 包内与工作区副本 **md5 一致**|项目配置 `_默认形态` 已同步四类口径。✅ **锁**:机制层 ⇒ 不带 `--domains` 独占,收尾走 `dsh.py close` 反序释放。⚠️ **仍未做**:常驻投递未拉起、主会话仍 `target-deaf`、8788 看板未起。 |
> | 2026-10-01 19:5x | `会话类别口径修正-20261001`(**机制层 ⇒ 全局独占锁**) | 🔴 **用户订正会话类别口径(本轮唯一依据)**:「**接续会话 不是单独的一类会话,是这几类会话到达阈值时 创建的接续会话**,这里的第4类应该是 **队列上报的跟进会话**(之前考虑**只让主会话管目标和方向**,**队列上报的会话 让专门的 跟进会话处理**)」⇒ **订正了我上一轮(19:1x)的错答**(我答"四类=主/协作/唤醒/**接续**",**把"接续"当成第 4 类了**)。<br>🔴 **正确口径**:**四类 = ① 主会话 ② 协作会话 ③ 唤醒会话 ④「队列上报的跟进会话」**;**「接续会话」是「形态」、⛔ 不是第 5 类** —— 任一类撞阈值时**由它自己**建出下一棒,**角色继承被接续的那条**(主会话的接续**仍是主会话候选**)。<br>✅ **一次改齐(⛔ 不许只改一半)**:`architecture.md`(结论表两行/§2.3 第1级角色改四类含 `[跟进]`/§2.3.0b ② 换标题+定性块/🆕 **新增 §2.3.0c 第④类**/「需求内闭环」作废框/§9 补 ⑦)· `pitfalls.md`(🆕 **P0-11**)· `collab.md §4`(**三"角色"→四"类别"**)· `collab-detail.md`(三处:命名前缀/第四形态/**§12「主体」vs「会话类别」两轴消歧**)· `SKILL.md`(`description` + §2 铁律两条 + `last_change`)· 项目 `MEMORY.md` 对应行(⚠️ 原已 7,956 字符超上限 ⇒ **同步压缩三处、净 -4**,守住「只减不增」)。<br>🔴 **两处判据缺口只登记、⛔ 未擅自改**(触碰"用户定案"域,须先拍板):① 解析器把「接续」**一律判 worker** ⇒ 主会话的接续被吞掉(⚠️ 当初这么做是为堵**自指死结** ⇒ 正解**不是**"改成未知",那会**重新打开**死结);② 第④类 `[跟进]` **全库零命中**(落地清单已写进 §2.3.0c)。<br>🔴 **实测(⛔ 别用假读数)**:**不带 `COLLABD_CONFIG` 直接 import ⇒ `cand=0` ="没配置",⛔ 不是"逻辑坏了"**;带 `COLLABD_CONFIG=<工作区>/.workbuddy/collab/collabd.config.json` 复跑 ⇒ **8 条会话全 `role=worker`、`cand=0`**,`resolve_main → {"sid":"","source":"no-register"}` ⇒ **主会话候选 0**。⚠️ **「自测全绿 ⇒ 判据没问题」是错的**:那条用例只喂**合成样本**。<br>✅ **验收**:`--manifest` **34 文件 / 0 失败**|`--verify` **全绿**(10 条接线 `rc=0` / `collabd --where` 指包内 / selftest **PASS 39 / FAIL 0**)|`manifest.md` 行尾 **CRLF / 0 裸 LF**、表内 34 行、磁盘缺失 0。✅ **锁**:已 `--release-exec "会话类别口径修正-20261001"` 释放。 |
> | 2026-10-01 19:0x | `常驻定案统一-20261001`(**同一会话续棒 · 收尾**) | ✅ **补完最后一处盘符清零**:`scripts/lock/preflight-lock.sh` + `scripts/hooks/lock-guard-hook.py`(技能包与文档库 `07-scripts/` 各一份)。🔴 **结论:两份「有意不一致」,⛔ 别当 bug 修平** —— 判据是**位置**:包内那份住在技能包里、**推不出文档库根** ⇒ 走 `roots.env` +(`lock-guard-hook.py` 的)末位字面量兜底;库内那份住在 `<文档库>/07-scripts/` ⇒ **按 `__file__` 上溯两级即文档库根**(已改,库内自此零盘符)。已写进 `manifest.md` 做永久说明;规范化行尾后 `difflib` 比对确认**唯一差异就是引导块**,`py_compile` / `bash -n` 均过。<br>✅ **固化 `install.py --manifest`**(本轮为"重算逐文件表"**又**手搓了一次一次性脚本,上轮也是)—— 只重写**表 + 计数行 + 重算时间**,⛔ 不碰上方散文;**行尾随原文件**(本表是 CRLF,写成 LF 会造成无声的全文件 diff;实测保持 81 CRLF / 0 裸 LF)。<br>🔴 **顺手揪出并修掉一个「静默假绿」**:`scripts/selftest.py:617` 的 `t_tick_wired` 用例**写死了项目绝对路径**(`<某工作区>/.workbuddy/tools/wb-result-hook.py`)—— 既违反用户本轮规则,又因宿主接线已改指**包内**而**恒走"文件不在,跳过"** ⇒ 看着绿、其实什么都没验;已改按包内相对路径取 `hooks/wb-result-hook.py`,该用例从"跳过"变**真检**(4 项实跑通过)。<br>✅ **重算 + 复验**:`--manifest` **34 文件 / 语法失败 0**|`--verify` **全绿**(10 条接线 `rc=0` / `collabd --where` / selftest **PASS 39 / FAIL 0**)|包内**无 `tmp/`、无 `__pycache__`**(证实缺陷 ① 的修法真生效)。<br>🔴 **两条如实登记的遗留**:① `<工作区>/.workbuddy/tools/wb-result-hook.py` 是**陈旧死件**(35,975 B vs 包内 37,837 B;宿主三条接线实测**全指包内**)——按"未经确认不删他人文件"留在原地,登记为 **P2 清理候选**;② 一把**陈旧域锁**属 `[协作]N9复测-2248`(09-29 22:48,域 `ai1net-dsh-anywhere`)—— `--release-exec` 回显「另有 1 把锁属他人,按 R9 未动」,**只能由持有者本人释放**。<br>✅ **锁**:因本轮属**机制层**故按规矩**不带 `--domains` 独占**(故无域锁目录);已 `--release-exec "常驻定案统一-20261001"` 释放,回显「✓ 已释放全局执行锁」,`.exec-lock` 已消失。⚠️ **仍未起常驻投递**(`--supervise` 未拉),`guard` 自 09-29 23:59 停着,8788 看板未运行 —— 阻滞项是"四道闸门不全(无 `NEXT.md`)",⛔ 非本轮改动引入。 |
> | 2026-10-01 18:5x | `常驻定案统一-20261001` | ✅ **常驻定案已全量传导**(用户原话:「**协作与投递一直运行(常驻)** 因为可能不是所有 队列都是钩子产生的 ,而且定时任务的方案已经废弃了」)—— 改 `architecture.md`(结论表 + §2⑤ + §3 + §4 抬头 + §4.0/§4.1 作废框 + §5-1 + §8 + §9 新增教训)、`collab.md` §3/§8、`deploy.md`(结论表 + §0 + §2 步骤 ③④⑤ + 验收 6/7 + §5 重写)、`rules.md` §6、`pitfalls.md` P0-3、`collab-detail.md` 头部作废横幅、`SKILL.md` 铁律、项目层 `CODEBUDDY.md §1.5 D/E` 与 `MEMORY.md`。<br>✅ **按用户新规则「技能中要用相对路径」审计整包** —— 查出自述与实现不符:`SKILL.md` 写「**各脚本**先读 `roots.env`」,实际只有 9 份装了引导块 ⇒ **补齐 7 个非钩子脚本(共 16 份)+ 盘符字面量清零**(配置目录→`~/.workbuddy`;工作区→`DSH_WS_ROOT`/cwd;覆盖网 glob→`DSH_OVERLAY_LOG_GLOB`,⚠️ 原写死的 `E:/dsh-worker-dev` **本机不存在**=假警报源);`roots.env` 增写 `CODEBUDDY_CONFIG_DIR`。<br>🔴 **修掉 2 个真缺陷**:① `wb-result-hook.py` 的 `_WS_ROOT` 按 `3×dirname(__file__)` 推 ⇒ 包内这份**推出来就是技能包自己** ⇒ 钩子把包当工作区、包内长出 `tmp/`(同文件里本有正确的 `resolve_ws()` ⇒ 同一事实两套实现)⇒ 改为复用 `resolve_ws()` + 未知工作区即停手;② **重定向 + GBK ⇒ `print("⛔…")` 抛异常 ⇒ 顶层记 `fatal`、整轮失败**(实测连续 4 次)—— 🔴 **这正是常驻的拦路石**(常驻必须重定向 stdout)⇒ 7 份加**输出编码兜底** + `log()` 在配置缺失时拒写。<br>✅ **验收**:`--apply` 真装(带备份)|`--verify` **全绿**(10 条接线 `rc=0` / `collabd --where` / selftest **PASS 39 / FAIL 0**)|清单 **34 文件 / 0 失败**|复现原路径 **0 次 `fatal`**、包内干净|工作区 4 份副本逐字节对齐。<br>⚠️ **行为变更(如实登记)**:钩子驱动的投递由"静默崩"变**真会跑**;当前无 `NEXT.md` ⇒ 四道闸门不全 ⇒ **实际不会投出消息**。 |
> | 2026-10-01 18:2x | `技能整合检查与瘦身-20261001` | ✅ **整合独立复核全过**(`install.py --verify` 全绿:10 条钩子空载荷 `rc=0`、`collabd --where` 解析到包内、selftest **PASS 39 / FAIL 0**;两个原技能已在 `<工作区>/归档/技能-退役-20261001/`;活文件零旧技能名残留)+ ✅ **包瘦身**(`collab-detail.md` 删**原件 YAML 变更流水 9.4 KB** + 加**读法索引**;`manifest.md` 去流水账、修已过期的「未了①」;`taskgraph/pitfalls` 两处旧抬头改指;工作区 `goalctl.py` 旧文案对齐包内;清 `__pycache__`×2 + `hooks/bak-*` + 包内 `tmp/` ⇒ **包 1,098,918 → 1,000,604 B**)。<br>🔴 **途中修掉 1 个真缺陷**:`install.py --verify` 跑 `collabd --where` **未指定 cwd** ⇒ 按调用者 cwd 推导工作区 ⇒ **把技能包自己当工作区**、在包内写出 `tmp/supervise-inbox/`(这正是包内总冒出 `tmp/` 的根因)⇒ 已改为**在临时目录里跑 ②③ 并收尾清理**,复跑确认不再重生。<br>🔴 **上抛(⛔ 未擅自改)**:`architecture.md` 的「当前结论」表 + §5-1 说「投递**必须常驻**/已回退 09-29 版」,而**同文件 §4 抬头**、`collab.md §3`、`deploy.md §7`、项目层 `MEMORY.md` 都说「**钩子事件驱动 + 自动任务拨钟、⛔ 不常驻**」⇒ 同一文件内自相矛盾且与项目状态层相反;按该文件 §9 自己写的教训条款(**⛔ 不得擅自改"用户定案"**)**留待用户拍板**。 |
> | 2026-10-01 16:5x | `会话机制合并包-收尾2-20261001` | ✅ **§9.3 解阻并做完**:包内 `board.py --serve 8788 --takeover` 接管(旧 PID 21084 停、新 PID 35068;`healthz ok:true`、`/board.json` 真实快照)⇒ **两个原技能目录已用 `os.rename` 移入 `归档/技能-退役-20261001/`**(`agent-operating-rules` 原样保留)⇒ 三项复测全过(钩子空载荷 `rc=0`;`goalctl._collabd()` 与 `wb-result-hook._collabd_ctx()` **运行时**都解析到 `<包>/scripts/collabd.py`;`install.py --verify` 全绿 **PASS 39 / FAIL 0**)。<br>🔴 **② 有界实验(会话日志自愈)= 可行**:真实会话里对本会话自己的 `<sid>.log` 做 `os.rename` **一次成功**,宿主**紧邻下一次读数即重建同名文件并继续写入** ⇒ 推翻当天早些时候「句柄常开、间隙不可改名」的结论(那条只证明"正在写的那一瞬"不可改名)⇒ 已把「硬档就地回收」接进 `session-log-guard.py`(包内 + `07-scripts` 原件同语义同步;空载荷 `rc=0`、`--verify` 全绿、验收通道端到端跑出 `RECYCLE` 真记录)。<br>🔴 **③ 缺口(已写清、⛔ 未擅自改)**:哑掉检测**只覆盖"不许投+不当主会话",没有"重排"**;且哑会话仍被 `reconcile()` 计入"在跑" ⇒ **反而挡住**「执行者已消失 ⇒ 待重派」这条唯一的重排入口(详见 §9.7)。<br>⚠️ §9.5(`load_cfg` 报真实原因)**本轮未做**(留作独立小活)。 |
> | 2026-10-01 16:0x | `会话机制合并包-收尾-20261001` | ✅ **§9.1 指针全改**(`CODEBUDDY.md`/工作区 `MEMORY.md`/用户级 `MEMORY.md`/`board_ext.py`/`协同监管棒-SOP.md`/`agent-operating-rules` ×4 处)+ ✅ **§9.2 打包**(`归档/技能-退役-20261001.tar.gz` · **377,103 B** · 22 条目 · 含两份 `SKILL.md`)+ ✅ **§9.4 清单重算**(**33 文件 / 0 失败**)+ 🔴 **§9.3 阻塞,已按规矩回滚**(见下)。<br>🔴 **途中发现并修掉一个结构性缺陷**:原 `multi-session-collab/SKILL.md`(**96,723 B**)**根本没被并入包**,而包内 `architecture/deploy/pitfalls/taskgraph.md` 四份都写着「**主干判据在 `SKILL.md §x`**」⇒ **8 处悬空引用**(`§0.05/§0.5.2/§0.5.4/§0.5.5/§1.3/§1.4a/§3/§6` 实测只存在于原文件)。已**逐字带进** `references/collab-detail.md`(头部写明「以 `architecture.md` 为准」)+ 8 处全改指 + 清掉另 9 处旧名/旧路径活文本。(判定「看板卡住」≠ 本包坏了) |
> | 2026-10-01 15:40 | `会话机制合并包-任务4-5-6` | ✅ **§3 第 5 条完成**(补齐 `references/{collab,rules,forensics}.md` 三份,从架构/坑/任务图 + 两个原技能原文提炼,⛔ 非空壳) + ✅ **§3 第 4a 条完成**(两处硬编码回落路径已改指新包:`goalctl.py:_collabd()` 与 `wb-result-hook.py:_collabd_ctx()` —— **新落点优先、旧落点留作过渡兜底**,并已 `cp` 同步包内副本 + 重打 `roots.env` 补丁;实测两者都解析到 `<包>/scripts/collabd.py`、空载荷 `rc=0`)。🔴 **因会话诊断日志到 8.06 MiB(硬档)停手** ⇒ **第 4b/4c/4d、第 6 条留给下一棒**(见 §9)。 |
> | 2026-10-01 15:1x | `会话机制合并包-任务234b` | ✅ **§3 第 1、2、3 条全部做完**:① `install.py` 两处缺陷已修并**沙箱验收全过**(含原先🔴的第 3、4 项)② **`--verify` 全绿**(钩子 10/10 `rc=0`、`collabd --where` 正常、selftest **PASS 39 / FAIL 0**)③ 🔴 **真机 `--apply` 已执行**:全局钩子已从 `07-scripts` 切到包内(0 条残留旧指向、`decision_bridge` 4 条原样保留),装后自检全绿、闸门日志实测在推进 ⇒ **本包现已是主技能**。<br>⚠️ **途中修掉 1 个真阻塞缺陷 + 1 个生产回归**(见 §8)。⛔ **未做**:§3 第 4 条(归档原技能+改 2 处回落路径)、第 5 条(3 份 references)、第 6 条收尾。 |
> | 2026-10-01 14:59 | `会话机制合并包-任务234` | 🔴 **硬档停手**(日志 **8.02 MiB / 10 MiB**)。**包已可用、沙箱验收 5 项过了 4 项**;⛔ **未做**:真机 `--apply`、`--verify`、归档原技能、3 份 references。**install.py 有 2 处已定位未修的缺陷**(见 §3 第 1 条)。 |
> | 2026-10-01 14:2x | `会话机制合并包-任务0` | ✅ 任务 0(方案落盘)+ 任务 1(建包骨架 27 文件,全 `cp`)完成。 |
>
> 🔴 **本包 §1 = 已实测的现状(⛔ 不要重跑);§2 = 已验/未验;§3 = 未做完的活(按序);§4 = ⛔ 不要重做;§6 = 硬阻塞点(先读)。**
---
## §1 已实测的现状(直接引用,⛔ 不要再跑一遍)
### 1.1 用户原话与目标(2026-10-01 两轮)
> 第一轮:「整合 会话机制相关技能为一个skill包 , 在将多会话协作机制 也整合到这个技能包,要求换一台电脑的 workbuddy 上运行也能自动完成配置,让所有会话遵循 会话机制,并且可独立使用 多会话协作」
> 第二轮:「1、检查 和测试 整合技能所有功能是否正常,确认后将整合技能作为主技能 使用,原技能全部打包存放」
### 1.2 包现状 `~/.workbuddy/skills/session-mechanism/`(**已建成**)
- **30 个文件**:`SKILL.md`(新写 · 两段式:会话机制/多会话协作)+ `install.py`(新写)+ `references/`(architecture/deploy/pitfalls/taskgraph/manifest)+ `scripts/hooks/`(6) + `scripts/lock/`(4) + `scripts/collab/`(10) + `scripts/forensics/`(1) + `assets/`(2)。
- 机制脚本**一律 `cp` 拷入(⛔ 无 `mv`)**,源路径全部原样可用;md5 与源逐字节一致(清单 `references/manifest.md`)。
- `references/manifest.md` 含**逐文件 provenance** + **「暂未纳入」清单**(tools/ 监控成本类 16 项、collab/ 排期文件、全部 `*.bak-*`)——⛔ 不是遗漏,是**判定不纳入**。
### 1.3 🔴 本轮最重要的技术发现(**推翻接续包 §3.4 第 4 条**)
原设计「唯一实现放包内 + `07-scripts/<同名>` 改**转发壳**」**在本机不可行**,两条硬事实:
1. **9 个机制脚本里 8 个按自身位置推导根目录**(实测行号):
`handoff-guard.sh:37` `ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"` /
`preflight-lock.sh:25` 同型 / `handoff-status.py:14` `dirname(dirname(__file__))` /
`session-log-guard.py:107` `stop-dialog-guard.py:74` `bash-output-guard.py:43` `skill-load-guard.py:139`(`WS_FALLBACK` 上溯)/
`lock-guard-hook.py:41`(env+**硬编码默认**)。
⇒ **改壳会改 `$0`、`mv` 会改 `__file__`** ⇒ 锁根从 `/d/github/.../dsh-server-docs` 直接挪到 `~/.workbuddy/skills` ⇒ 锁目录全错。
这正是 `wb-result-hook.py` docstring 记过的原事故:**「一次搬迁后指错 ⇒ 台账静默写到别处而测试全绿」**。
2. 🔴 **`settings.json` 的 hook 条目没有 `env` 字段**(实测 schema:`{matcher, hooks:[{type,command,timeout}]}`)⇒ **无法靠钩子向脚本注入环境变量**。
**解法(本轮已落地)**:**包内脚本根目录外置** —— 新增 `<包根>/roots.env`(`install.py` 生成),脚本**先读它、再回落按位置推导**。
已打补丁 8 份(全部语法通过):`handoff-guard.sh` `preflight-lock.sh` `handoff-status.py` `session-log-guard.py` `stop-dialog-guard.py` `bash-output-guard.py` `skill-load-guard.py` `lock-guard-hook.py` + `wb-result-hook.py`(`WB_RESULT_HOOK_WS` 增加 `DSH_WS_ROOT` 回落)。
补丁器(一次性脚本,⛔ 不入库):`$WS/tmp/inv-20261001/patch-roots.py`(精确锚点替换,锚点必须唯一命中,幂等)。
### 1.4 归档原技能的**真实依赖**(⛔ 直接归档会打碎协作机制)
`grep` 实测:两个原技能被引用很多,但**只有 2 处是真实代码依赖**(其余是文档/记忆):
- `$WS/.workbuddy/collab/goalctl.py:460` → `base / "skills" / "multi-session-collab" / "scripts" / "collabd.py"`(**已支持 `COLLABD_SCRIPT` env 覆盖**)
- `$WS/.workbuddy/tools/wb-result-hook.py:247` → `r"E:/ProgramData/.workbuddy/skills/multi-session-collab/scripts/collabd.py"`(**已支持 `COLLABD_PATH` env 覆盖**)
⇒ **归档前必须**把这两处的回落路径加/改指到新包(`scripts/collab/collabd.py`),并**重新同步包内副本**。
⚠️ `wb-result-hook.py` **是全局钩子**(`PreToolUse ^Bash$` + `UserPromptSubmit` + `SessionEnd` 三条接线都指向它,实测)⇒ 改它 = 改**所有会话每次工具调用**都会过的钩子;改完**必须立即用空载荷复测 `rc=0`**。
### 1.5 `install.py` 现状(**核心交付物,已写但未定稿**)
- 用法:`--dry-run` / `--apply` / `--verify` / `--uninstall`;`--workspace/--docs-root/--code-repo`。
- 自解析:`sys.executable` + `CODEBUDDY_CONFIG_DIR`(⛔ 无硬编码 python 路径、⛔ 无硬编码盘符)。
- 声明表驱动 **10 条接线**(`HOOKS`),**⛔ 显式排除** `decision_bridge.py` 的 **4 条**(属 `ai1net-decision-laya` 另一条线);`--dry-run` 会**打印被排除条数**供目视核对。
- 幂等(先删自己的旧条目再插,**按脚本名认旧落点**)/写前备份/`--uninstall` 还原/写 `install.log`/写 `roots.env`/工作区初始化(由 example 生成,**已存在则不覆盖**)。
- 判语:**「`--apply` 每次都备份 ⇒ 最新那份其实是已装状态」** —— `--uninstall` 必须挑**最早那份不含本包痕迹**的备份(本轮已就此改成 fail-closed:挑不到就**拒做**,⛔ 不拿已装状态糊弄)。
---
## §2 沙箱验收:**已过 4 项 / 未过 1 项**(⛔ 不要再重跑前 4 项)
做法:把 `settings.json` **拷进沙箱目录**,用 `CODEBUDDY_CONFIG_DIR=<沙箱>` 跑 —— **现场全局配置零风险**。
| # | 判据 | 结果 |
|---|---|---|
| 1 | `--dry-run` 不写盘 + diff 与预期一致 | ✅ **过**:rc=0;删旧 **10** 条(07-scripts/tools)、增新 **10** 条;**沙箱 settings.json md5 装前=装后**(未写盘);被排除 **4** 条 |
| 1b | 幂等:连装两遍零改动 | ✅ **过**:第二遍 md5 与第一遍**完全相同**,diff 行数 **0** |
| 1c | 结构核对 | ✅ **过**:总接线 **14** 条;**指向 07-scripts 的机制钩子 = 0**;`decision_bridge` **4** 条原样保留;无重复条目(按 `command` 计数出现的 4 个"重复"是**同一脚本挂在不同事件上**,正常) |
| 2 | `--verify`(6 钩子空载荷 rc=0 + `collabd --where` + `selftest`) | ⬜ **未做** |
| 3 | `--uninstall` 后与装前**逐字节**相同 | ✅ **过(15:15 复测)**:`-orig` 固定名备份 + 微秒时间戳备份 ⇒ `--uninstall` 还原后 `cmp` 逐字节相同;改名/换目录场景同样通过。**修法已落地见 §3 第 1 条** |
| 4 | 包拷到**另一个目录**再 `--apply` ⇒ 接线指向新路径 | ✅ **过(15:15 复测)**:改名包 `--apply` ⇒ 10 条全指向新路径、`07-scripts` 残留 **0**、`decision_bridge` **4** 条保留;`--uninstall` 逐字节还原。判据已按**包实际路径**(运行时从 `__file__` 推导)判"是否已接管" |
**沙箱现成可复用**:`$WS/tmp/inv-20261001/sbx-settings/`(`settings.json` + 日志),⛔ 不要删,接着用。
---
## §3 未做完的活(**按序做,一次只做一件**)
> 🔴 **进度(2026-10-01 15:1x)**:第 **1、2、3** 条 ✅ **已完成**(明细见下);**下一条从第 4 条开始**。
**1、✅【已修 · 15:15】`install.py` 两处缺陷**
a. **备份唯一化 + 原状备份单列**:首次安装(当前 `settings.json` 无本包痕迹)时写 `settings.json.bak-session-mechanism-orig`,**已存在则⛔不覆盖**;其后每次 `--apply` 写**带微秒**的时间戳备份。`--uninstall` **优先用 `-orig`**。
b. **"自己的痕迹"改按脚本名判**:本轮已往包内加了 `has_our_trace(text)`(按 `OWN_BASENAMES` 判)但**尚未接线** ⇒ 把 `--uninstall` 与其它判"痕迹"的地方从 `OWN_MARK`(路径片段)改成 `has_our_trace()`。
**2、✅【已过 · 15:15】`--verify`**:包内 10 条钩子空载荷**全 `rc=0`**;`collabd.py --where` `rc=0` 且**正确报出配置来源**;`selftest.py` **PASS 39 / FAIL 0**。⚠️ 无 `COLLABD_CONFIG` 时 `--where` 仍 `rc=0`(回落 DEFAULTS 并明确标注)⇒ ⛔ 别把"没配置"读成"包坏了"。
**3、✅【已执行 · 15:15】真机 `--apply`(= 用户要的「作为主技能使用」)**
`"$PY" "<包>/install.py" --apply --docs-root "D:/github/dsh_shenxian/dsh-server-docs"`
装完**立刻 `--verify`**;⚠️ 这一步会把全局钩子从 `07-scripts` 切到包内 ⇒ **影响所有正在跑的会话的每一次工具调用** ⇒ 动手前一句话说明、装完立刻复测、异常立刻 `--uninstall`。
**4、归档原技能(用户明确要求)**
a. **先**按 §1.4 改那 2 处回落路径 → 指向新包,**并重新 `cp` 同步包内副本**;改完**空载荷复测 `rc=0`**。
b. 打包:`tar -czf "$WS/归档/技能-退役-20261001.tar.gz" -C ~/.workbuddy/skills multi-session-collab workbuddy-session-forensics`
c. **然后**才从 `~/.workbuddy/skills/` 移走那两个目录(⚠️ **移走前**确认 `agent-operating-rules` 仍在,它是**声明依赖,⛔ 不并、⛔ 不移**)。
d. 改指针:`$WS/CODEBUDDY.md` 头部「协作机制:唯一权威 ⇒ `multi-session-collab/references/architecture.md`」**改指新包**;`.workbuddy/memory/MEMORY.md` 内同型指针一并改。
**5、补 3 份 references**(`SKILL.md` 已引用,**现在是指向空处**):`references/{collab,rules,forensics}.md`。
⚠️ ⛔ 不要写空壳(写空壳=文档说谎);从包内已有的 `architecture.md`/`pitfalls.md`/`taskgraph.md` 与 `multi-session-collab` 原文提炼。
**6、收尾**:`references/manifest.md` 重算(改过补丁)→ 更新本接续包 §0 → 今日日志 → 三层沉淀 → **反序释放锁**。
---
## §4 ⛔ 不要重做 / 不要碰
- ⛔ **不要重跑**:三源目录盘点(§1.2/1.3 已实测)、`install.py` 沙箱前 4 项验收(§2 已过)、`patch-roots.py`(幂等,已跑)。
- ⛔ **不要 `mv` 机制脚本**;⛔ **不要做转发壳**(§1.3 已证不可行 —— 会挪走锁根)。
- ⛔ **不要动** `decision_bridge.py` 所在线(`ai1net-decision-laya`)—— 它只是恰好出现在同一张钩子表里(4 条)。
- ⛔ **不要动自动化排期**(用户明令)。⚠️ 唯一例外=本包自己登记的接续会话。
- ⛔ **不要在会话里起常驻后台任务**(会把日志推过 10 MiB ⇒ 界面静默哑掉)。
- ⛔ **不许报未经实测的系数**;本包只写"实测到的计数/字节"。
- ⛔ **不要在没有沙箱验证的情况下直接改现场 `settings.json`**。
- ⛔ **不要删** `$WS/tmp/inv-20261001/`(含补丁器、构建清单脚本、沙箱)。
---
## §5 关键路径与命令
```
PY = E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe
WS = E:/ProgramData/AIProject/ai1net-dsh-server
DOC = D:/github/dsh_shenxian/dsh-server-docs
PKG = E:/ProgramData/.workbuddy/skills/session-mechanism
BASH = E:/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin/bash.exe # ⚠️ 裸 bash 会落到 WSL ⇒ 假阴性
锁(机制层=独占,⛔ 不带 --domains):
bash "$DOC/07-scripts/handoff-guard.sh" --claim-exec "<会话名>"
释放:--release → --release-exec "<会话名>"
```
---
## §6 本机踩坑(已踩到,⛔ 别再踩)
1. 🔴 **`subprocess.run(["bash", …])` 在本机落到 WSL 启动器** ⇒ 输出 UTF-8 乱码、路径里 `/` 被吃 ⇒ `bash -n` **假阴性**(脚本明明没错却报 FAIL)。必须显式用 `$BASH`。
2. ⚠️ **`ls -1 目录A 目录B | sort` 把两个目录的输出合并排序**(无分组标题)⇒ 极易误读成"某目录是空的"。**多目录必须分开列**。
3. ⚠️ 补丁器踩到过两次:把 **Python 引导块插进 `.sh`**;引导块插在 **`from __future__ import` 之前**(有的文件把 `__future__` 放在注释区之后,⛔ 不能只跳过文件开头)。两处都已修,规则:**引导块只插 `.py`,且插到"全文件最后一个 `__future__` 之后"**。
4. ⚠️ 沙箱备份**同秒同名被覆盖** ⇒ 原状备份必须单列(§3 第 1.a 条)。
5. ⚠️ `settings.json` 的 hook 条目**无 `env` 字段**(实测 schema)⇒ 别指望用 env 给钩子传参。
---
## §7 验收判据(全过才算交付)
1. `install.py --dry-run` diff 与预期一致;真装后**再跑一遍零改动**。→ ✅ **已过(沙箱)**
2. 装机后 `--verify`:包内钩子空载荷全 `rc=0`;`collabd --where` 正确;`selftest` 见 `FAIL 0`。→ ⬜ 未做
3. `--uninstall` 后与**装前**备份**逐字节相同**(`cmp`)。→ 🔴 未过(修法 §3.1)
4. 包拷到**另一目录**再装 ⇒ 接线指向新路径且 `--verify` 全绿。→ ⚠️ 部分(§2 #4)
5. 原技能归档后:`goalctl.py` 与 `wb-result-hook.py` **仍能定位到 `collabd.py`**(改路径后复测 `rc=0`);`CODEBUDDY.md` 指针已改指新包。
---
## §8 本轮(15:05–15:15)实测记录 —— ⛔ 别重做
### 8.1 🔴 新发现①:**包内脚本目录层级放错 ⇒ `--verify` 恒红**(已修)
- **现象**:`--verify` 的 selftest **PASS 32 / FAIL 7**,7 项全是 `FileNotFoundError`;
同一个 selftest 从**原件**(`multi-session-collab/scripts/selftest.py`)跑是 **PASS 39 / FAIL 0**。
- **真因**:`board.py` 与 `selftest.py` 按 **`HERE.parent / "assets"`** 与 **`HERE.parent / "scripts"`** 定位
—— 它们假设自己在 `<技能>/scripts/` 这一层。包把它们放进了 `scripts/collab/` ⇒ 两个路径双双落空。
- **修法(对机制脚本零改动)**:**把 11 个协作脚本拉平到 `<包>/scripts/` 顶层**,删掉 `collab/` 子目录
⇒ 实测立刻 **PASS 39 / FAIL 0**。
- 🔴 **教训(写进规矩)**:**拷脚本必须拷到 `scripts/` 顶层,⛔ 不要再建组子目录**
(`hooks/` `lock/` `forensics/` 可以留,因为它们靠 `roots.env` 定位、不依赖 `../assets`)。
- 牵连改动:`install.py --verify` 两处路径、`SKILL.md` 一处路径、`references/manifest.md` 全表。
### 8.2 🔴 新发现②:**工作区协作配置是坏 JSON ⇒ `collabd` 静默回落 DEFAULTS**(已修)
- **现象**:`collabd.py --where` 报「未找到部署配置(…不存在)」,但**文件明明在**。
- **真因**:`$WS/.workbuddy/collab/collabd.config.json` 第 53 行把 `"按工作区分线"` 写成了
**ASCII 双引号**(夹在 JSON 字符串里)⇒ `json.loads` 抛 `JSONDecodeError`;
而 `load_cfg()` 的 `except Exception: pass` **静默吞掉**,于是回落 DEFAULTS 并**谎报"不存在"**。
- **影响面(实测对比)**:DEFAULTS 下 `LIVE`/`TG` 指向 `tmp/supervise-inbox/{realtime.md,taskgraph.json}`
—— **两个文件都不存在**;配置文件里指的才是真落点 `交付物/本机协作-实时状态.md`、
`交付物/任务图.json`(都在,13:48 还被写过)。⇒ 这是**当天 13:55 的一次编辑引入的回归**
(13:48 还在正常写 `交付物/本机协作-实时状态.md`)。
- **修法**:改成中文引号 `「」`;已 `json.loads` 校验通过,`--where` 现已正确报出
`配置来源 = …collabd.config.json`。原坏件备份在 `<WS>/.workbuddy/collab/bak-collabd-config-20261001/`。
- ⚠️ **遗留(⛔ 本轮未动,留给下一棒判)**:`collabd.load_cfg()` 对"文件存在但解析失败"
**既不区分也不告警**,还谎报"不存在"。建议改成**报出真实原因**(解析失败 ≠ 不存在),
否则下一次同样的手误还会静默降级。改它要**同时改两处**(`07-scripts` 原件 + 包内副本)并保持同步。
### 8.3 真机切换的实测读数(15:13:17 执行)
- 切换前 `settings.json` md5 `7dca0aeb798e52728dc93dce2e7804b8`;`-orig` 原状备份已写
`~/.workbuddy/settings.json.bak-session-mechanism-orig`。
- 切换后:总接线 **14**、指向 `07-scripts` **0**、指向包内 **10**、`decision_bridge` **4**(原样保留)。
- ✅ **语义等价性已逐条核对**:事件 / matcher / 额外参数(含 `-S`)/ 超时 / **解释器** 全一致,只有脚本目录不同。
- ✅ **端到端取证**:装后 `bash-guard.log` 由 15:13:16 → **15:13:39** 且新增 `event=PreToolUse`
行、`stop-dialog-guard.log` 同步推进、`state.py` 报「关键闸门在册」⇒ 包内钩子在**生产链路真跑**。
- 回滚:`python <包>/install.py --uninstall`(`-orig` 已就位,逐字节还原已验收)。
### 8.4 下一棒从哪开始
1. **§3 第 4 条**(归档原技能):⚠️ **必须先**改 `goalctl.py:460` 与 `wb-result-hook.py:247` 两处回落路径
→ 指向 `<包>/scripts/collabd.py`,**并重新 `cp` 同步包内副本**,改完**空载荷复测 `rc=0`**;
再打包 `归档/技能-退役-20261001.tar.gz`;最后才移目录(⚠️ 确认 `agent-operating-rules` 仍在 ⇒ ⛔ 不并、不移)。
2. **§3 第 5 条**:补 `references/{collab,rules,forensics}.md`(⛔ 不许写空壳)。
3. **§3 第 6 条**收尾:本接续包 §0 / 日志 / 三层沉淀 / 反序释放锁。
4. 可选:处理 8.2 的遗留(`load_cfg` 报真实原因)。
---
## §9 下一棒剩余步骤(**从 4b 开始 · 逐条做 · 一次一件**)
> 🔴 **本轮(15:40)已完成**:§3 第 1、2、3、5 条 + 第 4a 条(两处回落路径)。**日志硬档停手,剩余如下的机械动作。**
> ⛔ 先读 §4「不要重做 / 不要碰」清单;⛔ 仍不要重跑盘点与沙箱验收。
### 9.1 第 4d 条 · **先改指针**(★ 必须在 4c 之前 —— 否则目录一移走,指针就悬空)
需要改指的**活文件**(其余命中都在 `traces/` / `changes-detail/` / `modify_backup/` / `归档/` / 历史日志里,⛔ **不要动**):
1. `$WS/CODEBUDDY.md` 头部「**协作机制:唯一权威 ⇒ `multi-session-collab/references/architecture.md`**」⇒ 改指 `~/.workbuddy/skills/session-mechanism/references/architecture.md`。
2. `$WS/.workbuddy/memory/MEMORY.md` 里同型指针(`multi-session-collab §0.5.5/§1.4a` 那条)⇒ 改指新包同名位置(`references/collab.md` / `references/architecture.md`)。
3. `~/.workbuddy/MEMORY.md`(跨项目长期记忆)里的 1 处引用。
4. `$WS/.workbuddy/collab/board_ext.py` 里 1 处(`multi-session-collab/scripts/board.py` 契约注释)。
5. `$WS/交付物/任务图.json` 与 `$WS/交付物/协同监管棒-SOP.md` 各 1 处。
6. 检查 `~/.workbuddy/skills/agent-operating-rules/SKILL.md` 是否也有引用(本轮未确认)。
⚠️ `交付物/` 下的**历史过程件**(`多会话协同*-20260929.md` 等)⇒ **⛔ 不改正文**,按其自身规矩只加头部状态块指向新包。
### 9.2 第 4b 条 · 打包
`tar -czf "$WS/归档/技能-退役-20261001.tar.gz" -C ~/.workbuddy/skills multi-session-collab workbuddy-session-forensics`
(⚠️ 打包前确认 4d 已做完。)
### 9.3 第 4c 条 · 移走两个原技能目录 —— ✅ **2026-10-01 16:5x 已做完**(正解=先让包内看板 `--takeover` 接管,再移)
> ✅ **实测结果(第 5 棒)**:把顺序改成「**① 包内 board 接管(=一并完成"停旧+起新")→ ② `os.rename` 移目录 → ③ 复测**」
> —— 比原计划的「先停 → 移 → 再启」更安全:**新代码先验证可用,失败还能立刻回退旧实例**(此时目录尚未动)。
> - ① `"$PY" "<包>/scripts/board.py" --serve 8788 --takeover`(cwd=`<包>/scripts`,env `COLLABD_CONFIG=<WS>/.workbuddy/collab/collabd.config.json`)
> ⇒ 输出 `✓ 接管:已停旧看板 PID 21084(成功)`;新 PID **35068**;`/healthz` `{"ok":true}`、`/` `HTTP 200 / 84,874 B`、`/board.json` 是**真实快照**(goal+V1–V5 都在)⇒ 只有 1 个实例在听 8788。
> - ② `os.rename` 两个目录 → `归档/技能-退役-20261001/`(⛔ 不用 `mv`)⇒ **一次成功**(旧实例句柄随停进程释放)。
> - ③ 三项复测全过(读数见 §0 最新行)。
> ⚠️ **启动方式**:本机唯一可行=会话后台任务 + stdout 全重定向(`> <WS>/tmp/board-serve.out.log 2>&1`);旧实例(08:05 起的那个)也是同款血统、存活 9 小时 ⇒ 该模式在本机**实测 durable**。
> 🔴 **2026-10-01 16:0x 实测**:`os.rename` 移 `multi-session-collab` **恒失败** `WinError 5/32`,
> 而**每个文件单独改名都成功** ⇒ 定位到 **`multi-session-collab/scripts/` 被另一个进程占用**。
> **占用者=看板服务 PID 21084**:命令行 `python board.py --serve 8788 --takeover`(**相对路径** ⇒ cwd 就在该 `scripts/`),
> 起于 **08:05:56**,端口 8788 实测在听、`HTTP 200 / 84,874 B`(=`board.html`)⇒ **它是活的、是用户正在看的看板**。
> ⛔ 不可强移(会打断看板);按 §9.3 规矩**已回滚**:`workbuddy-session-forensics` 移回原地、`multi-session-collab` 未被改动。
> ⇒ **正解(三步,按序)**:
> ① 停看板(结束 PID 21084,或它自己的 stop 入口);
> ② `python os.rename` 把两个目录移到 `$WS/归档/技能-退役-20261001/`(tar 已在 §9.2 完成,⛔ 不必重打);
> ③ 用**包内**路径重启看板:`"$PY" "<包>/scripts/board.py" --serve 8788 --takeover`(cwd 设 `<包>/scripts`),
> 起来后复测 `HTTP 200` + 三个读数(钩子空载荷 `rc=0`、`goalctl` → `<包>/scripts/collabd.py`、`install.py --verify` 全绿)。
> ⚠️ **教训(值得写进技能)**:归档一个「被服务依赖的技能目录」时,**先查有没有进程正跑在里面**
> (判据=相对路径启动的服务 ⇒ cwd 即句柄),否则会得出"移不动=权限问题"的错误结论。
- ⚠️ **移走前**确认 `agent-operating-rules` **仍在** `~/.workbuddy/skills/` —— 它是**声明依赖,⛔ 不并、⛔ 不移**。
- 用 python `os.rename` 移,⛔ 不用 `mv`(见 §6 第 1 条)。
- 移走后**立刻复测**:`wb-result-hook.py` 空载荷 `rc=0`、`goalctl.py` 能解析到 `<包>/scripts/collabd.py`、`install.py --verify` 全绿。
🔴 这两个脚本的回落路径**已在本轮改成新包优先**,所以移走原技能后仍应正常;**若异常 ⇒ 立刻把目录移回来报障**。
### 9.4 第 6 条 · 收尾
`references/manifest.md` 重算(本轮已重算过一次,4a 又改了 `goalctl.py`/`wb-result-hook.py` 两份 md5 ⇒ **必须再算**)→ 更新本接续包 §0 → 当日日志 → 三层沉淀 → **反序释放锁**(`--release` → `--release-exec "<会话名>"`)。
### 9.5 可选(低优先)
`collabd.load_cfg()` 对「配置存在但解析失败」不区分、还谎报"不存在" ⇒ 建议改成报真实原因;**要同时改 `07-scripts` 原件与包内副本并保持同步**。
### 9.6 候选(本轮登记 · 由用户质疑引出)—— ✅ **2026-10-01 16:5x 已实测并落地,见 §9.7**
用户问「工具日志不是已经拦截了吗」⇒ 核实结论:`session-log-guard.py` **只叫停、不降速**(零删改动作,只注入提醒);10 MiB 上限真实(当日已有 6 个会话日志卡在 10,485,7xx B)。
⇒ **候选**:把「**会话中途回收日志**」做成正式机制(重命名让宿主重建),使会话**不必因撞顶而停手**。
⚠️ 上手前必须先实测两条:① 宿主**正在写**时改名是否安全(现有证据只覆盖**冻结**日志);② 丢掉被拒写的那段诊断内容的代价是否可接受。
⛔ 未实测前⛔ 不要写进机制、⛔ 不要报"已经解决"。
---
## §9.7 第 5 棒结果(2026-10-01 16:5x)—— ② 有界实验 + ③ 缺口
### A. ②「会话日志自愈」—— ✅ **可行,已接入机制**
**实测(真实会话,非 scratch)**
- 对本会话自己的 `logs/2026-10-01/sdk/conversations/63f5e790-….log` 做 **一次** `os.rename` ⇒ **成功**(无 `WinError 32/5`)。
- 紧邻的下一次读数:**原文件名已回来**(宿主重建,50,073 B)且继续增长;旧件**冻结**在 1,728,992 B。
- ⇒ 判语:宿主**不是"句柄常开"**,而是**每次日志批写开-写-关** ⇒ **工具调用的间隙里句柄是关着的**。
⚠️ 当天更早那条"不可改名"的结论**只对"正在被写的那一瞬"成立**(scratch 复现里子进程一直在 append)。
**落地(机制改动 · 只动了 1 份文件)**
- 位置:`<包>/scripts/hooks/session-log-guard.py`(+ 文档库原件 `07-scripts/session-log-guard.py` **同语义同步**)。
- 规则:**到硬档(8 MiB)先试一次就地回收** —— 把 `<sid>.log` 改名为 `<sid>.log.recycled-<YYYYMMDD-HHMMSS>`
⇒ 成功:**复位本会话档位**(不复位则后半程再也报不出来)+ 注入 ♻️ 提示("可继续、不必停手"),**直接结束**;
⇒ 失败:**一个字都不改**,逐字退回原「停手+建接续」口径(fail-safe)。
- 收窄与防撞:⛔ 只动**本会话自己那一份**(`_log_path` 按 `session_id` 精确拼名);⛔ **只试一次**(放在档位去重之后 ⇒ 每个档位天然只试一次),⛔ 不 sleep、不重试、不降级成删/截断。
- 与清扫器不冲突:后缀**不以 `.log` 结尾** ⇒ `wb-logcap-sweep.py` 的 `*.log` 匹配**天然不重叠**(不会被二次搬运)。
- 开关:`DSH_SLG_NO_RECYCLE=1` ⇒ 只关回收、**保留**软/硬档叫停。
- **验收(都做过)**:空载荷 `rc=0` 零输出 ✅|真实形状载荷(未达档)`rc=0` 零输出 ✅|`install.py --verify` 全绿 **PASS 39 / FAIL 0** ✅|
验收通道(`tmp/.session-log-guard.verify.json` 收窄到本会话 sid)**端到端跑出真记录** `RECYCLE|size=279862 B|…log -> …log.recycled-20261001-165257` + ♻️ 注入 1,077 B ✅(跑完**立即删除**通道文件,已复核不存在)。
- ⚠️ **途中修掉 1 个真缺陷**:`_recycle_note` 里字面量 `97.8%` 未转义 ⇒ 抛 `TypeError: not enough arguments for format string`
(回收仍生效、只是提示发不出;已在两副本改 `%%` 并加注释)。
- ⚠️ 另一处已修:`_recycle` 的阈值**必须传实际生效的 `hard`**(⛔ 不写死模块常量),否则验收通道只能验"叫停"、**验不到"回收"**。
**遗留(⛔ 本轮未做)**
- 回收后**那一轮会话会不会自己继续**,仍**未验证**(本轮只证明了"写入不中断",没证明"已失败的那轮会重跑")。
⇒ 仍必须靠 §B 的「哑掉 → 重排」兜底 —— 但**恢复写入**已把"沉默窗口"从"永久"压到"≤ 1 分钟(清扫器周期)"。
- 回收阈值暂定=硬档(8 MiB),⛔ **未验证"更低阈值(如 5 MiB)是否更优"**(更低=更频繁改名、更高=更逼近平台上限)。
### B. ③「日志撞顶 ⇒ 哑掉」这一型的覆盖情况 —— 🔴 **部分覆盖,缺口已定位,⛔ 未改代码**
**已有的覆盖(都在 `collabd.py`,均已实测在跑)**
1. **检测**:`_deaf_sids()` —— 取每个会话**最新一天**的日志,`≥ 9.5 MiB` 判哑(只读,扫不到=空集)。
2. **不许投**:两条投递路径(`_deliver_str` ≈1016 行、另一条 ≈1133 行)命中哑 ⇒ `skipped="target-deaf"` + `need_user(...)`
⇒ **不记假绿**,并把"该点哪一下"落到 `NEED-USER.md`。
3. **不当主会话**:`_pick_live()`(≈1902 行)把哑会话从"主会话候选"里剔除。
**🔴 缺口(没有"重排"这一层)**
- 处置动作**只有"喊用户换会话"**(`need_user` 原文:「请把主会话换到一条新会话(旧的那条只能弃用)」)⇒ **恢复链条要靠人**。
- 🔴 **更糟的是它会反过来挡住唯一那条重排入口**:`reconcile()`(≈2190 行)的僵尸判据是
「台账 `running` **且除观察者外无人在跑**、且无 `IN_PROGRESS`」才标 `blocked`+`待主会话重派`。
而哑会话若在宿主库里**仍记 `working`**(会话被冻在半途)⇒ 它**被当成"有人还在跑"** ⇒ 僵尸判据**永不触发** ⇒
形成死角:**既投不进去、又不判死、也没人重派**。
(注:若哑会话在库里已转成 `completed`,僵尸判据**可以**触发 —— 所以是"**条件性覆盖**",不是全无。)
- 且 `collabd.py` **自身设计上就不派活/不开新会话**(其 docstring 第 12 行明写),⛔ 不能指望它自动补一条接续。
**最小修法(⛔ 本轮**未实施**,留给下一棒/用户点头)**
- 在 `reconcile()` 里把 `_deaf_sids()` 命中者**从"在跑"集合里剔除**(即"哑会话不算执行者"):
`allrun` 剔掉哑 ⇒ ① 它们不再被"启动对账收编"成 `running`(投不进去的会话本来就不该算执行者);
② 僵尸判据得以触发 ⇒ 标 `blocked` + `block_reason="…待主会话重派"` ⇒ **队列里看得见**,主会话/监管棒才会重派。
- 量级:**一处改动(约 1–2 行)**,复用现成的 `_deaf_sids()`,⛔ 不新增常驻、⛔ 不动排期、⛔ 不新增阈值。
- ⚠️ 注意比较口径:`_deaf_sids()` 返回**完整 sid**,而 `reconcile` 里 `me` 用的是前 8 位 ⇒ 剔哑时要**按完整 sid 比**。
- ⚠️ 前置:**先把 ② 的回收跑一段时间**(它把撞顶概率大幅压低)⇒ 再决定这条兜底要不要上(它只服务"已经哑了"的存量与漏网)。
- ⛔ 另有一条**已排除**的做法:让 `collabd` 直接开接续会话 —— **违反它自己的设计约束**(不派活/不开新会话),且触"⛔ 不要动自动化排期"。
### C. 本轮 ⛔ 未做(明确不碰)
- §9.5 `collabd.load_cfg()` 对"配置存在但解析失败"不区分、还谎报"不存在"(要同时改 `07-scripts` 原件与包内副本)。
- 自动化排期、`decision_bridge` 那条线、`wb-logcap-sweep.py` 与计划任务 `WorkBuddy-LogCapSweep`(⛔ 一个字没动)。