67220a14ea
Prevent phase2/phase3/all reruns from burning DashScope tokens unless GOOSE_V149_ALLOW_REAL_LLM=1 is explicitly set with human approval. Co-authored-by: Cursor <cursoragent@cursor.com>
164 lines
5.2 KiB
JavaScript
164 lines
5.2 KiB
JavaScript
#!/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);
|
||
});
|