Files
memind/scripts/check-goosed-v149-page-e2e.mjs
T
john 67220a14ea fix(goose-v149): block unattended real LLM smokes by default
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>
2026-09-09 22:08:39 +08:00

164 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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);
});