Files
memind/scripts/verify-session-repair-local.mjs
T
john b09b269199 chore(infra): migrate 103 production host IP to 180.159.29.143
Update scripts, docs, nginx configs, and release-gate safety checks after the Studio server public IP changed from 58.38.22.103.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-11 12:28:19 +08:00

129 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Simulate GET /api/sessions/:id repair for a session using live 103 data.
* Usage: node scripts/verify-session-repair-local.mjs [sessionId] [userId]
*/
import { createConnection } from 'mysql2/promise';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
countNonEmptyConversationMessages,
loadSessionConversationRows,
repairConversationFromDbRows,
shouldRepairConversationFromDb,
} from '../conversation-repair.mjs';
import { extractConversationMessageText } from '../conversation-memory.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = join(__dirname, '..');
const sessionId = process.argv[2] ?? '20260705_2';
const userId = process.argv[3] ?? 'a6fb1e97-2b0f-447b-b138-4561d8e5c53e';
function previewMessage(message, index) {
const text = extractConversationMessageText(message).replace(/\s+/g, ' ').slice(0, 90);
return `${index}. [${message.role}] ${text || '(empty)'}`;
}
async function load103Env() {
const { execSync } = await import('node:child_process');
const envText = execSync('ssh -o BatchMode=yes john@180.159.29.143 cat /Users/john/Project/Memind/.env', {
encoding: 'utf8',
});
const dbLine = envText.match(/^DATABASE_URL=(.+)$/m)?.[1]?.trim().replace(/^["']|["']$/g, '');
const secret = envText.match(/^TKMIND_SERVER__SECRET_KEY=(.+)$/m)?.[1]?.trim().replace(/^["']|["']$/g, '');
if (!dbLine || !secret) throw new Error('missing DATABASE_URL or TKMIND_SERVER__SECRET_KEY on 103');
const url = new URL(dbLine);
return {
pool: await createConnection({
host: url.hostname,
port: Number(url.port || 3306),
user: decodeURIComponent(url.username),
password: decodeURIComponent(url.password),
database: url.pathname.replace(/^\//, ''),
}),
secret,
};
}
async function fetchGooseSession(secret, sid) {
const { execSync } = await import('node:child_process');
const json = execSync(
`ssh -o BatchMode=yes john@180.159.29.143 'SECRET=$(grep ^TKMIND_SERVER__SECRET_KEY= /Users/john/Project/Memind/.env | cut -d= -f2- | tr -d "\\""); curl -k -sf "https://127.0.0.1:18006/sessions/${sid}" -H "X-Secret-Key: $SECRET"'`,
{ encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 },
);
return JSON.parse(json);
}
async function fetchProductionApi(sessionId) {
const { execSync } = await import('node:child_process');
const login = execSync(
`curl -sk -c /tmp/verify-session-repair-cj.txt -X POST https://m.tkmind.cn/auth/login -H 'Content-Type: application/json' -d '{"username":"john","password":"981122tj"}'`,
{ encoding: 'utf8' },
);
const auth = JSON.parse(login);
if (!auth.authenticated) throw new Error('login failed');
const detail = execSync(
`curl -sk -b /tmp/verify-session-repair-cj.txt 'https://m.tkmind.cn/api/sessions/${encodeURIComponent(sessionId)}'`,
{ encoding: 'utf8' },
);
return JSON.parse(detail);
}
async function main() {
console.log(`\n=== 模拟验证 session ${sessionId} (user ${userId}) ===\n`);
const { pool, secret } = await load103Env();
const gooseSession = await fetchGooseSession(secret, sessionId);
const dbRows = await loadSessionConversationRows(pool, sessionId, userId);
await pool.end();
const gooseConversation = (gooseSession.conversation ?? []).filter(
(m) => m?.metadata?.userVisible !== false,
);
const repairedConversation = repairConversationFromDbRows(gooseConversation, dbRows);
console.log('--- Goose 原始(API 发版前会返回这类数据)---');
console.log(`消息数: ${gooseConversation.length}, 非空: ${countNonEmptyConversationMessages(gooseConversation)}`);
gooseConversation.forEach((m, i) => console.log(previewMessage(m, i)));
console.log('\n--- DB h5_conversation_messages ---');
console.log(`行数: ${dbRows.length}`);
dbRows.forEach((row, i) => {
const text = String(row.text ?? '').replace(/\s+/g, ' ').slice(0, 90);
console.log(`${i}. [${row.role}] seq=${row.sequence_no} ${text || '(empty)'}`);
});
console.log(`\nshouldRepair: ${shouldRepairConversationFromDb(gooseConversation, dbRows)}`);
console.log('\n--- 本地 repair 后(发版后 API 应接近此结果)---');
console.log(`消息数: ${repairedConversation.length}, 非空: ${countNonEmptyConversationMessages(repairedConversation)}`);
repairedConversation.forEach((m, i) => console.log(previewMessage(m, i)));
console.log('\n--- 当前生产 API(未发版,无 repair---');
try {
const prod = await fetchProductionApi(sessionId);
const prodConv = (prod.conversation ?? []).filter((m) => m?.metadata?.userVisible !== false);
console.log(`name: ${prod.name}`);
console.log(`消息数: ${prodConv.length}, 非空: ${countNonEmptyConversationMessages(prodConv)}`);
prodConv.forEach((m, i) => console.log(previewMessage(m, i)));
} catch (err) {
console.log(`生产 API 拉取失败: ${err instanceof Error ? err.message : err}`);
}
console.log('\n=== 结论 ===');
const repairedNonEmpty = countNonEmptyConversationMessages(repairedConversation);
const gooseNonEmpty = countNonEmptyConversationMessages(gooseConversation);
if (repairedNonEmpty > gooseNonEmpty) {
console.log(`✔ repair 后非空消息 ${gooseNonEmpty}${repairedNonEmpty},发版后 john 应能看到完整历史`);
} else {
console.log(`✖ repair 未改善非空消息数 (${gooseNonEmpty}${repairedNonEmpty})`);
process.exitCode = 1;
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});