// 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, };