feat: 发布热更 1.5.94,修复中转历史会话并支持一键清空

This commit is contained in:
mac
2026-09-11 09:06:41 +08:00
parent 0446d09b3b
commit a74576c3e1
28 changed files with 3217 additions and 155 deletions
+76 -1
View File
@@ -41,6 +41,26 @@ function writeSubagentRollout(root, id, parentId) {
].join("\n") + "\n");
}
async function writeLegacyPollutedState(root, id, subId) {
const SQL = await initSqlJs({ locateFile: (file) => path.join(__dirname, "../node_modules/sql.js/dist", file) });
const state = new SQL.Database();
state.run("CREATE TABLE threads (id TEXT PRIMARY KEY, source TEXT, thread_source TEXT, has_user_event INTEGER)");
state.run("INSERT INTO threads VALUES (?,?,?,?)", [id, "vscode", "user", 1]);
state.run("INSERT INTO threads VALUES (?,?,?,?)", [subId, "[object Object]", "subagent", 1]);
fs.writeFileSync(path.join(root, "state_5.sqlite"), Buffer.from(state.export()));
state.close();
fs.mkdirSync(path.join(root, "sqlite"), { recursive: true });
const catalog = new SQL.Database();
catalog.run("CREATE TABLE local_thread_catalog (thread_id TEXT PRIMARY KEY, thread_source TEXT)");
catalog.run("CREATE TABLE local_thread_catalog_metadata (id INTEGER PRIMARY KEY, catalog_revision INTEGER NOT NULL DEFAULT 0)");
catalog.run("INSERT INTO local_thread_catalog VALUES (?,?)", [id, "user"]);
catalog.run("INSERT INTO local_thread_catalog VALUES (?,?)", [subId, "subagent"]);
catalog.run("INSERT INTO local_thread_catalog_metadata VALUES (1,0)");
fs.writeFileSync(path.join(root, "sqlite", "codex-dev.db"), Buffer.from(catalog.export()));
catalog.close();
}
test("会话恢复在没有 sqlite3 时插入线程并创建侧栏目录", { concurrency: false }, async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "codex-session-restore-"));
const dataDir = path.join(root, "codex");
@@ -146,6 +166,7 @@ test("subagent source 对象不再让 Windows(sql.js) 路径整批失败,且
const subSource = state.exec(`SELECT source FROM threads WHERE id='${subId}'`)[0].values[0][0];
assert.ok(subSource.startsWith('{"subagent"'), `source 应为 JSON 字符串,实际: ${subSource}`);
assert.ok(subSource.includes("Dirac"));
assert.equal(state.exec(`SELECT has_user_event FROM threads WHERE id='${subId}'`)[0].values[0][0], 0);
state.close();
// 自建 codex-dev.db 必须带 user_version(对齐 Codex 迁移链 v30),
@@ -153,7 +174,61 @@ test("subagent source 对象不再让 Windows(sql.js) 路径整批失败,且
const catalog = new SQL.Database(fs.readFileSync(path.join(dataDir, "sqlite", "codex-dev.db")));
const uv = catalog.exec("PRAGMA user_version")[0].values[0][0];
assert.equal(uv, 30);
assert.equal(catalog.exec("SELECT COUNT(*) FROM local_thread_catalog")[0].values[0][0], 2);
// 子任务仍进 state 库,但不再作为 Codex 顶层侧栏任务展示;
// 否则大量 subagent 会话会污染侧栏,点击派生线程容易一直转圈。
assert.equal(catalog.exec("SELECT COUNT(*) FROM local_thread_catalog")[0].values[0][0], 1);
catalog.close();
} finally {
Module._load = originalLoad;
childProcess.spawnSync = originalSpawnSync;
global.__BUNDLE_ROOT__ = originalBundleRoot;
delete require.cache[PROVIDER_MODULE];
delete require.cache[DESKTOP_MODULE];
fs.rmSync(root, { recursive: true, force: true });
}
});
test("启动轻量迁移清理旧 subagent 顶层目录污染", { concurrency: false }, async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "codex-session-repair-"));
const dataDir = path.join(root, "codex");
const userData = path.join(root, "app-data");
const id = "dddddddd-dddd-4ddd-8ddd-dddddddddddd";
const subId = "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee";
const originalLoad = Module._load;
const originalSpawnSync = childProcess.spawnSync;
const originalBundleRoot = global.__BUNDLE_ROOT__;
try {
fs.mkdirSync(userData, { recursive: true });
fs.mkdirSync(dataDir, { recursive: true });
await writeLegacyPollutedState(dataDir, id, subId);
Module._load = function (request, parent, isMain) {
if (request === "electron") return { app: { getPath: () => userData } };
return originalLoad.call(this, request, parent, isMain);
};
childProcess.spawnSync = function (command, args, options) {
if (command === "sqlite3") return { error: new Error("sqlite3 not installed"), status: null, stdout: "", stderr: "" };
return originalSpawnSync.call(this, command, args, options);
};
delete require.cache[PROVIDER_MODULE];
delete require.cache[DESKTOP_MODULE];
global.__BUNDLE_ROOT__ = path.resolve(__dirname, "..");
const provider = require(PROVIDER_MODULE);
const desktop = require(DESKTOP_MODULE);
desktop.codexRunning = () => false;
provider.setCodexDataDir(dataDir);
const result = await provider.repairLegacySessionVisibility(() => {});
assert.equal(result.stateRows, 1);
assert.equal(result.catalogRows, 1);
const SQL = await initSqlJs({ locateFile: (file) => path.join(__dirname, "../node_modules/sql.js/dist", file) });
const state = new SQL.Database(fs.readFileSync(path.join(dataDir, "state_5.sqlite")));
assert.equal(state.exec(`SELECT has_user_event FROM threads WHERE id='${subId}'`)[0].values[0][0], 0);
state.close();
const catalog = new SQL.Database(fs.readFileSync(path.join(dataDir, "sqlite", "codex-dev.db")));
assert.equal(catalog.exec("SELECT COUNT(*) FROM local_thread_catalog")[0].values[0][0], 1);
assert.equal(catalog.exec(`SELECT COUNT(*) FROM local_thread_catalog WHERE thread_id='${subId}'`)[0].values[0][0], 0);
catalog.close();
} finally {
Module._load = originalLoad;