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>
203 lines
6.6 KiB
JavaScript
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);
|
|
});
|