Files
dsh_ai1net_server/交付物/唤醒交接-机制能否跟着换主会话-20260930.md
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

12 KiB
Raw Permalink Blame History

唤醒交接:机制能不能跟着换主会话?—— 实测结论

时间:2026-09-30 18:03–18:25(本机)|会话:6ecf6d98(本棒)|新主会话:67dfd365 用户原话:「可以试试,创建接续主会话后 能否在接续主会话上继续这个机制,旧的主会话停」 产出:本文件 + tmp/_resolve_probe.py(只读探针)+ tmp/_handover/waiter.json(快照)+ tmp/_handover/67dfd365-receipt.md(新主会话自己的回执)


0. 一句话结论

能,而且创建那一刻就自动跟着换了 —— 新建的「接续主会话」一出现,「主会话解析」立刻从旧目标切到它(依据=标题前缀 主控),不需要任何额外动作。

但有三条必须同时记住,否则「能解析」≠「能投达」:

  1. 🔴 只有"登记为 main 的那条不再出现在活会话里"才会换 —— 登记那条还开着 ⇒ 机制钉死在它身上,新主会话再好也不换(实测场景④⑤)。用户说的「旧的主会话停」正是这个唯一触发条件。
  2. 🔴 换过去 ≠ 投得进去 —— 新主会话此刻不是桌面活动会话,投递被拒(no-live-match)。⇒ 交接必须交给一条"正在跑 或 已被打开"的会话,否则交接即断链(这正是历史上"静默断链"的成因)。
  3. 🔴 前缀判据与实际命名不一致(主控 ✅ / [主控] ❌)⇒ 本项目正在用的主会话标题从来不是靠"显式前缀"被认出来的,而是靠"工作区最近活动"兜底,任何一条本工作区会话活动更新就会静默顶掉它。

1. 机制里"主会话"是怎么定出来的

投递方 = collabd.py --tick(宿主钩子唤起,⛔ 不常驻)→ _deliver_str() → 找 live == 目标 的那个网关口 → POST /api/v1/sessions/{id}/reply。

_deliver_str() 不读死 id,每次现算:

resolve_main()  三条路,按下面顺序短路:
 ① 登记(roles 里写着 main)**且它此刻在活会话集合里**  → 认登记   source=roles:main
 ② 否则按工作区:cwd==本工作区 且 标题不含 [协作]
       · 标题 strip() 后 startswith("主控") 的那条(显式标记,更可信) → source=prefix:主控
       · 都没有 ⇒ 取"最近活动"的那条                                  → source=workspace
 ③ 还是解析不出 ⇒ 空串 ⇒ **调用方拒绝盲投**(⛔ 不投给任意会话)

换了(switched_from 非空)⇒ _follow_main() 改 roles(旧→worker/新→main)+ 显式告警(⛔ 不静默)。

⚠️ 注意 ③ 的怪处:活会话集合为空时(枚举失败/没口)反而"信任登记"(not live 分支为真)⇒ 会解析出一条早关掉的会话。方向不算错(最终仍拒投),但归因会误导。


2. 本棒实测读数

# 场景 活会话集合 解析结果 依据
① 真实(改动前 18:07) [6ecf6d98] 6ecf6d98(本会话) workspace
② 同上,登记那条也不在 [6ecf6d98] 6ecf6d98 workspace
③ 全无活会话(模拟) [] fe146dd9(早关掉的那条) roles:main ⚠️
④ 模拟:登记那条还活着 [...] + [fe146dd9] fe146dd9 roles:main 🔴 被钉死
⑤ 模拟:登记活着 + 新主会话也活着 [...] + [fe146dd9, 67dfd365] fe146dd9 roles:main 🔴 被钉死
①' 真实(改动后 18:12) [6ecf6d98, 67dfd365] 67dfd365 ✅ prefix:主控

关键对比:④⑤ vs ①' —— 新主会话有没有被认,只取决于「登记那条在不在活会话里」, 与「另一条老会话(6ecf6d98)还开着」无关(因为它不是登记的那条)。

交接实验(真做,非模拟)

步骤 结果
建一条一次性自动化(42be9561,18:11 触发,名=主控 · 机制线 · 接续主会话(唤醒交接验证)) ✅ 已建(属「接续会话」类,白名单内免确认)
它起出来的会话 ✅ 67dfd365,标题=自动化名逐字,cwd=本工作区,18:12 跑完(completed)
新会话自己写回执 ✅ tmp/_handover/67dfd365-receipt.md —— "解析出来的就是我,依据 prefix:主控",独立复核与本节一致
新会话自己带的网关口 ✅ 运行期间 127.0.0.1:64766,live=67dfd365 —— 跑完口即消失
现在还能投进去吗 ❌ no-live-match(它已不在任何网关口的 live= 上)

3. 两个真缺陷 + 一个空窗期现象

🔴 缺陷 A:前缀判据不认带方括号的写法(代码与命名规范打架)

代码认 title.strip().startswith("主控"),而本项目自己在用的主会话标题是 [主控]-[机制线]-接续:…(带方括号)⇒ 判 False。 后果:这条主会话从来不是靠 prefix:主控 被认出来的,而是靠 workspace("最近活动")兜底。 ⇒ 本工作区里任何一条会话只要活动更新,就会静默把它顶掉(无告警、无症状,只表现成"通知投到别的窗口去了")。

🔴 缺陷 B:登记(路径依赖)压过显式前缀(="旧的不停就不换")

① 登记且还活着 ⇒ 认登记 这条短路排在 ② prefix:主控 前面。 ⇒ 只要旧登记那条还开着,用户新建的主控会话永远接管不了(实测④⑤)。 ⇒ 这条恰好是用户要的语义("旧的主会话停"),但它意味着:"显式标记 > 路径依赖"这条正确优先级目前是反的。

