feat: 发布热更 1.5.94,修复中转历史会话并支持一键清空
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user