Files
memind/goal-run-admin-ops.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

208 lines
6.5 KiB
JavaScript

import { createGoalRunService, ensureGoalRunSchema } from './goal-run-service.mjs';
import { isGoalRunEnabledForUser } from './goal-run-intent.mjs';
function envFlag(value, fallback = false) {
const raw = String(value ?? '').trim().toLowerCase();
if (!raw) return fallback;
return ['1', 'true', 'yes', 'on'].includes(raw);
}
function parseUserIdList(raw) {
return String(raw ?? '')
.split(/[,;\s]+/)
.map((item) => item.trim())
.filter(Boolean);
}
function parseJson(value, fallback = null) {
if (value == null) return fallback;
if (typeof value === 'object') return value;
try {
return JSON.parse(String(value));
} catch {
return fallback;
}
}
function normalizeGoalListRow(row) {
return {
id: String(row.id),
userId: String(row.user_id),
username: row.username == null ? null : String(row.username),
title: String(row.title),
intentSummary: String(row.intent_summary),
status: String(row.status),
priority: Number(row.priority ?? 5),
sourceChannel: String(row.source_channel ?? 'h5'),
sourceSessionId: row.source_session_id == null ? null : String(row.source_session_id),
currentCheckpointId: row.current_checkpoint_id == null ? null : String(row.current_checkpoint_id),
checkpointCount: Number(row.checkpoint_count ?? 0),
activeAgentRunCount: Number(row.active_agent_run_count ?? 0),
createdAt: Number(row.created_at ?? 0),
updatedAt: Number(row.updated_at ?? 0),
completedAt: row.completed_at == null ? null : Number(row.completed_at),
};
}
export function createGoalRunAdminOpsService(pool, { env = process.env, now = () => Date.now() } = {}) {
if (!pool?.query) return null;
const goalRunService = createGoalRunService({ pool, now });
let schemaReady = false;
async function ensureSchema() {
if (schemaReady) return;
await ensureGoalRunSchema(pool);
schemaReady = true;
}
return {
getRuntime() {
const canaryUserIds = parseUserIdList(env.GOAL_RUN_CANARY_USER_IDS);
return {
enabled: envFlag(env.GOAL_RUN_ENABLED, false),
canaryUserIds,
canaryMode: canaryUserIds.length > 0,
};
},
async countByStatus() {
await ensureSchema();
const [rows] = await pool.query(
`SELECT status, COUNT(*) AS count
FROM h5_goal_runs
GROUP BY status`,
);
const counts = {};
for (const row of rows) {
counts[String(row.status)] = Number(row.count ?? 0);
}
return counts;
},
async listGoals({
status = null,
userId = null,
limit = 50,
offset = 0,
} = {}) {
await ensureSchema();
const safeLimit = Math.max(1, Math.min(100, Number(limit) || 50));
const safeOffset = Math.max(0, Number(offset) || 0);
const filters = [];
const params = [];
if (status) {
filters.push('g.status = ?');
params.push(String(status));
}
if (userId) {
filters.push('g.user_id = ?');
params.push(String(userId));
}
const where = filters.length ? `WHERE ${filters.join(' AND ')}` : '';
const [rows] = await pool.query(
`SELECT g.*,
u.username,
(
SELECT COUNT(*)
FROM h5_goal_checkpoints c
WHERE c.goal_run_id = g.id
) AS checkpoint_count,
(
SELECT COUNT(*)
FROM h5_agent_runs r
WHERE r.goal_run_id = g.id
AND r.status NOT IN ('succeeded', 'failed')
) AS active_agent_run_count
FROM h5_goal_runs g
LEFT JOIN h5_users u ON u.id = g.user_id
${where}
ORDER BY g.updated_at DESC
LIMIT ? OFFSET ?`,
[...params, safeLimit, safeOffset],
);
return rows.map(normalizeGoalListRow);
},
async getGoalDetail(goalRunId) {
await ensureSchema();
const [rows] = await pool.query(
`SELECT g.*, u.username
FROM h5_goal_runs g
LEFT JOIN h5_users u ON u.id = g.user_id
WHERE g.id = ?
LIMIT 1`,
[String(goalRunId)],
);
const row = rows[0];
if (!row) return null;
const goal = await goalRunService.getGoalRun({
userId: String(row.user_id),
goalRunId: String(goalRunId),
});
if (!goal) return null;
const [agentRuns] = await pool.query(
`SELECT id, status, request_id, goal_checkpoint_id, created_at, updated_at, completed_at
FROM h5_agent_runs
WHERE goal_run_id = ?
ORDER BY created_at DESC
LIMIT 20`,
[String(goalRunId)],
);
return {
...goal,
username: row.username == null ? null : String(row.username),
context: parseJson(row.context_json, goal.context),
agentRuns: agentRuns.map((run) => ({
id: String(run.id),
status: String(run.status),
requestId: String(run.request_id),
goalCheckpointId: run.goal_checkpoint_id == null ? null : String(run.goal_checkpoint_id),
createdAt: Number(run.created_at ?? 0),
updatedAt: Number(run.updated_at ?? 0),
completedAt: run.completed_at == null ? null : Number(run.completed_at),
})),
canaryEnabled: isGoalRunEnabledForUser(String(row.user_id), env),
};
},
async approveCheckpoint({ goalRunId, checkpointId, feedback = null, reviewedBy = null } = {}) {
await ensureSchema();
const [rows] = await pool.query(
'SELECT user_id FROM h5_goal_runs WHERE id = ? LIMIT 1',
[String(goalRunId)],
);
const userId = rows[0]?.user_id == null ? null : String(rows[0].user_id);
if (!userId) {
const error = new Error('目标不存在');
error.code = 'GOAL_RUN_NOT_FOUND';
throw error;
}
const note = feedback ?? (reviewedBy ? `admin:${reviewedBy}` : null);
return goalRunService.approveCheckpoint({
userId,
goalRunId,
checkpointId,
feedback: note,
});
},
async cancelGoal({ goalRunId, reviewedBy = null } = {}) {
await ensureSchema();
const [rows] = await pool.query(
'SELECT user_id FROM h5_goal_runs WHERE id = ? LIMIT 1',
[String(goalRunId)],
);
const userId = rows[0]?.user_id == null ? null : String(rows[0].user_id);
if (!userId) {
const error = new Error('目标不存在');
error.code = 'GOAL_RUN_NOT_FOUND';
throw error;
}
return goalRunService.cancelGoal({
userId,
goalRunId,
});
},
};
}