⚠️ 空窗期现象:旧的停了、新的没开 ⇒ 投递被拒

实测:新主会话不在活会话集合里 ⇒ 投递路径在 resolve_main 之后找不到匹配的网关口 ⇒ 走 main-not-live 拒投 + 告警(⛔ 不盲投、⛔ 不静默)—— 这是设计正确的行为,但它意味着 "交接"必须交给一个正在跑 / 已被打开的会话,否则交接当天就断链。

✅ 已排除的两个"疑似缺陷"

  • discover_gateways() 只看到 2 个网关、wake-session.py --list 看到 3 个 —— 不是缺陷:前者按 cwd == 本工作区 过滤(已归一化斜杠/大小写),后者列全部回环口。
  • 场景 ③"全无活会话 ⇒ 认登记" —— 有误导性但不导致误投(后续仍拒投)。

4. 正确的交接姿势(操作序列)

  1. 新主会话标题以 主控 开头(⛔ 别写 [主控],见缺陷 A)。
  2. 先把旧主会话从桌面上切走/关掉(⛔ 不是删会话、⛔ 不是杀进程 —— 只要它不再出现在任何网关口的 live= 上;实测④⑤ 证明只要它还 live,机制就一直钉在它身上)。
  3. 同时把新主会话在桌面上打开(它必须在某个网关口的 live= 上,才投得进去)。
  4. 以上三步做完,下一次投递就会:解析→新会话 ⇒ _follow_main 改 roles ⇒ 投达 + 显式告警。

⛔ 不要做的:把两条主会话同时开着(会被钉回旧的那条);用带方括号的标题(会静默降级为"最近活动")。


5. 泳道图:机制换主会话,卡在哪一格

     触发源            投递方(collabd --tick)        解析器 resolve_main         目标会话
   (钩子)              (宿主进程树内,带口令)        (三条路短路)                (要收通知的那条)
 ──────────────────────────────────────────────────────────────────────────────────────────
 钩子事件 ──▶ 有内容要投 ──▶ ①登记那条在活会话里?──是──▶ 用登记 ═══════════▶ 投给【旧的】
                              │                    🔴【红】这格把新主会话挡在外面(缺陷B)
                              └─否─▶ ②有标题以 主控 开头?─有─▶ 用那条 ═══════▶ 投给【新的】 ✅
                                       │                🔴【红】[主控]-… 判 False(缺陷A)⇒ 落到③
                                       └─否─▶ ③本工作区最近活动 ────────────▶ 随便一条(谁新是谁)

 ⚠️【第二红】投达前还有一道:目标必须出现在某网关口的 live= 上
      新的没在桌面打开 ⇒ ❌ 此处断(拒投 + 告警,不静默)

6. 事故链:历史上"静默断链"是怎么长出来的

主会话标题写成 [主控]-…            (命名规范与代码判据不一致 → 缺陷A)
   ↓ 缺防线①:没有"前缀写法兼容"的单测/自检
真正的主会话只能靠"最近活动"兜底
   ↓ 缺防线②:没有任何"当前主会话 vs 登记"的交叉校验
别的会话活动更晚 ⇒ 通知静默投到别的窗口
   ↓ 缺防线③:投递成功只看 HTTP 200,不回查"消费方真的动了吗"
主会话以为"没人在跑" / 协作方以为"已通知" ⇒ 双方各说各话
   ↓
登记那条被关掉后,机制不换(缺陷B + 只有"登记失效"才换)
   ↓ 缺防线④:换了没有显式告警(早期版本)
断链 → 排查从"为什么没唤醒"开始,实际是"从来没指向对过"

本该拦住的三道防线:① 前缀判据兼容实际命名(含方括号)+ 自带单测 ② 每次投递都做"登记 vs 显式前缀"交叉校验,不一致就换+告警 ③ 投递后回查目标会话是否真跑了一轮。


7. 待拍板(一轮一问)

问题:交接已经自动生效(机制现在指向新起的「接续主会话」),但它现在处于"没在桌面打开"的状态 ⇒ 投递会被拒。要不要把它真正接通,以及要不要顺手修掉那个"标题写法不兼容"的判据?

  • A. 你在桌面上打开那条新会话(我这边零动作)—— 优点:一步到位、零风险、零改动;缺点:必须你动手一次,且之后旧窗口不能再同时开着。
  • B. 我改掉那个"只认不带方括号写法"的判据(顺带把"显式前缀"提到"登记"前面)—— 优点:以后交接不再挑标题写法、也不再被旧窗口钉住;缺点:这是跨工作区共用的机制,改它要拿独占锁、且不能边改边用,得单独开一棒。
  • C. 维持现状 —— 优点:什么都不动;缺点:机制指向的是一条投不到的目标,下次投递会报"主会话不在活会话里"。

我的默认(无人值守下已按此执行):只做验证,⛔ 不动共用机制代码(B 属机制层,须独占锁、单独一棒);新会话与自动化保留(它是你要的交接产物)。回退极简单:把 67dfd365 的标题改掉、不再以 主控 开头,解析就会退回原样。


8. 出处与可复现命令

# 只读探针(三条路 + 工作区候选 + 模拟"旧会话是否还活着")
"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" tmp/_resolve_probe.py
"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" tmp/_resolve_probe2.py
# 现查网关与各自 live(⛔ 端口每次都变,别记端口)
"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" .workbuddy/collab/wake-session.py --list
# 解析逻辑原文
E:/ProgramData/.workbuddy/skills/multi-session-collab/scripts/collabd.py  (resolve_main §1545 / _follow_main §1598 / _deliver_str §938)
# 新主会话自己的回执
tmp/_handover/67dfd365-receipt.md