Files
boss/tests/local-agent-codex-discovery.test.mjs

197 lines
5.8 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import { DatabaseSync } from "node:sqlite";
let runtimeRoot = "";
let discoverCodexProjectCandidates;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-local-agent-discovery-"));
({ discoverCodexProjectCandidates } = await import("../local-agent/codex-session-discovery.mjs"));
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test("discoverCodexProjectCandidates prefers Codex sqlite indexes and session names over raw rollout fallback", async () => {
await setup();
const codexRoot = path.join(runtimeRoot, ".codex");
const now = new Date("2026-03-30T12:45:00+08:00");
await mkdir(codexRoot, { recursive: true });
const stateDbPath = path.join(codexRoot, "state_5.sqlite");
const logsDbPath = path.join(codexRoot, "logs_1.sqlite");
const sessionIndexPath = path.join(codexRoot, "session_index.jsonl");
const globalStatePath = path.join(codexRoot, ".codex-global-state.json");
const stateDb = new DatabaseSync(stateDbPath);
stateDb.exec(`
CREATE TABLE threads (
id TEXT PRIMARY KEY,
rollout_path TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
source TEXT NOT NULL,
model_provider TEXT NOT NULL,
cwd TEXT NOT NULL,
title TEXT NOT NULL,
sandbox_policy TEXT NOT NULL,
approval_mode TEXT NOT NULL,
tokens_used INTEGER NOT NULL DEFAULT 0,
has_user_event INTEGER NOT NULL DEFAULT 0,
archived INTEGER NOT NULL DEFAULT 0,
archived_at INTEGER,
git_sha TEXT,
git_branch TEXT,
git_origin_url TEXT,
cli_version TEXT NOT NULL DEFAULT '',
first_user_message TEXT NOT NULL DEFAULT '',
agent_nickname TEXT,
agent_role TEXT,
memory_mode TEXT NOT NULL DEFAULT 'enabled',
model TEXT,
reasoning_effort TEXT,
agent_path TEXT
);
`);
const insertThread = stateDb.prepare(`
INSERT INTO threads (
id, rollout_path, created_at, updated_at, source, model_provider, cwd, title,
sandbox_policy, approval_mode, tokens_used, has_user_event, archived,
cli_version, first_user_message, agent_nickname, agent_role, memory_mode, model, reasoning_effort
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 1, ?, '0.118.0', '', ?, ?, 'enabled', 'gpt-5.4', 'medium')
`);
insertThread.run(
"019d3bossmain",
path.join(codexRoot, "sessions/2026/03/30/rollout-boss-main.jsonl"),
1774845600,
1774845618,
"desktop",
"openai",
"/Users/kris/code/boss",
"Boss 主线程",
"workspace-write",
"never",
0,
null,
null,
);
insertThread.run(
"019d3yuandiexplorer",
path.join(codexRoot, "sessions/2026/03/30/rollout-yuandi-explorer.jsonl"),
1774845760,
1774845776,
"desktop",
"openai",
"/Users/kris/.codex/worktrees/tmp123/yuandi",
"Yuandi 子线程",
"workspace-write",
"never",
0,
"Epicurus",
"explorer",
);
insertThread.run(
"019d3oldsession",
path.join(codexRoot, "sessions/2026/03/27/rollout-too-old.jsonl"),
1774584000,
1774584000,
"desktop",
"openai",
"/Users/kris/code/old-project",
"Old Session",
"workspace-write",
"never",
0,
null,
null,
);
stateDb.close();
const logsDb = new DatabaseSync(logsDbPath);
logsDb.exec(`
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts INTEGER NOT NULL,
ts_nanos INTEGER NOT NULL,
level TEXT NOT NULL,
target TEXT NOT NULL,
feedback_log_body TEXT,
module_path TEXT,
file TEXT,
line INTEGER,
thread_id TEXT,
process_uuid TEXT,
estimated_bytes INTEGER NOT NULL DEFAULT 0
);
`);
const insertLog = logsDb.prepare(`
INSERT INTO logs (ts, ts_nanos, level, target, thread_id, estimated_bytes)
VALUES (?, 0, 'info', 'codex', ?, 0)
`);
insertLog.run(1774845618, "019d3bossmain");
insertLog.run(1774845776, "019d3yuandiexplorer");
logsDb.close();
await writeFile(
sessionIndexPath,
[
JSON.stringify({
id: "019d3bossmain",
thread_name: "Boss 主线程",
updated_at: "2026-03-30T04:40:18.000000Z",
}),
JSON.stringify({
id: "019d3yuandiexplorer",
thread_name: "Epicurus",
updated_at: "2026-03-30T04:42:56.000000Z",
}),
].join("\n") + "\n",
"utf8",
);
await writeFile(
globalStatePath,
JSON.stringify(
{
"thread-workspace-root-hints": {
"019d3yuandiexplorer": "/Users/kris/code/yuandi",
},
},
null,
2,
),
"utf8",
);
const discovered = await discoverCodexProjectCandidates({
stateDbPath,
logsDbPath,
sessionIndexPath,
globalStatePath,
lookbackHours: 24,
now,
});
assert.deepEqual(discovered.projects, ["boss", "yuandi"]);
assert.equal(discovered.projectCandidates.length, 2);
const bossSession = discovered.projectCandidates.find((item) => item.threadId === "019d3bossmain");
const yuandiSession = discovered.projectCandidates.find((item) => item.threadId === "019d3yuandiexplorer");
assert.ok(bossSession);
assert.ok(yuandiSession);
assert.equal(bossSession?.folderName, "boss");
assert.equal(bossSession?.codexFolderRef, "/Users/kris/code/boss");
assert.equal(bossSession?.codexThreadRef, "019d3bossmain");
assert.equal(bossSession?.threadDisplayName, "Boss 主线程");
assert.equal(yuandiSession?.folderName, "yuandi");
assert.equal(yuandiSession?.threadDisplayName, "Epicurus");
assert.equal(yuandiSession?.codexFolderRef, "/Users/kris/code/yuandi");
});