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

18 KiB
Raw Permalink Blame History

协作机制 · 同一工作区 · 多任务类别(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 的 ③ 服务层原本只说"不自启常驻"、没给起看板的命令(="开"这条路漏了看板) ⇒ 已补上可直接复制的整行命令。