Files
memind/scripts/check-goosed-v149-memory-verbal-recall.mjs
T
john 1d165bc6e3 feat(goose): complete v1.49 phase3 closeout gates and context fusion plan
Expand Goose v1.49 smoke coverage (memory chat, portal resume, page e2e,
multiturn provider), add canary memory policy lock, refresh baselines, and
ignore one-off evidence artifacts. Document headroom-based context runtime
fusion plan; include auth, scheduled-task, and wechat intent fixes on branch.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 18:11:39 +08:00

210 lines
6.7 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 {
createReporter,
extractAssistantTexts,
getSession,
loginViaApi,
resolvePortalBase,
waitForAssistantGrowth,
waitForRunTerminal,
} from './scenario-test-lib.mjs';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
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);
});