Files
dsh_ai1net_server/交付物/协作-同工作区多任务类别-20260930.md
T
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

235 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 协作机制 · 同一工作区 · 多任务类别(2026-09-30)
> **一句话**:把归属锚点从「**跨工作区**(cwd)」改成「**同一个工作区 + 名称前缀里的任务类别**」——
> 同一工作区里可并存多个任务类别,**每类各有自己的主会话**,投递**按件的类别**选主会话。
> **验证**:协作机制自测 **PASS 28 / FAIL 0**;真实工作区冒烟:分工板已按类别归位、投递目标解析正确。
> **图** ⇒ `协作-同工作区多任务类别-20260930-图.html`(① 泳道图:类别前缀在哪些环节起作用 ② 缺口→修复链图)
---
## 一、用户要求(原话)
> 「之前的协作会话是**跨工作区**的,要能支持**同一个工作区** 多会话协作
> (主会话根据任务**自动梳理任务类别**:**通过协作会话名称前缀**的方式区分具体任务会话,
> 所有主会话,协作会话,自动唤醒任务,**都在一个工作区**)」
拆成三条可判据的要求:
1. **都在一个工作区** —— 归属判据 ⛔ 不许挂在 `cwd` 上;
2. **任务类别靠名称前缀区分** —— 会话标题里的类别名就是类别键;
3. **主会话/协作会话/自动唤醒任务一视同仁** —— 三类都走同一套前缀规范。
---
## 二、旧版哪里不够(代码级取证,⛔ 不是推测)
| # | 位置 | 旧实现 | 后果 |
|---|---|---|---|
| A | `collabd._in_project()` / `board.in_project()` | 归属判据 ②「标题含 `[<goal.short>]`」——**只有一个** `short` | 同一工作区跑**两个类别**时,除那一个之外的类别**全被判"不属于本项目"** ⇒ 看板不列、`--ready-next` 不算 ⇒ **静默漏管**(不报错,是"什么也不说") |
| B | `collabd.resolve_main()` / `board._resolve_main()` | 返回**一条**主会话("本工作区里那一条就是它") | 多类别 ⇒ **只认一条** ⇒ 投递会把 A 类别的件投给 B 类别的主会话=**投错窗口** |
| C | `collabd._deliver_str()` | 目标是**唯一主会话**,无"按类别选路" | 同上 |
| D | `board._labor()` | 分工板的"**线**"取自 `goal.lines`,判据是 `cwd_tail == 线` | 同工作区多类别 ⇒ `cwd` 全相同 ⇒ **多类全塌成一行** |
| E | 实际排期名(证据) | `[协作]-唤醒轮A · ai1net-dsh-server` —— **缺第 2 级(类别)** | 解析不出类别;活动一多还会被当成主会话候选 |
**实测读到的现状(本工作区,宿主 `sessions` 表,读时 11 条)**
```
a202550c | working | 主控 · 唤醒机制线 · 棒:worker 退出后能否被外部叫起(无人值守决定性一问)
6ecf6d98 | completed | [主控]-[机制线]-接续:查「唤醒」为什么断
fe146dd9 | completed | 接续 · 机制线(钩子锚点真实投递取证) ← ⚠️ 登记里的 main,**已死**
```
⇒ 本工作区**本来就有 ≥2 条"主控"会话**,且它们的**类别名已经写在标题里**(`唤醒机制线` / `机制线`)——
说明"用名称前缀分任务类别"是**已经在用的习惯**,只是**程序侧没认**。这一棒就是把它接上。
---
## 三、落地清单(改了哪几处)
### 3.1 技能侧代码(`~/.workbuddy/skills/multi-session-collab/scripts/`)
| 文件 | 改动 |
|---|---|
| `collabd.py` | ① 新增 `_goal_topics()`(类别清单,缺省回落 `short`)② 新增 `_as_list()`(单值/集合归一)③ `_in_project()` 第 4 参泛化为**类别集合**(⛔ 传 str 时与旧版逐字一致)④ 新增 `_topic_in_title()`(**最长优先**,防短名抢走)⑤ 新增 `_scan_mains()`(**一遍扫描**、`resolve_main` 与 `resolve_mains` 共用 ⇒ ⛔ 不各扫各的=防两边漂移)⑥ `resolve_main()` 改为薄封装(行为不变)⑦ 新增 `resolve_mains()` / `main_for_topic()` / `_title_of()` ⑧ `_deliver_str(..., topic=)` **按类别选主会话**,类别已登记但无主会话 ⇒ **降级报告**(⛔ 不投给别类别)⑨ `supervise()` 上报投递**带上件所属类别**(`topic=台账条目的 line`)⑩ `ready_next()` 排除**全部主会话**(⛔ 否则别类别主会话被算成"在跑"⇒ 永远拦住)⑪ `goal_line()` 改显示**类别清单** |
| `board.py` | ① 同款 `_goal_topics()` / `_as_list()` / `_topic_in_title()` ② 新增 `_scan_ws_mains()`(与 `collabd._scan_mains()` **同款**)③ `_resolve_main()` 改用它并返回 `all_sids` ④ `project_scope()` 带出 `topics` / `main_sids` ⑤ `in_project()` 认**任一类别**(⛔ 无 `topics` 回落 `short`)⑥ `_sessions()` 的 `role` 判定改成"**是任一主会话**",并给每条会话带出 `topic` ⑦ `_labor()`:**分工板的行 = 任务类别(`goal.topics` 优先)**,承接判据 `topic == 线 ∨ cwd_tail == 线 ∨ 标题命中该线的台账件`(双判据并列=新旧数据都能显示) |
| `guard.py` | 帮助文本补 `topics` |
| `selftest.py` | 主会话用例**加强**(22 项,原 12 项):新增 9 条"两处同款/按类别选路/严格不回落"断言;归属判据用例**从 7 例扩到 15 例**(新增 8 例多类别),并加一条**结构断言**(两处 `by_topic` 值必须是 dict) |
### 3.2 本工作区配置
| 文件 | 改动 |
|---|---|
| `tmp/supervise-inbox/goal.json` | 新增 **`topics`**(`唤醒机制` / `文档库治理` / `规则载体` / `IM`,来源=工作区根现有的接续入口/接续包,⛔ 不凭空编)+ 说明段;`lines` 与 `topics` **统一为同一个概念**(`topics` 权威,`lines` 兼容旧名)——旧值是**工作区名**,正是"跨工作区"时代的残留 |
| 排期 `caca9a89…` / `16bec5ce…` | 名字补类别前缀:`[协作]-唤醒轮A/B · …` → **`[协作]-唤醒机制-唤醒轮A/B · ai1net-dsh-server`**(两条均 PAUSED,⛔ 未改 `rrule`、⛔ 未启停) |
### 3.3 技能文档
- `references/architecture.md`:§2.3 第 2 级从「主题」正名 **「任务类别」**;新增 **§2.3.0「同一工作区 · 多任务类别」**(为什么旧版不够/三条规则表/命名/已知边界)。
- `SKILL.md`:命名定则补"任务类别"四处同义 + 多类别要求 + **自动唤醒任务的排期名也必须带类别前缀**;`last_change` 更新。
---
## 四、验证
### 4.1 自测(技能内置回归)
```
协作机制 · 回归自测 合计:PASS 28 / FAIL 0
```
(含"两处判据同款"15 例比对、"投递方唯一"、"零投递"、"不消费队列"等既有硬项。)
### 4.2 真实工作区冒烟(本棒实测读数,非示例)
```
topics = ['唤醒机制','文档库治理','规则载体','IM']
resolve_mains(带活网关)
default = a202550c (source=prefix:主控, switched_from=fe146dd9) ← 登记的旧主会话已死 ⇒ 正确跟随
mains = {唤醒机制: a202550c(prefix:主控), 文档库治理: (空), 规则载体: (空), IM: (空)}
main_for("") → a202550c # 空 ⇒ default(心跳这类全局内容走它)
main_for("唤醒机制") → a202550c # 已登记类别 ⇒ 严格取该类别的
main_for("文档库治理") → (空) # 该类无主会话 ⇒ 报"找不到",⛔ 不投给别类别
main_for("ai1net-dsh-desktop")→ a202550c # 未登记(旧数据的工作区名)⇒ 回落 default,向后兼容
分工板(board.build())
唤醒机制 state=busy run=[a202550c] ← ✅ 按任务类别归位生效
文档库治理/规则载体/IM idle
ai1net-dsh-desktop / ai1net-dsh-anywhere ← 旧台账里的工作区名(历史,⛔ 不迁移)
--tick → tick: item=- awaiting=- phase=- deliver=- (rc=0, 静默)
--ready-next → ✅ 可以排下一棒 (rc=0)
```
### 4.3 冒烟**抓到**的一个真 bug(顺带修掉)
`by_topic` 的值在 `collabd._scan_mains()` 里是 **dict**、在 `board._scan_ws_mains()` 里曾是 **裸 sid**
⇒ `board._resolve_main()` 会把整个 dict 当 sid 塞进 `all_sids`(**静默错值**)。
已统一为 dict 并加了**自测结构断言**(⛔ 防回潮)。⇒ 这是"写完必须冒烟"的一个正面例子。
---
## 五、边界与已知坑(诚实标注,⛔ 不粉饰)
1. 🔴 **类别名是子串匹配**(主会话是口语式命名如 `主控 · 机制线(…)`,没有方括号)⇒
类别名**别取太通用的词**(如"机制"),否则会**误吞**别人的标题。源码已做"**最长优先**",但**根本解法是类别名取得足够具体**。
2. ⚠️ **台账里的历史条目** `line` 仍是**工作区名**(跨工作区时代产物)⇒ 见 **§八**:
已改为**折叠成一格「未归类」照实画出来**(初版曾把它们当"分工位"再被图上截断 ⇒ 整块不见,**已修**)。
⛔ **不迁移历史数据**(迁移=改写记录);新条目上报时用**类别**即可。
3. ⚠️ `_in_project()` 的**标题判据要求方括号**(`[类别]`),而**主会话标题是口语式(无方括号)** ⇒
非默认类别的主会话靠"**sid ∈ 解析出来的主会话集**"这条判据进来(不是靠标题)。这是**有意**的:归属与"谁是主会话"是两件事。
4. ⚠️ `main_sids` 取**解析出来的**(默认 + 各类别),⛔ **不是**"本工作区所有会话" ——
否则"本工作区随便开个无关会话"会被当成本项目(判据被无声放宽)。
5. ⚠️ 只有**默认类别**(`topics[0]`)在某类未解析出主会话时会**回落 `default`**(⛔ 只为兼容单类别部署);
其它类别**严格**——没解析出来就**降级报告**,这是设计意图(用户要求"每类各有自己的主会话")。
---
## 六、改动落点 / 回滚
- 备份:`tmp/bak-同工作区多任务-20260930-205722/`(`collabd.py` / `board.py` / `goal.json.bak`)
- 回滚:把备份里的三个文件覆盖回去即可(⛔ 技能目录不留副本,一律绝对路径调用)
- 涉及文件(绝对路径):
- `E:/ProgramData/.workbuddy/skills/multi-session-collab/scripts/{collabd.py,board.py,guard.py,selftest.py}`
- `E:/ProgramData/.workbuddy/skills/multi-session-collab/{SKILL.md,references/architecture.md}`
- `E:/ProgramData/AIProject/ai1net-dsh-server/tmp/supervise-inbox/goal.json`
---
## 七、待用户拍板(⛔ 只有一条,其余我自决)
**要不要现在就把 `goal.topics` 定成"长期类别表"?**
- 现状:我按**工作区已有的接续入口/接续包**填了 4 类(唤醒机制/文档库治理/规则载体/IM),
并把 `唤醒机制` 放第一位(=默认类别,当前唯一有活主会话的)。
- 说明:这决定"哪些会话算本项目、投递往哪条主会话去"。填错不会坏数据,但**会静默漏管/多管**。
- **倾向**:**先按现状用**(4 类,够用且都取自真实文件);等你哪天说"这几类不对/还要加一类",
改 `goal.json.topics` 一行即可(**下一轮钩子即时生效,⛔ 不必重启**)。
---
## 八、看板改动 + 整体验证抓到的两个**同族红线**(2026-09-30 收尾棒)
> 用户原话:「**改完记得整体验证,确定一切正常 就修改看板 看看流程对不对**」⇒ 本节就是这三步的账。
### 8.1 看板改了什么(`assets/board.html` + `board.py`)
| 位置 | 改成 |
|---|---|
| 项目卡 | 新增**「任务类别(同一个工作区 · 按名称前缀区分)」**一行:每类一枚 chip,**带该类主会话**;无主会话 ⇒ `⚠️ 无主会话` 警示色 |
| 架构图第三层 | 从"按线"改成**按任务类别**:一行 = 一类;格内五行 = 类别名/**该类主会话**/最近在做什么/当前谁承接/件汇总。`R3` 行高 110→**134**,`R4` 366→516 顺移 |
| 会话表 | 新增**「任务类别」**列(值=解析出来的类别,判不出显 `—`) |
| 台账表 | 列名「分工(线)」→**「任务类别」** |
| 验收为空 | 不再静默 ⇒ `⚠️ 未声明验收判据(⛔ 不因此判完成)` |
### 8.2 🔴 红线 ①:`acceptance_state` 只有说明行时被当成"**全过**"
- **形状**(同族红线:**不崩溃,只是少说一句话**):`goalctl.goals_open()` 写的是 `if not acc`
—— 判的是**字典空不空**;而实际有 `_说明`/`_更新` 两个说明行 ⇒ **永不触发** ⇒ 控制台显示「三路全过」。
`collabd._acc_short()` 同族(兜底打印"全部 pass —— 目标已达成")。
- **修法**:判据改成「**有没有有效项**」(键不以 `_` 开头)⇒ 新增 `goal_state()` **三态**:
`open`(有未过项)/`pass`(有判据且全过)/`undeclared`(**一条有效判据都没有** ⇒ ⛔ 不算过)。
`board.py` 的命令行摘要同族(`… or "无"` 读起来就是"全过")⇒ 一并收敛到 `_acc_summary()`。
- **复验**:控制台现显 `⚠️ 未声明验收判据(只有说明行)⇒ **判不出来**,⛔ 不因此判完成`。
### 8.3 🔴 红线 ②:台账里的**旧线(工作区名)整块不见**
- **实况**:类别清单已迁到任务类别(唤醒机制/文档库治理/规则载体/IM,各 **0 件**),
而台账里还躺着 **2 条跨工作区时代的旧线**(`ai1net-dsh-anywhere` / `ai1net-dsh-desktop`,各 **2 件且都已完成**)。
旧版把它们也当"分工位"塞进 `labor`,架构图又只画前 4 格 ⇒ **4 件已完成的活一格都看不见**,
读者只看到 4 个"件 0 · 完成 0"。**形状同族:不崩溃,只是少说一句话。**
- **修法**:① `_labor()` 每行加 **`kind`**(`topic` = 命中 `goal.topics`;`legacy` = 只在台账/`lines` 里出现过)
② `build()` 加 **`orphan`** 汇总(条数 + 件数求和)③ 看板**折叠成一格「未归类」照样画出来**
(格内:`⚠ 不属任何当前任务类别` + 线名 + 件数)④ **图外提示点名**"哪几条线、共几件"
⑤ 台账表给这些值跟一句弱化说明 **`(旧值 · 不在当前任务类别清单)`** —— ⛔ 只弱化、**不隐藏**。
- **判据**:图上不再有"看不见的活";也不让工作区名**冒充**类别。
### 8.4 验证读数(全部可复跑)
| 验证 | 结果 |
|---|---|
| 技能自测 `selftest.py` | **PASS 31 / FAIL 0**(新增 2 个看板用例:验收三态 4 项、未归类可见 4 项) |
| 看板渲染断言(Node DOM 桩,**现取** `board.html` 主脚本喂真实快照) | **18 / 18** |
| 架构图几何自检(越界/压行/格间重叠) | **3 / 3**(R3 现 5 格:`x=60,296.8,533.6,770.4,1007.2`,宽各 212.8,**零重叠**、右沿正好 1220) |
| 真实工作区快照 | 4 类 + 1 个「未归类」格;`orphan = {n:2, total:4, done:4}`;台账表 4 条均标「旧值」 |
⚠️ **为什么用 Node DOM 桩而不是真浏览器**:本机 9223 上没有独立浏览器实例,而本机**明令禁止自起 headless
chrome /附着用户 Chrome** ⇒ 不擅自开浏览器。桩只验**渲染出的 DOM 内容与几何**,验不了像素级观感;
另出一份**离线预览页**(快照内联、`file://` 直开)供人眼复核。
---
## 九、用户两条订正(2026-10-01)—— 都是"我把机制当成了固定配置"
### 9.1 🔴 「**目标是 通过对话在调用 会话协作skill时说明的,不是固定的**」
**我上一轮做错了什么**:把 `goal.json.topics` 按**工作区里现有的接续入口/接续包文件名**填了 4 类
(唤醒机制/文档库治理/规则载体/IM),还当成"待你拍板定死"的事项去问。
⇒ **那是猜,不是说明**。危害不是"填错会报错",而是它**静默决定**了
「哪些会话算本项目、投递往哪条主会话去」—— 且**没有任何一处会说"这批类别是猜的"**。
**落地(三件)**
| # | 落点 | 内容 |
|---|---|---|
| 1 | 技能 `SKILL.md §0.05` + `architecture.md §0.1` | 写明:目标/为什么/任务类别/验收判据**四样都由"调用技能时的对话"产生**;⛔ 技能侧与脚本**不许预设**、⛔ **不许从目录名/文件名推** |
| 2 | 使用方 `goalctl.py declare`(**新命令**) | "说明"的**唯一落点**:`declare --title … [--why …] [--topics …] [--kpi …] --yes`。⛔ 默认干跑;`--title` 必填(脚本**不替你编目标**);省略某项 ⇒ **不动**该项;`--topics ""` ⇒ 显式清空 |
| 3 | 看板 `project.topics_source` | `kind ∈ {declared, fallback, none}` ⇒ **没说明过时明写「⚠ 未声明 ⇒ 暂回落目标简称」**(⛔ 不许让人以为"这就是定下来的类别");`declared` 时显示说明时间/说明人 |
**数据订正**:`goal.json` 里**我猜的那 3 类已移出 `topics`**(进 `_topics候选`,注明"未经对话确认、
依据是文件名"),`topics` 只保留**有依据的那一条**(`唤醒机制` —— 来自本线会话标题
`主控 · 唤醒机制线 · …`)。`lines` 同步。
### 9.2 🔴 「**我说的看板是 实时动态看板 现在被关闭了**」
**事实**:`goalctl stop --yes`(2026-09-30 20:27)把 `board.py --serve` 一起停了 ⇒ 21:00 之后你看到的
只是我做的**离线快照页**(复核手段),不是你要的**活着的那一个**。
**已恢复**:`http://127.0.0.1:8788/` 已重新起着(**会话后台任务 + stdout 落文件**)。
验收读数:`GET /` → **200(59,076 B)**;`GET /board.json` → **200(9,551 B)**;
连拉两次 `epoch` **1790785810.9 → 1790785814.1** ⇒ **真·实时**;`netstat` 确认**只绑 127.0.0.1:8788**(PID 34184)。
快照内容:`topics=['唤醒机制'] · source=declared`;`labor` = 1 个类别 + 2 条 `legacy`;
`orphan={n:2,total:4,done:4}`;`warn=[]`。
**落点同步**:`SKILL.md §0.5.0` 新增「**看板默认指那个实时动态看板**」(地址/起法/`--takeover` 换代码重起/
停机出口),并**如实登记两条边界**:
1. 它是**会话后台任务** ⇒ **关会话/关宿主就停** —— 本机**没有**真正常驻的手段(detached 活不过工具调用边界;
`schtasks`/`reg` 等持久化工具在内置黑名单里)⇒ **这不是"忘了常驻",是做不到**。
2. ⚠️ **该会话挂着 `pending`/`running` 后台任务时,宿主的 `idle` 钩子会被静默压制**(09-30 实测压过 6h20m)
⇒ 正解=**让"起看板"落在一个不承担派活职责的会话里**,⛔ 别让主会话干这件事。
3. 另:`goalctl start` 的 ③ 服务层**原本只说"不自启常驻"、没给起看板的命令**(="开"这条路漏了看板)
⇒ 已补上**可直接复制的整行命令**。