graph-engine-workgraph-planning-memory.test.js
6.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/**
* 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: <reason>"
* ③ 注入假 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: <reason>"', () => {
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);
});
});