/** * Unit Tests — subgraph 节点结算 → workgraph-planning 自己的 memory.md 桥接(36 号文档追加) * * 背景:36 号文档真实回归发现 `workgraph-planning` 的图形状裁剪判据现在只有"允许裁剪" * 没有"具体判据"(单页静态网站任务原样照抄 planner.json 全部 5 节点,一个都没砍),而它 * 自己完全没有可积累的历史——阶段一的 `recordNodeOutcomeMemory` 桥接明确排除了 subgraph * 节点(那是给 agent_call 节点记的,subgraph 节点没有对应的"真实执行"概念)。 * * 本文件锁的是补上的那半:subgraph 节点结算(= 它规划出的子 work graph 最终收敛)时, * 把"这次规划的图跑得怎么样"记进 `workgraph-planning` 自己的 memory.md(不是宿主 org node * 的 `ctx.systemAgentId`,那是宿主自己的身份,不是真正执行规划的 agent——规划本体全局唯一)。 * * ① 真实规划(`planWorkGraph === defaultPlanWorkGraph`)+ success → 记 "success" * ② 真实规划 + fail(reason) → 记 "fail: " * ③ 注入假 planWorkGraph(全仓库现有 subgraph 测试的常态,见 graph-engine-revive-parent.test.js * 的 buildParentWithFailingChild)→ 不记,不能因为这次改动就把假地址写进真实 * system/agents/workgraph-planning/ 目录 * * ①②借道直调(不必真起 CLI 触发 planWorkGraph===defaultPlanWorkGraph 才能验证格式化), * ③走完整的 subgraph 结算流程(复用 graph-engine-revive-parent.test.js 的建图手法), * 确认现有"全仓库 subgraph 测试都注入假 planWorkGraph"这条约定下这个新桥接确实按兵不动。 */ import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'; import fs from 'fs'; import os from 'os'; import path from 'path'; describe('subgraph 节点结算桥接进 workgraph-planning 的 memory.md(36 号文档)', () => { let wdb; let engine; let memoryService; let runsDir; const TEST_DB_PATH = '/tmp/test-graph-engine-workgraph-planning-memory.sqlite'; let origAppendToMemoryMd; let calls; beforeAll(() => { for (const f of [TEST_DB_PATH, `${TEST_DB_PATH}-shm`, `${TEST_DB_PATH}-wal`]) { try { fs.unlinkSync(f); } catch {} } process.env.GRAPH_DB_PATH = TEST_DB_PATH; runsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'graph-wgplan-memory-runs-')); process.env.GRAPH_RUNS_DIR = runsDir; for (const m of [ '../../services/graph-db', '../../services/graph-service', '../../services/org-graph-service', '../../services/work-graph-db', '../../services/graph-run-sse', '../../services/graph-engine', '../../services/memory-service', ]) { delete require.cache[require.resolve(m)]; } wdb = require('../../services/work-graph-db'); engine = require('../../services/graph-engine'); memoryService = require('../../services/memory-service'); origAppendToMemoryMd = memoryService.appendToMemoryMd; }); afterAll(() => { for (const f of [TEST_DB_PATH, `${TEST_DB_PATH}-shm`, `${TEST_DB_PATH}-wal`]) { try { fs.unlinkSync(f); } catch {} } delete process.env.GRAPH_DB_PATH; try { fs.rmSync(runsDir, { recursive: true, force: true }); } catch {} delete process.env.GRAPH_RUNS_DIR; }); beforeEach(() => { const db = wdb.getDb(); db.pragma('foreign_keys = OFF'); db.exec(` DELETE FROM graph_run_loop_records; DELETE FROM graph_run_nodes; DELETE FROM graph_runs; DELETE FROM work_graph_edges; DELETE FROM work_graph_nodes; DELETE FROM work_graphs; DELETE FROM system_agents; `); db.pragma('foreign_keys = ON'); engine.setAgentExecutor(null); engine.setPlanWorkGraph(null); memoryService.appendToMemoryMd = origAppendToMemoryMd; }); function spyMemory() { calls = []; memoryService.appendToMemoryMd = (...args) => { calls.push(args); }; } it('① 真实规划(直调,不必起真 CLI)+ success → 记一行 "success"', () => { spyMemory(); engine.recordWorkgraphPlanningMemory('co-wgplan-1', 'org-coder', 'success', null); expect(calls.length).toBe(1); const [addr, task, result] = calls[0]; expect(addr).toBe('co-wgplan-1:workgraph-planning'); expect(task).toContain('org-coder'); expect(result).toBe('success'); }); it('② 真实规划(直调)+ fail(reason) → 记一行 "fail: "', () => { spyMemory(); engine.recordWorkgraphPlanningMemory('co-wgplan-2', 'org-validator', 'fail', 'impl_bug'); expect(calls.length).toBe(1); const [addr, , result] = calls[0]; expect(addr).toBe('co-wgplan-2:workgraph-planning'); expect(result).toBe('fail: impl_bug'); }); // ③:复用 graph-engine-revive-parent.test.js 的建图手法——父 run 一个 subgraph 节点 // (宿主 org node)+ 一张会失败/成功的子图,全程注入假 setPlanWorkGraph(全仓库 subgraph // 测试的常态:defaultPlanWorkGraph 会真的起 CLI 调 workgraph-planning)。 function ensureAgents(ids) { const db = wdb.getDb(); const now = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); } it('③ 注入假 planWorkGraph(全仓库现有 subgraph 测试的常态)→ 不记,不污染真实目录', async () => { spyMemory(); const companyId = 'co-wgplan-3'; ensureAgents(['org-coder', 'wg-only']); const childG = wdb.createWorkGraph({ companyId, nodes: ['wg-only'], edges: [] }); expect(childG.errors || []).toEqual([]); const parentG = wdb.createWorkGraph({ companyId, nodes: [{ nodeName: 'org-coder', systemAgentId: 'org-coder', nodeKind: 'subgraph' }], edges: [], }); expect(parentG.errors || []).toEqual([]); const parentRun = wdb.createRun({ graphKind: 'work', graphId: parentG.id, companyId, executionMode: 'manual' }); engine.markRunReady(parentRun.id); engine.startRun(parentRun.id, { mode: 'manual' }); engine.setPlanWorkGraph(() => ({ workGraphId: childG.id })); engine.setAgentExecutor(async () => ({ status: 'success', output: '子节点成功' })); for (let i = 0; i < 12; i += 1) { const r = await engine.advanceTree(parentRun.id); if (!r || r.ok === false) break; } expect(wdb.getRunState(parentRun.id).run.run_status).toBe('completed'); expect(calls.length).toBe(0); }); });