#!/usr/bin/env node /** * Portal + Goose v1.49 page E2E: agent/runs → Finish → HTML materialize + public link. */ import crypto from 'node:crypto'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { buildChatSkillPrompt } from '../chat-skills.mjs'; import { prepareGooseV149CheckEnv } from './goose-v149-canary.mjs'; import { enforceRealLlmGate } from './goose-v149-real-llm-gate.mjs'; import { createReporter, extractAssistantTexts, getSession, loginViaApi, resolvePortalBase, verifyPageAccess, waitForAssistantGrowth, waitForRunTerminal, } from './scenario-test-lib.mjs'; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); enforceRealLlmGate('check-goosed-v149-page-e2e.mjs'); prepareGooseV149CheckEnv(process.env, root); const baseUrl = resolvePortalBase(Number(process.env.H5_PORT ?? 8081)); const skip = process.env.GOOSE_V149_PAGE_E2E_SKIP === '1'; const timeoutMs = Number(process.env.GOOSE_V149_PAGE_E2E_TIMEOUT_MS || 600_000); async function portalReachable() { try { const response = await fetch(`${baseUrl}/auth/status`); return response.ok; } catch { return false; } } async function createGoosedPageRun(baseUrl, cookie, { sessionId, message }) { const requestId = crypto.randomUUID(); const response = await fetch(`${baseUrl}/api/agent/runs`, { method: 'POST', headers: { Cookie: cookie, 'Content-Type': 'application/json', }, body: JSON.stringify({ request_id: requestId, session_id: sessionId, force_deep_reasoning: true, user_message: { id: crypto.randomUUID(), role: 'user', created: Math.floor(Date.now() / 1000), content: [{ type: 'text', text: message }], metadata: { userVisible: true, agentVisible: true, displayText: message, }, }, }), }); const payload = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(`POST /api/agent/runs ${response.status}: ${JSON.stringify(payload).slice(0, 400)}`); } const run = payload.run ?? payload; return { runId: run.id, requestId, sessionId: run.sessionId ?? run.agent_session_id ?? sessionId, }; } async function main() { if (skip || !(await portalReachable())) { console.log(`GOOSE_V149_PAGE_E2E_SKIP: Portal not running at ${baseUrl}`); console.log('GOOSE_V149_PAGE_E2E_OK: skipped'); return; } const reporter = createReporter(); const username = process.env.RELEASE_GATE_SCENARIO_USERNAME ?? 'john'; const password = process.env.JOHN_PASSWORD ?? process.env.H5_ACCESS_PASSWORD ?? process.env.MEMIND_PASSWORD ?? ''; if (!password) { throw new Error('set JOHN_PASSWORD (or H5_ACCESS_PASSWORD) for page E2E'); } const auth = await loginViaApi(baseUrl, { username, password }, reporter); const startRes = await fetch(`${baseUrl}/api/agent/start`, { method: 'POST', headers: { Cookie: auth.cookie, 'Content-Type': 'application/json' }, body: JSON.stringify({}), }); const started = await startRes.json().catch(() => ({})); if (!startRes.ok || !started?.id) { throw new Error(`agent/start failed: ${startRes.status}`); } const sessionId = started.id; const skillPrefix = buildChatSkillPrompt('generate-page', 'static-page-publish'); const pageName = `goose-v149-portal-e2e-${Date.now()}.html`; const message = `${skillPrefix}请做一个全新的苏州一日游攻略页面,保存为 public/${pageName},` + '不要修改已有页面,完成后在回复里给出可访问链接。'; const run = await createGoosedPageRun(baseUrl, auth.cookie, { sessionId, message }); const terminal = await waitForRunTerminal(baseUrl, auth.cookie, run.runId, timeoutMs); if (terminal.status !== 'succeeded') { throw new Error(`agent run failed: ${terminal.status} ${terminal.error ?? ''}`); } reporter.pass('agent run', terminal.status); const activeSessionId = terminal.sessionId ?? terminal.agent_session_id ?? run.sessionId ?? sessionId; const reply = await waitForAssistantGrowth(baseUrl, auth.cookie, activeSessionId, { minChars: 20, timeoutMs: Math.min(timeoutMs, 120_000), }); if (!reply?.combined) { throw new Error('assistant reply missing after page run'); } reporter.pass('assistant 回复', `${reply.combined.length} 字`); const sessionDetail = await getSession(baseUrl, auth.cookie, activeSessionId); const assistantCount = sessionDetail.ok ? extractAssistantTexts(sessionDetail.session).length : 0; if (assistantCount <= 0) { throw new Error('session conversation has no assistant messages after page run'); } const pageOk = await verifyPageAccess({ baseUrl, cookie: auth.cookie, publishKey: auth.userId, replyText: reply.combined, expect: { keywords: ['苏州'], requirePublicLink: true, requireHttp200: true, }, reporter, }); if (!pageOk) { throw new Error('page delivery verification failed'); } console.log( `GOOSE_V149_PAGE_E2E_OK: session=${activeSessionId} run=${run.runId} ` + `assistantChars=${reply.combined.length} base=${baseUrl}`, ); } main().catch((error) => { console.error(`GOOSE_V149_PAGE_E2E_FAIL: ${error.message}`); process.exit(1); });