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, }); }, }; }