## 第 6 步:调度器只管图任务(一行判据,但漏了是静默的)
listQueue / deriveQueueBlocks / sweep 三处都加 executor='graph'。
派单任务虽然也进 tasks 表,但它的执行载体是「直接起一次 agent 对话」,
由 /task/dispatch 自己触发。漏这一条的后果:调度器拿一条根本没有 run 行的
task 去出队 → syncRunNodesToDef 判空 → abandonRun → **把一条正在被 agent
执行的派单收成 cancelled**。守卫用例把它摆成「最像可出队」的样子
(queued + auto + 无依赖),只有 executor 这一维拦得住它。
## 第 5 步:GET /api/tasks 改读表,删掉 122 行的会话反推
原来的 getAggregatedTasks 是启发式:扫全部 agent 的 conversations,按会话 id
里的 T-xxx 归组,再用三条策略(meta 的 parentTaskId / from agent 的活跃会话
时间包含 / master conv 的标题相似)**猜**父子关系。猜出来的树没法验证对错,
而且每次请求都要遍历所有 agent 的会话目录 —— route 那 10 秒缓存就是为它加的。
现在父子关系是 tasks.parent_task_id,派单时就显式写下来了,不用猜。
task-service.js 391 行 → 236 行;_inferParentChild / _loadTaskMetas /
TASK_META_DIR 一并删除(改完就是零调用方)。
**getTaskTimeline 保留**,但来源拆清楚:任务树从表来(parent_task_id,
可验证),时间线正文仍从 agent 的 conversations/ 扫 —— 那是一手记录,
没有替代品。删掉的只是「从会话反推任务树」那一半,不是会话本身。
两处口径变化(写在代码注释里):
- agents:原来是「会话里出现过的 agent 集合」,现在是「谁派的 + 派给谁」。
对派单任务这两者本来就是同一件事
- conversationCount:原来是扫出来的文件数,现在是「有没有起过执行会话」(0/1)。
为一个列表字段遍历所有 agent 的会话目录,正是这次要去掉的那笔开销;
真要看内容走 /api/tasks/:taskId/timeline,那条仍读一手记录
## 漏改一个消费方,真打接口才发现
routes/tasks.js 自己还在算 t.conversations.length,而 conversations 是会话
反推那套的产物、已随之删除 → 500「Cannot read properties of undefined」。
测试没覆盖这条路(它只在真跑接口时才走到)。已改成用 t.conversationCount。
真打验证:GET /api/tasks 返回 17 条、12 个字段齐全 —— 条数比改前多,
因为测试跑出来的派单现在也进表了,这本身就是「存储真的换了」的证据。
全量:132 文件 / 1257 测试全绿。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>