diff --git a/CLAUDE.md b/CLAUDE.md index a30a7ba..a0e149a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -110,7 +110,7 @@ npm run okr:migrate # goals → okr 存量迁移 | `/ws/claude` | Claude 实时对话(仅 `index.js`) | | `/api/health` `/api/plugins` | 健康检查 / 插件状态 | | `/api/devloop/projects` | DevLoop:创建项目 + 跑 PM/Architect(POST)、列项目 / 查状态(GET,见下) | -| `/api/dream/*` | 经验汇流(29-DREAM):`dream-service.js` 能力的 REST 封装,只读查询 + 触发 `dreamOnce`/`dreamSweep`,不新增业务逻辑,见 [`docs/todo/system-design/29-dream-design.md`](docs/todo/system-design/29-dream-design.md) | +| `/api/dream/*` | 经验汇流(29-DREAM):`dream-service.js` 能力的 REST 封装,只读查询 + 触发 `dreamOnce`/`dreamSweep`,不新增业务逻辑,见 [`docs/done/system-design/29-dream-design.md`](docs/done/system-design/29-dream-design.md) | ### DevLoop 子系统 diff --git a/bin/lemon.js b/bin/lemon.js index 55cdfcf..e8c78cf 100755 --- a/bin/lemon.js +++ b/bin/lemon.js @@ -184,6 +184,16 @@ async function run() { phase: values.phase, artifact: values.artifact }); console.log(JSON.stringify(res.data, null, 2)); + + // 42 号文档 Phase 4:额外打一次 graph-runs 的 phase-note 端点,把这次阶段流转 + // 落进 timeline.ndjson(大多数调用方——legacy 会话式 agent——这个端点会安静 + // no-op,因为 --artifact 不是 work graph 节点产物目录的形状;不影响上面那次 + // 调用的结果,出错也不影响本命令的退出码)。 + try { + await request('POST', '/graph-runs/phase-note', { + artifact: values.artifact, phase: values.phase, agentId: values.agentId + }); + } catch { /* best-effort,静默丢弃 */ } } else if (command === 'pipeline create') { const { values } = parseArgs({ args: args.slice(2), options: { diff --git a/docs/README.md b/docs/README.md index 700ba7b..1308388 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,6 +31,12 @@ | [system-design/27-p3c-batch3-handover.md](./done/system-design/27-p3c-batch3-handover.md) | 批 3 交接 —— ✅ 已收口(2026-08-20)。**事故记录归档**:一份未经复核的代码被第三方会话提交进 main。留着是因为 §六 那几条多会话协作规则是真踩出来的 | | [system-design/38-auth-multi-tenant-isolation.md](./done/system-design/38-auth-multi-tenant-isolation.md) | 登录鉴权 + 公司级多租户隔离 —— ✅ Phase 0/1/2/3 全部完成(2026-08-25)。种子账号 `admin`/`hex1234` + 登录页;全站挂 `requireAuth`(`agent-chat.js` 用 `requireAuthOrInternal` 放行三处服务端自环调用);`company.json` 加 `ownerId`,`canAccessCompany` 是全仓库唯一归属判据,`company.js`/`graph-run.js`/`devloop.js` 分别接入(`graph-run.js` 双挂载点参数形状不同,设计阶段描述不准确,实现时才发现);`okr.js` 因是零导航入口的孤儿页面改挂 `requireAdmin` 而非归属校验。**Phase 3 验证阶段真查出一个漏洞**:`agent-chat.js` 靠 `:agentId` 的 `"companyId:role"` 前缀区分会话归属却从没做过归属校验,任意登录用户可跨租户读别家公司会话,已修复。全量 `vitest` 1433 passed | | [system-design/01-26](./done/system-design/) | 系统设计正文 01~26 章(14、25-harness-engineering-improvements 例外,见 todo/future/;27 例外,见 todo/),各章节状态见文内「状态」标记 | +| [system-design/29-dream-design.md](./done/system-design/29-dream-design.md) | **dream(经验汇流)**— ✅ 已实现。定时整理所有 agent 在所有公司的记忆,提炼后跨公司沉淀(不泄漏公司信息)。四条取舍已拍板并实现:去敏独立判定、四落点分层写入(经验/`SKILL.md`/`soul.md`/`agent.md`,受管区块+三道闸门)、每日定时(串行+空转保护)、引用真伪自动查、REST+控制台入口(`/api/dream/*`、`/dream`)。`innovator-evolver` 真 CLI 跑通一次。**仍缺的**:「结论对不对」只能人抽查;「套进 org/work graph 来跑」评估后用户选了不做(作用域不匹配,见文内 §5.7) | +| [system-design/33-needs-gate-role-filter-bug.md](./done/system-design/33-needs-gate-role-filter-bug.md) | ✅ **已修复**。`executor.json`/`executor-fanout.json` 里 `produces:["log"]` 声明了一个 test/integration 类节点永远不会被索引到的 kind,导致 `org-coder-validator` 被 `missing_required_input` 反复误判 blocked;改成 `required:false`(对齐 `validator.json` 既有写法)。文档记录了一条**走过的弯路**(按 role 过滤 `inboundContracts` 会削弱 T-mt25ecbd 的产出闸门,已撤回),发现于 31 号文档的真实回归验证过程中,与兜底边问题无关 | +| [system-design/35-validation-rigor-and-body-readiness-proposal.md](./done/system-design/35-validation-rigor-and-body-readiness-proposal.md) | 📝 **复盘 + 改进方案,四条提案全部已实施**。承接 31/32/33/34 号的复盘结论:任务结果差主要是架构问题(完整性闸门 `missingBodyDefinition` 只在招聘时查、图执行侧完全没有第二道防线——新发现)+ 一个具体的招聘装配可靠性 bug,不是 agent 能力问题。提案 A:本体不完整时图执行前置判 `fail_reason=body_not_ready`,不扣预算。提案 D:`goal-contract.js` 的 `evidence_hint` 命中浏览器类关键词的维度必须有证据支撑,否则判不过。提案 C:新增 `scripts/visual-acceptance-check.mjs` 复用检查工具。提案 B:控制台「验收证据」小节展示每个维度有没有证据支撑 | +| [system-design/40-agent-chat-streaming-interrupt.md](./done/system-design/40-agent-chat-streaming-interrupt.md) | ✅ **已实现**(commit `4d23f50`,2026-08-30)。回应"前端刷新不及时/没有打字机效果,要不要把 SSE 换成 WebSocket"——排查后结论是传输层不是瓶颈(对照 hermes-webui/hermes-agent/deepseek-harness 三个参考实现,全部走 SSE,不用 WebSocket)。三个独立缺口全部补上:① 打字机不流畅→后端按字符数切片 + 前端 `createTypewriterQueue` 匀速吐出;② 执行中无法打断并接上新消息→`stop` 端点改轮询确认锁真正释放,前端据此直接发送/强制发送兜底,顺带修了服务端对同 convId 并发缺闸门这个真实 bug;③ 串台加固→`handleEvent` 抽成纯函数并补齐 `9b1c3f1` 那条不变式的回归测试。已用真实 Claude CLI 会话在浏览器验证 | +| [system-design/39-boss-agent-task-supervision.md](./done/system-design/39-boss-agent-task-supervision.md) | ✅ **P1-P5 全部已实现**(2026-08-30,`a00590a`/`5108d67`/`d967ac0`/`14cbaf5`)。boss agent(复用 `ceo`)监督 goal/task 看板闭环:draft task 回写、事件驱动+低频兜底唤醒、独立任务看板页、公司级 `target_dir`。**真实端到端测试**(建公司→下 goal→跑出一个 Playwright 5/5 验证通过的贪吃蛇游戏)撞出并记录了三个单测没覆盖的真问题,见文内「七、真跑复盘」;沉淀的待办已在 [42-org-work-graph-reliability-followups.md](./done/system-design/42-org-work-graph-reliability-followups.md) 全部收口 | +| [system-design/42-org-work-graph-reliability-followups.md](./done/system-design/42-org-work-graph-reliability-followups.md) | ✅ **Phase 1-4 全部已实现**(2026-08-30,`6ae1f76`/`2a60fe1`/`ae7650e`/`2a0ffb8`)。39 号文档真跑复盘沉淀的四 phase 可靠性收尾:① 卡住恢复闭环(服务器重启僵尸节点检测 + 强制判失败端点)② `ceo` 自建 task 的闸门穿透性回归测试 ③ `target_dir` 的 API/UI 配置入口(Phase 3 前端缺一次真实浏览器点击验证,见该节说明,是唯一已知缺口)④ `timeline.ndjson` 承接 planning/reflection 轨迹——`--artifact` 路径本身编码 `rootRunId`/`nodeRunId`,不用改任何 agent.md 或塞新环境变量 | ## todo/(当前主线) @@ -48,17 +54,13 @@ Graph Agent 架构重构(27 系列)。**已完成的阶段文档已移入 `d | [27-goal-contract-format.md](./todo/system-design/27-goal-contract-format.md) | 验收契约格式(`goal-benchmark` / `goal-test-data` / `goal-test-script` 的产物形态与跨节点传递) | | [27-p4-parallel-and-artifacts.md](./todo/system-design/27-p4-parallel-and-artifacts.md) | **P4(回收编号)并行执行与产物索引** — 📝 设计中,未实现。fan-out 真并发 + 同 agent 多实例隔离 + `files.json` 产物索引。三个 Phase,C 可插队 | | [28-followups.md](./todo/system-design/28-followups.md) | 28 号(Agent 注册表统一)的遗留清单 — 主文档已在 `done/`,这份装没做完的:dream 机制(设计见 29 号,已实现,见下)、`source_kind`(✅已删)/`legacy_path`(不能删)删列、三项「已实施未验证」。任务态清理(✅已实现,见下)。§五 是**已立起来的守卫清单**(别重复造,也别删) | -| [29-dream-design.md](./todo/system-design/29-dream-design.md) | **dream(经验汇流)**— ✅ 已实现。定时整理所有 agent 在所有公司的记忆,提炼后跨公司沉淀(不泄漏公司信息)。四条取舍已拍板并实现:去敏独立判定、四落点分层写入(经验/`SKILL.md`/`soul.md`/`agent.md`,受管区块+三道闸门)、每日定时(串行+空转保护)、引用真伪自动查、REST+控制台入口(`/api/dream/*`、`/dream`)。`innovator-evolver` 真 CLI 跑通一次。**仍缺的**:「结论对不对」只能人抽查;「套进 org/work graph 来跑」评估后用户选了不做(作用域不匹配,见文内 §5.7) | | [30-task-delegation-reliability-gaps.md](./todo/system-design/30-task-delegation-reliability-gaps.md) | **任务委派可靠性现状**— 📝 调研记录,非设计方案。DevLoop `runUnit`、Company 招聘装配、Graph 执行三条链路核对结论一致:都是「失败预算耗尽 → 停等人工」,没有「卡住了主动通知人」这一层;DevLoop 当前还默认走 mock,真实 CLI 成功率无实证数据。2026-08-23 追加真实实例并已修:`infra_flake` 原地重跑没有熔断线,真撞上过一次确定性死循环(节点把诊断结论误判成自己的执行结果,反复重跑却从不路由到诊断建议的目标节点);已加 `infra_flake_count` 列 + 硬顶 3 次熔断,连续判 infra_flake 超过硬顶就改判 blocked 交给人工 | | [31-work-graph-fail-fallback-coverage.md](./todo/system-design/31-work-graph-fail-fallback-coverage.md) | work graph 兜底边覆盖不全 — ✅ 已完成(除入口节点外强制兜底边,`planner.json`/`executor-fanout.json` 补 6 条边),978/978 单测通过;2026-08-21 又用真实 LLM 起了一条新任务做端到端回归验证,`org-planner-sdd` 自判失败后自动打回 `org-planner-prd`(3 次,零人工介入),修复确认生效 | -| [39-boss-agent-task-supervision.md](./todo/system-design/39-boss-agent-task-supervision.md) | 📝 **设计评估中,未实现**。评估用户提出的五阶段方案(雇 boss agent 监督 goal/task 看板、独立任务看板页、心跳挨个调度、org/work graph 产出的 bug/新任务回写看板、公司级目标代码目录)。结论:P2 看板已实现、P1/P3 心跳与 `ceo` 本体基础设施已有大半、P5 的 `linked-existing` 模式已在 `path-resolver.js` 完整实现只差公司级默认值,**真正从零开始的是 P4(回写闭环)**——当前没有任何 agent 工具能读写 goal/task 板。建议顺序倒过来先建 P4 地基 | | [41-graph-vs-fde-reliability-discussion.md](./todo/system-design/41-graph-vs-fde-reliability-discussion.md) | 📝 **讨论记录 + 落地实测,非设计方案**。org/work graph 与 master-Innovator-FDE 两套工作流的稳定性对比:根因是"拓扑运行时生成 vs 写死"。问题 7 是真实执行记录——新建 `org-hunt/digest/evolve`(graph 原生协议,方法论抄 innovator-hunt/digest/evolver)跑 Hunt→Digest→Evolve,11 轮真跑中挖出并修复 5 个独立缺陷(建公司自动规划竞态、work-graph 模板库缺内容型交付物骨架、验收节点任务描述越权、两个规划 agent 的路径口径写错、R-19 产出索引 `indexNodeOutputs()` 不覆盖项目根——最后一条改了 `graph-engine.js` 核心逻辑,加 `contract.json` 的 `produces` 显式声明机制,全局生效,补了 2 条守卫测试)。最终产出与原生 FDE(`prd-methodology-trial`)对比:内容骨架可迁移、质量不打折,但过程严谨度不如原生 FDE,可靠性差距悬殊(原生一次跑通,graph 版 11 轮才稳定)。`routeFailure` 的 `infra_flake` 分支绕过跨层寻址(`target_node`)仍是唯一确认还没补的口子 | | [37-org-graph-department-scoping.md](./todo/system-design/37-org-graph-department-scoping.md) | 📝 **设计讨论,调用点扫描已完成,尚未立项实现**。现状 org graph 按 `company_id` 单例(`27-P2` 标题原文即"公司级"),塞不下 HR/财务/市场这类跟"交付流水线"无关的公司常设职能——`system/agents/hr/`("招·留·管·退")本体已存在却从没被装进任何图,全靠 fire-and-forget chat 招聘、无图执行审计轨迹。提议把 org graph 作用域从 `company_id` 升级为 `(company_id, department_id)`,一家公司可装配多张部门图,每张仍保持人工固定/慢变/可审计。全仓库调用点扫描结论:改造面是最初估算的 4 倍——真正的单点真相在 `work-graph-db.js:readDefGraph` 的裸 SQL 不变式(不在服务层),还牵涉两个易混淆服务文件间必须字节级一致的重复唯一索引、独立的 `graph-seed-service.js` 对账子系统、`company-hire-org-layer.test.js` 专属测试、`orggraph-planning`/`task-planning` 两份 agent 人设文档的自我认知改写 | | [36-prd-agent-methodology-training-feasibility.md](./todo/system-design/36-prd-agent-methodology-training-feasibility.md) | 📝 **调研 + 阶段一/二均已完成**。回应"落地页太单薄是不是该用 Innovator 先训练专业 agent、再用 Dream 持续迭代"——认同方向。**阶段一**:`graph-engine.js` 结算节点后把 `success`/`fail:` 写进本体 `memory.md`(此前只有人类对话路径会写,Dream 天天空转)。**阶段二**:新建独立试验节点 `prd-methodology-trial` 走真实 Hunt→Digest→Evolve,2 轮收敛(96/97 分,真实基线 50 分),三个真实历史项目反推验证方法论有效;过程中连带修复同一个 2026-08-16 改名批次遗留的 5 处 `innovator`→`org-innovator` 死链(后端两处硬编码、前端"重新训练"按钮、`/api/agent/list` 展示 id 来源的四个 `config.json`),以及 `AgentScaffold` 默认模板一处裸相对路径缺陷。结论:FDE 对方法论类角色确实管用,但收敛速度/验收严谨度依赖任务书显式写明的自纠正要求,非免人工黑盒。环节 3(训练产物 → 雇佣本体转换)尚未开工 | -| [35-validation-rigor-and-body-readiness-proposal.md](./todo/system-design/35-validation-rigor-and-body-readiness-proposal.md) | 📝 **复盘 + 改进方案,四条提案全部已实施**。承接 31/32/33/34 号的复盘结论:任务结果差主要是架构问题(完整性闸门 `missingBodyDefinition` 只在招聘时查、图执行侧完全没有第二道防线——新发现)+ 一个具体的招聘装配可靠性 bug,不是 agent 能力问题。提案 A:本体不完整时图执行前置判 `fail_reason=body_not_ready`,不扣预算。提案 D:`goal-contract.js` 的 `evidence_hint` 命中浏览器类关键词的维度必须有证据支撑,否则判不过。提案 C:新增 `scripts/visual-acceptance-check.mjs` 复用检查工具。提案 B:控制台「验收证据」小节展示每个维度有没有证据支撑 | | [34-validator-scored-without-real-deliverable.md](./todo/system-design/34-validator-scored-without-real-deliverable.md) | 📝 **调研记录,checklist 已查完**。最初以为 `org-coder-impl` 从未产出任何 HTML/CSS,后来发现真实网站其实存在于 `graph_files` 索引不覆盖的「项目根」目录(已顺手修:控制台「产物」面板现在会展示项目根)。追加查清 validator 有没有真的做浏览器级检查——中途自我订正一次:不是"没工具做不到"(`bash` 能走到仓库自带的 Playwright,另一条对照任务证明可行),是**没人教它这么做**;抽查另外两个项目根后发现**验证严谨度完全不稳定**(同类任务有的做了真实 Playwright 级验证、有的只做静态正则、有的没验证就被取消),根因很可能是新招聘的 `unit-tester` 本体撞上了 [32 号文档](./todo/system-design/32-hire-body-fill-reliability.md) 那类"装配没填充完"的真实样本,不是平台工具限制 | -| [40-agent-chat-streaming-interrupt.md](./todo/system-design/40-agent-chat-streaming-interrupt.md) | 📝 **需求文档,讨论中,未实现**。回应"前端刷新不及时/没有打字机效果,要不要把 SSE 换成 WebSocket"——排查后结论是传输层不是瓶颈(对照 hermes-webui/hermes-agent/deepseek-harness 三个参考实现,全部走 SSE,不用 WebSocket)。真正的三个独立缺口:① 打字机不流畅(ACP chunk 粒度太粗,方案是前后端都做匀速切片/渲染缓冲);② 执行中无法打断并接上新消息(现状"先 Stop 再发"两步,方案是深度 1 的打断-排队,顺带发现服务端对同 convId 并发缺闸门这个现存 bug);③ 串台已在 `9b1c3f1` 修复但没有回归测试兜底 | -| [33-needs-gate-role-filter-bug.md](./todo/system-design/33-needs-gate-role-filter-bug.md) | ✅ **已修复**。`executor.json`/`executor-fanout.json` 里 `produces:["log"]` 声明了一个 test/integration 类节点永远不会被索引到的 kind,导致 `org-coder-validator` 被 `missing_required_input` 反复误判 blocked;改成 `required:false`(对齐 `validator.json` 既有写法)。文档记录了一条**走过的弯路**(按 role 过滤 `inboundContracts` 会削弱 T-mt25ecbd 的产出闸门,已撤回),发现于 31 号文档的真实回归验证过程中,与兜底边问题无关 | +| [43-task-decomposition-granularity.md](./todo/system-design/43-task-decomposition-granularity.md) | 📝 **需求 + 方案定案 + 技术实现计划(5 个 Phase)已写完,尚未开工**。回应"org-planner 出 brd/prd/sdd 后,任务看板能不能展示成 task/sub-task 树,coder-validator 按清单逐条开发验收"。方案:goal→task 流水线自建父子机制、先只拆验收(开发仍整体完成)、`tasks.acceptance_mode` 由 task-planning 判断、子任务复用 `tasks.status` 既有状态语义、任一子任务 fail 父任务即 fail。**Phase 0-4** 逐条核实了代码形态:`createTask()`/`parent_task_id` 迁移模板、task-planning "硬规则 5"需要开例外让 LLM 多写一个字段、`org-planner` 产出的验收清单其实是 `acceptance.md` 不是 `acceptance.json`(且⚠️真实回归里从未被用上,落地前需先验证可靠性)、`coder-validator`(不是 org-validator,文档订正过一次)的 `checks[]` 目前没有任何服务端代码解析、看板树形渲染有 `FileNode.vue` 现成模板可抄。建议实施顺序 Phase 0→1→4→2→3 | ## todo/future/(暂缓,非当前重心) diff --git a/docs/todo/system-design/29-dream-design.md b/docs/done/system-design/29-dream-design.md index 8856d56..8856d56 100644 --- a/docs/todo/system-design/29-dream-design.md +++ b/docs/done/system-design/29-dream-design.md diff --git a/docs/todo/system-design/33-needs-gate-role-filter-bug.md b/docs/done/system-design/33-needs-gate-role-filter-bug.md index af89b4f..af89b4f 100644 --- a/docs/todo/system-design/33-needs-gate-role-filter-bug.md +++ b/docs/done/system-design/33-needs-gate-role-filter-bug.md diff --git a/docs/todo/system-design/35-validation-rigor-and-body-readiness-proposal.md b/docs/done/system-design/35-validation-rigor-and-body-readiness-proposal.md index bf4cf8e..bf4cf8e 100644 --- a/docs/todo/system-design/35-validation-rigor-and-body-readiness-proposal.md +++ b/docs/done/system-design/35-validation-rigor-and-body-readiness-proposal.md diff --git a/docs/todo/system-design/39-boss-agent-task-supervision.md b/docs/done/system-design/39-boss-agent-task-supervision.md index e0c05ad..afbfae8 100644 --- a/docs/todo/system-design/39-boss-agent-task-supervision.md +++ b/docs/done/system-design/39-boss-agent-task-supervision.md @@ -1,6 +1,6 @@ # 39. Boss Agent 任务看板监督 + 公司目标代码目录 — 需求评估与设计草案 -> **状态**:✅ P1-P5 已实现(2026-08-30,commit `a00590a` P1/P3/P4 后端、`5108d67` P2 前端、`d967ac0` P5)。本文档是落地前的设计草案,实现细节以代码为准,本文档不再更新,仅存档决策过程。**已知未覆盖的后续工作见文末新增的「六、已知遗留」**。 +> **状态**:✅ P1-P5 已实现(2026-08-30,commit `a00590a` P1/P3/P4 后端、`5108d67` P2 前端、`d967ac0` P5、`14cbaf5` graph-run.js 鉴权补丁)。本文档是落地前的设计草案,实现细节以代码为准,仅存档决策过程。**已知未覆盖的后续工作见「六、已知遗留」,真实端到端跑测的复盘见「七、真跑复盘」,复盘沉淀的待办已拆成独立文档 [42-org-work-graph-reliability-followups.md](./42-org-work-graph-reliability-followups.md)(按 phase 排开工顺序)**。 > **日期**:2026-08-30 > **背景**:承接对话讨论——goal/task 两级模型([27-P3C](../../done/system-design/27-p3c-goal-task-model.md))已落地,但推进它的是纯确定性代码(`task-scheduler.js` + `transitionRun` 钩子),没有任何 agent 在持续监督这块看板、处理异常、把执行中发现的新问题回写成新任务。用户提出五阶段方案补上这一层,并追加公司级目标代码目录需求。 > **读者须知**:本文档大量引用现有代码路径核实"已有 vs 缺失",结论以文内给出的具体函数/文件为准;这些引用会随代码演进过期,落地前请重新核实。 @@ -190,3 +190,29 @@ 1. **§四.4 定案的"回归测试钉死闸门"没有单独落地**。`ceo` 自主 `draft` + `enqueue` 走的是和人类调用完全相同的 `POST /:cid/tasks` → `POST /:cid/tasks/:taskId/enqueue` 两个端点(`max_parallel_tasks`/`DEFAULT_TASK_BUDGET`/goal 级 `org-validator` 这几道闸门都挂在数据层/调度器层,不认"谁创建的",理论上自动覆盖),但没有一条测试专门断言"ceo 创建的 task 一样会被这几道闸门拦下"——现有测试(`task-entry-draft-mode.test.js`、`task-blocked-ceo-wake.test.js`)验的是 draft 模式本身和唤醒机制,不是闸门穿透性。理论上没有绕过的口子,但没有测试钉死就不能说"已确认"。 2. **`company.json.target_dir`/`target_mode` 没有任何 API/UI 入口去设置**——现在只能手工编辑公司态目录下的 `company.json` 文件才能配置。P5 落地的是"读取并生效"这一半,"人怎么把这两个字段填进去"这一半还没做(哪怕只是一个 `PATCH /api/company/:cid` 端点)。 3. **`server/routes/graph-run.js` 漏了同一处鉴权修复,已在真实跑动中撞出来并修复(2026-08-30)**。地基 0 当时只改了 `company.js`(goals/tasks 端点),`graph-run.js`(run 的 start/stop/retry/budget 等控制端点)还是纯 `requireAuth`。真实端到端测试里 `ceo` 被唤醒后能读写 goal/task 板,但调 `/api/graph-runs/*` 处理卡住的 run 时精确撞上 401,自己在对话记录里诊断出这一点并写出了人工需要跑的 curl 命令。已修:`router`/`companyRouter` 两个挂载点都改成 `requireAuthOrInternal`,两处 `:companyId`/`:id` 的 `param` 中间件加了与 `company.js` 一致的 `req.user?.internal` 旁路,`route-auth-gate.test.js` 同步更新断言,全量测试零回归(1469/1473,剩余 4 个是核实过的 pre-existing 失败)。**这次的教训**:地基 0 的"这一步做完了"判据不能只看 `company.js` 一个文件——`ceo` 需要触达的控制面横跨两个路由文件,遗漏了没有靠代码审查发现,是靠真实跑一遍才暴露的。 + +--- + +## 七、真跑复盘(2026-08-30,`snakegame-co` 端到端测试) + +P1-P5 落地后,用它做了一次真实端到端测试:建公司 → 手工配 `target_dir` 指向一个真实的空目录 → 下一个"做 HTML 版贪吃蛇游戏"的 goal → `task-planning` 真实拆分 → 全程真实 Claude CLI 调用把 org-planner(PRD/SDD/验收标准)→ org-coder(RESEARCH/PLAN/EXECUTE/VERIFY,含真实的 test/validator/impl 修正迭代)跑完,最终 `goal status: done`、`task status: completed`,产出的 `index.html` 用独立跑的 Playwright 测试验证 5/5 通过(一个可以在浏览器里直接玩的贪吃蛇游戏)。 + +**核心结论:1469 个单测全绿,这一次真实端到端跑动却测出了至少三个单测完全没覆盖到的真问题。** "全部测试通过"不能当"系统能用"的证据——这次最有价值的发现,没有一个是单测挑出来的,全是真实跑一遍撞出来的。 + +### 7.1 真跑撞出的技术缺口 + +按发现顺序: + +1. **服务器重启会让 in-flight 的 graph 节点变僵尸,没有任何机制能自动发现**。为了让 `graph-run.js` 鉴权修复生效重启了一次服务器,正在跑 `org-planner-sdd` 的真实 Claude CLI 子进程被连带杀掉,但 `graph_run_nodes.status` 永远停在 `running`——`ceo` 的两条唤醒路径(事件驱动 + 30 分钟兜底扫描)判据都只认 `status='blocked'`,对"运行中但背后进程已死"这个状态完全不可见。查过现成的"重启后清理残留"机制(`server/index.js` 的 `resumeIncompleteTasks()`),发现它只管 ArenaSK 的 Skill 训练任务,跟 org/work graph 毫无关系;`shutdown()` 也只关插件管理器,不碰 `graph_run_nodes`。这是这次代价最大的一个坑,恢复时手写了一段脚本直接调 `graph-run-sse.transitionNode` 强制把节点判成 `fail`(走的是引擎自己的转换机制,不是绕过系统直接改数据库),才能接上标准的 `retry` 端点继续推进——但这条恢复路径本身也暴露了:**"这个节点看着卡住了,强制判失败以便恢复"这个动作现在没有任何 REST 入口**,只能改代码。 +2. **`graph-run.js` 的鉴权缺口**(见「六、已知遗留」第 3 条,已修复推送)。 +3. **规划/行动/反思没有统一的可追溯位置**。`org-coder` 的四段式阶段流转(RESEARCH→PLAN→EXECUTE→VERIFY)靠调 `bin/lemon.js task transition`,打到 `agent-chat.js` 的 `/task/transition` 端点——这个端点靠自己的 `sessions` Map 找"当前活跃会话",但 `org-coder` 作为 work graph 节点跑的时候走的是 `graph-engine.js` → `AgentManager` → `session-pool.js`,是 CLAUDE.md 里早就记录过的"两个互不相干的会话池"那个坑的又一个受害者:阶段流转的业务逻辑(P0 审批拦截、OKR 子状态更新)在这条路径上**静默跳过**,`_plan.md` 这类中间产物既不进 `graph_files` 契约(这个是刻意的——validator 判定不该依赖"coder 计划写得像不像样"),也不进任何跨节点共享的轨迹,人类/`ceo` 事后想知道"这一步当时怎么想的",只能去读原始的 agent transcript。 + +这三条已经拆成具体的开发计划:[42-org-work-graph-reliability-followups.md](./42-org-work-graph-reliability-followups.md)。 + +### 7.2 过程方法上的教训 + +1. **三个并行 fork 分工做 P1-P5 这个模式本身是对的**——文件边界分得干净,`git diff` 核实过没有意外重叠,最后能精确按 hunk(不是整文件)拆成三个独立 commit。值得作为以后类似规模改动的默认工作方式。 +2. **"全部测试通过"不能替代真实跑一遍**——见上,这是本次复盘最核心的教训,直接决定了 41 号文档 Phase 1-2 优先做"让已经在跑的东西更可靠"而不是急着加新功能。 +3. **"这一步做完了"的判据容易只验证了子集**——`graph-run.js` 的鉴权缺口就是典型:地基 0 当时的验证只覆盖了 `company.js`,`ceo` 实际需要触达的控制面横跨两个路由文件。以后给 agent 开权限类的工作,落地前要先列一份"这个角色实际会调哪些端点"的清单,逐条核对,不能默认"改了一个入口就等于都改了"。 +4. **重启共享的开发服务器要谨慎**——为了让代码改动生效重启了一次服务器,没意识到这会杀掉正在跑的真实 Claude CLI 子进程,浪费了一整轮真实 LLM 调用(真金白银)。以后类似场景应该先确认有没有 in-flight 的真实执行,能等就等它到一个安全点再重启。 +5. **"为什么不能自动恢复"这类追问是复盘里最值钱的部分**——第一反应往往是"手动修一下继续跑",是持续追问"这本该由谁负责、为什么没发生"才把表面的一次性意外,看清楚成系统性盲区(僵尸节点检测缺失)。这次几个最高价值的发现,都来自这种不满足于表面解释、继续往下问一层的模式,值得作为以后排查问题的默认姿势,不只是这一次的运气。 diff --git a/docs/todo/system-design/40-agent-chat-streaming-interrupt.md b/docs/done/system-design/40-agent-chat-streaming-interrupt.md index b9572c3..4c9a4a5 100644 --- a/docs/todo/system-design/40-agent-chat-streaming-interrupt.md +++ b/docs/done/system-design/40-agent-chat-streaming-interrupt.md @@ -1,7 +1,14 @@ # 40-agent-chat-streaming-interrupt. Agent Chat 打字机平滑 + 打断-排队 + 串台加固 > **日期**: 2026-08-30 -> **状态**: 📝 需求文档,讨论中,尚未实现 +> **状态**: ✅ 已实现(commit `4d23f50`,2026-08-30)。三个方案全部落地:后端按字符数切片 + +> 前端 `createTypewriterQueue` 匀速吐出(打字机平滑);`stop` 端点改轮询确认 +> `agentChatLocks` 真正释放后返回 `cleaned`,前端据此直接发送或走强制发送兜底,执行中解锁 +> 输入框(打断-排队);`handleEvent` 分发逻辑抽成不依赖组件实例的纯函数 +> (`agentChatEvents.js`),补上 `9b1c3f1` 那条不变式的回归测试(串台加固)。已用真实 +> Claude CLI 会话(非 mock)在浏览器里验证打字机效果与打断并发送。实现过程中顺手修了一个 +> 真实并发漏洞:同一 `agentId` + 同一 `convId` 的并发请求此前完全没有闸门。本文档不再更新, +> 仅存档设计过程,实现细节以代码为准。 > **触发**: 用户反馈"前端刷新不及时、没有打字机效果",怀疑要不要把传输层从 HTTP 换成 WebSocket > 来解决打字机效果、多 Agent/多聊天切换、执行中无法打断这几个问题。经排查现状代码 + > 对照 [hermes-webui](file:///Users/yibo/Documents/lemonAI/hermes-webui)、 diff --git a/docs/done/system-design/42-org-work-graph-reliability-followups.md b/docs/done/system-design/42-org-work-graph-reliability-followups.md new file mode 100644 index 0000000..306cd33 --- /dev/null +++ b/docs/done/system-design/42-org-work-graph-reliability-followups.md @@ -0,0 +1,130 @@ +# 42. Org/Work Graph 可靠性收尾 —— 39 号文档真跑复盘的待办 + +> **状态**:Phase 1-4 全部 ✅ 已实现(2026-08-30)。Phase 3 前端缺一次真实浏览器验证(见该节说明),其余无已知缺口。 +> **日期**:2026-08-30 +> **背景**:[39-boss-agent-task-supervision.md](./39-boss-agent-task-supervision.md) 的 P1-P5 落地后,用它做了一次真实端到端测试(建公司 → 下 goal → `ceo` 监督 → 真实跑出一个可玩的贪吃蛇游戏,Playwright 5/5 验证通过)。**1469 个单测全绿,这次真跑却测出至少 3 个单测完全没覆盖到的真问题**——这份文档是那次复盘沉淀下来的待办,按开工顺序分四个 phase。 +> **读者须知**:每个 phase 的现状描述都来自那次真实测试里实际撞到的场景,不是推测;具体文件/行号落地前按惯例重新核实。 + +--- + +## 一、为什么排这个顺序 + +四个 phase 分两类:**Phase 1-2 是"让已经在跑的东西更可靠"**(不改变行为,只是补上恢复路径和验证),**Phase 3-4 是"补功能缺口"**(新增能力)。前者优先——真跑复盘里代价最大的坑(僵尸节点、鉴权漏挂)都是"东西本来能跑,只是卡住了没人能救"这一类,越早补上,后面几个 phase 开工时越不容易被同样的坑绊到。Phase 5(timeline 承接规划轨迹)单独放最后,因为它是结构性改动,需要先摸清 `org-coder` 之外还有哪些角色也在打那条断线的 `/task/transition`,工作量比其余四个 phase 加起来还大。 + +``` +Phase 1:卡住恢复闭环(僵尸节点检测 + 强制判失败端点)——两个互补的口子一起补 +Phase 2:§四.4 闸门穿透性回归测试 —— 纯测试,零设计风险 +Phase 3:target_dir 的 API/UI 入口 —— 纯加法 +Phase 4:timeline.ndjson 承接 planning/reflection 轨迹 —— 结构性改动,排最后 +``` + +--- + +## 二、Phase 1:卡住恢复闭环 —— ✅ 已实现(2026-08-30) + +**落地结果**(与下面的设计一一对应,实现时没有偏离): +- 1a:`graph-engine.js` 新增 `sweepOrphanedRunningNodes(cutoffIso)`,复用 `routeFailure` 的 `infra_flake` 分支;`server/index.js` 在 `PROCESS_STARTED_AT`(模块顶层捕获的本次进程启动时刻)挂上开机一次性调用,排在其余「开机补一次全量扫描」钩子之前。测试:`server/tests/unit/graph-engine-orphan-sweep.test.js`(4 例:孤儿转 infra_flake 原地重跑、晚于 cutoff 的不受影响、连续判孤儿撞硬顶转 blocked、无孤儿时安静 no-op)。 +- 1b:`graph-engine.js` 新增 `forceFailNode(runId, nodeRunId, failReason='operator_intervention')`;`graph-run.js` 新增 `POST /:id/nodes/:nodeId/force-fail`,只接受 `status='running'`。测试:`server/tests/unit/graph-run-force-fail.test.js`(8 例:正常转 fail 并可被 retry 接上、自定义 failReason 透传、五种非 running 状态各返 400、节点不存在返 404)。 +- 全量测试 1483/1487,零回归(剩余 4 个是核实过的 pre-existing 失败)。 + +--- + +### 1a. 服务器重启的僵尸节点检测 + +**现状(真跑撞到的)**:`graph_run_nodes.status='running'` 这一行,没有任何机制确认"背后真的还有个活的进程"。真实测试里为了让 `graph-run.js` 鉴权修复生效重启了一次服务器,正在跑 `org-planner-sdd` 的 Claude CLI 子进程被连带杀掉,但那一行的状态永远停在 `running`——不会自己变成 `blocked`,`ceo` 的两条唤醒路径(事件驱动 + 30 分钟兜底扫描)判据都是 `status='blocked'`,对这个状态完全不可见。 + +查过现成的"重启后清理残留"机制:`server/index.js` 的 `resumeIncompleteTasks()` 只处理 ArenaSK 的 Skill 训练任务(`skill-task-manager`),跟 org/work graph 毫无关系;`shutdown()`(`SIGTERM`/`SIGINT` 处理)只关插件管理器,不碰 `graph_run_nodes`。两处都不覆盖这个场景。 + +**设计**:启动时扫一遍 `graph_run_nodes`,`status='running'` 且 `updated_at`(或 `startedAt`)早于**本次进程启动时间**的行,判定为孤儿(整个进程树都被换过一轮,不可能有真实进程还在跑它)。判定后按 **`infra_flake` 语义**处理,不发明新状态——原地重跑逻辑本来就是为"基础设施打断,跟设计/实现对不对无关"这类场景设计的,服务器重启只是这个桶里更严重的一个成员。复用 `graph-engine.js` 已有的 `routeFailure` 里 `INFRA_FLAKE_REASON` 那条分支,不用另起一套判定/熔断逻辑。 + +**落点**:`server/index.js` 启动流程(挂在 `resumeIncompleteTasks()`/`sweepStuckActiveGoals()` 同一批"开机补一次全量扫描"的地方,跟这些既有钩子形状一致),加一个 `sweepOrphanedRunningNodes()`;判定逻辑放 `graph-engine.js` 或 `work-graph-db.js`(参照 `infra_flake` 现有实现挂在哪个文件)。 + +**验收**:新增测试——起一个 `status='running'`、`startedAt` 早于"当前进程启动时间"的节点,跑一次 `sweepOrphanedRunningNodes()`,断言它被转成 `infra_flake` 路径能处理的状态,且不影响 `startedAt` 晚于启动时间的正常运行节点。 + +### 1b. "强制判失败以便恢复"的 REST 端点 + +**现状**:`POST /:id/nodes/:nodeId/retry` 只接受 `status ∈ {fail, blocked}` 的节点。真实测试里遇到一个卡住的 `running` 节点,唯一的恢复手段是**直接写一段脚本调用 `graph-run-sse.transitionNode` 内部函数**,绕过了任何 REST 接口——这对人类操作者或 `ceo` 都不现实,两者都只能走 HTTP。 + +**设计**:新增 `POST /api/graph-runs/:id/nodes/:nodeId/force-fail`,语义是"我确认这个节点已经不可能再自己完成了,把它判成 `fail`(`failReason` 可选,默认某个通用值如 `operator_intervention`),交给既有的 fail→retry/backward-edge 路径处理"。**只接受 `status='running'` 的节点**(`fail`/`blocked` 已经有 `retry` 端点了,不重复造轮子);调用方是人类还是 `ceo` 不做区分,鉴权走已有的 `requireAuthOrInternal`(跟着 Phase 1a 一起补,`graph-run.js` 这次已经全部换过了)。1a 落地后,这个端点的日常触发频率应该会显著下降(大部分"卡住"场景 1a 已经自动兜底),但保留它作为人工/`ceo` 判断"这个不是重启造成的,就是卡了"时的手动出口。 + +**验收**:新增测试——`running` 节点调 `force-fail` 转 `fail` 且可被 `retry` 端点接上;`fail`/`blocked`/`pending` 状态调用返回 400(沿用 `ensureStatus` 现成的错误形状)。 + +--- + +## 三、Phase 2:§四.4 闸门穿透性回归测试 —— ✅ 已实现(2026-08-30) + +**落地结果**:新建 `server/tests/unit/ceo-created-task-gates.test.js`,3 例全部走 `ceo` 实际会用的通路(`POST /tasks` 的 `draft:true` + `created_by:'ceo'` → `/enqueue`,不抄近道直接摆数据库行): +1. `max_parallel_tasks=1` 时坑已占满,`ceo` 建的第二条任务落 `queued`,不插队。 +2. 预算给 `total_agent_calls:0`,`engine.checkBudget()` 照样判耗尽,`created_by='ceo'` 不改变结果。 +3. `ceo` 建的两条 task 挂在同一个 goal 下,全部 `completed` 后 `goal-validation` 的 `validateGoal` 钩子只被调一次(用 `validation.setValidateGoal()` 注入假验收,照抄 `goal-task-acceptance.test.js` 的既有手法)。 + +全量测试 1486/1490,零回归(剩余 4 个是核实过的 pre-existing 失败)。 + +**过程小插曲**:驱动第二条 task 的 run 出队时,先试了直接调 `task-scheduler.tick()`——撞上一个跟这次改动无关的既有工具链毛刺(`require.cache` 手工失效对 vitest 的 CJS 模块图不完全生效,`task-scheduler.js` 惰性 `require` 的 `work-graph-db` 引用有时会绑定到清缓存之前的实例,触发 `graph-db.js` 的"测试进程别碰生产库"守卫)。绕开的办法是不依赖 `scheduleTick` 的 `setImmediate` 时序,坑空出来后直接调 `updateRunStatus(runB,'queued','ready')`——这条路径本来就是准入事务的真实实现,只是不经过调度器这层编排,测试意图("闸门拦不拦得住")不受影响。 + +**现状**:39 号文档 §四.4 已拍板"`ceo` 全权自主 `enqueue`,但 `max_parallel_tasks`/`DEFAULT_TASK_BUDGET`/goal 级 `org-validator` 这几道闸门必须照样拦住它",落地时没有补这条测试(§六"已知遗留"记过一笔)。理论上这几道闸门挂在数据层/调度器层,不认"谁创建的",天然覆盖,但没有测试钉死就不能说"已确认"。 + +**设计**:不涉及任何设计决策,纯粹是补测试。新增用例(大致形状,落地时按现有测试文件的辅助函数抄写法): +1. 把公司 `max_parallel_tasks` 设成 1,已有一条 task 占着坑,`ceo` 走 `draft`+`enqueue` 建第二条——断言它排队而不是插队执行。 +2. 把公司 `DEFAULT_TASK_BUDGET` 设小,`ceo` 建的任务跑到预算耗尽——断言按现有 `budget-exhausted` 路径 `blocked`,不会因为"是 agent 建的"就获得额外预算。 +3. `ceo` 建的多 task goal 全部 `completed` 后,断言 goal 级 `org-validator` 照样被调一次(不因创建者是 agent 而跳过收敛验收)。 + +**落点**:新建 `server/tests/unit/ceo-created-task-gates.test.js`,或并入现有 `task-blocked-ceo-wake.test.js`(按当时哪个更顺手)。 + +--- + +## 四、Phase 3:`target_dir` 的 API/UI 入口 —— ✅ 已实现(2026-08-30) + +**落地结果**: +- 后端复用了已存在的 `PUT /api/company/:companyId`(`companyService.update()`),没有新建端点——设计里"如果已存在就扩展"这条命中了。`update()` 新增 `target_dir`/`target_mode` 分支,校验直接调 `path-resolver.validateLinkedTarget`(39 号文档 P5 抽出来的共享函数,和 `graph-engine.js` 运行时校验同一份判据),校验失败抛出,路由层 `try/catch` 转 400;`target_dir` 传空字符串清空配置;不传 `target_mode` 时默认 `linked-existing`,与 `graph-engine.js` 的 `readCompanyTargetConfig` 同一口径。测试:`server/tests/unit/company-target-dir-config.test.js`(8 例:`linked-existing`/`linked-new` 各自成功路径、默认 mode、敏感路径黑名单、`linked-new` 非空目录、`linked-existing` 不存在、清空配置、公司不存在)。 +- 前端:`CompanyDashboard.vue` 的"概览"tab 加一张卡片(目标目录输入框 + 模式下拉 + 保存按钮),本地表单状态不直接双向绑定 `selectedCompany`(保存失败时不该让用户刚打的字被冲掉)。 + +**验证方式的取舍**:`npm run web:build` 编译通过 + 后端测试直接打真实路由层(不 mock service)。**没有做真实浏览器点击验证**——当时服务器上有其它并行会话真实在跑的 agent 进程(`ps` 能看到 3 个 `claude` 子进程),而重启服务器才能让后端改动生效,42 号文档 §二 已经记过一次"重启服务器杀掉别人真实进程"的教训,这次选择不重复。**这是一个已知的验证缺口,不是"做完了"**,下次服务器有安全窗口(没有并行会话在跑真实进程)时应该补一次真实点击验证。 + +--- + +**现状**:P5 落地的是"配了 `company.json.target_dir` 就生效"这一半,"人怎么把这两个字段填进去"这一半没做——真实测试里是手工编辑公司态目录下的 JSON 文件配的。 + +**设计**: +- **后端**:`PATCH /api/company/:companyId`(如果这个端点已存在就扩展,不存在就新建)接受 `target_dir`/`target_mode`,写回 `company.json`。校验复用 `path-resolver.js` 的 `validateLinkedTarget`(39 号文档 P5 已经抽出来的共享校验函数),配置阶段就能提前报错(不用等到第一次 `ensureProjectRoot` 才发现路径不对)。 +- **前端**:公司设置页面(或 `TaskBoard.vue`/`GraphConsole.vue` 附近,具体挂哪个页面待定)加一个表单,填目录路径 + 选 `linked-new`/`linked-existing`。 + +**验收**:新增测试覆盖 `PATCH` 端点的校验分支(合法路径写入成功、`isSensitivePath` 命中黑名单拒绝、`linked-new` 目录非空拒绝);前端至少过一次真实点击验证表单能提交成功。 + +--- + +## 五、Phase 4:`timeline.ndjson` 承接 planning/reflection 轨迹 —— ✅ 已实现(2026-08-30) + +**调研结论(落地前按设计要求先做的那一步)**:`grep` 全仓库 `system/agents/*/agent.md`,引用 `task transition` 的有 9 个真实角色(`_base`、`code-planner`、`coder`、`org-coder`、`org-planner`、`org-writer`、`pricing-api-integrator`、`unit-tester`、`workgraph-planning`,另有几个 `zz-*` 测试夹具不算)。**`_base` 在列**——这是所有新建 agent 复制的骨架模板,意味着这条能力管的不是几个角色的特例,是整个"多阶段流转"这一类角色的通用约定,往后任何继承 `_base` 的新角色自动适用。 + +**关键发现,决定了整个实现方式**:`AgentManager.callAgent()` 的 `metadata`(含 `taskId`/`nodeRunId`/`rootRunId`)**只进服务端日志和 hook,agent 读不到**(代码原文注释)。但 agent 的节点私有产物目录本来就以 `data/companies/{cid}/runs/{rootRunId}/nodes/{nodeRunId}/` 这个形状写进它的提示词(`describeArtifactContract`),而 `--artifact` 参数本来就是这个目录下的文件路径——**两个 ID 已经现成地编码在调用方唯一知道、唯一会传的那个参数里**,不需要给 `AgentManager`/子进程塞任何新的环境变量,也不需要改一个字的 agent.md 提示词。 + +**实现**(与设计里"两种做法二选一"对应:选的是**不改 `bin/lemon.js` 的 CLI 签名、不碰 `agent-chat.js`**这条更保守的路): +- `bin/lemon.js` 的 `task transition` 命令保持原有对 `/agent/:agentId/task/transition`(legacy)的调用完全不变,额外 best-effort 打一次新端点 `POST /api/graph-runs/phase-note`(body 只传 `artifact`/`phase`/`agentId`),失败静默吞掉,不影响命令本身的返回值/退出码。 +- 新端点从 `artifact` 路径正则解出 `rootRunId`/`nodeRunId`,校验 `rootRunId` 对应真实存在的 run(不存在就是 legacy 会话式调用的路径形状,安静返回 `not-a-graph-artifact`,不是错误),校验通过后调 `graph-run-sse.emitEvent()` 追加一条 `kind:'agent.phase'` 的 timeline 记录,`ref` 是 `nodeRunId`。 +- **`agent-chat.js` 完全没有改动**——这次特意避开了它:落地当天这个文件正被另一个并行会话实时编辑(`git status` 显示未提交修改),去碰一个正在被别人改的共享文件风险太高,改成把新能力放进这次 Phase 1-3 已经在用的 `graph-run.js`,物理上不产生交集。 +- **"不改的部分"边界确认成立**:新端点只调 `emitEvent`(纯追加 timeline,`apply` 传 `null`),不碰 `graph_run_nodes`/`graph_runs` 任何一行——测试里断言了这一点(节点状态在调用前后不变)。 +- **设计里 open 的第三个问题(P0 拦截/OKR 更新怎么处理)就此关闭**:答案是"完全不动"——这两段逻辑挂在 legacy `/task/transition` 里,图执行场景下(`sessions` Map 找不到会话)本来就从没触发过,这次也没打算让它们触发;新端点是完全独立的第二条路径,不是对旧端点的迁移或替换。 + +**测试**:`server/tests/unit/graph-run-phase-note.test.js`(4 例:合法节点产物路径写进 timeline 且节点状态不受影响、路径形状不对时安静跳过、路径形状对但 run 不存在时安静跳过、没传 artifact 时报 `no-artifact`)。全量测试 1503/1506,零回归(剩余 3 个失败全部来自另一个并行会话正在进行中、还没提交的工作)。 + +**现状(真跑撞到的,这是四个 phase 里最大的一个)**:`org-coder` 的四段式(RESEARCH→PLAN→EXECUTE→VERIFY)流转靠调 `bin/lemon.js task transition`,打到 `agent-chat.js` 的 `/task/transition` 端点——这个端点靠自己的 `sessions` Map 找"当前活跃会话",但 `org-coder` 作为 work graph 节点跑的时候走的是 `graph-engine.js` → `AgentManager` → `session-pool.js`,是 CLAUDE.md 里早就记录过的"两个互不相干的会话池"那个坑的又一个受害者。结果:`org-coder` 在 work graph 里调这条流转指令时,`sessions.entries()` 根本找不到它的会话,阶段流转的业务逻辑(P0 审批拦截、OKR 子状态更新)**静默跳过**;它的中间产物(`_research.md`/`_plan.md`/`_lessons.md`)不进 `graph_files` 契约(这个是刻意的,见下"不改的部分"),但也**不进任何跨节点/跨角色共享的轨迹**,人类或 `ceo` 事后想搞清楚"这一步当时是怎么想的",只能去读原始的 agent 会话 transcript(`GET /:id/nodes/:nodeRunId/transcript`),没有结构化的入口。 + +**不改的部分(上一轮已经讨论定案,写在这里防止实现时跑偏)**: +- `_plan.md` 这类中间产物**不进 `needs`/`produces` 契约**,`org-validator` 判定依据仍然只认 `acceptance.md` + `artifact`/`log`。理由:validator 判定"代码对不对",不该依赖"coder 计划写得像不像样",这条边界一旦模糊,两个坏结果都会发生——coder 学会把计划写漂亮而不是把代码写对;`graph_files` 的契约面失控(这个仓库反复栽在"两套体系不统一"上)。 +- 本 phase 只解决**可见性**(人类/`ceo` 事后能不能追溯),不解决**契约**(下游节点该不该依赖)——这是两件事,上一轮讨论已经拆开过。 + +**设计**(需要先做的调研,落地前必做): +1. **调研范围**:`org-coder` 是这次撞到的一个,不代表只有它——扫一遍 `system/agents/*/agent.md` 里还有哪些角色的提示词也在调 `bin/lemon.js task transition`,把范围摸清楚再动手,不要只按 `org-coder` 这一个样本改。 +2. **迁移方向**:把"阶段流转"这个动作,从打 `agent-chat.js` 的 `/task/transition`(legacy,不认 graph run),改成写一条 `timelineNote()`(`graph-engine.js` 已有机制,per-run、append-only、永不改已写行),带上 `phase`(research/plan/execute/verify 或角色自定义的阶段名)+ `artifact` 路径。**具体是改 `bin/lemon.js task transition` 这条命令本身去打新端点,还是给它接一条新命令**,两种做法都要过一遍现有 agent 提示词的改动成本再定——这些提示词文件散在几十个 `agent.md` 里,改一条命令的调用方式等于牵动所有引用它的本体。 +3. **P0 审批拦截 / OKR 子状态更新这两块旧逻辑怎么处理**:`/task/transition` 里现在还挂着 P0 任务拦截、OKR 子状态更新这两段业务逻辑——这两者本身是不是还需要(尤其 OKR 那段,跟 P3C 的新 goal/task 模型是不是还有效对应),需要单独判断,不能假设"迁移流转机制"就等于"这两段逻辑原样照抄"。 + +**验收**(待设计细化后补全,这里先占位):至少要有一条测试证明——`org-coder`(或调研出的任意一个角色)在 work graph 里完成一次阶段流转后,`timeline.ndjson` 里能查到对应的 `phase`+`artifact` 记录,且这条记录不影响 `graph_files`/`needs`/`produces` 的既有行为(回归测试守住"不改的部分"那条边界)。 + +--- + +## 六、开放问题 + +1. Phase 1a 的孤儿判定条件(`startedAt` 早于进程启动时间)在多进程/多副本部署下是否还成立?现在是单进程部署,这条先按当前架构写,多副本场景不在这次范围内。 +2. Phase 3 的 `PATCH /api/company/:companyId` 端点如果本来就不存在,是新建这一个端点,还是这次顺手看一下公司配置类的改动有没有更合适的既有入口——落地前查一遍 `company.js` 现状再定,不在这里预判。 +3. Phase 4 的调研如果发现有 10+ 个角色都在用旧的 `/task/transition`,工作量会明显超出"一个 phase"的量级,届时可能需要再拆细——先按"调研完再评估"处理,不在这里预先估工作量。 diff --git a/docs/todo/system-design/28-followups.md b/docs/todo/system-design/28-followups.md index c3f9661..4a935e8 100644 --- a/docs/todo/system-design/28-followups.md +++ b/docs/todo/system-design/28-followups.md @@ -21,7 +21,7 @@ ### 1.1 dream 机制 **状态(2026-08-20 更新)**:设计已成文并已实现,见 -[29-dream-design.md](./29-dream-design.md)。四条取舍(去敏判据 / 沉淀落点 / 触发时机 / +[29-dream-design.md](../../done/system-design/29-dream-design.md)。四条取舍(去敏判据 / 沉淀落点 / 触发时机 / 质量评估)均已拍板,五步推进顺序 + 定时挂载 + 定义文件写入均已落地, `innovator-evolver` 上真 CLI 跑通一次(40 条原料 → 4 条抽象经验,8 条引用全部核对回原文)。 diff --git a/docs/todo/system-design/31-work-graph-fail-fallback-coverage.md b/docs/todo/system-design/31-work-graph-fail-fallback-coverage.md index 313e58a..8106ca6 100644 --- a/docs/todo/system-design/31-work-graph-fail-fallback-coverage.md +++ b/docs/todo/system-design/31-work-graph-fail-fallback-coverage.md @@ -15,7 +15,7 @@ > (零 `retryNode` 调用),最终 planner 子图收敛到 `completed`。**修复确认生效,有真实证据**。 > 同一次验证顺带在 `org-coder` 子图撞见一个**独立的新 bug**(`inboundContracts` 未按 `role` > 过滤,把 `produces` 声明误当 `needs`,导致 `missing_required_input` 假阳性),与本文档无关, -> 已记录在 [33-needs-gate-role-filter-bug.md](./33-needs-gate-role-filter-bug.md)。 +> 已记录在 [33-needs-gate-role-filter-bug.md](../../done/system-design/33-needs-gate-role-filter-bug.md)。 --- diff --git a/docs/todo/system-design/34-validator-scored-without-real-deliverable.md b/docs/todo/system-design/34-validator-scored-without-real-deliverable.md index e04fdd4..8c4c549 100644 --- a/docs/todo/system-design/34-validator-scored-without-real-deliverable.md +++ b/docs/todo/system-design/34-validator-scored-without-real-deliverable.md @@ -17,7 +17,7 @@ > 前置闸门统一验证严谨度,留给下次设计。 > **触发**: `e2e-demo` 公司真实回归任务 `T-mt2r6fz0-125b`(goal `G-mt2r62ia-73af`, > root run `run_0e9074815130`)——[31 号文档](./31-work-graph-fail-fallback-coverage.md) -> 兜底边修复 + [33 号文档](./33-needs-gate-role-filter-bug.md) 契约修复之后,把这条任务 +> 兜底边修复 + [33 号文档](../../done/system-design/33-needs-gate-role-filter-bug.md) 契约修复之后,把这条任务 > 真正推到 `completed`,回头查"最终产出在哪"时发现的 --- diff --git a/docs/todo/system-design/43-task-decomposition-granularity.md b/docs/todo/system-design/43-task-decomposition-granularity.md new file mode 100644 index 0000000..53d241e --- /dev/null +++ b/docs/todo/system-design/43-task-decomposition-granularity.md @@ -0,0 +1,138 @@ +# 43 · Goal 分解粒度:规划产出该长成文件,还是长成 task/subtask 树 + +> **状态**: 📝 需求 + 现状调研,尚未设计、未实现 +> **日期**: 2026-08-31 +> **触发场景**: snakegame-co 公司,goal `G-mtfh23mu-bd63`("HTML 贪吃蛇游戏"),task `T-mtfh2kuf-7c31`。 +> 该任务当天被 `missing_required_input` 死循环卡住(根因与修复见 [33 号文档](../../done/system-design/33-needs-gate-role-filter-bug.md) 的复发一例, +> `tools.md` 范例与 `work-graph-db.js` 引擎层已同日修复),排查过程中用户提出了本篇的需求。 +> **格式对齐** `27-requirement.md`:原话照搬 + 结构化复述,现状调研另起一节。 + +--- + +## 原始需求(用户原话,一字不改) + +> 就这个任务,我讲讲我理想的状态,应该是 org planner 完成了brd prd sdd 后,在 task 任务看板应该能看到规划出来的 todo list 作为这个 html 贪吃蛇游戏 goal 的 task 和 sub task 展示出来,然后每一个 org coder 都是按照这个 task todo list 在逐个的开发完成,然后 org validator 应该是针对 task todo list 在逐个验收,这个现在是不是实现不了? + +--- + +## 结构化复述 + +用户设想的理想流程,拆成四步: + +1. **规划阶段**:org-planner 完成 brd/prd/sdd 之后,不只是产出文档,还应该把这份规划**分解成一份 todo 清单**。 +2. **展示阶段**:这份 todo 清单应该以 **task 和 sub-task** 的形态出现在任务看板上,挂在"HTML 贪吃蛇游戏"这个 goal 下面(例子:方向键控制、吃食物加分、碰撞结束、重新开始——即当前 `acceptance.json` 里的 A.1-A.5)。 +3. **开发阶段**:org-coder 不是一次性对着一大段任务描述写完整个交付物,而是**按 todo 清单逐条**开发。 +4. **验收阶段**:org-validator 也不是一次性给整个交付物判总分,而是**按 todo 清单逐条**验收。 + +用户明确问:**现状是不是做不到这个?** + +--- + +## 现状调研结论(本次会话核实,非最终设计) + +结论:**部分可以,"做不到"的那部分是当前 agent 设计上的主动选择,不是漏做的功能**。四层现状分别是: + +### 1. `tasks.parent_task_id`——字段在,但服务的是另一条流水线 + +`tasks` 表(`server/services/work-graph-db.js`)确实有 `parent_task_id` 列,也确实有代码在读写它——但那是 **LemonBoss Agent Chat 的人工派单流程**(`server/routes/agent-chat.js` + `task-service.js` 建任务树),跟本篇讨论的 goal → task → org-graph/work-graph 这条流水线是两套独立机制。 + +本篇关心的这条流水线里,真正负责"从 goal 生成 task 行"的函数是 `work-graph-db.js` 的 `createTask({goalId, companyId, seq, title, description, executionMode, dependsOn, priority, budgetJson, reportTo, createdBy})`——**这个函数签名里根本没有 `parentTaskId` 参数**。也就是说,子任务对这条流水线而言是**结构性够不着**,不是"字段有、没人填"。 + +### 2. task-planning 阶段本身有能力拆多任务,但拆分依据不是"检查项" + +真正把 goal 拆成 task 的阶段是 `task-planning`(`server/services/task-planning.js` + `system/agents/task-planning/`,落库调用点在 `server/routes/company.js:1577 insertTasksForGoal`)。这一步**确实能一次拆出多条 task**(已有测试验证过 2 条以上的场景)。 + +snakegame-co 这次只拆出 1 条 task,不是能力限制,是 **`system/agents/task-planning/agent.md` 第二步的明确设计判断**:拆分依据是"交付物/验收边界",**不是"工作阶段"**。原文点名"①调研②设计③开发④测试⑤上线"这种按阶段拆是"最常见也最致命的错法"。按这份人设的判断,方向键/得分/碰撞/重开这四条,是**同一个交付物的验收清单**(正是最终 `acceptance.json` 里一起被同一份 Playwright 脚本验证的 A.1-A.5),不是四个可以独立排期验收的 task——agent 设计上就会拒绝把它们拆成四条 task。该 agent 人设还硬顶单 goal ≤ 8 条 task,整体偏"合并"不偏"细分"。 + +### 3. org-planner 没有"建任务"这个工具 + +org-planner 在跑的时候(`system/agents/org-planner/tools.md`)能力边界是:调研 → 阶段流转 → 写 brd/prd/sdd/acceptance.md 这几份**文件** → 向 CEO 汇报。**没有任何一个工具允许它创建新的 task 行**(org-coder / org-validator 同样没有)。规划阶段的产出,结构上只能落成文档,附着在它所属的那一条已经固定的 task 上,没法在跑的过程中反过来生出新的、可独立调度的子任务。 + +### 4. 看板 UI 没有树形渲染 + +`web/src/views/TaskBoard.vue`、`web/src/components/graph-console/GoalTaskPane.vue` 目前都没有父子任务的渲染逻辑——即便前三层都打通、数据库里真的出现了带 `parentTaskId` 的行,看板现在也画不出树形结构,只会当成平铺列表处理(或者干脆不显示子任务)。 + +--- + +## 设计决策(2026-08-31 定案,三选一各选完) + +| 问题 | 决定 | 理由 | +|------|------|------| +| 子任务挂在哪套机制上 | **goal → task 流水线里新建一套独立父子机制**(不复用 Agent Chat 的 `parent_task_id`) | 后者是为「人工派单、多轮对话」设计的语义,跟 org-graph 自动执行的生命周期(谁创建、何时终结)对不上,硬凑容易埋雷 | +| 子任务要不要独立开发+验收循环 | **先只拆验收,开发仍整体完成**——org-coder 一次性写完整个交付物,验收侧改成按子任务(验收条目)逐条判,而不是给总分 | 改动小得多,且先解决「验收颗粒度太粗」这个更痛的点;独立开发循环(每个子任务各起一个 work-graph run)留作后续再评估 | +| 什么粒度的 goal 该拆 | **由 task-planning agent 自己判断**,不写死规则 | 跟它现在「要不要拆多个 task」的判断逻辑同源,交给同一套"交付物/验收边界"标准,不额外发明一套阈值规则 | + +由这三条决定组合出的实现方向:**task-planning 判断"值不值得走细粒度验收"是一个粗粒度的开关,真正的子任务清单在 org-planner 写完 acceptance.json 之后才生成**——因为验收条目本身就是规划阶段的产出,task-planning 跑的时候 brd/prd/sdd 还不存在,没法预先知道要拆成哪几条。这意味着"判断要不要拆"和"生成子任务"是两个时间点、两个步骤: + +1. **task-planning(goal 创建时)**:新增一个判断维度——这个 task 的验收后续要不要走"逐条子任务"模式,写一个标记(例如 task 行上加 `acceptance_mode: 'holistic' | 'itemized'`,或等价字段),不生成任何子任务行。 +2. **org-planner 收尾(acceptance 产物写完之后,org-graph 流程内新增一步)**:如果上一步标记是 `itemized`,读验收条目,逐条 `createTask({parentTaskId, ...})` 生成子任务行,挂在同一个 goal 下。 +3. **`coder-validator`(work-graph 内既有节点,就是本 session 全程调试的那个 agent)**:判断逻辑从"给整个 task 一个总分"改成"遍历子任务逐条判",每条子任务各自 success/fail,父任务的终态由子任务汇总(全 success → success,否则看具体子任务)。 +4. **看板 UI**:`TaskBoard.vue` / `GoalTaskPane.vue` 加父子任务树形渲染,只读展示,不涉及交互改动。 + +## 实现细节决策(2026-08-31 定案) + +| 问题 | 决定 | 理由 | +|------|------|------| +| 标记字段怎么设计 | **`tasks` 表加 `acceptance_mode` 字段**,取值 `'holistic' \| 'itemized'`,命名风格对齐既有 `execution_mode`(manual/auto),由 task-planning 生成 task 行时一并写入 | 不额外发明隐式推导规则,创建时就能在库里直接看到这条 task 走哪种验收模式,调试/展示都直观 | +| 部分子任务失败时父任务终态 | **必须全部子任务 success,父任务才 success**;任一子任务 fail → 父任务 fail | 跟当前"一次性给总分、不合格就是不合格"的严谨程度保持一致,也呼应 [34 号文档](./34-validator-scored-without-real-deliverable.md) 关于验证不能打折的教训;`optional` 子验收项留作后续需要时再加,先不预留这层复杂度 | +| 子任务要不要复用现成的状态语义 | **复用**(pending/ready/running/success/fail/blocked 这套状态词汇) | 直接复用能白拿状态流转、看板渲染的既有约定,不用另起一套并行的状态语义 | + +> **落地时的订正**(技术设计阶段核实后发现):上一行"复用现成状态机"字面上写的是 `graph_run_nodes` 那套,但子任务本质是 `tasks` 表的行(带 `parent_task_id`),不是某个 work-graph run 内部的节点实例——它没有自己独立的 run。所以真正落地时复用的是 **`tasks.status` 这套既有枚举**(`queued/running/blocked/completed/failed/cancelled`),只是**沿用同一套状态词汇的精神**(有限状态、终态幂等、`settleNode`/`settleTaskFromRun` 那一类条件更新闸门),不是字面上去 `graph_run_nodes` 表插行。见下方技术实现计划 Phase 3。 + +> **另一处订正**:上面第 3 步原来写的是"org-validator",但技术设计阶段查证后发现 **`org-validator` 和 `coder-validator` 是两个不同目录、不同职责的独立 agent**——`org-validator` 是 org-graph 顶层节点(整体验收),`coder-validator` 是 org-coder 这一层 work-graph 内部的节点(就是本 session 全程调试的那个,`work-validator/v1` 输出格式)。真正产出逐项验收证据、需要按子任务逐条判的是 **`coder-validator`**,已在上面订正。 + +至此,43 号文档的方案层面已经全部定案。下面是核实过具体代码形态后的技术实现计划。 + +--- + +## 技术实现计划(2026-08-31,代码形态已核实,未开工) + +### Phase 0 · Schema + +- `tasks` 表加 `acceptance_mode` 列,`CHECK (acceptance_mode IN ('holistic','itemized')) DEFAULT 'holistic'`——语法上照抄 `execution_mode` 已有的 CHECK 写法(`work-graph-db.js:533`),迁移方式照抄 `parent_task_id` 自己当年落地时用的 `hasColumn` 判断 + `ALTER TABLE` 兜底那一段(`work-graph-db.js:626-627`),同一个文件里已有两份现成模板可抄,不用发明新的迁移写法。 +- `createTask({...})` 签名(`work-graph-db.js:1861-1873`)加 `parentTaskId`、`acceptanceMode` 两个参数,INSERT 列表(`work-graph-db.js:1902-1907`)跟着加。`parent_task_id` 列本身早就存在(是 Agent Chat 那条派单流程在用),这里只是**第一次让 goal→task 流水线自己也写这一列**——顺手确认一下现有读它的 `task-service.js:66-81` 那段任务树构建逻辑,会不会因为多了一类新来源的父子行而产生非预期的合并/展示(大概率不会,因为按 `company_id`/`goalId` 天然分域,但落地前应该跑一次它的既有测试)。 +- 守卫测试:图流水线创建的顶层 task(`insertTasksForGoal` 路径)`parent_task_id` 仍是 `NULL`;子任务的 `parent_task_id` 非空且指向父任务、`acceptance_mode` 恒为 `'holistic'`(子任务不允许再递归拆一层)。 + +### Phase 1 · task-planning 判断 `acceptance_mode` + +`task-planning` 目前是纯"回复 JSON 代码块"模式(`system/agents/task-planning/tools.md:5`),产出 4 字段 `[{seq, title, description, depends_on}]`(`agent.md:120-142`),而且**硬规则 5**(`agent.md:10`)明确禁止 LLM 多写字段——"服务端生成,你写了会被当成脏字段打回",`mode`/`priority`/`budget` 这些运营字段都是 `company.js:1396-1429 buildTaskPlans()` 在服务端按 goal 级默认值统一注入的(`company.js:1421-1423`),不是 LLM 决定的。 + +`acceptance_mode` 要不要拆本质上是一次语义判断(跟"要不要拆多个 task"用的是同一种判断力,这正是当初选"由 agent 自己判断、不写死规则"的理由),**没法**像 `mode`/`priority`/`budget` 那样简单地由服务端注入默认值——所以这里必须**显式在硬规则 5 上开一个例外**: + +- `agent.md` 的 JSON schema 从 4 字段扩到 5 字段,加 `acceptance_mode`,并在硬规则 5 旁边补一句明确的例外说明(为什么这一个字段跟其它运营字段不同,避免以后被"这些字段都是服务端生成"的惯例带偏、被误当脏字段清掉)。 +- `buildTaskPlans()`(`company.js:1396-1429`)改成**透传** LLM 给的 `acceptance_mode`,而不是像其它字段那样用服务端默认值覆盖;LLM 没给这个字段时兜底 `'holistic'`(保守,不引入新行为,除非规划阶段明确判断要拆)。 + +### Phase 2 · org-planner 收尾生成子任务(⚠️ 有一个真实风险要先处理) + +org-planner 的 4 阶段 FSM(`system/agents/org-planner/tools.md:19-23`,`node bin/lemon.js task transition --phase {plan|execute|verify}`)产出的验收清单叫 **`acceptance.md`**(不是 `.json`)——Markdown 表格 `| # | 验收项 | 判定方式 | pass 条件 |`(`tools.md:44-50`),一行一条验收项,是子任务清单最自然的数据源。这**不是**同名的 R-19-B `acceptance.json` 契约(`goal-contract.js:76` 定义的 `benchmark|test_data|test_script` 三段式,由另外三个独立的 `goal-benchmark`/`goal-test-data`/`goal-test-script` 节点产出)——两者名字像、来源和用途完全不同,写代码时一定要认清楚读的是哪一份。 + +**⚠️ 已知风险,动工前必须先确认**:本 session 全程调试的 snakegame-co 这条真实任务,`acceptance.md` 和 `acceptance.json` **实际上都没有被真正当成验收依据用**——`coder-validator` 最终判定用的是自己临时写的一套 Playwright 测试(A.1-A.5),`goal-contract.js` 那份 `acceptance.json` 甚至报了 `note:"goal-contract.js cannot parse index.html as JSON...treat as no task-level contract"`,被当场判定"这个任务没有 task 级契约"直接跳过。如果子任务清单要建在 `acceptance.md` 之上,得先跑一次真实回归确认它确实会被稳定产出、格式跟文档描述一致——不能假设它已经可靠,这跟 33 号文档"建立在一个从来没人真正产出过的 `kind='log'` 之上"是同一类风险,值得先花一次真跑单独验证,而不是直接在上面盖楼。 + +如果验证通过,新增步骤:org-planner 的 `verify` 阶段落盘 `acceptance.md` 之后,插入一步"物化子任务"——仅当父任务 `acceptance_mode==='itemized'` 时,逐行读表格,为每一条验收项调用 `createTask({parentTaskId: 当前 task.id, acceptanceMode:'holistic', title: 该行"验收项"文本, description: 该行"判定方式"+"pass 条件", ...})`。 + +两个待确认的实现细节: + +- 这一步该是给 org-planner 加一个新工具(让它自己触发"物化子任务"),还是挂在服务端某个 hook 上(`verify` 阶段落盘后自动触发,agent 无感)?倾向后者——子任务生成是纯数据搬运,不需要占用一次 agent 调用去决定"要不要生成"(`acceptance_mode` 这个语义判断已经在 Phase 1 做完了),机械地转换 Markdown 表格到 task 行不需要 LLM 参与。 +- 本次调研**没能确认** org-planner"完成→进入 org-coder"具体由哪个信号触发(`verify` transition 本身,还是 `tools.md:59-65` 那个额外的 `task/report` 调用)——插入新步骤前必须先查清楚,避免插错时机打断现有的阶段流转。 + +### Phase 3 · coder-validator 改成逐条判 + 回写子任务状态 + +关键发现:`coder-validator` 现在产出的 `checks[]`/`verdict` 结构化 JSON(就是本 session 里 `attempt-2.json` 那种形状),**目前没有任何服务端代码解析它**——`graph-engine.js` 判定这个节点成功与否只看 executor 返回的顶层 `status` 字段,`checks[]` 纯粹是给人看的审计文本,`goal-contract.js` 走的是完全独立的另一条 CLI 路径(自己生成 `verdict_id`),跟 `coder-validator` 的输出没有关系。 + +所以"逐条判"要真正影响子任务状态,**不能只是把 agent 提示词写得更细**——必须新增一个真实的工具调用:`coder-validator` 每判完一条验收项,调用一次新工具(例如 `report-subtask-result`),带上子任务 id + success/fail,服务端据此走一次 `tasks` 表的条件更新(复用 `settleTaskFromRun` 同款的"终态幂等闸门"思路:已经是终态的子任务不能被覆盖)。这跟本 repo 一贯的做法一致——状态改变走真实工具调用,不解析自由文本 JSON(`checks[]` 格式在 `org-validator`/`coder-validator`/`planner-validator`/`testdata-validator`/`benchmark-validator` 五个 validator 之间只是"约定俗成、没有强制"的 `work-validator/v1`,不是哪里都强制校验的 schema,解析它不安全)。 + +父任务终态:全部子任务 `completed` → 父任务按现有 `settleNode` 逻辑判 success;任一子任务 `failed` → 父任务 fail(Phase 0 就已经决定)。 + +### Phase 4 · 看板树形渲染 + +- `web/src/components/FileNode.vue` 是现成的自递归 ` + + diff --git a/web/src/components/task-board/TaskTable.vue b/web/src/components/task-board/TaskTable.vue new file mode 100644 index 0000000..df96297 --- /dev/null +++ b/web/src/components/task-board/TaskTable.vue @@ -0,0 +1,218 @@ + + + + + + diff --git a/web/src/views/CompanyDashboard.vue b/web/src/views/CompanyDashboard.vue index 18c3c61..64393c9 100644 --- a/web/src/views/CompanyDashboard.vue +++ b/web/src/views/CompanyDashboard.vue @@ -123,6 +123,44 @@ + +
+
+

📁 目标代码目录

+
+

+ 配了之后,这家公司所有任务的交付物代码都固定落在这个目录,不再逐任务分配临时沙盒 + (类似 Claude Code 选工作目录)。留空则维持默认的沙盒模式。 +

+
+
+ 目标目录(绝对路径) + +
+
+ 模式 + +
+
+
+ + {{ targetDirError }} + + 当前生效:{{ selectedCompany.target_dir }}({{ selectedCompany.target_mode }}) + +
+
+
@@ -739,7 +777,7 @@ @@ -104,7 +137,7 @@ function goToDetail({ goalId, taskId }) { .to-console { text-decoration: none; color: #2b6cb0; font-size: 12px; font-weight: 600; } .to-console:hover { text-decoration: underline; } -.body { flex: 1 1 auto; overflow-y: auto; padding: 16px 24px; max-width: 900px; width: 100%; margin: 0 auto; box-sizing: border-box; } +.body { flex: 1 1 auto; overflow-y: auto; padding: 16px 24px; box-sizing: border-box; } @media (max-width: 700px) { .body { padding: 12px; }