task-dispatch-store.test.js
4.8 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
/**
* 批 5 第 3 步:`/task/dispatch` 的存储从 system/tasklog/*.json 换成 tasks 表。
*
* **这一步唯一不能坏的是契约**:`/task/dispatch` 与 `/task/report` 的 URL、请求体、响应体
* 印在 `packages/agent-core/base/common/tools-base.md` 里,是所有 agent 的工具说明 ——
* 改一个字等于同时改所有 agent 的提示词。所以这份用例盯的是「形状没变」,不是「实现对不对」。
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createRequire } from 'module';
import fs from 'fs';
import os from 'os';
import path from 'path';
const require_ = createRequire(import.meta.url);
describe('派单存储换成 tasks 表', () => {
let wdb, app, srv, base;
beforeAll(async () => {
process.env.GRAPH_DB_PATH = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'dispatch-store-')), 'graph.sqlite');
for (const m of ['../../services/work-graph-db', '../../services/graph-db', '../../routes/agent-chat']) {
delete require_.cache[require_.resolve(m)];
}
wdb = require_('../../services/work-graph-db');
const express = require_('express');
app = express();
app.use(express.json());
app.use('/api/agent', require_('../../routes/agent-chat'));
await new Promise((r) => { srv = app.listen(0, '127.0.0.1', r); });
base = `http://127.0.0.1:${srv.address().port}`;
});
afterAll(() => { try { srv?.close(); } catch {} });
const post = async (p, body) => {
const r = await fetch(base + p, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
});
return { status: r.status, body: await r.json().catch(() => ({})) };
};
/** 用真实存在的两个本体(端点会校验目录存在) */
const FROM = 'ceo';
const TO = 'qa-judge';
it('派单落进 tasks 表,且响应体是老形态(agent 提示词里印着它)', async () => {
const r = await post('/api/agent/task/dispatch', {
from: FROM, to: TO, task: '守卫用例:形态检查', priority: 'P1',
goalId: 'O-900', nodeId: 'O-900.1.1', autoExecute: false,
});
expect(r.status).toBe(200);
// 响应体的字段一个都不能少 —— 少了就是改了所有 agent 的工具说明
for (const k of ['success', 'taskId', 'from', 'to', 'task', 'priority', 'autoExecute', 'message']) {
expect(r.body).toHaveProperty(k);
}
expect(r.body.success).toBe(true);
const row = wdb.getDb().prepare('SELECT * FROM tasks WHERE id = ?').get(r.body.taskId);
expect(row).toBeTruthy();
expect(row.executor).toBe('agent'); // 调度器据此跳过它(图任务才排队)
expect(row.assignee_agent_id).toBe(TO);
expect(row.created_by).toBe(FROM);
expect(row.status).toBe('running'); // meta 的 dispatched ↔ 表的 running
// 每条派单自带一个 goal:goal_id 是 NOT NULL 真外键,派单没有「人提的需求」那一层
const goal = wdb.getDb().prepare('SELECT * FROM goals WHERE id = ?').get(row.goal_id);
expect(goal.title).toContain(FROM);
expect(goal.okr_node_id).toBe('O-900.1.1'); // objectiveId 由它的首段推导,不单独存
});
it('去重仍然拦得住,且 409 的响应体形态没变', async () => {
const body = { from: FROM, to: TO, task: '守卫用例:去重', autoExecute: false };
const first = await post('/api/agent/task/dispatch', body);
expect(first.status).toBe(200);
const dup = await post('/api/agent/task/dispatch', body);
expect(dup.status).toBe(409);
for (const k of ['success', 'error', 'existingTaskId', 'existingTask', 'message']) {
expect(dup.body).toHaveProperty(k);
}
expect(dup.body.existingTaskId).toBe(first.body.taskId);
});
it('汇报回路:状态 / summary / 完成时间都落到表上', async () => {
const d = await post('/api/agent/task/dispatch', {
from: FROM, to: TO, task: '守卫用例:汇报回路', autoExecute: false,
});
const rep = await post('/api/agent/task/report', {
taskId: d.body.taskId, from: TO, to: FROM, status: 'done', summary: '干完了',
});
expect(rep.status).toBe(200);
const row = wdb.getDb().prepare('SELECT * FROM tasks WHERE id = ?').get(d.body.taskId);
expect(row.status).toBe('completed'); // meta 的 done ↔ 表的 completed
expect(row.summary).toBe('干完了');
expect(row.finished_at).toBeTruthy();
});
it('不再往 system/tasklog/ 写新文件', async () => {
const dir = path.resolve(process.cwd(), 'system', 'tasklog');
const before = fs.existsSync(dir) ? fs.readdirSync(dir).length : 0;
await post('/api/agent/task/dispatch', { from: FROM, to: TO, task: '守卫用例:不写文件', autoExecute: false });
const after = fs.existsSync(dir) ? fs.readdirSync(dir).length : 0;
expect(after).toBe(before);
});
});