@threadbase-sh/scanner 0.7.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -10
- package/dist/cli.js +2060 -339
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +2115 -377
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -6
- package/dist/index.d.ts +154 -6
- package/dist/index.js +2112 -378
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/filters.ts","../src/git.ts","../src/logger.ts","../src/indexer.ts","../src/profiles.ts","../src/scanner.ts","../src/cache.ts","../src/discovery.ts","../src/parser.ts","../src/tags.ts","../src/tiers.ts","../src/types.ts"],"sourcesContent":["export {\n applyAccountFilter,\n applyIncludeFilter,\n applyPagination,\n applyProjectFilter,\n applySinceFilter,\n applySort,\n} from \"./filters\";\nexport { readGitBranch } from \"./git\";\nexport { SearchIndexer } from \"./indexer\";\nexport type { Logger, LoggerOptions } from \"./logger\";\nexport { createLogger, getLogger, setLogger } from \"./logger\";\nexport {\n detectDefaultProfile,\n getProjectsDir,\n loadProfiles,\n resolveConfigDir,\n saveProfiles,\n} from \"./profiles\";\nexport { ConversationScanner } from \"./scanner\";\nexport { cleanSystemTags } from \"./tags\";\nexport { DEFAULT_TIERS, resolveTier } from \"./tiers\";\nexport * from \"./types\";\n\n// ─── Standalone Convenience Functions ───────────────────────────────\n\nimport { ConversationScanner } from \"./scanner\";\nimport type {\n Conversation,\n GetConversationOptions,\n ScanOptions,\n ScanResult,\n SearchOptions,\n SearchResult,\n} from \"./types\";\n\nlet defaultScanner: ConversationScanner | undefined;\n\nfunction getDefaultScanner(): ConversationScanner {\n if (!defaultScanner) {\n defaultScanner = new ConversationScanner();\n }\n return defaultScanner;\n}\n\nexport function resetDefaultScanner(): void {\n defaultScanner = undefined;\n}\n\nexport async function scan(\n options?: ScanOptions,\n scanner?: ConversationScanner,\n): Promise<ScanResult> {\n return (scanner ?? getDefaultScanner()).scan(options);\n}\n\nexport async function search(\n query: string,\n options?: SearchOptions,\n scanner?: ConversationScanner,\n): Promise<SearchResult[]> {\n return (scanner ?? getDefaultScanner()).search(query, options);\n}\n\nexport async function getConversation(\n id: string,\n options?: GetConversationOptions,\n scanner?: ConversationScanner,\n): Promise<Conversation | null> {\n return (scanner ?? getDefaultScanner()).getConversation(id, options);\n}\n","import type { ConversationMeta, Include, SortOrder } from \"./types\";\n\nexport function applySort(metas: ConversationMeta[], order: SortOrder): ConversationMeta[] {\n const out = [...metas];\n switch (order) {\n case \"recent\":\n out.sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n break;\n case \"oldest\":\n out.sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n break;\n case \"messages-desc\":\n out.sort((a, b) => b.messageCount - a.messageCount);\n break;\n case \"messages-asc\":\n out.sort((a, b) => a.messageCount - b.messageCount);\n break;\n case \"alpha\":\n out.sort((a, b) => {\n const cmp = a.projectName.localeCompare(b.projectName);\n return cmp !== 0 ? cmp : a.preview.localeCompare(b.preview);\n });\n break;\n }\n return out;\n}\n\nexport function applySinceFilter(metas: ConversationMeta[], since: string): ConversationMeta[] {\n const cutoff = parseSinceCutoff(since);\n return metas.filter((m) => new Date(m.timestamp).getTime() >= cutoff.getTime());\n}\n\nexport function applyIncludeFilter(\n metas: ConversationMeta[],\n include: Include,\n): ConversationMeta[] {\n switch (include) {\n case \"all\":\n return metas;\n case \"conversations\":\n return metas.filter((m) => !m.isSubagent && !m.isTeammate);\n case \"subagents\":\n return metas.filter((m) => m.isSubagent);\n case \"teammates\":\n return metas.filter((m) => m.isTeammate);\n }\n}\n\nexport function applyProjectFilter(metas: ConversationMeta[], project: string): ConversationMeta[] {\n const lower = project.toLowerCase();\n return metas.filter(\n (m) =>\n m.projectPath.toLowerCase().includes(lower) || m.projectName.toLowerCase().includes(lower),\n );\n}\n\nexport function applyAccountFilter(metas: ConversationMeta[], account: string): ConversationMeta[] {\n return metas.filter((m) => m.account === account);\n}\n\nexport function applyPagination<T>(\n items: T[],\n limit: number,\n offset: number,\n): { items: T[]; total: number } {\n return {\n items: items.slice(offset, offset + limit),\n total: items.length,\n };\n}\n\nexport function parseSinceCutoff(value: string): Date {\n const s = value.trim();\n if (!s) throw new Error(\"Empty --since value\");\n\n // Try ISO date\n const isoMatch = s.match(/^\\d{4}-\\d{2}-\\d{2}$/);\n if (isoMatch) {\n const d = new Date(`${s}T00:00:00Z`);\n if (!Number.isNaN(d.getTime())) return d;\n }\n\n // Try duration: digits + unit\n const durationMatch = s.match(/^(\\d+)([hdw])$/);\n if (!durationMatch) {\n throw new Error(\n `Invalid --since value \"${s}\": expected duration like \"7d\", \"24h\", \"2w\" or ISO date \"2006-01-02\"`,\n );\n }\n\n const n = parseInt(durationMatch[1], 10);\n const unit = durationMatch[2];\n let ms: number;\n switch (unit) {\n case \"h\":\n ms = n * 60 * 60 * 1000;\n break;\n case \"d\":\n ms = n * 24 * 60 * 60 * 1000;\n break;\n case \"w\":\n ms = n * 7 * 24 * 60 * 60 * 1000;\n break;\n default:\n throw new Error(`Invalid unit \"${unit}\"`);\n }\n\n return new Date(Date.now() - ms);\n}\n","import { readFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { getLogger } from \"./logger\";\n\nconst REF_PREFIX = \"ref: refs/heads/\";\nconst MAX_DEPTH = 6;\n\nexport function readGitBranch(projectPath: string): string | null {\n if (!projectPath) return null;\n const log = getLogger();\n\n let dir = projectPath;\n let depth = 0;\n\n while (depth < MAX_DEPTH) {\n const headPath = join(dir, \".git\", \"HEAD\");\n try {\n const content = readFileSync(headPath, \"utf-8\").trim();\n if (content.startsWith(REF_PREFIX)) {\n const branch = content.slice(REF_PREFIX.length);\n log.trace({ projectPath, dir, branch }, \"git: branch resolved\");\n return branch;\n }\n // Detached HEAD: raw commit SHA\n if (content.length >= 7) {\n log.trace({ projectPath, dir }, \"git: detached HEAD\");\n return \"(detached)\";\n }\n return null;\n } catch {\n // .git/HEAD not found at this level\n }\n\n const parent = dirname(dir);\n if (parent === dir) return null; // filesystem root\n dir = parent;\n depth++;\n }\n\n log.trace({ projectPath }, \"git: no .git found within depth\");\n return null;\n}\n","import pino, { type Logger, type LoggerOptions } from \"pino\";\n\nlet currentLogger: Logger = pino({ level: \"silent\" });\n\nexport function createLogger(options?: LoggerOptions | Logger): Logger {\n if (options && typeof (options as Logger).child === \"function\") {\n return options as Logger;\n }\n return pino((options as LoggerOptions) ?? { level: \"silent\" });\n}\n\nexport function setLogger(logger: Logger): void {\n currentLogger = logger;\n}\n\nexport function getLogger(): Logger {\n return currentLogger;\n}\n\nexport type { Logger, LoggerOptions };\n","import FlexSearchModule from \"flexsearch\";\n\n// FlexSearch has inconsistent default export across ESM/CJS\nconst FlexSearch = (FlexSearchModule as any).default ?? FlexSearchModule;\n\nimport { getLogger } from \"./logger\";\nimport type { ConversationMeta, SearchMatch, SearchResult } from \"./types\";\n\nexport class SearchIndexer {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private index: any;\n private documents = new Map<string, ConversationMeta>();\n\n constructor() {\n this.index = this.createIndex();\n }\n\n private createIndex() {\n return new (FlexSearch as any).Document({\n document: {\n id: \"id\",\n index: [\n \"content\",\n \"projectName\",\n \"projectPath\",\n \"sessionId\",\n \"sessionName\",\n \"account\",\n \"model\",\n \"gitBranch\",\n \"toolNames\",\n ],\n store: [\"id\"],\n },\n tokenize: \"forward\",\n resolution: 9,\n cache: 100,\n });\n }\n\n addDocument(meta: ConversationMeta): void {\n this.documents.set(meta.id, meta);\n this.index.add({\n id: meta.id,\n content: meta.contentSnippet,\n projectName: meta.projectName,\n projectPath: meta.projectPath,\n sessionId: meta.sessionId,\n sessionName: meta.sessionName,\n account: meta.account,\n model: meta.model || \"\",\n gitBranch: meta.gitBranch || \"\",\n toolNames: meta.toolNames.join(\" \"),\n });\n }\n\n buildIndex(metas: ConversationMeta[]): void {\n this.clear();\n for (const meta of metas) {\n this.addDocument(meta);\n }\n getLogger().debug({ docCount: metas.length }, \"indexer: built\");\n }\n\n search(query: string, options?: { fields?: string[]; limit?: number }): SearchResult[] {\n const limit = options?.limit ?? 50;\n\n if (!query.trim()) {\n return this.getRecent(limit);\n }\n\n const results = this.index.search(query, { limit: limit * 2, enrich: true });\n\n const seen = new Set<string>();\n const searchResults: SearchResult[] = [];\n\n for (const fieldResult of results) {\n if (!fieldResult.result) continue;\n for (const item of fieldResult.result) {\n const id = typeof item === \"object\" ? (item as { id: string }).id : String(item);\n if (seen.has(id)) continue;\n seen.add(id);\n\n const meta = this.documents.get(id);\n if (!meta) continue;\n\n const matches = this.generateMatches(meta, query);\n\n searchResults.push({ meta, score: 1, matches });\n if (searchResults.length >= limit) break;\n }\n if (searchResults.length >= limit) break;\n }\n\n return searchResults;\n }\n\n private getRecent(limit: number): SearchResult[] {\n return Array.from(this.documents.values())\n .sort((a, b) => b.timestamp.localeCompare(a.timestamp))\n .slice(0, limit)\n .map((meta) => ({\n meta,\n score: 1,\n matches: [{ field: \"timestamp\", snippet: meta.preview }],\n }));\n }\n\n private generateMatches(meta: ConversationMeta, query: string): SearchMatch[] {\n const matches: SearchMatch[] = [];\n const lowerQuery = query.toLowerCase();\n\n const fields: [string, string][] = [\n [\"contentSnippet\", meta.contentSnippet],\n [\"projectName\", meta.projectName],\n [\"sessionId\", meta.sessionId],\n [\"sessionName\", meta.sessionName],\n [\"account\", meta.account],\n [\"model\", meta.model || \"\"],\n [\"gitBranch\", meta.gitBranch || \"\"],\n [\"toolNames\", meta.toolNames.join(\" \")],\n ];\n\n for (const [field, value] of fields) {\n const idx = value.toLowerCase().indexOf(lowerQuery);\n if (idx !== -1) {\n const start = Math.max(0, idx - 80);\n const end = Math.min(value.length, idx + query.length + 120);\n let snippet = value.slice(start, end);\n if (start > 0) snippet = `...${snippet}`;\n if (end < value.length) snippet = `${snippet}...`;\n matches.push({ field, snippet });\n }\n }\n\n return matches.length > 0 ? matches : [{ field: \"preview\", snippet: meta.preview }];\n }\n\n getDocumentCount(): number {\n return this.documents.size;\n }\n\n // Replace an already-indexed document in place. FlexSearch's `add` does not\n // overwrite an existing id, so a single-file refresh must go through\n // `update` to avoid stale matches lingering in the index.\n updateDocument(meta: ConversationMeta): void {\n this.documents.set(meta.id, meta);\n this.index.update({\n id: meta.id,\n content: meta.contentSnippet,\n projectName: meta.projectName,\n projectPath: meta.projectPath,\n sessionId: meta.sessionId,\n sessionName: meta.sessionName,\n account: meta.account,\n model: meta.model || \"\",\n gitBranch: meta.gitBranch || \"\",\n toolNames: meta.toolNames.join(\" \"),\n });\n }\n\n removeDocument(id: string): void {\n this.documents.delete(id);\n this.index.remove(id);\n }\n\n clear(): void {\n this.documents.clear();\n this.index = this.createIndex();\n getLogger().trace(\"indexer: cleared\");\n }\n}\n","import { mkdir, readFile, writeFile } from \"fs/promises\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { getLogger } from \"./logger\";\nimport type { Profile } from \"./types\";\n\nconst PROFILES_FILE = \"profiles.json\";\n\nexport function resolveConfigDir(configDir: string): string {\n return configDir.replace(/^~/, homedir());\n}\n\nexport function getProjectsDir(profile: Profile): string {\n return join(resolveConfigDir(profile.configDir), \"projects\");\n}\n\nexport async function detectDefaultProfile(): Promise<Profile> {\n return {\n id: \"default\",\n label: \"Default\",\n configDir: join(homedir(), \".claude\"),\n enabled: true,\n emoji: \"🤖\",\n };\n}\n\nexport async function loadProfiles(configPath: string): Promise<Profile[]> {\n const log = getLogger();\n try {\n const resolved = resolveConfigDir(configPath);\n const data = await readFile(join(resolved, PROFILES_FILE), \"utf-8\");\n const profiles = JSON.parse(data) as Profile[];\n log.debug({ configPath, count: profiles.length }, \"profiles: loaded\");\n return profiles;\n } catch (err) {\n log.debug({ configPath, err }, \"profiles: load failed, using default\");\n const defaultProfile = await detectDefaultProfile();\n return [defaultProfile];\n }\n}\n\nexport async function saveProfiles(profiles: Profile[], configPath: string): Promise<void> {\n const resolved = resolveConfigDir(configPath);\n await mkdir(resolved, { recursive: true });\n await writeFile(join(resolved, PROFILES_FILE), JSON.stringify(profiles, null, 2));\n getLogger().debug({ configPath, count: profiles.length }, \"profiles: saved\");\n}\n","import { statSync } from \"fs\";\nimport { LRUCache } from \"./cache\";\nimport { discoverJsonlFiles } from \"./discovery\";\nimport {\n applyAccountFilter,\n applyIncludeFilter,\n applyPagination,\n applyProjectFilter,\n applySinceFilter,\n applySort,\n parseSinceCutoff,\n} from \"./filters\";\nimport { readGitBranch } from \"./git\";\nimport { SearchIndexer } from \"./indexer\";\nimport { getLogger } from \"./logger\";\nimport { parseConversation, parseMeta } from \"./parser\";\nimport { getProjectsDir, loadProfiles } from \"./profiles\";\nimport { resolveTier } from \"./tiers\";\nimport type {\n ContentTier,\n Conversation,\n ConversationMeta,\n ConversationPage,\n GetConversationOptions,\n GetConversationPageOptions,\n GroupedConversations,\n Profile,\n ScanOptions,\n ScanResult,\n SearchOptions,\n SearchResult,\n SingleFilePage,\n TreeConversation,\n} from \"./types\";\n\nconst BATCH_SIZE = 12;\nconst DEFAULT_CONFIG_PATH = \"~/.config/threadbase-scanner\";\n\nexport class ConversationScanner {\n private metadataCache: Map<string, ConversationMeta> = new Map();\n private conversationLRU: LRUCache<string, Conversation>;\n private sessionIdIndex: Map<string, ConversationMeta> = new Map();\n private projects: Set<string> = new Set();\n private indexer: SearchIndexer = new SearchIndexer();\n // Tier the most recent scan() ran with, so refreshFile() re-parses a single\n // file at the same content depth. Defaults to the standard tier.\n private lastTier: ContentTier = resolveTier(\"standard\");\n\n constructor(options?: { metadataCacheSize?: number; conversationCacheSize?: number }) {\n this.conversationLRU = new LRUCache<string, Conversation>(options?.conversationCacheSize ?? 5);\n }\n\n async scan(options: ScanOptions = {}): Promise<ScanResult> {\n const log = getLogger();\n const startedAt = Date.now();\n const profiles = await this.resolveProfiles(options.profiles);\n const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);\n\n const tier = resolveTier(options.tier ?? \"standard\", options.tiers);\n this.lastTier = tier;\n\n log.info(\n {\n activeProfiles: activeProfiles.length,\n tier: tier.name,\n sort: options.sort ?? \"recent\",\n include: options.include ?? \"all\",\n view: options.view ?? \"flat\",\n },\n \"scan: start\",\n );\n\n // Clear caches\n this.metadataCache.clear();\n this.conversationLRU.clear();\n this.sessionIdIndex.clear();\n this.projects.clear();\n this.indexer.clear();\n\n const configDirs = activeProfiles.map((p) => ({\n projectsDir: getProjectsDir(p),\n account: p.id,\n }));\n\n const files = await discoverJsonlFiles(configDirs);\n const totalFiles = files.length;\n let scanned = 0;\n let parseFailures = 0;\n\n const allMetas: ConversationMeta[] = [];\n // Memoize git-branch lookups per project path for the duration of this\n // scan. readGitBranch walks the filesystem for .git/HEAD; without this it\n // runs once per conversation (thousands of times) even though there are\n // only a handful of distinct project roots. Scoped to one scan() call —\n // branches can change between scans, so a longer-lived memo would go stale.\n const gitBranchMemo = new Map<string, string | null>();\n const resolveGitBranch = (projectPath: string): string | null => {\n let branch = gitBranchMemo.get(projectPath);\n if (branch === undefined) {\n branch = readGitBranch(projectPath);\n gitBranchMemo.set(projectPath, branch);\n }\n return branch;\n };\n const { statCache } = options;\n for (let i = 0; i < files.length; i += BATCH_SIZE) {\n const batch = files.slice(i, i + BATCH_SIZE);\n const results = await Promise.all(\n batch.map(async ({ filePath, account }) => {\n if (statCache) {\n const cached = statCache.get(filePath);\n if (cached) {\n try {\n const s = statSync(filePath);\n if (s.mtimeMs === cached.stat.mtimeMs && s.size === cached.stat.size) {\n return cached.meta;\n }\n } catch {\n // file disappeared — fall through to parseMeta which will return null\n }\n }\n }\n try {\n const meta = await parseMeta(filePath, account, tier);\n if (meta) {\n meta.gitBranch = resolveGitBranch(meta.projectPath);\n }\n return meta;\n } catch (err) {\n parseFailures++;\n log.warn({ filePath, account, err }, \"scan: parseMeta threw\");\n return null;\n }\n }),\n );\n\n const batchMetas: ConversationMeta[] = [];\n for (const meta of results) {\n if (meta && meta.messageCount > 0) {\n this.metadataCache.set(meta.id, meta);\n this.sessionIdIndex.set(meta.sessionId, meta);\n this.projects.add(meta.projectPath);\n allMetas.push(meta);\n batchMetas.push(meta);\n this.indexer.addDocument(meta);\n }\n }\n\n if (batchMetas.length > 0) {\n options.onBatch?.(batchMetas);\n }\n\n scanned += batch.length;\n log.debug({ scanned, totalFiles, batchKept: batchMetas.length }, \"scan: batch complete\");\n options.onProgress?.(scanned, totalFiles);\n }\n\n // Apply filters\n let filtered = allMetas;\n if (options.include && options.include !== \"all\") {\n filtered = applyIncludeFilter(filtered, options.include);\n }\n if (options.project) {\n filtered = applyProjectFilter(filtered, options.project);\n }\n if (options.account) {\n filtered = applyAccountFilter(filtered, options.account);\n }\n if (options.since) {\n filtered = applySinceFilter(filtered, options.since);\n }\n\n filtered = applySort(filtered, options.sort ?? \"recent\");\n\n const total = filtered.length;\n const conversations = this.transformView(filtered, options);\n\n const elapsedMs = Date.now() - startedAt;\n log.info(\n {\n totalFiles,\n scanned,\n kept: allMetas.length,\n filteredTotal: total,\n parseFailures,\n elapsedMs,\n },\n \"scan: complete\",\n );\n\n if (Array.isArray(conversations)) {\n const limit = options.limit ?? 50;\n const offset = options.offset ?? 0;\n const paginated = applyPagination(conversations, limit, offset);\n return { conversations: paginated.items, total, scanned };\n }\n\n return { conversations, total, scanned };\n }\n\n async search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n const log = getLogger();\n log.debug({ query, indexSize: this.indexer.getDocumentCount() }, \"search: start\");\n\n if (this.indexer.getDocumentCount() === 0) {\n log.debug(\"search: index empty, triggering scan\");\n await this.scan({ ...options, limit: undefined, offset: undefined });\n }\n\n let results = this.indexer.search(query, {\n fields: options.fields,\n limit: (options.limit ?? 50) * 2,\n });\n\n if (options.include && options.include !== \"all\") {\n results = results.filter((r) => {\n switch (options.include) {\n case \"conversations\":\n return !r.meta.isSubagent && !r.meta.isTeammate;\n case \"subagents\":\n return r.meta.isSubagent;\n case \"teammates\":\n return r.meta.isTeammate;\n default:\n return true;\n }\n });\n }\n if (options.project) {\n const lower = options.project.toLowerCase();\n results = results.filter(\n (r) =>\n r.meta.projectPath.toLowerCase().includes(lower) ||\n r.meta.projectName.toLowerCase().includes(lower),\n );\n }\n if (options.account) {\n results = results.filter((r) => r.meta.account === options.account);\n }\n if (options.since) {\n const cutoff = parseSinceCutoff(options.since);\n results = results.filter((r) => new Date(r.meta.timestamp).getTime() >= cutoff.getTime());\n }\n\n const limit = options.limit ?? 50;\n const offset = options.offset ?? 0;\n const sliced = results.slice(offset, offset + limit);\n log.debug({ query, matched: results.length, returned: sliced.length }, \"search: complete\");\n return sliced;\n }\n\n async getConversation(\n id: string,\n _options?: GetConversationOptions,\n ): Promise<Conversation | null> {\n const log = getLogger();\n const cached = this.conversationLRU.get(id);\n if (cached) {\n log.debug({ id }, \"getConversation: cache hit\");\n return cached;\n }\n\n const meta = this.metadataCache.get(id) ?? this.sessionIdIndex.get(id);\n if (!meta) {\n log.debug({ id }, \"getConversation: not found in metadata\");\n return null;\n }\n\n log.debug({ id, filePath: meta.filePath }, \"getConversation: cache miss, parsing\");\n try {\n const conversation = await parseConversation(meta.filePath, meta.account);\n if (conversation) {\n this.conversationLRU.set(id, conversation);\n }\n return conversation;\n } catch (err) {\n log.warn({ id, filePath: meta.filePath, err }, \"getConversation: parse failed\");\n return null;\n }\n }\n\n // Return one bounded window of a conversation's messages plus the total\n // message count, so a caller can serve the last page and scroll back without\n // holding the whole conversation itself.\n //\n // The window is `[max(0, beforeIndex - limit), beforeIndex)` in chronological\n // order; `beforeIndex` defaults to `total` (the newest page). `fromIndex` is\n // the window's start index, so the caller can derive `has_more_older`\n // (fromIndex > 0). Returns null when the id can't be resolved/parsed — the\n // same contract as getConversation.\n //\n // Strategy: parse-once-then-slice. This delegates to getConversation, which\n // parses the full conversation and caches it in conversationLRU, then slices\n // the window. Message indices are therefore identical to a full\n // parseConversation() by construction (same parse, same messages array).\n // Repeated page requests for the same id reuse the single cached parse. The\n // bounded-memory win (not holding all messages) is deferred — see\n // docs/plans/2026-06-10-paged-conversation-parse.md.\n async getConversationPage(\n id: string,\n options: GetConversationPageOptions,\n ): Promise<ConversationPage | null> {\n const conversation = await this.getConversation(id);\n if (!conversation) return null;\n\n const { messages } = conversation;\n const total = messages.length;\n const { limit } = options;\n const beforeIndex = options.beforeIndex ?? total;\n\n const fromIndex = Math.max(0, beforeIndex - limit);\n const window = messages.slice(fromIndex, beforeIndex);\n\n return { messages: window, total, fromIndex };\n }\n\n // Parse one JSONL file directly and slice a page window — without any prior\n // scan() or metadata index. This is the cold-start fast path: a single\n // conversation can be served from one file parse (~ms) instead of waiting\n // for a full filesystem scan. The window is the same\n // `[max(0, beforeIndex - limit), beforeIndex)` slice as getConversationPage,\n // and the parsed Conversation is returned alongside so the caller can build\n // the response meta (projectPath, timestamp, messageCount, …) without a\n // second parse. Returns null when the file can't be parsed. `account` only\n // feeds the conversation's account field; \"default\" is the single-profile\n // fallback, matching refreshFile.\n async parseSingleFilePage(\n filePath: string,\n account: string | undefined,\n options: GetConversationPageOptions,\n ): Promise<SingleFilePage | null> {\n const conversation = await parseConversation(filePath, account ?? \"default\");\n if (!conversation) return null;\n\n const { messages } = conversation;\n const total = messages.length;\n const { limit } = options;\n const beforeIndex = options.beforeIndex ?? total;\n\n const fromIndex = Math.max(0, beforeIndex - limit);\n const window = messages.slice(fromIndex, beforeIndex);\n\n return { messages: window, total, fromIndex, conversation };\n }\n\n // Re-parse a single JSONL file and update every in-memory index in place —\n // metadata cache, sessionId index, project set, search index — and evict the\n // file's parsed conversation from the LRU so the next getConversation()\n // re-reads it. This lets a long-lived scanner stay current with a file that\n // grew after the initial scan() without paying for a full rescan.\n //\n // `account` defaults to the account already recorded for this file (the id\n // is the file path), falling back to \"default\" for a file the scanner has\n // not seen before. Returns the fresh ConversationMeta, or null when the file\n // no longer parses (missing/empty) — in which case any prior entry for it is\n // dropped from all indexes.\n async refreshFile(filePath: string, account?: string): Promise<ConversationMeta | null> {\n const log = getLogger();\n const previous = this.metadataCache.get(filePath);\n const resolvedAccount = account ?? previous?.account ?? \"default\";\n\n let meta: ConversationMeta | null = null;\n try {\n meta = await parseMeta(filePath, resolvedAccount, this.lastTier);\n } catch (err) {\n log.warn({ filePath, err }, \"refreshFile: parseMeta threw\");\n meta = null;\n }\n\n // The LRU is keyed by the id used at getConversation() time, which can be\n // either the file-path id or the sessionId — evict both for the prior and\n // refreshed metas so no stale parse survives.\n const evict = (m: ConversationMeta | undefined | null) => {\n if (!m) return;\n this.conversationLRU.delete(m.id);\n this.conversationLRU.delete(m.sessionId);\n };\n evict(previous);\n evict(meta);\n\n if (!meta || meta.messageCount === 0) {\n if (previous) {\n this.metadataCache.delete(previous.id);\n this.sessionIdIndex.delete(previous.sessionId);\n this.indexer.removeDocument(previous.id);\n }\n log.debug({ filePath }, \"refreshFile: dropped (no parseable messages)\");\n return null;\n }\n\n meta.gitBranch = readGitBranch(meta.projectPath);\n\n // A re-parse can change the sessionId mapping; clear the old one first.\n if (previous && previous.sessionId !== meta.sessionId) {\n this.sessionIdIndex.delete(previous.sessionId);\n }\n this.metadataCache.set(meta.id, meta);\n this.sessionIdIndex.set(meta.sessionId, meta);\n this.projects.add(meta.projectPath);\n if (previous) {\n this.indexer.updateDocument(meta);\n } else {\n this.indexer.addDocument(meta);\n }\n\n log.debug(\n { filePath, messageCount: meta.messageCount },\n \"refreshFile: updated in-memory indexes\",\n );\n return meta;\n }\n\n getMetadataCache(): Map<string, ConversationMeta> {\n return this.metadataCache;\n }\n\n getProjects(): string[] {\n const normalized = new Set<string>();\n for (const p of this.projects) {\n normalized.add(p.replace(/\\/+$/, \"\"));\n }\n return Array.from(normalized).sort();\n }\n\n private async resolveProfiles(profiles?: Profile[]): Promise<Profile[]> {\n if (profiles && profiles.length > 0) return profiles;\n return loadProfiles(DEFAULT_CONFIG_PATH);\n }\n\n private transformView(\n metas: ConversationMeta[],\n options: ScanOptions,\n ): ConversationMeta[] | TreeConversation[] | GroupedConversations {\n switch (options.view) {\n case \"tree\":\n return this.toTree(metas);\n case \"grouped\":\n return this.toGrouped(metas);\n default:\n return metas;\n }\n }\n\n private toTree(metas: ConversationMeta[]): TreeConversation[] {\n const parents: TreeConversation[] = [];\n const subagents: ConversationMeta[] = [];\n\n for (const meta of metas) {\n if (meta.isSubagent) {\n subagents.push(meta);\n } else {\n parents.push({ ...meta, subagents: [] });\n }\n }\n\n const parentById = new Map(parents.map((p) => [p.id, p]));\n for (const sub of subagents) {\n const parent = sub.parentSessionId ? parentById.get(sub.parentSessionId) : undefined;\n if (parent) {\n parent.subagents.push(sub);\n } else {\n parents.push({ ...sub, subagents: [] });\n }\n }\n\n return parents;\n }\n\n private toGrouped(metas: ConversationMeta[]): GroupedConversations {\n const groups: GroupedConversations = {};\n for (const meta of metas) {\n const key = meta.teamName || \"_default\";\n if (!groups[key]) groups[key] = [];\n groups[key].push(meta);\n }\n return groups;\n }\n}\n","export class LRUCache<K, V> {\n private map = new Map<K, V>();\n private readonly capacity: number;\n\n constructor(capacity: number) {\n this.capacity = capacity;\n }\n\n get(key: K): V | undefined {\n const value = this.map.get(key);\n if (value === undefined) return undefined;\n // Move to end (most recently used)\n this.map.delete(key);\n this.map.set(key, value);\n return value;\n }\n\n set(key: K, value: V): void {\n this.map.delete(key);\n this.map.set(key, value);\n if (this.map.size > this.capacity) {\n const oldest = this.map.keys().next();\n if (!oldest.done) this.map.delete(oldest.value);\n }\n }\n\n has(key: K): boolean {\n return this.map.has(key);\n }\n\n delete(key: K): boolean {\n return this.map.delete(key);\n }\n\n clear(): void {\n this.map.clear();\n }\n\n get size(): number {\n return this.map.size;\n }\n}\n","import fg from \"fast-glob\";\nimport { stat } from \"fs/promises\";\nimport { getLogger } from \"./logger\";\n\nexport interface DiscoveredFile {\n filePath: string;\n account: string;\n}\n\nconst EXCLUDED_SEGMENTS = [\"/memory/\", \"/tool-results/\"];\n\n// stat() is cheap and IO-bound, so a large history scans much faster when the\n// calls run concurrently rather than one-at-a-time. Cap concurrency well under\n// typical fd limits.\nconst STAT_CONCURRENCY = 32;\n\nexport async function discoverJsonlFiles(\n dirs: { projectsDir: string; account: string }[],\n onProgress?: (found: number) => void,\n): Promise<DiscoveredFile[]> {\n const log = getLogger();\n const results: DiscoveredFile[] = [];\n\n for (const { projectsDir, account } of dirs) {\n let filePaths: string[];\n try {\n filePaths = await fg(\"**/*.jsonl\", {\n cwd: projectsDir,\n absolute: true,\n dot: false,\n });\n } catch (err) {\n log.warn({ projectsDir, account, err }, \"discovery: glob failed\");\n continue;\n }\n\n // Filter out excluded directory segments\n const filtered = filePaths.filter((fp) => !EXCLUDED_SEGMENTS.some((seg) => fp.includes(seg)));\n\n // Filter out empty files. Stat concurrently in bounded chunks; chunk order\n // is preserved and intra-chunk order follows the input, so discovery order\n // is stable (downstream sorts anyway).\n let kept = 0;\n let skippedEmpty = 0;\n let skippedInaccessible = 0;\n for (let i = 0; i < filtered.length; i += STAT_CONCURRENCY) {\n const chunk = filtered.slice(i, i + STAT_CONCURRENCY);\n const statted = await Promise.all(\n chunk.map(async (filePath) => {\n try {\n const s = await stat(filePath);\n return { filePath, size: s.size };\n } catch (err) {\n log.warn({ filePath, err }, \"discovery: stat failed\");\n return { filePath, size: -1 };\n }\n }),\n );\n for (const { filePath, size } of statted) {\n if (size < 0) {\n skippedInaccessible++;\n } else if (size > 0) {\n results.push({ filePath, account });\n kept++;\n } else {\n skippedEmpty++;\n }\n }\n }\n\n log.debug(\n {\n projectsDir,\n account,\n globMatches: filePaths.length,\n afterExclusions: filtered.length,\n kept,\n skippedEmpty,\n skippedInaccessible,\n },\n \"discovery: directory scanned\",\n );\n\n onProgress?.(results.length);\n }\n\n log.debug({ totalFiles: results.length, dirs: dirs.length }, \"discovery: complete\");\n return results;\n}\n","import { createReadStream } from \"fs\";\nimport { basename, dirname, join } from \"path\";\nimport { createInterface } from \"readline\";\nimport { getLogger } from \"./logger\";\nimport { cleanSystemTags } from \"./tags\";\nimport type {\n AttachmentSidecar,\n ContentTier,\n Conversation,\n ConversationMessage,\n ConversationMeta,\n MessageMetadata,\n MessageSender,\n MessageSnapshot,\n TeamInfo,\n ToolResultBlock,\n ToolUseBlock,\n TurnDuration,\n} from \"./types\";\n\nexport async function parseMeta(\n filePath: string,\n account: string,\n tier: ContentTier,\n): Promise<ConversationMeta | null> {\n const log = getLogger();\n log.trace({ filePath, account, tier: tier.name }, \"parseMeta: start\");\n let sessionId = \"\";\n let badJsonLines = 0;\n let sessionName = \"\";\n let latestTimestamp = \"\";\n let cwd = \"\";\n let teamName = \"\";\n let model: string | null = null;\n let messageCount = 0;\n let lastMessageSender: MessageSender = \"user\";\n let isTeammate = false;\n let firstUserSeen = false;\n let firstMessage: MessageSnapshot | null = null;\n let lastMessage: MessageSnapshot | null = null;\n let lastPrompt = \"\";\n const toolNameSet = new Set<string>();\n const previewParts: string[] = [];\n const snippetParts: string[] = [];\n let snippetLength = 0;\n let previewLength = 0;\n\n const fileStream = createReadStream(filePath);\n const rl = createInterface({ input: fileStream, crlfDelay: Infinity });\n\n try {\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n let entry: Record<string, unknown>;\n try {\n entry = JSON.parse(line);\n } catch {\n badJsonLines++;\n continue;\n }\n\n if (entry.cwd && !cwd) cwd = entry.cwd as string;\n if (entry.sessionId && !sessionId) sessionId = entry.sessionId as string;\n if (entry.slug && !sessionName) sessionName = entry.slug as string;\n if (entry.teamName && !teamName) teamName = entry.teamName as string;\n if (entry.timestamp) {\n const ts = entry.timestamp as string;\n if (!latestTimestamp || ts > latestTimestamp) latestTimestamp = ts;\n }\n\n const type = entry.type as string;\n\n if (type === \"last-prompt\") {\n if (entry.lastPrompt && !lastPrompt) lastPrompt = entry.lastPrompt as string;\n continue;\n }\n\n // file-history-snapshot entries are intentionally excluded:\n // they contain file-edit undo state, not conversation content.\n if (type !== \"user\" && type !== \"assistant\") continue;\n if (entry.isMeta) continue;\n\n // Extract model from first assistant message\n if (model === null) {\n const msg = entry.message as Record<string, unknown> | undefined;\n if (msg?.model) model = msg.model as string;\n }\n\n // Check for teammate in first user message\n if (type === \"user\" && !firstUserSeen) {\n firstUserSeen = true;\n if (isTeammateContent((entry.message as Record<string, unknown>)?.content)) {\n isTeammate = true;\n }\n }\n\n // Extract content\n const msg = entry.message as Record<string, unknown> | undefined;\n const content = extractTextContent(msg?.content);\n const hasToolUseResult = type === \"user\" && entry.toolUseResult != null;\n const isOnlyToolResult = hasToolUseResult && isOnlyToolResultContent(msg?.content);\n\n // Collect tool names\n collectToolNames(msg?.content, toolNameSet);\n\n if (content || isOnlyToolResult) {\n messageCount++;\n lastMessageSender = type as MessageSender;\n\n if (content) {\n const ts = (entry.timestamp as string) || \"\";\n if (!firstMessage) {\n firstMessage = { text: content.slice(0, 200), timestamp: ts };\n }\n lastMessage = { text: content.slice(0, 200), timestamp: ts };\n\n if (previewLength < tier.previewMax) {\n previewParts.push(content);\n previewLength += content.length;\n }\n if (snippetLength < tier.snippetMax) {\n const remaining = tier.snippetMax - snippetLength;\n const chunk = content.length > remaining ? content.slice(0, remaining) : content;\n snippetParts.push(chunk);\n snippetLength += chunk.length;\n }\n }\n }\n }\n } catch (err) {\n log.warn({ filePath, err }, \"parseMeta: read failed\");\n return null;\n }\n\n if (badJsonLines > 0) {\n log.warn({ filePath, badJsonLines }, \"parseMeta: skipped malformed JSON lines\");\n }\n\n if (messageCount === 0) {\n log.trace({ filePath }, \"parseMeta: no messages\");\n return null;\n }\n\n const isSubagent = filePath.includes(\"/subagents/\");\n let parentSessionId: string | null = null;\n if (isSubagent) {\n const uuidDir = dirname(dirname(filePath));\n parentSessionId = join(dirname(uuidDir), `${basename(uuidDir)}.jsonl`);\n }\n\n const projectPath = cwd;\n const preview = previewParts.join(\" \").slice(0, tier.previewMax);\n\n return {\n id: filePath,\n filePath,\n sessionId: sessionId || basename(filePath, \".jsonl\"),\n sessionName,\n projectPath,\n projectName: getShortProjectName(projectPath),\n account,\n timestamp: latestTimestamp || new Date().toISOString(),\n messageCount,\n lastMessageSender,\n preview,\n contentSnippet: snippetParts.join(\" \"),\n gitBranch: null,\n model,\n isSubagent,\n parentSessionId,\n isTeammate,\n teamName: teamName || null,\n toolNames: Array.from(toolNameSet),\n firstMessage,\n lastMessage,\n lastPrompt: lastPrompt || undefined,\n };\n}\n\nexport async function parseConversation(\n filePath: string,\n account: string,\n): Promise<Conversation | null> {\n const log = getLogger();\n log.trace({ filePath, account }, \"parseConversation: start\");\n const messages: ConversationMessage[] = [];\n let badJsonLines = 0;\n let sessionId = \"\";\n let sessionName = \"\";\n let latestTimestamp = \"\";\n let cwd = \"\";\n const textParts: string[] = [];\n const pendingToolUses = new Map<string, ToolUseBlock>();\n const teamInfoMap = new Map<string, TeamInfo>();\n const turnDurations: TurnDuration[] = [];\n let lastPrompt = \"\";\n\n const fileStream = createReadStream(filePath);\n const rl = createInterface({ input: fileStream, crlfDelay: Infinity });\n\n try {\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n let entry: Record<string, unknown>;\n try {\n entry = JSON.parse(line);\n } catch {\n badJsonLines++;\n continue;\n }\n\n if (entry.cwd && !cwd) cwd = entry.cwd as string;\n if (entry.sessionId && !sessionId) sessionId = entry.sessionId as string;\n if (entry.slug && !sessionName) sessionName = entry.slug as string;\n if (entry.timestamp) {\n const ts = entry.timestamp as string;\n if (!latestTimestamp || ts > latestTimestamp) latestTimestamp = ts;\n }\n\n const type = entry.type as string;\n\n if (type === \"last-prompt\") {\n if (entry.lastPrompt && !lastPrompt) lastPrompt = entry.lastPrompt as string;\n continue;\n }\n\n if (type === \"system\") {\n if (entry.subtype === \"turn_duration\" && typeof entry.durationMs === \"number\") {\n turnDurations.push({\n durationMs: entry.durationMs as number,\n messageCount: (entry.messageCount as number) || 0,\n uuid: entry.uuid as string | undefined,\n });\n }\n // file-history-snapshot entries are intentionally excluded:\n // they contain file-edit undo state, not conversation content.\n // stop_hook_summary and bridge_status are internal housekeeping only.\n continue;\n }\n\n if (type !== \"user\" && type !== \"assistant\") continue;\n if (entry.isMeta) continue;\n\n const msg = entry.message as Record<string, unknown> | undefined;\n\n // Track tool_use blocks from assistant messages\n const toolUseBlocks = extractToolUseBlocks(msg?.content);\n for (const block of toolUseBlocks) {\n pendingToolUses.set(block.id, block);\n }\n\n const hasToolUseResult = type === \"user\" && entry.toolUseResult != null;\n const isToolResultOnly = hasToolUseResult && isOnlyToolResultContent(msg?.content);\n\n const content = extractTextContent(msg?.content);\n\n const thinking = type === \"assistant\" ? extractThinking(msg?.content) : null;\n const hasThinking = !!(thinking?.content || thinking?.signature);\n\n if (content || isToolResultOnly || toolUseBlocks.length > 0 || hasThinking) {\n const metadata: MessageMetadata = {};\n\n if (msg?.model) metadata.model = msg.model as string;\n if (msg?.stop_reason !== undefined) metadata.stopReason = msg.stop_reason as string | null;\n if (entry.gitBranch) metadata.gitBranch = entry.gitBranch as string;\n if (entry.version) metadata.version = entry.version as string;\n\n const usage = msg?.usage as Record<string, number> | undefined;\n if (usage) {\n if (usage.input_tokens) metadata.inputTokens = usage.input_tokens;\n if (usage.output_tokens) metadata.outputTokens = usage.output_tokens;\n if (usage.cache_read_input_tokens)\n metadata.cacheReadTokens = usage.cache_read_input_tokens;\n if (usage.cache_creation_input_tokens)\n metadata.cacheCreationTokens = usage.cache_creation_input_tokens;\n }\n\n const toolUseNames = extractToolUseNames(msg?.content);\n if (toolUseNames.length > 0) metadata.toolUses = toolUseNames;\n if (toolUseBlocks.length > 0) metadata.toolUseBlocks = toolUseBlocks;\n\n if (isToolResultOnly) {\n const toolResultBlocks = extractToolResultBlocks(msg?.content, pendingToolUses);\n if (toolResultBlocks.length > 0) metadata.toolResults = toolResultBlocks;\n }\n\n if (entry.teamName) {\n metadata.teamName = entry.teamName as string;\n if (!teamInfoMap.has(metadata.teamName) && content) {\n const info = parseTeammateMessageTag(content);\n if (info) teamInfoMap.set(metadata.teamName, info);\n }\n }\n\n const thinkingContent = thinking?.content || undefined;\n const thinkingSignature = thinking?.signature || undefined;\n\n const hasMetadata = Object.keys(metadata).length > 0;\n\n messages.push({\n role: type as MessageSender,\n text: content || \"\",\n timestamp: (entry.timestamp as string) || \"\",\n uuid: (entry.uuid as string) || undefined,\n metadata: hasMetadata ? metadata : undefined,\n isToolResult: isToolResultOnly || undefined,\n isThinking: thinkingContent || thinkingSignature ? true : undefined,\n thinkingContent,\n thinkingSignature,\n parentUuid:\n entry.parentUuid !== undefined ? (entry.parentUuid as string | null) : undefined,\n requestId: type === \"assistant\" ? (entry.requestId as string | undefined) : undefined,\n promptId: type === \"user\" ? (entry.promptId as string | undefined) : undefined,\n isSidechain: typeof entry.isSidechain === \"boolean\" ? entry.isSidechain : undefined,\n permissionMode:\n type === \"user\" ? (entry.permissionMode as string | undefined) : undefined,\n hasImages: hasImageBlocks(msg?.content) || undefined,\n attachment:\n entry.attachment !== undefined ? (entry.attachment as AttachmentSidecar) : undefined,\n });\n if (content) textParts.push(content);\n }\n }\n } catch (err) {\n log.warn({ filePath, err }, \"parseConversation: read failed\");\n return null;\n }\n\n if (badJsonLines > 0) {\n log.warn({ filePath, badJsonLines }, \"parseConversation: skipped malformed JSON lines\");\n }\n\n if (messages.length === 0) {\n log.trace({ filePath }, \"parseConversation: no messages\");\n return null;\n }\n\n log.debug({ filePath, messageCount: messages.length }, \"parseConversation: complete\");\n\n // Apply collected team info to matching messages\n if (teamInfoMap.size > 0) {\n for (const msg of messages) {\n if (msg.metadata?.teamName) {\n const info = teamInfoMap.get(msg.metadata.teamName);\n if (info) msg.metadata.teamInfo = info;\n }\n }\n }\n\n return {\n id: filePath,\n filePath,\n projectPath: cwd,\n projectName: getShortProjectName(cwd),\n sessionId: sessionId || basename(filePath, \".jsonl\"),\n sessionName,\n messages,\n fullText: textParts.join(\" \"),\n timestamp: latestTimestamp || new Date().toISOString(),\n messageCount: messages.length,\n account,\n turnDurations: turnDurations.length > 0 ? turnDurations : undefined,\n lastPrompt: lastPrompt || undefined,\n };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction extractTextContent(content: unknown): string {\n if (!content) return \"\";\n if (typeof content === \"string\") return cleanSystemTags(content);\n if (Array.isArray(content)) {\n return content\n .map((item) => {\n if (typeof item === \"string\") return item;\n if (item?.type === \"text\" && item?.text) return item.text;\n if (item?.type === \"tool_result\" && typeof item?.content === \"string\") return item.content;\n return \"\";\n })\n .filter(Boolean)\n .map(cleanSystemTags)\n .join(\" \");\n }\n return \"\";\n}\n\nfunction extractToolUseNames(content: unknown): string[] {\n if (!Array.isArray(content)) return [];\n return content\n .filter((item) => item?.type === \"tool_use\" && item?.name)\n .map((item) => item.name as string);\n}\n\nfunction extractToolUseBlocks(content: unknown): ToolUseBlock[] {\n if (!Array.isArray(content)) return [];\n return content\n .filter((item) => item?.type === \"tool_use\" && item?.name && item?.id)\n .map((item) => ({\n id: item.id as string,\n name: item.name as string,\n input: (item.input as Record<string, unknown>) || {},\n }));\n}\n\nconst TOOL_NAME_TO_TYPE: Record<string, ToolResultBlock[\"type\"]> = {\n Edit: \"edit\",\n Write: \"write\",\n Read: \"read\",\n Bash: \"bash\",\n Grep: \"grep\",\n Glob: \"glob\",\n Agent: \"taskAgent\",\n TaskCreate: \"taskCreate\",\n TaskUpdate: \"taskUpdate\",\n};\n\nfunction extractToolResultBlocks(\n content: unknown,\n pendingToolUses: Map<string, ToolUseBlock>,\n): ToolResultBlock[] {\n if (!Array.isArray(content)) return [];\n return content\n .filter((item) => item?.type === \"tool_result\" && item?.tool_use_id)\n .map((item) => {\n const toolName = pendingToolUses.get(item.tool_use_id as string)?.name ?? \"\";\n return {\n toolUseId: item.tool_use_id as string,\n type: TOOL_NAME_TO_TYPE[toolName] ?? \"generic\",\n content:\n typeof item.content === \"string\"\n ? { text: item.content as string }\n : ((item.content as Record<string, unknown>) ?? {}),\n isError: typeof item.is_error === \"boolean\" ? item.is_error : undefined,\n };\n });\n}\n\nfunction collectToolNames(content: unknown, toolSet: Set<string>): void {\n if (!Array.isArray(content)) return;\n for (const item of content) {\n if (item?.type === \"tool_use\" && item?.name) {\n toolSet.add(item.name as string);\n }\n }\n}\n\nfunction isOnlyToolResultContent(content: unknown): boolean {\n if (!Array.isArray(content)) return false;\n return content.length > 0 && content.every((item) => item?.type === \"tool_result\");\n}\n\nfunction isTeammateContent(content: unknown): boolean {\n const raw =\n typeof content === \"string\"\n ? content\n : Array.isArray(content)\n ? content\n .map((item) =>\n typeof item === \"string\" ? item : item?.type === \"text\" ? (item.text ?? \"\") : \"\",\n )\n .join(\"\")\n : \"\";\n return raw.includes(\"<teammate-message\");\n}\n\nfunction extractThinking(content: unknown): { content: string; signature: string } {\n if (!Array.isArray(content)) return { content: \"\", signature: \"\" };\n const blocks = content.filter((item) => item?.type === \"thinking\");\n return {\n content: blocks\n .map((b) => b.thinking as string)\n .filter(Boolean)\n .join(\"\\n\\n\"),\n signature: blocks\n .map((b) => b.signature as string)\n .filter(Boolean)\n .join(\"\"),\n };\n}\n\nfunction hasImageBlocks(content: unknown): boolean {\n if (!Array.isArray(content)) return false;\n return content.some(\n (item) =>\n item?.type === \"image\" &&\n (item?.source?.type === \"base64\" || item?.file?.base64 !== undefined),\n );\n}\n\nfunction parseTeammateMessageTag(content: string): TeamInfo | null {\n const match = content.match(/<teammate-message\\s+([^>]*)>/);\n if (!match) return null;\n const attrs = match[1];\n const id = attrs.match(/teammate_id=\"([^\"]*)\"/)?.[1];\n if (!id) return null;\n const summary = attrs.match(/summary=\"([^\"]*)\"/)?.[1];\n const color = attrs.match(/color=\"([^\"]*)\"/)?.[1];\n return { teammateId: id, summary, color };\n}\n\nfunction getShortProjectName(fullPath: string): string {\n const parts = fullPath.split(\"/\").filter(Boolean);\n return parts.slice(-3).join(\"/\");\n}\n","const SYSTEM_TAGS = [\n \"system-reminder\",\n \"command-name\",\n \"command-message\",\n \"command-args\",\n \"ide_selection\",\n \"ide_opened_file\",\n \"local-command-stdout\",\n \"local-command-caveat\",\n \"retrieval_status\",\n \"task_id\",\n \"task_type\",\n \"task-id\",\n \"task-notification\",\n \"fast_mode_info\",\n \"persisted-output\",\n \"tool_use_error\",\n \"user-prompt-submit-hook\",\n \"thinking\",\n \"ask_user\",\n \"teammate-message\",\n];\n\nconst SYSTEM_TAG_RE = new RegExp(`<(${SYSTEM_TAGS.join(\"|\")})[^>]*>[\\\\s\\\\S]*?<\\\\/\\\\1>`, \"g\");\n\nexport function cleanSystemTags(text: string): string {\n return text\n .replace(SYSTEM_TAG_RE, \"\")\n .replace(/[^\\S\\n]+/g, \" \")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n","import type { ContentTier } from \"./types\";\n\nexport const DEFAULT_TIERS: Record<string, ContentTier> = {\n standard: { name: \"standard\", previewMax: 200, snippetMax: 5_000 },\n full: { name: \"full\", previewMax: 1_200, snippetMax: 50_000 },\n};\n\nexport function resolveTier(\n tierName: string,\n customTiers?: Record<string, ContentTier>,\n): ContentTier {\n const tier = customTiers?.[tierName] ?? DEFAULT_TIERS[tierName];\n if (!tier) {\n throw new Error(\n `Unknown tier \"${tierName}\". Available: ${Object.keys({ ...DEFAULT_TIERS, ...customTiers }).join(\", \")}`,\n );\n }\n return tier;\n}\n","// ─── Primitives ─────────────────────────────────────────────────────\n\nexport type MessageSender = \"user\" | \"assistant\";\nexport type Include = \"all\" | \"conversations\" | \"subagents\" | \"teammates\";\nexport type View = \"flat\" | \"tree\" | \"grouped\";\nexport type SortOrder = \"recent\" | \"oldest\" | \"messages-desc\" | \"messages-asc\" | \"alpha\";\n\nexport const VALID_SORT_ORDERS: SortOrder[] = [\n \"recent\",\n \"oldest\",\n \"messages-desc\",\n \"messages-asc\",\n \"alpha\",\n];\n\n// ─── Profile ────────────────────────────────────────────────────────\n\nexport interface Profile {\n id: string;\n label: string;\n configDir: string;\n enabled: boolean;\n emoji?: string;\n scanHistory?: boolean;\n}\n\n// ─── Content Tiers ──────────────────────────────────────────────────\n\nexport interface ContentTier {\n name: string;\n previewMax: number;\n snippetMax: number;\n}\n\n// ─── ConversationMeta (full superset) ───────────────────────────────\n\nexport interface MessageSnapshot {\n text: string;\n timestamp: string;\n}\n\nexport interface ConversationMeta {\n id: string;\n filePath: string;\n sessionId: string;\n sessionName: string;\n projectPath: string;\n projectName: string;\n account: string;\n timestamp: string;\n messageCount: number;\n lastMessageSender: MessageSender;\n preview: string;\n contentSnippet: string;\n gitBranch: string | null;\n model: string | null;\n isSubagent: boolean;\n parentSessionId: string | null;\n isTeammate: boolean;\n teamName: string | null;\n toolNames: string[];\n firstMessage: MessageSnapshot | null;\n lastMessage: MessageSnapshot | null;\n lastPrompt?: string;\n}\n\n// ─── View Variants ──────────────────────────────────────────────────\n\nexport interface TreeConversation extends ConversationMeta {\n subagents: ConversationMeta[];\n}\n\nexport interface GroupedConversations {\n [groupKey: string]: ConversationMeta[];\n}\n\n// ─── Options ────────────────────────────────────────────────────────\n\nexport interface FileStatEntry {\n mtimeMs: number;\n size: number;\n}\n\nexport interface ScanOptions {\n profiles?: Profile[];\n tier?: string;\n tiers?: Record<string, ContentTier>;\n include?: Include;\n view?: View;\n sort?: SortOrder;\n since?: string;\n project?: string;\n account?: string;\n limit?: number;\n offset?: number;\n onProgress?: (scanned: number, total: number) => void;\n onBatch?: (metas: ConversationMeta[]) => void;\n /** Known file stats from a previous scan. Files whose (mtimeMs, size) match\n * are skipped — the cached ConversationMeta is reused instead. */\n statCache?: Map<string, { stat: FileStatEntry; meta: ConversationMeta }>;\n}\n\nexport interface ScanResult {\n conversations: ConversationMeta[] | TreeConversation[] | GroupedConversations;\n total: number;\n scanned: number;\n}\n\nexport interface SearchOptions extends ScanOptions {\n fields?: string[];\n}\n\nexport interface SearchMatch {\n field: string;\n snippet: string;\n}\n\nexport interface SearchResult {\n meta: ConversationMeta;\n score: number;\n matches: SearchMatch[];\n}\n\nexport interface GetConversationOptions {\n profiles?: Profile[];\n}\n\nexport interface GetConversationPageOptions {\n beforeIndex?: number;\n limit: number;\n}\n\nexport interface ConversationPage {\n messages: ConversationMessage[];\n total: number;\n fromIndex: number;\n}\n\n// A page sliced directly from a single parsed JSONL file, carrying the parsed\n// Conversation alongside the window so a caller can build a full response\n// (meta + messages) from one parse, without a prior scan().\nexport interface SingleFilePage extends ConversationPage {\n conversation: Conversation;\n}\n\n// ─── Full Conversation ──────────────────────────────────────────────\n\nexport interface TurnDuration {\n durationMs: number;\n messageCount: number;\n uuid?: string;\n}\n\nexport interface DeferredToolsDeltaAttachment {\n type: \"deferred_tools_delta\";\n addedNames: string[];\n addedLines: unknown[];\n removedNames: string[];\n}\n\nexport type AttachmentSidecar =\n | DeferredToolsDeltaAttachment\n | { type: string; [key: string]: unknown };\n\nexport interface ToolUseBlock {\n id: string;\n name: string;\n input: Record<string, unknown>;\n}\n\nexport interface ToolResultBlock {\n toolUseId: string;\n type:\n | \"edit\"\n | \"write\"\n | \"read\"\n | \"bash\"\n | \"grep\"\n | \"glob\"\n | \"taskAgent\"\n | \"taskCreate\"\n | \"taskUpdate\"\n | \"generic\";\n content: Record<string, unknown>;\n isError?: boolean;\n}\n\nexport interface TeamInfo {\n teammateId: string;\n summary?: string;\n color?: string;\n}\n\nexport interface MessageMetadata {\n model?: string;\n stopReason?: string | null;\n inputTokens?: number;\n outputTokens?: number;\n cacheReadTokens?: number;\n cacheCreationTokens?: number;\n gitBranch?: string;\n version?: string;\n toolUses?: string[];\n toolUseBlocks?: ToolUseBlock[];\n toolResults?: ToolResultBlock[];\n teamName?: string;\n teamInfo?: TeamInfo;\n}\n\nexport interface ConversationMessage {\n role: MessageSender;\n text: string;\n timestamp: string;\n uuid?: string;\n metadata?: MessageMetadata;\n isToolResult?: boolean;\n isThinking?: boolean;\n thinkingContent?: string;\n thinkingSignature?: string;\n parentUuid?: string | null;\n requestId?: string;\n promptId?: string;\n isSidechain?: boolean;\n permissionMode?: string;\n hasImages?: boolean;\n attachment?: AttachmentSidecar;\n}\n\nexport interface Conversation {\n id: string;\n filePath: string;\n projectPath: string;\n projectName: string;\n sessionId: string;\n sessionName: string;\n messages: ConversationMessage[];\n fullText: string;\n timestamp: string;\n messageCount: number;\n account: string;\n turnDurations?: TurnDuration[];\n lastPrompt?: string;\n}\n"],"mappings":"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","names":["pino","FlexSearchModule","import_path","import_fs","import_promises","fg","import_fs","import_path","msg"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/filters.ts","../src/git.ts","../src/logger.ts","../src/indexer.ts","../src/search-matches.ts","../src/persistent/sidecar.ts","../src/profiles.ts","../src/providers/codex-cli.ts","../src/tags.ts","../src/providers/provider.ts","../src/discovery.ts","../src/persistent/metadata-reducer.ts","../src/parser.ts","../src/persistent/conversation-reducer.ts","../src/providers/threadbase.ts","../src/scanner.ts","../src/cache.ts","../src/persistent/cursor.ts","../src/providers/parse.ts","../src/tiers.ts","../src/persistent/db.ts","../src/persistent/schema.ts","../src/persistent/migrations.ts","../src/persistent/jsonl-tail-reader.ts","../src/persistent/paged-reader.ts","../src/persistent/repositories/checkpoints.repo.ts","../src/persistent/repositories/conversation-files.repo.ts","../src/persistent/repositories/conversations.repo.ts","../src/persistent/repositories/fts.repo.ts","../src/persistent/index-engine.ts","../src/watcher/file-watcher.ts","../src/watcher/index-queue.ts","../src/types.ts"],"sourcesContent":["export {\n applyAccountFilter,\n applyIncludeFilter,\n applyPagination,\n applyProjectFilter,\n applySinceFilter,\n applySort,\n} from \"./filters\";\nexport { readGitBranch } from \"./git\";\nexport { SearchIndexer } from \"./indexer\";\nexport type { Logger, LoggerOptions } from \"./logger\";\nexport { createLogger, getLogger, setLogger } from \"./logger\";\nexport type { Sidecar } from \"./persistent/sidecar\";\nexport { readSidecar, sidecarPath } from \"./persistent/sidecar\";\nexport {\n detectDefaultProfile,\n getProjectsDir,\n loadProfiles,\n resolveConfigDir,\n saveProfiles,\n} from \"./profiles\";\nexport { CodexCliProvider } from \"./providers/codex-cli\";\nexport type {\n DiscoveredConversationFile,\n ScannerProvider,\n ScannerProviderName,\n} from \"./providers/provider\";\nexport { ThreadbaseProvider } from \"./providers/threadbase\";\nexport type {\n ConversationScannerOptions,\n PersistentConfig,\n ScannerChangeEvent,\n WatchOptions,\n} from \"./scanner\";\nexport { ConversationScanner } from \"./scanner\";\nexport { cleanSystemTags } from \"./tags\";\nexport { DEFAULT_TIERS, resolveTier } from \"./tiers\";\nexport * from \"./types\";\n\n// ─── Standalone Convenience Functions ───────────────────────────────\n\nimport { ConversationScanner } from \"./scanner\";\nimport type {\n Conversation,\n GetConversationOptions,\n ScanOptions,\n ScanResult,\n SearchOptions,\n SearchResult,\n} from \"./types\";\n\nlet defaultScanner: ConversationScanner | undefined;\n\nfunction getDefaultScanner(): ConversationScanner {\n if (!defaultScanner) {\n defaultScanner = new ConversationScanner();\n }\n return defaultScanner;\n}\n\nexport function resetDefaultScanner(): void {\n defaultScanner = undefined;\n}\n\nexport async function scan(\n options?: ScanOptions,\n scanner?: ConversationScanner,\n): Promise<ScanResult> {\n return (scanner ?? getDefaultScanner()).scan(options);\n}\n\nexport async function search(\n query: string,\n options?: SearchOptions,\n scanner?: ConversationScanner,\n): Promise<SearchResult[]> {\n return (scanner ?? getDefaultScanner()).search(query, options);\n}\n\nexport async function getConversation(\n id: string,\n options?: GetConversationOptions,\n scanner?: ConversationScanner,\n): Promise<Conversation | null> {\n return (scanner ?? getDefaultScanner()).getConversation(id, options);\n}\n","import type { ConversationMeta, Include, SortOrder } from \"./types\";\n\nexport function applySort(metas: ConversationMeta[], order: SortOrder): ConversationMeta[] {\n const out = [...metas];\n // Tie-break by id (the file path) so the order is fully deterministic and does\n // not depend on input order. This matters because metas can arrive in\n // different orders (filesystem discovery vs. an SQLite query), and a tie on\n // the primary key would otherwise resolve differently between them.\n const tie = (a: ConversationMeta, b: ConversationMeta) => a.id.localeCompare(b.id);\n switch (order) {\n case \"recent\":\n out.sort((a, b) => b.timestamp.localeCompare(a.timestamp) || tie(a, b));\n break;\n case \"oldest\":\n out.sort((a, b) => a.timestamp.localeCompare(b.timestamp) || tie(a, b));\n break;\n case \"messages-desc\":\n out.sort((a, b) => b.messageCount - a.messageCount || tie(a, b));\n break;\n case \"messages-asc\":\n out.sort((a, b) => a.messageCount - b.messageCount || tie(a, b));\n break;\n case \"alpha\":\n out.sort((a, b) => {\n const cmp = a.projectName.localeCompare(b.projectName);\n if (cmp !== 0) return cmp;\n return a.preview.localeCompare(b.preview) || tie(a, b);\n });\n break;\n }\n return out;\n}\n\nexport function applySinceFilter(metas: ConversationMeta[], since: string): ConversationMeta[] {\n const cutoff = parseSinceCutoff(since);\n return metas.filter((m) => new Date(m.timestamp).getTime() >= cutoff.getTime());\n}\n\nexport function applyIncludeFilter(\n metas: ConversationMeta[],\n include: Include,\n): ConversationMeta[] {\n switch (include) {\n case \"all\":\n return metas;\n case \"conversations\":\n return metas.filter((m) => !m.isSubagent && !m.isTeammate);\n case \"subagents\":\n return metas.filter((m) => m.isSubagent);\n case \"teammates\":\n return metas.filter((m) => m.isTeammate);\n }\n}\n\nexport function applyProjectFilter(metas: ConversationMeta[], project: string): ConversationMeta[] {\n const lower = project.toLowerCase();\n return metas.filter(\n (m) =>\n m.projectPath.toLowerCase().includes(lower) || m.projectName.toLowerCase().includes(lower),\n );\n}\n\nexport function applyAccountFilter(metas: ConversationMeta[], account: string): ConversationMeta[] {\n return metas.filter((m) => m.account === account);\n}\n\nexport function applyPagination<T>(\n items: T[],\n limit: number,\n offset: number,\n): { items: T[]; total: number } {\n return {\n items: items.slice(offset, offset + limit),\n total: items.length,\n };\n}\n\nexport function parseSinceCutoff(value: string): Date {\n const s = value.trim();\n if (!s) throw new Error(\"Empty --since value\");\n\n // Try ISO date\n const isoMatch = s.match(/^\\d{4}-\\d{2}-\\d{2}$/);\n if (isoMatch) {\n const d = new Date(`${s}T00:00:00Z`);\n if (!Number.isNaN(d.getTime())) return d;\n }\n\n // Try duration: digits + unit\n const durationMatch = s.match(/^(\\d+)([hdw])$/);\n if (!durationMatch) {\n throw new Error(\n `Invalid --since value \"${s}\": expected duration like \"7d\", \"24h\", \"2w\" or ISO date \"2006-01-02\"`,\n );\n }\n\n const n = parseInt(durationMatch[1], 10);\n const unit = durationMatch[2];\n let ms: number;\n switch (unit) {\n case \"h\":\n ms = n * 60 * 60 * 1000;\n break;\n case \"d\":\n ms = n * 24 * 60 * 60 * 1000;\n break;\n case \"w\":\n ms = n * 7 * 24 * 60 * 60 * 1000;\n break;\n default:\n throw new Error(`Invalid unit \"${unit}\"`);\n }\n\n return new Date(Date.now() - ms);\n}\n","import { readFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { getLogger } from \"./logger\";\n\nconst REF_PREFIX = \"ref: refs/heads/\";\nconst MAX_DEPTH = 6;\n\nexport function readGitBranch(projectPath: string): string | null {\n if (!projectPath) return null;\n const log = getLogger();\n\n let dir = projectPath;\n let depth = 0;\n\n while (depth < MAX_DEPTH) {\n const headPath = join(dir, \".git\", \"HEAD\");\n try {\n const content = readFileSync(headPath, \"utf-8\").trim();\n if (content.startsWith(REF_PREFIX)) {\n const branch = content.slice(REF_PREFIX.length);\n log.trace({ projectPath, dir, branch }, \"git: branch resolved\");\n return branch;\n }\n // Detached HEAD: raw commit SHA\n if (content.length >= 7) {\n log.trace({ projectPath, dir }, \"git: detached HEAD\");\n return \"(detached)\";\n }\n return null;\n } catch {\n // .git/HEAD not found at this level\n }\n\n const parent = dirname(dir);\n if (parent === dir) return null; // filesystem root\n dir = parent;\n depth++;\n }\n\n log.trace({ projectPath }, \"git: no .git found within depth\");\n return null;\n}\n","import pino, { type Logger, type LoggerOptions } from \"pino\";\n\nlet currentLogger: Logger = pino({ level: \"silent\" });\n\nexport function createLogger(options?: LoggerOptions | Logger): Logger {\n if (options && typeof (options as Logger).child === \"function\") {\n return options as Logger;\n }\n return pino((options as LoggerOptions) ?? { level: \"silent\" });\n}\n\nexport function setLogger(logger: Logger): void {\n currentLogger = logger;\n}\n\nexport function getLogger(): Logger {\n return currentLogger;\n}\n\nexport type { Logger, LoggerOptions };\n","import FlexSearchModule from \"flexsearch\";\n\n// FlexSearch has inconsistent default export across ESM/CJS\nconst FlexSearch = (FlexSearchModule as any).default ?? FlexSearchModule;\n\nimport { getLogger } from \"./logger\";\nimport { generateMatches } from \"./search-matches\";\nimport type { ConversationMeta, SearchResult } from \"./types\";\n\nexport class SearchIndexer {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private index: any;\n private documents = new Map<string, ConversationMeta>();\n\n constructor() {\n this.index = this.createIndex();\n }\n\n private createIndex() {\n return new (FlexSearch as any).Document({\n document: {\n id: \"id\",\n index: [\n \"content\",\n \"projectName\",\n \"projectPath\",\n \"sessionId\",\n \"sessionName\",\n \"account\",\n \"model\",\n \"gitBranch\",\n \"toolNames\",\n ],\n store: [\"id\"],\n },\n tokenize: \"forward\",\n resolution: 9,\n cache: 100,\n });\n }\n\n addDocument(meta: ConversationMeta): void {\n this.documents.set(meta.id, meta);\n this.index.add({\n id: meta.id,\n content: meta.contentSnippet,\n projectName: meta.projectName,\n projectPath: meta.projectPath,\n sessionId: meta.sessionId,\n sessionName: meta.sessionName,\n account: meta.account,\n model: meta.model || \"\",\n gitBranch: meta.gitBranch || \"\",\n toolNames: meta.toolNames.join(\" \"),\n });\n }\n\n buildIndex(metas: ConversationMeta[]): void {\n this.clear();\n for (const meta of metas) {\n this.addDocument(meta);\n }\n getLogger().debug({ docCount: metas.length }, \"indexer: built\");\n }\n\n search(query: string, options?: { fields?: string[]; limit?: number }): SearchResult[] {\n const limit = options?.limit ?? 50;\n\n if (!query.trim()) {\n return this.getRecent(limit);\n }\n\n const results = this.index.search(query, { limit: limit * 2, enrich: true });\n\n const seen = new Set<string>();\n const searchResults: SearchResult[] = [];\n\n for (const fieldResult of results) {\n if (!fieldResult.result) continue;\n for (const item of fieldResult.result) {\n const id = typeof item === \"object\" ? (item as { id: string }).id : String(item);\n if (seen.has(id)) continue;\n seen.add(id);\n\n const meta = this.documents.get(id);\n if (!meta) continue;\n\n const matches = generateMatches(meta, query);\n\n searchResults.push({ meta, score: 1, matches });\n if (searchResults.length >= limit) break;\n }\n if (searchResults.length >= limit) break;\n }\n\n return searchResults;\n }\n\n private getRecent(limit: number): SearchResult[] {\n return Array.from(this.documents.values())\n .sort((a, b) => b.timestamp.localeCompare(a.timestamp))\n .slice(0, limit)\n .map((meta) => ({\n meta,\n score: 1,\n matches: [{ field: \"timestamp\", snippet: meta.preview }],\n }));\n }\n\n getDocumentCount(): number {\n return this.documents.size;\n }\n\n // Replace an already-indexed document in place. FlexSearch's `add` does not\n // overwrite an existing id, so a single-file refresh must go through\n // `update` to avoid stale matches lingering in the index.\n updateDocument(meta: ConversationMeta): void {\n this.documents.set(meta.id, meta);\n this.index.update({\n id: meta.id,\n content: meta.contentSnippet,\n projectName: meta.projectName,\n projectPath: meta.projectPath,\n sessionId: meta.sessionId,\n sessionName: meta.sessionName,\n account: meta.account,\n model: meta.model || \"\",\n gitBranch: meta.gitBranch || \"\",\n toolNames: meta.toolNames.join(\" \"),\n });\n }\n\n removeDocument(id: string): void {\n this.documents.delete(id);\n this.index.remove(id);\n }\n\n clear(): void {\n this.documents.clear();\n this.index = this.createIndex();\n getLogger().trace(\"indexer: cleared\");\n }\n}\n","import type { ConversationMeta, SearchMatch } from \"./types\";\n\n// Build the per-field match snippets for a search hit. Shared by the in-memory\n// FlexSearch indexer and the persistent FTS backend so search() returns\n// identical SearchMatch output regardless of which engine ran the query.\nexport function generateMatches(meta: ConversationMeta, query: string): SearchMatch[] {\n const matches: SearchMatch[] = [];\n const lowerQuery = query.toLowerCase();\n\n const fields: [string, string][] = [\n [\"contentSnippet\", meta.contentSnippet],\n [\"projectName\", meta.projectName],\n [\"sessionId\", meta.sessionId],\n [\"sessionName\", meta.sessionName],\n [\"account\", meta.account],\n [\"model\", meta.model || \"\"],\n [\"gitBranch\", meta.gitBranch || \"\"],\n [\"toolNames\", meta.toolNames.join(\" \")],\n ];\n\n for (const [field, value] of fields) {\n const idx = value.toLowerCase().indexOf(lowerQuery);\n if (idx !== -1) {\n const start = Math.max(0, idx - 80);\n const end = Math.min(value.length, idx + query.length + 120);\n let snippet = value.slice(start, end);\n if (start > 0) snippet = `...${snippet}`;\n if (end < value.length) snippet = `${snippet}...`;\n matches.push({ field, snippet });\n }\n }\n\n return matches.length > 0 ? matches : [{ field: \"preview\", snippet: meta.preview }];\n}\n","import { readFileSync, writeFileSync } from \"fs\";\nimport { getLogger } from \"../logger\";\nimport type { ConversationMeta } from \"../types\";\n\nexport const SIDECAR_VERSION = 1;\n\n// Portable, human-readable index summary written next to a JSONL file as\n// `<file>.idx.json`. Not the canonical index (SQLite is) — useful for\n// debugging, portability, and rebuilding the DB if it's lost. Off by default.\nexport interface Sidecar {\n version: number;\n sourcePath: string;\n sizeBytes: number;\n mtimeMs: number;\n lastIndexedOffset: number;\n lastIndexedLine: number;\n messageCount: number;\n projectPath: string;\n projectName: string;\n branch: string | null;\n firstSentAt: string | null;\n firstSentText: string | null;\n lastSentAt: string | null;\n lastSentText: string | null;\n updatedAt: string;\n}\n\nexport function sidecarPath(jsonlPath: string): string {\n return `${jsonlPath}.idx.json`;\n}\n\nexport function buildSidecar(\n meta: ConversationMeta,\n cursor: { sizeBytes: number; mtimeMs: number; offset: number; line: number },\n updatedAt: string,\n): Sidecar {\n return {\n version: SIDECAR_VERSION,\n sourcePath: meta.filePath,\n sizeBytes: cursor.sizeBytes,\n mtimeMs: cursor.mtimeMs,\n lastIndexedOffset: cursor.offset,\n lastIndexedLine: cursor.line,\n messageCount: meta.messageCount,\n projectPath: meta.projectPath,\n projectName: meta.projectName,\n branch: meta.gitBranch,\n firstSentAt: meta.firstMessage?.timestamp ?? null,\n firstSentText: meta.firstMessage?.text ?? null,\n lastSentAt: meta.lastMessage?.timestamp ?? null,\n lastSentText: meta.lastMessage?.text ?? null,\n updatedAt,\n };\n}\n\n// Write the sidecar; never throw into the indexing path — a sidecar is a\n// best-effort artifact, so a write failure (read-only dir, etc.) is logged and\n// swallowed.\nexport function writeSidecar(jsonlPath: string, sidecar: Sidecar): void {\n try {\n writeFileSync(sidecarPath(jsonlPath), JSON.stringify(sidecar, null, 2));\n } catch (err) {\n getLogger().warn({ jsonlPath, err }, \"sidecar: write failed\");\n }\n}\n\nexport function readSidecar(jsonlPath: string): Sidecar | null {\n try {\n return JSON.parse(readFileSync(sidecarPath(jsonlPath), \"utf-8\")) as Sidecar;\n } catch {\n return null;\n }\n}\n","import { mkdir, readFile, writeFile } from \"fs/promises\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { getLogger } from \"./logger\";\nimport type { Profile } from \"./types\";\n\nconst PROFILES_FILE = \"profiles.json\";\n\nexport function resolveConfigDir(configDir: string): string {\n return configDir.replace(/^~/, homedir());\n}\n\nexport function getProjectsDir(profile: Profile): string {\n return join(resolveConfigDir(profile.configDir), \"projects\");\n}\n\nexport async function detectDefaultProfile(): Promise<Profile> {\n return {\n id: \"default\",\n label: \"Default\",\n configDir: join(homedir(), \".claude\"),\n enabled: true,\n emoji: \"🤖\",\n };\n}\n\nexport async function loadProfiles(configPath: string): Promise<Profile[]> {\n const log = getLogger();\n try {\n const resolved = resolveConfigDir(configPath);\n const data = await readFile(join(resolved, PROFILES_FILE), \"utf-8\");\n const profiles = JSON.parse(data) as Profile[];\n log.debug({ configPath, count: profiles.length }, \"profiles: loaded\");\n return profiles;\n } catch (err) {\n log.debug({ configPath, err }, \"profiles: load failed, using default\");\n const defaultProfile = await detectDefaultProfile();\n return [defaultProfile];\n }\n}\n\nexport async function saveProfiles(profiles: Profile[], configPath: string): Promise<void> {\n const resolved = resolveConfigDir(configPath);\n await mkdir(resolved, { recursive: true });\n await writeFile(join(resolved, PROFILES_FILE), JSON.stringify(profiles, null, 2));\n getLogger().debug({ configPath, count: profiles.length }, \"profiles: saved\");\n}\n","import fg from \"fast-glob\";\nimport { createReadStream } from \"fs\";\nimport { stat } from \"fs/promises\";\nimport { basename } from \"path\";\nimport { createInterface } from \"readline\";\nimport { getLogger } from \"../logger\";\nimport { cleanSystemTags } from \"../tags\";\nimport type {\n ContentTier,\n Conversation,\n ConversationMessage,\n ConversationMeta,\n MessageSender,\n MessageSnapshot,\n} from \"../types\";\nimport {\n CODEX_CLI_PROVIDER,\n type DiscoveredConversationFile,\n type ScannerProvider,\n} from \"./provider\";\n\n// Serializable fold state for a Codex CLI rollout session. Plain JSON so a\n// future persistent engine can resume it from a byte offset (same contract as\n// the Threadbase ReducerState).\nexport interface CodexAccumulator {\n sessionId: string;\n cwd: string;\n gitBranch: string | null;\n model: string | null;\n latestTimestamp: string;\n messageCount: number;\n lastMessageSender: MessageSender;\n firstUser: MessageSnapshot | null;\n lastUser: MessageSnapshot | null;\n lastAssistant: MessageSnapshot | null;\n toolNames: string[];\n previewParts: string[];\n previewLength: number;\n snippetParts: string[];\n snippetLength: number;\n}\n\n// Codex CLI rollout sessions live as rollout-*.jsonl under a date-partitioned\n// tree. The Codex provider is opt-in: the scanner only discovers under roots the\n// caller passes explicitly (no default home scan).\nexport class CodexCliProvider implements ScannerProvider<CodexAccumulator> {\n readonly name = CODEX_CLI_PROVIDER;\n\n async discover(roots: string[]): Promise<DiscoveredConversationFile[]> {\n const log = getLogger();\n const results: DiscoveredConversationFile[] = [];\n for (const root of roots) {\n let paths: string[];\n try {\n paths = await fg([\"**/rollout-*.jsonl\", \"**/*.jsonl\"], {\n cwd: root,\n absolute: true,\n dot: false,\n unique: true,\n });\n } catch (err) {\n log.warn({ root, err }, \"codex discovery: glob failed\");\n continue;\n }\n for (const filePath of paths) {\n try {\n const s = await stat(filePath);\n if (s.size > 0) results.push({ filePath, account: \"codex\" });\n } catch (err) {\n log.warn({ filePath, err }, \"codex discovery: stat failed\");\n }\n }\n }\n return results;\n }\n\n // Codex rollout lines carry distinctive top-level types.\n canParse(_filePath: string, sample: string): boolean {\n for (const line of sample.split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n const e = JSON.parse(line) as Record<string, unknown>;\n if (e.type === \"session_meta\" || e.type === \"response_item\" || e.type === \"event_msg\") {\n return true;\n }\n if (e.type === \"user\" || e.type === \"assistant\") return false;\n } catch {}\n }\n return false;\n }\n\n createEmptyAccumulator(): CodexAccumulator {\n return {\n sessionId: \"\",\n cwd: \"\",\n gitBranch: null,\n model: null,\n latestTimestamp: \"\",\n messageCount: 0,\n lastMessageSender: \"user\",\n firstUser: null,\n lastUser: null,\n lastAssistant: null,\n toolNames: [],\n previewParts: [],\n previewLength: 0,\n snippetParts: [],\n snippetLength: 0,\n };\n }\n\n reduceEntry(acc: CodexAccumulator, entry: Record<string, unknown>, tier: ContentTier): void {\n reduceCodexEntry(acc, entry, tier);\n }\n\n finalize(\n acc: CodexAccumulator,\n filePath: string,\n account: string,\n tier: ContentTier,\n ): ConversationMeta | null {\n return finalizeCodexMeta(acc, filePath, account, tier);\n }\n}\n\n// ─── Reducer (exported for tests / future incremental engine) ───────────\n\nconst asString = (v: unknown): string => (typeof v === \"string\" ? v : \"\");\n\n// Pull display text out of a Codex content array of {type,text} blocks.\nfunction extractCodexText(content: unknown): string {\n if (typeof content === \"string\") return cleanSystemTags(content);\n if (!Array.isArray(content)) return \"\";\n return content\n .map((item) => {\n if (typeof item === \"string\") return item;\n const t = item?.type;\n if ((t === \"input_text\" || t === \"output_text\" || t === \"text\") && item?.text) {\n return item.text as string;\n }\n return \"\";\n })\n .filter(Boolean)\n .map(cleanSystemTags)\n .join(\" \");\n}\n\n// Fold one Codex rollout line into the accumulator. Tolerant by construction:\n// any line it doesn't recognise is ignored, never thrown on.\nexport function reduceCodexEntry(\n acc: CodexAccumulator,\n entry: Record<string, unknown>,\n tier: ContentTier,\n): void {\n const ts = asString(entry.timestamp);\n if (ts && (!acc.latestTimestamp || ts > acc.latestTimestamp)) acc.latestTimestamp = ts;\n\n const payload = entry.payload as Record<string, unknown> | undefined;\n if (!payload || typeof payload !== \"object\") return;\n\n const type = entry.type as string;\n\n if (type === \"session_meta\") {\n if (!acc.sessionId) acc.sessionId = asString(payload.id);\n if (!acc.cwd) acc.cwd = asString(payload.cwd);\n const git = payload.git as Record<string, unknown> | undefined;\n if (acc.gitBranch === null && git?.branch) acc.gitBranch = asString(git.branch) || null;\n return;\n }\n\n // Model lives on turn_context records (and occasionally elsewhere).\n if (acc.model === null && payload.model) acc.model = asString(payload.model) || null;\n\n if (type !== \"response_item\") return;\n\n const ptype = payload.type as string;\n\n // Tool calls — collect the tool name.\n if (ptype === \"function_call\" || ptype === \"custom_tool_call\") {\n const name = asString(payload.name);\n if (name && !acc.toolNames.includes(name)) acc.toolNames.push(name);\n return;\n }\n\n if (ptype !== \"message\") return;\n\n const role = payload.role;\n // developer/system/tool roles carry sandbox boilerplate — not user-visible turns.\n if (role !== \"user\" && role !== \"assistant\") return;\n\n const text = extractCodexText(payload.content);\n if (!text) return;\n\n const sender = role as MessageSender;\n acc.messageCount++;\n acc.lastMessageSender = sender;\n\n const snapshot: MessageSnapshot = { text: text.slice(0, 200), timestamp: ts };\n if (sender === \"user\") {\n if (!acc.firstUser) acc.firstUser = snapshot;\n acc.lastUser = snapshot;\n } else {\n acc.lastAssistant = snapshot;\n }\n\n if (acc.previewLength < tier.previewMax) {\n acc.previewParts.push(text);\n acc.previewLength += text.length;\n }\n if (acc.snippetLength < tier.snippetMax) {\n const remaining = tier.snippetMax - acc.snippetLength;\n const chunk = text.length > remaining ? text.slice(0, remaining) : text;\n acc.snippetParts.push(chunk);\n acc.snippetLength += chunk.length;\n }\n}\n\nexport function finalizeCodexMeta(\n acc: CodexAccumulator,\n filePath: string,\n account: string,\n tier: ContentTier,\n): ConversationMeta | null {\n if (acc.messageCount === 0) return null;\n\n const sessionId = acc.sessionId || basename(filePath, \".jsonl\");\n const projectPath = acc.cwd;\n // Treat a session with tool calls but no plain back-and-forth as task-shaped.\n const kind: \"conversation\" | \"task\" =\n acc.lastAssistant === null && acc.toolNames.length > 0 ? \"task\" : \"conversation\";\n\n return {\n id: filePath,\n filePath,\n provider: CODEX_CLI_PROVIDER,\n kind,\n externalSessionId: acc.sessionId || undefined,\n sessionId,\n sessionName: \"\",\n projectPath,\n projectName: getShortProjectName(projectPath),\n account,\n timestamp: acc.latestTimestamp || new Date().toISOString(),\n messageCount: acc.messageCount,\n lastMessageSender: acc.lastMessageSender,\n preview: acc.previewParts.join(\" \").slice(0, tier.previewMax),\n contentSnippet: acc.snippetParts.join(\" \"),\n gitBranch: acc.gitBranch,\n model: acc.model,\n isSubagent: false,\n parentSessionId: null,\n isTeammate: false,\n teamName: null,\n toolNames: acc.toolNames,\n firstMessage: acc.firstUser,\n lastMessage: acc.lastAssistant ?? acc.lastUser,\n lastPrompt: acc.lastUser?.text || undefined,\n };\n}\n\nfunction getShortProjectName(fullPath: string): string {\n return fullPath.split(\"/\").filter(Boolean).slice(-3).join(\"/\");\n}\n\n// Full conversation parse for a Codex rollout session — the Codex analogue of\n// parseConversation(). Streams the file, emitting one ConversationMessage per\n// user/assistant message item. Used by getConversation()/getConversationPage()\n// when the resolved meta is a codex-cli conversation.\nexport async function parseCodexConversation(\n filePath: string,\n account: string,\n): Promise<Conversation | null> {\n const log = getLogger();\n const messages: ConversationMessage[] = [];\n const textParts: string[] = [];\n let sessionId = \"\";\n let cwd = \"\";\n let latestTimestamp = \"\";\n let lastUserText = \"\";\n\n const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });\n try {\n for await (const line of rl) {\n if (!line.trim()) continue;\n let entry: Record<string, unknown>;\n try {\n entry = JSON.parse(line);\n } catch {\n continue;\n }\n const ts = asString(entry.timestamp);\n if (ts && (!latestTimestamp || ts > latestTimestamp)) latestTimestamp = ts;\n const payload = entry.payload as Record<string, unknown> | undefined;\n if (!payload || typeof payload !== \"object\") continue;\n\n if (entry.type === \"session_meta\") {\n if (!sessionId) sessionId = asString(payload.id);\n if (!cwd) cwd = asString(payload.cwd);\n continue;\n }\n if (entry.type !== \"response_item\" || payload.type !== \"message\") continue;\n const role = payload.role;\n if (role !== \"user\" && role !== \"assistant\") continue;\n const text = extractCodexText(payload.content);\n if (!text) continue;\n messages.push({ role: role as MessageSender, text, timestamp: ts });\n textParts.push(text);\n if (role === \"user\") lastUserText = text;\n }\n } catch (err) {\n log.warn({ filePath, err }, \"parseCodexConversation: read failed\");\n return null;\n }\n\n if (messages.length === 0) return null;\n\n return {\n id: filePath,\n filePath,\n projectPath: cwd,\n projectName: getShortProjectName(cwd),\n sessionId: sessionId || basename(filePath, \".jsonl\"),\n sessionName: \"\",\n messages,\n fullText: textParts.join(\" \"),\n timestamp: latestTimestamp || new Date().toISOString(),\n messageCount: messages.length,\n account,\n lastPrompt: lastUserText || undefined,\n };\n}\n","const SYSTEM_TAGS = [\n \"system-reminder\",\n \"command-name\",\n \"command-message\",\n \"command-args\",\n \"ide_selection\",\n \"ide_opened_file\",\n \"local-command-stdout\",\n \"local-command-caveat\",\n \"retrieval_status\",\n \"task_id\",\n \"task_type\",\n \"task-id\",\n \"task-notification\",\n \"fast_mode_info\",\n \"persisted-output\",\n \"tool_use_error\",\n \"user-prompt-submit-hook\",\n \"thinking\",\n \"ask_user\",\n \"teammate-message\",\n];\n\nconst SYSTEM_TAG_RE = new RegExp(`<(${SYSTEM_TAGS.join(\"|\")})[^>]*>[\\\\s\\\\S]*?<\\\\/\\\\1>`, \"g\");\n\nexport function cleanSystemTags(text: string): string {\n return text\n .replace(SYSTEM_TAG_RE, \"\")\n .replace(/[^\\S\\n]+/g, \" \")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n","import type { ContentTier, ConversationMeta } from \"../types\";\n\n// A scanner provider knows how to discover and parse one local conversation-log\n// format into the shared ConversationMeta model. The reducer triplet\n// (createEmptyAccumulator / reduceEntry / finalize) is the same fold used by a\n// full streamed parse and — by design — a future offset-resumed incremental\n// parse, since the accumulator is plain serializable state. See\n// metadata-reducer.ts for the Threadbase implementation this mirrors.\nexport interface ScannerProvider<Acc = unknown> {\n name: ScannerProviderName;\n\n // Discover candidate files for this provider under the given roots. Roots are\n // already absolute. Returns one entry per file worth parsing.\n discover(roots: string[]): Promise<DiscoveredConversationFile[]>;\n\n // Cheap structural sniff: does this file look like this provider's format?\n // `sample` is the first few lines (newline-joined). Used so a single roots\n // list can be shared across providers without misclassifying files.\n canParse(filePath: string, sample: string): boolean;\n\n createEmptyAccumulator(): Acc;\n\n // Fold one already-JSON.parsed line into the accumulator, in place. Must never\n // throw on an unknown entry shape — ignore what it doesn't understand.\n reduceEntry(acc: Acc, entry: Record<string, unknown>, tier: ContentTier): void;\n\n // Build the ConversationMeta from the accumulator, or null if no messages.\n finalize(acc: Acc, filePath: string, account: string, tier: ContentTier): ConversationMeta | null;\n}\n\nexport type ScannerProviderName = \"claude-code\" | \"codex-cli\";\n\nexport const CLAUDE_CODE_PROVIDER = \"claude-code\" as const;\nexport const CODEX_CLI_PROVIDER = \"codex-cli\" as const;\n\nexport interface DiscoveredConversationFile {\n filePath: string;\n account: string;\n}\n","import fg from \"fast-glob\";\nimport { stat } from \"fs/promises\";\nimport { getLogger } from \"./logger\";\n\nexport interface DiscoveredFile {\n filePath: string;\n account: string;\n}\n\nconst EXCLUDED_SEGMENTS = [\"/memory/\", \"/tool-results/\"];\n\n// stat() is cheap and IO-bound, so a large history scans much faster when the\n// calls run concurrently rather than one-at-a-time. Cap concurrency well under\n// typical fd limits.\nconst STAT_CONCURRENCY = 32;\n\nexport async function discoverJsonlFiles(\n dirs: { projectsDir: string; account: string }[],\n onProgress?: (found: number) => void,\n): Promise<DiscoveredFile[]> {\n const log = getLogger();\n const results: DiscoveredFile[] = [];\n\n for (const { projectsDir, account } of dirs) {\n let filePaths: string[];\n try {\n filePaths = await fg(\"**/*.jsonl\", {\n cwd: projectsDir,\n absolute: true,\n dot: false,\n });\n } catch (err) {\n log.warn({ projectsDir, account, err }, \"discovery: glob failed\");\n continue;\n }\n\n // Filter out excluded directory segments\n const filtered = filePaths.filter((fp) => !EXCLUDED_SEGMENTS.some((seg) => fp.includes(seg)));\n\n // Filter out empty files. Stat concurrently in bounded chunks; chunk order\n // is preserved and intra-chunk order follows the input, so discovery order\n // is stable (downstream sorts anyway).\n let kept = 0;\n let skippedEmpty = 0;\n let skippedInaccessible = 0;\n for (let i = 0; i < filtered.length; i += STAT_CONCURRENCY) {\n const chunk = filtered.slice(i, i + STAT_CONCURRENCY);\n const statted = await Promise.all(\n chunk.map(async (filePath) => {\n try {\n const s = await stat(filePath);\n return { filePath, size: s.size };\n } catch (err) {\n log.warn({ filePath, err }, \"discovery: stat failed\");\n return { filePath, size: -1 };\n }\n }),\n );\n for (const { filePath, size } of statted) {\n if (size < 0) {\n skippedInaccessible++;\n } else if (size > 0) {\n results.push({ filePath, account });\n kept++;\n } else {\n skippedEmpty++;\n }\n }\n }\n\n log.debug(\n {\n projectsDir,\n account,\n globMatches: filePaths.length,\n afterExclusions: filtered.length,\n kept,\n skippedEmpty,\n skippedInaccessible,\n },\n \"discovery: directory scanned\",\n );\n\n onProgress?.(results.length);\n }\n\n log.debug({ totalFiles: results.length, dirs: dirs.length }, \"discovery: complete\");\n return results;\n}\n","import { basename, dirname, join } from \"path\";\nimport {\n collectToolNames,\n extractTextContent,\n extractThinking,\n extractToolUseBlocks,\n isOnlyToolResultContent,\n isTeammateContent,\n} from \"../parser\";\nimport { CLAUDE_CODE_PROVIDER } from \"../providers/provider\";\nimport type { ContentTier, ConversationMeta, MessageSender, MessageSnapshot } from \"../types\";\n\n// Serializable fold state for incremental metadata indexing. parseMeta()'s\n// per-line loop is expressed as reduceLine(state, entry); finalizeMeta(state)\n// produces the ConversationMeta. Because the state is plain JSON, it can be\n// persisted in conversation_files.reducer_state and resumed when a file grows,\n// so an append only needs to fold the newly-read lines.\nexport interface ReducerState {\n sessionId: string;\n sessionName: string;\n latestTimestamp: string;\n cwd: string;\n teamName: string;\n model: string | null;\n messageCount: number;\n // Count of messages parseConversation() would produce — broader than\n // messageCount (also counts tool_use-only and thinking-only lines). Used as\n // the page total for bounded reads, since metadata messageCount differs.\n pageMessageCount: number;\n lastMessageSender: MessageSender;\n isTeammate: boolean;\n firstUserSeen: boolean;\n firstMessage: MessageSnapshot | null;\n lastMessage: MessageSnapshot | null;\n lastPrompt: string;\n toolNames: string[];\n previewParts: string[];\n snippetParts: string[];\n previewLength: number;\n snippetLength: number;\n badJsonLines: number;\n}\n\nexport function initialReducerState(): ReducerState {\n return {\n sessionId: \"\",\n sessionName: \"\",\n latestTimestamp: \"\",\n cwd: \"\",\n teamName: \"\",\n model: null,\n messageCount: 0,\n lastMessageSender: \"user\",\n isTeammate: false,\n firstUserSeen: false,\n firstMessage: null,\n lastMessage: null,\n lastPrompt: \"\",\n pageMessageCount: 0,\n toolNames: [],\n previewParts: [],\n snippetParts: [],\n previewLength: 0,\n snippetLength: 0,\n badJsonLines: 0,\n };\n}\n\n// Fold one already-parsed JSONL entry into the state, mutating it in place.\n// Mirrors parseMeta()'s loop body exactly so a full reduce equals a streamed\n// parseMeta. `entry` is the JSON.parse() of one non-empty line.\nexport function reduceLine(\n state: ReducerState,\n entry: Record<string, unknown>,\n tier: ContentTier,\n): void {\n if (entry.cwd && !state.cwd) state.cwd = entry.cwd as string;\n if (entry.sessionId && !state.sessionId) state.sessionId = entry.sessionId as string;\n if (entry.slug && !state.sessionName) state.sessionName = entry.slug as string;\n if (entry.teamName && !state.teamName) state.teamName = entry.teamName as string;\n if (entry.timestamp) {\n const ts = entry.timestamp as string;\n if (!state.latestTimestamp || ts > state.latestTimestamp) state.latestTimestamp = ts;\n }\n\n const type = entry.type as string;\n\n if (type === \"last-prompt\") {\n if (entry.lastPrompt && !state.lastPrompt) state.lastPrompt = entry.lastPrompt as string;\n return;\n }\n\n // file-history-snapshot and other non-message entries are excluded.\n if (type !== \"user\" && type !== \"assistant\") return;\n if (entry.isMeta) return;\n\n const msg = entry.message as Record<string, unknown> | undefined;\n\n if (state.model === null && msg?.model) state.model = msg.model as string;\n\n if (type === \"user\" && !state.firstUserSeen) {\n state.firstUserSeen = true;\n if (isTeammateContent(msg?.content)) state.isTeammate = true;\n }\n\n const content = extractTextContent(msg?.content);\n const hasToolUseResult = type === \"user\" && entry.toolUseResult != null;\n const isOnlyToolResult = hasToolUseResult && isOnlyToolResultContent(msg?.content);\n\n const toolSet = new Set(state.toolNames);\n collectToolNames(msg?.content, toolSet);\n state.toolNames = Array.from(toolSet);\n\n // Count messages the same way parseConversation does, so bounded paging has a\n // correct total. (Broader than messageCount: includes tool_use-only and\n // thinking-only lines.)\n const toolUseBlocks = extractToolUseBlocks(msg?.content);\n const thinking = type === \"assistant\" ? extractThinking(msg?.content) : null;\n const hasThinking = !!(thinking?.content || thinking?.signature);\n if (content || isOnlyToolResult || toolUseBlocks.length > 0 || hasThinking) {\n state.pageMessageCount++;\n }\n\n if (content || isOnlyToolResult) {\n state.messageCount++;\n state.lastMessageSender = type as MessageSender;\n\n if (content) {\n const ts = (entry.timestamp as string) || \"\";\n if (!state.firstMessage) state.firstMessage = { text: content.slice(0, 200), timestamp: ts };\n state.lastMessage = { text: content.slice(0, 200), timestamp: ts };\n\n if (state.previewLength < tier.previewMax) {\n state.previewParts.push(content);\n state.previewLength += content.length;\n }\n if (state.snippetLength < tier.snippetMax) {\n const remaining = tier.snippetMax - state.snippetLength;\n const chunk = content.length > remaining ? content.slice(0, remaining) : content;\n state.snippetParts.push(chunk);\n state.snippetLength += chunk.length;\n }\n }\n }\n}\n\n// Build the ConversationMeta from accumulated state, or null when no messages\n// were seen — matching parseMeta()'s final shape exactly.\nexport function finalizeMeta(\n state: ReducerState,\n filePath: string,\n account: string,\n tier: ContentTier,\n): ConversationMeta | null {\n if (state.messageCount === 0) return null;\n\n const isSubagent = filePath.includes(\"/subagents/\");\n let parentSessionId: string | null = null;\n if (isSubagent) {\n const uuidDir = dirname(dirname(filePath));\n parentSessionId = join(dirname(uuidDir), `${basename(uuidDir)}.jsonl`);\n }\n\n const projectPath = state.cwd;\n return {\n id: filePath,\n filePath,\n provider: CLAUDE_CODE_PROVIDER,\n sessionId: state.sessionId || basename(filePath, \".jsonl\"),\n sessionName: state.sessionName,\n projectPath,\n projectName: getShortProjectName(projectPath),\n account,\n timestamp: state.latestTimestamp || new Date().toISOString(),\n messageCount: state.messageCount,\n lastMessageSender: state.lastMessageSender,\n preview: state.previewParts.join(\" \").slice(0, tier.previewMax),\n contentSnippet: state.snippetParts.join(\" \"),\n gitBranch: null,\n model: state.model,\n isSubagent,\n parentSessionId,\n isTeammate: state.isTeammate,\n teamName: state.teamName || null,\n toolNames: state.toolNames,\n firstMessage: state.firstMessage,\n lastMessage: state.lastMessage,\n lastPrompt: state.lastPrompt || undefined,\n };\n}\n\nfunction getShortProjectName(fullPath: string): string {\n const parts = fullPath.split(\"/\").filter(Boolean);\n return parts.slice(-3).join(\"/\");\n}\n","import { createReadStream } from \"fs\";\nimport { basename } from \"path\";\nimport { createInterface } from \"readline\";\nimport { getLogger } from \"./logger\";\nimport { applyTeamInfo, initialConvState, reduceConvLine } from \"./persistent/conversation-reducer\";\nimport { finalizeMeta, initialReducerState, reduceLine } from \"./persistent/metadata-reducer\";\nimport { cleanSystemTags } from \"./tags\";\nimport type {\n ContentTier,\n Conversation,\n ConversationMessage,\n ConversationMeta,\n TeamInfo,\n ToolResultBlock,\n ToolUseBlock,\n TurnDuration,\n} from \"./types\";\n\nexport async function parseMeta(\n filePath: string,\n account: string,\n tier: ContentTier,\n): Promise<ConversationMeta | null> {\n const log = getLogger();\n log.trace({ filePath, account, tier: tier.name }, \"parseMeta: start\");\n\n // Stream the whole file through the same per-line fold the incremental\n // indexer uses, so a full parse and an append-then-resume produce identical\n // metadata by construction.\n const state = initialReducerState();\n const fileStream = createReadStream(filePath);\n const rl = createInterface({ input: fileStream, crlfDelay: Infinity });\n\n try {\n for await (const line of rl) {\n if (!line.trim()) continue;\n let entry: Record<string, unknown>;\n try {\n entry = JSON.parse(line);\n } catch {\n state.badJsonLines++;\n continue;\n }\n reduceLine(state, entry, tier);\n }\n } catch (err) {\n log.warn({ filePath, err }, \"parseMeta: read failed\");\n return null;\n }\n\n if (state.badJsonLines > 0) {\n log.warn(\n { filePath, badJsonLines: state.badJsonLines },\n \"parseMeta: skipped malformed JSON lines\",\n );\n }\n\n const meta = finalizeMeta(state, filePath, account, tier);\n if (!meta) log.trace({ filePath }, \"parseMeta: no messages\");\n return meta;\n}\n\nexport async function parseConversation(\n filePath: string,\n account: string,\n): Promise<Conversation | null> {\n const log = getLogger();\n log.trace({ filePath, account }, \"parseConversation: start\");\n const messages: ConversationMessage[] = [];\n let badJsonLines = 0;\n const textParts: string[] = [];\n const turnDurations: TurnDuration[] = [];\n\n // The message line→message reduction lives in reduceConvLine (shared with the\n // bounded page reader). turn_duration collection is parseConversation-only\n // (pages don't need it), so it stays inline here.\n const state = initialConvState();\n\n const fileStream = createReadStream(filePath);\n const rl = createInterface({ input: fileStream, crlfDelay: Infinity });\n\n try {\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n let entry: Record<string, unknown>;\n try {\n entry = JSON.parse(line);\n } catch {\n badJsonLines++;\n continue;\n }\n\n if (\n entry.type === \"system\" &&\n entry.subtype === \"turn_duration\" &&\n typeof entry.durationMs === \"number\"\n ) {\n turnDurations.push({\n durationMs: entry.durationMs as number,\n messageCount: (entry.messageCount as number) || 0,\n uuid: entry.uuid as string | undefined,\n });\n continue;\n }\n\n const message = reduceConvLine(state, entry);\n if (message) {\n messages.push(message);\n if (message.text) textParts.push(message.text);\n }\n }\n } catch (err) {\n log.warn({ filePath, err }, \"parseConversation: read failed\");\n return null;\n }\n\n if (badJsonLines > 0) {\n log.warn({ filePath, badJsonLines }, \"parseConversation: skipped malformed JSON lines\");\n }\n\n if (messages.length === 0) {\n log.trace({ filePath }, \"parseConversation: no messages\");\n return null;\n }\n\n log.debug({ filePath, messageCount: messages.length }, \"parseConversation: complete\");\n applyTeamInfo(messages, state);\n\n return {\n id: filePath,\n filePath,\n projectPath: state.cwd,\n projectName: getShortProjectName(state.cwd),\n sessionId: state.sessionId || basename(filePath, \".jsonl\"),\n sessionName: state.sessionName,\n messages,\n fullText: textParts.join(\" \"),\n timestamp: state.latestTimestamp || new Date().toISOString(),\n messageCount: messages.length,\n account,\n turnDurations: turnDurations.length > 0 ? turnDurations : undefined,\n lastPrompt: state.lastPrompt || undefined,\n };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nexport function extractTextContent(content: unknown): string {\n if (!content) return \"\";\n if (typeof content === \"string\") return cleanSystemTags(content);\n if (Array.isArray(content)) {\n return content\n .map((item) => {\n if (typeof item === \"string\") return item;\n if (item?.type === \"text\" && item?.text) return item.text;\n if (item?.type === \"tool_result\" && typeof item?.content === \"string\") return item.content;\n return \"\";\n })\n .filter(Boolean)\n .map(cleanSystemTags)\n .join(\" \");\n }\n return \"\";\n}\n\nexport function extractToolUseNames(content: unknown): string[] {\n if (!Array.isArray(content)) return [];\n return content\n .filter((item) => item?.type === \"tool_use\" && item?.name)\n .map((item) => item.name as string);\n}\n\nexport function extractToolUseBlocks(content: unknown): ToolUseBlock[] {\n if (!Array.isArray(content)) return [];\n return content\n .filter((item) => item?.type === \"tool_use\" && item?.name && item?.id)\n .map((item) => ({\n id: item.id as string,\n name: item.name as string,\n input: (item.input as Record<string, unknown>) || {},\n }));\n}\n\nconst TOOL_NAME_TO_TYPE: Record<string, ToolResultBlock[\"type\"]> = {\n Edit: \"edit\",\n Write: \"write\",\n Read: \"read\",\n Bash: \"bash\",\n Grep: \"grep\",\n Glob: \"glob\",\n Agent: \"taskAgent\",\n TaskCreate: \"taskCreate\",\n TaskUpdate: \"taskUpdate\",\n};\n\nexport function extractToolResultBlocks(\n content: unknown,\n pendingToolUses: Map<string, ToolUseBlock>,\n): ToolResultBlock[] {\n if (!Array.isArray(content)) return [];\n return content\n .filter((item) => item?.type === \"tool_result\" && item?.tool_use_id)\n .map((item) => {\n const toolName = pendingToolUses.get(item.tool_use_id as string)?.name ?? \"\";\n return {\n toolUseId: item.tool_use_id as string,\n type: TOOL_NAME_TO_TYPE[toolName] ?? \"generic\",\n content:\n typeof item.content === \"string\"\n ? { text: item.content as string }\n : ((item.content as Record<string, unknown>) ?? {}),\n isError: typeof item.is_error === \"boolean\" ? item.is_error : undefined,\n };\n });\n}\n\nexport function collectToolNames(content: unknown, toolSet: Set<string>): void {\n if (!Array.isArray(content)) return;\n for (const item of content) {\n if (item?.type === \"tool_use\" && item?.name) {\n toolSet.add(item.name as string);\n }\n }\n}\n\nexport function isOnlyToolResultContent(content: unknown): boolean {\n if (!Array.isArray(content)) return false;\n return content.length > 0 && content.every((item) => item?.type === \"tool_result\");\n}\n\nexport function isTeammateContent(content: unknown): boolean {\n const raw =\n typeof content === \"string\"\n ? content\n : Array.isArray(content)\n ? content\n .map((item) =>\n typeof item === \"string\" ? item : item?.type === \"text\" ? (item.text ?? \"\") : \"\",\n )\n .join(\"\")\n : \"\";\n return raw.includes(\"<teammate-message\");\n}\n\nexport function extractThinking(content: unknown): { content: string; signature: string } {\n if (!Array.isArray(content)) return { content: \"\", signature: \"\" };\n const blocks = content.filter((item) => item?.type === \"thinking\");\n return {\n content: blocks\n .map((b) => b.thinking as string)\n .filter(Boolean)\n .join(\"\\n\\n\"),\n signature: blocks\n .map((b) => b.signature as string)\n .filter(Boolean)\n .join(\"\"),\n };\n}\n\nexport function hasImageBlocks(content: unknown): boolean {\n if (!Array.isArray(content)) return false;\n return content.some(\n (item) =>\n item?.type === \"image\" &&\n (item?.source?.type === \"base64\" || item?.file?.base64 !== undefined),\n );\n}\n\nexport function parseTeammateMessageTag(content: string): TeamInfo | null {\n const match = content.match(/<teammate-message\\s+([^>]*)>/);\n if (!match) return null;\n const attrs = match[1];\n const id = attrs.match(/teammate_id=\"([^\"]*)\"/)?.[1];\n if (!id) return null;\n const summary = attrs.match(/summary=\"([^\"]*)\"/)?.[1];\n const color = attrs.match(/color=\"([^\"]*)\"/)?.[1];\n return { teammateId: id, summary, color };\n}\n\nfunction getShortProjectName(fullPath: string): string {\n const parts = fullPath.split(\"/\").filter(Boolean);\n return parts.slice(-3).join(\"/\");\n}\n","import {\n extractTextContent,\n extractThinking,\n extractToolResultBlocks,\n extractToolUseBlocks,\n extractToolUseNames,\n hasImageBlocks,\n isOnlyToolResultContent,\n parseTeammateMessageTag,\n} from \"../parser\";\nimport type {\n AttachmentSidecar,\n ConversationMessage,\n MessageMetadata,\n MessageSender,\n TeamInfo,\n ToolUseBlock,\n} from \"../types\";\n\n// Resumable, serializable state for the conversation line→message reduction.\n// Mirrors parseConversation()'s loop so a full reduce equals a streamed parse,\n// and so a bounded page read can resume an *equivalent* parse from a checkpoint\n// by restoring the cross-line state (pending tool_use blocks + team info).\nexport interface ConvReducerState {\n cwd: string;\n sessionId: string;\n sessionName: string;\n latestTimestamp: string;\n lastPrompt: string;\n // id -> tool_use block, so a later tool_result line resolves its tool type.\n pendingToolUses: Record<string, ToolUseBlock>;\n // teamName -> info, collected as lines are read and applied to messages.\n teamInfo: Record<string, TeamInfo>;\n}\n\nexport function initialConvState(): ConvReducerState {\n return {\n cwd: \"\",\n sessionId: \"\",\n sessionName: \"\",\n latestTimestamp: \"\",\n lastPrompt: \"\",\n pendingToolUses: {},\n teamInfo: {},\n };\n}\n\n// Fold one JSONL entry. Returns the produced ConversationMessage (its message\n// index is the count of messages emitted so far), or null if the line produced\n// no message. Mutates `state` (header fields + cross-line maps).\nexport function reduceConvLine(\n state: ConvReducerState,\n entry: Record<string, unknown>,\n): ConversationMessage | null {\n if (entry.cwd && !state.cwd) state.cwd = entry.cwd as string;\n if (entry.sessionId && !state.sessionId) state.sessionId = entry.sessionId as string;\n if (entry.slug && !state.sessionName) state.sessionName = entry.slug as string;\n if (entry.timestamp) {\n const ts = entry.timestamp as string;\n if (!state.latestTimestamp || ts > state.latestTimestamp) state.latestTimestamp = ts;\n }\n\n const type = entry.type as string;\n\n if (type === \"last-prompt\") {\n if (entry.lastPrompt && !state.lastPrompt) state.lastPrompt = entry.lastPrompt as string;\n return null;\n }\n // system (incl. turn_duration), file-history-snapshot, etc. produce no message.\n if (type !== \"user\" && type !== \"assistant\") return null;\n if (entry.isMeta) return null;\n\n const msg = entry.message as Record<string, unknown> | undefined;\n\n const toolUseBlocks = extractToolUseBlocks(msg?.content);\n for (const block of toolUseBlocks) state.pendingToolUses[block.id] = block;\n\n const hasToolUseResult = type === \"user\" && entry.toolUseResult != null;\n const isToolResultOnly = hasToolUseResult && isOnlyToolResultContent(msg?.content);\n const content = extractTextContent(msg?.content);\n const thinking = type === \"assistant\" ? extractThinking(msg?.content) : null;\n const hasThinking = !!(thinking?.content || thinking?.signature);\n\n if (!(content || isToolResultOnly || toolUseBlocks.length > 0 || hasThinking)) return null;\n\n const metadata: MessageMetadata = {};\n if (msg?.model) metadata.model = msg.model as string;\n if (msg?.stop_reason !== undefined) metadata.stopReason = msg.stop_reason as string | null;\n if (entry.gitBranch) metadata.gitBranch = entry.gitBranch as string;\n if (entry.version) metadata.version = entry.version as string;\n\n const usage = msg?.usage as Record<string, number> | undefined;\n if (usage) {\n if (usage.input_tokens) metadata.inputTokens = usage.input_tokens;\n if (usage.output_tokens) metadata.outputTokens = usage.output_tokens;\n if (usage.cache_read_input_tokens) metadata.cacheReadTokens = usage.cache_read_input_tokens;\n if (usage.cache_creation_input_tokens)\n metadata.cacheCreationTokens = usage.cache_creation_input_tokens;\n }\n\n const toolUseNames = extractToolUseNames(msg?.content);\n if (toolUseNames.length > 0) metadata.toolUses = toolUseNames;\n if (toolUseBlocks.length > 0) metadata.toolUseBlocks = toolUseBlocks;\n\n if (isToolResultOnly) {\n const pending = new Map(Object.entries(state.pendingToolUses));\n const toolResultBlocks = extractToolResultBlocks(msg?.content, pending);\n if (toolResultBlocks.length > 0) metadata.toolResults = toolResultBlocks;\n }\n\n if (entry.teamName) {\n metadata.teamName = entry.teamName as string;\n if (!state.teamInfo[metadata.teamName] && content) {\n const info = parseTeammateMessageTag(content);\n if (info) state.teamInfo[metadata.teamName] = info;\n }\n }\n\n const thinkingContent = thinking?.content || undefined;\n const thinkingSignature = thinking?.signature || undefined;\n const hasMetadata = Object.keys(metadata).length > 0;\n\n return {\n role: type as MessageSender,\n text: content || \"\",\n timestamp: (entry.timestamp as string) || \"\",\n uuid: (entry.uuid as string) || undefined,\n metadata: hasMetadata ? metadata : undefined,\n isToolResult: isToolResultOnly || undefined,\n isThinking: thinkingContent || thinkingSignature ? true : undefined,\n thinkingContent,\n thinkingSignature,\n parentUuid: entry.parentUuid !== undefined ? (entry.parentUuid as string | null) : undefined,\n requestId: type === \"assistant\" ? (entry.requestId as string | undefined) : undefined,\n promptId: type === \"user\" ? (entry.promptId as string | undefined) : undefined,\n isSidechain: typeof entry.isSidechain === \"boolean\" ? entry.isSidechain : undefined,\n permissionMode: type === \"user\" ? (entry.permissionMode as string | undefined) : undefined,\n hasImages: hasImageBlocks(msg?.content) || undefined,\n attachment:\n entry.attachment !== undefined ? (entry.attachment as AttachmentSidecar) : undefined,\n };\n}\n\n// Back-apply collected team info to a set of messages (the post-pass\n// parseConversation does). Safe to call on a window: teamInfo for a given name\n// is stable once first seen, so a window restored from a checkpoint that\n// carries earlier teamInfo gets the same result.\nexport function applyTeamInfo(messages: ConversationMessage[], state: ConvReducerState): void {\n if (Object.keys(state.teamInfo).length === 0) return;\n for (const m of messages) {\n const name = m.metadata?.teamName;\n if (name && state.teamInfo[name] && m.metadata) m.metadata.teamInfo = state.teamInfo[name];\n }\n}\n","import { discoverJsonlFiles } from \"../discovery\";\nimport {\n finalizeMeta,\n initialReducerState,\n type ReducerState,\n reduceLine,\n} from \"../persistent/metadata-reducer\";\nimport type { ContentTier, ConversationMeta } from \"../types\";\nimport {\n CLAUDE_CODE_PROVIDER,\n type DiscoveredConversationFile,\n type ScannerProvider,\n} from \"./provider\";\n\n// The existing Claude/Threadbase format, expressed as a provider. All behavior\n// is the already-shared reducer (metadata-reducer.ts) — no logic is duplicated\n// here. Discovery is delegated to discoverJsonlFiles; the scanner passes the\n// profiles' projects dirs as roots paired with their account ids.\nexport class ThreadbaseProvider implements ScannerProvider<ReducerState> {\n readonly name = CLAUDE_CODE_PROVIDER;\n\n // Roots are passed as \"<projectsDir>\\0<account>\" so the scanner can carry the\n // per-root account through the shared interface. The scanner builds these.\n async discover(roots: string[]): Promise<DiscoveredConversationFile[]> {\n const dirs = roots.map((r) => {\n const [projectsDir, account = \"default\"] = r.split(\"\\0\");\n return { projectsDir, account };\n });\n return discoverJsonlFiles(dirs);\n }\n\n // Threadbase JSONL has top-level type \"user\"/\"assistant\" with a cwd/sessionId.\n canParse(_filePath: string, sample: string): boolean {\n for (const line of sample.split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n const e = JSON.parse(line) as Record<string, unknown>;\n if (e.type === \"user\" || e.type === \"assistant\") return true;\n // Codex's distinctive top-level types — definitively not us.\n if (e.type === \"session_meta\" || e.type === \"response_item\") return false;\n } catch {}\n }\n return false;\n }\n\n createEmptyAccumulator(): ReducerState {\n return initialReducerState();\n }\n\n reduceEntry(acc: ReducerState, entry: Record<string, unknown>, tier: ContentTier): void {\n reduceLine(acc, entry, tier);\n }\n\n finalize(\n acc: ReducerState,\n filePath: string,\n account: string,\n tier: ContentTier,\n ): ConversationMeta | null {\n // finalizeMeta already tags provider: \"claude-code\".\n return finalizeMeta(acc, filePath, account, tier);\n }\n}\n","import { EventEmitter } from \"events\";\nimport { closeSync, openSync, readSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { LRUCache } from \"./cache\";\nimport { discoverJsonlFiles } from \"./discovery\";\nimport {\n applyAccountFilter,\n applyIncludeFilter,\n applyPagination,\n applyProjectFilter,\n applySinceFilter,\n applySort,\n parseSinceCutoff,\n} from \"./filters\";\nimport { readGitBranch } from \"./git\";\nimport { SearchIndexer } from \"./indexer\";\nimport { getLogger } from \"./logger\";\nimport { parseConversation, parseMeta } from \"./parser\";\nimport { classify } from \"./persistent/cursor\";\nimport { PersistentEngine } from \"./persistent/index-engine\";\nimport { getProjectsDir, loadProfiles } from \"./profiles\";\nimport { CodexCliProvider, parseCodexConversation } from \"./providers/codex-cli\";\nimport { parseMetaWithProvider } from \"./providers/parse\";\nimport {\n CLAUDE_CODE_PROVIDER,\n CODEX_CLI_PROVIDER,\n type ScannerProvider,\n} from \"./providers/provider\";\nimport { ThreadbaseProvider } from \"./providers/threadbase\";\nimport { generateMatches } from \"./search-matches\";\nimport { resolveTier } from \"./tiers\";\nimport type {\n ContentTier,\n Conversation,\n ConversationMeta,\n ConversationPage,\n GetConversationOptions,\n GetConversationPageOptions,\n GroupedConversations,\n Profile,\n ScanOptions,\n ScanResult,\n SearchOptions,\n SearchResult,\n SingleFilePage,\n TreeConversation,\n} from \"./types\";\nimport { FileWatcher } from \"./watcher/file-watcher\";\nimport { type IndexJob, IndexQueue } from \"./watcher/index-queue\";\n\nconst BATCH_SIZE = 12;\nconst DEFAULT_CONFIG_PATH = \"~/.config/threadbase-scanner\";\n\n// Default persistent-index location. Overridable via TB_SCANNER_DB (used by\n// tests for isolation, and handy for pointing at an alternate DB in ops).\nfunction defaultDbPath(): string {\n return process.env.TB_SCANNER_DB ?? join(homedir(), \".config\", \"threadbase-scanner\", \"index.db\");\n}\n\nexport interface PersistentConfig {\n dbPath?: string;\n // Write a portable <file>.idx.json sidecar next to each indexed JSONL for\n // debugging/portability/recovery. Off by default. SQLite stays canonical.\n sidecar?: boolean;\n}\n\nexport interface ConversationScannerOptions {\n metadataCacheSize?: number;\n conversationCacheSize?: number;\n // SQLite-backed persistent index. Enabled by default (at DEFAULT_DB_PATH).\n // Pass `false` for the legacy in-memory path (no native dependency, no DB\n // file). Pass `{ dbPath }` to override the database location.\n persistent?: false | PersistentConfig;\n}\n\nexport interface WatchOptions {\n profiles?: Profile[];\n // Watcher debounce window (ms). Default 400.\n debounceMs?: number;\n // Periodic full rescan as a correctness fallback for missed FS events.\n // Default 60_000ms; set 0 to disable.\n periodicMs?: number;\n}\n\n// Emitted after the index changes for one file. `meta` is the fresh metadata,\n// or null when the file was removed/emptied (deleted from the index).\nexport interface ScannerChangeEvent {\n filePath: string;\n account: string;\n meta: ConversationMeta | null;\n reason: IndexJob[\"reason\"];\n}\n\nexport class ConversationScanner {\n private metadataCache: Map<string, ConversationMeta> = new Map();\n private conversationLRU: LRUCache<string, Conversation>;\n // session_id is NOT unique, so this maps a sessionId to every active meta that\n // carries it. Resolution picks deterministically (newest timestamp, then path\n // ascending) so dropping one file never hides another with the same id.\n private sessionIdIndex: Map<string, ConversationMeta[]> = new Map();\n private projects: Set<string> = new Set();\n private indexer: SearchIndexer = new SearchIndexer();\n // Tier the most recent scan() ran with, so refreshFile() re-parses a single\n // file at the same content depth. Defaults to the standard tier.\n private lastTier: ContentTier = resolveTier(\"standard\");\n\n // null when persistent mode is disabled (legacy in-memory path). Lazily\n // opened on first use so merely constructing a scanner never touches disk.\n private readonly dbPath: string | null;\n private readonly sidecarEnabled: boolean;\n private engineInstance: PersistentEngine | null = null;\n\n private emitter = new EventEmitter();\n private watcher: FileWatcher | null = null;\n private queue: IndexQueue | null = null;\n private periodicTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(options?: ConversationScannerOptions) {\n this.conversationLRU = new LRUCache<string, Conversation>(options?.conversationCacheSize ?? 5);\n if (options?.persistent === false) {\n this.dbPath = null;\n this.sidecarEnabled = false;\n } else {\n this.dbPath = options?.persistent?.dbPath ?? defaultDbPath();\n this.sidecarEnabled = options?.persistent?.sidecar ?? false;\n }\n }\n\n private get persistent(): boolean {\n return this.dbPath !== null;\n }\n\n private engine(): PersistentEngine {\n if (!this.engineInstance) {\n this.engineInstance = new PersistentEngine(this.dbPath as string, {\n sidecar: this.sidecarEnabled,\n });\n }\n return this.engineInstance;\n }\n\n // Release the SQLite connection. No-op in legacy mode. Safe to call\n // repeatedly. Stops the watcher first if one is running; call unwatch()\n // explicitly beforehand if you need to await watcher teardown.\n close(): void {\n if (this.watcher || this.queue || this.periodicTimer) void this.unwatch();\n this.engineInstance?.close();\n this.engineInstance = null;\n }\n\n async scan(options: ScanOptions = {}): Promise<ScanResult> {\n const profiles = await this.resolveProfiles(options.profiles);\n const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);\n this.lastTier = resolveTier(options.tier ?? \"standard\", options.tiers);\n\n if (this.persistent) {\n return this.scanPersistent(activeProfiles, options);\n }\n return this.scanInMemory(activeProfiles, options);\n }\n\n // SQLite-backed scan: (re)index changed files into the DB, then query all\n // active metas and run the identical filter/sort/view/paginate pipeline as\n // the in-memory path — guaranteeing an identical ScanResult shape.\n private async scanPersistent(\n activeProfiles: Profile[],\n options: ScanOptions,\n ): Promise<ScanResult> {\n const log = getLogger();\n const startedAt = Date.now();\n const engine = this.engine();\n\n const { scanned } = await engine.indexAll(activeProfiles, options);\n const allMetas = engine.allActive();\n\n const { conversations, total } = this.finalize(allMetas, options);\n log.info(\n { scanned, kept: allMetas.length, filteredTotal: total, elapsedMs: Date.now() - startedAt },\n \"scan: complete (persistent)\",\n );\n return { conversations, total, scanned };\n }\n\n // Apply include/project/account/since filters, sort, view transform, and\n // pagination. Shared by both backends so results never diverge.\n private finalize(\n allMetas: ConversationMeta[],\n options: ScanOptions,\n ): { conversations: ScanResult[\"conversations\"]; total: number } {\n let filtered = allMetas;\n if (options.include && options.include !== \"all\") {\n filtered = applyIncludeFilter(filtered, options.include);\n }\n if (options.project) filtered = applyProjectFilter(filtered, options.project);\n if (options.account) filtered = applyAccountFilter(filtered, options.account);\n if (options.since) filtered = applySinceFilter(filtered, options.since);\n\n filtered = applySort(filtered, options.sort ?? \"recent\");\n const total = filtered.length;\n const conversations = this.transformView(filtered, options);\n\n if (Array.isArray(conversations)) {\n const limit = options.limit ?? 50;\n const offset = options.offset ?? 0;\n return { conversations: applyPagination(conversations, limit, offset).items, total };\n }\n return { conversations, total };\n }\n\n private async scanInMemory(activeProfiles: Profile[], options: ScanOptions): Promise<ScanResult> {\n const log = getLogger();\n const startedAt = Date.now();\n const tier = this.lastTier;\n\n log.info(\n {\n activeProfiles: activeProfiles.length,\n tier: tier.name,\n sort: options.sort ?? \"recent\",\n include: options.include ?? \"all\",\n view: options.view ?? \"flat\",\n },\n \"scan: start\",\n );\n\n // Clear caches\n this.metadataCache.clear();\n this.conversationLRU.clear();\n this.sessionIdIndex.clear();\n this.projects.clear();\n this.indexer.clear();\n\n const files = await this.discoverWithProviders(activeProfiles, options);\n const totalFiles = files.length;\n let scanned = 0;\n let parseFailures = 0;\n\n const allMetas: ConversationMeta[] = [];\n // Memoize git-branch lookups per project path for the duration of this\n // scan. readGitBranch walks the filesystem for .git/HEAD; without this it\n // runs once per conversation (thousands of times) even though there are\n // only a handful of distinct project roots. Scoped to one scan() call —\n // branches can change between scans, so a longer-lived memo would go stale.\n const gitBranchMemo = new Map<string, string | null>();\n const resolveGitBranch = (projectPath: string): string | null => {\n let branch = gitBranchMemo.get(projectPath);\n if (branch === undefined) {\n branch = readGitBranch(projectPath);\n gitBranchMemo.set(projectPath, branch);\n }\n return branch;\n };\n const { statCache } = options;\n for (let i = 0; i < files.length; i += BATCH_SIZE) {\n const batch = files.slice(i, i + BATCH_SIZE);\n const results = await Promise.all(\n batch.map(async ({ filePath, account, provider }) => {\n if (statCache) {\n const cached = statCache.get(filePath);\n if (cached) {\n try {\n const s = statSync(filePath);\n if (s.mtimeMs === cached.stat.mtimeMs && s.size === cached.stat.size) {\n return cached.meta;\n }\n } catch {\n // file disappeared — fall through to parse which will return null\n }\n }\n }\n try {\n const meta = await parseMetaWithProvider(provider, filePath, account, tier);\n // The provider may already know its branch (Codex reads it from\n // session_meta). Only walk the filesystem when it doesn't.\n if (meta && meta.gitBranch === null && meta.projectPath) {\n meta.gitBranch = resolveGitBranch(meta.projectPath);\n }\n return meta;\n } catch (err) {\n parseFailures++;\n log.warn({ filePath, account, provider: provider.name, err }, \"scan: parse threw\");\n return null;\n }\n }),\n );\n\n const batchMetas: ConversationMeta[] = [];\n for (const meta of results) {\n if (meta && meta.messageCount > 0) {\n this.metadataCache.set(meta.id, meta);\n this.addToSessionIndex(meta);\n this.projects.add(meta.projectPath);\n allMetas.push(meta);\n batchMetas.push(meta);\n this.indexer.addDocument(meta);\n }\n }\n\n if (batchMetas.length > 0) {\n options.onBatch?.(batchMetas);\n }\n\n scanned += batch.length;\n log.debug({ scanned, totalFiles, batchKept: batchMetas.length }, \"scan: batch complete\");\n options.onProgress?.(scanned, totalFiles);\n }\n\n const { conversations, total } = this.finalize(allMetas, options);\n\n const elapsedMs = Date.now() - startedAt;\n log.info(\n {\n totalFiles,\n scanned,\n kept: allMetas.length,\n filteredTotal: total,\n parseFailures,\n elapsedMs,\n },\n \"scan: complete\",\n );\n\n return { conversations, total, scanned };\n }\n\n async search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n const log = getLogger();\n log.debug({ query, indexSize: this.indexer.getDocumentCount() }, \"search: start\");\n\n let results: SearchResult[];\n if (this.persistent) {\n // SQLite FTS5 is the persistent search engine. Index once if the DB is\n // empty (mirroring the in-memory \"scan on first search\" contract), then\n // query FTS directly — no in-memory index to warm.\n const engine = this.engine();\n if (engine.conversations.count() === 0) {\n log.debug(\"search: persistent index empty, triggering scan\");\n const profiles = await this.resolveProfiles(options.profiles);\n const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);\n await engine.indexAll(activeProfiles, { ...options, limit: undefined, offset: undefined });\n }\n const metas = engine.searchMetas(query, (options.limit ?? 50) * 2);\n results = query.trim()\n ? metas.map((meta) => ({ meta, score: 1, matches: generateMatches(meta, query) }))\n : metas.map((meta) => ({\n meta,\n score: 1,\n matches: [{ field: \"timestamp\", snippet: meta.preview }],\n }));\n } else {\n if (this.indexer.getDocumentCount() === 0) {\n log.debug(\"search: index empty, triggering scan\");\n await this.scan({ ...options, limit: undefined, offset: undefined });\n }\n results = this.indexer.search(query, {\n fields: options.fields,\n limit: (options.limit ?? 50) * 2,\n });\n }\n\n if (options.include && options.include !== \"all\") {\n results = results.filter((r) => {\n switch (options.include) {\n case \"conversations\":\n return !r.meta.isSubagent && !r.meta.isTeammate;\n case \"subagents\":\n return r.meta.isSubagent;\n case \"teammates\":\n return r.meta.isTeammate;\n default:\n return true;\n }\n });\n }\n if (options.project) {\n const lower = options.project.toLowerCase();\n results = results.filter(\n (r) =>\n r.meta.projectPath.toLowerCase().includes(lower) ||\n r.meta.projectName.toLowerCase().includes(lower),\n );\n }\n if (options.account) {\n results = results.filter((r) => r.meta.account === options.account);\n }\n if (options.provider) {\n results = results.filter(\n (r) => (r.meta.provider ?? CLAUDE_CODE_PROVIDER) === options.provider,\n );\n }\n if (options.since) {\n const cutoff = parseSinceCutoff(options.since);\n results = results.filter((r) => new Date(r.meta.timestamp).getTime() >= cutoff.getTime());\n }\n\n const limit = options.limit ?? 50;\n const offset = options.offset ?? 0;\n const sliced = results.slice(offset, offset + limit);\n log.debug({ query, matched: results.length, returned: sliced.length }, \"search: complete\");\n return sliced;\n }\n\n async getConversation(\n id: string,\n _options?: GetConversationOptions,\n ): Promise<Conversation | null> {\n const log = getLogger();\n const cached = this.conversationLRU.get(id);\n if (cached) {\n log.debug({ id }, \"getConversation: cache hit\");\n return cached;\n }\n\n const meta = this.persistent\n ? this.engine().getByIdOrSession(id)\n : (this.metadataCache.get(id) ?? this.resolveSessionId(id));\n if (!meta) {\n log.debug({ id }, \"getConversation: not found in metadata\");\n return null;\n }\n\n log.debug({ id, filePath: meta.filePath }, \"getConversation: cache miss, parsing\");\n try {\n const conversation =\n meta.provider === CODEX_CLI_PROVIDER\n ? await parseCodexConversation(meta.filePath, meta.account)\n : await parseConversation(meta.filePath, meta.account);\n if (conversation) {\n this.conversationLRU.set(id, conversation);\n }\n return conversation;\n } catch (err) {\n log.warn({ id, filePath: meta.filePath, err }, \"getConversation: parse failed\");\n return null;\n }\n }\n\n // Return one bounded window of a conversation's messages plus the total\n // message count, so a caller can serve the last page and scroll back without\n // holding the whole conversation itself.\n //\n // The window is `[max(0, beforeIndex - limit), beforeIndex)` in chronological\n // order; `beforeIndex` defaults to `total` (the newest page). `fromIndex` is\n // the window's start index, so the caller can derive `has_more_older`\n // (fromIndex > 0). Returns null when the id can't be resolved/parsed — the\n // same contract as getConversation.\n //\n // Persistent mode: a true bounded read — the engine seeks from the nearest\n // checkpoint and parses only the requested window (checkpoints are built\n // lazily for large conversations). Windowed message indices are proven\n // identical to parseConversation().messages.slice(...) by the paged-reader\n // equivalence test.\n //\n // Legacy mode: parse-once-then-slice via getConversation (cached in the LRU),\n // which is identical by construction.\n async getConversationPage(\n id: string,\n options: GetConversationPageOptions,\n ): Promise<ConversationPage | null> {\n if (this.persistent) {\n return this.engine().getPage(id, options);\n }\n\n const conversation = await this.getConversation(id);\n if (!conversation) return null;\n\n const { messages } = conversation;\n const total = messages.length;\n const { limit } = options;\n const beforeIndex = options.beforeIndex ?? total;\n\n const fromIndex = Math.max(0, beforeIndex - limit);\n const window = messages.slice(fromIndex, beforeIndex);\n\n return { messages: window, total, fromIndex };\n }\n\n // Parse one JSONL file directly and slice a page window — without any prior\n // scan() or metadata index. This is the cold-start fast path: a single\n // conversation can be served from one file parse (~ms) instead of waiting\n // for a full filesystem scan. The window is the same\n // `[max(0, beforeIndex - limit), beforeIndex)` slice as getConversationPage,\n // and the parsed Conversation is returned alongside so the caller can build\n // the response meta (projectPath, timestamp, messageCount, …) without a\n // second parse. Returns null when the file can't be parsed. `account` only\n // feeds the conversation's account field; \"default\" is the single-profile\n // fallback, matching refreshFile.\n async parseSingleFilePage(\n filePath: string,\n account: string | undefined,\n options: GetConversationPageOptions,\n ): Promise<SingleFilePage | null> {\n const conversation = await parseConversation(filePath, account ?? \"default\");\n if (!conversation) return null;\n\n const { messages } = conversation;\n const total = messages.length;\n const { limit } = options;\n const beforeIndex = options.beforeIndex ?? total;\n\n const fromIndex = Math.max(0, beforeIndex - limit);\n const window = messages.slice(fromIndex, beforeIndex);\n\n return { messages: window, total, fromIndex, conversation };\n }\n\n // Re-parse a single JSONL file and update every in-memory index in place —\n // metadata cache, sessionId index, project set, search index — and evict the\n // file's parsed conversation from the LRU so the next getConversation()\n // re-reads it. This lets a long-lived scanner stay current with a file that\n // grew after the initial scan() without paying for a full rescan.\n //\n // `account` defaults to the account already recorded for this file (the id\n // is the file path), falling back to \"default\" for a file the scanner has\n // not seen before. Returns the fresh ConversationMeta, or null when the file\n // no longer parses (missing/empty) — in which case any prior entry for it is\n // dropped from all indexes.\n async refreshFile(filePath: string, account?: string): Promise<ConversationMeta | null> {\n const log = getLogger();\n\n if (this.persistent) {\n const engine = this.engine();\n const previous = engine.getByIdOrSession(filePath);\n const resolvedAccount = account ?? previous?.account ?? \"default\";\n const evict = (m: ConversationMeta | null) => {\n if (!m) return;\n this.conversationLRU.delete(m.id);\n this.conversationLRU.delete(m.sessionId);\n };\n evict(previous);\n // Resolve the provider so a Codex file refreshes through the Codex reducer\n // rather than the Threadbase tail-read. Prefer stored metadata; fall back\n // to a structural sniff for a file the index hasn't seen yet.\n const provider = await this.resolveProviderForFile(filePath, previous);\n const meta = await engine.indexFile(\n filePath,\n resolvedAccount,\n this.lastTier.name,\n undefined,\n readGitBranch,\n true,\n provider,\n );\n evict(meta);\n log.debug({ filePath, kept: !!meta }, \"refreshFile: updated persistent index\");\n return meta;\n }\n\n const previous = this.metadataCache.get(filePath);\n const resolvedAccount = account ?? previous?.account ?? \"default\";\n\n let meta: ConversationMeta | null = null;\n try {\n meta = await parseMeta(filePath, resolvedAccount, this.lastTier);\n } catch (err) {\n log.warn({ filePath, err }, \"refreshFile: parseMeta threw\");\n meta = null;\n }\n\n // The LRU is keyed by the id used at getConversation() time, which can be\n // either the file-path id or the sessionId — evict both for the prior and\n // refreshed metas so no stale parse survives.\n const evict = (m: ConversationMeta | undefined | null) => {\n if (!m) return;\n this.conversationLRU.delete(m.id);\n this.conversationLRU.delete(m.sessionId);\n };\n evict(previous);\n evict(meta);\n\n if (!meta || meta.messageCount === 0) {\n if (previous) {\n this.metadataCache.delete(previous.id);\n this.removeFromSessionIndex(previous);\n this.indexer.removeDocument(previous.id);\n }\n log.debug({ filePath }, \"refreshFile: dropped (no parseable messages)\");\n return null;\n }\n\n meta.gitBranch = readGitBranch(meta.projectPath);\n\n // A re-parse can change the sessionId mapping; clear the old entry first so\n // we don't leave this file listed under a stale sessionId.\n if (previous) this.removeFromSessionIndex(previous);\n this.metadataCache.set(meta.id, meta);\n this.addToSessionIndex(meta);\n this.projects.add(meta.projectPath);\n if (previous) {\n this.indexer.updateDocument(meta);\n } else {\n this.indexer.addDocument(meta);\n }\n\n log.debug(\n { filePath, messageCount: meta.messageCount },\n \"refreshFile: updated in-memory indexes\",\n );\n return meta;\n }\n\n getMetadataCache(): Map<string, ConversationMeta> {\n if (this.persistent) {\n const map = new Map<string, ConversationMeta>();\n for (const meta of this.engine().allActive()) map.set(meta.id, meta);\n return map;\n }\n return this.metadataCache;\n }\n\n // Collision-safe sessionId lookup. session_id is NOT unique (the parser falls\n // back to the file basename, and resumed/subagent sessions repeat ids), so\n // getConversation(sessionId) is a convenience that resolves to one match;\n // this returns every active conversation sharing the id, newest first.\n getConversationsBySessionId(sessionId: string): ConversationMeta[] {\n if (this.persistent) {\n return this.engine().getAllBySessionId(sessionId);\n }\n return this.sortBySessionPriority(this.sessionIdIndex.get(sessionId) ?? []);\n }\n\n getProjects(): string[] {\n const source = this.persistent ? this.engine().getProjects() : this.projects;\n const normalized = new Set<string>();\n for (const p of source) {\n normalized.add(p.replace(/\\/+$/, \"\"));\n }\n return Array.from(normalized).sort();\n }\n\n // ── File watching (persistent mode only) ────────────────────────────────\n\n // Subscribe to index changes. Events: \"change\" → ScannerChangeEvent,\n // \"error\" → Error. Returns this for chaining.\n on(event: \"change\", listener: (e: ScannerChangeEvent) => void): this;\n on(event: \"error\", listener: (err: Error) => void): this;\n on(event: string, listener: (...args: any[]) => void): this {\n this.emitter.on(event, listener);\n return this;\n }\n\n off(event: string, listener: (...args: any[]) => void): this {\n this.emitter.off(event, listener);\n return this;\n }\n\n // Start watching the active profiles' project dirs. A filesystem watcher\n // feeds debounced, path-deduplicated index jobs through a single-writer\n // queue; a periodic full rescan backstops any events the watcher misses\n // (sleep/wake, network FS, restarts). Emits \"change\" per indexed file.\n // Persistent mode only — throws in legacy mode (no durable index to update).\n async watch(options: WatchOptions = {}): Promise<void> {\n if (!this.persistent) {\n throw new Error(\"watch() requires persistent mode; construct with persistent enabled\");\n }\n if (this.watcher) return; // already watching\n\n const profiles = await this.resolveProfiles(options.profiles);\n const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);\n\n this.queue = new IndexQueue(async (job) => {\n try {\n // Create/change and delete both route through refreshFile: a removed\n // file stat-fails or parses empty, so refreshFile drops its row and\n // returns null. The index stays correct either way.\n const meta = await this.refreshFile(job.filePath, job.account);\n this.emitter.emit(\"change\", {\n filePath: job.filePath,\n account: job.account,\n meta,\n reason: job.reason,\n } satisfies ScannerChangeEvent);\n } catch (err) {\n this.emitter.emit(\"error\", err instanceof Error ? err : new Error(String(err)));\n }\n });\n\n this.watcher = new FileWatcher(\n activeProfiles,\n (e) => {\n this.queue?.enqueue({\n filePath: e.filePath,\n account: e.account,\n reason: \"watcher\",\n });\n },\n { debounceMs: options.debounceMs },\n );\n await this.watcher.start();\n\n const periodicMs = options.periodicMs ?? 60_000;\n if (periodicMs > 0) {\n this.periodicTimer = setInterval(() => {\n void this.periodicReconcile(activeProfiles);\n }, periodicMs);\n // Don't keep the process alive solely for the rescan timer.\n this.periodicTimer.unref?.();\n }\n\n getLogger().info({ profiles: activeProfiles.length, periodicMs }, \"watch: started\");\n }\n\n // Periodic correctness backstop: re-discover all files and enqueue them\n // through the same queue the watcher uses, so any add/change the watcher\n // missed still gets indexed and emits a \"change\" event. Vanished files\n // (active in the DB but no longer on disk) are enqueued too — refreshFile\n // drops them. Routing through the queue (not a direct scan) keeps event\n // emission and single-writer serialization unified.\n private async periodicReconcile(activeProfiles: Profile[]): Promise<void> {\n if (!this.queue) return;\n try {\n const engine = this.engine();\n const configDirs = activeProfiles.map((p) => ({\n projectsDir: getProjectsDir(p),\n account: p.id,\n }));\n const discovered = await discoverJsonlFiles(configDirs);\n const seen = new Set<string>();\n for (const { filePath, account } of discovered) {\n seen.add(filePath);\n // Only enqueue genuinely-changed files so an idle tick stays silent\n // (no spurious \"change\" events for thousands of unchanged files).\n const { change } = classify(filePath, engine.files.getByPath(filePath));\n if (change !== \"unchanged\") {\n this.queue.enqueue({ filePath, account, reason: \"periodic\" });\n }\n }\n // Files in the index but gone from disk → enqueue so refreshFile drops them.\n for (const path of engine.files.allActivePaths()) {\n if (!seen.has(path)) {\n this.queue.enqueue({ filePath: path, account: \"default\", reason: \"periodic\" });\n }\n }\n } catch (err) {\n this.emitter.emit(\"error\", err instanceof Error ? err : new Error(String(err)));\n }\n }\n\n // Stop watching and drain any in-flight index jobs.\n async unwatch(): Promise<void> {\n if (this.periodicTimer) {\n clearInterval(this.periodicTimer);\n this.periodicTimer = null;\n }\n if (this.watcher) {\n await this.watcher.stop();\n this.watcher = null;\n }\n if (this.queue) {\n await this.queue.onIdle();\n this.queue = null;\n }\n }\n\n // ── Provider plumbing (in-memory path) ──────────────────────────────────\n\n // Build the flat parse worklist for the enabled providers. Threadbase\n // discovers under the active profiles' project dirs; Codex discovers under\n // the explicit codexRoots (opt-in — no default home scan).\n private async discoverWithProviders(\n activeProfiles: Profile[],\n options: ScanOptions,\n ): Promise<{ filePath: string; account: string; provider: ScannerProvider }[]> {\n const enabled = options.providers ?? [CLAUDE_CODE_PROVIDER];\n const work: { filePath: string; account: string; provider: ScannerProvider }[] = [];\n\n if (enabled.includes(CLAUDE_CODE_PROVIDER)) {\n const provider = new ThreadbaseProvider();\n const roots = activeProfiles.map((p) => `${getProjectsDir(p)}\\0${p.id}`);\n for (const f of await provider.discover(roots)) {\n work.push({ ...f, provider });\n }\n }\n if (enabled.includes(CODEX_CLI_PROVIDER) && (options.codexRoots?.length ?? 0) > 0) {\n const provider = new CodexCliProvider();\n for (const f of await provider.discover(options.codexRoots as string[])) {\n work.push({ ...f, provider });\n }\n }\n return work;\n }\n\n // Resolve which provider should (re)parse a file. Stored metadata wins; for a\n // file the index has not seen, sniff the first lines with each non-Threadbase\n // provider's canParse. Returns undefined for Threadbase (the engine's default\n // tail-read path).\n private async resolveProviderForFile(\n filePath: string,\n previous: ConversationMeta | null,\n ): Promise<ScannerProvider | undefined> {\n if (previous?.provider === CODEX_CLI_PROVIDER) return new CodexCliProvider();\n if (previous?.provider) return undefined; // known Threadbase\n let sample = \"\";\n try {\n const fd = openSync(filePath, \"r\");\n try {\n const buf = Buffer.alloc(8192);\n const n = readSync(fd, buf, 0, buf.length, 0);\n sample = buf.subarray(0, n).toString(\"utf8\");\n } finally {\n closeSync(fd);\n }\n } catch {\n return undefined;\n }\n const codex = new CodexCliProvider();\n if (codex.canParse(filePath, sample)) return codex;\n return undefined;\n }\n\n private addToSessionIndex(meta: ConversationMeta): void {\n const list = this.sessionIdIndex.get(meta.sessionId);\n if (list) {\n // Replace any existing entry for the same file (re-index), else append.\n const i = list.findIndex((m) => m.id === meta.id);\n if (i >= 0) list[i] = meta;\n else list.push(meta);\n } else {\n this.sessionIdIndex.set(meta.sessionId, [meta]);\n }\n }\n\n private removeFromSessionIndex(meta: ConversationMeta): void {\n const list = this.sessionIdIndex.get(meta.sessionId);\n if (!list) return;\n const next = list.filter((m) => m.id !== meta.id);\n if (next.length > 0) this.sessionIdIndex.set(meta.sessionId, next);\n else this.sessionIdIndex.delete(meta.sessionId);\n }\n\n // Deterministic single-result sessionId resolution: newest timestamp first,\n // tie-broken by absolute path ascending.\n private resolveSessionId(sessionId: string): ConversationMeta | undefined {\n return this.sortBySessionPriority(this.sessionIdIndex.get(sessionId) ?? [])[0];\n }\n\n private sortBySessionPriority(metas: ConversationMeta[]): ConversationMeta[] {\n return [...metas].sort((a, b) => {\n if (a.timestamp !== b.timestamp) return a.timestamp < b.timestamp ? 1 : -1;\n return a.filePath < b.filePath ? -1 : a.filePath > b.filePath ? 1 : 0;\n });\n }\n\n private async resolveProfiles(profiles?: Profile[]): Promise<Profile[]> {\n // An explicit array (including an empty one) means \"use exactly these\" —\n // `[]` scans zero profiles. Only an absent argument falls back to defaults.\n // Treating `[]` as \"load defaults\" silently scanned the real ~/.claude\n // history, which looked like a hang for callers isolating from it.\n if (profiles) return profiles;\n return loadProfiles(DEFAULT_CONFIG_PATH);\n }\n\n private transformView(\n metas: ConversationMeta[],\n options: ScanOptions,\n ): ConversationMeta[] | TreeConversation[] | GroupedConversations {\n switch (options.view) {\n case \"tree\":\n return this.toTree(metas);\n case \"grouped\":\n return this.toGrouped(metas);\n default:\n return metas;\n }\n }\n\n private toTree(metas: ConversationMeta[]): TreeConversation[] {\n const parents: TreeConversation[] = [];\n const subagents: ConversationMeta[] = [];\n\n for (const meta of metas) {\n if (meta.isSubagent) {\n subagents.push(meta);\n } else {\n parents.push({ ...meta, subagents: [] });\n }\n }\n\n const parentById = new Map(parents.map((p) => [p.id, p]));\n for (const sub of subagents) {\n const parent = sub.parentSessionId ? parentById.get(sub.parentSessionId) : undefined;\n if (parent) {\n parent.subagents.push(sub);\n } else {\n parents.push({ ...sub, subagents: [] });\n }\n }\n\n return parents;\n }\n\n private toGrouped(metas: ConversationMeta[]): GroupedConversations {\n const groups: GroupedConversations = {};\n for (const meta of metas) {\n const key = meta.teamName || \"_default\";\n if (!groups[key]) groups[key] = [];\n groups[key].push(meta);\n }\n return groups;\n }\n}\n","export class LRUCache<K, V> {\n private map = new Map<K, V>();\n private readonly capacity: number;\n\n constructor(capacity: number) {\n this.capacity = capacity;\n }\n\n get(key: K): V | undefined {\n const value = this.map.get(key);\n if (value === undefined) return undefined;\n // Move to end (most recently used)\n this.map.delete(key);\n this.map.set(key, value);\n return value;\n }\n\n set(key: K, value: V): void {\n this.map.delete(key);\n this.map.set(key, value);\n if (this.map.size > this.capacity) {\n const oldest = this.map.keys().next();\n if (!oldest.done) this.map.delete(oldest.value);\n }\n }\n\n has(key: K): boolean {\n return this.map.has(key);\n }\n\n delete(key: K): boolean {\n return this.map.delete(key);\n }\n\n clear(): void {\n this.map.clear();\n }\n\n get size(): number {\n return this.map.size;\n }\n}\n","import { createHash } from \"crypto\";\nimport { closeSync, openSync, readSync, statSync } from \"fs\";\nimport type { FileRow } from \"./repositories/conversation-files.repo\";\n\nexport type FileChange = \"unchanged\" | \"appended\" | \"reindex\" | \"vanished\";\n\nexport interface FileStat {\n size: number;\n mtimeMs: number;\n}\n\n// Cheap content fingerprint: hash of the first + last 4KB plus the size. Lets\n// us detect a same-size in-place rewrite (e.g. a file replaced atomically)\n// without hashing an entire 200MB file. (Spec §9.4.)\nconst FP_BYTES = 4096;\n\nexport function fingerprint(filePath: string, size: number): string {\n const hash = createHash(\"sha1\");\n hash.update(String(size));\n const fd = openSync(filePath, \"r\");\n try {\n const head = Buffer.alloc(Math.min(FP_BYTES, size));\n if (head.length > 0) {\n readSync(fd, head, 0, head.length, 0);\n hash.update(head);\n }\n if (size > FP_BYTES) {\n const tailLen = Math.min(FP_BYTES, size);\n const tail = Buffer.alloc(tailLen);\n readSync(fd, tail, 0, tailLen, size - tailLen);\n hash.update(tail);\n }\n } finally {\n closeSync(fd);\n }\n return hash.digest(\"hex\");\n}\n\n// Classify how a file changed relative to its persisted cursor row. Drives\n// whether the indexer skips, tail-reads from the cursor, or reindexes from 0.\n// (Spec §9.)\nexport function classify(\n filePath: string,\n existing: FileRow | undefined,\n): { change: FileChange; stat?: FileStat } {\n let stat: FileStat;\n try {\n const s = statSync(filePath);\n stat = { size: s.size, mtimeMs: s.mtimeMs };\n } catch {\n return { change: \"vanished\" };\n }\n\n if (!existing || existing.status !== \"active\" || existing.last_indexed_offset === 0) {\n return { change: \"reindex\", stat };\n }\n\n // Smaller than where we left off → truncated/replaced. Reindex.\n if (stat.size < existing.last_indexed_offset) {\n return { change: \"reindex\", stat };\n }\n\n // Same size + same mtime → nothing to do.\n if (stat.size === existing.size_bytes && stat.mtimeMs === existing.mtime_ms) {\n return { change: \"unchanged\", stat };\n }\n\n // Same size but mtime moved → possible in-place rewrite. Fingerprint to be\n // sure; a changed fingerprint means the bytes we already indexed are stale.\n if (stat.size === existing.last_indexed_offset) {\n const fp = fingerprint(filePath, stat.size);\n if (existing.content_fingerprint && fp !== existing.content_fingerprint) {\n return { change: \"reindex\", stat };\n }\n return { change: \"unchanged\", stat };\n }\n\n // Grew past the cursor → append-only fast path.\n return { change: \"appended\", stat };\n}\n","import { createReadStream } from \"fs\";\nimport { createInterface } from \"readline\";\nimport { getLogger } from \"../logger\";\nimport type { ContentTier, ConversationMeta } from \"../types\";\nimport type { ScannerProvider } from \"./provider\";\n\n// Stream a file through a provider's reducer triplet. This is the same fold a\n// future offset-resumed incremental parse would run (createEmptyAccumulator →\n// reduceEntry per line → finalize), just starting from offset 0. Bad JSON lines\n// and unknown entry shapes are skipped, never fatal.\nexport async function parseMetaWithProvider(\n provider: ScannerProvider,\n filePath: string,\n account: string,\n tier: ContentTier,\n): Promise<ConversationMeta | null> {\n const log = getLogger();\n const acc = provider.createEmptyAccumulator();\n const rl = createInterface({\n input: createReadStream(filePath),\n crlfDelay: Infinity,\n });\n\n try {\n for await (const line of rl) {\n if (!line.trim()) continue;\n let entry: Record<string, unknown>;\n try {\n entry = JSON.parse(line);\n } catch {\n continue;\n }\n try {\n provider.reduceEntry(acc, entry, tier);\n } catch (err) {\n // A provider reducer should never throw, but one bad line must not kill\n // the whole file.\n log.warn({ filePath, provider: provider.name, err }, \"provider reduce threw; line skipped\");\n }\n }\n } catch (err) {\n log.warn({ filePath, provider: provider.name, err }, \"provider parse: read failed\");\n return null;\n }\n\n return provider.finalize(acc, filePath, account, tier);\n}\n","import type { ContentTier } from \"./types\";\n\nexport const DEFAULT_TIERS: Record<string, ContentTier> = {\n standard: { name: \"standard\", previewMax: 200, snippetMax: 5_000 },\n full: { name: \"full\", previewMax: 1_200, snippetMax: 50_000 },\n};\n\nexport function resolveTier(\n tierName: string,\n customTiers?: Record<string, ContentTier>,\n): ContentTier {\n const tier = customTiers?.[tierName] ?? DEFAULT_TIERS[tierName];\n if (!tier) {\n throw new Error(\n `Unknown tier \"${tierName}\". Available: ${Object.keys({ ...DEFAULT_TIERS, ...customTiers }).join(\", \")}`,\n );\n }\n return tier;\n}\n","import Database from \"better-sqlite3\";\nimport { mkdirSync } from \"fs\";\nimport { dirname } from \"path\";\nimport { getLogger } from \"../logger\";\nimport { runMigrations } from \"./migrations\";\n\nexport type DB = Database.Database;\n\n// Open (creating if needed) the SQLite index at dbPath, apply WAL pragmas, and\n// run migrations. \":memory:\" is supported for tests.\nexport function openDatabase(dbPath: string): DB {\n if (dbPath !== \":memory:\") {\n mkdirSync(dirname(dbPath), { recursive: true });\n }\n\n const db = new Database(dbPath);\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"synchronous = NORMAL\");\n db.pragma(\"temp_store = MEMORY\");\n db.pragma(\"foreign_keys = ON\");\n\n runMigrations(db);\n\n getLogger().debug({ dbPath }, \"db: opened\");\n return db;\n}\n","// Persistent index schema for @threadbase-sh/scanner.\n//\n// Embedded as a string (not a sibling .sql file) so it survives tsup bundling\n// into dist/ without runtime file I/O.\n//\n// One JSONL file == one conversation, so conversation_files.id maps 1:1 to\n// conversations.file_id.\n//\n// NOTE: session_id is intentionally NOT unique. parseMeta() falls back to the\n// file's basename when a line carries no sessionId, and resumed/subagent\n// sessions can repeat a sessionId across files. The stable unique key is the\n// file's absolute_path (and conversations.file_id).\n\n// Bumped whenever DDL below changes; drives migrations.ts via PRAGMA user_version.\nexport const SCHEMA_VERSION = 3;\n\nexport const SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS conversation_files (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n\n absolute_path TEXT NOT NULL UNIQUE,\n parent_dir TEXT NOT NULL,\n file_name TEXT NOT NULL,\n\n account TEXT NOT NULL DEFAULT 'default',\n\n size_bytes INTEGER NOT NULL DEFAULT 0,\n mtime_ms INTEGER NOT NULL DEFAULT 0,\n\n last_indexed_offset INTEGER NOT NULL DEFAULT 0,\n last_indexed_line INTEGER NOT NULL DEFAULT 0,\n\n reducer_state TEXT,\n\n content_fingerprint TEXT,\n\n status TEXT NOT NULL DEFAULT 'active',\n\n last_indexed_at TEXT,\n created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,\n updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,\n deleted_at TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_conversation_files_status ON conversation_files(status);\nCREATE INDEX IF NOT EXISTS idx_conversation_files_parent_dir ON conversation_files(parent_dir);\nCREATE INDEX IF NOT EXISTS idx_conversation_files_account ON conversation_files(account);\n\nCREATE TABLE IF NOT EXISTS conversations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n\n file_id INTEGER NOT NULL UNIQUE,\n\n source_path TEXT NOT NULL UNIQUE,\n -- Which provider produced this row. Canonical identity is (provider,\n -- source_path); session_id stays non-unique across providers too.\n provider TEXT NOT NULL DEFAULT 'claude-code',\n kind TEXT,\n external_session_id TEXT,\n session_id TEXT NOT NULL,\n session_name TEXT,\n\n project_path TEXT,\n project_name TEXT,\n account TEXT NOT NULL DEFAULT 'default',\n branch TEXT,\n\n preview TEXT,\n content_snippet TEXT,\n\n message_count INTEGER NOT NULL DEFAULT 0,\n -- Count of messages parseConversation() produces (broader than message_count;\n -- includes tool_use-only and thinking-only lines). The total for bounded paging.\n page_message_count INTEGER NOT NULL DEFAULT 0,\n last_message_sender TEXT NOT NULL DEFAULT 'user',\n timestamp TEXT,\n\n -- Monotonic write counter; the highest value is the most recently indexed\n -- row. Drives last-writer-wins resolution for shared session_ids (matching\n -- the in-memory sessionId map), at the sub-second precision updated_at lacks.\n index_seq INTEGER NOT NULL DEFAULT 0,\n\n first_sent_at TEXT,\n first_sent_text TEXT,\n\n last_sent_at TEXT,\n last_sent_text TEXT,\n\n model TEXT,\n is_subagent INTEGER NOT NULL DEFAULT 0,\n parent_session_id TEXT,\n is_teammate INTEGER NOT NULL DEFAULT 0,\n team_name TEXT,\n tool_names_json TEXT,\n last_prompt TEXT,\n\n status TEXT NOT NULL DEFAULT 'active',\n\n created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,\n updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\n FOREIGN KEY (file_id) REFERENCES conversation_files(id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_conversations_session_id ON conversations(session_id);\nCREATE INDEX IF NOT EXISTS idx_conversations_provider_session ON conversations(provider, session_id);\nCREATE INDEX IF NOT EXISTS idx_conversations_provider_recent ON conversations(provider, timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_provider_project_branch ON conversations(provider, project_path, branch);\nCREATE INDEX IF NOT EXISTS idx_conversations_recent ON conversations(timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_project_recent ON conversations(project_path, timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_project_branch_recent ON conversations(project_path, branch, timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_account_recent ON conversations(account, timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_subagent_recent ON conversations(is_subagent, timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_team_recent ON conversations(team_name, timestamp DESC);\n\n-- Full-text search index over conversation content + metadata. Kept separate\n-- from the metadata tables so list-screen queries stay small and fast.\n-- source_path is UNINDEXED (stored, not tokenized) and links back to a\n-- conversations row. One FTS row per conversation, replaced on each upsert.\nCREATE VIRTUAL TABLE IF NOT EXISTS conversation_messages_fts USING fts5(\n source_path UNINDEXED,\n content,\n project_name,\n session_id,\n session_name,\n account,\n model,\n branch,\n tool_names,\n tokenize = 'unicode61'\n);\n\n-- Seek index for large conversations: every N messages, record the byte offset\n-- where the next message's line begins plus the parser's cross-line state\n-- (pending tool_use blocks, team info) needed to resume an equivalent parse\n-- from that point. Lets getConversationPage() read a window near the end of a\n-- huge file without parsing from byte 0.\nCREATE TABLE IF NOT EXISTS message_checkpoints (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_path TEXT NOT NULL,\n message_index INTEGER NOT NULL,\n byte_offset INTEGER NOT NULL,\n line_number INTEGER NOT NULL,\n parser_state TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE INDEX IF NOT EXISTS idx_message_checkpoints_lookup\n ON message_checkpoints(source_path, message_index);\n`;\n","import type { Database } from \"better-sqlite3\";\nimport { getLogger } from \"../logger\";\nimport { SCHEMA_SQL, SCHEMA_VERSION } from \"./schema\";\n\n// Minimal migration runner gated by PRAGMA user_version. The whole schema uses\n// CREATE TABLE/INDEX IF NOT EXISTS, so a fresh DB and an up-to-date DB both\n// no-op safely. When SCHEMA_VERSION bumps in the future, add ALTER statements\n// keyed on the stored version before stamping the new version.\nexport function runMigrations(db: Database): void {\n const current = db.pragma(\"user_version\", { simple: true }) as number;\n if (current >= SCHEMA_VERSION) return;\n\n const log = getLogger();\n log.info({ from: current, to: SCHEMA_VERSION }, \"migrations: applying\");\n\n // v1 → v2: add provider columns to a pre-existing conversations table BEFORE\n // running SCHEMA_SQL, because SCHEMA_SQL now creates indexes on those columns.\n // CREATE TABLE IF NOT EXISTS can't add columns, so ALTER the existing one.\n // Non-destructive: existing rows keep their data and default to 'claude-code'.\n // Guarded on table existence so a fresh DB (table created by SCHEMA_SQL below)\n // skips this entirely.\n if (current >= 1 && current < 2 && tableExists(db, \"conversations\")) {\n for (const [col, ddl] of [\n [\n \"provider\",\n // Keep the historical DEFAULT 'threadbase' — v2→v3 below updates these rows.\n \"ALTER TABLE conversations ADD COLUMN provider TEXT NOT NULL DEFAULT 'threadbase'\",\n ],\n [\"kind\", \"ALTER TABLE conversations ADD COLUMN kind TEXT\"],\n [\"external_session_id\", \"ALTER TABLE conversations ADD COLUMN external_session_id TEXT\"],\n ] as const) {\n if (!hasColumn(db, \"conversations\", col)) db.exec(ddl);\n }\n }\n\n // v2 → v3: rename provider 'threadbase' → 'claude-code'.\n if (current >= 1 && current < 3 && tableExists(db, \"conversations\")) {\n db.exec(\"UPDATE conversations SET provider = 'claude-code' WHERE provider = 'threadbase'\");\n }\n\n // Fresh DB and re-runs both no-op safely (CREATE ... IF NOT EXISTS). Creates\n // any missing tables/indexes, including the new provider indexes.\n db.exec(SCHEMA_SQL);\n\n db.pragma(`user_version = ${SCHEMA_VERSION}`);\n}\n\nfunction tableExists(db: Database, table: string): boolean {\n return (\n db.prepare(\"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?\").get(table) !==\n undefined\n );\n}\n\nfunction hasColumn(db: Database, table: string, column: string): boolean {\n const cols = db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];\n return cols.some((c) => c.name === column);\n}\n","import { createReadStream } from \"fs\";\nimport type { ContentTier } from \"../types\";\nimport { type ReducerState, reduceLine } from \"./metadata-reducer\";\n\nexport interface TailReadResult {\n // Byte offset of the first un-consumed byte (start of the trailing partial\n // line, or EOF). Safe to persist as the next cursor.\n newOffset: number;\n newLine: number;\n parsedLines: number;\n badJsonLines: number;\n}\n\n// Stream a JSONL file from `startOffset` to EOF, folding each COMPLETE line into\n// `state`. A trailing line with no newline (a writer mid-append) is left\n// un-consumed: the offset is not advanced past it, so the next pass re-reads it\n// once it's complete. Offsets are byte offsets (Buffer.byteLength), never string\n// .length, so multibyte content stays aligned. (Spec §7.2–7.4.)\nexport async function tailReduce(\n filePath: string,\n startOffset: number,\n startLine: number,\n state: ReducerState,\n tier: ContentTier,\n): Promise<TailReadResult> {\n const stream = createReadStream(filePath, { start: startOffset, encoding: \"utf8\" });\n\n let buffer = \"\";\n let offset = startOffset;\n let line = startLine;\n let parsedLines = 0;\n\n for await (const chunk of stream) {\n buffer += chunk;\n let nl: number;\n // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic line scan\n while ((nl = buffer.indexOf(\"\\n\")) >= 0) {\n const lineWithNewline = buffer.slice(0, nl + 1);\n const text = lineWithNewline.trimEnd();\n buffer = buffer.slice(nl + 1);\n\n if (text.length > 0) {\n try {\n reduceLine(state, JSON.parse(text), tier);\n } catch {\n state.badJsonLines++;\n }\n parsedLines++;\n }\n\n offset += Buffer.byteLength(lineWithNewline, \"utf8\");\n line++;\n }\n }\n\n return { newOffset: offset, newLine: line, parsedLines, badJsonLines: state.badJsonLines };\n}\n","import { createReadStream } from \"fs\";\nimport type { ConversationMessage, ConversationPage } from \"../types\";\nimport {\n applyTeamInfo,\n type ConvReducerState,\n initialConvState,\n reduceConvLine,\n} from \"./conversation-reducer\";\nimport type { Checkpoint } from \"./repositories/checkpoints.repo\";\n\n// Snapshot the parser state every CHECKPOINT_INTERVAL messages.\nexport const CHECKPOINT_INTERVAL = 500;\n\n// Stream a JSONL file from `startOffset`, folding each complete line through the\n// conversation reducer. `onMessage` receives every produced message plus the\n// byte offset of the line that FOLLOWS it (a safe resume point) and the current\n// state. Returns the total messages produced from this offset.\nasync function streamMessages(\n filePath: string,\n startOffset: number,\n startLine: number,\n state: ConvReducerState,\n // Return true to stop streaming (window filled).\n onMessage: (message: ConversationMessage, nextByteOffset: number, nextLine: number) => boolean,\n): Promise<void> {\n const stream = createReadStream(filePath, { start: startOffset, encoding: \"utf8\" });\n let buffer = \"\";\n let offset = startOffset;\n let line = startLine;\n\n for await (const chunk of stream) {\n buffer += chunk;\n let nl: number;\n // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic line scan\n while ((nl = buffer.indexOf(\"\\n\")) >= 0) {\n const lineWithNewline = buffer.slice(0, nl + 1);\n const text = lineWithNewline.trimEnd();\n buffer = buffer.slice(nl + 1);\n offset += Buffer.byteLength(lineWithNewline, \"utf8\");\n line += 1;\n\n if (text.length === 0) continue;\n let entry: Record<string, unknown>;\n try {\n entry = JSON.parse(text);\n } catch {\n continue;\n }\n const message = reduceConvLine(state, entry);\n if (message && onMessage(message, offset, line)) {\n stream.destroy();\n return;\n }\n }\n }\n}\n\n// Build checkpoints by streaming the whole file once. Each checkpoint captures\n// the byte offset + reducer state immediately AFTER message (k*interval - 1),\n// i.e. a clean resume point for message index k*interval.\nexport async function buildCheckpoints(\n filePath: string,\n interval = CHECKPOINT_INTERVAL,\n): Promise<Checkpoint[]> {\n const checkpoints: Checkpoint[] = [];\n const state = initialConvState();\n let index = 0;\n\n await streamMessages(filePath, 0, 0, state, (_msg, nextOffset, nextLine) => {\n index += 1;\n // After consuming `index` messages, if the next message index is a multiple\n // of the interval, snapshot a resume point for it.\n if (index % interval === 0) {\n checkpoints.push({\n messageIndex: index,\n byteOffset: nextOffset,\n lineNumber: nextLine,\n state: structuredClone(state),\n });\n }\n return false; // never stop early — we want the whole file\n });\n\n return checkpoints;\n}\n\n// Read a bounded message window [fromIndex, beforeIndex) plus the total message\n// count, seeking from the nearest checkpoint <= fromIndex. `total` requires\n// knowing the message count; callers pass it (from the indexed summary) so we\n// never scan the whole file just to count.\nexport async function readPage(\n filePath: string,\n total: number,\n options: { beforeIndex?: number; limit: number },\n floor: Checkpoint | null,\n): Promise<ConversationPage> {\n const beforeIndex = options.beforeIndex ?? total;\n const fromIndex = Math.max(0, beforeIndex - options.limit);\n\n const state = floor ? structuredClone(floor.state) : initialConvState();\n const startOffset = floor ? floor.byteOffset : 0;\n const startLine = floor ? floor.lineNumber : 0;\n let index = floor ? floor.messageIndex : 0;\n\n const window: ConversationMessage[] = [];\n await streamMessages(filePath, startOffset, startLine, state, (message) => {\n const current = index;\n index += 1;\n if (current >= fromIndex && current < beforeIndex) window.push(message);\n return index >= beforeIndex; // stop once the window is filled\n });\n\n applyTeamInfo(window, state);\n return { messages: window, total, fromIndex };\n}\n","import type { Database } from \"better-sqlite3\";\nimport type { ConvReducerState } from \"../conversation-reducer\";\n\nexport interface Checkpoint {\n messageIndex: number;\n byteOffset: number;\n lineNumber: number;\n state: ConvReducerState;\n}\n\ninterface CheckpointRow {\n message_index: number;\n byte_offset: number;\n line_number: number;\n parser_state: string;\n}\n\n// Stores periodic seek points for a conversation so a page near the end of a\n// large file can be read without parsing from byte 0.\nexport class CheckpointsRepo {\n constructor(private db: Database) {}\n\n replaceAll(sourcePath: string, checkpoints: Checkpoint[]): void {\n const tx = this.db.transaction(() => {\n this.db.prepare(\"DELETE FROM message_checkpoints WHERE source_path = ?\").run(sourcePath);\n const insert = this.db.prepare(\n `INSERT INTO message_checkpoints\n (source_path, message_index, byte_offset, line_number, parser_state)\n VALUES (?, ?, ?, ?, ?)`,\n );\n for (const c of checkpoints) {\n insert.run(sourcePath, c.messageIndex, c.byteOffset, c.lineNumber, JSON.stringify(c.state));\n }\n });\n tx();\n }\n\n // The latest checkpoint at or before `messageIndex`, or null if none (read\n // from the file start). Lets a page seek to the nearest prior anchor.\n floor(sourcePath: string, messageIndex: number): Checkpoint | null {\n const row = this.db\n .prepare(\n `SELECT message_index, byte_offset, line_number, parser_state\n FROM message_checkpoints\n WHERE source_path = ? AND message_index <= ?\n ORDER BY message_index DESC LIMIT 1`,\n )\n .get(sourcePath, messageIndex) as CheckpointRow | undefined;\n return row ? toCheckpoint(row) : null;\n }\n\n count(sourcePath: string): number {\n return (\n this.db\n .prepare(\"SELECT COUNT(*) AS n FROM message_checkpoints WHERE source_path = ?\")\n .get(sourcePath) as { n: number }\n ).n;\n }\n\n remove(sourcePath: string): void {\n this.db.prepare(\"DELETE FROM message_checkpoints WHERE source_path = ?\").run(sourcePath);\n }\n}\n\nfunction toCheckpoint(row: CheckpointRow): Checkpoint {\n return {\n messageIndex: row.message_index,\n byteOffset: row.byte_offset,\n lineNumber: row.line_number,\n state: JSON.parse(row.parser_state) as ConvReducerState,\n };\n}\n","import type { Database } from \"better-sqlite3\";\nimport { basename, dirname } from \"path\";\n\n// One tracked JSONL source file + its incremental indexing cursor.\nexport interface FileRow {\n id: number;\n absolute_path: string;\n parent_dir: string;\n file_name: string;\n account: string;\n size_bytes: number;\n mtime_ms: number;\n last_indexed_offset: number;\n last_indexed_line: number;\n reducer_state: string | null;\n content_fingerprint: string | null;\n status: string;\n last_indexed_at: string | null;\n}\n\nexport class ConversationFilesRepo {\n constructor(private db: Database) {}\n\n getByPath(absolutePath: string): FileRow | undefined {\n return this.db\n .prepare(\"SELECT * FROM conversation_files WHERE absolute_path = ?\")\n .get(absolutePath) as FileRow | undefined;\n }\n\n // Insert a freshly-discovered file at offset 0; returns its row id. Existing\n // path is left untouched (returns the existing id) so a re-discovery is safe.\n ensure(absolutePath: string, account: string): number {\n const existing = this.getByPath(absolutePath);\n if (existing) return existing.id;\n\n const info = this.db\n .prepare(\n `INSERT INTO conversation_files (absolute_path, parent_dir, file_name, account)\n VALUES (?, ?, ?, ?)`,\n )\n .run(absolutePath, dirname(absolutePath), basename(absolutePath), account);\n return Number(info.lastInsertRowid);\n }\n\n // Advance the cursor + persisted reducer state after a successful index pass.\n updateCursor(\n id: number,\n fields: {\n sizeBytes: number;\n mtimeMs: number;\n offset: number;\n line: number;\n reducerState: string | null;\n fingerprint: string | null;\n status?: string;\n },\n ): void {\n this.db\n .prepare(\n `UPDATE conversation_files\n SET size_bytes = ?, mtime_ms = ?, last_indexed_offset = ?, last_indexed_line = ?,\n reducer_state = ?, content_fingerprint = ?, status = ?,\n last_indexed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP\n WHERE id = ?`,\n )\n .run(\n fields.sizeBytes,\n fields.mtimeMs,\n fields.offset,\n fields.line,\n fields.reducerState,\n fields.fingerprint,\n fields.status ?? \"active\",\n id,\n );\n }\n\n // Reset the cursor to 0 for a truncated/replaced file before a full reindex.\n resetCursor(id: number): void {\n this.db\n .prepare(\n `UPDATE conversation_files\n SET last_indexed_offset = 0, last_indexed_line = 0, reducer_state = NULL,\n status = 'needs_reindex', updated_at = CURRENT_TIMESTAMP\n WHERE id = ?`,\n )\n .run(id);\n }\n\n setStatus(id: number, status: string): void {\n const deletedAt = status === \"deleted\" ? \"CURRENT_TIMESTAMP\" : \"deleted_at\";\n this.db\n .prepare(\n `UPDATE conversation_files\n SET status = ?, deleted_at = ${deletedAt}, updated_at = CURRENT_TIMESTAMP\n WHERE id = ?`,\n )\n .run(status, id);\n }\n\n allActivePaths(): string[] {\n const rows = this.db\n .prepare(\"SELECT absolute_path FROM conversation_files WHERE status != 'deleted'\")\n .all() as { absolute_path: string }[];\n return rows.map((r) => r.absolute_path);\n }\n}\n","import type { Database } from \"better-sqlite3\";\nimport { CLAUDE_CODE_PROVIDER } from \"../../providers/provider\";\nimport type { ConversationMeta, MessageSender, ProviderName } from \"../../types\";\n\nexport interface ConversationRow {\n id: number;\n file_id: number;\n source_path: string;\n provider: string;\n kind: string | null;\n external_session_id: string | null;\n session_id: string;\n session_name: string | null;\n project_path: string | null;\n project_name: string | null;\n account: string;\n branch: string | null;\n preview: string | null;\n content_snippet: string | null;\n message_count: number;\n page_message_count: number;\n last_message_sender: string;\n timestamp: string | null;\n first_sent_at: string | null;\n first_sent_text: string | null;\n last_sent_at: string | null;\n last_sent_text: string | null;\n model: string | null;\n is_subagent: number;\n parent_session_id: string | null;\n is_teammate: number;\n team_name: string | null;\n tool_names_json: string | null;\n last_prompt: string | null;\n status: string;\n}\n\n// Row -> ConversationMeta. Must reproduce the exact in-memory shape so the\n// persistent and legacy paths return identical ScanResult/SearchResult objects.\nexport function rowToMeta(row: ConversationRow): ConversationMeta {\n return {\n id: row.source_path,\n filePath: row.source_path,\n provider: (row.provider as ProviderName) ?? CLAUDE_CODE_PROVIDER,\n kind: (row.kind as ConversationMeta[\"kind\"]) ?? undefined,\n externalSessionId: row.external_session_id ?? undefined,\n sessionId: row.session_id,\n sessionName: row.session_name ?? \"\",\n projectPath: row.project_path ?? \"\",\n projectName: row.project_name ?? \"\",\n account: row.account,\n timestamp: row.timestamp ?? \"\",\n messageCount: row.message_count,\n lastMessageSender: row.last_message_sender as MessageSender,\n preview: row.preview ?? \"\",\n contentSnippet: row.content_snippet ?? \"\",\n gitBranch: row.branch,\n model: row.model,\n isSubagent: row.is_subagent === 1,\n parentSessionId: row.parent_session_id,\n isTeammate: row.is_teammate === 1,\n teamName: row.team_name,\n toolNames: row.tool_names_json ? (JSON.parse(row.tool_names_json) as string[]) : [],\n firstMessage: row.first_sent_text\n ? { text: row.first_sent_text, timestamp: row.first_sent_at ?? \"\" }\n : null,\n lastMessage: row.last_sent_text\n ? { text: row.last_sent_text, timestamp: row.last_sent_at ?? \"\" }\n : null,\n lastPrompt: row.last_prompt ?? undefined,\n };\n}\n\nexport class ConversationsRepo {\n constructor(private db: Database) {}\n\n // Upsert by file_id (1 file = 1 conversation). Keyed on the unique file_id so\n // a re-index overwrites the prior summary in place.\n upsert(fileId: number, meta: ConversationMeta, pageMessageCount = meta.messageCount): void {\n this.db\n .prepare(\n `INSERT INTO conversations (\n file_id, source_path, provider, kind, external_session_id,\n session_id, session_name, project_path, project_name,\n account, branch, preview, content_snippet, message_count, page_message_count,\n last_message_sender,\n timestamp, index_seq, first_sent_at, first_sent_text, last_sent_at, last_sent_text,\n model, is_subagent, parent_session_id, is_teammate, team_name, tool_names_json,\n last_prompt, status, updated_at\n ) VALUES (\n @file_id, @source_path, @provider, @kind, @external_session_id,\n @session_id, @session_name, @project_path, @project_name,\n @account, @branch, @preview, @content_snippet, @message_count, @page_message_count,\n @last_message_sender,\n @timestamp,\n (SELECT COALESCE(MAX(index_seq), 0) + 1 FROM conversations),\n @first_sent_at, @first_sent_text, @last_sent_at, @last_sent_text,\n @model, @is_subagent, @parent_session_id, @is_teammate, @team_name, @tool_names_json,\n @last_prompt, 'active', CURRENT_TIMESTAMP\n )\n ON CONFLICT(file_id) DO UPDATE SET\n source_path = excluded.source_path,\n provider = excluded.provider,\n kind = excluded.kind,\n external_session_id = excluded.external_session_id,\n session_id = excluded.session_id,\n session_name = excluded.session_name,\n project_path = excluded.project_path,\n project_name = excluded.project_name,\n account = excluded.account,\n branch = excluded.branch,\n preview = excluded.preview,\n content_snippet = excluded.content_snippet,\n message_count = excluded.message_count,\n page_message_count = excluded.page_message_count,\n last_message_sender = excluded.last_message_sender,\n timestamp = excluded.timestamp,\n first_sent_at = excluded.first_sent_at,\n first_sent_text = excluded.first_sent_text,\n last_sent_at = excluded.last_sent_at,\n last_sent_text = excluded.last_sent_text,\n model = excluded.model,\n is_subagent = excluded.is_subagent,\n parent_session_id = excluded.parent_session_id,\n is_teammate = excluded.is_teammate,\n team_name = excluded.team_name,\n tool_names_json = excluded.tool_names_json,\n last_prompt = excluded.last_prompt,\n status = 'active',\n index_seq = (SELECT COALESCE(MAX(index_seq), 0) + 1 FROM conversations),\n updated_at = CURRENT_TIMESTAMP`,\n )\n .run({\n file_id: fileId,\n source_path: meta.id,\n provider: meta.provider ?? CLAUDE_CODE_PROVIDER,\n kind: meta.kind ?? null,\n external_session_id: meta.externalSessionId ?? null,\n session_id: meta.sessionId,\n session_name: meta.sessionName || null,\n project_path: meta.projectPath || null,\n project_name: meta.projectName || null,\n account: meta.account,\n branch: meta.gitBranch,\n preview: meta.preview || null,\n content_snippet: meta.contentSnippet || null,\n message_count: meta.messageCount,\n page_message_count: pageMessageCount,\n last_message_sender: meta.lastMessageSender,\n timestamp: meta.timestamp || null,\n first_sent_at: meta.firstMessage?.timestamp ?? null,\n first_sent_text: meta.firstMessage?.text ?? null,\n last_sent_at: meta.lastMessage?.timestamp ?? null,\n last_sent_text: meta.lastMessage?.text ?? null,\n model: meta.model,\n is_subagent: meta.isSubagent ? 1 : 0,\n parent_session_id: meta.parentSessionId,\n is_teammate: meta.isTeammate ? 1 : 0,\n team_name: meta.teamName,\n tool_names_json: JSON.stringify(meta.toolNames),\n last_prompt: meta.lastPrompt ?? null,\n });\n }\n\n getBySourcePath(sourcePath: string): ConversationMeta | null {\n const row = this.db\n .prepare(\"SELECT * FROM conversations WHERE source_path = ? AND status = 'active'\")\n .get(sourcePath) as ConversationRow | undefined;\n return row ? rowToMeta(row) : null;\n }\n\n // Dual lookup matching scanner.getConversation: resolve by source_path (the\n // canonical id) first, then by session_id.\n //\n // session_id is NOT unique (see schema header) — the sessionId form is a\n // compatibility convenience. Resolution is deterministic and matches the\n // in-memory scanner: among active rows sharing the session_id, newest\n // timestamp wins, index_seq breaks sub-second ties (precision updated_at\n // lacks), then source_path ascending. Collision-safe callers should use\n // getAllBySessionId() instead.\n getByIdOrSession(id: string): ConversationMeta | null {\n const direct = this.getBySourcePath(id);\n if (direct) return direct;\n const row = this.db\n .prepare(\n `SELECT * FROM conversations WHERE session_id = ? AND status = 'active'\n ORDER BY COALESCE(timestamp, '') DESC, index_seq DESC, source_path ASC LIMIT 1`,\n )\n .get(id) as ConversationRow | undefined;\n return row ? rowToMeta(row) : null;\n }\n\n // All active conversations sharing a session_id, newest first (same ordering\n // as getByIdOrSession). Collision-safe counterpart to the convenience\n // getByIdOrSession() sessionId lookup.\n getAllBySessionId(sessionId: string): ConversationMeta[] {\n const rows = this.db\n .prepare(\n `SELECT * FROM conversations WHERE session_id = ? AND status = 'active'\n ORDER BY COALESCE(timestamp, '') DESC, index_seq DESC, source_path ASC`,\n )\n .all(sessionId) as ConversationRow[];\n return rows.map(rowToMeta);\n }\n\n // All active metas (unsorted/unfiltered) — callers apply the existing\n // filters/view transforms. Used by scan() before SQL filtering is wired in.\n allActive(): ConversationMeta[] {\n const rows = this.db\n .prepare(\"SELECT * FROM conversations WHERE status = 'active'\")\n .all() as ConversationRow[];\n return rows.map(rowToMeta);\n }\n\n // Most recent active conversations, newest first. Backs the empty-query\n // search path (mirrors the in-memory indexer's getRecent).\n recent(limit: number): ConversationMeta[] {\n const rows = this.db\n .prepare(\n `SELECT * FROM conversations WHERE status = 'active'\n ORDER BY COALESCE(timestamp, '') DESC, source_path ASC LIMIT ?`,\n )\n .all(limit) as ConversationRow[];\n return rows.map(rowToMeta);\n }\n\n distinctProjects(): string[] {\n const rows = this.db\n .prepare(\n `SELECT DISTINCT project_path FROM conversations\n WHERE status = 'active' AND project_path IS NOT NULL AND project_path != ''\n ORDER BY project_path ASC`,\n )\n .all() as { project_path: string }[];\n return rows.map((r) => r.project_path);\n }\n\n deleteByFileId(fileId: number): void {\n this.db\n .prepare(\n \"UPDATE conversations SET status = 'deleted', updated_at = CURRENT_TIMESTAMP WHERE file_id = ?\",\n )\n .run(fileId);\n }\n\n // The parseConversation message total for a file (for bounded paging), or 0\n // if not indexed.\n pageMessageCount(sourcePath: string): number {\n const row = this.db\n .prepare(\n \"SELECT page_message_count AS n FROM conversations WHERE source_path = ? AND status = 'active'\",\n )\n .get(sourcePath) as { n: number } | undefined;\n return row?.n ?? 0;\n }\n\n count(): number {\n return (\n this.db.prepare(\"SELECT COUNT(*) AS n FROM conversations WHERE status = 'active'\").get() as {\n n: number;\n }\n ).n;\n }\n}\n","import type { Database } from \"better-sqlite3\";\nimport type { ConversationMeta } from \"../../types\";\n\n// FTS5-backed search index over conversation content + metadata. One row per\n// conversation, keyed by source_path (== ConversationMeta.id). Replaced on each\n// upsert so a re-index never leaves stale text behind.\nexport class FtsRepo {\n constructor(private db: Database) {}\n\n upsert(meta: ConversationMeta): void {\n const tx = this.db.transaction(() => {\n this.db.prepare(\"DELETE FROM conversation_messages_fts WHERE source_path = ?\").run(meta.id);\n this.db\n .prepare(\n `INSERT INTO conversation_messages_fts\n (source_path, content, project_name, session_id, session_name, account, model, branch, tool_names)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n meta.id,\n meta.contentSnippet ?? \"\",\n meta.projectName ?? \"\",\n meta.sessionId ?? \"\",\n meta.sessionName ?? \"\",\n meta.account ?? \"\",\n meta.model ?? \"\",\n meta.gitBranch ?? \"\",\n meta.toolNames.join(\" \"),\n );\n });\n tx();\n }\n\n remove(sourcePath: string): void {\n this.db.prepare(\"DELETE FROM conversation_messages_fts WHERE source_path = ?\").run(sourcePath);\n }\n\n // Ranked source_paths matching the query, best first. Returns [] on an empty\n // query (callers fall back to a recency listing).\n search(query: string, limit: number): string[] {\n const match = toMatchQuery(query);\n if (!match) return [];\n const rows = this.db\n .prepare(\n `SELECT source_path FROM conversation_messages_fts\n WHERE conversation_messages_fts MATCH ?\n ORDER BY rank\n LIMIT ?`,\n )\n .all(match, limit) as { source_path: string }[];\n return rows.map((r) => r.source_path);\n }\n\n count(): number {\n return (\n this.db.prepare(\"SELECT COUNT(*) AS n FROM conversation_messages_fts\").get() as { n: number }\n ).n;\n }\n}\n\n// Turn free-text into a safe FTS5 prefix query. Each whitespace-separated term\n// is wrapped in double quotes (so FTS5 special chars are treated literally) and\n// suffixed with * for prefix matching, mirroring FlexSearch's forward tokenizer.\n// Terms are ANDed together. Returns \"\" when nothing usable remains.\nfunction toMatchQuery(query: string): string {\n const terms = query\n .trim()\n .split(/\\s+/)\n .map((t) => t.replace(/\"/g, \"\").trim())\n .filter(Boolean);\n if (terms.length === 0) return \"\";\n return terms.map((t) => `\"${t}\"*`).join(\" AND \");\n}\n","import { discoverJsonlFiles } from \"../discovery\";\nimport { readGitBranch } from \"../git\";\nimport { getLogger } from \"../logger\";\nimport { getProjectsDir } from \"../profiles\";\nimport { CodexCliProvider } from \"../providers/codex-cli\";\nimport { parseMetaWithProvider } from \"../providers/parse\";\nimport {\n CLAUDE_CODE_PROVIDER,\n CODEX_CLI_PROVIDER,\n type ScannerProvider,\n} from \"../providers/provider\";\nimport { resolveTier } from \"../tiers\";\nimport type {\n ConversationMeta,\n ConversationPage,\n GetConversationPageOptions,\n Profile,\n ScanOptions,\n} from \"../types\";\nimport { classify, fingerprint } from \"./cursor\";\nimport { type DB, openDatabase } from \"./db\";\nimport { type TailReadResult, tailReduce } from \"./jsonl-tail-reader\";\nimport { finalizeMeta, initialReducerState, type ReducerState } from \"./metadata-reducer\";\nimport { buildCheckpoints, CHECKPOINT_INTERVAL, readPage } from \"./paged-reader\";\nimport { CheckpointsRepo } from \"./repositories/checkpoints.repo\";\nimport { ConversationFilesRepo } from \"./repositories/conversation-files.repo\";\nimport { ConversationsRepo } from \"./repositories/conversations.repo\";\nimport { FtsRepo } from \"./repositories/fts.repo\";\nimport { buildSidecar, writeSidecar } from \"./sidecar\";\n\nconst BATCH_SIZE = 12;\n\n// Persistent indexing engine. Owns the SQLite connection and the discover ->\n// classify -> tail-read -> upsert pipeline. Query helpers return ConversationMeta\n// straight from the DB so the scanner facade can run the existing\n// filter/sort/view pipeline over them, guaranteeing parity with the in-memory\n// path.\n//\n// Indexing is incremental: an appended file resumes the persisted reducer fold\n// from its byte cursor and reads only the new bytes (cursor.ts + the tail\n// reader); a truncated/replaced file reindexes from offset 0.\nexport class PersistentEngine {\n readonly db: DB;\n readonly files: ConversationFilesRepo;\n readonly conversations: ConversationsRepo;\n readonly fts: FtsRepo;\n readonly checkpoints: CheckpointsRepo;\n // When true, write a portable <file>.idx.json sidecar next to each indexed\n // JSONL. Off by default.\n private readonly sidecar: boolean;\n\n constructor(dbPath: string, options: { sidecar?: boolean } = {}) {\n this.db = openDatabase(dbPath);\n this.files = new ConversationFilesRepo(this.db);\n this.conversations = new ConversationsRepo(this.db);\n this.fts = new FtsRepo(this.db);\n this.checkpoints = new CheckpointsRepo(this.db);\n this.sidecar = options.sidecar ?? false;\n }\n\n close(): void {\n this.db.close();\n }\n\n // Discover all JSONL files under the active profiles, (re)parse files that\n // changed since the last index, and upsert their metadata. Returns the number\n // of files seen on disk this pass (the scan \"scanned\" count).\n async indexAll(activeProfiles: Profile[], options: ScanOptions): Promise<{ scanned: number }> {\n const log = getLogger();\n const tier = resolveTier(options.tier ?? \"standard\", options.tiers);\n\n const enabled = options.providers ?? [CLAUDE_CODE_PROVIDER];\n\n // Each discovered file carries the provider that should parse it. claude-code\n // files fold through the byte-offset-resumable tail reader; Codex files\n // (opt-in, only under explicit codexRoots) reparse from offset 0.\n const discovered: { filePath: string; account: string; provider?: ScannerProvider }[] = [];\n\n if (enabled.includes(CLAUDE_CODE_PROVIDER)) {\n const configDirs = activeProfiles.map((p) => ({\n projectsDir: getProjectsDir(p),\n account: p.id,\n }));\n for (const f of await discoverJsonlFiles(configDirs)) discovered.push(f);\n }\n\n const codex = new CodexCliProvider();\n if (enabled.includes(CODEX_CLI_PROVIDER) && (options.codexRoots?.length ?? 0) > 0) {\n for (const f of await codex.discover(options.codexRoots as string[])) {\n discovered.push({ ...f, provider: codex });\n }\n }\n let scanned = 0;\n\n const gitBranchMemo = new Map<string, string | null>();\n const resolveGitBranch = (projectPath: string): string | null => {\n if (gitBranchMemo.has(projectPath)) {\n return gitBranchMemo.get(projectPath) ?? null;\n }\n const branch = readGitBranch(projectPath);\n gitBranchMemo.set(projectPath, branch);\n return branch;\n };\n\n for (let i = 0; i < discovered.length; i += BATCH_SIZE) {\n const batch = discovered.slice(i, i + BATCH_SIZE);\n const results = await Promise.all(\n batch.map(async ({ filePath, account, provider }) => {\n const meta = await this.indexFile(\n filePath,\n account,\n tier.name,\n options.tiers,\n resolveGitBranch,\n false,\n provider,\n );\n return meta;\n }),\n );\n const kept = results.filter((m): m is ConversationMeta => m != null);\n if (kept.length > 0) options.onBatch?.(kept);\n scanned += batch.length;\n options.onProgress?.(scanned, discovered.length);\n }\n\n // Reconcile deletions: any previously-active file that wasn't discovered\n // this pass is gone from disk — mark it deleted. This is the correctness\n // backstop for unlink events the watcher may have missed (spec §9.5).\n const seen = new Set(discovered.map((d) => d.filePath));\n for (const path of this.files.allActivePaths()) {\n if (!seen.has(path)) this.markDeleted(path);\n }\n\n log.info({ scanned, indexed: this.conversations.count() }, \"persistent: indexAll complete\");\n return { scanned };\n }\n\n // (Re)index a single file. Classifies the change vs. the persisted cursor:\n // unchanged → return the stored summary; appended → resume the fold and read\n // only new bytes; reindex/force → fold from offset 0. Writes the summary +\n // cursor + reducer state in one transaction so a crash never leaves a\n // half-written row or an over-advanced cursor.\n async indexFile(\n filePath: string,\n account: string,\n tierName: string,\n customTiers: ScanOptions[\"tiers\"],\n resolveGitBranch: (projectPath: string) => string | null,\n force = false,\n provider?: ScannerProvider,\n ): Promise<ConversationMeta | null> {\n const log = getLogger();\n const tier = resolveTier(tierName, customTiers);\n\n const existing = this.files.getByPath(filePath);\n const { change, stat } = classify(filePath, existing);\n\n if (change === \"vanished\" || !stat) {\n // File gone between discovery and indexing — treat as deleted.\n this.markDeleted(filePath);\n return null;\n }\n // Unchanged: serve the persisted summary without re-reading (unless a\n // caller forces a re-parse, e.g. refreshFile after a same-size edit).\n if (change === \"unchanged\" && !force) {\n return this.conversations.getBySourcePath(filePath);\n }\n\n // ponytail: Codex reparses from offset 0 on every change rather than\n // resuming a byte cursor like claude-code. Codex rollout sessions are small,\n // so a full reparse is cheap; the resumable-fold path stays claude-code-only.\n // Upgrade path if Codex files ever get large: give CodexAccumulator the same\n // serialized-reducer-state treatment and route it through tailReduce.\n if (provider && provider.name !== CLAUDE_CODE_PROVIDER) {\n return this.indexFileWithProvider(provider, filePath, account, tier, stat, resolveGitBranch);\n }\n\n // appended → resume the persisted fold from the cursor; reindex/force →\n // start fresh from offset 0. This is the incremental win: an append only\n // reads the newly-written bytes.\n const resume = change === \"appended\" && !force && existing?.reducer_state;\n const state: ReducerState = resume\n ? (JSON.parse(existing.reducer_state as string) as ReducerState)\n : initialReducerState();\n const startOffset = resume ? existing.last_indexed_offset : 0;\n const startLine = resume ? existing.last_indexed_line : 0;\n\n let result: TailReadResult;\n try {\n result = await tailReduce(filePath, startOffset, startLine, state, tier);\n } catch (err) {\n log.warn({ filePath, err }, \"persistent: tail read failed\");\n return null;\n }\n\n const meta = finalizeMeta(state, filePath, account, tier);\n if (!meta) {\n this.markDeleted(filePath);\n return null;\n }\n meta.gitBranch = resolveGitBranch(meta.projectPath);\n\n const fp = stat.size > 0 ? fingerprint(filePath, stat.size) : null;\n const fileId = this.files.ensure(filePath, account);\n const upsert = this.db.transaction(() => {\n this.conversations.upsert(fileId, meta, state.pageMessageCount);\n this.fts.upsert(meta);\n // Byte offsets shift when the file changes, so any checkpoints are stale.\n // Drop them; they're rebuilt lazily on the next page request.\n this.checkpoints.remove(filePath);\n this.files.updateCursor(fileId, {\n sizeBytes: stat.size,\n mtimeMs: stat.mtimeMs,\n // Advance only to the last fully-parsed line; a trailing partial line\n // is left for the next pass.\n offset: result.newOffset,\n line: result.newLine,\n reducerState: JSON.stringify(state),\n fingerprint: fp,\n status: \"active\",\n });\n });\n upsert();\n\n if (this.sidecar) {\n writeSidecar(\n filePath,\n buildSidecar(\n meta,\n {\n sizeBytes: stat.size,\n mtimeMs: stat.mtimeMs,\n offset: result.newOffset,\n line: result.newLine,\n },\n new Date().toISOString(),\n ),\n );\n }\n\n log.debug(\n { filePath, change, bytesRead: result.newOffset - startOffset, msgs: meta.messageCount },\n \"persistent: indexed file\",\n );\n return meta;\n }\n\n // Index a non-Threadbase provider file: full reparse from offset 0 through the\n // provider's reducer/finalize, then the same upsert + FTS write + cursor bump\n // the Threadbase path uses. The cursor records size/mtime (and offset = size)\n // so the next pass classifies an unchanged file as \"unchanged\" and skips it;\n // any change reparses from 0 again. No reducer_state is persisted.\n private async indexFileWithProvider(\n provider: ScannerProvider,\n filePath: string,\n account: string,\n tier: ReturnType<typeof resolveTier>,\n stat: { size: number; mtimeMs: number },\n resolveGitBranch: (projectPath: string) => string | null,\n ): Promise<ConversationMeta | null> {\n const log = getLogger();\n\n const meta = await parseMetaWithProvider(provider, filePath, account, tier);\n if (!meta) {\n this.markDeleted(filePath);\n return null;\n }\n // The provider may already know its branch (Codex reads it from\n // session_meta). Only walk the filesystem when it doesn't.\n if (meta.gitBranch === null && meta.projectPath) {\n meta.gitBranch = resolveGitBranch(meta.projectPath);\n }\n\n const fp = stat.size > 0 ? fingerprint(filePath, stat.size) : null;\n const fileId = this.files.ensure(filePath, account);\n const upsert = this.db.transaction(() => {\n this.conversations.upsert(fileId, meta, meta.messageCount);\n this.fts.upsert(meta);\n this.checkpoints.remove(filePath);\n this.files.updateCursor(fileId, {\n sizeBytes: stat.size,\n mtimeMs: stat.mtimeMs,\n // offset = size marks the file fully consumed (non-zero so the next pass\n // can classify it \"unchanged\"); no resumable reducer state is kept.\n offset: stat.size,\n line: 0,\n reducerState: null,\n fingerprint: fp,\n status: \"active\",\n });\n });\n upsert();\n\n log.debug(\n { filePath, provider: provider.name, msgs: meta.messageCount },\n \"persistent: indexed provider file\",\n );\n return meta;\n }\n\n private markDeleted(filePath: string): void {\n const existing = this.files.getByPath(filePath);\n if (!existing) return;\n const tx = this.db.transaction(() => {\n this.conversations.deleteByFileId(existing.id);\n this.fts.remove(filePath);\n this.checkpoints.remove(filePath);\n this.files.setStatus(existing.id, \"deleted\");\n });\n tx();\n }\n\n // ── Query helpers (read straight from SQLite) ───────────────────────────\n\n allActive(): ConversationMeta[] {\n return this.conversations.allActive();\n }\n\n getByIdOrSession(id: string): ConversationMeta | null {\n return this.conversations.getByIdOrSession(id);\n }\n\n getAllBySessionId(sessionId: string): ConversationMeta[] {\n return this.conversations.getAllBySessionId(sessionId);\n }\n\n // Ranked metas matching the FTS query, best first. Empty query returns the\n // most recent conversations (mirroring the in-memory indexer's empty-query\n // behavior). Resolves each FTS hit to its active conversation row.\n searchMetas(query: string, limit: number): ConversationMeta[] {\n if (!query.trim()) {\n return this.conversations.recent(limit);\n }\n const paths = this.fts.search(query, limit);\n const metas: ConversationMeta[] = [];\n for (const path of paths) {\n const meta = this.conversations.getBySourcePath(path);\n if (meta) metas.push(meta);\n }\n return metas;\n }\n\n getProjects(): string[] {\n return this.conversations.distinctProjects();\n }\n\n // Bounded conversation page: read only the requested window from the file,\n // seeking from the nearest checkpoint. Returns null if the id can't be\n // resolved to an indexed conversation. For conversations large enough to span\n // a checkpoint interval, checkpoints are built lazily on first access and\n // reused thereafter. Smaller conversations read from the start (cheap).\n async getPage(id: string, options: GetConversationPageOptions): Promise<ConversationPage | null> {\n const meta = this.conversations.getByIdOrSession(id);\n if (!meta) return null;\n const filePath = meta.filePath;\n // Bounded paging uses the parseConversation message total, which differs\n // from meta.messageCount (the metadata count excludes tool_use-only and\n // thinking-only lines).\n const total = this.conversations.pageMessageCount(filePath);\n\n if (total > CHECKPOINT_INTERVAL && this.checkpoints.count(filePath) === 0) {\n const built = await buildCheckpoints(filePath);\n if (built.length > 0) this.checkpoints.replaceAll(filePath, built);\n }\n\n const beforeIndex = options.beforeIndex ?? total;\n const fromIndex = Math.max(0, beforeIndex - options.limit);\n const floor = this.checkpoints.floor(filePath, fromIndex);\n return readPage(filePath, total, options, floor);\n }\n}\n","import type { FSWatcher } from \"chokidar\";\nimport chokidar from \"chokidar\";\nimport { getLogger } from \"../logger\";\nimport { getProjectsDir } from \"../profiles\";\nimport type { Profile } from \"../types\";\n\nconst EXCLUDED_SEGMENTS = [\"/memory/\", \"/tool-results/\"];\n\nexport interface WatchEvent {\n filePath: string;\n account: string;\n type: \"add\" | \"change\" | \"unlink\";\n}\n\nexport interface FileWatcherOptions {\n // Debounce window (ms) coalescing rapid events for one file. Spec §8.\n debounceMs?: number;\n}\n\n// Watches each active profile's projects dir for *.jsonl changes and forwards\n// debounced add/change/unlink events. Thin wrapper over chokidar; the scanner\n// owns what to do with the events (enqueue index jobs, emit to host apps).\nexport class FileWatcher {\n private watchers: FSWatcher[] = [];\n private timers = new Map<string, ReturnType<typeof setTimeout>>();\n private readonly debounceMs: number;\n\n constructor(\n private profiles: Profile[],\n private onEvent: (event: WatchEvent) => void,\n options: FileWatcherOptions = {},\n ) {\n this.debounceMs = options.debounceMs ?? 400;\n }\n\n // Resolves once every underlying chokidar watcher has finished its initial\n // scan, so the caller knows subsequent FS changes will be observed.\n async start(): Promise<void> {\n const log = getLogger();\n const ready: Promise<void>[] = [];\n for (const profile of this.profiles) {\n const dir = getProjectsDir(profile);\n const watcher = chokidar.watch(dir, {\n ignoreInitial: true,\n ignored: (p: string) => EXCLUDED_SEGMENTS.some((seg) => p.includes(seg)),\n awaitWriteFinish: { stabilityThreshold: this.debounceMs, pollInterval: 100 },\n });\n watcher\n .on(\"add\", (p) => this.dispatch(p, profile.id, \"add\"))\n .on(\"change\", (p) => this.dispatch(p, profile.id, \"change\"))\n .on(\"unlink\", (p) => this.dispatch(p, profile.id, \"unlink\"));\n ready.push(new Promise<void>((resolve) => watcher.once(\"ready\", () => resolve())));\n this.watchers.push(watcher);\n log.debug({ dir, account: profile.id }, \"watcher: watching\");\n }\n await Promise.all(ready);\n }\n\n async stop(): Promise<void> {\n for (const t of this.timers.values()) clearTimeout(t);\n this.timers.clear();\n await Promise.all(this.watchers.map((w) => w.close()));\n this.watchers = [];\n }\n\n private dispatch(filePath: string, account: string, type: WatchEvent[\"type\"]): void {\n if (!filePath.endsWith(\".jsonl\")) return;\n // Debounce per path: collapse a flurry of events into the latest one.\n const existing = this.timers.get(filePath);\n if (existing) clearTimeout(existing);\n this.timers.set(\n filePath,\n setTimeout(() => {\n this.timers.delete(filePath);\n this.onEvent({ filePath, account, type });\n }, this.debounceMs),\n );\n }\n}\n","import { getLogger } from \"../logger\";\n\nexport type JobReason = \"watcher\" | \"manual\" | \"periodic\";\n\nexport interface IndexJob {\n filePath: string;\n account: string;\n reason: JobReason;\n}\n\n// In-process job queue for incremental indexing. Jobs are deduplicated by file\n// path (the latest job for a path wins) and processed one at a time, because\n// SQLite has a single writer. A burst of filesystem events for the same file\n// collapses to one index pass.\nexport class IndexQueue {\n private pending = new Map<string, IndexJob>();\n private running = false;\n private idleResolvers: Array<() => void> = [];\n\n constructor(private process: (job: IndexJob) => Promise<void>) {}\n\n enqueue(job: IndexJob): void {\n this.pending.set(job.filePath, job);\n void this.drain();\n }\n\n // Resolves when the queue has fully drained — useful for tests and for a\n // clean shutdown.\n onIdle(): Promise<void> {\n if (!this.running && this.pending.size === 0) return Promise.resolve();\n return new Promise((resolve) => this.idleResolvers.push(resolve));\n }\n\n get size(): number {\n return this.pending.size;\n }\n\n private async drain(): Promise<void> {\n if (this.running) return;\n this.running = true;\n const log = getLogger();\n\n while (this.pending.size > 0) {\n const [path, job] = this.pending.entries().next().value as [string, IndexJob];\n this.pending.delete(path);\n try {\n await this.process(job);\n } catch (err) {\n log.warn({ filePath: job.filePath, err }, \"index-queue: job failed\");\n }\n }\n\n this.running = false;\n const resolvers = this.idleResolvers;\n this.idleResolvers = [];\n for (const r of resolvers) r();\n }\n}\n","// ─── Primitives ─────────────────────────────────────────────────────\n\nexport type MessageSender = \"user\" | \"assistant\";\nexport type Include = \"all\" | \"conversations\" | \"subagents\" | \"teammates\";\nexport type View = \"flat\" | \"tree\" | \"grouped\";\nexport type SortOrder = \"recent\" | \"oldest\" | \"messages-desc\" | \"messages-asc\" | \"alpha\";\n\nexport const VALID_SORT_ORDERS: SortOrder[] = [\n \"recent\",\n \"oldest\",\n \"messages-desc\",\n \"messages-asc\",\n \"alpha\",\n];\n\n// ─── Profile ────────────────────────────────────────────────────────\n\nexport interface Profile {\n id: string;\n label: string;\n configDir: string;\n enabled: boolean;\n emoji?: string;\n scanHistory?: boolean;\n}\n\n// ─── Content Tiers ──────────────────────────────────────────────────\n\nexport interface ContentTier {\n name: string;\n previewMax: number;\n snippetMax: number;\n}\n\n// ─── ConversationMeta (full superset) ───────────────────────────────\n\nexport interface MessageSnapshot {\n text: string;\n timestamp: string;\n}\n\nexport type ProviderName = \"claude-code\" | \"codex-cli\";\n\nexport interface ConversationMeta {\n id: string;\n filePath: string;\n sessionId: string;\n // Which provider produced this meta. Optional/additive: existing claude-code\n // metas default to \"claude-code\" when unset.\n provider?: ProviderName;\n // Distinguishes plain chat conversations from task-oriented logs (Codex).\n kind?: \"conversation\" | \"task\";\n // Provider-native session id when it differs from the (non-unique) sessionId.\n externalSessionId?: string;\n sessionName: string;\n projectPath: string;\n projectName: string;\n account: string;\n timestamp: string;\n messageCount: number;\n lastMessageSender: MessageSender;\n preview: string;\n contentSnippet: string;\n gitBranch: string | null;\n model: string | null;\n isSubagent: boolean;\n parentSessionId: string | null;\n isTeammate: boolean;\n teamName: string | null;\n toolNames: string[];\n firstMessage: MessageSnapshot | null;\n lastMessage: MessageSnapshot | null;\n lastPrompt?: string;\n}\n\n// ─── View Variants ──────────────────────────────────────────────────\n\nexport interface TreeConversation extends ConversationMeta {\n subagents: ConversationMeta[];\n}\n\nexport interface GroupedConversations {\n [groupKey: string]: ConversationMeta[];\n}\n\n// ─── Options ────────────────────────────────────────────────────────\n\nexport interface FileStatEntry {\n mtimeMs: number;\n size: number;\n}\n\nexport interface ScanOptions {\n profiles?: Profile[];\n // Providers to scan. Defaults to [\"claude-code\"]. Including \"codex-cli\"\n // requires codexRoots (no default home scan).\n providers?: ProviderName[];\n // Absolute roots to discover Codex CLI history under (e.g. ~/.codex/sessions).\n codexRoots?: string[];\n tier?: string;\n tiers?: Record<string, ContentTier>;\n include?: Include;\n view?: View;\n sort?: SortOrder;\n since?: string;\n project?: string;\n account?: string;\n limit?: number;\n offset?: number;\n onProgress?: (scanned: number, total: number) => void;\n onBatch?: (metas: ConversationMeta[]) => void;\n /** Known file stats from a previous scan. Files whose (mtimeMs, size) match\n * are skipped — the cached ConversationMeta is reused instead. */\n statCache?: Map<string, { stat: FileStatEntry; meta: ConversationMeta }>;\n}\n\nexport interface ScanResult {\n conversations: ConversationMeta[] | TreeConversation[] | GroupedConversations;\n total: number;\n scanned: number;\n}\n\nexport interface SearchOptions extends ScanOptions {\n fields?: string[];\n // Restrict results to one provider.\n provider?: ProviderName;\n}\n\nexport interface SearchMatch {\n field: string;\n snippet: string;\n}\n\nexport interface SearchResult {\n meta: ConversationMeta;\n score: number;\n matches: SearchMatch[];\n}\n\nexport interface GetConversationOptions {\n profiles?: Profile[];\n}\n\nexport interface GetConversationPageOptions {\n beforeIndex?: number;\n limit: number;\n}\n\nexport interface ConversationPage {\n messages: ConversationMessage[];\n total: number;\n fromIndex: number;\n}\n\n// A page sliced directly from a single parsed JSONL file, carrying the parsed\n// Conversation alongside the window so a caller can build a full response\n// (meta + messages) from one parse, without a prior scan().\nexport interface SingleFilePage extends ConversationPage {\n conversation: Conversation;\n}\n\n// ─── Full Conversation ──────────────────────────────────────────────\n\nexport interface TurnDuration {\n durationMs: number;\n messageCount: number;\n uuid?: string;\n}\n\nexport interface DeferredToolsDeltaAttachment {\n type: \"deferred_tools_delta\";\n addedNames: string[];\n addedLines: unknown[];\n removedNames: string[];\n}\n\nexport type AttachmentSidecar =\n | DeferredToolsDeltaAttachment\n | { type: string; [key: string]: unknown };\n\nexport interface ToolUseBlock {\n id: string;\n name: string;\n input: Record<string, unknown>;\n}\n\nexport interface ToolResultBlock {\n toolUseId: string;\n type:\n | \"edit\"\n | \"write\"\n | \"read\"\n | \"bash\"\n | \"grep\"\n | \"glob\"\n | \"taskAgent\"\n | \"taskCreate\"\n | \"taskUpdate\"\n | \"generic\";\n content: Record<string, unknown>;\n isError?: boolean;\n}\n\nexport interface TeamInfo {\n teammateId: string;\n summary?: string;\n color?: string;\n}\n\nexport interface MessageMetadata {\n model?: string;\n stopReason?: string | null;\n inputTokens?: number;\n outputTokens?: number;\n cacheReadTokens?: number;\n cacheCreationTokens?: number;\n gitBranch?: string;\n version?: string;\n toolUses?: string[];\n toolUseBlocks?: ToolUseBlock[];\n toolResults?: ToolResultBlock[];\n teamName?: string;\n teamInfo?: TeamInfo;\n}\n\nexport interface ConversationMessage {\n role: MessageSender;\n text: string;\n timestamp: string;\n uuid?: string;\n metadata?: MessageMetadata;\n isToolResult?: boolean;\n isThinking?: boolean;\n thinkingContent?: string;\n thinkingSignature?: string;\n parentUuid?: string | null;\n requestId?: string;\n promptId?: string;\n isSidechain?: boolean;\n permissionMode?: string;\n hasImages?: boolean;\n attachment?: AttachmentSidecar;\n}\n\nexport interface Conversation {\n id: string;\n filePath: string;\n projectPath: string;\n projectName: string;\n sessionId: string;\n sessionName: string;\n messages: ConversationMessage[];\n fullText: string;\n timestamp: string;\n messageCount: number;\n account: string;\n turnDurations?: TurnDuration[];\n lastPrompt?: string;\n}\n"],"mappings":"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","names":["pino","FlexSearchModule","import_fs","import_path","import_fs","import_promises","import_path","fg","import_fast_glob","import_promises","fg","import_path","import_fs","import_path","import_readline","getShortProjectName","getShortProjectName","import_fs","import_os","import_path","import_fs","stat","import_fs","import_readline","import_fs","import_path","Database","import_fs","import_fs","import_path","stat","EXCLUDED_SEGMENTS","chokidar","process","BATCH_SIZE","previous","resolvedAccount","evict","meta"]}
|