978 lines
110 KiB
Markdown
978 lines
110 KiB
Markdown
---
|
||
name: session-mechanism
|
||
description: 「**会话机制 + 多会话执行**」的合并总入口(原 `multi-session-collab` + `workbuddy-session-forensics` 已并入本包)。🔴 **2026-10-05 起会话只三类:主会话 + 任务会话 + 检查会话**(唤醒/跟进/队列上报**已整套退役**;**任务会话=旧称「执行会话」,同一个角色**)|🔴 **2026-10-03 起「上报」整套真删**——现行机制=**常驻程序 `collabd.py` 接收(`--report` 写台账)+ 处理(建检查会话排期)**(⛔ 「协作程序」是 10-03 已退役的旧称,见文首术语表),⛔ **队列变化不再自动通知任何人**,要落事得**显式建执行会话**。两段可分别加载:① **会话机制** —— 管「会话怎么活下去、怎么不哑掉、怎么不失忆、跨机器怎么一键装好」:治「会话卡住 / 一直转圈 / 发消息没反应 / 界面不刷新」「会话日志涨到上限把界面顶死」「上下文爆了要换会话 / 接续会话怎么开」「抢锁 / 执行锁 / 并发 / 别的会话在动」「钩子没生效 / 钩子把我也拦住了」「换一台电脑要重装这一堆钩子」「某个历史会话当时到底干了啥」。② **多会话执行** —— 管「一个主会话带多个执行会话把需求做完」:治「多个会话协同但工期被拖长 / 有会话在干等 / 链条断了没人接 / 分不清『真完成』还是『只排了下一棒』/ 问清主会话·执行会话两类怎么分(**接续会话是撞阈值时的「形态」,⛔ 不是第 3 类**)」。🔴 **主触发句(2026-10-03 用户定案)**:用户说「**使用执行会话完成 XXXX 目标**」「**继续 XXXX 目标**」时**直接走本技能**(=要派执行会话去把一个目标做完/接着做)。其他触发:「怎么协同多个会话」「别的会话都在干等」「任务没推进」「链条断了」「你监督这些会话」「会话卡住」「日志要爆了」「换机器怎么配」「升级 / 安装这套机制」。核心=**会话机制三件套(钩子 + 锁 + 日志闸)全局生效** + **执行两条通道各走各的**(派活靠自动化/收结果直读宿主库/机械判定下沉到常驻程序/人只看一个看板)+ **一键配置 `install.py`**(换机器不手抄绝对路径)。
|
||
version: 1.3.0
|
||
updated_at: 2026-10-05
|
||
last_change: 2026-10-05 10:5x · 🔴🔴 **`lifecycle` 判定收敛为「单一事实源」**:抽 `goal_life_of(root)`(剥全角/半角括号后缀 + 认不出时写日志),`goal_life()` 降为薄封装,`peer_supervise_sweep()` 改调它 —— 此前它**自己又抄了一遍**精确匹配(不剥后缀)⇒ **同一份 `goal.json` 两条路两个答案**。⛔ 判「目标状态」一律问代码(`--check-status`/`goal_life_of()`),⛔ 不许肉眼读原文比对。⚠️ 端到端实测坐实**夹具坑**:合成区只复制配置字节、不改 `workspace` 字段 ⇒ 钩子读的是**真区心跳** ⇒ 正确地"在位沉默",而我差点误判成"钩子失效"(P0-53)。 🔴 起不来先报错这条红线已落进 T 表 §1.6(用户原话「必须首先启动好在执行」)+ **`supervise-ensure-hook.py` 两个真缺陷已修并变异验证报红**:① 工作区原先只读 `DSH_WS_ROOT`(宿主 env 三级全空 ⇒ **钩子长期空转**)② `env=dict(os.environ)` 对 `exec_module` 里的 `load_cfg()` **无效** ⇒ 会验到别的区。⛔ 教训=**「零输出」既可能是"正确沉默"也可能是"根本没看见"⇒ 必须造合成区做反向对照**。 🔴 10-04 17:3x · **文档索引已建**:`references/01-文档索引.md`(干什么事→看哪篇 + **文档四条规则**:分类索引/结论在最前/历史倒排·新的在前/单条 ≤6 KB)+ SKILL.md 第一屏挂「改机制前必读三篇」(⛔ 只写进工作区日志的规矩=没立:10-03 立的「⛔ 写会怎样前先取证」10-04 复发)。 🔴 **本行是「现行口径台账」,⛔ 不是变更日记**:只记**现行结论**,实体与来路在 `references/`(下面每条都给指向)。⛔ **不再在这里堆叠「本条为准」式的旧口径**(那会让同一件事在两处各存一份、改一处忘另一处就打架)。
|
||
· **常驻形态(2026-10-03 定案)**:每个工作区的常驻**由它自己的主会话起**(主会话开工第0 步:确认 pid 活 ∧ 心跳 <90 s);看板**全平台只保留一份**,其它区用 `peer_workspaces` 并列看。→ `architecture.md`《运行形态》+`pitfalls.md` **P0-41**(载体与三条封死路)。
|
||
· **常驻存活唯一机读判据**:`pid 活 ∧ 心跳新鲜(<90 s)`—— ⛔ 别看退役旧戳。→ `pitfalls.md`。
|
||
· **看板 tab 跨工作区并列**:一样数据源跟着格走(⛔ 只换标题不换数据源 ⇒ 看板在说假话)。→ `pitfalls.md` **P0-40**。
|
||
· **改看板要不要重启?四类答案**(读前部的真相)。→ `pitfalls.md` **P0-38**。
|
||
· **钩子超时是「拦截」不是「变慢」**:会报阻塞、不会只慢。→ `pitfalls.md`。
|
||
· **开工第 0 步:会话机制体检**(不是规划)**—— 用户原话「不是规划 是 规则」。→ `references/collab.md`。
|
||
· **诊断口径**:删看板里「负责类别:—」那行(它会说假话)、会话类别与主体的两轴消歧。→ `references/collab-detail.md`。
|
||
· **看板标签用「简称」**:简称只是压缩版面,⛔ 不是又多了角色。→ `collab-detail.md`。
|
||
· **常驻被杀的真因结案**:「释放锁」≠「删文件」(**P0-6**);载体与长期在线→ `references/supervise-persistence.md`。
|
||
· **投赴与队列投递**:「投递」整套已真删(2026-10-03,用户原话「没用了就删除」);实际机制=**常驻接收(`--report` 写台账)+处理(建检查排期)**,⛔没有「推送」环节。→ `pitfalls.md`。
|
||
· 🔴🔴 **术语统一**(用户「语言全部统一」)——**同一件事只许有一个词,现行以第三代为准**:
|
||
|
||
| 代 | 时间 | 被派的会话 | 常驻程序 | 状态 |
|
||
|---|---|---|---|---|
|
||
| 一 | 10-02 前 | 协作会话 | **协作程序** | ⛔ **退役**(10-03 用户下令改,⛔ 包内未同步) |
|
||
| 二 | 10-03 | 执行会话 | 目标检查 | ⚠️ **过渡名**(代码与看板仍在用) |
|
||
| 三 | **10-05** | **主会话/任务会话/检查会话** | **常驻程序** | ✅ **现行** |
|
||
|
||
🔴 **任务会话 = 旧称「执行会话」,同一个角色**(⛔ 不是第四类)。
|
||
🔴🔴 **2026-10-05 用户定案**:**「协作 全部 改为执行」**(连说两遍)⇒ **新建前缀一律 `[执行]`**,包内现行文案一律用「**执行**」(旧「协作」只作历史沿革保留)。
|
||
🔴🔴🔴 **2026-10-05 追加定案(同一话题,用户逐字)**:「**兼容个毛线,今天兼容一个明天兼容一个 过不了一周就成大杂烩了**」
|
||
⇒ **本包禁用「兼容」二字描述任何前缀/字段**。凡看见旧前缀,**必须一句话答出"删了会坏在哪"**;
|
||
答不出 ⇒ **它就是该删的**(不许以"兼容"为由留)。
|
||
· ✅ **留得住的唯一理由 = 历史行解析正确性**:`[协作]`/`[协作目标]`/`[任务会话]` 三条**不许删** ——
|
||
实测(2026-10-05 跑 `board.py::_role_of_title()`)删任一条 ⇒ 存量旧标题行解析成 `role=""`
|
||
⇒ **看板画不出、派活漏管,且不可逆**。⇒ 这是"**删了会坏**",⛔ **不是"兼容"**。
|
||
· ⛔ **不许进「活类别表」**:`session-rules-check.py::ROLES_LIVE` **只有 `[执行]`**。
|
||
旧前缀只出现在**扫描面** `SESS_PREFIXES`(捞历史行用)+ `_PFX` 映射表(解析用)。
|
||
· 🔴 **机制层闸门(已落地,防"明天又长一条")**:`session-rules-check.py` **第 ⑬ 项自检**
|
||
—— 扫**活排期名**与**活会话标题**,出现旧前缀 ⇒ **当场 fail**(判据只扫"活件",⛔ 不扫注释/考古段)。
|
||
变异对照已跑:全 `[执行]`⇒ok|混 1 条 `[协作]`⇒fail|混 1 条 `[任务会话]`⇒fail|历史行不以旧前缀开头⇒ok。
|
||
🔴 **改名落地位置**:发源地 `collabd.py::_gap_plan()` 的 `_zh = {"worker": "执行"}`(**唯一出处**)+ 文首提示词模板 9 处 + 本文件现行规范句。
|
||
🔴 **待办(未做)**:一/二代旧词在包内仍有 ~174 处(`collabd.py` 37、`board.py` 27、`board.html` 28、`selftest.py` 22、`goalctl.py` 15…)。
|
||
⛔ **改名不能盲替换**,先分三类:① 注释/文档 ⇒ 可直接改;② `selftest.py` 的 `@case` 标题 ⇒ 改了会断 `-k` 用例引用;③ 看板显示文案 ⇒ 改了用户可见,且可能有字面判据(`board.py:191` 那条就是防两处漂移的)。→ 待办清单 `references/manifest.md`。
|
||
· **本包自身**:`install.py`(干跑/安装/校验)+`references/manifest.md`(清单);⛔ 包外**不应**有第二套会话机制代码。
|
||
agent_created: true
|
||
---
|
||
|
||
# session-mechanism — 会话机制(含多会话执行)
|
||
|
||
## 🔴🔴 改流程/改机制前,**先看这三篇**(10 分钟,⛔ 别通读文档)
|
||
|
||
| 序 | 必读 | 为什么是它 |
|
||
|---|---|---|
|
||
| 1 | **`references/00-动手前必过.md`** | 六条动作红线(⛔ 开工前只读这一篇就够) |
|
||
| 2 | **`references/rules.md`** | 现行规则本体(⛔ 历史在 `pitfalls.md`) |
|
||
| 3 | **`references/manifest.md`** | 清单:哪个文件是**权威**(改之前先确认) |
|
||
| 4 | **`references/作业规矩/`** | **会话必须遵守的作业规矩**(原 `agent-operating-rules`,2026-10-06 按用户令整包搬入):`00-作业总规矩` · `01-协作与提报用户判据` · `02-工作区纪律` · `03-多棒接力编排` · `04-去AI味与说话方式` |
|
||
|
||
**📇 完整索引(干什么事 → 看哪篇):`references/01-文档索引.md`**
|
||
⚠️ 那篇里还有**文档四条规则**(分类索引/结论在最前/历史倒排·新的在前/单条 ≤6 KB)——
|
||
**⛔ 只写进工作区日志的规矩 = 没立**(10-04 实证:一条红线立在对的地方,换会话照样读不到)。
|
||
|
||
## 🔴🔴 第 0 步:加载门槛(2026-10-05 立 · ⛔ 这是**第一条**,在十条禁令之前)
|
||
|
||
> **判据(一句话)**:**用户说了触发句 ⇒ 第 0 步是先加载本技能,然后才动手。⛔ 没有"我大概知道"这条捷径。**
|
||
|
||
**触发句(命中任一条即须加载)**:`使用任务会话完成 X 目标` / `使用执行会话完成 X 目标` / `继续 X 目标` /
|
||
`用执行会话完成 X` / `派任务会话` / `建任务会话` / 任何"让我去把某个目标做完/接着做"的表述。
|
||
|
||
**⛔ 禁止的三种"跳过理由"**(都真实发生过):
|
||
|
||
| 跳过理由 | 为什么不算数 |
|
||
|---|---|
|
||
| 「我已经很熟这套机制了」 | 熟悉 ≠ 现行。机制 10-03/10-04/10-05 连续改过三轮(会话类别、常驻形态、术语),**记忆里的版本大概率是旧的**。 |
|
||
| 「上下文里已经有规则片段了」 | 片段 ≠ 全文。**只读片段最容易漏「自决策白名单」这一节**(见下)。 |
|
||
| 「注入通报里说本轮不需要」 | 🔴🔴 **用户原话 > 注入通报**。通报是机制层的**旁路信号**,⛔ 不能拿它反驳用户当轮的明确指令。 |
|
||
|
||
🔴🔴 **最关键的一条(2026-10-05 实测栽过)**:**用户明确说了"使用任务会话完成目标",就不许再回头问"要不要建任务会话"。**
|
||
- 用户原话:「**任务会话的目的不就是 建立任务会话执行嘛,不然我调用任务会话技能干什么**」
|
||
- 用户原话:「**我想知道你哪来的这么多问题啊,你去执行不行啊,会话技能里面没有告诉你自决策的规则和机制嘛**」
|
||
- ⇒ 说这句话**本身就是授权**:直接建排期、拉起任务会话。把"怎么执行"当待拍板项 = 把已授权的事重新要一遍签字。
|
||
|
||
**自决策白名单(九类,命中即永不问)** —— 全文在 `references/02-功能优先协作协议.md` §2:
|
||
技术选型/实现路径/命名/调参/部署/排查/版本/兼容降级/文档技术内容。
|
||
**只准提报三类**:① 功能语义分叉(做 A 还是做 B,影响用户看到什么)② 红线门禁 ③ 超出决策边界(花钱、对外承诺、要凭据)。
|
||
|
||
## 🚨 十条禁令(2026-10-05 立 · ⛔ 每条都栽过,栽一次就重来一遍)
|
||
|
||
> 用户 2026-10-05 原话:「**你不要给我知道了,你给我记下来,每次都是知道了,知道了,过一会儿又忘了**」。
|
||
> ⇒ 所以这些话不放日志、不放事后复盘,**放在每次加载都会读到的地方**。⛔ 改任何东西之前先过一遍。
|
||
|
||
1、⛔ **不清楚就先读代码/日志/任务定义,⛔ 不许推断后当结论说**。栽过:把"5 分钟一次"编出来(实际每分钟)、把"已经停了"说出口(实际任务还在跑)。
|
||
2、⛔ **报数字必须先数**(日志行数、次数、间隔),⛔ 不许从现象反推节奏。
|
||
3、⛔ **说"已修复"之前必须复核一次现场**(进程/心跳/端口/日志时间戳),复核读数贴进回复。
|
||
4、⛔ **⛔ 不许在用户机器上做起停实验**。为取证起一个可能弹窗的东西 = 本末倒置。栽过:为"证明留不住"起了个循环脚本。
|
||
5、⛔ **任何"改成交给用户做"的方案,必须先有用户原话**。栽过两次:脑补"开机自启"、脑补"手动启动"。
|
||
6、⛔ **改动只许往一个方向收敛**:先只读盘点 → 说明 → 再动手。⛔ 不许一边分析一边改,那样会把现场越改越乱。
|
||
7、⛔ **禁用不等于停止**(任务禁了老进程还在跑);**杀进程不等于停止**(任务到点又拉起)。要么都做,要么不做。
|
||
8、⛔ **凡"东西自己反复触发",第一步读它的调度定义**(触发条件/重复间隔),⛔ 别从现象反推。
|
||
9、⛔ **验证通过的结论,落点只能是技能**(`SKILL.md` 第一屏 或 `references/`),⛔ 不许只写工作区日志。
|
||
10、⛔ **用户说"你知道了"时,⛔ 不许回答"知道了"** —— 去把它写成上面这种条文。
|
||
|
||
## 🗺️ 现状地图(2026-10-05 · ⛔ 看机制**先读这一屏**,读完再往下)
|
||
|
||
**三类会话(⛔ 只有三类,没有第四类)**
|
||
|
||
| 角色 | **谁创建它** | 它干什么 | 怎么停 |
|
||
|---|---|---|---|
|
||
| **主会话** | 用户手动开 | 管目标与方向、**派活** | 用户关窗 |
|
||
| **任务会话**(旧称执行会话) | 🔴 **派活产生** —— 主会话判缺口后登记自动化 → 宿主到点拉起(⛔ **不是常驻建的**) | 一棒一线、一次一件,做完即上报 | 做完自止 |
|
||
| **检查会话** | 🔴 **常驻程序** `collabd.py`(`maybe_spawn_check_agent()`,六道闸) | 核对结果/目标,缺口再派活 | 做完自止 |
|
||
|
||
**三条线各归各的(⛔ 别混谈)**
|
||
|
||
1. **派活线**:主会话 → 任务会话。排期名 `[执行]-[<类别>]-<具体>`。🔴 **不受总开关影响**(开关只挡常驻)。
|
||
2. **检查线**:常驻 → 检查会话。排期名 `[检查]-[结果检查/目标检查]-…-第N棒`。🔴 **受总开关管**。
|
||
3. **常驻线**:一工作区一条 `collabd.py --supervise` + 看板**全局只一条**(端口 20099)。
|
||
|
||
**🔴🔴 目标口径(2026-10-05 用户定案 · ⛔ 这是"关注哪个目标"的唯一判据)**
|
||
|
||
> 用户原话逐字:**「一个工作区 历史的旧的目标会有很多个,当前要求完成什么目标 就因该关注和检查那个目标」**
|
||
> **「一个工作区 同时只能执行一个目标,如果要切换目标,需要用户确认,然后切换和关注检查切换后的目标」**
|
||
|
||
拆成**两条硬规矩**:
|
||
|
||
1、**同时只有一个"当前目标"** —— `tmp/supervise-inbox/goal.json` = **唯一当前目标的载体**(⛔ 不存在"多目标并行")。
|
||
历史旧目标**移到 `tmp/supervise-inbox/goals/` 归档**(⛔ 不许原地堆在 `goal.json` 里)。
|
||
2、**切目标 = 用户确认的动作** —— 「声明新目标」**不是**随便覆盖;**必须先问用户**,得到确认才切,切完**关注和检查的是切换后的那个**。
|
||
⛔ **机制不许自作主张换目标**(同 `lifecycle` 那条铁律:**宁可等,不可动**)。
|
||
|
||
✅ **三条落点现状(2026-10-05 已全补,⛔ 别再照抄旧缺口表)**:
|
||
| 项 | 现状 |
|
||
|---|---|
|
||
| 当前目标载体 | `goal.json`(单文件,✅ 天然只有一个) |
|
||
| 切目标需确认 | ✅ `declare` 加**确认闸** —— 标题变了 ⇒ **默认拒绝**(rc=3),必须带 `--switch-goal` |
|
||
| 旧目标归档 | ✅ 换目标时**整份**存 `goals/<旧标题≤40字>__<时间>.json` + 新目标留 `_前身目标` 指针(⛔ 不再靠人手写) |
|
||
|
||
🔴 **改前取证结论**(P0-78 留档):① 载体**本就满足**;② 归档**零实现**(全文搜 `goals/` 零命中 ⇒ 那份归档件是**手工造的**);③ 确认闸**零实现**(`declare` 直接覆盖 `title`)。
|
||
⚠️ **加闸带出的连带缺陷**:`declare` 没给 `--title` 时**回落成工作区名** ⇒ 把"纯细化"误判成"换目标" ⇒ 已修成「没给 ⇒ 保持现有标题」。
|
||
📂 全文 ⇒ `references/pitfalls.md` **P0-78**;⚠️ `lifecycle` 那条独立铁律 ⇒ **P0-79**(从 P0-77 拆出)。
|
||
|
||
**🔴🔴 换目标的正确动作(⛔ 照这个做,别自己发明)**
|
||
|
||
1、**先问用户** —— 「当前目标是 X,要放弃它换成 Y 吗?」(⛔ 机制不许自作主张换目标)。
|
||
2、用户**确认后**才执行:
|
||
```
|
||
python .workbuddy/collab/goalctl.py declare --title "<新目标>" --switch-goal --yes
|
||
```
|
||
3、**紧接着同步 `lifecycle`**(🔴 `declare` **不碰它**,不同步 ⇒ 检查机制会空转):
|
||
```
|
||
python .workbuddy/collab/collabd.py --set-life 进行中 --by "主会话"
|
||
```
|
||
4、切完**关注和检查的是切换后的新目标** —— 旧目标已归档,⛔ 不再建它的检查会话。
|
||
|
||
⚠️ **只细化同一目标**(补 `--why`/`--kpi`/改错别字)⇒ **不带 `--switch-goal`**,正常放行(⛔ 不该被拦)。
|
||
⚠️ `--switch-goal` ⛔ **不等于** `--yes`:前者=「用户已同意换目标」,后者只是「干跑转真写」。**混用 = 伪造用户授权**。
|
||
|
||
**🔴🔴 机制新建会话「用哪个模型」(2026-10-05 用户点破 · P0-80)**
|
||
|
||
> 用户原话:**「为什么 vibe-product 检查进程 创建的 执行和检查会话没有使用 主会话相同的模型」**
|
||
|
||
一句话判据:**机制另建会话时,模型与思考档一律「跟本区主会话」—— ⛔ 不许在代码里写死。**
|
||
|
||
- **改前事实**:`collabd.py::create_check_schedule()` 的 INSERT 里**字面写死** `"space-bunny"` + `model_is_thinking=0`
|
||
⇒ 机制建的 49 条会话/9 条排期全是 `space-bunny`,而**主会话是 `deepseek-v4.1-flash`(thought=high)**。
|
||
- **两条建会话的路,模型来源不同(⛔ 修的时候别只修一条)**:
|
||
· **常驻直连 SQLite**(`create_check_schedule()`)⇒ 这次已改成**取本区主会话**;
|
||
· **会话侧走 `automation_update`** ⇒ 天然跟主会话同档(宿主默认)。
|
||
- ✅ **现在的实现**:`collabd.py::preferred_model(cwd)` —— 取 `sessions` 里**人开的**(`is_background_automation <> 1`)、
|
||
**本区**、**最近一条**的 `model` + `thought_level`;取不到才回落常量,且**必打日志**(⛔ 不许静默)。
|
||
⚠️ **必须排除 `bg=1`** —— 否则会取到"机制自己上次建的那条" ⇒ **自我强化**,永远锁死在历史值上。
|
||
- 🔴 **配套判据**(`session-rules-check.py` ⑧):报「**机制建的排期模型 ≠ 本区主会话模型**」。
|
||
⚠️ 扫描面**必须含 `once`** —— 机制建的排期全是 `once`,旧判据只扫 `recurring` ⇒ **一条都看不见**(恒绿)。
|
||
|
||
**常驻的唯一管理入口**:`python collabctl.py <status|on|off|ensure>`(加 `--out <文件>` 取结果)
|
||
**用户可见入口(双击)**:`会话机制-一键开关.bat`(工作区根 + 桌面各一份)= 启动/全部停止/看状态
|
||
|
||
**🔴🔴 常驻机制的真实形态(2026-10-05 实测定性 + 用户拍板 · ⛔ 别再凭印象设计)**
|
||
|
||
**定案形态(两层,⛔ 结束"三层嵌套"):**
|
||
|
||
1、**常驻本体** `collabd.py --supervise`:**内部自带循环**(每 10~30 秒一轮),这是干活的那层,✅ 必要。
|
||
2、**计划任务**(`collabd-keepalive-<工作区>` / 看板 `dsh-board-keepalive`):
|
||
动作 = **`pythonw.exe` 起"启动器"**(⛔ **不许直起 `collabd.py`/`board.py`**),每 **5 分钟**触发一次**判活**。
|
||
|
||
🔴🔴🔴 **为什么要夹一个"启动器"(2026-10-05 血的教训 · P0-72+P0-73)**:
|
||
**计划任务的"动作"里没有 env 字段** ⇒ 一切环境变量**只能由启动器在进程内设**。两个启动器**对称存在**,缺一个就静默半瘫:
|
||
|
||
| 启动器 | 服务的程序 | 进程内必须设 | ⛔ 缺了会怎样 |
|
||
|---|---|---|---|
|
||
| `scripts/supervise-launch.py` | 协作程序(`collabd.py --supervise`) | **`CODEBUDDY_CONFIG_DIR`** + `COLLABD_CONFIG` + `PYTHONIOENCODING` + 兜 `stdout/stderr` | **检查程序静默失效**(读 0 字节空库 ⇒ `no such table: sessions` ⇒ fail-safe 恒判"有会话"⇒ **再也不建检查会话**) |
|
||
| `scripts/board-launch.py` | 看板(`board.py --serve 20099`) | `COLLABD_CONFIG` + `PYTHONIOENCODING` + 兜 `stdout/stderr` | **看板崩溃重启循环**(`已拒跑`)、**20099 从没绑上** |
|
||
|
||
🔴🔴 **两条"起法"必须收敛成同一个(P0-74)**:机制里**曾经有第二条起法** ——
|
||
`collabd.py` 的 `_escalate_to_keeper()`(自我供给旁路)会建 `collabd-supervise-<区>` 任务,
|
||
动作写死 **`powershell.exe -WindowStyle Hidden -File start-supervise.ps1`**
|
||
⇒ PowerShell 是**控制台程序** ⇒ 每次触发分配 `conhost.exe` ⇒ **闪黑窗**(用户 2026-10-05 报「又弹了窗口」)。
|
||
✅ 已改指 `pythonw.exe + supervise-launch.py`。⇒ **凡"同一种东西有第二条起法",收口时必须全文搜一遍**:
|
||
判据 = `grep -n "New-ScheduledTaskAction"` 与 `grep -n "\.ps1"`(本次就是靠这个抓到的)。
|
||
|
||
🔴🔴 **2026-10-06 二次收口(上一次只改了一半)**:只换"动作"不够,**名字与旁路也得一起换**。三处:
|
||
1、**任务名统一** —— `_escalate_to_keeper()` 原建 `collabd-supervise-<区>`,而 `collabctl.py` 建
|
||
`collabd-keepalive-<区>` ⇒ 前者**恰好落在 `collabctl.py` 的 `SCHED_TASKS` 禁用名单里**
|
||
⇒ **自愈起的常驻会被下一次 `off` 当"旧形态"顺手干掉**(自愈链断在这里)。✅ 已统一为 `collabd-keepalive-<区>`。
|
||
2、**`init_workspace.py` 是漏网的第二个建任务者** —— 它自带一份 `_register_keeper_task()`,
|
||
还用着 `powershell.exe -File start-supervise.ps1` 与 `assets/start-supervise.ps1.tpl`。
|
||
✅ 整份去掉:建任务**只许有一处实现**=`collabctl.py`(受 `supervise.switch` 总电闸管辖),
|
||
该脚本改为只检查启动器在位并提示入口。模板 `assets/start-supervise.ps1.tpl` 一并删除。
|
||
3、**探针曾打偏** —— 守形态的自检用例**只扫 `collabd.py`**,同族的 `init_workspace.py` 整份没被扫到。
|
||
✅ 已扩成扫**全族**(`collabd.py` / `init_workspace.py` / `collabctl.py`),并新增判据
|
||
「**任务名口径唯一**」与「旧形态死代码已清」。
|
||
⚠️ **教训**:凡"这一族文件都不许有 X",必须**枚举整族**,⛔ 不许只挑一个代表 —— 挑一个就等于没扫。
|
||
⚠️ 该判据**扫文本**,故必须先剥掉 `SCHED_TASKS` 那种"故意列旧名去禁用它"的**否定语境块**
|
||
(同一坑本项目踩过三次)。
|
||
|
||
- ⚠️ **变量名坑**:`roots.env` 给的是 **`COLLABD_PROD_CONFIG`**,而 `board.py`/`collabd.py` 读的是 **`COLLABD_CONFIG`** ⇒ 名字对不上 ⇒ `roots.env` **兜不住**。
|
||
- 🔴 **启动器里写死最稳**(⛔ 别指望 `roots.env` 兜住各区 —— 它只在技能目录,而各区副本在 `<ws>/.workbuddy/collab/`,`_sm_load_roots()` 向上 4 层**找不到**)。
|
||
- 🔴 **重构"起法"时,旧起法里的 env 必须逐条搬过去**(P0-73 就是改成两层形态时**丢了 `CODEBUDDY_CONFIG_DIR` 这一句**)。
|
||
- 🔴 **各区 config 的 `host_db` 写死绝对路径最稳**(vibe 一直这么写 ⇒ 只有 ai1net 炸)。
|
||
|
||
🔴🔴 **三条硬约束(用户 2026-10-05 逐字重述:「这些常驻程序每次创建之前,要先检查是否已经存在。如果已经存在了,就不需要重复创建,而且各工作区是各工作区的。不共用,共用的只有看板。」)**:
|
||
|
||
1、**先查后建(幂等)**:创建前必须判「已经有了吗」;已有 ⇒ **什么也不做**。
|
||
载体=`--supervise` 进场即判(`supervise_alive()`:`pid` 活 ∧ 心跳新 ∧ `_pid_is_collabd` 身份核验),
|
||
已在 ⇒ 新起的**写心跳前原子抢位失败 ⇒ 立刻让位退出**(实测连触两次,进程数恒为 3,⛔ 不叠加)。
|
||
2、**各工作区各工作区的**:一区一条 `collabd-keepalive-<工作区>`,动作指向**本区副本** `.workbuddy/collab/collabd.py`;
|
||
心跳里的 `argv0` 必须指向本区路径(实测 ai1net/vibe 各自 argv0 互不相同)⇒ **⛔ 不许一区去起另一区**。
|
||
3、**共用的只有看板**:看板任务**全局唯一一条** `dsh-board-keepalive`(端口 20099,动作指向技能目录那份共用 `board.py`)⇒
|
||
⛔ 不许每区各起一个(每区一个 = 白多 N 个进程 + N 个端口)。
|
||
|
||
🔴🔴 **一句判据(这是本条的**全部价值**)**:**常驻靠什么活着 —— 不在「在不在作业对象里」,在「谁拉起它」。**
|
||
- 会话树里起的(父链穿到 `WorkBuddy.exe`)⇒ **会话一收工就死**(表现为「有些区能常驻、有些不能」)。
|
||
- **计划任务起的**(父链**断在自己身上**,父进程已退出)⇒ **真常驻**。
|
||
- ⚠️ **`IsProcessInJob` 在本机恒为真、没有鉴别力**(四组对照全部 `IN-JOB`)⇒ ⛔ 别拿它当判据。
|
||
|
||
**⛔ 两个致命细节(掉一个就"看着配好了、其实没跑"):**
|
||
|
||
1、🔴 **`WorkingDirectory` 必须是「工作区根」**,⛔ **不是脚本所在目录**。
|
||
设成脚本目录 ⇒ 配置查找路径变成 `<ws>/.workbuddy/collab/.workbuddy/collab/…` ⇒ **找不到** ⇒ 拒跑。
|
||
2、🔴 **常驻不需要网关口令**(`--supervise` 只写 SQLite,不走网关)⇒ **保活用 `--supervise`**,
|
||
⛔ 用 `--ensure` 会在任务环境里"起了就退"。
|
||
|
||
**⛔ 一条虚警(别误判成故障)**:看板任务的 `LastResult=**1**` 是**正常的** ——
|
||
`board.py` 有全局单例守卫("看板全局只允许一个"),已在跑时它打印一行后 `return 0`,
|
||
但 **`pythonw` 在任务环境下没有 stdout** ⇒ 退出码被顶成 1。⇒ **判据看「端口在不在听」,⛔ 不看 `LastResult`**。
|
||
|
||
🔴 **弹窗真因(已封死)**:旧形态任务动作是 `powershell.exe -WindowStyle Hidden -File …` —— PowerShell 是**控制台程序**,
|
||
`-WindowStyle Hidden` 只是把窗口藏起来,**仍会分配控制台并闪一下** ⇒ 要完全不闪,必须**由 `pythonw.exe` 起**(GUI 子系统,不分配控制台)。
|
||
🔴 **判据**:任何"自动触发"的东西,**只许由 `pythonw.exe` 启动**;⛔ 不许拿 `powershell.exe` / `cmd.exe` 当动作。
|
||
⛔ **绝不允许再出现"每分钟创建一次进程"**(那本身就是设计错误,不是参数没调好)。
|
||
|
||
🔴🔴🔴 **怎么判「两套程序都正常」(2026-10-05 用户点名 · 判据写死)**:
|
||
⛔ **"进程活着" ≠ "它在干活"** —— 常驻本体活得好好的,内部某一环读错库、被 fail-safe 兜成"什么都不做",**外表零报错**。
|
||
|
||
| 要验的 | ✅ 看什么(唯一判据) | ⛔ 别只看 |
|
||
|---|---|---|
|
||
| **协作程序** | 心跳文件新(`logs/supervise-heartbeat.json` 的 `ts` < 90 s)∧ `pid` 活 ∧ **`argv0` 指本区** | 进程在不在、日志在不在走(钩子也写同一日志,**会骗人**) |
|
||
| **检查程序** | `logs/_collabd.log` 里**有没有产出预期分支**:`检查会话:目标状态=… ⇒ 不建/已建` 或 `已建检查会话排期 …` | 同上;尤其**别只看"没报错"** |
|
||
|
||
🔴 **fail-safe 读法**:`all_sessions_idle` 读库失败 ⇒ 判「有会话在跑」⇒ 不建检查会话(安全但**不可见**)
|
||
⇒ **凡出现 `读库失败`/`no such table` 就是真故障**,不是"安静很正常"。
|
||
🔴 **前置闸是合法的**:本区有 `status='working'` 的会话(含**你自己**被排掉后仍有别人的)⇒ **正确地不建**
|
||
⇒ 想观察 `检查会话:…` 分支,**得等本区会话全结束**(判据:`_collabd.log` 出现 `all_sessions_idle:还有 N 条 working`)。
|
||
|
||
🔴 **用户可见入口(用户点名要的「一键能关掉」)**:
|
||
`会话机制-一键开关.bat`(工作区根 + **桌面各一份**)⇒ 双击选 `1 启动 / 2 全部停止 / 3 看状态`。
|
||
它的"停止"=**一个动作做全**:关开关 + 禁任务 + 杀进程 + 复核(⛔ 不再"禁了任务却没杀进程")。
|
||
→ 章法与事故复盘:`references/pitfalls.md` **P0-70 / P0-71**(P0-71 是载体定案的完整实测)。
|
||
|
||
> 🔴🔴 **2026-10-03 07:2x 口径改(本条为准,排在 10-02 那条之上)——「上报」整套退役 + 看板两处几何改动**
|
||
>
|
||
> 用户逐字:「**没用了就删除,现在的机制是 协作程序接收和处理队列**」。
|
||
>
|
||
> **一、现行机制只剩两条腿(都已实测活着)**:
|
||
> · **接收** = `--report` 把任务会话的执行状态写进 `tasks.json`(四态台账,唯一权威)—— `task_report()`。
|
||
> · **处理** = 建**检查会话排期**(`maybe_spawn_check_agent()`,五道闸)让会话去读队列干活
|
||
> —— 常驻 `--supervise` 每 2 轮判一次,是这条腿的**唯一载体**(⛔ 检查会话只能靠排期开)。
|
||
> 🔴🔴 **2026-10-04 用户口径(本节为准)——「检查程序必然要去处理队列」**:
|
||
> 逐字:「首先执行程序要把执行结果 的文本地址或引用写入检查程序的队列,
|
||
> **检查程序必然要去处理队列的情况**(是等待工作区所有会话停止后,把队列情况一并处理)」
|
||
> ⇒ 三条逐条对:
|
||
> | 用户的话 | 机制里的对应 | 状态 |
|
||
> |---|---|---|
|
||
> | 执行结果要写**地址或引用**入队列 | `--report --state done` 缺 `--artifact` **直接拒收** | ✅ 早已落地 |
|
||
> | 检查程序**必然**处理队列 | → 见下面「闸① 收窄」 | ✅ 2026-10-04 修 |
|
||
> | **等所有会话停止后**一并处理 | `_all_sessions_idle()`(闸②) | ✅ 早已存在 |
|
||
> 🔴 **闸① 收窄(当天修)**:原来 `if life != GOAL_LIFE_RUN: return None` 让
|
||
> 「目标已标完成」成为「**处理队列**」的前置条件 ⇒ 队列非空时**照样不建** ⇒ **活永远没人接**。
|
||
> ✅ 现口径:
|
||
> · **`sessions-ended`(队列非空 = 有活没人干)⇒ ⛔ 不受目标状态限制**(活没干完就是没干完);
|
||
> · **`queue-empty`(队列空)⇒ 仍须「进行中」**(它的职责正是**判目标该不该收口**,只在进行中才有意义)。
|
||
> 📌 台账实体 ⇒ `references/pitfalls.md` **P0-56**。
|
||
> · ⛔ **没有"推送"这一环**:队列变化**不再自动通知任何人**。要落一件事,**显式建一条任务会话**。
|
||
>
|
||
> **二、本轮真删了哪些(⛔ 不是"标成已退役")**:
|
||
> · `collabd.py::supervise()` 里的 **①′待反馈序列 + ②③单条握手 + ④唤醒** 四段,
|
||
> 以及两个 `_deliver_str()` 调用点 —— **203 行 → 71 行(-132)**。
|
||
> (原文随历史备份一并清理;⛔ 要恢复得按 `collabd.py::supervise()` docstring 里那四条清单重写。)
|
||
> · `--tick` 里的 `check_delivery_consumed()` 调用(判据 `st["wake"]["expect"]` 只由 `_deliver_str()` 写
|
||
> ⇒ 投递删后**恒返回 `{}`**)。
|
||
> · 看板「**最近上报 `wakeups.jsonl`**」整张卡(该文件**已不再被写**、实测根本不存在 ⇒ 恒空假面板)。
|
||
> · 看板两处几何改动,**都是用户当面纠正后落地的**(⛔ 不是我第一版那么写的):
|
||
> ① 架构图 ④ 那格 —— 我第一版把「上报」**改名成「常驻」**,用户逐字驳回:
|
||
> 「**怎么又把上报 改成常驻了 常驻什么,不是 协作程序常驻吗**」。
|
||
> **根因=把「角色名」与「运行形态」混成一个词**:那一格的角色本来就叫**常驻程序**(`collabd.py`),
|
||
> "常驻"只是它的一种跑法(`--supervise`)—— 用模糊词替代具体词 = 越改越糊。
|
||
> ✅ **处置=两格合并成一格**(`PX=280, PW=720`;删 `UX`、删随之悬空的 `RKX`、`TICKX` 改算为 `PX+PW/2+120`):
|
||
> 标题「**协作程序**」/副标题「**接收:--report 写台账 · 处理:建检查会话排期 · 常驻一直运行**」
|
||
> /右侧小字「⛔ 队列投递已于 2026-10-03 退役(曾叫「上报」)」。
|
||
> ⚠️ 删变量在**代码行**零引用(用自写检查器逐个 `grep` 过 `UX`/`RKX`)。
|
||
> ② 「最近上报 `wakeups.jsonl`」整张卡删除(该文件**已不再被写**、实测根本不存在 ⇒ 恒空假面板)+其渲染块。
|
||
> · 另 4 处用户可见「上报」文本替换:`② 上报 · --report` → 「② 写台账 · --report」(空态/有会话两处);
|
||
> `Hook进程` 框内 `--tick(上报)` → 「--tick(补检查排期)」;台账空态文案 → 「用 --report 把状态写进这里(四态…)」;
|
||
> 常驻程序格副标题「只维护不上报」→「**只维护不推送**」。
|
||
> · `collabd.config.json`:`wake_enable` **true → false**(第二道闸,第一道是代码里已删那两段)。
|
||
>
|
||
> **二之二、🔴 顺带查清的判据:「改看板要不要重启」有四类答案,混成一句话就是误导**
|
||
> (用户第二句纠正:「**看板每次修改都不处理看板**」⇒ 我把"改配置要重启"说成了"改看板都要重启",**是分类错误**):
|
||
> · 改 `assets/board.html` ⛔ **不用**(`board.py:1293` `serve()` 里 `html_p.read_bytes()` **每请求实时读盘**;
|
||
> `/board.json` 带 `html_sig`(`mtime.size`)⇒ 页面自动重载)—— 现跑实证 `tmp/_probe_hotreload.py`:
|
||
> 改 `<title>` 插标记 → ⛔ 未重启 → 页面立即含标记(md5 已还原一致 ✔)。
|
||
> · 改 `.workbuddy/collab/board_ext.py` ⛔ **不用**(`EXT_CACHE` 按 `(mtime,size)` 签名**热重载**,`board.py:855-870`)。
|
||
> · 改 `scripts/board.py` 自身 ✅ **必须**(进程里是启动那一刻载入的旧代码)。
|
||
> · 改 `collabd.config.json` ✅ **必须**(`C = _cfg()` 在 `board.py:81` **模块级执行一次**,⛔ 无 re-read 路径)。
|
||
> ⇒ **判据=先答"这个文件是谁在读、什么时候读"**;⛔ 别把"改了 X"与"改看板"当同一件事。
|
||
> ⇒ 全文 ⇒ `references/pitfalls.md` **P0-38**(与 P0-17/P0-33 同族但**方向相反**:那两条讲"该重启却没重启",
|
||
> 本条讲"⛔ 不该重启却去重启了" —— **重启不是万能药**)。
|
||
>
|
||
> **三、🔴 删除前的实测读数(这才是"为什么该删"的证据)**:
|
||
> | 读数 | 值 |
|
||
> |---|---|
|
||
> | 日志 `follow-retired` 行数 | **1 313 行且每 20 s +1**(每轮试投、每轮失败) |
|
||
> | `notify_pending` | `["S8=done","S9=done","S5=done","S12=done"]` —— **4 条全 `done` 堆在队首** |
|
||
> | `wakeups.jsonl` | **文件不存在**(投递早已没成功过) |
|
||
> | `TO-MAIN.md` | 每轮被覆写成 `S8 -> done`(那份通知永远送不出去) |
|
||
> | `NEED-USER.md` | 每轮刷新「投递链路没有收件人」 |
|
||
>
|
||
> **四、🔴 它是三重死锁,不是一条腿断**:
|
||
> ① 投递失败 → `collabd.py`「**只有真投出去才允许消费队列**」⇒ 不消费;
|
||
> ② 不消费 ⇒ 队首永驻一条投不出去的死件(`S8=done` 从 23:55 堵到 07:08);
|
||
> ③ 队首非空 ⇒ `no_fb` 恒 False ⇒ **唤醒的条件②「队列无待反馈」永不成立** ⇒ 唤醒那条路跟着一起死。
|
||
>
|
||
> **五、⛔ 保留的零调用点函数(本体不动,是"将来重建收件人"的实现)**:
|
||
> `_deliver_str()` / `follow_for_topic()` / `wake_round()` / `check_delivery_consumed()`。
|
||
> 🔴 **为什么不删**:`selftest.py` 有 4 处**真调用**前两个 ⇒ 删函数体 = `NameError` 崩自测。
|
||
> ⚠️ **判据必须是 AST 而不是字面 grep** —— 退役说明的 docstring 里**必然**提到被删的函数名
|
||
> ⇒ 任何 `grep "_deliver_str(" in <函数体切片>` 都**永久假红**(见 `selftest.py::_calls_in()`)。
|
||
>
|
||
> **六、验收(全部现算)**:`py_compile` 4 份全过 | `board.html` 两个 script 块 `new Function()` 全过
|
||
> | **`selftest` PASS 47 / FAIL 1**(回到基线;那个 FAIL 是域锁锚点词表的项目名,⛔ 既有问题)
|
||
> | **60 秒观察窗内 `follow-retired` 增量 = 0**(改前每 20 s +1)
|
||
> | 常驻新实例 pid 49324、心跳 `round` 递增、心跳 JSON 里 `wake` 键**已消失** ✔
|
||
> | 看板 `--takeover` 后 `/board.json` 的 `meta.deliver_retired == "2026-10-03"` ✔
|
||
> | Edge headless 截图(`tmp/board_merged.png`)实证:④ **一格「协作程序」**+「队列 2 件 · 已完成 2 · 未完 0」
|
||
> +「接收:--report 写台账 · 处理:建检查会话排期 · 常驻一直运行」+ 右侧退役小字;
|
||
> `--once`(x=440) 与 `--tick`(x=640) **两条线都落在框内**;**「最近上报」卡已消失** ✔
|
||
> | 两格合一后**再跑一次 `selftest` 终检**= PASS 47 / FAIL 1(与合并前同基线,⛔ 合并没引入新问题) ✔
|
||
|
||
> 🔴🔴 **2026-10-02 23:4x 口径改(本条为准,全文其余「四类」表述按此读)**
|
||
>
|
||
> **会话类别从四类收敛为两类**:① **主会话** ② **任务会话**。
|
||
> **唤醒会话 / 跟进会话 / 队列上报机制 —— 整套退役**(用户逐字,三句):
|
||
> ① 「按照之前的讨论 唤醒会话 跟进会话 和 上报程序 都去掉才对」
|
||
> ② 「上报机制也不需要了」
|
||
> ③ 「按照新的逻辑整体修改」
|
||
> (口径**早就在** `scripts/collabd.py:2293` 写着:「创建检查会话的为 协作程序
|
||
> **(现在不需要上报机制了、之前已经去掉 唤醒会话和跟进会话机制)**」—— 本轮是**把代码与文档对齐到已有口径**。)
|
||
>
|
||
> **已落地(现算)**:
|
||
> · 角色表 `collabd.py::parse_session_name()` 与 `board.py::_role_of_title()` **两处同款**收到 `主/执行`
|
||
> ⇒ `[唤醒]`/`[跟进]` **判空**(旧 `sessions` 行不删,但不再是活类别,也**不会被当主会话候选**)。
|
||
> · 主会话候选排除元组**两处**收到 `("worker",)`。
|
||
> · `follow_for_topic()` **短路退役**(恒返回 `why="follow-retired"`、`sid=""`)⇒
|
||
> ⛔ **不投主会话**(主会话只由用户触发),⛔ 不盲投。
|
||
> · `goalctl.py::_WHY` 登记新档 `follow-retired`(「需人看」)⛔ 不降级去抢锁自己干。
|
||
> · 两台周期钟 `[唤醒]-…-脉冲` / `[跟进]-…-队列上报` + 3 条一次性跟进排期 ⇒ `ACTIVE→PAUSED`、
|
||
> **零删除**、`integrity_check=ok`、周期排期未误伤。
|
||
> · **检查会话由常驻程序建**(`maybe_spawn_check_agent()` 五道闸),⛔ 不经唤醒/跟进转手。
|
||
> · `selftest` **PASS 47 / FAIL 1**(那个 FAIL 是域锁锚点词表里的项目名,⛔ 既有问题、⛔ 故意不动)。
|
||
>
|
||
> ⚠️ **旧正文里「四类」「跟进会话」「唤醒会话」的大量表述按本块读** ⛔ 不是"没改",
|
||
> 是**刻意保留**(那是历史证据与踩坑记录;与本块冲突处以本块为准)。
|
||
> 🔴 **`board_ext.py` 里的「链路前置」已停用**(讲的是 2026-09-30 随概念退役的手机接入线 20090 链路)
|
||
> ⇒ `collabd.config.json` 的 `board_ext` 指向 `.RETIRED-20260930.py`。
|
||
> ⚠️ **改了 `collabd.config.json` 必须重启看板才生效** —— `C` 是**模块级加载一次**,
|
||
> ⛔ 不每次快照重读(这是 P0-17「改了看不见」漏记的一面)。
|
||
|
||
> 🔴 **一句话**:**会话要先活得下去(钩子 + 锁 + 日志闸),再谈协作(派活靠自动化、收结果直读宿主库、机械判定下沉、人只看一个看板)。**
|
||
|
||
> ⚠️ **包自包含**:所有脚本、参考件、资产都在本包内(`scripts/` `references/` `assets/`),⛔ 不再依赖文档库 `07-scripts/`。换机器 = 拷本包 + 跑一次 `install.py`。
|
||
|
||
---
|
||
|
||
## §0 怎么用(先读这一节)
|
||
|
||
**① 我要装 / 要换机器** ⇒ 跑 `python install.py --dry-run` 看 diff,再 `python install.py --apply`,最后 `python install.py --verify`。
|
||
(`--apply` 会:写 `roots.env` → 按**声明表**接线全局钩子 → 初始化工作区 → 记 `install.log`。⛔ 不硬编码 python 路径与盘符。)
|
||
|
||
**⓪ 我要动手(建判据/写验收/报"已完成"/任何带"现在·已·还差"的结论)** ⇒ **先读 `references/00-动手前必过.md`**(🔴 **动手层**,2026-10-04 建,只有三条动作:① 查状态**问程序自己**,⛔ 不许 glob/手拼路径 ② 引用任何读数**先看它什么时候写的** ③ 写判据**先让基线全绿**再谈抓得住 —— 每条都对应当天真实翻车,且都已落判据不许退化)。
|
||
|
||
**② 我要查会话机制** ⇒ 读 `references/architecture.md`(唯一权威:机制全貌)+ `references/rules.md`(规矩与判据)。
|
||
|
||
**③ 我要查多会话执行** ⇒ 读 `references/collab.md`(执行四条通道 + 派活模板)。
|
||
常驻程序可独立使用:`python scripts/collabd.py --where` / `--tick` / `--report`。
|
||
|
||
**④ 我要复盘某个历史会话** ⇒ 读 `references/forensics.md`,取证脚本 `scripts/forensics/proc-parent.py`。
|
||
|
||
**⑤ 踩过坑 / 要避坑** ⇒ `references/pitfalls.md`;**包内文件清单与来源** ⇒ `references/manifest.md`(逐文件 md5 + provenance)。
|
||
**⑥ 我要让常驻长期在线(⛔ 会话/工具调用起的活活不过当轮)** ⇒ 读 **`references/supervise-persistence.md`**(唯一权威:Windows 计划任务 + 守护循环 · 三条封死路 · 三个秒退坑 · `LastTaskResult` 验收)。
|
||
🔴 **先分场景再动手**(该文档 §〇 有对照表):**用户手动创建主会话** / **定时任务创建主会话** —— 两者都要**同一个载体**(计划任务 + 永不返回的守护循环);⛔ **排期代替不了载体**(跑完即 `completed`,下一跳之前是空窗);⚠️ `status=ACTIVE` **≠ 在跑**(`once` 排期过期即哑:`next_run_at=None`)。
|
||
|
||
**⑦ 我要判断「这件事该自己定,还是该问用户」/ 要给用户一个「待拍板」清单** ⇒ 读 **`references/02-功能优先协作协议.md`**(2026-10-04 由 `dsh-decision` 搬入本包,**内容守恒**)。
|
||
它答的是**会话里做事时的自决策口径**:**功能卡 4 问**(用户只填「谁用/在哪用/要做什么/怎样算成功」)· **9 类自决策白名单**(技术选型 / 实现路径 / 命名 / 调参 / 部署 / 排查 / 版本 / 兼容降级 / 文档技术内容 ⇒ **永不问**)· **只准提报用户 3 类**(功能语义分叉 / 红线门禁 / 超出决策边界)· **提报用户格式**(⛔ 不许出现包名 / 路径 / commit / 代码标识符)· **拆包提报用户**(红线问题⛔ 不得与技术方案捆着问)· **答复与交付格式**。
|
||
✅ **判据实体就在本档正文里**(2026-10-04 改,原文写「权威在 `agent-operating-rules §1.6`」——现已改为**本档即权威**)⇒ 🔴 **只复制这一个技能到别的机器,这些功能全部可用,⛔ 不依赖任何其他技能**。📌 来路:2026-10-04 从 `dsh-decision/references/01` 逐行搬入;⛔ `dsh-decision` 那份视为副本,**改判据以本档为准**。
|
||
🔴 **配合任务会话的六阶段一起用**(见 `references/collab.md` §5):**需求识别 → 调研 → 规划 → 执行 → 验证 → 归档**——每个阶段都能撞上"该自己定还是该问用户",本档就是那一刻的判据。
|
||
|
||
---
|
||
|
||
## 🔴🔴 T 表 · 状态 → 我该做什么(2026-10-04 用户口述,⛔ **唯一权威,不许 AI 推断**)
|
||
|
||
> 📌 **为什么有这张表**:技能原先只有「你要干什么 → 去看哪篇」(知识组织),
|
||
> **没有「现在什么状态 → 下一步做什么」(行动组织)** ⇒ AI 查到一条知识
|
||
> 就以为「知道该做什么了」⇒ **2026-10-04 一天连栽三次**(凭空造出「开机自启」
|
||
> 「三类会话」「两类会话」三个需求,全是 AI 自己推断的,⛔ 用户从没要求过)。
|
||
> ⇒ 这张表是**堵这个洞**的。⛔ **不在表里的状态 ⇒ 问用户,⛔ 不许自己推断。**
|
||
|
||
### 一、执行会话的**生命周期**(用户原话逐字)
|
||
|
||
> 「**使用执行会话完成目标的时候 主会话 才建立一轮执行会话,
|
||
> 后续无意外都是 检查进程创建**」
|
||
|
||
```
|
||
主会话收到「用执行会话完成 X」
|
||
│
|
||
├─ ① **建一轮任务会话** ← 🔴 只有这一步是「主会话建执行会话」
|
||
│ └─ 它们干活 → 完成后**向检查程序队列投递执行情况**(`--report`)
|
||
│
|
||
├─ ② **后续无意外 ⇒ 只建检查会话** ← 🔴 检查会话**由常驻建**(`maybe_spawn_check_agent()`)
|
||
│ └─ ⛔ **主会话到这一步就收手**,⛔ 不再自己建任务会话
|
||
│
|
||
└─ ③ **有意外**(链条断了 / 推不动 / 检查会话判不过)
|
||
└─ 才需要再建任务会话去接 —— ⛔ 建不建、建几条 ⇒ **问用户**
|
||
```
|
||
|
||
### 🔴 1.5 前置条件(2026-10-04 用户补充,⛔ **顺序不能颠倒**)
|
||
|
||
> 用户原话:「**不只是创建执行会话,如果主会话下没有 后台任务 或 检查程序
|
||
> 还需要启动常驻程序**」
|
||
|
||
⇒ **三层是串起来的**,⛔ 不许跳过中间那层直接建会话:
|
||
|
||
```
|
||
第 0 层:常驻(collabd.py --supervise) ← ⛔ 一切的前提
|
||
└─ 它是「建检查会话」那条腿的**唯一载体**(`maybe_spawn_check_agent()`,每 2 轮判一次)
|
||
└─ 🔴 实测依据:`collabd.py` 文首「处理 = 建检查会话排期 …… 由常驻 --supervise
|
||
每 2 轮判一次,是这条腿的**唯一载体**」⇒ **没有常驻 ⇒ 检查会话永远建不出来**
|
||
第 1 层:检查会话([结果检查]/[目标检查]) ← 常驻建的,⛔ 你自己建不了
|
||
第 2 层:任务会话([执行]-…) ← ⛔ **只有主会话在第①步建一轮**,之后不再建
|
||
```
|
||
|
||
🔴 **因此开工第 0 步的真顺序是**:
|
||
|
||
| 步 | 查什么(**现取,⛔ 别看快照**) | 不在位时 |
|
||
|---|---|---|
|
||
| **0-a** | **常驻在不在**:`supervise_alive()` = `pid 活 ∧ 心跳新鲜(<90 s)` | 🔴 **先起常驻**,⛔ 不许直接建检查会话 |
|
||
| **0-b** | **有没有目标** + 生命周期 | 🔴 **没有目标 ⇒ 用户在用「基础会话方式」,⛔ 那不是故障、⛔ 不必修**;
|
||
| | | 有目标但 `lifecycle` =「等待」⇒ **`queue-empty` 那条不动**(闸①,⛔ 别替他改);
|
||
| | | 🔴 `lifecycle` =「已完成」(**含带后缀**形态)⇒ **先看台账**:还有未完成件 ⇒ **照建 `[结果检查]`**;全 `done`/空 ⇒ **收工** |
|
||
| **0-c** | 五道闸其余(会话全结束/队列对得上 reason/无待执行排期/同名未在册) | ⛔ 缺一即静默不动 |
|
||
|
||
🔴🔴 **0-b 的读数必须看"标准值",⛔ 不看原文**(10-04 实测栽到,`vibe-product`):
|
||
`goal.json` 里可能写着 **带后缀**的 `已完成(机器可判部分)`,而 `goal_life()` 只做精确匹配
|
||
⇒ **静默回落成「等待」** ⇒ 「已完成」被读成「等待」(**语义相反**,排查被直接带偏)。
|
||
✅ 已修:`goal_life()` **剥掉全角/半角括号后缀再判**,且**认不出时写日志**(⛔ 不静默回落)。
|
||
⇒ 📌 判 0-b 一律**问代码**(`python collabd.py --check-status` 或 `goal_life()`),
|
||
⛔ **不要自己读 `goal.json` 的原文再肉眼比对** —— 那正是被带偏的入口。
|
||
|
||
📌 **「没有目标」不是缺口,是「用户用基础会话方式」(用户 2026-10-04 口径)**:
|
||
⛔ **别把「没启用机制」说成「没配好」**。
|
||
代码同向:`goal_life()` 读不到 `goal.json` ⇒ 判「等待」,注释原话
|
||
「**默认必须是「等待」而不是「进行中」**(只有进行中才建检查会话)⇒ 默认给进行中 ⇒
|
||
用户还没开口机制就开始自动建会话 ⇒ **越权**。**宁可等,不可动**」。
|
||
⇒ 🔴 **不设目标就不会有检查会话,这是设计**;⛔ 想让它有 ⇒ **用户开目标**,⛔ **不许我代设**。
|
||
⚠️ 另:`acceptance_state` 记着 **pid 8024 / 10-02 13:44**(两天前快照),
|
||
实际是 **pid 19424 / 心跳 4 s 前** ⇒ ⛔ 判据只用 `started_ts` 与 `ts`,⛔ 别读那个字段。
|
||
|
||
📌 **一句话**:`⛔ 顺序是「先常驻 → 再检查会话 → 才是主会话那一轮执行会话」;
|
||
⛔ **跳过常驻直接建会话 = 建了也不会被接上**(检查会话只能靠排期开,而排期只有常驻会建)。
|
||
|
||
### 🔴 1.6 起不来 = **先报错、先解决,再执行目标**(10-04 用户原话)
|
||
|
||
> 用户原话:「**加个规则 后台任务和检查程序如果不能启动,就先报错解决了问题
|
||
> 在执行完成目标的任务**」「**必须首先启动好在执行**」
|
||
|
||
⇒ 这是**顺序红线**,⛔ 不是"尽量起一下":
|
||
|
||
```
|
||
判断「常驻/检查程序在不在位」
|
||
│
|
||
├─ 在位 ────────────────────────────▶ 才允许往下建会话、派活
|
||
│
|
||
└─ 不在位 ──▶ 🔴 **先起**
|
||
│
|
||
├─ 起成功(pid 活 ∧ 心跳新鲜)──▶ 才允许往下
|
||
│
|
||
└─ ⛔ **起不来 ⇒ 立刻停下、如实报错**
|
||
⛔ 不许"边起边干"(建了会话也没人接)
|
||
⛔ 不许只说一句"起好了"就往下走
|
||
⛔ 不许编原因(只报**读到的现象**,⛔ 不猜病因)
|
||
🔴 **先解决问题,再执行完成目标的任务**
|
||
```
|
||
|
||
⚖️ **三条边界(越界就是又一次"凭空造需求")**:
|
||
1. ⛔ **没目标 ⇒ 不起、也不报**(那是用户在用**基础会话方式**,正常态)
|
||
2. ⛔ **只管「常驻在不在位」这一件事** —— ⛔ 不代用户开目标、⛔ 不自己建检查会话
|
||
3. 🔴 **在位判据只有一条**:`pid 活 ∧ 心跳新鲜(<90 s)`,⛔ 别看 `supervise.pid` 文件本身
|
||
|
||
📌 **谁来执行这条**:**不是靠我记得**,是 `scripts/hooks/supervise-ensure-hook.py`
|
||
(挂 `UserPromptSubmit`)—— 它每轮自动问 `supervise_alive()`,不在位就起,
|
||
**起不来就注入一条报错**让本轮对话看见。
|
||
⚠️ 🔴 **2026-10-04 实测:这个钩子曾长期空转**(只读 `DSH_WS_ROOT`,
|
||
而宿主 env 三级全空)⇒ 详见 `references/pitfalls.md`。
|
||
|
||
|
||
### 二、**「这轮结束了」的判据**(用户原话:靠投递,不靠我判断)
|
||
|
||
| 判据 | 怎么查 |
|
||
|---|---|
|
||
| 任务会话**做完了** | 台账出现它那条的 `state=done` |
|
||
| ⛔ **`done` 必须带 `--artifact`** | `collabd.py --report` 拒收没产物的 `done`(10-03 用户逐字要求)⇒ **`done` 存在 = 产物已登记** |
|
||
| 🔴 **`done` 的产物要真读得到** | `artifact_state()`(**唯一事实源**)判 `ok`/`missing`/`gone` ⇒ **读不到也拒收**(10-04 加,见 `pitfalls.md` P0-58) |
|
||
| 卡住了 | `state=blocked` **必须**带 `--reason`(⛔ 不许只标「卡了」不说卡在哪、谁在等) |
|
||
| **这轮结束** | 台账该轮全部 `done` ⇒ ⛔ 此时**零活任务会话是正常的**,⛔ **不许报「缺执行会话」** |
|
||
|
||
### 二·补 🔴 **文档合同 —— 「谁写哪份文档」**(用户 2026-10-04 口径逐字)
|
||
|
||
> 「之前还说过 **执行会话的结果要形成文档,目标也要完成情况的文档**,
|
||
> 这样后续检查会话和后续执行会话都可根据文档继续处理,**避免全工作区到处找信息**」
|
||
|
||
🔴 **为什么必须有这份合同**(实测**三区三种形态** ⇒ 缺的**不是能力,是合同**):
|
||
某区只有 `目标执行状态.md`(420 B,**执行产物没落这儿**)/某区有**手工**写的「过程记录」(⛔ 非机制要求)/
|
||
本区 `S12_*.md` 落对了 —— **同一套机制、三个区三种形态**。
|
||
|
||
| 文档 | 谁写 | 什么时候写 | 谁读 |
|
||
|---|---|---|---|
|
||
| `<目标目录>/目标执行状态.md` | **目标检查会话** | 核对完目标状态后 | 下一个检查会话/用户 |
|
||
| `<目标目录>/<棒次>_<事项>_<日期>.md` | **任务会话(执行棒)自己** | 本轮做完时(=`--report done` **之前**) | 检查会话照 `artifact` 读 |
|
||
| `tasks.json` | `--report`(机器) | 每次状态转移 | 全部环节 |
|
||
|
||
🔴🔴 **目标目录的完整形态(2026-10-05 用户定案,逐字:「`<工作区根>/执行会话/目标-xxx-xxxxxx/` 改成这样」)**:
|
||
|
||
```
|
||
<工作区根>/执行会话/目标-<简称或标题前缀≤20字>-<sha1(title)前6位>/
|
||
```
|
||
|
||
- **加了一层 `执行会话/`** —— ⛔ 目标文件夹**不再直接散在工作区根**(用户诉求:根目录干净、历史目标归拢一处)。
|
||
- 单一真源:常量 `_GOAL_DIR_PARENT = "执行会话"` + `goal_dir_name()`(⛔ 别在调用方各拼一遍)。
|
||
- ⚠️ 短哈希算的是 **`title`(全量标题)**,⛔ 不是 `short` ⇒ **改标题 = 换目录名**(存量里已有漂移案例,见 `pitfalls.md`)。
|
||
- ⚠️ **域目录不受影响**:独立域仍是「工作区第一层目录」;`执行会话/` 是**目标文件夹的父层**,⛔ 不参与域键计算。
|
||
|
||
🔴 **落点是死的**:执行产物的 `--artifact` **必须**指进 `<目标目录>/` 里
|
||
(⛔ 不许写回 `交付物/`、`docs/`、工作区根 —— **那正是「到处找」的由来**)。
|
||
⚠️ **落点=软判据**(`artifact_dir_ok()` 只**提示**):存量里有落在别处但有效的产物,
|
||
硬拒会误杀既有工作流;但**必须说出来 + 写日志**(⛔ 静默通过=合同等于没立)。
|
||
🔴 **存在性=硬闸**(`artifact_state()`):**读不到就拒收**(两层判据 ⛔ 别混)。
|
||
|
||
📌 **`artifact_state()` 是唯一事实源** —— ⛔ 不许在 prompt/看板/CLI 里各写一段 `if not artifact`。
|
||
|
||
📌 **台账是唯一权威**:`tmp/supervise-inbox/tasks.json`(四态 `pending/running/blocked/done`)。
|
||
⛔ 判「有没有在做的事」一律现取它,⛔ 别读看板/文档里的快照(`acceptance_state` 是快照,
|
||
⚠️ 实测它记着 10-02 的 `pid 8024`,而 10-04 实际是 `pid 19424`)。
|
||
|
||
### 三、检查会话**不能固定席位**(用户原话)
|
||
|
||
> 「**没办法固定要是有好了,为什么不行有记录吗**」
|
||
|
||
⇒ 🔴 **不许预先摆一条「固定检查会话」**。理由(有记录就不怕丢):
|
||
**记录在台账里** ⇒ 检查会话是**可丢弃的** ⇒ 有好了就换一条,⛔ 不必留一个常设席位。
|
||
⇒ ⛔ **反过来的推论**:台账空 = **没有在做的事** ⇒ ⛔ **不许**因此去建任务会话。
|
||
|
||
### 四、状态 → 动作(**这张表就是「什么时候该做什么」的答案**)
|
||
|
||
| 当前状态(**现取台账**) | 我该做的 | ⛔ 不许做的 |
|
||
|---|---|---|
|
||
| 台账有 `pending` | 取那条执行;没意外就交给检查会话 | ⛔ 另起一批任务会话 |
|
||
| 台账有 `running` | 让它跑;检查会话去核 | ⛔ 催它、⛔ 重建 |
|
||
| 台账全 `done` | **这轮结束** ⇒ 报「已完成 + 剩什么」 | ⛔ **不许**编「欠项」、⛔ 不许找活干 |
|
||
| 台账空 | 报「**没有在做的事**」 | ⛔ **不许**建任务会话、⛔ 不许造需求 |
|
||
| **目标 `lifecycle=已完成`**(含 **带后缀**形态,如 `已完成(机器可判部分)`) | 🔴 **先看台账,⛔ 不是一律收工**:<br>· 台账**还有未完成件**(`pending`/`running`/`blocked`)⇒ **判定=还有活没人干** ⇒ **照常建 `[结果检查]`**(闸① **不**拦它)<br>· 台账**全 `done`/空** ⇒ **判定=收工** ⇒ 报「做完了 + 还差什么人工确认」 | ⛔ **不许**把「已完成」说成「等待/没点头」(**语义相反**);⛔ 不许自行续活 |
|
||
| **没有目标**(`goal.json` 无 / `lifecycle=等待`) | **判定=用户在用「基础会话方式」** ⇒ 正常,⛔ 什么都不用起 | ⛔ **不许**说成「缺目标/缺配置」、⛔ **不许**代他设目标、⛔ 不许起检查会话 |
|
||
| 有 `blocked` | 报「卡在 X,等谁」⇒ **问用户** | ⛔ 不许自己替用户决定绕过去 |
|
||
| 用户明确说「用执行会话完成 X」 | 才走上面第 ① 步建一轮 | ⛔ 不许替用户决定开不开 |
|
||
|
||
### 五、⛔ 三条由此推出来的红线(2026-10-04 当天栽出来的)
|
||
|
||
1. ⛔ **凡不在上表的状态 ⇒ 问用户,⛔ 不许推断后当成需求或欠项。**
|
||
2. ⛔ **「实体里存在」⛔ 不等于「该有」** —— 台账里 8 条任务会话不在跑 = **上一轮的遗留**,
|
||
⛔ **不是**「缺 8 条」。⚠️ 实测:2026-10-04 我正是把「遗留」读成「欠项」,
|
||
而那 8 条对应的轮次早已完成。
|
||
3. ⛔ **⛔ 不许把自己以为该有的东西摊成「欠项」/「风险」/「待办」** ——
|
||
那是**凭空造需求**(当天三次:开机自启/三类会话/两类会话)。
|
||
|
||
|
||
### 六、🔴🔴 **完成情况判据 · 唯一事实源**(2026-10-04 用户定案)
|
||
|
||
> 用户原话:「**是不是应该统一完成情况的 状态标准,不要换个工作区换个目标,就统计不准确**」
|
||
|
||
**病根**:同一条「这条验收算不算过」的判据,曾在**三处各写一遍**,于是必然漂移:
|
||
|
||
1. `scripts/board.py` → `acc_is_pass()`(**唯一实现,改判据只改这里**)
|
||
2. `scripts/collabd.py` → `_acc_is_pass()`(**只转发 board,⛔ 不许自己写词表**)
|
||
3. `assets/board.html` → `accIsPass()`(前端跑不了 Python ⇒ **抄同一份白名单**)
|
||
|
||
**白名单(全库唯一)**:`pass` / `过` / `通过` / `达` / `达标` / `合格` / `完成`
|
||
(Python 侧常量 `ACC_PASS_WORDS`;JS 侧常量 `ACC_PASS_WORDS` —— 两处**必须逐字相同**。)
|
||
|
||
**算法(三处同款)**:① 掐掉括注(`(`/`(` 起)② 按 `:`/`:` 切段 ③ 逐段 `startswith` 白名单词
|
||
④ 一段都没命中 ⇒ `False`(**fail-closed**)。
|
||
⚠️ ⛔ **不许「只取最后一段」** —— 那是 collabd 的一版漂移(`过:1440` 与 `过:390` 会判相反)。
|
||
|
||
**分母口径(三处同款)**:`_` 开头的键是**说明行**(`_说明`/`_更新`…),
|
||
⛔ **不许进分母** ⇒ 分母 = **有效判据条数**(否则通过率永远到不了 100%)。
|
||
|
||
**新增判定词的正确姿势**:改 `board.py::ACC_PASS_WORDS` **一处** ⇒ 同步 `board.html` 同名字符串
|
||
⇒ 跑 `selftest.py`(`t_acc_is_pass_chinese` 已含 `达/达标/合格/完成` 与反例 `未达/待达标`)
|
||
⇒ 跑 `sync` 到各工作区副本 ⇒ **看板要重启**才吃到 `board.py`(`board.html` 实时读盘、不用重启)。
|
||
|
||
**为什么当初没抓到**:自检用例**只喂了 11 种写法、⛔ 没有 `达`**,而真源恰恰主用 `达`
|
||
⇒ **用例盲区 = 判据盲区**(同族:`pitfalls.md` P0-13/P0-20「判据写死期望值 ⇒ 永远不命中真源」)。
|
||
|
||
|
||
## §1 第一段 · 会话机制
|
||
|
||
> 这一段**不依赖**多会话协作;只把「会话活得下去」这套装上。
|
||
|
||
**三件套**
|
||
1. **钩子**(`settings.json` 全局生效,`scripts/hooks/` 6 份):日志闸(`session-log-guard.py`)/锁闸(`lock-guard-hook.py`)/输出闸(`bash-output-guard.py`)/叫停闸(`stop-dialog-guard.py`)/技能闸(`skill-load-guard.py`)/结果回报(`wb-result-hook.py`)。
|
||
2. **锁**(`scripts/lock/`):
|
||
- 开工三步=`scripts/dsh.py open`(本项目入口,内部跑 ①状态 ②preflight ③抢锁);
|
||
- 抢锁 `handoff-guard.sh --claim-exec "<会话名>" [--domains <域>]` —— **⛔ 机制层必须独占(不带 `--domains`)**;抢不到 ⇒ **停手 + 报告**(红线 R9:⛔ 不删锁、不接管)。
|
||
- 释放必须反序:`--release` → `--release-exec "<会话名>"`;⛔ 不带名 ⇒ 拒释放。
|
||
3. **日志闸**:文件软 **5** / 硬 **8 MiB**;工具调用软 **200** / 硬 **250**。命中 ⇒ 开接续会话。
|
||
|
||
**🔴🔴 执行会话「独立域」硬规则**(用户 2026-10-02 定案,逐字)
|
||
> 「创建协作会话还要加个判断:协作会话必须是独立域运行的,就是做所有修改操作都在单独的文件下运行
|
||
> (比如某个项目要开发 webserver,desktop,phone app,独立插件或产品原型)这些文件都可以放在工作区对应
|
||
> 独立文件夹下,**只能只读的方式访问别的文件夹内容**。应为会话有锁的机制,开多个会话都操作一个域的文件
|
||
> 只有一个会话能执行,别的只能干等。」
|
||
|
||
- **域目录=工作区下的第一层目录**(`<工作区>/webserver/`、`<工作区>/desktop/`…)。
|
||
🔴⛔ **绝不许套公共父目录**(`domains/xxx`、`projects/xxx` 那种)—— 实测那样所有子目录会算出
|
||
**同一个域键** ⇒ 域锁等于没有、并行直接失效(本机制实测踩过并已改正)。
|
||
- **域键算法只认第一层**:域键=`<工作区名>/<第一层目录名>`,与再往下钻几层**无关**。
|
||
⇒ 想让两个会话真并行,就给它们**两个不同的第一层目录**;⛔ 在同一目录里再分层**不能**解锁并行。
|
||
- **开工第 0 步必须先抢域锁**:`handoff-guard.sh --claim-exec "<会话名>" --domains "<域目录>"`;抢不到 ⇒ **停手报告**,⛔ 不许硬写。
|
||
- **写只许在域目录内,跨目录一律只读**;必须写到外面时 ⇒ **不写**,在 `tmp/supervise-inbox/NEED-USER.md` 写明要谁批准。
|
||
- 三条命令(`collabd.py`):
|
||
```bash
|
||
python "<包>/scripts/collabd.py" --domain-status # 域现状体检(在册域锁 + 锚点词表一致性)
|
||
python "<包>/scripts/collabd.py" --domain-suggest "<类别>" # 推荐一个**当前空闲**的域名
|
||
python "<包>/scripts/collabd.py" --domain-check "<域名>" # 判这个域能不能派(⛔ 被占时 rc=1)
|
||
python "<包>/scripts/collabd.py" --domain-block "<域名>" # 打出派活要嵌的门禁块,⛔ 别手抄
|
||
```
|
||
- **门禁已自动嵌进派活**:任务会话 prompt(`GAP_PROMPT["worker"]`)与检查会话 prompt(`CHECK_PROMPT`)都带上了这段,
|
||
⛔ 跟进/唤醒**不带**(它们只读+建排期,给域目录是反向约束)。
|
||
|
||
**关键判据(踩过才写在这)**
|
||
- 🔴🔴 **用这套技能的第一件事=查环境配置,配没配决定后面全部动作**(用户 2026-10-02 定案:「重点是**技能的使用时要检查环境配置是否已配置,如果没有配置就要先配置**」)。
|
||
```bash
|
||
# 体检(只读,⛔ 无副作用)—— 判技能库根能否定位 + 已注册钩子逐条目标是否存在
|
||
python "<包>/scripts/hooks/_env.py" --ws "<工作区绝对路径>"
|
||
# 写环境标记(状态变量+时间),scope 决定落在哪个文件夹
|
||
python "<包>/scripts/hooks/_env.py" --scope global --stamp --fixed "<修了什么>"
|
||
python "<包>/scripts/hooks/_env.py" --ws "<WS>" --scope workspace --stamp
|
||
```
|
||
- **作用域要问用户**(用户 2026-10-02 原话:「可以询问是配置在**全局**还是**本工作区**」)——
|
||
🔴 **默认问、别默认写**:**全局**=一次配好所有工作区共用(改它影响**所有**工作区 ⇒ 属影响面变更);
|
||
**工作区**=只管本工作区(换机器/别的项目要各自重配)。
|
||
判据:影响面超出本工作区 ⇒ **必须问**;纯本工作区 ⇒ 自决策并**一句话说明**。
|
||
- 标记落点:全局 ⇒ `<配置目录>/env-stamp.json`;工作区 ⇒ `<工作区>/.workbuddy/env-stamp.json`。
|
||
字段=`scope`/`checked_at`(ISO 到秒)|`ok`|`config_dir`|`skills_root`|`hooks`(逐条存在性)|`fixed`。
|
||
- 🔴 **定位不到技能库根 ⇒ 报错 + 非零退出,⛔ 绝不「静默零输出」**(2026-10-02 实测事故的病根):
|
||
`reply-style-guard.py` 被注册成文档库里的旧副本 ⇒ 沿 `__file__` 上溯够不到 `skills/`
|
||
⇒ 落进 `os.path.expanduser('~/.workbuddy/skills')`,而 **Windows 上 `~` 不是真配置目录**(真值在
|
||
`CODEBUDDY_CONFIG_DIR`)⇒ **静默零输出**,日志只留一行 `core=0 字符`
|
||
⇒ **「机制坏了」与「没配规则」表现完全一样**(真实代价:另一工作区为此绕了两轮,在「规则文件在不在」上打转)。
|
||
⛔ `~` 回落已从 `_skills_root()` 删除;**每一档 env 都要验目录真存在**(env 给错要继续往下找,⛔ 不猜)。
|
||
- 工作区 `state.py` 已有 `§5c [钩子环境]` 一项跑它 ⇒ **开工跑状态就能看见**,⛔ 不必另记命令。
|
||
- 🔴 **各脚本的根目录一律先读包内 `roots.env`,再回落按位置推导**(已贯通 **16 份**:9 钩子/锁 + 7 非钩子脚本)—— 因为 `settings.json` 的 hook 条目**没有 `env` 字段**,包内脚本**搬一次就会静默指错**(历史事故:台账写到别处、测试却全绿;近期又复现一次:`wb-result-hook.py` 按 `__file__` 推三层 ⇒ **把技能包当工作区**,在包里长出 `tmp/supervise-inbox/`)。**⛔ 不要靠 `mv` 搬迁,⛔ 不要靠"改壳转发"**(转发会改 `$0`,同样挪走根)。**⛔ 代码里不留盘符字面量**:外部根只能来自 `roots.env`/宿主 env。**🔴 2026-10-02 起再收一层**:「配置目录/技能库根」的取法统一走 `scripts/hooks/_env.py`(7 个钩子已改),⛔ 各脚本不再自己拼 `~`。
|
||
- 🔴 **出口一律声明编码**:钩子脚本写 stdout 走 `sys.stdout.buffer.write(bytes)`(文案含 `⛔` ⇒ 否则 `UnicodeEncodeError` ⇒ stdout 空 ⇒ **静默放行**);**非钩子脚本**(`collabd` / `board` 等,会被重定向到文件或 `DEVNULL`)在文件头加**输出编码兜底**(`sys.stdout/stderr.reconfigure(encoding="utf-8", errors="replace")`)—— 实测:重定向 + 本地 GBK ⇒ `print("⛔…")` 抛异常 ⇒ 被顶层 handler 记成 `fatal`、**整轮失败**(常驻必踩)。
|
||
- ⚠️ **本机**:裸 `bash` 可能落到 WSL 启动器 ⇒ 要跑 shell 一律显式用 `PortableGit/.../usr/bin/bash.exe`。
|
||
- 🔴🔴 **钩子总预算(2026-10-02 事故)**:**钩子超时 ≠ 钩子变慢,而是用户这一句话被拦下**(`UserPromptSubmit operation blocked by hook: Hook timed out after 20000ms` ⇒ 提交失败,不是慢)。根因=**`UserPromptSubmit`(宿主注册 20 s)上一个脚本里串了三个子进程**:`collabd --gap` **13.5 s** + `--once` 0.5 s + `--tick` **13.5 s** ≈ **27.5 s** ⇒ 必超。四条硬规则(已落进 `wb-result-hook.py`):
|
||
1. **开局认领预算**:`BUDGET = {UserPromptSubmit: 18, PreToolUse: 25, SessionEnd: 8}`(各比注册值少 2 s)⇒ 任何**要等**的子进程,先问 `_left()` 够不够,不够 ⇒ 跳过并留痕。
|
||
2. **门槛按"实测耗时"给,⛔ 不按硬超时给**:`sweep`/`supervisor` 实测都是 **0.95 s**,若按硬超时 8 s/6 s 设门槛,在 8 s 预算下**永远跑不到**(判据看着在、其实恒假)。
|
||
3. **不需要结果的活 ⇒ 后台**(`_bg()`:`Popen` + `DETACHED_PROCESS|NEW_PROCESS_GROUP` + stdout **落文件**,⛔ 不用 PIPE):实测**父进程退出后仍能跑完**(15 s 写完 11 KB)。⛔ `CREATE_BREAKAWAY_FROM_JOB` 在本机**必失败**(`PermissionError 13`)⇒ 别加。
|
||
4. **一轮只允许一个贵活**(`_HEAVY_DONE`),其余后台;**慢的产物落缓存**(`gap-cache.json`,后台写 `.tmp` → 下轮 `os.replace` 收割,钩子只读缓存=毫秒级)⇒ 实测钩子 **27 s → 0.5~1.9 s**。
|
||
⚠️ 附带发现:`SessionEnd` 注册只有 **10 s**,而它上面挂着 `--tick`(13.5 s)+`--once`(25 s 硬超时) ⇒ 那条**从来就没跑完过**(被掐)⇒ 别拿它的缺失当"机制没装好"。
|
||
|
||
- 🔴🔴 **后台子进程一律无窗口(pythonw)**:`collabd.py --supervise`(投递常驻,每 10 s 一轮)+ 看门狗 `guard.py`(每 15 s 探活、子进程一死就重生)原本以 `python.exe`(**控制台子系统**)起来,被宿主/钩子/看门狗拉起时 Windows **新分配一个控制台窗口** ⇒ 每次重生 / 每轮 `netstat` 就闪一下黑窗(用户原话「一会弹出来一会弹出来的,影响我操作」)。🔴 **根因**:`guard.py Child.ensure()` 拉起子进程 `creationflags=0x00000008`(**只有 DETACHED_PROCESS,漏 CREATE_NO_WINDOW**);`wake-session.py` 的 `netstat` **没有任何 creationflags**。`collabd.py` 的 `netstat` 已于 09-29 修(带 `0x08000000`)。✅ **根治(2026-10-02 落地)**:所有常驻/后台 `subprocess` spawn 一律改用同目录 `pythonw.exe`(**GUI 子系统,Windows 永不为其分配控制台**)+ `creationflags` 补 `NO_WINDOW|DETACHED|NEW_PROCESS_GROUP`:① 三个脚本加 `_win_pythonw()` 解析器(模块级 `PYW`);② `collabd.py ensure_supervise` 与 `guard.py Child.ensure` 的 `sys.executable`→`PYW`;③ `wb-result-hook.py` 5 处 spawn(含 `_bg`/sweep/once/tick)`sys.executable`→`PYW`;④ `wake-session.py netstat` 补 `0x08000000`。⛔ **今后任何新加的常驻/后台 spawn 都走 `PYW` + NO_WINDOW**,⛔ 别再用 `sys.executable` 起会长期存活的子进程(备份 `*.bak-flicker-20261002.py` ×4)。
|
||
|
||
- 🔴🔴🔴 **R 红线:⛔ 严禁用「排期/自动任务」当常驻的载体或替身**(2026-10-04 用户定案,当场立)
|
||
|
||
**一句话**:「**常驻要死**」⛔ **不是**建一条排期去续它的命。
|
||
✅ **正确处置=按需重起**(`collabd.py --supervise` + `pythonw` + `NO_WINDOW`)。
|
||
📌 **口径(2026-10-04 用户订正)**:**常态=调用技能完成目标时起后台任务 + 检查程序**;
|
||
「开机自启/计划任务」只是**想做"目标做完程序还继续跑"时**的可选做法,⛔ **不是需求、不是欠项**。
|
||
|
||
🔴 **实测的反面案例**(`vibe-product`,`id=e181b51b`):`[执行]-界面交互-常驻续命`,
|
||
`FREQ=HOURLY;INTERVAL=1` —— prompt 明写「不在 ⇒ **后台任务**起一条 `collabd.py --supervise`」。
|
||
**15:23 真跑过一次**,会话结论是「**✅ 常驻存活,本轮未做续命**」
|
||
⇒ **每小时唤醒一个新会话,只为看一眼常驻活没活**;而常驻本来就活(pid 19424 连跑 17.7 h)。
|
||
⇒ **零产出、纯烧钱,且违背本技能自己写的 `supervise-persistence.md`「⛔ 排期代替不了载体」**。
|
||
|
||
⛔ **四条禁止**(逐条都有实测/文档依据):
|
||
1. ⛔ **不许建 `recurring` 排期去「续命/保活/巡检常驻」** ——
|
||
✅ 要续命就**按需重起**(`pythonw` + `NO_WINDOW`,见 `references/00-动手前必过.md`)。
|
||
⚠️ 需不需要"目标做完还继续跑" ⇒ **问用户**,⛔ 别自己假设要。
|
||
2. ⛔ **不许把排期当"谁来按点喊一次"的常驻替身** —— 排期喊完会话就结束 ⇒ **跳与跳之间必有空窗**,静默窗内无人。
|
||
3. ⛔ **不许在排期 prompt 里写「不在就起一条 `--supervise`」** —— 那是把「换载体」偷换成「每次重拉」,且起于**会话/工具调用**的子进程**活不过当轮**。
|
||
4. ⛔ **常驻真死时不许用排期兜底**,先查三样:停止标志 `guard.stop`(在=正常收工,⛔ 不是故障)|
|
||
心跳年龄(`supervise-heartbeat.json` 的 `ts`)|pid 还在不在(`_pid_alive`)。
|
||
|
||
✅ **常驻真死时的正确四条**:
|
||
① 先看**停止标志** `guard.stop`(在则是「被正常收工」,⛔ 不是故障);
|
||
② 看**心跳年龄**(`ts` 距今 > `SUPERVISE_STALE` 即已陈旧,⛔ 别看退役旧戳);
|
||
③ 宿主钩子按需**一次性**唤起 `collabd.py --tick`(**补充**,⛔ 不是「常驻」);
|
||
④ 需要长期跑 ⇒ 问用户要不要登记**计划任务**(`references/supervise-persistence.md`,⛔ **不默认要**)。
|
||
|
||
📌 **判据**:`selftest.py` 的 `t_no_schedule_as_supervisor`(⛔ 扫技能文档里"排期当常驻载体"的表述 + 扫脚本里"排期里起 --supervise"的写法),改动前后都报红即通过。
|
||
📌 **用户 2026-10-01 已定**:「**协作与投递一直运行(常驻)**」+**「自动任务当闹钟」方案已废弃** —— 本条只是把它**升格为红线 + 落判据**。
|
||
|
||
- 🔴🔴🔴 **S 红线:⛔ 严禁把「被监控对象所在的工作区」当成「目标归属的工作区」**(2026-10-04 用户当场纠正)
|
||
|
||
**一句话**:**目标在哪个工作区下达,执行它的会话就属于那个工作区** —— 被它操作/观察的**别的**区只是**对象**,⛔ 不是归属。
|
||
|
||
🔴 **实测的反面案例**:用户在本工作区(`ai1net-dsh-server`)下达
|
||
「使用执行会话完成目标:持续监控 **vibe-product** 工作区主会话使用会话技能的情况」,
|
||
我把排期 `cwds` 写成 `E:/ProgramData/AIProject/vibe-product`
|
||
⇒ 会话 `c88a157c` 落在 `vibe-product` ⇒ **本工作区看板/台账里直接看不见它**,
|
||
而本工作区恰恰是机制问题最集中、最需要它的地方。
|
||
⇒ 用户原话:「**本工作区下面的目标 为什么执行会话要创建到 vibe-product 工作区下面**」。
|
||
|
||
⛔ **三条禁止**:
|
||
1. ⛔ **不许把「要去看/要改的那个区」写成 `cwds`** —— 那是**对象**,不是**归属**。
|
||
2. ⛔ **不许用「目标文本里出现了某区名」来定 `cwds`** —— 出现的是**被操作对象**,
|
||
归属看的是「**这条命令从哪个工作区发出**」(`sessions.cwd` 逐字同形,正斜杠)。
|
||
3. ⛔ **交付物、台账、修复动作**一律落在**归属区**,⛔ 不许顺手落到对象区。
|
||
|
||
✅ **正确做法**:目标跨区时,`cwds`=**下达目标的那个工作区**;prompt 里显式写
|
||
「⛔ `<对象区>` 是被监控对象,**不是**你的工作区 —— 别去那儿建会话、别改那儿的东西」;
|
||
同步技能改动用 `scripts/workspace_mirror.py --sync <对象区副本>`(⛔ 只读对象区 + 写**全局**技能)。
|
||
|
||
⚠️ **唯一例外**(需用户在**当轮**明确要求):用户说「在 X 工作区建」⇒ 才建在 X。
|
||
⚠️ 机械背景:`automations.cwds` ⛔ **不落** `sessions.cwd`(后台会话可带独立 cwd)⇒ 判断归属看 `cwds` 字面 + 会话 `cwd`。
|
||
|
||
📌 **判据**:`selftest.py` 的 `t_ws_attribution`(扫技能文档里「对象区 / 归属区」的口径是否在位)。
|
||
|
||
|
||
---
|
||
|
||
## §2 第二段 · 多会话执行
|
||
|
||
> 这一段**可单独使用**(不装钩子与锁也能跑常驻程序本体)。
|
||
|
||
**四条通道各走各的**
|
||
· **派活** ⇒ 自动化(**唯一能开新会话的通道**;⛔ 钩子做不到)。
|
||
· **收结果** ⇒ 直读宿主库(0 token)。
|
||
· **机械判定** ⇒ 下沉到本地只读程序(`collabd.py` / `board.py` / `goalctl.py`)。
|
||
· **人看的** ⇒ 只有一个看板(`assets/board.html` + `board_ext.py`)。
|
||
|
||
**铁律(全部实测得来)**
|
||
- ⚠️ **【2026-10-03 口径已改 · 本条「四类」部分作废】** 现行=**两类**(① 主会话 ② 任务会话;唤醒会话/跟进会话/队列上报**整套退役**)⇒ 以**文首口径块**为准,⛔ **别照本条去建会话**。(原文保留仅为留痕,⛔ 不删。)
|
||
- 🔴 **同工作区 = 四类会话**(2026-10-01 用户口径,⛔ **已于 2026-10-03 作废**):**① 主会话(只管目标和方向)② 执行会话 ③ 唤醒会话 ④ 队列上报的跟进会话**。四类**同处一个目录**,**靠标题两级前缀区分**(第 1 级=角色/第 2 级=任务类别),⛔ **不按 `cwd`**。✅ 第 ④ 类**解析层 + 投递路由都已落地** —— 投递目标=**跟进会话**(`follow_for_topic()`),解析不出 ⇒ **喊用户**,⛔ **不降级投主会话**(用户:「**换新会话 唤醒的是 跟进会话,主会话只能是用户触发**」)。🔴🔴 **第④类的职责只有一件事:创建执行会话**(用户 2026-10-01 22:5x 细化,逐字:「**跟进会话 只负责 ,创建协作会话(1、跟进上报后判断是否创建 2、被唤醒后 跟进目标情况 判断是否创建)**」)⇒ 两条触发、同一个动作:**收到队列上报** 或 **被唤醒** ⇒ 跟进目标情况 ⇒ **判断是否建一条 `[协作]` 会话**。⛔ **它自己不做具体活**(⛔ 不改台账 `state`、⛔ 不写 `blocked.json`、⛔ 不派活、⛔ 不抢锁)—— 那些是**它建出来的那条执行会话**的事。细则 ⇒ `architecture.md §2.3.0c`。⚠️ **看板图上这三处写的是简称**(用户 2026-10-01 23:1x 定):**唤醒**=唤醒会话、**跟进**=队列上报的跟进会话、**协作**=目标检查 —— 简称**只是压缩版面**,⛔ **不是又多了角色**(尤其「**协作**」⛔ 别读成「**协作会话**」,那是另一层)。对照表 ⇒ `architecture.md §2.3.0c-2` / `collab-detail.md`。
|
||
- 🔴 **`board.html` 的"虚线大框"=分组,⛔ 不是节点、也⛔ 不是"新一层"**(用户 2026-10-01 第四改:「用一个**虚线大框**把 主会话 唤醒会话 和 跟进会话都框起来,这个虚线大框 **连接 协作会话 虚线大框**就好」)⇒ `UA`=主会话+唤醒+跟进、`UB`=任务会话那一排,两组之间**只有一条线 = ① 派活**(⛔ 不再从主会话往每一格画射线)。⚠️ 两个框的尺寸**都从里面的格子现推**(⛔ 不写死坐标)—— 改了格宽不重算框 ⇒ 虚线**横穿文字**,而**那种图照样能渲染**(两道自检守着它)。细则 ⇒ `architecture.md §2.3.0c-2`。
|
||
- 🔴🔴 **开工第 0 步 = 会话规则机制体检**(用户 2026-10-02 明令)⇒ **先查清,再动手**(补建会话是它**后面**一步)。用户给了**两句,第二句是纠正**:
|
||
· ① 原话逐字:「**这个会话和协作会话的技能包 运行的第一件事 ,就应该是检查清楚 所有会话规划是否配置完整且生效,然后标记一个状态**」
|
||
· ② 原话逐字:「**就应该是检查清楚 所有会话规则机制 是否配置完整且生效, 不是规划 是 规则**」⇒ 对象 = **规则机制**(钩子 / 闸门 / 技能指针 / 常驻 / 编排…),⛔ 不是"排期规划"。🔴 首版按①的字面做成「会话规划体检」、**只查排期那一面** ⇒ 当天实测出的三类失效(钩子注入指向**已退役技能名** / 快照**写进幽灵目录** / **每轮注入的记忆**里指针悬空)**一条都查不到** ⇒ 旧脚本已退役到 `<WS>/归档/技能包-旧件-20261002/`,**同包内只剩一个入口**(两个入口 = 「在册 ≠ 生效」本身)。
|
||
⇒ **怎么跑**:`"$PY" "<本包>/scripts/session-rules-check.py" [--ws <工作区>]` —— ✅ **已接进工作区 `state.py`**(跑状态快照就自带这一段,⛔ 不必另记一条命令)。
|
||
⇒ **查三类、十二项**:**A 机制装没装好** ① 关键钩子在册 ② 钩子脚本路径存在 ③ 钩子注入里引用的技能名**是否还存在** ④ 钩子**真在被调用**没(闸门日志新鲜度)|**B 规则载体同没同步** ⑤ **每轮注入的记忆**里引用的技能名存在 ⑥ 常驻规则快照**不比权威旧**|**C 编排在不在跑** ⑦ 唤醒 / 跟进两台**周期钟**(缺 = 没人推 / 没人收)(⚠️ 其中「唤醒」这台是**代偿形态** —— 定案的唤醒时钟=**常驻投递**(⑩ 那一项查的才是它);「唤醒排期在册」⛔ **不等于唤醒时钟已就位**,两件事要分开读)⑧ 排期绑的模型**会不会被服务端拒**(`model_is_thinking=0` + flash 系 ⇒ 每触发必拒,2026-10-02 实测)+ 🔴🔴 **机制建的排期模型是否与「本区主会话」同值**(2026-10-05 加,P0-80 —— 此前 `collabd.py` 把模型**写死**成 `space-bunny`,而主会话是 `deepseek-v4.1-flash` ⇒ 「同一件活在两种模型上跑」;⚠️ 该判据**扫描面必须含 `once`**,机制建的排期全是 `once`,只扫 `recurring` 就恒绿)⑨ `cwds` **归属同形**(错一字面 ⇒ 裂组且自我强化)⑩ **投递(常驻)**心跳 ⑪ 三类会话**当前有没有活的** ⑫ 有没有「**从未运行**就失效」的一次性排期。
|
||
⇒ **标记**:结论写成 `<WS>/.workbuddy/collab/session-rules.json`(`verdict` = `ok`/`warn`/`fail` + 逐项 `detail`)—— 后续会话与看板**读它**,⛔ 不靠人复述。
|
||
🔴 **为什么必须是第一件事**:2026-10-02 实测——排期**都在册**、模型**都可用**、cwds **都同形**,**却三类会话一条活的都没有**(=配置在、机制没在跑);同一天还查出钩子注入文本指着**已合并退役的技能名**、常驻快照脚本**写到没人读的幽灵目录** ⇒ 全是「**看着有配置、其实没生效**」。这类状态**不问就不会知道**,等它表现成"卡住"时已经晚了。
|
||
🔴 **判据本身也要能报出问题**:⑨ / ⑫ 这两项(以及 `cwds` 判据的边界)用**合成样本 + 四个变异体**做过红绿对照(夹具 `<WS>/tmp/rules-check-mutate.py`,跑完即弃)—— 变异体=判据恒空 / 判据放宽成"同父目录即报" / 把"从未运行"当"跑完了" / 不排除"还有下次触发"的排期,**逐一按预期报红**。⛔ **别拿"实跑一次没报错"当验收** —— 判据恒空时那次实跑**同样是绿的**。
|
||
⛔ **只标记、不设卡**:体检 `rc≠0` 也照常开工 —— 它的职责是**把状态问清楚**,不是拦人。
|
||
- 🔴🔴 **开工清单:主会话开工的第 0 步不是"派活",是"把三类会话摆好"**(用户 2026-10-01 明令:「**开始会话完成需求的时候,主会话需要创建 唤醒会话 以及根据分工类别 创建 协作会话 和 跟进会话呢 不然整个机制跑不起来**」)⇒ 建 **唤醒会话** `[唤醒]-<类别>-<具体>`(少建=**没人推**,需求原地静着)+ **每个分工类别一条任务会话** `[执行]-<类别>-<具体>`(少建=**没人干**)+ **跟进会话** `[跟进]-<具体>`(少建=**没人收**)。🔴 **跟进会话是全局唯一席位、⛔ 不按类别各建一条**(2026-10-02 用户订正);它是**收口者**:任务会话干完活把**待核对状态**写进执行队列,上报给**这固定的一个**跟进会话处理。⚠️ **只有自动化能开新会话** ⇒ "建会话"=登记一条自动化,⛔ 不是自己 spawn;标题**第 2 级必须带方括号、值取 `goal.json` 的 `topics`**(⛔ 用 `short` ⇒ 静默漏管)。细则 ⇒ `architecture.md §2.3.0d`。
|
||
- 🔴🔴 **缺会话 ⇒ 自动拉起**(用户 **2026-10-02** 口径,逐字:「**是用户说 使用协作会话方式 完成目标 或 继续完成目标**」)
|
||
⇒ 用户说这两句(或队列堵住)时:**先查三类会话齐不齐、活不活**;**缺 ⇒ 机制自己补建排期把它拉起来**,
|
||
⛔ **不许把"你去开一条会话"甩给用户**(旧行为=写 `NEED-USER.md` 喊人开会话,本条**取代**它)。
|
||
· 判据 + 现成排期参数 ⇒ `collabd.py --gap [--json]`(**只读**:判缺 + 给 `automation_update` 的 name/prompt/scheduledAt)
|
||
· 把结论**送进会话** ⇒ 钩子 `wb-result-hook.py::maybe_inject_session_gap()`(`UserPromptSubmit` 注入,触发词命中即查)
|
||
· ⛔ **脚本不许写 `automations` 表**(双红线)⇒ 建排期只能由**会话**用 `automation_update` 执行
|
||
—— 这一步**就是"自动"的全部通路**(用户只需照常说那句话,什么都不用做)。
|
||
· 🔴 **跟进会话只有一条、不带类别**(2026-10-02 用户订正,逐字:「**跟进会话只创建一个,跟进的内容
|
||
来自 执行会话执行完成 后 把 待核对状态 写入 执行队列,上报给那个 固定的 跟进会话处理**」)
|
||
⇒ `collabd.py --gap` 对 `follow` **不按类别分桶**(桶键恒 `(follow, "")`,显示成「全类别(固定席位)」),
|
||
拉起的排期名=`[跟进]-队列上报(固定席位·不分类别)`;`worker`/`waker` **仍按类别**分。
|
||
细则 ⇒ `architecture.md §2.3.0g`。
|
||
- 🔴 **「接续会话」是「形态」,⛔ 不是第 5 类**(用户 2026-10-01 原话:「**接续会话 不是单独的一类会话,是这几类会话到达阈值时 创建的接续会话**」)⇒ **角色继承被接续的那条**(主会话的接续仍是主会话候选)。
|
||
- 🔴 **执行与投递「一直运行」(常驻)** —— 09-29 定案,**2026-10-01 用户再确认**(理由:「**可能不是所有队列都是钩子产生的**」)。
|
||
⛔ **不要用自动任务当闹钟**(用户 2026-10-01:「**定时任务的方案已经废弃了**」);宿主钩子只作**补充**,⛔ 不是主路径。
|
||
- **一棒一线**;**派活 ≠ 结束**,要建监管棒并跟进。
|
||
- **自动化四律**:开机第 0 步跑状态 | prompt ⛔ 不抄任务细节 | **下一棒 id 只来自工具返回值** | 排期=收口+3~4 分钟、每条线只挂一个。
|
||
- **`cwds` 逐字同形**(去重键=`path.trim().toLowerCase()`);**入口头部声明的「工作区」决定归属**。
|
||
- 🔴 **⛔ 别用 OS 文件锁做并发**:本机实测「隔离目录全绿、上生产即 `rc=124` 卡死」⇒ 用**临时文件 + `os.replace` 原子替换 + 回读核对(最多 3 次)**。
|
||
- 🔴 **⛔ 不在「会干活的会话」里起常驻后台任务**:它会**压制该会话的 idle 钩子**(实测被僵尸任务压 6h20m);
|
||
要常驻 ⇒ 用**专用容器会话** + `stdout` **全重定向**(⛔ 否则输出反复唤醒宿主 ⇒ 界面静默哑掉)。
|
||
- 🔴🔴 **2026-10-02 23:0x 实测订正(⛔ 推翻当天早些时候的错误结论,本条为准)**:
|
||
一句判据:**不是「本机不存在长跑进程」,是「载体不同」—— 从工具调用进程树里起的活不过当轮,宿主后台任务/用户自己从桌面起的活能长跑。**
|
||
① **实测坐实**:同一时刻用 `start /b` 与 `Popen+DETACHED` 各起一条每秒打点的探针 ⇒ 两条**活到约 11 分钟后停在同一 tick**(67/64 行)⇒ **差别不在起法关键字,在父链**。
|
||
② **反证(⛔ 就是它让我误判的)**:MCN 工作台 `<mcn-short-video>/.../mcn-work-shop/start.bat` = `start "" node.exe server.js 8900`,用户**从桌面双击**、属用户登录会话 ⇒ **一直活着**;另有 4 个 `node.exe`(会话名 `Console`)长期存活。
|
||
③ ✅ **正解(本轮已跑通,两条都现算)**:走**WorkBuddy 自己的后台任务**(工具的 `run_in_background`,⛔ 别用 `subprocess` 自己造)⇒ 载体由宿主管理、不由当轮工具调用决定:
|
||
`python tmp/start_board.py 8788` → `board.py --serve --takeover` ⇒ **`127.0.0.1:8788 LISTENING`(pid 43176)/`HTTP=200`/119 704 B**;
|
||
`python tmp/start_mcn_board.py 8900` → **`LISTENING`(pid 14056)/`HTTP=200`/1 797 B**。
|
||
⚠️ 一次**带 `--takeover`**(静默并存会看到旧图);⚠️ 用 `pythonw.exe` 起子进程(GUI 子系统 ⇒ Windows 永不分配控制台 ⇒ ⛔ 不闪窗);⚠️ `pythonw` 无 stdout ⇒ **显式重定向到 `tmp/board-serve.log`**,否则静默无痕、连"起没起"都查不到。
|
||
④ ⛔ **因此撤回**当天那条「改用排期当时钟」的建议 —— 它建立在错误前提上。**排期仍保留,但身份是复活兜底**,⛔ 不是时钟。唤醒时钟**回到常驻进程本身**。
|
||
⑤ ✅ **S8 那套自愈仍有效**(`--supervise` 心跳 + `--tick` 顺手续命 + 存活判据 `pid 活 ∧ 心跳新鲜(<90 s)`)—— ⛔ 但它**不是**因为"没法长跑"才需要,而是**常驻总会被各种事打断**(重启、换会话、用户手动收),所以要能自动补回来。
|
||
🔴 **判"常驻在不在"只看两样**:`pid 活 ∧ 心跳新鲜(<90 s)`(`logs/supervise-heartbeat.json`);
|
||
⛔ **不许拿 `_tick.stamp`/投递日志当证据** —— 那些轮次是**钩子**写的(`pitfalls.md P0-22`)。
|
||
🔴 **启完必查三样**(⛔ 别凭"打印了启动消息"当成了):`netstat` 里有 **`LISTENING`**(⛔ `TIME_WAIT`/`FIN_WAIT_2` 是历史连接残留,不算)+ `curl` 有 `200` + `tasklist` 里进程在。
|
||
- 🔴 **自动唤醒任务的排期名必须带 `[执行]`**,否则被认成"主会话"把通知投给自己。
|
||
⚠️ 🔴 **2026-10-02 标注(⛔ 不是口径变更)**:该规则**仅代偿形态适用**(定案时钟=常驻投递,不开会话);🔴 **定案口径**=「协作与投递**一直运行(常驻)**」+「**定时任务的方案已废弃**」(2026-10-01 用户原话)。
|
||
|
||
- 🔴 **看板 tab 可并列查看别的「工作区」的目标**(用户 2026-10-03 报障,逐字:「**为什么 会话协作看板 tab 选项不能切换看另外两个工作区的目标**」)。
|
||
· **真因(看代码,不猜)**:`board.py::goal_files()` 只扫 `INBOX/goal.json` + `INBOX/goals/*.json`,而 `INBOX` 由**部署配置的 `workspace`** 决定 ⇒ **一个 `--serve` 实例天生只看见一个工作区**。
|
||
· **修法**:部署配置加 `peer_workspaces`(要并列查看的其它工作区,**正斜杠**)⇒ 把对方的 `goal.json` 读进来当**额外一格 tab**(打 `peer` 标记,前端显示在标题旁)。
|
||
· 🔴 **严格只读**:⛔ 不写对方文件、⛔ 不起对方进程、⛔ 不改对方状态;**活跃目标仍然只有本工作区那份** ⇒ `collabd.py` 行为零改动。
|
||
· 🔴🔴 **那一格的数据源必须跟着格走**(⛔ 否则=**假数据**,见 `pitfalls.md` **P0-40**):peer 格只喂 `_peer_tasks()`/`_peer_srows()`/`_peer_state()`(**只读对方目录**),读不到 ⇒ **空 + 界面如实说明**,⛔ **绝不拿本工作区的台账/会话/状态补位**(2026-10-03 实测:三格 `labor`/`sessions` md5 完全相同 ⇒ 把本区执行情况挂到了别人名下)。⚠️ 会话还要**单独补捞**(`_session_rows()` 只取全库最近 50 条,对方会话不在里面)+ 把 `sc["workspace"]` 换成对方根(`_sessions()` 有 `cwd` 硬过滤)。
|
||
· ⚠️ **它只是「只读概览」,⛔ 不是对方的完整看板**:peer 格的 `sessions` 目前为空(卡在 `in_project()` 判据,⛔ 不改那条 —— 它与 `collabd.py` 有**逐条同款**约束,历史已踩三次)。
|
||
· 🔴🔴 **2026-10-03 16:3x 用户定案:⛔ 不再为每个工作区各起一个看板** —— **看板只保留一份**(就是主工作区这一个),其它工作区靠 `peer_workspaces` 并列查看 ⇒ 各区的目标、验收、台账、常驻心跳**都在这份上看**(实测:三区常驻同时在跑,各自的 `heartbeat_age_min` 都能在这份看板上读到)。
|
||
· ⛔ 配置里**不含本工作区**(它已是 active 那格,重复列 ⇒ 出现两格);⛔ 去重键必须带**工作区前缀**(同名目标会互相顶掉)。
|
||
· 🔴🔴 **某格工作区「已经不在会话列表里」⇒ ⛔ 不再占 tab**(用户 2026-10-04 20:59 报障,逐字:
|
||
「**tab 要把已经不再 会话列表的工作区 目标移除,不然都放不下了**」)。
|
||
- 🔴 **判据(唯一)**:宿主库 `sessions` 表里该 `cwd` 的**未删会话数**(`deleted_at is null or 0`)
|
||
= **0** ⇒ 这个工作区在会话列表里**已经不存在了** ⇒ 不列 tab。
|
||
实现在 `board.py::goal_files()`(peer 循环里 `_peer_session_rows()` 为空就 `continue`)。
|
||
- 🔴🔴 **⛔ 别拿「心跳新不新鲜」当判据**:心跳只说明那个区的**常驻进程**在不在,
|
||
与「这个工作区还有没有人在用」**是两件事**。实测反例:测试2/3 心跳早停 ⇒ 但**未删会话也为 0**
|
||
(用户把会话全删了)⇒ 确实退场;而 vibe-product 心跳 1.5 小时前、**未删会话 5 条** ⇒ **还在用** ⇒ 必须留。
|
||
- ⚠️ **读不到库 ⇒ 保留**(fail-open):宁可多一格,⛔ 不因自己的读数失败就把别人的格子吞掉。
|
||
- ✅ **零删除、可逆**:只影响"要不要画这一格",⛔ 不动对方任何文件、⛔ 不替用户改 `peer_workspaces`。
|
||
- ⚠️ 改 `board.py` ⇒ **必须重启看板**(本节上一条);只改 `collabd.config.json` 里的 `peer_workspaces` 也**必须重启**(`C = _cfg()` 模块级只执行一次)。
|
||
· 🔴🔴 **「已退役角色」⛔ 不许当主会话候选**(用户 2026-10-04 21:5x 拍板「候选一」)。
|
||
- 🔴 **判据(唯一)**:标题的**一级方括号里是退役角色词**(`[跟进]`/`[唤醒]`)⇒ ⛔ 不进主会话候选。
|
||
实现在 `collabd.py::is_retired_role_title()`(+表 `_RETIRED_PFX`;`board.py` 从该模块**取**,⛔ 不另写一份)。
|
||
- 🔴 **病根**:`[跟进]`/`[唤醒]` 两键在 2026-10-02 从映射表摘掉后 ⇒ 角色解析成 **`""`**
|
||
⇒ 而候选排除元组是 `_role not in ("worker",)` ⇒ **`""` 恰好放行** ⇒ 这两类**已退役的干活的棒**
|
||
被收进主会话候选 ⇒ `_role_label()` 判据①命中 ⇒ 看板「主会话」位上坐着它。
|
||
**实测现网**:`main_by_topic` = `{"机制排查与修复": "6ab1463e", "会话协作自检": "57f58ecf"}`
|
||
—— 两个任务类别**全都指向 `[跟进]` 退役会话**(`6ab1463e`「[跟进]-机制排查与修复-队列跟进」等 11 条)。
|
||
- ⛔ **修法不是「排除空串」**:空串里还有**真主会话**(现役 `a80f300d`「复盘失败并避免重犯」无前缀)
|
||
⇒ 一刀切会把真主会话一起排掉。也⛔ **不是「恢复 `[跟进]`/`[唤醒]` 映射」**(那等于让退役类别复活,与 10-02 口径相反)。
|
||
- ⚠️ 闸**只认一级方括号里的整词**:⛔ 不许误杀「类别名里恰好含『唤醒』」的在役会话
|
||
(`[协作]-[唤醒机制]-…` 一级是「协作」⇒ 是 worker,本就该排,但**理由不该是"退役角色"**)。
|
||
- 🔴🔴 **⛔ 危害不止"标签难看"**:主会话候选=**投递/派活的收件人** ⇒ 会**投错窗口**。
|
||
- ⚠️ 判据**两侧同源**(`board.py` import 而非抄写)+ 用例是**行为级**(真造宿主库+真跑扫描)——
|
||
⛔ 只测「那个小函数返回什么」= **测了零件没测装配**(2026-10-04 变异验证当场抓到:
|
||
拆掉闸后单函数断言**照样全绿**)。⇒ 见 `pitfalls.md` P0-54。
|
||
· ✅ 现算:`/board.json` 的 `goals`=**3 格**,`peer`=`会话协作测试1`/`会话协作测试2`(⛔ 区名是历史名,不改),`active` 恰好 **1** 个。
|
||
· ⚠️ 改 `board.py`/配置 ⇒ **必须重启**(**P0-38**),且用 `--takeover`(⛔ 否则新旧实例静默并存、同一个 URL 随机应答)。
|
||
- 🔴🔴 **自测的「现网真读数」类判据 ⛔ 不许复用 `imp()` 注入的测试环境**(2026-10-03 实测栽的,同族第 2 次):`imp()` 会把 `COLLABD_CONFIG`/`DSH_COLLAB_WS` 指到 `tmp/selftest/` ⇒ 用例里**无论怎么加载 `board.py` 都只读到测试那 1 格**(同一时刻命令行直接加载是 3 格)⇒ **恒绿与恒红都是假象**。正解三条:**加载前换 env、用完 `finally` 还原** + **在真磁盘上造目标**(`tempfile.mkdtemp()`)+ **变异对照**(打掉接线必须报红;还原后 md5 校回原值)。⚠️ 「配置缺省 ⇒ 回落旧行为」那条**只守配置侧**(代码被破坏时它照样绿)⇒ ⛔ 别把它当证伪项写进标题。详 ⇒ `references/pitfalls.md` **P0-39**。
|
||
- 🔴🔴 **每个工作区的常驻由「它自己的主会话」起**(用户 2026-10-03 定案,逐字:「**每个工作区 会话协作机制的主会话自己创建后台任务 启动常驻协作程序**」,同句「**后台看板不用运行这么多 共享一份就可以**」)。
|
||
· **主会话开工第 0 步 = 确认本工作区的常驻在跑**:判据=`.workbuddy/collab/logs/supervise-heartbeat.json` 里 **`pid` 活 ∧ 心跳距今 < 90 秒**;在跑 ⇒ **跳过**(⛔ 别起第二个,同端口两个实例会互相顶掉);不在 ⇒ 用**后台任务**启动一次,**输出必须重定向到文件**(⛔ 否则它的日志会把会话日志顶满)。
|
||
· ⛔ **不是由别的会话代起** —— 谁的工作区谁负责:跨区代起会让「哪个进程属于哪个区」彻底糊涂,而且**代起方一结束,被代起的那个区立刻失联**(实测就是这么断的)。
|
||
· 🔴🔴 **本工作区的会话 ⛔ 绝不许去起「别的区」的常驻**(用户 2026-10-03 16:4x 当场纠正,逐字:「**不是 为什么这个会话 要创建别的会话的 常驻任务,让那边的会话自己创建啊**」)—— ⚠️ **我自己就违反了这一条**:写完禁令后转头在本会话里替两个测试工作区起了后台任务(看着是"帮忙让它跑起来",实际是**代起**),当场被纠正后已停掉。
|
||
⇒ **正确做法**:那边的常驻**只能由那边的会话起**;我这边唯一能做的是**把它那条排期的触发时间提前**(改 `scheduledAt`,或等它周期触发),⛔ **不是替它执行**。
|
||
⇒ **怎么判断有没有越界**:问一句「**这个进程归哪个工作区?起它的会话又归哪个工作区?**」—— 两个答案不一致 ⇒ 就是代起。
|
||
· 🔴 **看板只保留一份**:各工作区配置里**没有** `board_port`;只有主工作区起一个 `board.py --serve`,靠 `peer_workspaces` 并列看其它区。⛔ 每区一个看板=白白多 N 个进程+N 个端口。
|
||
· 🔴🔴🔴 **全局只允许一个看板 / 一个常驻(用户 2026-10-04 21:0x 定案,逐字:「看板 包括常驻 按现在的方式启动,全局只允许一个,把规则记录下来」)**:
|
||
**现状即标准** —— 就是本轮已经跑通并取证的那一套,⛔ 不要再发明第二套。
|
||
- **看板载体**:计划任务 **`dsh-board-keepalive`(🔴 全局唯一一条,⛔ 不每区一个 —— 2026-10-05 收敛,旧名 `dsh-board-20099` 已禁用)**。
|
||
· 🔴🔴 **动作 = `pythonw.exe` 起"启动器" `scripts/board-launch.py`**(⛔ **不许直起 `board.py`**)。
|
||
**为什么(2026-10-05 实测)**:直起 ⇒ **崩溃重启循环、20099 从没绑上**。现场输出:
|
||
`⚠️ collabd:未找到部署配置(COLLABD_CONFIG 未设)⇒ 已拒跑` —— `board.py` 顶层用
|
||
**`COLLABD_CONFIG`** 定位配置(它要 `import collabd.py`),而**计划任务的动作里没有 env 字段**
|
||
⇒ 该变量只能由**启动器在进程内设**。⚠️ **名字别混**:`roots.env` 给的是 `COLLABD_PROD_CONFIG`,
|
||
而 `board.py` 读的是 `COLLABD_CONFIG` ⇒ 名字对不上,`roots.env` **兜不住它**。
|
||
· ⚠️ 启动器的 `WorkingDirectory` =**技能 `scripts/` 目录**(启动器自己定位 `board.py`);
|
||
看板**配置恒指主工作区**那份(看板是全局共用一份)——这与 `--supervise` 必须指**工作区根**是两回事。
|
||
· 🔴 **`ExecutionTimeLimit` 必须 `0`(无时限)** —— 看板是**长驻服务**;
|
||
⛔ 设 2 分钟 ⇒ 到点被调度器掐死 ⇒ 又变成"每 5 分钟重建一次"的抖动(与 `--supervise` 同理)。
|
||
· 🔴🔴 **⛔ 动作绝不许写成 `.cmd`/`.bat`/`powershell.exe`** —— 它们是**控制台程序**,
|
||
计划任务每次触发都分配 `conhost.exe` ⇒ **弹黑窗**;更糟:`.cmd` 里若**前台**跑 pythonw
|
||
(没 `start`)⇒ `cmd.exe` **不退出** ⇒ 黑窗**常驻**(实测 `cmd.exe` + `conhost.exe` 挂在看板树上)。
|
||
· 🔴 **为什么用 launcher `.py` 而不是直接起 `board.py`**:`COLLABD_CONFIG` 等环境变量
|
||
原本靠 `.cmd` 的 `set` 传(`board.py` 在**模块顶层**读它们,`roots.env` 只兜底 `COLLABD_PROD_CONFIG`,
|
||
**名字对不上**)⇒ 现在改在**进程内** `os.environ[...] = ...`,等价且零 shell。
|
||
· 🔴 **launcher 必须用 `runpy.run_path(BOARD, run_name="__main__")`,⛔ 不许用 `exec(compile(...))`** ——
|
||
实测:`exec` 版从 `C:\Windows\System32`(=任务默认 cwd)起时 **rc=1 且零输出**,
|
||
换 `runpy` 后从任意 cwd 都正常(`runpy` 会正确设好 `__file__`/`__name__`/`sys.path[0]`,
|
||
`board.py` 顶部的 `_sm_load_roots()` 靠 `__file__` 往上找 `roots.env`)。
|
||
· 🔴 **任务设置**:`ExecutionTimeLimit=0`(无时限)|`MultipleInstances=IgnoreNew`|
|
||
`RestartCount=999`/`RestartInterval=1min`|`RunLevel=Highest`(⚠️ `Limited` 实测 `LastTaskResult=1`)。
|
||
· ✅ **验收(三条全绿才算成,⛔ "打印了启动消息"不算)**:`netstat` 见 `127.0.0.1:<port> LISTENING` ∧
|
||
`curl /` = 200 ∧ **父进程是 `svchost.exe -s Schedule`**(⇒ 已脱离会话,⛔ 不是会话树里的 bash)。
|
||
🔴 **且要看进程树下**:**零 `cmd.exe`/零 `conhost.exe`** ⇒ 黑窗彻底消除,这才算对。
|
||
- **常驻载体**:同规矩 —— **`pythonw.exe` 起"启动器" `scripts/supervise-launch.py`**(⛔ **不许直起 `collabd.py --supervise`**),
|
||
⚠️ 启动器的 `WorkingDirectory` =**工作区根**(⛔ 与看板相反,看板指 `scripts/`);参数由启动器给(⛔ 动作里不再自带 `--supervise`);
|
||
存活判据仍是 `pid 活 ∧ 心跳 <90 s ∧ argv0 指本区`(⛔ 不换判据)。**为什么必须经启动器**:见本节开头「为什么要夹一个启动器」(P0-73)。
|
||
- 🔴 **「全局只允许一个」怎么守**:看板=`board.py` 内置单实例护栏(端口上已有实例 ⇒ **拒绝启动**并提示;
|
||
换代码要 `--takeover`)。**端口即互斥锁**,⛔ 别再加一层自造锁。
|
||
常驻=`ensure_supervise()` 心跳判据(已有活的 ⇒ 幂等 `exit 0`)。
|
||
- ⛔ **看着"要起两个"时的正解不是多起,是查为什么第一个没起**(`LastTaskResult`/launcher 日志/端口占用)。
|
||
· 🔴🔴 **实测警告(决定这个口径能不能落地)**:**后台任务的寿命 ≈ 发起它的那个会话的寿命** —— 2026-10-03 实测两条:由**一次性会话**启动的那份只活 **5 分钟**(14:42:00 起 → 14:47:10 最后心跳,`round=32`);由**长期存在的会话**启动的那份已连续运行 **4.5 小时**(12:03 起仍在跑,`round=1624`)。⇒ 所以主会话必须是**周期性**的(现配 `FREQ=HOURLY;INTERVAL=1`)才能反复补,⛔ **别指望"起一次就一直活着"**。
|
||
· ⚠️ 由此推出的**残留**:仅靠周期性主会话 ⇒ 常驻每小时只在主会话那几分钟在线 ⇒ 要**真正长期在线**还得有一层**脱离会话**的载体(计划任务/桌面启动/系统服务)⇒ **尚未定,见本节末待定项**。
|
||
- 🔴🔴 **各工作区的「程序」也独立 —— 看板是唯一共用的一份**(用户 2026-10-03 17:0x 定案,逐字:「**跨工作区使用会话协作技能,除了看板共用,其余都是独立的,包括程序和相关文件**」)。
|
||
· **形态**:技能目录 `scripts/collabd.py` = **源(唯一真身)**;每个工作区 `.workbuddy/collab/collabd.py` = **它自己的一份副本**(另含 `goalctl.py`)。⛔ 各区**不再跑技能目录那份**。
|
||
· **一键分发**:`python scripts/deploy_code.py --ws <工作区>` —— 覆盖前**自动备份**、打印源与副本的 md5;支持 `--dry-run` 与 `--only collabd.py`。
|
||
· 🔴 **代价(明说)**:一处改动要分发 N 处 ⇒ **改完代码必须重跑分发**;⛔ 漏了**不报错**,只是"某个区行为不对",最难查。
|
||
· ✅ **可观测性(关键)**:`collabd.py` 每次写心跳都带 **`argv0`**(启动入口绝对路径)⇒ 「这个区跑的到底是哪份文件」一眼可查,**也能证伪**"改了副本却没重启"。
|
||
⚠️ **改副本不重启=没生效**(P0-17 同族)⇒ 主会话开工第 0 步要核 `argv0`:指向的不是本区路径就**自己换掉**。
|
||
· ⛔ **`board.py` 不进副本清单** —— 看板是**共用一份**的(用户同句定案);⛔ 每区一个看板=白白多 N 个进程与端口。
|
||
· 🔴🔴 **启动脚本/载体也必须在清单内 —— 只分发程序、不修启动入口 = 这条规则形同虚设**(2026-10-04 实测事故,P0-57):
|
||
- **病根**:某区的 `tmp/start_supervise.py` 第 11 行写死 `WS + "/.workbuddy/skills/session-mechanism/scripts/collabd.py"`
|
||
⇒ **它指的是「技能副本」,不是「本区发布物」** ⇒ `.workbuddy/collab/collabd.py` 成了**孤儿副本**
|
||
(**全工作区只有 0 处引用它**,落后一版也没人发现)。判据②(`argv0` 指向本区)实测**不过**。
|
||
- 🔴 **这是"旧口径的启动脚本 + 新口径的目录结构"的典型症状**:目录改对了、**引用没跟着改**。
|
||
⇒ ⛔ **别只盯 `.workbuddy/collab/` 里文件在不在** —— 文件在 ≠ 有人跑它。
|
||
- ✅ **正解**:启动脚本里**必须**写**本区发布物**路径,并**优先+回落**两段式(缺了才用技能副本,且要**打印告警**,⛔ 静默回落=用户以为跑的是副本)。
|
||
```python
|
||
COLLABD_OWN = WS + "/.workbuddy/collab/collabd.py" # ✅ 首选
|
||
COLLABD_SKILL = WS + "/.workbuddy/skills/session-mechanism/scripts/collabd.py" # ⚠️ 仅回落
|
||
COLLABD = COLLABD_OWN if os.path.isfile(COLLABD_OWN) else COLLABD_SKILL
|
||
```
|
||
- 🔴 **开工第 0 步的自查(三条一起做,⛔ 少一条就漏)**:
|
||
① `deploy_code.py --ws <工作区> --dry-run` ⇒ 副本与源 **md5 一致**;
|
||
② 心跳 **`argv0` 指向本区发布物**(⛔ 指向 `skills/` 或全局技能都算**没接线**);
|
||
③ **grep 启动脚本**(`tmp/*.py`、`*.ps1`、计划任务的 `Args`)⇒ 引用的路径**逐字是本区发布物**。
|
||
⛔ 只做①② ⇒ 会漏掉「副本是孤儿、跑的是另一份」这种形态 —— 今天就是这么漏的。
|
||
- 🔴🔴 **分发是"手动"的 —— 这是 2026-10-05 用户拍板(选 A,⛔ 不做"保存即分发"钩子)**:
|
||
**技能目录是源、各区 `.workbuddy/collab/` 是发布物**;改完源**必须显式跑** `deploy_code.py --ws <区>`
|
||
(覆盖前自动备份、打印 md5 对照),⛔ **没有 watcher、不会自己流到各区**。
|
||
· 为什么不自动:`collab/` 是**生产面**,静默覆盖一旦**源改错**会**同时打歪所有区**,且不备份时机难控;
|
||
显式一条命令=**可回滚、可审计、不易误伤**。
|
||
· ⇒ **收口口径**:改完本包脚本 ⇒ ①跑 `deploy_code.py`(**两区都跑**)②**重启常驻**(见下)。
|
||
- 🔴🔴 **"部署完" ≠ "生效了" —— 必须重启常驻**(2026-10-05 实测):
|
||
常驻进程**启动时就把代码读进内存**,改文件**不影响正在跑的进程**。
|
||
· ✅ **判据**:心跳里的 **`started_h`(进程启动时间)必须晚于部署时间**;
|
||
⛔ 只看副本 md5 一致 ⇒ 会误判成"已生效"(实测 vibe 副本 md5 早就是新的,但进程还是 12:15 起的旧代码)。
|
||
· ✅ **重启做法**:`Stop-Process` 杀掉常驻 ⇒ 触发该区计划任务(或等 5 分钟自动判活)⇒ 新进程读新副本。
|
||
· ⚠️ 重启前确认 **无 `guard.stop`**(有 ⇒ 拉起后立即自停,看着像"没起来")。
|
||
· 🔴🔴 **跑自测的位置(2026-10-05 实测踩到)**:**⛔ 别在本区 `.workbuddy/collab/` 目录里跑 `selftest.py`**。
|
||
- **为什么**:`collab/` 是**生产部署面**,只有 `collabd.py` + `goalctl.py`(⛔ **不含** `board.py`/
|
||
`judge_audit.py` 等**包内依赖** —— 看板是共用一份的)⇒ 在那里跑会大面积 `FileNotFoundError`,
|
||
实测 **PASS 42 / FAIL 45**,看着像"回归",其实是**跑错地方**(⛔ 假警报,会把人带偏去查不存在的 bug)。
|
||
- ✅ **正确跑法**:在**技能目录**跑源那份、把 `cwd` 设成目标工作区即可(`selftest.py` 用 `cwd` 推工作区):
|
||
`cd <工作区> && python <技能目录>/scripts/selftest.py`
|
||
- 📌 **验收基线**:本区正式截面应 **PASS ≥92 / FAIL 0**(另 1 条报告型)。
|
||
- ⚠️ 判据:**"FAIL 一片" 先问一句"我是不是在 `collab/` 里跑的"**,再去怀疑代码。
|
||
|
||
---
|
||
|
||
## §3 目录与依赖
|
||
|
||
```
|
||
SKILL.md install.py roots.env(装后生成) install.log(装后生成)
|
||
references/ architecture collab collab-detail rules deploy pitfalls taskgraph forensics manifest
|
||
scripts/hooks/ 6 份宿主钩子(10 条接线)
|
||
scripts/lock/ handoff-guard.sh preflight-lock.sh handoff-status.py op-lock.sh
|
||
scripts/ collabd.py board.py board_ext.py goalctl.py guard.py wake-session.py
|
||
stop-collab.py deliver-gateway-token.py selftest.py
|
||
collabd.config.example.json
|
||
scripts/forensics/ proc-parent.py(查进程父链 · 纯 ctypes · 只读)
|
||
assets/ board.html design-tokens.css
|
||
```
|
||
|
||
⚠️ 逐文件清单 + 来源(provenance)⇒ `references/manifest.md`(**唯一权威**,⛔ 别按本树猜)。
|
||
|
||
**⛔ 已无必需外部依赖(2026-10-04)**:原先声明「依赖 `agent-operating-rules`」——现已把**排版核心块收进本包并定为权威** ⇒ `references/03-回复排版-核心块.md`
|
||
(2026-10-06 校正:原措辞是「内联副本」,与 03 号自称「权威源」打架 ⇒ 权威声明分裂)、把 `_env.py` 的技能库识别特征改为**候选数组(首个=本包自己)** ⇒ **本包自包含**。📌 那个技能若同时装着,属**可选增强**(多了跨项目排版/去 AI 味的完整版),⛔ 不装也能跑。
|
||
|
||
---
|
||
|
||
## §4 验证(装完必跑)
|
||
|
||
```
|
||
python install.py --dry-run # 看 settings.json 将怎么变
|
||
python install.py --apply # 真装
|
||
python install.py --verify # 10 条接线空载荷 rc=0 + collabd --where + selftest PASS 39/0
|
||
python install.py --manifest --note "<本轮:…>" # 重算 references/manifest.md 的逐文件表(**改完包必跑**)
|
||
python install.py --uninstall # 还原 settings.json(应与装前备份逐字节相同)
|
||
```
|