188 lines
6.0 KiB
JavaScript
188 lines
6.0 KiB
JavaScript
|
|
// session_sync_enhanced.js —— 增强版会话同步:确保本地所有会话一个不剩地同步到新 Codex 账号。
|
|||
|
|
// 在 provider_sync.js 的基础上增强,确保:
|
|||
|
|
// 1. 扫描所有 sessions/ 和 archived_sessions/ 下的会话
|
|||
|
|
// 2. 强制插入到当前账号的数据库(UPSERT 逻辑)
|
|||
|
|
// 3. 重建 session_index.jsonl
|
|||
|
|
// 4. 更新全局项目索引
|
|||
|
|
// 5. 提供详细的同步报告
|
|||
|
|
|
|||
|
|
const fs = require("fs");
|
|||
|
|
const path = require("path");
|
|||
|
|
const os = require("os");
|
|||
|
|
const providerSync = require("./provider_sync");
|
|||
|
|
|
|||
|
|
const CODEX_HOME = path.join(os.homedir(), ".codex");
|
|||
|
|
const SESSION_INDEX = path.join(CODEX_HOME, "session_index.jsonl");
|
|||
|
|
|
|||
|
|
// 完整同步方案:确保所有本地会话都进入新账号
|
|||
|
|
async function syncAllSessionsToNewAccount(opts = {}) {
|
|||
|
|
const log = opts.log || (() => {});
|
|||
|
|
const onProgress = opts.onProgress || (() => {});
|
|||
|
|
|
|||
|
|
log("=== 开始全量会话同步 ===");
|
|||
|
|
log("目标:将本地所有会话同步到当前 Codex 账号");
|
|||
|
|
|
|||
|
|
// 1. 检查 Codex 是否在运行
|
|||
|
|
if (providerSync.codexRunning()) {
|
|||
|
|
throw new Error("检测到 Codex 正在运行,请先完全退出 Codex 再同步。");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 获取当前 provider(从 config.toml)
|
|||
|
|
const targetProvider = providerSync.targetProvider();
|
|||
|
|
log(`当前账号 provider:${targetProvider}`);
|
|||
|
|
|
|||
|
|
// 3. 执行核心同步(provider_sync 已经做了 UPSERT)
|
|||
|
|
log("开始同步本地会话到数据库...");
|
|||
|
|
const result = await providerSync.syncProviderToCurrent(log, onProgress);
|
|||
|
|
|
|||
|
|
// 4. 重建 session_index.jsonl(确保索引完整)
|
|||
|
|
log("重建会话索引...");
|
|||
|
|
const indexResult = rebuildSessionIndex(log);
|
|||
|
|
|
|||
|
|
// 5. 生成同步报告
|
|||
|
|
const report = {
|
|||
|
|
success: true,
|
|||
|
|
provider: result.target,
|
|||
|
|
totalSessions: result.totalConversations,
|
|||
|
|
syncedToDatabase: result.insertedRows,
|
|||
|
|
updatedProvider: result.provRows,
|
|||
|
|
updatedUserEvent: result.ueRows,
|
|||
|
|
indexRebuilt: indexResult.total,
|
|||
|
|
projects: result.projectRows,
|
|||
|
|
backup: result.backup,
|
|||
|
|
details: {
|
|||
|
|
changedFiles: result.changedFiles,
|
|||
|
|
userEventSessions: result.userEvents,
|
|||
|
|
archivedSessions: indexResult.archived,
|
|||
|
|
activeSessions: indexResult.active,
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
log("\n=== 同步完成 ===");
|
|||
|
|
log(`✅ 总会话数:${report.totalSessions}`);
|
|||
|
|
log(`✅ 新增到数据库:${report.syncedToDatabase} 条`);
|
|||
|
|
log(`✅ 更新 provider:${report.updatedProvider} 条`);
|
|||
|
|
log(`✅ 重建索引:${report.indexRebuilt} 条`);
|
|||
|
|
log(`✅ 项目分组:${report.projects} 个`);
|
|||
|
|
log(`✅ 备份位置:${report.backup}`);
|
|||
|
|
log("\n现在可以启动 Codex,所有会话应该都能看到了。");
|
|||
|
|
|
|||
|
|
return report;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重建 session_index.jsonl:扫描所有会话文件,生成完整索引
|
|||
|
|
function rebuildSessionIndex(log = () => {}) {
|
|||
|
|
const sessions = require("./sessions");
|
|||
|
|
|
|||
|
|
// 列出所有会话(包括 archived)
|
|||
|
|
const allConversations = sessions.listConversations({
|
|||
|
|
includeSubagent: true,
|
|||
|
|
includeArchived: true,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
log(`扫描到 ${allConversations.length} 个会话文件`);
|
|||
|
|
|
|||
|
|
// 备份旧索引
|
|||
|
|
if (fs.existsSync(SESSION_INDEX)) {
|
|||
|
|
const backup = SESSION_INDEX + ".bak-" + Date.now();
|
|||
|
|
fs.copyFileSync(SESSION_INDEX, backup);
|
|||
|
|
log(`已备份旧索引:${path.basename(backup)}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重建索引:每个会话一行 JSON
|
|||
|
|
const lines = [];
|
|||
|
|
let active = 0;
|
|||
|
|
let archived = 0;
|
|||
|
|
|
|||
|
|
for (const conv of allConversations) {
|
|||
|
|
if (!conv.id) continue;
|
|||
|
|
|
|||
|
|
const indexEntry = {
|
|||
|
|
id: conv.id,
|
|||
|
|
thread_name: conv.title || "(无标题对话)",
|
|||
|
|
project_path: conv.cwd || null,
|
|||
|
|
created_at: conv.ts || new Date().toISOString(),
|
|||
|
|
source: conv.source || "vscode",
|
|||
|
|
archived: conv.archived || false,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
lines.push(JSON.stringify(indexEntry));
|
|||
|
|
|
|||
|
|
if (conv.archived) archived++;
|
|||
|
|
else active++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 写入新索引
|
|||
|
|
fs.writeFileSync(SESSION_INDEX, lines.join("\n") + "\n", "utf-8");
|
|||
|
|
log(`已重建索引:活跃 ${active} 条,归档 ${archived} 条`);
|
|||
|
|
|
|||
|
|
return { total: lines.length, active, archived };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 诊断:检查会话同步状态
|
|||
|
|
function diagnoseSessionSync(log = () => {}) {
|
|||
|
|
const sessions = require("./sessions");
|
|||
|
|
|
|||
|
|
log("=== 会话同步诊断 ===");
|
|||
|
|
|
|||
|
|
// 1. 统计本地会话文件
|
|||
|
|
const allConversations = sessions.listConversations({
|
|||
|
|
includeSubagent: true,
|
|||
|
|
includeArchived: true,
|
|||
|
|
});
|
|||
|
|
const activeSessions = allConversations.filter(c => !c.archived);
|
|||
|
|
const archivedSessions = allConversations.filter(c => c.archived);
|
|||
|
|
|
|||
|
|
log(`本地会话文件:${allConversations.length} 个`);
|
|||
|
|
log(` - 活跃:${activeSessions.length} 个`);
|
|||
|
|
log(` - 归档:${archivedSessions.length} 个`);
|
|||
|
|
|
|||
|
|
// 2. 检查数据库
|
|||
|
|
const dbFiles = fs.readdirSync(CODEX_HOME).filter(f => /^state_.*\.sqlite$/.test(f));
|
|||
|
|
log(`数据库文件:${dbFiles.length} 个`);
|
|||
|
|
for (const db of dbFiles) {
|
|||
|
|
log(` - ${db}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 检查索引文件
|
|||
|
|
const indexExists = fs.existsSync(SESSION_INDEX);
|
|||
|
|
log(`session_index.jsonl:${indexExists ? "存在" : "不存在"}`);
|
|||
|
|
|
|||
|
|
if (indexExists) {
|
|||
|
|
const indexContent = fs.readFileSync(SESSION_INDEX, "utf-8");
|
|||
|
|
const indexLines = indexContent.split("\n").filter(l => l.trim());
|
|||
|
|
log(` - 索引条目:${indexLines.length} 条`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. 检查 provider
|
|||
|
|
const currentProvider = providerSync.targetProvider();
|
|||
|
|
log(`当前 provider:${currentProvider}`);
|
|||
|
|
|
|||
|
|
// 5. 检查会话文件中的 provider 分布
|
|||
|
|
const providerStats = {};
|
|||
|
|
for (const conv of allConversations) {
|
|||
|
|
const p = conv.provider || "(未设置)";
|
|||
|
|
providerStats[p] = (providerStats[p] || 0) + 1;
|
|||
|
|
}
|
|||
|
|
log("会话文件 provider 分布:");
|
|||
|
|
for (const [p, count] of Object.entries(providerStats)) {
|
|||
|
|
log(` - ${p}: ${count} 个`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
totalSessions: allConversations.length,
|
|||
|
|
activeSessions: activeSessions.length,
|
|||
|
|
archivedSessions: archivedSessions.length,
|
|||
|
|
databases: dbFiles.length,
|
|||
|
|
indexExists,
|
|||
|
|
currentProvider,
|
|||
|
|
providerStats,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = {
|
|||
|
|
syncAllSessionsToNewAccount,
|
|||
|
|
rebuildSessionIndex,
|
|||
|
|
diagnoseSessionSync,
|
|||
|
|
};
|