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
+90
View File
@@ -231,6 +231,8 @@ function createFakePool({ sessionDeliverables = {}, workspaceDeliverables = {} }
id,
userId,
sessionId,
goalRunId,
goalCheckpointId,
requestId,
userMessageJson,
createdAt,
@@ -242,6 +244,8 @@ function createFakePool({ sessionDeliverables = {}, workspaceDeliverables = {} }
id,
user_id: userId,
agent_session_id: sessionId,
goal_run_id: goalRunId,
goal_checkpoint_id: goalCheckpointId,
request_id: requestId,
status: 'queued',
attempts: 0,
@@ -2199,6 +2203,92 @@ test('agent run uses direct chat on regular agent sessions when llm routes direc
assert.ok(pool.events.some((event) => event.eventType === 'direct_chat_completed'));
});
test('agent run reuses portal session when direct chat fails on first turn', async () => {
const previousNormalized = process.env.MEMIND_ROUTER_NORMALIZED_DECISION;
process.env.MEMIND_ROUTER_NORMALIZED_DECISION = '1';
const pool = createFakePool();
const startedSessions = [];
const submitted = [];
try {
const gateway = createAgentRunGateway({
pool,
userAuth: {},
tkmindProxy: {
async startSessionForUser() {
startedSessions.push('called');
return { id: '20260731_12' };
},
async submitSessionReplyAndAwaitFinishForUser(userId, sessionId, requestId, userMessage) {
submitted.push({ userId, sessionId, requestId, userMessage });
return {
ok: true,
finishEvent: { type: 'Finish' },
tokenState: { totalTokens: 8 },
};
},
},
chatIntentRouter: {
isEnabled() {
return true;
},
async classify() {
return {
route: 'direct_chat',
confidence: 0.95,
reason: '简单寒暄或连通性测试',
source: 'rule',
decision: {
mode: 'sse',
flags: [],
route: 'chat',
session_hint: 'new',
},
};
},
},
directChatService: {
canHandle({ routingDecision }) {
return routingDecision === 'direct_chat';
},
explainCanHandle({ routingDecision }) {
return { ok: routingDecision === 'direct_chat', reason: null };
},
async run() {
throw new Error('fetch failed');
},
getStatus() {
return { enabled: true };
},
},
retryDelaysMs: [],
});
const run = await gateway.createRun('user-1', {
sessionId: '20260731_11',
requestId: 'req-direct-fallback-reuse',
userMessage: {
role: 'user',
content: [{ type: 'text', text: 'hi' }],
metadata: {
displayText: 'hi',
memindRun: { sessionMessageCount: 0 },
},
},
});
await waitFor(() => pool.runs.get(run.id)?.status === 'succeeded');
assert.equal(pool.runs.get(run.id).agent_session_id, '20260731_11');
assert.equal(startedSessions.length, 0);
assert.equal(submitted.length, 1);
assert.equal(submitted[0].sessionId, '20260731_11');
assert.ok(pool.events.some((event) => event.eventType === 'direct_chat_failed'));
assert.equal(pool.events.some((event) => event.eventType === 'session_started'), false);
} finally {
if (previousNormalized == null) delete process.env.MEMIND_ROUTER_NORMALIZED_DECISION;
else process.env.MEMIND_ROUTER_NORMALIZED_DECISION = previousNormalized;
}
});
test('agent run invalidates portal direct chat snapshot before submitting to goosed', async () => {
const pool = createFakePool({
sessionDeliverables: {