2026-09-09 18:11:39 +08:00
|
|
|
|
#!/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';
|
2026-09-09 22:08:39 +08:00
|
|
|
|
import { enforceRealLlmGate } from './goose-v149-real-llm-gate.mjs';
|
2026-09-09 18:11:39 +08:00
|
|
|
|
import {
|
|
|
|
|
|
createReporter,
|
|
|
|
|
|
extractAssistantTexts,
|
|
|
|
|
|
getSession,
|
|
|
|
|
|
loginViaApi,
|
|
|
|
|
|
resolvePortalBase,
|
|
|
|
|
|
verifyPageAccess,
|
|
|
|
|
|
waitForAssistantGrowth,
|
|
|
|
|
|
waitForRunTerminal,
|
|
|
|
|
|
} from './scenario-test-lib.mjs';
|
|
|
|
|
|
|
|
|
|
|
|
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
2026-09-09 22:08:39 +08:00
|
|
|
|
enforceRealLlmGate('check-goosed-v149-page-e2e.mjs');
|
2026-09-09 18:11:39 +08:00
|
|
|
|
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);
|
|
|
|
|
|
});
|