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>
195 lines
6.7 KiB
JavaScript
195 lines
6.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Portal + Goose v1.49 integration smoke (optional when Portal is running).
|
|
*/
|
|
import crypto from 'node:crypto';
|
|
import { assertGooseCanaryMemoryStatusForPortalSmoke } from '../goose-canary-memory-policy.mjs';
|
|
import path from 'node:path';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
import { applyGooseV149CanaryBlockEnv, describeGooseCanaryConfig } from './goose-v149-canary.mjs';
|
|
import { enforceRealLlmGate } from './goose-v149-real-llm-gate.mjs';
|
|
import { loadMemindEnvFiles } from './memind-runtime-profile.mjs';
|
|
import { waitForRunTerminal } from './scenario-test-lib.mjs';
|
|
|
|
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
enforceRealLlmGate('check-goosed-v149-portal-smoke.mjs');
|
|
loadMemindEnvFiles(root, process.env);
|
|
applyGooseV149CanaryBlockEnv(process.env, root);
|
|
|
|
const runTimeoutMs = Number(process.env.GOOSE_V149_PORTAL_SMOKE_TIMEOUT_MS || 180_000);
|
|
|
|
const baseUrl = String(process.env.GOOSE_V149_PORTAL_BASE_URL ?? 'http://127.0.0.1:8081').replace(/\/+$/, '');
|
|
const v149Port = process.env.GOOSE_V149_PORT || '18049';
|
|
const v149Target = process.env.TKMIND_API_TARGET_V149 || `https://127.0.0.1:${v149Port}`;
|
|
const skipPortal = process.env.GOOSE_V149_PORTAL_SMOKE_SKIP === '1';
|
|
const canary = describeGooseCanaryConfig(process.env);
|
|
|
|
function curlJson(url, init = {}) {
|
|
const args = ['-sk', '--connect-timeout', '5'];
|
|
if (init.method && init.method !== 'GET') args.push('-X', init.method);
|
|
if (init.headers) {
|
|
for (const [key, value] of Object.entries(init.headers)) {
|
|
args.push('-H', `${key}: ${value}`);
|
|
}
|
|
}
|
|
if (init.body) args.push('-d', init.body);
|
|
args.push(url);
|
|
const body = execFileSync('curl', args, { encoding: 'utf8' }).trim();
|
|
return body ? JSON.parse(body) : null;
|
|
}
|
|
|
|
function portalReachable() {
|
|
try {
|
|
curlJson(`${baseUrl}/auth/status`);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function v149Reachable() {
|
|
try {
|
|
const body = execFileSync(
|
|
'curl',
|
|
['-sk', '--connect-timeout', '5', `https://127.0.0.1:${v149Port}/status`],
|
|
{ encoding: 'utf8' },
|
|
).trim();
|
|
return body === 'ok';
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function requestJson(url, { method = 'GET', headers = {}, body } = {}) {
|
|
const response = await fetch(url, {
|
|
method,
|
|
headers: {
|
|
...(body ? { 'Content-Type': 'application/json' } : {}),
|
|
...headers,
|
|
},
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
const text = await response.text();
|
|
let json = null;
|
|
try {
|
|
json = text ? JSON.parse(text) : null;
|
|
} catch {
|
|
json = null;
|
|
}
|
|
return { ok: response.ok, status: response.status, json, text, headers: response.headers };
|
|
}
|
|
|
|
async function main() {
|
|
if (!v149Reachable()) {
|
|
throw new Error(`v1.49 goosed not reachable on :${v149Port}; run scripts/run-goosed-v149-local.sh`);
|
|
}
|
|
|
|
if (skipPortal || !portalReachable()) {
|
|
console.log(
|
|
`GOOSE_V149_PORTAL_SMOKE_SKIP: Portal not running at ${baseUrl}; v149 direct checks only`,
|
|
);
|
|
console.log('GOOSE_V149_PORTAL_SMOKE_OK: skipped (v149 reachable)');
|
|
return;
|
|
}
|
|
|
|
const runtime = await requestJson(`${baseUrl}/api/runtime/status`);
|
|
const memory = runtime.json?.memory ?? null;
|
|
const targetStatuses = runtime.json?.targets ?? [];
|
|
const portalTargets = Array.isArray(targetStatuses)
|
|
? targetStatuses.map((item) => item.target ?? item).filter(Boolean)
|
|
: [];
|
|
const portalUsesV149 = portalTargets.some((item) => String(item).includes(`:${v149Port}`));
|
|
|
|
if (!runtime.ok || !memory) {
|
|
throw new Error(`runtime status failed: ${runtime.status} ${runtime.text?.slice?.(0, 200)}`);
|
|
}
|
|
|
|
if (canary.enabled && !portalUsesV149) {
|
|
console.log(
|
|
`GOOSE_V149_PORTAL_SMOKE_SKIP: .env.local canary=${canary.mode} primary=${canary.primary} `
|
|
+ `but Portal targets=${JSON.stringify(portalTargets)} — restart pnpm dev`,
|
|
);
|
|
console.log('GOOSE_V149_PORTAL_SMOKE_OK: skipped (stale Portal process)');
|
|
return;
|
|
}
|
|
|
|
if (canary.enabled && portalUsesV149) {
|
|
console.log(
|
|
`GOOSE_V149_PORTAL_SMOKE_NOTE: canary active targets=${portalTargets.join(',')}`,
|
|
);
|
|
}
|
|
|
|
const canaryMode = String(process.env.TKMIND_GOOSE_CANARY ?? 'off').trim().toLowerCase();
|
|
if (canaryMode !== 'off') assertGooseCanaryMemoryStatusForPortalSmoke(memory);
|
|
if (canaryMode === 'off' && memory.backend !== 'legacy') {
|
|
console.log(
|
|
`GOOSE_V149_PORTAL_SMOKE_NOTE: Portal memory.backend=${memory.backend} (canary off; enable switch-goose-v149-canary.sh for legacy gate)`,
|
|
);
|
|
}
|
|
|
|
const suffix = `${Date.now()}${Math.floor(Math.random() * 1000)}`;
|
|
const user = {
|
|
username: `gv149_${suffix}`,
|
|
password: 'GooseV149-Portal-2026',
|
|
email: `gv149-${suffix}@example.test`,
|
|
displayName: `gv149_${suffix}`,
|
|
};
|
|
|
|
const register = await requestJson(`${baseUrl}/auth/register`, { method: 'POST', body: user });
|
|
if (!register.ok) throw new Error(`register failed: ${register.status}`);
|
|
|
|
const login = await requestJson(`${baseUrl}/auth/login`, {
|
|
method: 'POST',
|
|
body: { username: user.username, password: user.password },
|
|
});
|
|
const cookie = login.headers?.get?.('set-cookie')?.split(';', 1)[0] ?? null;
|
|
if (!login.ok || !cookie) throw new Error(`login failed: ${login.status}`);
|
|
|
|
const started = await requestJson(`${baseUrl}/api/agent/start`, {
|
|
method: 'POST',
|
|
headers: { Cookie: cookie },
|
|
body: {},
|
|
});
|
|
const sessionId = started.json?.id ?? null;
|
|
if (!started.ok || !sessionId) {
|
|
throw new Error(`agent/start via Portal failed: ${started.status} ${started.text?.slice?.(0, 200)}`);
|
|
}
|
|
|
|
const requestId = crypto.randomUUID();
|
|
const run = await requestJson(`${baseUrl}/api/agent/runs`, {
|
|
method: 'POST',
|
|
headers: { Cookie: cookie },
|
|
body: {
|
|
session_id: sessionId,
|
|
request_id: requestId,
|
|
user_message: {
|
|
role: 'user',
|
|
created: Math.floor(Date.now() / 1000),
|
|
content: [{ type: 'text', text: 'ping from goose v1.49 portal smoke' }],
|
|
metadata: { userVisible: true, agentVisible: true, displayText: 'ping' },
|
|
},
|
|
},
|
|
});
|
|
const runId = run.json?.run?.id ?? null;
|
|
if (!run.ok || run.status !== 202 || !runId) {
|
|
throw new Error(`agent run failed: ${run.status} ${run.text?.slice?.(0, 200)}`);
|
|
}
|
|
|
|
const terminal = await waitForRunTerminal(baseUrl, cookie, runId, runTimeoutMs);
|
|
if (terminal.status !== 'succeeded') {
|
|
throw new Error(`agent run terminal=${terminal.status} ${terminal.error ?? ''}`);
|
|
}
|
|
|
|
console.log(
|
|
`GOOSE_V149_PORTAL_SMOKE_OK: base=${baseUrl} session=${sessionId} run=${runId} `
|
|
+ `terminal=${terminal.status} memory.backend=${memory.backend}`,
|
|
);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(`GOOSE_V149_PORTAL_SMOKE_FAIL: ${error.message}`);
|
|
process.exit(1);
|
|
});
|