import os from "node:os"; import { basename, resolve } from "node:path"; import { readFileSync } from "node:fs"; import { readFile, readdir } from "node:fs/promises"; import { DatabaseSync } from "node:sqlite"; import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; function toIsoFromUnixSeconds(value) { if (!Number.isFinite(value) || value <= 0) return null; return new Date(value * 1000).toISOString(); } function sanitizeDisplayName(raw, fallback) { const source = typeof raw === "string" ? raw : ""; const firstLine = source .replace(/\u0000/g, "") .split(/\r?\n/) .map((line) => line.trim()) .find(Boolean); if (!firstLine) return fallback; const compact = firstLine.replace(/\s+/g, " ").trim(); if (!compact) return fallback; return compact.length > 48 ? `${compact.slice(0, 45)}...` : compact; } function fallbackDisplayName(thread, folderName) { const suffix = thread.id.replace(/-/g, "").slice(0, 6); if (thread.agentNickname) { return thread.agentNickname; } if (thread.agentRole) { return `${thread.agentRole} · ${suffix}`; } return `${folderName} · ${suffix}`; } function trimToDefined(value) { const trimmed = typeof value === "string" ? value.trim() : ""; return trimmed ? trimmed : null; } function parseSandboxPolicyType(value) { const raw = trimToDefined(value); if (!raw) return null; try { const parsed = JSON.parse(raw); return trimToDefined(parsed?.type) ?? raw; } catch { return raw; } } function isReadOnlySandboxPolicy(value) { return parseSandboxPolicyType(value) === "read-only"; } function isPrimaryWorkspaceThread(thread) { return !trimToDefined(thread.agentRole) && !trimToDefined(thread.agentNickname); } function loadThreadWorkspaceHints(globalStatePath) { if (!globalStatePath) return new Map(); try { const parsed = JSON.parse(requireText(globalStatePath)); return new Map(Object.entries(parsed["thread-workspace-root-hints"] ?? {})); } catch { return new Map(); } } function loadSessionNames(sessionIndexPath) { if (!sessionIndexPath) return new Map(); const names = new Map(); try { const raw = requireText(sessionIndexPath); for (const line of raw.split(/\r?\n/)) { if (!line.trim()) continue; const parsed = JSON.parse(line); if (!parsed?.id) continue; const previous = names.get(parsed.id); if (!previous || String(parsed.updated_at ?? "") >= String(previous.updatedAt ?? "")) { names.set(parsed.id, { threadName: parsed.thread_name, updatedAt: parsed.updated_at, }); } } } catch { return names; } return names; } function loadRecentThreadActivity(logsDbPath) { if (!logsDbPath) return new Map(); try { const db = new DatabaseSync(logsDbPath, { readonly: true }); try { const rows = db .prepare("SELECT thread_id, MAX(ts) AS latest_ts FROM logs WHERE thread_id IS NOT NULL GROUP BY thread_id") .all(); return new Map( rows .filter((row) => typeof row.thread_id === "string" && Number.isFinite(row.latest_ts)) .map((row) => [row.thread_id, Number(row.latest_ts)]), ); } finally { db.close(); } } catch { return new Map(); } } function loadThreadsFromStateDb(stateDbPath) { if (!stateDbPath) return []; try { const db = new DatabaseSync(stateDbPath, { readonly: true }); try { return db .prepare( "SELECT id, cwd, updated_at, archived, title, sandbox_policy, agent_nickname, agent_role FROM threads WHERE archived = 0 ORDER BY updated_at DESC", ) .all() .map((row) => ({ id: String(row.id), cwd: String(row.cwd), updatedAtSeconds: Number(row.updated_at), archived: Boolean(row.archived), title: String(row.title ?? ""), sandboxPolicy: typeof row.sandbox_policy === "string" ? row.sandbox_policy : "", agentNickname: typeof row.agent_nickname === "string" ? row.agent_nickname : "", agentRole: typeof row.agent_role === "string" ? row.agent_role : "", })); } finally { db.close(); } } catch { return []; } } function parseSessionMeta(line) { try { const parsed = JSON.parse(line); if (parsed?.type !== "session_meta" || !parsed?.payload?.id || !parsed?.payload?.cwd) { return null; } return { id: String(parsed.payload.id), cwd: String(parsed.payload.cwd), updatedAtSeconds: Math.floor(new Date(parsed.payload.timestamp ?? parsed.timestamp ?? Date.now()).getTime() / 1000), archived: false, title: "", agentNickname: typeof parsed.payload.agent_nickname === "string" ? parsed.payload.agent_nickname : "", agentRole: typeof parsed.payload.agent_role === "string" ? parsed.payload.agent_role : "", }; } catch { return null; } } async function loadThreadsFromSessions(sessionsDir) { if (!sessionsDir) return []; const pending = [resolve(sessionsDir)]; const threads = []; while (pending.length > 0) { const dir = pending.pop(); if (!dir) continue; let entries = []; try { entries = await readdir(dir, { withFileTypes: true }); } catch { continue; } for (const entry of entries) { const fullPath = `${dir}/${entry.name}`; if (entry.isDirectory()) { pending.push(fullPath); continue; } if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue; try { const raw = await readFile(fullPath, "utf8"); const firstLine = raw.split(/\r?\n/, 1)[0]; const parsed = parseSessionMeta(firstLine); if (parsed) threads.push(parsed); } catch { continue; } } } return threads; } function requireText(filePath) { return readFileSync(resolve(filePath), "utf8"); } export async function discoverCodexProjectCandidates(options = {}) { const now = options.now instanceof Date ? options.now : new Date(); const lookbackHours = Number.isFinite(options.lookbackHours) ? Number(options.lookbackHours) : 24; const cutoffSeconds = Math.floor(now.getTime() / 1000) - lookbackHours * 60 * 60; const sessionNames = loadSessionNames(options.sessionIndexPath ?? resolve(os.homedir(), ".codex/session_index.jsonl")); const workspaceHints = loadThreadWorkspaceHints( options.globalStatePath ?? resolve(os.homedir(), ".codex/.codex-global-state.json"), ); const latestLogByThread = loadRecentThreadActivity( options.logsDbPath ?? resolve(os.homedir(), ".codex/logs_1.sqlite"), ); let threads = loadThreadsFromStateDb( options.stateDbPath ?? resolve(os.homedir(), ".codex/state_5.sqlite"), ); if (threads.length === 0) { threads = await loadThreadsFromSessions( options.sessionsDir ?? resolve(os.homedir(), ".codex/sessions"), ); } const seenThreadIds = new Set(); const groupedCandidates = new Map(); for (const thread of threads) { if (!thread?.id || seenThreadIds.has(thread.id)) continue; if (isReadOnlySandboxPolicy(thread.sandboxPolicy)) { continue; } const latestActivitySeconds = latestLogByThread.get(thread.id) ?? thread.updatedAtSeconds; if (!Number.isFinite(latestActivitySeconds) || latestActivitySeconds < cutoffSeconds) { continue; } seenThreadIds.add(thread.id); const hintedPath = workspaceHints.get(thread.id); const folderPath = resolve(hintedPath || thread.cwd || ""); const folderName = basename(folderPath); if (!folderName) continue; const sessionName = sessionNames.get(thread.id)?.threadName; const displayName = sanitizeDisplayName( sessionName, sanitizeDisplayName(thread.title, fallbackDisplayName(thread, folderName)), ); const candidate = { folderName, folderRef: folderPath, threadId: thread.id, threadDisplayName: displayName, codexFolderRef: folderPath, codexThreadRef: thread.id, lastActiveAt: toIsoFromUnixSeconds(latestActivitySeconds) ?? now.toISOString(), suggestedImport: true, }; const folderKey = folderPath || folderName; const bucket = groupedCandidates.get(folderKey) ?? []; bucket.push({ candidate, latestActivitySeconds, primary: isPrimaryWorkspaceThread(thread), }); groupedCandidates.set(folderKey, bucket); } const candidates = []; for (const entries of groupedCandidates.values()) { entries.sort((left, right) => right.latestActivitySeconds - left.latestActivitySeconds); const primaryEntries = entries.filter((entry) => entry.primary); const chosenEntries = primaryEntries.length > 0 ? primaryEntries : entries.slice(0, 1); candidates.push(...chosenEntries.map((entry) => entry.candidate)); } candidates.sort((a, b) => b.lastActiveAt.localeCompare(a.lastActiveAt)); const projects = [...new Set(candidates.map((candidate) => candidate.folderName))].sort((a, b) => a.localeCompare(b), ); return { projects, projectCandidates: candidates, }; } export async function discoverCodexProjectCandidatesInWorker(options = {}) { return await new Promise((resolvePromise, rejectPromise) => { const worker = new Worker(new URL(import.meta.url), { workerData: { kind: "boss_codex_discovery", options, }, }); worker.once("message", (payload) => { if (payload?.ok) { resolvePromise(payload.result); return; } rejectPromise(new Error(payload?.error ?? "DISCOVERY_WORKER_FAILED")); }); worker.once("error", rejectPromise); worker.once("exit", (code) => { if (code === 0) { return; } rejectPromise(new Error(`DISCOVERY_WORKER_EXIT_${code}`)); }); }); } if (!isMainThread && workerData?.kind === "boss_codex_discovery") { try { const result = await discoverCodexProjectCandidates(workerData.options ?? {}); parentPort?.postMessage({ ok: true, result }); } catch (error) { parentPort?.postMessage({ ok: false, error: error instanceof Error ? error.message : String(error), }); } }