#!/usr/bin/env node import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { setTimeout as sleep } from 'node:timers/promises'; import { createDbPool, migrateSchema } from '../db.mjs'; import { HELP_ESCALATION_STATUS, createHelpEscalationService, isHelpEscalationIntent, } from '../help-escalation.mjs'; import { processOneHelpEscalation } from '../cursor-help-worker.mjs'; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); function loadEnvFile(filePath) { if (!fs.existsSync(filePath)) return; for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const eq = trimmed.indexOf('='); if (eq < 0) continue; const key = trimmed.slice(0, eq).trim(); const value = trimmed.slice(eq + 1).trim(); if (!process.env[key]) process.env[key] = value; } } loadEnvFile(path.join(root, '.env')); loadEnvFile(path.join(root, '.env.local')); const args = process.argv.slice(2); const enqueueOnly = args.includes('--enqueue-only'); const processLocally = args.includes('--process-locally'); const userIdArg = args.find((item) => item.startsWith('--user-id='))?.slice('--user-id='.length)?.trim() ?? ''; const waitMs = Number( args.find((item) => item.startsWith('--wait-ms='))?.slice('--wait-ms='.length) ?? process.env.MEMIND_CURSOR_HELP_E2E_WAIT_MS ?? 10 * 60 * 1000, ); const pool = createDbPool(); async function waitForEscalation(helpEscalationService, escalationId) { const deadline = Date.now() + waitMs; while (Date.now() < deadline) { const current = await helpEscalationService.getById(escalationId); if ( current?.status === HELP_ESCALATION_STATUS.SUCCEEDED || current?.status === HELP_ESCALATION_STATUS.FAILED ) { return current; } console.log('waiting for worker...', { id: escalationId, status: current?.status ?? 'missing', }); await sleep(2000); } throw new Error(`Timed out after ${waitMs}ms waiting for escalation ${escalationId}`); } try { await migrateSchema(pool); const helpEscalationService = createHelpEscalationService({ pool }); if (!helpEscalationService.enabled) { console.error('MEMIND_CURSOR_HELP_ENABLED is not enabled'); process.exitCode = 1; } else { const sampleText = 'help 本机端到端测试:请检查 Portal /api/status 是否正常,并回复一行 JSON 结果。'; console.log('help intent:', isHelpEscalationIntent(sampleText)); let userId = userIdArg; if (!userId) { const [rows] = await pool.query('SELECT id, username FROM h5_users ORDER BY created_at ASC LIMIT 1'); userId = rows[0]?.id; } if (!userId) { console.error('No h5_users row found for e2e test'); process.exitCode = 1; } else { const escalation = await helpEscalationService.enqueue({ userId, channel: 'h5', userText: sampleText, context: { origin: 'e2e-test', requestId: `help-e2e-${Date.now()}`, }, }); console.log('enqueued:', { id: escalation.id, userId: escalation.userId, status: escalation.status, }); if (!enqueueOnly) { let completed = null; if (processLocally) { console.log('processing locally with agent CLI (portal worker should be disabled)...'); completed = await processOneHelpEscalation({ helpEscalationService, logger: console, }); } else { console.log('waiting for portal/standalone cursor-help worker...'); completed = await waitForEscalation(helpEscalationService, escalation.id); } console.log('completed:', { id: completed?.id ?? null, status: completed?.status ?? null, resultText: completed?.resultText ?? null, errorMessage: completed?.errorMessage ?? null, }); if (!completed || completed.status !== HELP_ESCALATION_STATUS.SUCCEEDED) { process.exitCode = 1; } } } } } finally { await pool.end(); }