Files
memind/scripts/check-goosed-v149-memory-verbal-recall.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

212 lines
6.8 KiB
JavaScript

#!/usr/bin/env node
/**
* Memory verbal recall via Portal chat (new session asks recall question).
* With canary injection=off, documents resolve-only path; optional canary override via env.
*/
import crypto from 'node:crypto';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createDbPool } from '../db.mjs';
import { prepareGooseV149CheckEnv } from './goose-v149-canary.mjs';
import { enforceRealLlmGate } from './goose-v149-real-llm-gate.mjs';
import {
createReporter,
extractAssistantTexts,
getSession,
loginViaApi,
resolvePortalBase,
waitForAssistantGrowth,
waitForRunTerminal,
} from './scenario-test-lib.mjs';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
enforceRealLlmGate('check-goosed-v149-memory-verbal-recall.mjs');
prepareGooseV149CheckEnv(process.env, root);
const baseUrl = resolvePortalBase(Number(process.env.H5_PORT ?? 8081));
const skip = process.env.GOOSE_V149_MEMORY_VERBAL_SKIP === '1';
const timeoutMs = Number(process.env.GOOSE_V149_MEMORY_VERBAL_TIMEOUT_MS || 240_000);
const MARKER = process.env.GOOSE_V149_MEMORY_VERBAL_MARKER
?? `gv149verb${Date.now().toString(36)}`;
const injectionMode = String(process.env.MEMORY_AGENT_INJECTION_MODE ?? 'off').trim();
async function requestJson(url, { method = 'GET', cookie, body } = {}) {
const response = await fetch(url, {
method,
headers: {
...(body ? { 'Content-Type': 'application/json' } : {}),
...(cookie ? { Cookie: cookie } : {}),
},
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 };
}
async function startSession(cookie) {
const started = await requestJson(`${baseUrl}/api/agent/start`, {
method: 'POST',
cookie,
body: {},
});
const sessionId = started.json?.id ?? null;
if (!started.ok || !sessionId) {
throw new Error(`agent/start failed: ${started.status}`);
}
return sessionId;
}
async function runChat(cookie, sessionId, text) {
const requestId = crypto.randomUUID();
const created = await requestJson(`${baseUrl}/api/agent/runs`, {
method: 'POST',
cookie,
body: {
request_id: requestId,
session_id: sessionId,
force_deep_reasoning: process.env.GOOSE_V149_MEMORY_VERBAL_FORCE_DEEP !== '0',
user_message: {
id: crypto.randomUUID(),
role: 'user',
created: Math.floor(Date.now() / 1000),
content: [{ type: 'text', text }],
metadata: { userVisible: true, agentVisible: true, displayText: text },
},
},
});
const runId = created.json?.run?.id ?? null;
if (!created.ok || created.status !== 202 || !runId) {
throw new Error(`agent run failed: ${created.status} ${created.text?.slice(0, 300)}`);
}
const terminal = await waitForRunTerminal(baseUrl, cookie, runId, timeoutMs);
if (terminal.status !== 'succeeded') {
throw new Error(`agent run terminal=${terminal.status} ${terminal.error ?? ''}`);
}
const activeSessionId = terminal.sessionId ?? terminal.agent_session_id ?? sessionId;
const reply = await waitForAssistantGrowth(baseUrl, cookie, activeSessionId, {
minChars: 1,
timeoutMs: Math.min(timeoutMs, 120_000),
});
return {
sessionId: activeSessionId,
replyText: reply?.combined ?? '',
runId,
};
}
async function cleanup(pool, userId, marker, sessionIds) {
await pool.query(
`DELETE FROM h5_user_memory_items WHERE user_id = ? AND memory_text LIKE ?`,
[userId, `%${marker}%`],
);
const [table] = await pool.query(
`SELECT 1 FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'h5_memory_v2_candidates' LIMIT 1`,
);
if (table.length) {
await pool.query(
`DELETE FROM h5_memory_v2_candidates WHERE user_id = ? AND content LIKE ?`,
[userId, `%${marker}%`],
);
}
if (sessionIds.length) {
await pool.query(
`DELETE FROM h5_conversation_messages WHERE user_id = ? AND agent_session_id IN (?)`,
[userId, sessionIds],
);
}
}
async function main() {
if (skip) {
console.log('GOOSE_V149_MEMORY_VERBAL_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 for memory verbal recall smoke');
}
const auth = await loginViaApi(baseUrl, { username, password }, reporter);
const userId = String(auth.user?.id ?? '');
const pool = createDbPool();
let sessionA = '';
let sessionB = '';
try {
sessionA = await startSession(auth.cookie);
await runChat(
auth.cookie,
sessionA,
`请记住:我的测试代号是「${MARKER}」。请简短确认已记住。`,
);
reporter.pass('memory write turn', MARKER);
const remember = await requestJson(`${baseUrl}/api/user-memory/v1/remember-recent`, {
method: 'POST',
cookie: auth.cookie,
body: { sessionId: sessionA },
});
if (!remember.ok || remember.json?.ok !== true) {
throw new Error(`remember-recent failed: ${remember.status}`);
}
await requestJson(`${baseUrl}/api/user-memory/v1/sync`, {
method: 'POST',
cookie: auth.cookie,
body: { sessionId: sessionA },
});
reporter.pass('remember/sync', `memories=${remember.json?.memories ?? 0}`);
sessionB = await startSession(auth.cookie);
const recall = await runChat(
auth.cookie,
sessionB,
`我的测试代号是什么?请只回答代号本身,不要解释。`,
);
const hit = recall.replyText.includes(MARKER);
reporter.pass('verbal recall reply', `${recall.replyText.length} chars marker=${hit}`);
if (!hit && (injectionMode === 'off' || injectionMode === 'shadow')) {
console.log(
`GOOSE_V149_MEMORY_VERBAL_OK_WITH_NOTE: injection=${injectionMode} `
+ 'verbal chat recall not expected; API resolve path covered by memory-chat smoke',
);
console.log(` marker=${MARKER} sessionB=${sessionB} reply=${recall.replyText.slice(0, 120)}`);
return;
}
if (!hit) {
throw new Error(
`assistant missed marker; injection=${injectionMode} reply=${recall.replyText.slice(0, 200)}`,
);
}
console.log(
`GOOSE_V149_MEMORY_VERBAL_OK: marker=${MARKER} injection=${injectionMode} `
+ `sessionB=${sessionB} replyChars=${recall.replyText.length}`,
);
} finally {
await cleanup(pool, userId, MARKER, [sessionA, sessionB].filter(Boolean)).catch(() => {});
await pool.end().catch(() => {});
}
}
main().catch((error) => {
console.error(`GOOSE_V149_MEMORY_VERBAL_FAIL: ${error.message}`);
process.exit(1);
});