import { deriveUserFacingText } from './conversation-display.mjs'; import { normalizeExperienceRecordInput } from './experience-schema.mjs'; const RUN_METADATA_KEY = 'memindRun'; function safeJsonParse(value, fallback = null) { if (value == null || value === '') return fallback; if (typeof value === 'object') return value; try { return JSON.parse(String(value)); } catch { return fallback; } } function extractRunMessageText(userMessage) { if (typeof userMessage?.content === 'string') return userMessage.content; if (!Array.isArray(userMessage?.content)) return ''; return userMessage.content .filter((item) => item?.type === 'text') .map((item) => String(item.text ?? '').trim()) .filter(Boolean) .join('\n'); } export function extractAgentRunProblem(row) { const userMessage = safeJsonParse(row?.user_message_json, {}) ?? {}; const displayText = String(userMessage?.metadata?.displayText ?? '').trim(); const problem = displayText || deriveUserFacingText(extractRunMessageText(userMessage)); return problem.trim(); } function resolveRunOptions(userMessage) { const metadata = userMessage?.metadata ?? {}; const runMetadata = metadata?.[RUN_METADATA_KEY] ?? metadata?.agentRun ?? {}; return { toolMode: String(runMetadata?.toolMode ?? metadata?.toolMode ?? 'chat').trim().toLowerCase(), taskType: String(runMetadata?.taskType ?? metadata?.taskType ?? '').trim(), requiredExecutor: String(runMetadata?.executor ?? runMetadata?.requiredExecutor ?? '').trim().toLowerCase(), suggestedSkill: String( runMetadata?.selectedChatSkill ?? metadata?.selectedChatSkill ?? '', ).trim(), }; } function summarizeToolEvidence(toolEvidence) { if (!toolEvidence) return ''; const calls = Array.isArray(toolEvidence.calls) ? toolEvidence.calls.filter(Boolean) : []; if (calls.length > 0) { return `工具调用: ${calls.join(', ')}`; } if (toolEvidence.generateImage?.succeeded === true) { return '图片生成完成'; } return ''; } function summarizeRunEvents(events = []) { for (const event of events) { const data = safeJsonParse(event?.data_json ?? event?.dataJson, {}) ?? {}; if (event.event_type === 'tool_gateway_result' || event.eventType === 'tool_gateway_result') { const stdout = String(data.stdoutTail ?? '').trim(); const stderr = String(data.stderrTail ?? '').trim(); const executor = String(data.executor ?? '').trim(); const parts = []; if (executor) parts.push(`执行器 ${executor}`); if (stdout) parts.push(stdout); if (stderr) parts.push(`stderr: ${stderr}`); if (parts.length > 0) return parts.join('\n').slice(0, 2000); } if (event.event_type === 'direct_chat_completed' || event.eventType === 'direct_chat_completed') { const model = String(data.model ?? '').trim(); return model ? `Direct chat 完成 (${model})` : 'Direct chat 完成'; } if (event.event_type === 'session_finished' || event.eventType === 'session_finished') { const calls = Array.isArray(data.toolCalls) ? data.toolCalls.filter(Boolean) : []; if (calls.length > 0) return `会话完成,工具: ${calls.join(', ')}`; return 'Goose 会话完成'; } } return ''; } export function buildAgentRunExperienceBody({ status, errorMessage = null, toolEvidence = null, eventSummary = null, routing = null, }) { if (status === 'failed') { const message = String(errorMessage ?? '').trim(); if (message) return message.slice(0, 4000); return 'Agent run failed'; } const parts = [ summarizeToolEvidence(toolEvidence), String(eventSummary ?? '').trim(), routing?.suggestedSkill ? `技能: ${routing.suggestedSkill}` : routing?.route ? `路由: ${routing.route}` : '', 'Agent run completed successfully.', ].filter(Boolean); return parts.join('\n').slice(0, 4000); } export function buildAgentRunExperiencePayload({ row, runId, status, sessionId = null, errorMessage = null, toolEvidence = null, routing = null, eventSummary = null, } = {}) { const terminalStatus = String(status ?? '').trim().toLowerCase(); if (!['succeeded', 'failed'].includes(terminalStatus)) return null; const problem = extractAgentRunProblem(row); const body = buildAgentRunExperienceBody({ status: terminalStatus, errorMessage, toolEvidence, eventSummary, routing, }); if (!problem || !body) return null; const userMessage = safeJsonParse(row?.user_message_json, {}) ?? {}; const runOptions = resolveRunOptions(userMessage); const executor = runOptions.requiredExecutor || (routing?.route === 'direct_chat' ? 'direct-chat' : 'goose'); const components = [ routing?.route ?? null, runOptions.toolMode !== 'chat' ? runOptions.toolMode : null, runOptions.requiredExecutor || null, runOptions.suggestedSkill || routing?.suggestedSkill || null, 'portal', ].filter(Boolean); return normalizeExperienceRecordInput({ kind: 'task_outcome', title: problem.slice(0, 200), body, problem, result: terminalStatus === 'succeeded' ? 'success' : 'failure', confidence: terminalStatus === 'succeeded' ? 0.7 : 0.6, sourceSessionId: sessionId, sourceUserId: row?.user_id ?? null, environment: { runtime: 'agent-run-gateway', components, }, action: { executor, steps: Array.isArray(toolEvidence?.calls) ? toolEvidence.calls : ['agent_run_complete'], artifacts: [], }, evidence: { sources: [{ source_id: `run:${runId}`, source_type: 'agent_run', actor: executor, timestamp_ms: Date.now(), modality: terminalStatus === 'failed' ? 'run_failure' : 'run_result', }], provenance: { run_id: runId, session_id: sessionId, user_id: row?.user_id ?? null, }, }, }); } export async function loadAgentRunEventSummary(pool, runId) { if (!pool?.query || !runId) return ''; const [rows] = await pool.query( `SELECT event_type, data_json FROM h5_agent_run_events WHERE run_id = ? ORDER BY created_at DESC LIMIT 20`, [runId], ); return summarizeRunEvents(Array.isArray(rows) ? rows : []); } export async function extractAgentRunExperience({ experienceService, pool = null, row, runId, status, sessionId = null, errorMessage = null, toolEvidence = null, routing = null, }) { if (!experienceService || typeof experienceService.record !== 'function') { return null; } const eventSummary = pool ? await loadAgentRunEventSummary(pool, runId).catch(() => '') : ''; const payload = buildAgentRunExperiencePayload({ row, runId, status, sessionId, errorMessage, toolEvidence, routing, eventSummary, }); if (!payload) return null; return experienceService.record(payload); }