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>
This commit is contained in:
john
2026-08-01 17:03:16 +08:00
parent 43bc8bbc2b
commit 666db0b939
47 changed files with 4417 additions and 4 deletions
+6
View File
@@ -1,5 +1,6 @@
import { sessionCookie } from '../auth.mjs';
import { isDatabaseConfigured } from '../db.mjs';
import { isGoalRunEnabledForUser } from '../goal-run-intent.mjs';
import {
exchangeMiniProgramCode,
loadWechatMiniappConfig,
@@ -17,6 +18,9 @@ export function attachPortalCoreAuthRoutes({
isSecureRequest,
resolveSkillRuntimeForClient = async () => null,
resolveAgentCodeRunForClient = async () => null,
resolveGoalRunForClient = async (userId) => ({
enabled: isGoalRunEnabledForUser(userId, process.env),
}),
getPlazaSeo = () => null,
plazaClientIp = (req) => req.ip,
logger = console,
@@ -59,6 +63,7 @@ export function attachPortalCoreAuthRoutes({
await resolveSkillRuntimeForClient();
const agentCodeRun =
await resolveAgentCodeRunForClient(me.id);
const goalRun = await resolveGoalRunForClient(me.id);
return res.json({
authenticated: true,
user: me,
@@ -69,6 +74,7 @@ export function attachPortalCoreAuthRoutes({
unrestricted: capabilityState.unrestricted,
skillRuntime,
agentCodeRun,
goalRun,
});
} catch (error) {
logger.error(
+5
View File
@@ -107,6 +107,10 @@ function createSetup(overrides = {}) {
calls.push(['agent-code-run', userId]);
return { enabled: true, userId };
},
async resolveGoalRunForClient(userId) {
calls.push(['goal-run', userId]);
return { enabled: true };
},
getPlazaSeo: () => plazaSeo,
plazaClientIp: () => '203.0.113.5',
logger: {
@@ -212,6 +216,7 @@ test('returns multi-user status and preserves capability projection', async () =
unrestricted: false,
skillRuntime: { enabled: true },
agentCodeRun: { enabled: true, userId: 'user-1' },
goalRun: { enabled: true },
});
});
@@ -2,6 +2,7 @@ import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { createAgentRunGateway } from '../agent-run-gateway.mjs';
import { createGoalRunService } from '../goal-run-service.mjs';
import { isDirectChatSessionId } from '../direct-chat-service.mjs';
import {
cancelSessionActiveRequest,
@@ -236,6 +237,7 @@ export function bootstrapPortalGatewayServices({
'dev',
).trim() || 'dev',
};
const goalRunService = createGoalRunService({ pool });
const agentRunGateway = createAgentRunGatewayFn({
pool,
userAuth,
@@ -247,6 +249,7 @@ export function bootstrapPortalGatewayServices({
chatIntentRouter,
sessionSnapshotService,
conversationMemoryService,
goalRunService,
observeWorkflowRun: workflowShadowObserver,
observeWorkflowValidation:
workflowShadowObserver?.observeValidation ?? null,
@@ -363,6 +366,7 @@ export function bootstrapPortalGatewayServices({
tkmindProxy,
toolGateway,
agentRunGateway,
goalRunService,
agentRunRecoveryTimer,
validateRunDeliverables,
};
+239
View File
@@ -0,0 +1,239 @@
import { isGoalRunEnabledForUser, buildGoalContinueUserMessage } from '../goal-run-intent.mjs';
import { resolveGoalBindingForAgentRun } from '../goal-run-resolve.mjs';
import crypto from 'node:crypto';
function assertRouter(api) {
if (
!api ||
typeof api.get !== 'function' ||
typeof api.post !== 'function' ||
typeof api.delete !== 'function'
) {
throw new Error(
'attachPortalGoalRunRoutes requires an Express-compatible router',
);
}
}
function goalRunUnavailable(res) {
return res.status(503).json({ message: 'Goal Run 功能未启用' });
}
function goalRunForbidden(res) {
return res.status(403).json({ message: '当前用户未开启 Goal Run' });
}
function parseStatuses(raw) {
const value = String(raw ?? '').trim();
if (!value) return ['active', 'awaiting_user', 'paused'];
return value.split(',').map((item) => item.trim()).filter(Boolean);
}
export function attachPortalGoalRunRoutes(
api,
{
getGoalRunService = () => null,
getAgentRunGateway = () => null,
env = process.env,
} = {},
) {
assertRouter(api);
function ensureGoalRunAccess(req, res) {
const service = getGoalRunService();
if (!service) {
goalRunUnavailable(res);
return null;
}
if (!isGoalRunEnabledForUser(req.currentUser?.id, env)) {
goalRunForbidden(res);
return null;
}
return service;
}
api.post('/goals', async (req, res) => {
try {
const service = ensureGoalRunAccess(req, res);
if (!service) return;
const title = String(req.body?.title ?? '').trim();
const intentSummary = String(
req.body?.intentSummary ?? req.body?.intent_summary ?? '',
).trim();
if (!title || !intentSummary) {
return res.status(400).json({ message: '缺少 title 或 intentSummary' });
}
const checkpoints = Array.isArray(req.body?.checkpoints) ? req.body.checkpoints : [];
const goal = await service.createGoalRun({
userId: req.currentUser.id,
title,
intentSummary,
sourceChannel: String(req.body?.sourceChannel ?? req.body?.source_channel ?? 'api'),
sourceSessionId: req.body?.sourceSessionId ?? req.body?.source_session_id ?? null,
sourceMessageId: req.body?.sourceMessageId ?? req.body?.source_message_id ?? null,
checkpoints,
context: req.body?.context ?? null,
memorySnapshot: req.body?.memorySnapshot ?? req.body?.memory_snapshot ?? null,
});
return res.status(201).json({ goal });
} catch (err) {
return res.status(500).json({
message: err instanceof Error ? err.message : '创建目标失败',
});
}
});
api.get('/goals', async (req, res) => {
try {
const service = ensureGoalRunAccess(req, res);
if (!service) return;
const goals = await service.listGoalRuns({
userId: req.currentUser.id,
statuses: parseStatuses(req.query?.status ?? req.query?.statuses),
limit: Number(req.query?.limit ?? 20),
});
return res.json({ goals });
} catch (err) {
return res.status(500).json({
message: err instanceof Error ? err.message : '读取目标列表失败',
});
}
});
api.get('/goals/:goalRunId', async (req, res) => {
try {
const service = ensureGoalRunAccess(req, res);
if (!service) return;
const goal = await service.getGoalRun({
userId: req.currentUser.id,
goalRunId: req.params.goalRunId,
});
if (!goal) {
return res.status(404).json({ message: '目标不存在' });
}
return res.json({ goal });
} catch (err) {
return res.status(500).json({
message: err instanceof Error ? err.message : '读取目标详情失败',
});
}
});
api.post('/goals/:goalRunId/checkpoints/:checkpointId/approve', async (req, res) => {
try {
const service = ensureGoalRunAccess(req, res);
if (!service) return;
const goal = await service.approveCheckpoint({
userId: req.currentUser.id,
goalRunId: req.params.goalRunId,
checkpointId: req.params.checkpointId,
feedback: req.body?.feedback ?? null,
});
const sessionId = String(
req.body?.sessionId ?? req.body?.session_id ?? '',
).trim() || null;
let run = null;
const gateway = getAgentRunGateway?.();
if (sessionId && gateway?.createRun) {
const userMessage = buildGoalContinueUserMessage(req.body?.feedback ?? null);
const requestId = String(
req.body?.request_id ?? req.body?.requestId ?? crypto.randomUUID(),
).trim();
const binding = await resolveGoalBindingForAgentRun({
goalRunService: service,
userId: req.currentUser.id,
userMessage,
sessionId,
body: { goal_run_id: req.params.goalRunId },
env,
});
if (binding?.goalRunId) {
run = await gateway.createRun(req.currentUser.id, {
sessionId,
requestId,
userMessage,
goalRunId: binding.goalRunId,
goalCheckpointId: binding.goalCheckpointId,
});
}
}
return res.json({ goal, ...(run ? { run } : {}) });
} catch (err) {
const status = err?.code === 'GOAL_RUN_NOT_FOUND' || err?.code === 'GOAL_CHECKPOINT_NOT_FOUND'
? 404
: 500;
return res.status(status).json({
message: err instanceof Error ? err.message : '确认阶段失败',
code: err?.code ?? null,
});
}
});
api.post('/goals/:goalRunId/pause', async (req, res) => {
try {
const service = ensureGoalRunAccess(req, res);
if (!service) return;
const goal = await service.pauseGoal({
userId: req.currentUser.id,
goalRunId: req.params.goalRunId,
});
return res.json({ goal });
} catch (err) {
const status = err?.code === 'GOAL_RUN_NOT_PAUSABLE' ? 409 : 500;
return res.status(status).json({
message: err instanceof Error ? err.message : '暂停目标失败',
code: err?.code ?? null,
});
}
});
api.post('/goals/:goalRunId/resume', async (req, res) => {
try {
const service = ensureGoalRunAccess(req, res);
if (!service) return;
const result = await service.resumeGoal({
userId: req.currentUser.id,
goalRunId: req.params.goalRunId,
});
return res.json(result);
} catch (err) {
const status = err?.code === 'GOAL_RUN_NOT_FOUND' || err?.code === 'GOAL_CHECKPOINT_UNAVAILABLE'
? 404
: err?.code === 'GOAL_RUN_NOT_RESUMABLE'
? 409
: 500;
return res.status(status).json({
message: err instanceof Error ? err.message : '续作目标失败',
code: err?.code ?? null,
});
}
});
api.delete('/goals/:goalRunId', async (req, res) => {
try {
const service = ensureGoalRunAccess(req, res);
if (!service) return;
const goal = await service.cancelGoal({
userId: req.currentUser.id,
goalRunId: req.params.goalRunId,
});
return res.json({ goal });
} catch (err) {
const status = err?.code === 'GOAL_RUN_NOT_CANCELLABLE' ? 409 : 500;
return res.status(status).json({
message: err instanceof Error ? err.message : '取消目标失败',
code: err?.code ?? null,
});
}
});
}
+226
View File
@@ -0,0 +1,226 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { attachPortalGoalRunRoutes } from './portal-goal-run-routes.mjs';
function createRouterRecorder() {
const routes = new Map();
return {
routes,
get(path, handler) {
routes.set(`GET ${path}`, handler);
},
post(path, handler) {
routes.set(`POST ${path}`, handler);
},
delete(path, handler) {
routes.set(`DELETE ${path}`, handler);
},
};
}
function createResponseRecorder() {
return {
statusCode: 200,
body: undefined,
status(code) {
this.statusCode = code;
return this;
},
json(body) {
this.body = body;
return this;
},
};
}
function createRequest(overrides = {}) {
return {
body: {},
query: {},
params: {},
currentUser: { id: 'user-canary' },
...overrides,
};
}
function enabledGoalRunService(overrides = {}) {
return {
async createGoalRun(input) {
return {
id: 'goal-1',
title: input.title,
intentSummary: input.intentSummary,
checkpoints: [{ id: 'cp-1', title: '启动', status: 'pending' }],
currentCheckpointId: 'cp-1',
};
},
async listGoalRuns() {
return [{ id: 'goal-1', title: '长期任务', status: 'active' }];
},
async getGoalRun({ goalRunId }) {
if (goalRunId !== 'goal-1') return null;
return {
id: 'goal-1',
title: '长期任务',
checkpoints: [{ id: 'cp-1', title: '启动', status: 'running' }],
};
},
async approveCheckpoint() {
return { id: 'goal-1', checkpoints: [{ id: 'cp-1', status: 'approved' }] };
},
async pauseGoal() {
return { id: 'goal-1', status: 'paused' };
},
async resumeGoal() {
return { goal: { id: 'goal-1', status: 'active' }, checkpointId: 'cp-2' };
},
async cancelGoal() {
return { id: 'goal-1', status: 'cancelled' };
},
...overrides,
};
}
const enabledEnv = {
GOAL_RUN_ENABLED: '1',
GOAL_RUN_CANARY_USER_IDS: 'user-canary',
};
test('Goal Run routes preserve MVP inventory', () => {
const api = createRouterRecorder();
attachPortalGoalRunRoutes(api, {
getGoalRunService: () => enabledGoalRunService(),
env: enabledEnv,
});
assert.deepEqual([...api.routes.keys()], [
'POST /goals',
'GET /goals',
'GET /goals/:goalRunId',
'POST /goals/:goalRunId/checkpoints/:checkpointId/approve',
'POST /goals/:goalRunId/pause',
'POST /goals/:goalRunId/resume',
'DELETE /goals/:goalRunId',
]);
});
test('POST /goals returns 503 when service unavailable', async () => {
const api = createRouterRecorder();
attachPortalGoalRunRoutes(api, {
getGoalRunService: () => null,
env: enabledEnv,
});
const res = createResponseRecorder();
await api.routes.get('POST /goals')(
createRequest({
body: { title: '任务', intentSummary: '分阶段完成' },
}),
res,
);
assert.equal(res.statusCode, 503);
});
test('POST /goals creates goal for enabled canary user', async () => {
const api = createRouterRecorder();
attachPortalGoalRunRoutes(api, {
getGoalRunService: () => enabledGoalRunService(),
env: enabledEnv,
});
const res = createResponseRecorder();
await api.routes.get('POST /goals')(
createRequest({
body: {
title: '准备下季度产品规划',
intentSummary: '收集竞品并输出草案',
},
}),
res,
);
assert.equal(res.statusCode, 201);
assert.equal(res.body.goal.id, 'goal-1');
});
test('GET /goals rejects non-canary user', async () => {
const api = createRouterRecorder();
attachPortalGoalRunRoutes(api, {
getGoalRunService: () => enabledGoalRunService(),
env: enabledEnv,
});
const res = createResponseRecorder();
await api.routes.get('GET /goals')(
createRequest({ currentUser: { id: 'other-user' } }),
res,
);
assert.equal(res.statusCode, 403);
});
test('POST /goals/:id/resume returns checkpoint binding hint', async () => {
const api = createRouterRecorder();
attachPortalGoalRunRoutes(api, {
getGoalRunService: () => enabledGoalRunService(),
env: enabledEnv,
});
const res = createResponseRecorder();
await api.routes.get('POST /goals/:goalRunId/resume')(
createRequest({ params: { goalRunId: 'goal-1' } }),
res,
);
assert.equal(res.statusCode, 200);
assert.equal(res.body.checkpointId, 'cp-2');
});
test('POST approve auto-dispatches agent run when session_id provided', async () => {
const createdRuns = [];
const api = createRouterRecorder();
attachPortalGoalRunRoutes(api, {
getGoalRunService: () => enabledGoalRunService({
async getGoalRun({ goalRunId }) {
if (goalRunId !== 'goal-1') return null;
return {
id: 'goal-1',
title: '长期任务',
checkpoints: [
{ id: 'cp-1', title: '启动', status: 'approved' },
{ id: 'cp-2', title: '输出', status: 'pending' },
],
};
},
async startNextCheckpoint({ goalRunId }) {
return { goalRunId, checkpointId: 'cp-2' };
},
async approveCheckpoint() {
return {
id: 'goal-1',
status: 'active',
checkpoints: [{ id: 'cp-1', status: 'approved' }],
};
},
}),
getAgentRunGateway: () => ({
async createRun(userId, payload) {
createdRuns.push({ userId, payload });
return {
id: 'run-1',
userId,
sessionId: payload.sessionId,
requestId: payload.requestId,
status: 'queued',
};
},
}),
env: enabledEnv,
});
const res = createResponseRecorder();
await api.routes.get('POST /goals/:goalRunId/checkpoints/:checkpointId/approve')(
createRequest({
params: { goalRunId: 'goal-1', checkpointId: 'cp-1' },
body: { session_id: 'session-1', feedback: '继续' },
}),
res,
);
assert.equal(res.statusCode, 200);
assert.equal(res.body.goal.id, 'goal-1');
assert.equal(res.body.run.id, 'run-1');
assert.equal(createdRuns.length, 1);
assert.equal(createdRuns[0].payload.sessionId, 'session-1');
assert.equal(createdRuns[0].payload.goalRunId, 'goal-1');
});