614837e199
Portal direct chat → goosed escalation can leave hollow Goose messages while h5_conversation_messages retains the full dialogue. Backfill session detail from DB, extend snapshot list fallback, and keep stored session id on transient boot failures so refresh no longer looks like a blank new chat. Co-authored-by: Cursor <cursoragent@cursor.com>
129 lines
5.4 KiB
JavaScript
129 lines
5.4 KiB
JavaScript
#!/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@58.38.22.103 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@58.38.22.103 '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);
|
||
});
|