Files
memind/scripts/check-goosed-v149-portal-resume.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

203 lines
6.6 KiB
JavaScript

#!/usr/bin/env node
/**
* Portal integration: start/resume session and verify conversation round-trip.
*/
import crypto from 'node:crypto';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { prepareGooseV149CheckEnv } from './goose-v149-canary.mjs';
import { enforceRealLlmGate } from './goose-v149-real-llm-gate.mjs';
import { waitForAgentRunWorkerIdle } from './goose-v149-worker-idle.mjs';
import {
createReporter,
loginViaApi,
resolvePortalBase,
waitForRunTerminal,
} from './scenario-test-lib.mjs';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
enforceRealLlmGate('check-goosed-v149-portal-resume.mjs');
prepareGooseV149CheckEnv(process.env, root);
const baseUrl = resolvePortalBase(Number(process.env.H5_PORT ?? 8081));
const skipPortal = process.env.GOOSE_V149_PORTAL_SMOKE_SKIP === '1';
function messageCount(session) {
const conversation = session?.conversation;
if (Array.isArray(conversation?.messages)) return conversation.messages.length;
if (Array.isArray(conversation)) return conversation.length;
return Number(session?.message_count ?? session?.messageCount ?? 0);
}
async function portalReachable() {
try {
const response = await fetch(`${baseUrl}/auth/status`);
return response.ok;
} catch {
return false;
}
}
async function createGoosedAgentRun(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,
// Resume smoke validates session round-trip, not deep-reasoning latency.
force_deep_reasoning: process.env.GOOSE_V149_PORTAL_RESUME_FORCE_DEEP === '1',
user_message: {
id: crypto.randomUUID(),
role: 'user',
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 ?? null,
status: run.status,
};
}
async function waitForSuccessfulAgentRun(baseUrl, cookie, { sessionId, message }) {
const maxAttempts = Number(process.env.GOOSE_V149_PORTAL_RESUME_ATTEMPTS || 2);
const timeoutMs = Number(process.env.GOOSE_V149_PORTAL_RESUME_TIMEOUT_MS || 300_000);
let lastStatus = 'unknown';
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
const run = await createGoosedAgentRun(baseUrl, cookie, { sessionId, message });
const terminal = await waitForRunTerminal(baseUrl, cookie, run.runId, timeoutMs);
lastStatus = terminal.status;
if (terminal.status === 'succeeded') {
return { ...terminal, attempt };
}
if (attempt < maxAttempts) {
console.log(
`GOOSE_V149_PORTAL_RESUME_RETRY: attempt=${attempt} status=${terminal.status} `
+ `run=${run.runId}`,
);
await new Promise((resolve) => {
setTimeout(resolve, Number(process.env.GOOSE_V149_PORTAL_RESUME_RETRY_MS || 3000));
});
}
}
throw new Error(`agent run did not succeed: ${lastStatus}`);
}
async function main() {
if (skipPortal || !(await portalReachable())) {
console.log(`GOOSE_V149_PORTAL_RESUME_SKIP: Portal not running at ${baseUrl}`);
console.log('GOOSE_V149_PORTAL_RESUME_OK: skipped');
return;
}
await waitForAgentRunWorkerIdle(root, process.env, {
logPrefix: '[goose-v149-portal-resume]',
});
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 portal resume smoke');
}
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 warmResume = await fetch(`${baseUrl}/api/agent/resume`, {
method: 'POST',
headers: {
Cookie: auth.cookie,
'Content-Type': 'application/json',
},
body: JSON.stringify({
session_id: sessionId,
load_model_and_extensions: true,
}),
});
if (!warmResume.ok) {
const warmBody = await warmResume.text().catch(() => '');
throw new Error(`pre-run resume failed: ${warmResume.status} ${warmBody.slice(0, 200)}`);
}
const terminal = await waitForSuccessfulAgentRun(baseUrl, auth.cookie, {
sessionId,
message: `portal resume smoke ping ${Date.now().toString(36)}`,
});
reporter.pass('agent run', terminal.status);
const resumeRes = await fetch(`${baseUrl}/api/agent/resume`, {
method: 'POST',
headers: {
Cookie: auth.cookie,
'Content-Type': 'application/json',
},
body: JSON.stringify({
session_id: sessionId,
load_model_and_extensions: true,
}),
});
const resumeBody = await resumeRes.json().catch(() => ({}));
if (!resumeRes.ok) {
throw new Error(`resume failed: ${resumeRes.status} ${JSON.stringify(resumeBody).slice(0, 300)}`);
}
const detailRes = await fetch(`${baseUrl}/api/sessions/${encodeURIComponent(sessionId)}`, {
headers: { Cookie: auth.cookie },
});
const detail = await detailRes.json().catch(() => ({}));
if (!detailRes.ok) {
throw new Error(`GET session failed: ${detailRes.status}`);
}
const detailCount = messageCount(detail);
const resumedCount = messageCount(resumeBody?.session ?? resumeBody);
if (detailCount <= 0 && resumedCount <= 0) {
throw new Error(
`resume ok but conversation empty after goosed agent run (run=${terminal.status})`,
);
}
console.log(
`GOOSE_V149_PORTAL_RESUME_OK: session=${sessionId} detailMessages=${detailCount} `
+ `resumedMessages=${resumedCount} run=${terminal.status} base=${baseUrl}`,
);
}
main().catch((error) => {
console.error(`GOOSE_V149_PORTAL_RESUME_FAIL: ${error.message}`);
process.exit(1);
});