Something went wrong. Try again.
presents omp as codex for use with chatgpt remote
Something went wrong. Try again.
29 kB · 740 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741import fs from "node:fs/promises";import os from "node:os";import path from "node:path";import { makeThread, makeTurn, makeUserMessageItem, makeAgentMessageItem, makeReasoningItem, makeCommandItem, makeFileChangeItem, makeDynamicToolCallItem, makeImageViewItem, makeMcpToolCallItem, makeWebSearchItem, makeContextCompactionItem, makeUserInputResponseItem, makeTokenUsage,} from "./protocol.mjs";import { id, askAnswersFromResult, commandActionsForOmpTool, fileChangesFromOmpEditDetails, formatToolCommand, extractReasoningSummaryAndContent, extractToolOutputText, hashlinePatchPaths, isNonFilePath, paginateArray } from "./util.mjs";export function defaultSessionsDir() { return process.env.PI_CODING_AGENT_DIR ? path.join(process.env.PI_CODING_AGENT_DIR, "sessions") : path.join(os.homedir(), ".omp", "agent", "sessions");}
export function cwdToSessionDirName(cwd) { const home = os.homedir(); const resolved = path.resolve(cwd || process.cwd()); if (resolved.startsWith(home)) { const rel = path.relative(home, resolved); if (!rel || rel === ".") return "-"; return "-" + rel.replace(/[/\\]+/g, "-"); } return "--" + resolved.replace(/^[/\\]+/, "").replace(/[/\\]+/g, "-") + "--";}
const EXPANDABLE_OMP_TOOL_NAMES = new Set([ "read", "glob", "grep", "eval", "task", "hub", "lsp", "checkpoint", "rewind", "learn", "manage_skill",]);
function makeHistoricalToolItem(call, cwd) { const name = String(call.name || "tool"); const normalized = name.toLowerCase(); const args = call.arguments && typeof call.arguments === "object" ? call.arguments : {}; const itemId = call.id || id("item"); if (["bash", "sh", "shell", "exec", "command"].includes(normalized)) { const item = makeCommandItem(itemId, formatToolCommand(name, args, call.intent), cwd); return item; } if (normalized === "ask") { return makeUserInputResponseItem(itemId, { requestId: itemId, questions: Array.isArray(args.questions) ? args.questions : [], }); } if (EXPANDABLE_OMP_TOOL_NAMES.has(normalized)) { const command = formatToolCommand(name, args, call.intent); const item = makeCommandItem(itemId, command, cwd); item.commandActions = commandActionsForOmpTool(name, args, command, cwd); return item; } const fileTarget = args.path || args.filePath || args.file; const patchTargets = hashlinePatchPaths(args.input ?? args.patch); if (/^(?:edit|write|file|apply[_-]?patch|ast[_-]?edit)$/.test(normalized)) { if (fileTarget && isNonFilePath(fileTarget)) { // same as the live mapper, a device write has to stay a visible command const command = formatToolCommand(name, args, call.intent); const item = makeCommandItem(itemId, command, cwd); return item; } const files = fileTarget ? [fileTarget] : patchTargets; const changes = Array.isArray(args.changes) && args.changes.length ? args.changes : files.map((file) => ({ path: file, kind: "update", diff: args.diff || "" })); const item = makeFileChangeItem(itemId, changes); return item; } if (normalized === "web_search" || normalized === "websearch") return makeWebSearchItem(itemId, args); if (normalized === "inspect_image" || normalized === "image_view") { const imagePath = args.path || args.filePath || args.file || ""; return makeImageViewItem(itemId, imagePath && !path.isAbsolute(imagePath) ? path.resolve(cwd, imagePath) : imagePath); } const mcp = parseHistoricalMcpToolName(name); if (mcp) return makeMcpToolCallItem(itemId, mcp.server, mcp.tool, args); return makeDynamicToolCallItem(itemId, name, args, "oh-my-pi");}
function parseHistoricalMcpToolName(toolName) { const raw = String(toolName); if (raw.toLowerCase().startsWith("mcp__")) { const rest = raw.slice(5); const separator = rest.indexOf("_"); if (separator > 0 && separator < rest.length - 1) { return { server: rest.slice(0, separator), tool: rest.slice(separator + 1) }; } } const match = /^mcp[:\/]([^:\/]+)[:\/](.+)$/i.exec(raw); return match ? { server: match[1], tool: match[2] } : null;}
export function completeHistoricalToolItem(item, result) { const output = extractToolOutputText(result.content); const failed = Boolean(result.isError); const durationMs = result.details?.wallTimeMs ?? result.details?.durationMs ?? result.durationMs ?? null; item.status = failed ? "failed" : "completed"; if (item.type === "commandExecution") { item.aggregatedOutput = output; item.exitCode = failed ? 1 : 0; item.durationMs = durationMs != null ? Math.max(1, Math.round(durationMs)) : (item.durationMs || 50); } else if (item.type === "userInputResponse") { item.answers = askAnswersFromResult(item.questions, result.details, output); item.status = failed ? "failed" : "completed"; } else if (item.type === "fileChange") { const changes = fileChangesFromOmpEditDetails(result.details, item.changes); if (changes.length) item.changes = changes; } else if (item.type === "dynamicToolCall") { item.contentItems = historicalDynamicContentItems(result.content, output); item.success = !failed; item.durationMs = durationMs == null ? null : Math.max(1, Math.round(durationMs)); } else if (item.type === "mcpToolCall") { const rawContent = result.details?.rawContent || result.content; item.result = failed ? null : { content: Array.isArray(rawContent) ? rawContent : (output ? [{ type: "text", text: output }] : []), structuredContent: result.details?.structuredContent || null, _meta: result.details?.mcpMeta || null, }; item.error = failed ? { message: output || "Tool failed" } : null; item.durationMs = durationMs == null ? null : Math.max(1, Math.round(durationMs)); } else if (item.type === "webSearch") { const results = result.details?.results; if (Array.isArray(results)) item.results = results; }}
function historicalDynamicContentItems(content, fallbackText = "") { const items = (Array.isArray(content) ? content : []).flatMap((part) => { if (typeof part === "string") return [{ type: "inputText", text: part }]; if (!part || typeof part !== "object") return []; if (part.type === "text" || part.type === "inputText") return [{ type: "inputText", text: String(part.text || "") }]; if (part.type === "image" || part.type === "inputImage") { const imageUrl = part.imageUrl || part.image_url || part.url || (part.data && part.mimeType ? `data:${part.mimeType};base64,${part.data}` : null); return imageUrl ? [{ type: "inputImage", imageUrl }] : []; } if (part.type === "audio" || part.type === "inputAudio") { const audioUrl = part.audioUrl || part.audio_url || part.url || (part.data && part.mimeType ? `data:${part.mimeType};base64,${part.data}` : null); return audioUrl ? [{ type: "inputAudio", audioUrl }] : []; } return []; }); return items.length ? items : (fallbackText ? [{ type: "inputText", text: fallbackText }] : []);}
const sessionDirCache = new Map();
export async function findSessionDirForCwd(cwd, baseDir = null) { const resolvedBaseDir = baseDir || defaultSessionsDir(); const cacheKey = `${resolvedBaseDir}:${cwd}`; if (sessionDirCache.has(cacheKey)) return sessionDirCache.get(cacheKey);
const directName = cwdToSessionDirName(cwd); const directPath = path.join(resolvedBaseDir, directName); try { const stat = await fs.stat(directPath); if (stat.isDirectory()) { sessionDirCache.set(cacheKey, directPath); return directPath; } } catch {}
sessionDirCache.set(cacheKey, directPath); return directPath;}
function modelSelectorFromChange(entry) { if (entry?.model) return entry.model; if (entry?.provider && entry?.modelId) return `${entry.provider}/${entry.modelId}`; return entry?.modelId || null;}
function normalizeOmpModel(activeModel) { const backendProvider = activeModel && activeModel.includes("/") ? activeModel.split("/")[0] : "openai"; const modelProvider = backendProvider === "openai-codex" ? "openai" : backendProvider; const modelId = activeModel && activeModel.includes("/") ? activeModel.slice(activeModel.indexOf("/") + 1) : (activeModel || null); const model = modelId && activeModel?.includes("/") ? { id: modelId, provider: modelProvider, selector: activeModel } : modelId; return { model, modelProvider };}
const headerCache = new Map();const MAX_HEADER_CACHE_SIZE = 5000;let globalIndexCache = null;let globalIndexCacheDir = null;let globalIndexCacheTime = 0;const GLOBAL_INDEX_TTL_MS = 2000;
export const DEFAULT_EXTERNAL_ACTIVITY_LEASE_MS = 15 * 60_000;export const MIN_EXTERNAL_ACTIVITY_LEASE_MS = 30_000;
export function getExternalActivityLeaseMs() { const raw = process.env.OMP_CODEX_EXTERNAL_ACTIVITY_LEASE_MS; const parsed = raw !== undefined && raw !== "" ? Number(raw) : DEFAULT_EXTERNAL_ACTIVITY_LEASE_MS; return Math.max(MIN_EXTERNAL_ACTIVITY_LEASE_MS, Number.isFinite(parsed) ? parsed : DEFAULT_EXTERNAL_ACTIVITY_LEASE_MS);}
export const EXTERNAL_ACTIVITY_LEASE_MS = DEFAULT_EXTERNAL_ACTIVITY_LEASE_MS;const SESSION_ACTIVITY_TAIL_BYTES = 1024 * 1024;const MAX_SESSION_ACTIVITY_SCAN_BYTES = 16 * 1024 * 1024;const activityCache = new Map();
function hasToolCall(content) { return Array.isArray(content) && content.some((item) => item?.type === "toolCall");}
function messageIndicatesActiveTurn(message) { if (!message || typeof message !== "object") return false; if (message.role === "user" || message.role === "toolResult") return true; if (message.role !== "assistant") return false; if (["error", "aborted", "length"].includes(message.stopReason)) return false; return message.stopReason === "toolUse" || hasToolCall(message.content);}
function activityFromMessage(message, mtimeMs) { const pending = messageIndicatesActiveTurn(message); const leaseMs = getExternalActivityLeaseMs(); const activeUntil = pending ? Number(mtimeMs || 0) + leaseMs : 0; return { pending, activeUntil, status: pending && activeUntil > Date.now() ? { type: "active", activeFlags: [] } : { type: "idle" }, };}
const TERMINAL_SESSION_CUSTOM_TYPES = new Set([ "session_exit", "session_stop", "session_shutdown",]);
function activityTailFromContent(content) { const lines = content.split("\n"); for (let index = lines.length - 1; index >= 0; index -= 1) { const line = lines[index]; if (!line || line.charCodeAt(0) !== 123) continue; try { const entry = JSON.parse(line); if (entry.type === "custom" && TERMINAL_SESSION_CUSTOM_TYPES.has(entry.customType)) { return { terminal: true, message: null }; } if (entry.type === "message" && entry.message) { return { terminal: false, message: entry.message }; } } catch {} } return null;}
async function inspectActivityHandle(handle, stat) { const leaseMs = getExternalActivityLeaseMs(); if (!stat || Date.now() - stat.mtimeMs > leaseMs) { return activityFromMessage(null, stat?.mtimeMs); } let start = stat.size; let content = ""; let scannedBytes = 0; while (start > 0 && scannedBytes < MAX_SESSION_ACTIVITY_SCAN_BYTES) { const length = Math.min(SESSION_ACTIVITY_TAIL_BYTES, start, MAX_SESSION_ACTIVITY_SCAN_BYTES - scannedBytes); start -= length; const buffer = Buffer.allocUnsafe(length); const { bytesRead } = await handle.read(buffer, 0, length, start); content = buffer.toString("utf8", 0, bytesRead) + content; scannedBytes += bytesRead; const tail = activityTailFromContent(content); if (tail) return activityFromMessage(tail.terminal ? null : tail.message, stat.mtimeMs); if (!bytesRead) break; } if (start > 0) { return activityFromMessage({ role: "user" }, stat.mtimeMs); } return activityFromMessage(null, stat.mtimeMs);}
export async function inspectOmpSessionActivity(filePath) { let handle; try { handle = await fs.open(filePath, "r"); const stat = await handle.stat(); const cached = activityCache.get(filePath); if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) { return { ...cached.activity, status: cached.activity.pending && cached.activity.activeUntil > Date.now() ? { type: "active", activeFlags: [] } : { type: "idle" }, }; } const activity = await inspectActivityHandle(handle, stat); if (activityCache.size >= MAX_HEADER_CACHE_SIZE) { const firstKey = activityCache.keys().next().value; if (firstKey) activityCache.delete(firstKey); } activityCache.set(filePath, { mtimeMs: stat.mtimeMs, size: stat.size, activity }); return activity; } catch { return activityFromMessage(null, 0); } finally { await handle?.close().catch(() => {}); }}
export async function parseOmpSessionIdentity(filePath) { let handle; try { handle = await fs.open(filePath, "r"); const buffer = Buffer.alloc(4096); const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0); const lines = buffer.toString("utf8", 0, bytesRead).split("\n").filter(Boolean); for (const line of lines) { try { const entry = JSON.parse(line); if (entry.type === "session" && entry.id && entry.cwd) { return { id: entry.id, sessionId: entry.id, cwd: entry.cwd }; } } catch {} } return null; } catch { return null; } finally { await handle?.close().catch(() => {}); }}
export async function parseOmpSessionHeader(filePath) { try { let handle; try { handle = await fs.open(filePath, "r"); } catch { return null; } try { const stat = await handle.stat(); const cached = headerCache.get(filePath); if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) { await handle.close(); const active = cached.header.activityPending && cached.header.externalActiveUntil > Date.now(); return { ...cached.header, status: active ? { type: "active", activeFlags: [] } : { type: "idle" } }; }
const buffer = Buffer.alloc(4096); const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0); const activity = await inspectActivityHandle(handle, stat); await handle.close();
const chunk = buffer.toString("utf8", 0, bytesRead); const lines = chunk.split("\n").filter(Boolean);
let sessionInfo = null; let title = null; let preview = ""; let activeModel = null; let activeThinking = null;
for (const line of lines) { try { const entry = JSON.parse(line); if (entry.type === "session" && !sessionInfo) { sessionInfo = entry; if (entry.title && !title) title = entry.title; } else if ((entry.type === "title" || entry.type === "title_change") && entry.title) { title = entry.title; } else if (entry.type === "model_change" && modelSelectorFromChange(entry)) { activeModel = modelSelectorFromChange(entry); } else if (entry.type === "thinking_level_change" && entry.thinkingLevel) { activeThinking = entry.thinkingLevel; } else if (entry.type === "message" && entry.message?.codexBridgeInjected === true) { preview = ""; } else if (entry.type === "message" && entry.message?.role === "user" && !preview) { const content = entry.message.content; if (typeof content === "string") preview = content.slice(0, 200); else if (Array.isArray(content)) { const text = content.filter((c) => c.type === "text").map((c) => c.text).join(" "); preview = text.slice(0, 200); } } } catch {} }
if (!sessionInfo) return null; if (!preview && !title) return null; if (preview && (preview.startsWith("You are a helpful assistant") || preview.includes("short title for a task") || preview === "cli" || preview === "test")) { return null; } if (title && (title.startsWith("You are a helpful assistant") || title === "OMP Session" || title === "cli" || title === "test")) { if (!preview) return null; title = preview; }
const resolvedTitle = (title && title !== "OMP Session" ? title : null) || (preview && preview !== "OMP Session" ? preview : null); if (!resolvedTitle) return null; const createdAt = sessionInfo.timestamp ? Math.round(new Date(sessionInfo.timestamp).getTime() / 1000) : 0; const updatedAt = Math.round(stat.mtimeMs / 1000);
const { model, modelProvider } = normalizeOmpModel(activeModel);
const header = { id: sessionInfo.id, sessionId: sessionInfo.id, file: filePath, cwd: sessionInfo.cwd || process.cwd(), title: resolvedTitle, preview: preview || resolvedTitle || "", model, modelProvider, reasoningEffort: activeThinking || "medium", createdAt, updatedAt, recencyAt: updatedAt, status: activity.status, activityPending: activity.pending, externalActiveUntil: activity.activeUntil, };
if (headerCache.size >= MAX_HEADER_CACHE_SIZE) { const firstKey = headerCache.keys().next().value; if (firstKey) headerCache.delete(firstKey); } headerCache.set(filePath, { mtimeMs: stat.mtimeMs, size: stat.size, header });
return header; } catch { try { await handle.close(); } catch {} return null; } } catch { return null; }}
export async function parseOmpSessionFile(filePath, options = {}) { let stat = null; let content = ""; const requestedOffset = Math.max(0, Number(options.startOffset || 0)); if (requestedOffset > 0) { const handle = await fs.open(filePath, "r"); try { stat = await handle.stat(); const startOffset = Math.min(requestedOffset, stat.size); const buffer = Buffer.allocUnsafe(Math.max(0, stat.size - startOffset)); let bytesRead = 0; while (bytesRead < buffer.length) { const result = await handle.read(buffer, bytesRead, buffer.length - bytesRead, startOffset + bytesRead); if (!result.bytesRead) break; bytesRead += result.bytesRead; } content = buffer.toString("utf8", 0, bytesRead); if (startOffset > 0) { const previous = Buffer.allocUnsafe(1); const boundary = await handle.read(previous, 0, 1, startOffset - 1); if (boundary.bytesRead === 1 && previous[0] !== 0x0a) { const firstNewline = content.indexOf("\n"); content = firstNewline >= 0 ? content.slice(firstNewline + 1) : ""; } } } finally { await handle.close().catch(() => {}); } } else { const handle = await fs.open(filePath, "r"); try { stat = await handle.stat(); const buffer = Buffer.allocUnsafe(stat.size); let bytesRead = 0; while (bytesRead < buffer.length) { const result = await handle.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead); if (!result.bytesRead) break; bytesRead += result.bytesRead; } content = buffer.toString("utf8", 0, bytesRead); stat = { size: bytesRead, mtimeMs: stat.mtimeMs }; } finally { await handle.close().catch(() => {}); } } let parsedSize = stat?.size ?? null; if (content && !content.endsWith("\n")) { const lastNewline = content.lastIndexOf("\n"); const trailing = lastNewline >= 0 ? content.slice(lastNewline + 1) : content; parsedSize = parsedSize == null ? null : parsedSize - Buffer.byteLength(trailing); content = lastNewline >= 0 ? content.slice(0, lastNewline + 1) : ""; } const lines = content.split("\n").filter(Boolean); let sessionInfo = options.sessionInfo || null; let title = options.title || null; let firstUserText = options.preview || ""; let activeModel = options.activeModel || null; let activeThinking = options.activeThinking || null; const turns = []; const toolResults = []; let currentTurn = null; let currentTurnId = 1; let lastMessage = null; for (const line of lines) { let entry; try { entry = JSON.parse(line); } catch { continue; }
if (entry.type === "session") { sessionInfo = entry; if (entry.title && !title) title = entry.title; } else if ((entry.type === "title" || entry.type === "title_change") && entry.title) { title = entry.title; } else if (entry.type === "model_change" && modelSelectorFromChange(entry)) { activeModel = modelSelectorFromChange(entry); } else if (entry.type === "thinking_level_change" && entry.thinkingLevel) { activeThinking = entry.thinkingLevel; } else if (entry.type === "compaction") { if (currentTurn) turns.push(currentTurn); const ts = entry.timestamp ? Math.round(new Date(entry.timestamp).getTime() / 1000) : null; const compactionItem = makeContextCompactionItem(entry.id || id("item")); currentTurn = makeTurn({ turnId: id("turn"), status: "completed", error: null, startedAt: ts, completedAt: ts, durationMs: 0, items: [compactionItem], }); turns.push(currentTurn); currentTurn = null; } else if (entry.type === "custom" && TERMINAL_SESSION_CUSTOM_TYPES.has(entry.customType)) { lastMessage = null; } else if (entry.type === "message") { const msg = entry.message; if (!msg) continue; if (msg.codexBridgeInjected === true) { turns.length = 0; currentTurn = null; firstUserText = ""; lastMessage = null; continue; } lastMessage = msg; if (msg.role === "user") { if (currentTurn) turns.push(currentTurn); const text = Array.isArray(msg.content) ? msg.content.filter((c) => c.type === "text").map((c) => c.text).join("") : (typeof msg.content === "string" ? msg.content : ""); if (!firstUserText && text) firstUserText = text.slice(0, 200); const ts = entry.timestamp ? Math.round(new Date(entry.timestamp).getTime() / 1000) : null; currentTurn = makeTurn({ turnId: id("turn"), status: "completed", error: null, startedAt: ts, completedAt: ts, items: [makeUserMessageItem(entry.id || id("item"), text)], }); } else if (msg.role === "assistant") { if (!currentTurn) { const ts = entry.timestamp ? Math.round(new Date(entry.timestamp).getTime() / 1000) : null; currentTurn = makeTurn({ turnId: id("turn"), status: "completed", error: null, startedAt: ts, completedAt: ts, items: [], }); }
if (Array.isArray(msg.content)) { for (const item of msg.content) { if (item.type === "thinking" && item.thinking) { const { summary, content } = extractReasoningSummaryAndContent(item.thinking); const resolvedSummary = summary.length ? summary : []; let existingReasoning = currentTurn.items.find((i) => i.type === "reasoning"); if (!existingReasoning) { existingReasoning = makeReasoningItem(entry.id || id("item"), resolvedSummary, content); const userMsgIndex = currentTurn.items.findIndex((i) => i.type === "userMessage"); if (userMsgIndex >= 0) { currentTurn.items.splice(userMsgIndex + 1, 0, existingReasoning); } else { currentTurn.items.unshift(existingReasoning); } } else { for (const s of resolvedSummary) { if (s && !existingReasoning.summary.includes(s)) existingReasoning.summary.push(s); } for (const c of content) { if (c && !existingReasoning.content.includes(c)) existingReasoning.content.push(c); } } } else if (item.type === "text" && item.text) { const lastItem = currentTurn.items[currentTurn.items.length - 1]; if (lastItem && lastItem.type === "agentMessage") { lastItem.text = `${lastItem.text || ""}\n\n${item.text}`; } else { currentTurn.items.push(makeAgentMessageItem(entry.id || id("item"), item.text)); } } else if (item.type === "toolCall") { const toolItem = makeHistoricalToolItem(item, sessionInfo?.cwd || process.cwd()); if (toolItem.type === "userInputResponse") toolItem.turnId = currentTurn.id; currentTurn.items.push(toolItem); } } } if (msg.usage) { currentTurn.tokenUsage = makeTokenUsage(msg.usage); } } else if (msg.role === "toolResult") { toolResults.push(msg); if (currentTurn) { const matchingItem = currentTurn.items.find((item) => item.id === msg.toolCallId); if (matchingItem) completeHistoricalToolItem(matchingItem, msg); } } } }
const activity = activityFromMessage(lastMessage, stat?.mtimeMs); if (currentTurn && activity.status.type === "active") { currentTurn.status = "inProgress"; currentTurn.completedAt = null; } if (currentTurn) turns.push(currentTurn);
const createdAt = sessionInfo?.timestamp ? Math.round(new Date(sessionInfo.timestamp).getTime() / 1000) : Number(options.createdAt || 0); const updatedAt = stat ? Math.round(stat.mtimeMs / 1000) : createdAt;
const resolvedTitle = title || firstUserText || "OMP Session"; const { model, modelProvider } = normalizeOmpModel(activeModel); const thread = makeThread({ id: sessionInfo?.id || path.basename(filePath, ".jsonl"), sessionId: sessionInfo?.id || path.basename(filePath, ".jsonl"), cwd: sessionInfo?.cwd || process.cwd(), model, modelProvider, reasoningEffort: activeThinking || "medium", createdAt, updatedAt, recencyAt: updatedAt, name: resolvedTitle, preview: resolvedTitle, status: activity.status, turns, }); return { thread, sessionInfo, title, parsedSize, parsedMtime: stat?.mtimeMs ?? null, externalActiveUntil: activity.activeUntil, activityPending: activity.pending, toolResults, };}export async function scanAllOmpSessionHeaders(baseDir = null, forceRefresh = false) { const resolvedBaseDir = baseDir || defaultSessionsDir(); const now = Date.now(); if (!forceRefresh && globalIndexCache && globalIndexCacheDir === resolvedBaseDir && now - globalIndexCacheTime < GLOBAL_INDEX_TTL_MS) { return globalIndexCache; }
const dirEntries = await fs.readdir(resolvedBaseDir, { withFileTypes: true }).catch(() => []); const dirNames = dirEntries.filter((e) => e.isDirectory()).map((e) => e.name);
const fileLists = await Promise.all( dirNames.map(async (d) => { const dirPath = path.join(resolvedBaseDir, d); const subEntries = await fs.readdir(dirPath, { withFileTypes: true }).catch(() => []); return subEntries .filter((e) => e.isFile() && e.name.endsWith(".jsonl")) .map((e) => path.join(dirPath, e.name)); }) ); const allFiles = fileLists.flat();
// Parse all headers using concurrent worker pool const concurrency = 32; let idx = 0; const headers = []; async function worker() { while (idx < allFiles.length) { const filePath = allFiles[idx++]; const header = await parseOmpSessionHeader(filePath); if (header) headers.push(header); } } await Promise.all(Array.from({ length: Math.min(concurrency, allFiles.length || 1) }, worker));
headers.sort((a, b) => (b.recencyAt || b.updatedAt || 0) - (a.recencyAt || a.updatedAt || 0)); globalIndexCache = headers; globalIndexCacheDir = resolvedBaseDir; globalIndexCacheTime = now; return headers;}
export async function listOmpSessionsForCwd(cwd, options = {}, baseDir = null) { const allSessions = await scanAllOmpSessionHeaders(baseDir); const resolvedCwd = path.resolve(cwd || process.cwd()); const targetBase = path.basename(resolvedCwd);
const matching = allSessions.filter((s) => { const sCwd = path.resolve(s.cwd || ""); return sCwd === resolvedCwd || path.basename(sCwd) === targetBase; });
const { items: sessions, nextCursor, total } = paginateArray(matching, options); return { sessions, nextCursor, total };}
export async function listAllOmpSessions(options = {}, baseDir = null) { const allSessions = await scanAllOmpSessionHeaders(baseDir); const { items: sessions, nextCursor, total } = paginateArray(allSessions, options); return { sessions, nextCursor, total };}