Files
memind/goal-run-service.test.mjs
T
john 666db0b939 feat(goal-run): add multi-checkpoint goal orchestration with H5 and admin surfaces.
Persist goal runs in MySQL, bind agent runs to checkpoints, expose awaiting-approval
UX in chat, and add admin inspection routes with local verify scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 17:03:16 +08:00

335 lines
12 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { createGoalRunService, ensureGoalRunSchema } from './goal-run-service.mjs';
test('ensureGoalRunSchema creates goal tables and agent run columns', async () => {
const queries = [];
const pool = {
async query(sql) {
queries.push(String(sql));
return [[]];
},
};
const columnExists = async (table, column) => {
if (table === 'h5_agent_runs' && ['goal_run_id', 'goal_checkpoint_id'].includes(column)) {
return false;
}
return true;
};
const indexExists = async () => false;
await ensureGoalRunSchema(pool, { columnExists, indexExists });
assert.ok(queries.some((sql) => sql.includes('h5_goal_runs')));
assert.ok(queries.some((sql) => sql.includes('h5_goal_checkpoints')));
assert.ok(queries.some((sql) => sql.includes('goal_run_id')));
});
test('createGoalRun persists goal and default checkpoint', async () => {
const store = { goals: new Map(), checkpoints: new Map() };
const pool = {
async query(sql, params) {
if (String(sql).startsWith('INSERT INTO h5_goal_runs')) {
store.goals.set(params[0], {
id: params[0],
user_id: params[1],
title: params[2],
intent_summary: params[3],
status: 'active',
source_channel: params[5],
current_checkpoint_id: null,
context_json: params[8],
memory_snapshot_json: params[9],
created_at: params[10],
updated_at: params[11],
completed_at: null,
priority: 5,
source_session_id: params[6],
source_message_id: params[7],
});
return [{ affectedRows: 1 }];
}
if (String(sql).startsWith('INSERT INTO h5_goal_checkpoints')) {
store.checkpoints.set(params[0], {
id: params[0],
goal_run_id: params[1],
sequence: params[2],
title: params[3],
description: params[4],
status: params[5],
agent_run_id: null,
output_summary: null,
output_artifact_ids: null,
user_feedback: null,
approved_at: null,
created_at: params[6],
updated_at: params[7],
started_at: null,
completed_at: null,
});
return [{ affectedRows: 1 }];
}
if (String(sql).startsWith('UPDATE h5_goal_runs SET current_checkpoint_id')) {
const goal = store.goals.get(params[2]);
if (goal) goal.current_checkpoint_id = params[0];
return [{ affectedRows: 1 }];
}
if (String(sql).includes('FROM h5_goal_runs WHERE id =')) {
const goal = store.goals.get(params[0]);
return [[goal].filter(Boolean)];
}
if (String(sql).includes('FROM h5_goal_checkpoints WHERE goal_run_id')) {
return [[...store.checkpoints.values()].filter((item) => item.goal_run_id === params[0])];
}
if (String(sql).includes('FROM h5_goal_runs') && String(sql).includes('status IN')) {
return [[...store.goals.values()].filter((item) => item.user_id === params[0])];
}
throw new Error(`Unexpected query: ${sql}`);
},
};
const service = createGoalRunService({ pool, now: () => 1000 });
const created = await service.createGoalRun({
userId: 'user-1',
title: '准备下季度产品规划',
intentSummary: '收集竞品信息并输出规划草案',
sourceSessionId: 'session-1',
});
assert.equal(created.title, '准备下季度产品规划');
assert.equal(created.checkpoints.length, 1);
assert.equal(created.checkpoints[0].title, '启动');
assert.equal(created.currentCheckpointId, created.checkpoints[0].id);
const listed = await service.listGoalRuns({ userId: 'user-1' });
assert.equal(listed.length, 1);
});
test('startNextCheckpoint and onAgentRunCompleted advance goal lifecycle', async () => {
const store = {
goals: new Map([
['goal-1', {
id: 'goal-1',
user_id: 'user-1',
title: '长期任务',
intent_summary: '分阶段完成',
status: 'active',
source_channel: 'h5',
current_checkpoint_id: 'cp-1',
context_json: null,
memory_snapshot_json: null,
created_at: 1000,
updated_at: 1000,
completed_at: null,
priority: 5,
source_session_id: null,
source_message_id: null,
}],
]),
checkpoints: new Map([
['cp-1', {
id: 'cp-1',
goal_run_id: 'goal-1',
sequence: 1,
title: '启动',
description: null,
status: 'pending',
agent_run_id: null,
output_summary: null,
output_artifact_ids: null,
user_feedback: null,
approved_at: null,
created_at: 1000,
updated_at: 1000,
started_at: null,
completed_at: null,
}],
]),
runs: new Map([
['run-1', {
id: 'run-1',
goal_run_id: 'goal-1',
goal_checkpoint_id: 'cp-1',
status: 'succeeded',
}],
]),
};
const pool = {
async query(sql, params) {
const text = String(sql);
if (text.includes('FROM h5_agent_runs') && text.includes('WHERE id = ?')) {
return [[store.runs.get(params[0])].filter(Boolean)];
}
if (text.startsWith('UPDATE h5_goal_checkpoints') && text.includes("SET status = 'running'")) {
const checkpoint = store.checkpoints.get(params[3]);
checkpoint.status = 'running';
checkpoint.agent_run_id = params[0];
checkpoint.started_at = params[1];
checkpoint.updated_at = params[2];
return [{ affectedRows: 1 }];
}
if (text.startsWith('UPDATE h5_goal_runs') && text.includes("status = 'active'")) {
const goal = store.goals.get(params[2]);
goal.current_checkpoint_id = params[0];
goal.status = 'active';
goal.updated_at = params[1];
return [{ affectedRows: 1 }];
}
if (text.includes("status = 'approved'") && text.includes('h5_goal_checkpoints')) {
const checkpoint = store.checkpoints.get(params[4]);
checkpoint.status = 'approved';
checkpoint.output_summary = params[0];
checkpoint.completed_at = params[1];
checkpoint.approved_at = params[2];
checkpoint.updated_at = params[3];
return [{ affectedRows: 1 }];
}
if (text.includes('FROM h5_goal_checkpoints') && text.includes("status IN ('pending', 'failed')")) {
return [[]];
}
if (text.includes("SET status = 'completed'") && text.includes('h5_goal_runs')) {
const goal = store.goals.get(params[3]);
goal.status = 'completed';
goal.completed_at = params[2];
goal.updated_at = params[2];
return [{ affectedRows: 1 }];
}
if (text.includes('FROM h5_goal_runs WHERE id = ? AND user_id = ?')) {
const goal = store.goals.get(params[0]);
if (!goal || goal.user_id !== params[1]) return [[]];
return [[goal]];
}
if (text.includes('FROM h5_goal_checkpoints WHERE goal_run_id = ?')) {
return [[...store.checkpoints.values()].filter((item) => item.goal_run_id === params[0])];
}
if (text.includes('FROM h5_goal_checkpoints c') && text.includes('INNER JOIN')) {
const checkpoint = store.checkpoints.get(params[0]);
const goal = store.goals.get(checkpoint?.goal_run_id);
if (!checkpoint || !goal) return [[]];
return [[{ ...checkpoint, user_id: goal.user_id, goal_status: goal.status }]];
}
throw new Error(`Unexpected query: ${sql}`);
},
};
const service = createGoalRunService({ pool, now: () => 2000 });
const started = await service.startNextCheckpoint({ userId: 'user-1', goalRunId: 'goal-1' });
assert.equal(started.checkpointId, 'cp-1');
assert.equal(store.checkpoints.get('cp-1').status, 'running');
const completed = await service.onAgentRunCompleted({
agentRunId: 'run-1',
status: 'succeeded',
outputSummary: '完成第一阶段',
});
assert.equal(completed.handled, true);
assert.equal(completed.goalCompleted, true);
assert.equal(store.checkpoints.get('cp-1').status, 'approved');
assert.equal(store.goals.get('goal-1').status, 'completed');
});
test('onAgentRunCompleted requires approval between multi-checkpoint stages', async () => {
const store = {
goals: new Map([
['goal-2', {
id: 'goal-2',
user_id: 'user-1',
title: '多阶段任务',
intent_summary: '两阶段',
status: 'active',
source_channel: 'h5',
current_checkpoint_id: 'cp-1',
context_json: null,
memory_snapshot_json: null,
created_at: 1000,
updated_at: 1000,
completed_at: null,
priority: 5,
source_session_id: null,
source_message_id: null,
}],
]),
checkpoints: new Map([
['cp-1', {
id: 'cp-1',
goal_run_id: 'goal-2',
sequence: 1,
title: '调研',
description: null,
status: 'running',
agent_run_id: 'run-1',
output_summary: null,
output_artifact_ids: null,
user_feedback: null,
approved_at: null,
created_at: 1000,
updated_at: 1000,
started_at: 1000,
completed_at: null,
}],
['cp-2', {
id: 'cp-2',
goal_run_id: 'goal-2',
sequence: 2,
title: '输出草案',
description: null,
status: 'pending',
agent_run_id: null,
output_summary: null,
output_artifact_ids: null,
user_feedback: null,
approved_at: null,
created_at: 1000,
updated_at: 1000,
started_at: null,
completed_at: null,
}],
]),
runs: new Map([
['run-1', {
id: 'run-1',
goal_run_id: 'goal-2',
goal_checkpoint_id: 'cp-1',
status: 'succeeded',
}],
]),
};
const pool = {
async query(sql, params) {
const text = String(sql);
if (text.includes('FROM h5_agent_runs') && text.includes('WHERE id = ?')) {
return [[store.runs.get(params[0])].filter(Boolean)];
}
if (text.includes('COUNT(*) AS total')) {
return [[{ total: store.checkpoints.size }]];
}
if (text.includes('FROM h5_goal_checkpoints') && text.includes("status IN ('pending', 'failed')")) {
return [[store.checkpoints.get('cp-2')].filter((item) => ['pending', 'failed'].includes(item.status))];
}
if (text.includes("SET status = 'awaiting_approval'")) {
store.checkpoints.get(params[3]).status = 'awaiting_approval';
return [{ affectedRows: 1 }];
}
if (text.includes("SET status = 'awaiting_user'")) {
store.goals.get('goal-2').status = 'awaiting_user';
return [{ affectedRows: 1 }];
}
if (text.includes('FROM h5_goal_checkpoints c') && text.includes('INNER JOIN')) {
const checkpoint = store.checkpoints.get(params[0]);
const goal = store.goals.get(checkpoint?.goal_run_id);
return [[{ ...checkpoint, user_id: goal.user_id, goal_status: goal.status }]];
}
throw new Error(`Unexpected query: ${sql}`);
},
};
const service = createGoalRunService({ pool, now: () => 2000 });
const completed = await service.onAgentRunCompleted({
agentRunId: 'run-1',
status: 'succeeded',
outputSummary: '调研完成',
});
assert.equal(completed.handled, true);
assert.equal(completed.awaitingApproval, true);
assert.equal(store.goals.get('goal-2').status, 'awaiting_user');
assert.equal(store.checkpoints.get('cp-1').status, 'awaiting_approval');
assert.equal(store.checkpoints.get('cp-2').status, 'pending');
});