Files
codex-market-app/electron/lib/session_sync_enhanced.js
T
mac 02fb553c9e feat: enhance session sync and add voice input feature
- 增强会话同步:确保所有会话完全同步到新账号
  - 强制更新 model_provider 字段
  - 添加 session_index.jsonl 重建功能
  - 确保数据库 UPSERT 逻辑正确处理所有情况
- 新增语音输入功能(voice_cdp.js, voice_inject.js)
- 测试脚本和增强模块

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 19:16:45 +08:00

188 lines
6.0 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.
// 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,
};