@gitkraken/core-gitlens 0.10.5 → 0.10.6
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/CHANGELOG.md +16 -1
- package/dist/git/providers/branches.d.ts +4 -1
- package/dist/git/providers/refs.d.ts +7 -0
- package/dist/git-cli/exec/git.js +1 -1
- package/dist/git-cli/providers/branches.d.ts +10 -1
- package/dist/git-cli/providers/branches.js +31 -6
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.js +2 -2
- package/dist/git-cli/providers/pausedOperations.js +2 -2
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.d.ts +12 -0
- package/dist/git-cli/providers/refs.js +18 -2
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/staging.js +1 -1
- package/dist/ipc/discovery.js +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
- package/dist/plus/integrations/reads/drains.d.ts +1 -0
- package/dist/utils/env/node/exec.js +2 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claudeCodeTranscript.js","names":["readdir"],"sources":["../../../../../plus/agents/src/providers/claudeCodeTranscript.ts"],"sourcesContent":["import { createReadStream } from 'fs';\nimport { open, readdir, readFile, stat } from 'fs/promises';\nimport { homedir } from 'os';\nimport { basename, dirname, join } from 'path';\nimport { createInterface } from 'readline';\n\nexport interface TranscriptTitles {\n\tcustom?: string;\n\tai?: string;\n\tagent?: string;\n}\n\n/** A transcript file discovered by {@link ClaudeCodeTranscriptReader.listSessions}, before its\n * contents are read — everything here comes from `readdir` + `stat`. */\nexport interface TranscriptSessionEntry {\n\treadonly sessionId: string;\n\treadonly path: string;\n\treadonly lastActivityMs: number;\n\treadonly size: number;\n}\n\n/** A {@link TranscriptSessionEntry} enriched with the fields recovered from the file's head/tail. */\nexport interface TranscriptSessionSummary extends TranscriptSessionEntry {\n\treadonly titles: TranscriptTitles;\n\treadonly lastPrompt?: string;\n}\n\nexport interface TranscriptSessionListing {\n\treadonly sessions: TranscriptSessionSummary[];\n\t/** Every transcript in the directory, not just the summarized slice — drives \"Showing N of M\". */\n\treadonly total: number;\n}\n\n/** A transcript found outside the queried cwd's project directory, plus the directory Claude\n * actually uses to resolve it for `--resume`. */\nexport interface ResumableTranscriptSessionSummary extends TranscriptSessionSummary {\n\treadonly cwd: string;\n}\n\nexport interface ResumableTranscriptSessionListing {\n\treadonly sessions: ResumableTranscriptSessionSummary[];\n\treadonly total: number;\n}\n\nexport interface EndedTranscriptDetails {\n\ttitles: TranscriptTitles;\n\tfirstPrompt?: string;\n\tlastPrompt?: string;\n}\n\ninterface CacheEntry {\n\tpath: string;\n\tmtimeMs: number;\n\tsize: number;\n\tnextOffset: number;\n\ttitles: TranscriptTitles;\n}\n\ninterface ListingCacheEntry {\n\tentries: TranscriptSessionEntry[];\n\tresolvedAt: number;\n}\n\ninterface NameCacheEntry {\n\tnames: string[];\n\tresolvedAt: number;\n}\n\ninterface SummaryCacheEntry {\n\tpath: string;\n\tmtimeMs: number;\n\tsize: number;\n\tsummary: TranscriptSessionSummary;\n}\n\ninterface TitleEntry {\n\ttype: string;\n\tsessionId?: string;\n\tcustomTitle?: string;\n\taiTitle?: string;\n\tagentName?: string;\n\tlastPrompt?: string;\n}\n\n/** Bytes read from each end of a transcript when summarizing. Titles and prompts cluster at both\n * extremes, and files run to tens of MB, so reading whole files is not an option. */\nconst summaryWindowSize = 64 * 1024;\n/** Listings are cheap (readdir + stat) but re-run per panel/sheet open; a short TTL absorbs bursts.\n * Time-based rather than dir-mtime-based because appends move file mtimes without touching the dir. */\nconst listingCacheTtlMs = 10 * 1000;\n/** Summaries are keyed by session, and a busy project has hundreds — unlike the per-live-session\n * title cache, this needs a ceiling. Sized to hold a whole junk-heavy store: the listing scan runs\n * to exhaustion, and each entry is tiny (a path plus a few short strings), so a cap below the\n * store's size would evict-and-re-read the junk tail on every listing instead of paying its\n * windowed reads once. */\nconst summaryCacheLimit = 1000;\nconst defaultListLimit = 50;\n\n/**\n * Reads Claude Code transcript JSONL files at `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`\n * to surface `custom-title`, `ai-title`, and `agent-name` entries as fallback session names.\n *\n * Maintains a per-session cache keyed by mtime + last-read byte offset, so repeated calls only\n * read the appended tail. Last occurrence per type wins.\n *\n * `resolve` is async, so a `forget` (or a newer `resolve`) for the same session can land between\n * its `await`s. A per-session generation counter is stamped at entry and re-checked before the\n * final cache write, so a stale `resolve` can't resurrect a forgotten entry or clobber a newer\n * resolve's result.\n */\nexport class ClaudeCodeTranscriptReader {\n\tprivate readonly _cache = new Map<string, CacheEntry>();\n\tprivate readonly _generations = new Map<string, number>();\n\tprivate readonly _listings = new Map<string, ListingCacheEntry>();\n\t/** Transcript file names per project directory — the readdir behind both the plain listing and\n\t * the by-id scan, which sweeps every project directory. */\n\tprivate readonly _names = new Map<string, NameCacheEntry>();\n\t/** Project directories under {@link getProjectsRoot}, re-scanned by every by-id recovery. */\n\tprivate _projectDirs: { dirs: string[]; resolvedAt: number } | undefined;\n\t/** Insertion-ordered LRU — re-inserted on hit, oldest key evicted past {@link summaryCacheLimit}. */\n\tprivate readonly _summaries = new Map<string, SummaryCacheEntry>();\n\t/** Resolved project path per transcript path, bounded like {@link _summaries}. */\n\tprivate readonly _transcriptCwds = new Map<string, string>();\n\tprivate _nextGen = 0;\n\t/** Bumped by {@link invalidateListings}; async listing scans capture it at entry and skip their\n\t * cache write when it moved, so a stale in-flight result can't re-seed a just-cleared cache. */\n\tprivate _listingGen = 0;\n\n\t/** Drops the directory-listing caches (entries, names, project dirs) so the next listing re-reads\n\t * the store. Called when a session ends or is removed: its transcript may have appeared inside\n\t * the TTL window, and a cached listing would hide the new file from the very fetch meant to\n\t * surface it. Summary and cwd caches are keyed by file identity and stay valid, so they're kept.\n\t * The generation bump makes any in-flight listing skip its own cache write — otherwise a scan\n\t * started before the invalidation would re-seed the caches with pre-end results, freshly\n\t * timestamped (same discipline as `resolve`'s per-session generations). */\n\tinvalidateListings(): void {\n\t\tthis._listingGen++;\n\t\tthis._listings.clear();\n\t\tthis._names.clear();\n\t\tthis._projectDirs = undefined;\n\t}\n\n\tasync resolve(sessionId: string, cwd: string | undefined): Promise<TranscriptTitles | undefined> {\n\t\tconst gen = ++this._nextGen;\n\t\tthis._generations.set(sessionId, gen);\n\n\t\tconst cached = this._cache.get(sessionId);\n\t\tconst path = cached?.path ?? (await this.locateTranscript(sessionId, cwd));\n\t\tif (path == null) return undefined;\n\n\t\tlet stats;\n\t\ttry {\n\t\t\tstats = await stat(path);\n\t\t} catch {\n\t\t\t// File disappeared; drop any stale cache entry so a future call retries discovery.\n\t\t\t// Skip the delete if a newer resolve/forget claimed the slot — their state is fresher.\n\t\t\tif (this._generations.get(sessionId) === gen) {\n\t\t\t\tthis._cache.delete(sessionId);\n\t\t\t\tthis._generations.delete(sessionId);\n\t\t\t}\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (stats.mtimeMs === cached?.mtimeMs && stats.size === cached?.size) {\n\t\t\treturn cached.titles;\n\t\t}\n\n\t\t// Truncation or rewrite: stat shrank below our read cursor or mtime moved backward → full re-scan.\n\t\tconst startFromScratch = cached == null || stats.size < cached.nextOffset || stats.mtimeMs < cached.mtimeMs;\n\t\tconst startOffset = startFromScratch ? 0 : cached.nextOffset;\n\t\tconst baseTitles: TranscriptTitles = startFromScratch ? {} : { ...cached.titles };\n\n\t\tconst { titles, consumedEnd } =\n\t\t\tstats.size > startOffset\n\t\t\t\t? await this.readRange(path, startOffset, stats.size, sessionId, baseTitles)\n\t\t\t\t: { titles: baseTitles, consumedEnd: startOffset };\n\n\t\t// If `forget` ran or a newer `resolve` started while we were awaiting, skip the cache write\n\t\t// — the entry we'd be writing reflects stale state.\n\t\tif (this._generations.get(sessionId) !== gen) return titles;\n\n\t\tconst entry: CacheEntry = {\n\t\t\tpath: path,\n\t\t\tmtimeMs: stats.mtimeMs,\n\t\t\tsize: stats.size,\n\t\t\tnextOffset: consumedEnd,\n\t\t\ttitles: titles,\n\t\t};\n\t\tthis._cache.set(sessionId, entry);\n\t\treturn titles;\n\t}\n\n\tforget(sessionId: string): void {\n\t\tthis._cache.delete(sessionId);\n\t\tthis._generations.delete(sessionId);\n\t}\n\n\t/**\n\t * One-shot read for an *ended* (terminal, static) session: extracts the transcript titles plus\n\t * the first and last user prompts. Unlike {@link resolve}, it bypasses the tail cache — a finished\n\t * transcript never grows, so it's read once on demand (when an ended row is opened) and the\n\t * result applied to the session. `last-prompt` entries carry the prompt as of that point; the first\n\t * one bearing a value is the session's opening prompt, the last its most recent. Returns `undefined`\n\t * when no transcript is found (archived/purged) — the caller keeps whatever durable-store fields it\n\t * already has.\n\t *\n\t * Streams the file line-by-line rather than buffering it whole: a finished transcript can run to\n\t * tens of MB, but this keeps only one line resident at a time. A *full* scan (not a head/tail\n\t * window) is required for correctness — when the first turn is large the opening `last-prompt` sits\n\t * past any fixed head window, and a windowed read would then mistake a later prompt for the first,\n\t * poisoning the derived session name.\n\t */\n\tasync resolveEndedDetails(sessionId: string, cwd: string | undefined): Promise<EndedTranscriptDetails | undefined> {\n\t\tconst path = await this.locateTranscript(sessionId, cwd);\n\t\tif (path == null) return undefined;\n\n\t\tlet stats;\n\t\ttry {\n\t\t\tstats = await stat(path);\n\t\t} catch (ex) {\n\t\t\t// A transcript that vanished is terminal; an I/O or permission failure must propagate so\n\t\t\t// the caller retries rather than caching this session as permanently unresolvable.\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\t\tif (stats.size === 0) return undefined;\n\n\t\tconst titles: TranscriptTitles = {};\n\t\tlet firstPrompt: string | undefined;\n\t\tlet lastPrompt: string | undefined;\n\n\t\tconst rl = createInterface({ input: createReadStream(path, { encoding: 'utf8' }), crlfDelay: Infinity });\n\t\ttry {\n\t\t\tfor await (const line of rl) {\n\t\t\t\tapplyTitleLine(line, sessionId, titles);\n\t\t\t\tif (!line.includes('last-prompt')) continue;\n\n\t\t\t\tconst entry = parseSummaryLine(line, sessionId);\n\t\t\t\tif (entry?.type === 'last-prompt' && entry.lastPrompt != null && entry.lastPrompt.length > 0) {\n\t\t\t\t\tfirstPrompt ??= entry.lastPrompt;\n\t\t\t\t\tlastPrompt = entry.lastPrompt;\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\trl.close();\n\t\t}\n\n\t\treturn { titles: titles, firstPrompt: firstPrompt, lastPrompt: lastPrompt };\n\t}\n\n\t/**\n\t * Lists the transcripts of sessions whose working directory is `cwd`, most-recently-active first,\n\t * summarizing until `limit` summarizable transcripts are found — not just the first `limit` on\n\t * disk, since junk transcripts (dropped below) would otherwise starve the result.\n\t * `excludeSessionIds` is skipped before `limit` applies, and excluded entries are also dropped\n\t * from `total` — they're already shown elsewhere (e.g. as live sessions), so a caller's \"N of M\"\n\t * count must not double-count them.\n\t *\n\t * Claude homes a transcript under the directory encoding the cwd from which the session started.\n\t * That directory is authoritative for where `claude --resume <id>` can find the transcript, but\n\t * Claude does not reliably move it when the session later changes worktrees. Callers that have a\n\t * durable session-to-worktree association can recover those moved sessions with\n\t * {@link listSessionsByIds}; this method intentionally remains the cheap legacy directory listing.\n\t *\n\t * Discovery (readdir + stat) covers the whole directory and is cheap; summarizing is not, so it's\n\t * capped. Entries whose summary yields neither a title nor a prompt are dropped — those are aborted\n\t * or empty transcripts with nothing to show or search on.\n\t */\n\tasync listSessions(\n\t\tcwd: string,\n\t\toptions?: { limit?: number; excludeSessionIds?: ReadonlySet<string> },\n\t): Promise<TranscriptSessionListing> {\n\t\tconst dir = await this.resolveProjectDir(cwd);\n\t\tif (dir == null) return { sessions: [], total: 0 };\n\n\t\tconst entries = await this.listEntries(dir);\n\t\tif (entries.length === 0) return { sessions: [], total: 0 };\n\n\t\tconst limit = options?.limit ?? defaultListLimit;\n\t\tconst exclude = options?.excludeSessionIds;\n\t\t// Zero-byte transcripts can never summarize — skip the read entirely and keep them out of\n\t\t// `total`, which otherwise counts sessions no `limit` could ever surface.\n\t\tconst candidates = entries.filter(e => e.size > 0 && exclude?.has(e.sessionId) !== true);\n\t\tconst { sessions, exhausted } = await this.collectSessions(candidates, limit, async entry => {\n\t\t\tconst summary = await this.resolveSummary(entry);\n\t\t\treturn summary != null && hasSummaryContent(summary) ? summary : undefined;\n\t\t});\n\n\t\t// An exhausted scan proved the exact valid count; otherwise unscanned candidates keep\n\t\t// `total` an upper bound so paging knows more MAY exist.\n\t\treturn { sessions: sessions, total: exhausted ? sessions.length : candidates.length };\n\t}\n\n\t/**\n\t * Finds a known set of session ids across every Claude project directory except `excludeCwd`'s.\n\t * This is the recovery path for durable CLI records whose worktree changed after Claude chose the\n\t * transcript's project directory. The caller supplies the association; transcript contents alone\n\t * are deliberately not used to guess which current worktree owns an arbitrary old session.\n\t *\n\t * Each result carries the project path encoded by the directory that actually holds the transcript,\n\t * so resuming it does not repeat discovery from the wrong worktree. Duplicate ids are counted once,\n\t * with the newest transcript winning.\n\t */\n\tasync listSessionsByIds(\n\t\tsessionIds: ReadonlySet<string>,\n\t\toptions?: { limit?: number; excludeCwd?: string },\n\t): Promise<ResumableTranscriptSessionListing> {\n\t\tif (sessionIds.size === 0) return { sessions: [], total: 0 };\n\n\t\tconst excludeDir = await this.resolveProjectDir(options?.excludeCwd);\n\t\t// Same zero-byte drop as `listSessions` — see the rationale there.\n\t\tconst entries = (await this.listEntriesByIds(sessionIds, excludeDir)).filter(e => e.size > 0);\n\t\tif (entries.length === 0) return { sessions: [], total: 0 };\n\n\t\tconst limit = options?.limit ?? defaultListLimit;\n\t\tconst { sessions, exhausted } = await this.collectSessions(entries, limit, async entry => {\n\t\t\tconst summary = await this.resolveSummary(entry);\n\t\t\tif (summary == null || !hasSummaryContent(summary)) return undefined;\n\n\t\t\tconst cwd = await this.resolveTranscriptCwd(entry);\n\t\t\treturn cwd != null ? { ...summary, cwd: cwd } : undefined;\n\t\t});\n\n\t\t// Exhausted scans prove the exact count — see `listSessions`.\n\t\treturn { sessions: sessions, total: exhausted ? sessions.length : entries.length };\n\t}\n\n\t/** Discovers every transcript in `dir`, newest first. The directory also holds one sibling\n\t * subdirectory per session (`<uuid>/subagents`, `<uuid>/tool-results`), so entries must be filtered\n\t * to files — a busy project has roughly as many subdirectories as transcripts. */\n\tprivate async listEntries(dir: string): Promise<TranscriptSessionEntry[]> {\n\t\tconst cached = this._listings.get(dir);\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.entries;\n\n\t\tconst gen = this._listingGen;\n\t\tlet names: string[];\n\t\ttry {\n\t\t\tnames = await this.listTranscriptNames(dir);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst entries = (await this.statEntries(dir, names)).sort((a, b) => b.lastActivityMs - a.lastActivityMs);\n\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._listings.set(dir, { entries: entries, resolvedAt: Date.now() });\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/** Stats `names` in `dir`, dropping the ones that failed — a transcript can vanish (or be\n\t * archived) between the readdir and the stat. Unsorted; callers order as they need.\n\t * Protected (like {@link readSummary}) so tests can interleave against the listing flow. */\n\tprotected async statEntries(dir: string, names: string[]): Promise<TranscriptSessionEntry[]> {\n\t\tconst settled = await Promise.allSettled(\n\t\t\tnames.map(async (name): Promise<TranscriptSessionEntry> => {\n\t\t\t\tconst path = join(dir, name);\n\t\t\t\tconst stats = await stat(path);\n\t\t\t\treturn {\n\t\t\t\t\tsessionId: basename(name, '.jsonl'),\n\t\t\t\t\tpath: path,\n\t\t\t\t\tlastActivityMs: stats.mtimeMs,\n\t\t\t\t\tsize: stats.size,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\n\t\treturn settled.filter(r => r.status === 'fulfilled').map(r => r.value);\n\t}\n\n\t/** The `.jsonl` file names in `dir`, cached for {@link listingCacheTtlMs}. Failures propagate\n\t * uncached: an unreadable directory means different things to different callers, and the next\n\t * call must be free to get a real answer. */\n\tprivate async listTranscriptNames(dir: string): Promise<string[]> {\n\t\tconst cached = this._names.get(dir);\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.names;\n\n\t\tconst gen = this._listingGen;\n\t\tconst dirents = await readdir(dir, { withFileTypes: true });\n\t\tconst names = dirents.filter(d => d.isFile() && d.name.endsWith('.jsonl')).map(d => d.name);\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._names.set(dir, { names: names, resolvedAt: Date.now() });\n\t\t}\n\n\t\treturn names;\n\t}\n\n\t/** Every project directory under the projects root, cached for {@link listingCacheTtlMs} — a\n\t * by-id recovery sweeps all of them, once per queried worktree. */\n\tprivate async listProjectDirs(): Promise<string[]> {\n\t\tconst cached = this._projectDirs;\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.dirs;\n\n\t\tconst gen = this._listingGen;\n\t\tconst root = this.getProjectsRoot();\n\t\tconst dirents = await readdir(root, { withFileTypes: true });\n\t\tconst dirs = dirents.filter(d => d.isDirectory()).map(d => join(root, d.name));\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._projectDirs = { dirs: dirs, resolvedAt: Date.now() };\n\t\t}\n\n\t\treturn dirs;\n\t}\n\n\t/** Scans project directory names once, then stats only requested transcript ids. */\n\tprivate async listEntriesByIds(\n\t\tsessionIds: ReadonlySet<string>,\n\t\texcludeDir: string | undefined,\n\t): Promise<TranscriptSessionEntry[]> {\n\t\tlet idsToFind = sessionIds;\n\t\tif (excludeDir != null) {\n\t\t\ttry {\n\t\t\t\tconst names = await this.listTranscriptNames(excludeDir);\n\t\t\t\tconst idsInExcludedDir = new Set(names.map(name => basename(name, '.jsonl')));\n\t\t\t\tidsToFind = new Set([...sessionIds].filter(id => !idsInExcludedDir.has(id)));\n\t\t\t\tif (idsToFind.size === 0) return [];\n\t\t\t} catch {\n\t\t\t\t// If the exact directory can't be inspected, don't risk returning a duplicate from a\n\t\t\t\t// stale transcript copy elsewhere. The ordinary listing still owns the exact path.\n\t\t\t\treturn [];\n\t\t\t}\n\t\t}\n\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await this.listProjectDirs();\n\t\t} catch (ex) {\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn [];\n\t\t}\n\n\t\tconst settledDirs = await Promise.allSettled(\n\t\t\tdirs.map(async dir => {\n\t\t\t\tif (dir === excludeDir) return [];\n\n\t\t\t\tconst names = (await this.listTranscriptNames(dir)).filter(name =>\n\t\t\t\t\tidsToFind.has(basename(name, '.jsonl')),\n\t\t\t\t);\n\t\t\t\treturn this.statEntries(dir, names);\n\t\t\t}),\n\t\t);\n\n\t\tconst bySessionId = new Map<string, TranscriptSessionEntry>();\n\t\tfor (const settledDir of settledDirs) {\n\t\t\tif (settledDir.status !== 'fulfilled') continue;\n\n\t\t\tfor (const entry of settledDir.value) {\n\t\t\t\tconst existing = bySessionId.get(entry.sessionId);\n\t\t\t\tif (existing == null || entry.lastActivityMs > existing.lastActivityMs) {\n\t\t\t\t\tbySessionId.set(entry.sessionId, entry);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn [...bySessionId.values()].sort((a, b) => b.lastActivityMs - a.lastActivityMs);\n\t}\n\n\t/** `exhausted` reports whether every candidate was scanned — an exhausted scan has proven the\n\t * exact valid count, so callers can report it instead of an upper bound that would keep a\n\t * \"Show More\" affordance alive with nothing left to show. */\n\tprivate async collectSessions<T>(\n\t\tcandidates: readonly TranscriptSessionEntry[],\n\t\tlimit: number,\n\t\tresolve: (entry: TranscriptSessionEntry) => Promise<T | undefined>,\n\t): Promise<{ sessions: T[]; exhausted: boolean }> {\n\t\tconst sessions: T[] = [];\n\t\tconst pushResolved = (settled: PromiseSettledResult<T | undefined>[]): void => {\n\t\t\tfor (const result of settled) {\n\t\t\t\tif (result.status !== 'fulfilled' || result.value == null) continue;\n\n\t\t\t\tsessions.push(result.value);\n\t\t\t}\n\t\t};\n\n\t\t// limit <= 0 means \"no ceiling\" — resolve every candidate.\n\t\tif (limit <= 0) {\n\t\t\tpushResolved(await Promise.allSettled(candidates.map(resolve)));\n\t\t\treturn { sessions: sessions, exhausted: true };\n\t\t}\n\n\t\t// Scans until `limit` results or the candidates run out — a fixed scan ceiling would let a run\n\t\t// of junk transcripts hide every older real session behind it (and the picker then claims there\n\t\t// are none at all). The cost stays bounded: callers pre-drop zero-byte candidates read-free, and\n\t\t// a junk summary is windowed, tiny, and LRU-cached, so the sweep is paid in syscalls, once.\n\t\tlet cursor = 0;\n\t\twhile (sessions.length < limit && cursor < candidates.length) {\n\t\t\t// Batched by remaining need so a hit-rich head stops the scan early instead of resolving\n\t\t\t// every candidate up front.\n\t\t\tconst batch = candidates.slice(cursor, cursor + (limit - sessions.length));\n\t\t\tcursor += batch.length;\n\t\t\tpushResolved(await Promise.allSettled(batch.map(resolve)));\n\t\t}\n\n\t\treturn { sessions: sessions, exhausted: cursor >= candidates.length };\n\t}\n\n\t/** Summarizes one transcript, reusing the cached result while its mtime and size are unchanged. */\n\tprivate async resolveSummary(entry: TranscriptSessionEntry): Promise<TranscriptSessionSummary | undefined> {\n\t\tconst cached = this._summaries.get(entry.sessionId);\n\t\tif (\n\t\t\tcached != null &&\n\t\t\tcached.path === entry.path &&\n\t\t\tcached.mtimeMs === entry.lastActivityMs &&\n\t\t\tcached.size === entry.size\n\t\t) {\n\t\t\t// Refresh recency for the LRU.\n\t\t\tthis._summaries.delete(entry.sessionId);\n\t\t\tthis._summaries.set(entry.sessionId, cached);\n\t\t\treturn cached.summary;\n\t\t}\n\n\t\tlet summary: TranscriptSessionSummary;\n\t\ttry {\n\t\t\tsummary = await this.readSummary(entry);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthis._summaries.set(entry.sessionId, {\n\t\t\tpath: entry.path,\n\t\t\tmtimeMs: entry.lastActivityMs,\n\t\t\tsize: entry.size,\n\t\t\tsummary: summary,\n\t\t});\n\t\tif (this._summaries.size > summaryCacheLimit) {\n\t\t\tconst oldest = this._summaries.keys().next();\n\t\t\tif (!oldest.done) {\n\t\t\t\tthis._summaries.delete(oldest.value);\n\t\t\t}\n\t\t}\n\t\treturn summary;\n\t}\n\n\t/** Recovers the project path represented by the transcript's containing directory. A resolved\n\t * answer is cached: it's a line-scan of the whole transcript, and the directory holding a given\n\t * path never changes. A miss isn't cached — a `cwd` line can still land on append. */\n\tprivate async resolveTranscriptCwd(entry: TranscriptSessionEntry): Promise<string | undefined> {\n\t\tconst cached = this._transcriptCwds.get(entry.path);\n\t\tif (cached != null) return cached;\n\n\t\tconst projectDir = dirname(entry.path);\n\t\tconst projectDirName = basename(projectDir);\n\t\tconst matchesProjectDir = (cwd: string): boolean =>\n\t\t\tencodeProjectDirName(cwd).toLowerCase() === projectDirName.toLowerCase();\n\n\t\ttry {\n\t\t\tconst index = JSON.parse(await readFile(join(projectDir, 'sessions-index.json'), 'utf8')) as {\n\t\t\t\tentries?: { sessionId?: unknown; projectPath?: unknown }[];\n\t\t\t};\n\t\t\tif (Array.isArray(index.entries)) {\n\t\t\t\tconst indexed = index.entries.find(item => item?.sessionId === entry.sessionId);\n\t\t\t\tif (typeof indexed?.projectPath === 'string' && matchesProjectDir(indexed.projectPath)) {\n\t\t\t\t\tthis.rememberTranscriptCwd(entry.path, indexed.projectPath);\n\t\t\t\t\treturn indexed.projectPath;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// The index is optional and can lag or be malformed. The transcript is authoritative below.\n\t\t}\n\n\t\tconst rl = createInterface({ input: createReadStream(entry.path, { encoding: 'utf8' }), crlfDelay: Infinity });\n\t\ttry {\n\t\t\tfor await (const line of rl) {\n\t\t\t\tif (!line.includes('\"cwd\"')) continue;\n\n\t\t\t\tlet parsed: { cwd?: unknown; sessionId?: unknown };\n\t\t\t\ttry {\n\t\t\t\t\tparsed = JSON.parse(line) as { cwd?: unknown; sessionId?: unknown };\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (parsed.sessionId != null && parsed.sessionId !== entry.sessionId) continue;\n\n\t\t\t\tif (typeof parsed.cwd === 'string' && matchesProjectDir(parsed.cwd)) {\n\t\t\t\t\tthis.rememberTranscriptCwd(entry.path, parsed.cwd);\n\t\t\t\t\treturn parsed.cwd;\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\trl.close();\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate rememberTranscriptCwd(path: string, cwd: string): void {\n\t\tthis._transcriptCwds.set(path, cwd);\n\t\tif (this._transcriptCwds.size > summaryCacheLimit) {\n\t\t\tconst oldest = this._transcriptCwds.keys().next();\n\t\t\tif (!oldest.done) {\n\t\t\t\tthis._transcriptCwds.delete(oldest.value);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Reads a summary from the first and last {@link summaryWindowSize} bytes of the transcript.\n\t *\n\t * Deliberately does not reuse `resolve` — that scans from the last-read offset and buffers the whole\n\t * range, which for a cold multi-MB transcript means loading the entire file. Titles are written early\n\t * and re-written as they're refined, while prompts land at the tail, so the two windows recover both\n\t * at a fraction of the bytes. The tail is parsed second so the newest value wins.\n\t */\n\tprotected async readSummary(entry: TranscriptSessionEntry): Promise<TranscriptSessionSummary> {\n\t\tconst titles: TranscriptTitles = {};\n\t\tlet lastPrompt: string | undefined;\n\n\t\tconst apply = (buffer: Buffer, dropPartialFirstLine: boolean): void => {\n\t\t\tconst text = buffer.toString('utf8');\n\t\t\tconst lines = text.split('\\n');\n\t\t\t// A window starting mid-file almost always opens mid-line; that fragment can't be parsed.\n\t\t\tif (dropPartialFirstLine) {\n\t\t\t\tlines.shift();\n\t\t\t}\n\t\t\tfor (const line of lines) {\n\t\t\t\tconst parsed = parseSummaryLine(line, entry.sessionId);\n\t\t\t\tif (parsed == null) continue;\n\n\t\t\t\tapplyTitleEntry(parsed, titles);\n\t\t\t\tif (parsed.lastPrompt != null && parsed.lastPrompt.length > 0) {\n\t\t\t\t\tlastPrompt = parsed.lastPrompt;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tconst handle = await open(entry.path, 'r');\n\t\ttry {\n\t\t\tif (entry.size <= summaryWindowSize * 2) {\n\t\t\t\tconst whole = Buffer.alloc(entry.size);\n\t\t\t\tawait handle.read(whole, 0, entry.size, 0);\n\t\t\t\tapply(whole, false);\n\t\t\t} else {\n\t\t\t\tconst head = Buffer.alloc(summaryWindowSize);\n\t\t\t\tawait handle.read(head, 0, summaryWindowSize, 0);\n\t\t\t\tapply(head, false);\n\n\t\t\t\tconst tail = Buffer.alloc(summaryWindowSize);\n\t\t\t\tawait handle.read(tail, 0, summaryWindowSize, entry.size - summaryWindowSize);\n\t\t\t\tapply(tail, true);\n\t\t\t}\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\n\t\treturn {\n\t\t\t...entry,\n\t\t\ttitles: titles,\n\t\t\tlastPrompt: lastPrompt,\n\t\t};\n\t}\n\n\tprotected getProjectsRoot(): string {\n\t\treturn join(homedir(), '.claude', 'projects');\n\t}\n\n\t/** Resolves the `~/.claude/projects` directory holding the sessions whose working directory is\n\t * `cwd`, or `undefined` when none exists. Falls back to a case-insensitive name match because on\n\t * Windows our\n\t * paths carry a lower-cased drive letter (`normalizePath`) while Claude encodes the OS-native\n\t * `C:\\...` — so the exact name never matches by case. */\n\tprotected async resolveProjectDir(cwd: string | undefined): Promise<string | undefined> {\n\t\tif (cwd == null || cwd.length === 0) return undefined;\n\n\t\tconst root = this.getProjectsRoot();\n\t\tconst encoded = encodeProjectDirName(cwd);\n\n\t\tconst exact = join(root, encoded);\n\t\tif (await directoryExists(exact)) return exact;\n\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await readdir(root);\n\t\t} catch (ex) {\n\t\t\t// Only a missing projects root means \"no transcript\". A transient failure propagates so the\n\t\t\t// caller retries instead of caching a permanent miss.\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst lowered = encoded.toLowerCase();\n\t\tconst match = dirs.find(d => d !== encoded && d.toLowerCase() === lowered);\n\t\treturn match != null ? join(root, match) : undefined;\n\t}\n\n\tprotected async locateTranscript(sessionId: string, cwd: string | undefined): Promise<string | undefined> {\n\t\tconst fileName = `${sessionId}.jsonl`;\n\n\t\tconst dir = await this.resolveProjectDir(cwd);\n\t\tif (dir != null) {\n\t\t\tconst candidate = join(dir, fileName);\n\t\t\tif (await fileExists(candidate)) return candidate;\n\t\t}\n\n\t\t// The recorded cwd drifts whenever the agent `cd`s, so it can encode to a directory the session\n\t\t// doesn't actually live in — scan every project for the file as a last resort.\n\t\tconst root = this.getProjectsRoot();\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await readdir(root);\n\t\t} catch (ex) {\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst candidates = await Promise.all(\n\t\t\tdirs.map(async d => {\n\t\t\t\tconst candidate = join(root, d, fileName);\n\t\t\t\treturn (await fileExists(candidate)) ? candidate : undefined;\n\t\t\t}),\n\t\t);\n\t\treturn candidates.find(c => c != null);\n\t}\n\n\t/**\n\t * Reads bytes `[start, end)` from `path` and applies any title entries it finds to a copy of\n\t * `baseTitles`. Returns the merged titles and the byte position immediately after the last\n\t * newline observed — partial trailing lines (writer hasn't flushed `\\n` yet) are intentionally\n\t * left unconsumed so they're re-read on the next pass.\n\t */\n\tprotected async readRange(\n\t\tpath: string,\n\t\tstart: number,\n\t\tend: number,\n\t\tsessionId: string,\n\t\tbaseTitles: TranscriptTitles,\n\t): Promise<{ titles: TranscriptTitles; consumedEnd: number }> {\n\t\tconst buffer = await readSlice(path, start, end);\n\t\tconst titles: TranscriptTitles = { ...baseTitles };\n\n\t\tlet lastNewlineEnd = start;\n\t\tlet lineStart = 0;\n\t\tfor (let i = 0; i < buffer.length; i++) {\n\t\t\tif (buffer[i] !== 0x0a) continue;\n\n\t\t\t// Slice the line (trim a trailing \\r for CRLF files), then advance the cursor past the \\n\n\t\t\t// regardless of whether the line yielded anything — we've fully observed those bytes.\n\t\t\tconst lineEnd = i > lineStart && buffer[i - 1] === 0x0d ? i - 1 : i;\n\t\t\tif (lineEnd > lineStart) {\n\t\t\t\tconst line = buffer.toString('utf8', lineStart, lineEnd);\n\t\t\t\tapplyTitleLine(line, sessionId, titles);\n\t\t\t}\n\t\t\tlineStart = i + 1;\n\t\t\tlastNewlineEnd = start + i + 1;\n\t\t}\n\n\t\treturn { titles: titles, consumedEnd: lastNewlineEnd };\n\t}\n}\n\nfunction applyTitleLine(line: string, sessionId: string, titles: TranscriptTitles): void {\n\tif (!isLikelyTitleLine(line)) return;\n\n\tlet entry: TitleEntry;\n\ttry {\n\t\tentry = JSON.parse(line) as TitleEntry;\n\t} catch {\n\t\treturn;\n\t}\n\tif (entry.sessionId != null && entry.sessionId !== sessionId) return;\n\n\tapplyTitleEntry(entry, titles);\n}\n\n/** Parses one transcript line for {@link ClaudeCodeTranscriptReader.readSummary}, rejecting entries\n * that belong to another session. Can't use the `-title` prefilter that {@link isLikelyTitleLine}\n * applies: `last-prompt` matches neither of its markers. */\nfunction parseSummaryLine(line: string, sessionId: string): TitleEntry | undefined {\n\tif (line.length === 0 || !line.includes('\"type\"')) return undefined;\n\n\tlet entry: TitleEntry;\n\ttry {\n\t\tentry = JSON.parse(line) as TitleEntry;\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (entry.sessionId != null && entry.sessionId !== sessionId) return undefined;\n\n\treturn entry;\n}\n\nfunction applyTitleEntry(entry: TitleEntry, titles: TranscriptTitles): void {\n\tswitch (entry.type) {\n\t\tcase 'custom-title':\n\t\t\tif (typeof entry.customTitle === 'string' && entry.customTitle.length > 0) {\n\t\t\t\ttitles.custom = entry.customTitle;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'ai-title':\n\t\t\tif (typeof entry.aiTitle === 'string' && entry.aiTitle.length > 0) {\n\t\t\t\ttitles.ai = entry.aiTitle;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'agent-name':\n\t\t\tif (typeof entry.agentName === 'string' && entry.agentName.length > 0) {\n\t\t\t\ttitles.agent = entry.agentName;\n\t\t\t}\n\t\t\tbreak;\n\t}\n}\n\n/** Encodes a working directory into its `~/.claude/projects` directory name. Claude replaces every\n * non-alphanumeric character — not just separators — preserving runs (`/home/e/.claude` →\n * `-home-e--claude`) and case. Separators-only would leave the dot in our own worktree convention\n * (`<repo>.worktrees/<name>`) intact and compute a directory that never exists. */\nexport function encodeProjectDirName(cwd: string): string {\n\treturn cwd.replace(/[^a-zA-Z0-9]/g, '-');\n}\n\nfunction isLikelyTitleLine(line: string): boolean {\n\tif (!line.includes('\"type\"')) return false;\n\treturn line.includes('-title') || line.includes('agent-name');\n}\n\n/** A transcript with no title and no prompt is an aborted or empty session — nothing to name it by,\n * nothing to search it on. */\nfunction hasSummaryContent(summary: TranscriptSessionSummary): boolean {\n\tconst { custom, ai, agent } = summary.titles;\n\treturn custom != null || ai != null || agent != null || summary.lastPrompt != null;\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n\ttry {\n\t\tconst stats = await stat(path);\n\t\treturn stats.isFile();\n\t} catch (ex) {\n\t\t// See {@link isMissingEntry}: only a genuine absence is an answer.\n\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\treturn false;\n\t}\n}\n\nasync function directoryExists(path: string): Promise<boolean> {\n\ttry {\n\t\tconst stats = await stat(path);\n\t\treturn stats.isDirectory();\n\t} catch (ex) {\n\t\t// \"Not there\" is an answer; anything else (EACCES, EIO, a mount hiccup) is a transient\n\t\t// failure the caller must be able to tell apart — see {@link isMissingEntry}.\n\t\tif (isMissingEntry(ex)) return false;\n\n\t\tthrow ex;\n\t}\n}\n\n/** True for the \"path simply isn't there\" errno codes, as opposed to a transient I/O or permission\n * failure. Callers cache a genuine absence as terminal, so a transient failure must not look like\n * one — it would pin a session to its fallback name until the window reloads. */\nfunction isMissingEntry(ex: unknown): boolean {\n\tconst code = (ex as NodeJS.ErrnoException | undefined)?.code;\n\treturn code === 'ENOENT' || code === 'ENOTDIR';\n}\n\nfunction readSlice(path: string, start: number, end: number): Promise<Buffer> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst chunks: Buffer[] = [];\n\t\tconst stream = createReadStream(path, { start: start, end: end - 1 });\n\t\tstream.on('data', chunk => chunks.push(chunk as Buffer));\n\t\tstream.on('end', () => resolve(Buffer.concat(chunks)));\n\t\tstream.on('error', reject);\n\t});\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"claudeCodeTranscript.js","names":[],"sources":["../../../../../plus/agents/src/providers/claudeCodeTranscript.ts"],"sourcesContent":["import { createReadStream } from 'fs';\nimport { open, readdir, readFile, stat } from 'fs/promises';\nimport { homedir } from 'os';\nimport { basename, dirname, join } from 'path';\nimport { createInterface } from 'readline';\n\nexport interface TranscriptTitles {\n\tcustom?: string;\n\tai?: string;\n\tagent?: string;\n}\n\n/** A transcript file discovered by {@link ClaudeCodeTranscriptReader.listSessions}, before its\n * contents are read — everything here comes from `readdir` + `stat`. */\nexport interface TranscriptSessionEntry {\n\treadonly sessionId: string;\n\treadonly path: string;\n\treadonly lastActivityMs: number;\n\treadonly size: number;\n}\n\n/** A {@link TranscriptSessionEntry} enriched with the fields recovered from the file's head/tail. */\nexport interface TranscriptSessionSummary extends TranscriptSessionEntry {\n\treadonly titles: TranscriptTitles;\n\treadonly lastPrompt?: string;\n}\n\nexport interface TranscriptSessionListing {\n\treadonly sessions: TranscriptSessionSummary[];\n\t/** Every transcript in the directory, not just the summarized slice — drives \"Showing N of M\". */\n\treadonly total: number;\n}\n\n/** A transcript found outside the queried cwd's project directory, plus the directory Claude\n * actually uses to resolve it for `--resume`. */\nexport interface ResumableTranscriptSessionSummary extends TranscriptSessionSummary {\n\treadonly cwd: string;\n}\n\nexport interface ResumableTranscriptSessionListing {\n\treadonly sessions: ResumableTranscriptSessionSummary[];\n\treadonly total: number;\n}\n\nexport interface EndedTranscriptDetails {\n\ttitles: TranscriptTitles;\n\tfirstPrompt?: string;\n\tlastPrompt?: string;\n}\n\ninterface CacheEntry {\n\tpath: string;\n\tmtimeMs: number;\n\tsize: number;\n\tnextOffset: number;\n\ttitles: TranscriptTitles;\n}\n\ninterface ListingCacheEntry {\n\tentries: TranscriptSessionEntry[];\n\tresolvedAt: number;\n}\n\ninterface NameCacheEntry {\n\tnames: string[];\n\tresolvedAt: number;\n}\n\ninterface SummaryCacheEntry {\n\tpath: string;\n\tmtimeMs: number;\n\tsize: number;\n\tsummary: TranscriptSessionSummary;\n}\n\ninterface TitleEntry {\n\ttype: string;\n\tsessionId?: string;\n\tcustomTitle?: string;\n\taiTitle?: string;\n\tagentName?: string;\n\tlastPrompt?: string;\n}\n\n/** Bytes read from each end of a transcript when summarizing. Titles and prompts cluster at both\n * extremes, and files run to tens of MB, so reading whole files is not an option. */\nconst summaryWindowSize = 64 * 1024;\n/** Listings are cheap (readdir + stat) but re-run per panel/sheet open; a short TTL absorbs bursts.\n * Time-based rather than dir-mtime-based because appends move file mtimes without touching the dir. */\nconst listingCacheTtlMs = 10 * 1000;\n/** Summaries are keyed by session, and a busy project has hundreds — unlike the per-live-session\n * title cache, this needs a ceiling. Sized to hold a whole junk-heavy store: the listing scan runs\n * to exhaustion, and each entry is tiny (a path plus a few short strings), so a cap below the\n * store's size would evict-and-re-read the junk tail on every listing instead of paying its\n * windowed reads once. */\nconst summaryCacheLimit = 1000;\nconst defaultListLimit = 50;\n\n/**\n * Reads Claude Code transcript JSONL files at `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`\n * to surface `custom-title`, `ai-title`, and `agent-name` entries as fallback session names.\n *\n * Maintains a per-session cache keyed by mtime + last-read byte offset, so repeated calls only\n * read the appended tail. Last occurrence per type wins.\n *\n * `resolve` is async, so a `forget` (or a newer `resolve`) for the same session can land between\n * its `await`s. A per-session generation counter is stamped at entry and re-checked before the\n * final cache write, so a stale `resolve` can't resurrect a forgotten entry or clobber a newer\n * resolve's result.\n */\nexport class ClaudeCodeTranscriptReader {\n\tprivate readonly _cache = new Map<string, CacheEntry>();\n\tprivate readonly _generations = new Map<string, number>();\n\tprivate readonly _listings = new Map<string, ListingCacheEntry>();\n\t/** Transcript file names per project directory — the readdir behind both the plain listing and\n\t * the by-id scan, which sweeps every project directory. */\n\tprivate readonly _names = new Map<string, NameCacheEntry>();\n\t/** Project directories under {@link getProjectsRoot}, re-scanned by every by-id recovery. */\n\tprivate _projectDirs: { dirs: string[]; resolvedAt: number } | undefined;\n\t/** Insertion-ordered LRU — re-inserted on hit, oldest key evicted past {@link summaryCacheLimit}. */\n\tprivate readonly _summaries = new Map<string, SummaryCacheEntry>();\n\t/** Resolved project path per transcript path, bounded like {@link _summaries}. */\n\tprivate readonly _transcriptCwds = new Map<string, string>();\n\tprivate _nextGen = 0;\n\t/** Bumped by {@link invalidateListings}; async listing scans capture it at entry and skip their\n\t * cache write when it moved, so a stale in-flight result can't re-seed a just-cleared cache. */\n\tprivate _listingGen = 0;\n\n\t/** Drops the directory-listing caches (entries, names, project dirs) so the next listing re-reads\n\t * the store. Called when a session ends or is removed: its transcript may have appeared inside\n\t * the TTL window, and a cached listing would hide the new file from the very fetch meant to\n\t * surface it. Summary and cwd caches are keyed by file identity and stay valid, so they're kept.\n\t * The generation bump makes any in-flight listing skip its own cache write — otherwise a scan\n\t * started before the invalidation would re-seed the caches with pre-end results, freshly\n\t * timestamped (same discipline as `resolve`'s per-session generations). */\n\tinvalidateListings(): void {\n\t\tthis._listingGen++;\n\t\tthis._listings.clear();\n\t\tthis._names.clear();\n\t\tthis._projectDirs = undefined;\n\t}\n\n\tasync resolve(sessionId: string, cwd: string | undefined): Promise<TranscriptTitles | undefined> {\n\t\tconst gen = ++this._nextGen;\n\t\tthis._generations.set(sessionId, gen);\n\n\t\tconst cached = this._cache.get(sessionId);\n\t\tconst path = cached?.path ?? (await this.locateTranscript(sessionId, cwd));\n\t\tif (path == null) return undefined;\n\n\t\tlet stats;\n\t\ttry {\n\t\t\tstats = await stat(path);\n\t\t} catch {\n\t\t\t// File disappeared; drop any stale cache entry so a future call retries discovery.\n\t\t\t// Skip the delete if a newer resolve/forget claimed the slot — their state is fresher.\n\t\t\tif (this._generations.get(sessionId) === gen) {\n\t\t\t\tthis._cache.delete(sessionId);\n\t\t\t\tthis._generations.delete(sessionId);\n\t\t\t}\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (stats.mtimeMs === cached?.mtimeMs && stats.size === cached?.size) {\n\t\t\treturn cached.titles;\n\t\t}\n\n\t\t// Truncation or rewrite: stat shrank below our read cursor or mtime moved backward → full re-scan.\n\t\tconst startFromScratch = cached == null || stats.size < cached.nextOffset || stats.mtimeMs < cached.mtimeMs;\n\t\tconst startOffset = startFromScratch ? 0 : cached.nextOffset;\n\t\tconst baseTitles: TranscriptTitles = startFromScratch ? {} : { ...cached.titles };\n\n\t\tconst { titles, consumedEnd } =\n\t\t\tstats.size > startOffset\n\t\t\t\t? await this.readRange(path, startOffset, stats.size, sessionId, baseTitles)\n\t\t\t\t: { titles: baseTitles, consumedEnd: startOffset };\n\n\t\t// If `forget` ran or a newer `resolve` started while we were awaiting, skip the cache write\n\t\t// — the entry we'd be writing reflects stale state.\n\t\tif (this._generations.get(sessionId) !== gen) return titles;\n\n\t\tconst entry: CacheEntry = {\n\t\t\tpath: path,\n\t\t\tmtimeMs: stats.mtimeMs,\n\t\t\tsize: stats.size,\n\t\t\tnextOffset: consumedEnd,\n\t\t\ttitles: titles,\n\t\t};\n\t\tthis._cache.set(sessionId, entry);\n\t\treturn titles;\n\t}\n\n\tforget(sessionId: string): void {\n\t\tthis._cache.delete(sessionId);\n\t\tthis._generations.delete(sessionId);\n\t}\n\n\t/**\n\t * One-shot read for an *ended* (terminal, static) session: extracts the transcript titles plus\n\t * the first and last user prompts. Unlike {@link resolve}, it bypasses the tail cache — a finished\n\t * transcript never grows, so it's read once on demand (when an ended row is opened) and the\n\t * result applied to the session. `last-prompt` entries carry the prompt as of that point; the first\n\t * one bearing a value is the session's opening prompt, the last its most recent. Returns `undefined`\n\t * when no transcript is found (archived/purged) — the caller keeps whatever durable-store fields it\n\t * already has.\n\t *\n\t * Streams the file line-by-line rather than buffering it whole: a finished transcript can run to\n\t * tens of MB, but this keeps only one line resident at a time. A *full* scan (not a head/tail\n\t * window) is required for correctness — when the first turn is large the opening `last-prompt` sits\n\t * past any fixed head window, and a windowed read would then mistake a later prompt for the first,\n\t * poisoning the derived session name.\n\t */\n\tasync resolveEndedDetails(sessionId: string, cwd: string | undefined): Promise<EndedTranscriptDetails | undefined> {\n\t\tconst path = await this.locateTranscript(sessionId, cwd);\n\t\tif (path == null) return undefined;\n\n\t\tlet stats;\n\t\ttry {\n\t\t\tstats = await stat(path);\n\t\t} catch (ex) {\n\t\t\t// A transcript that vanished is terminal; an I/O or permission failure must propagate so\n\t\t\t// the caller retries rather than caching this session as permanently unresolvable.\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\t\tif (stats.size === 0) return undefined;\n\n\t\tconst titles: TranscriptTitles = {};\n\t\tlet firstPrompt: string | undefined;\n\t\tlet lastPrompt: string | undefined;\n\n\t\tconst rl = createInterface({ input: createReadStream(path, { encoding: 'utf8' }), crlfDelay: Infinity });\n\t\ttry {\n\t\t\tfor await (const line of rl) {\n\t\t\t\tapplyTitleLine(line, sessionId, titles);\n\t\t\t\tif (!line.includes('last-prompt')) continue;\n\n\t\t\t\tconst entry = parseSummaryLine(line, sessionId);\n\t\t\t\tif (entry?.type === 'last-prompt' && entry.lastPrompt != null && entry.lastPrompt.length > 0) {\n\t\t\t\t\tfirstPrompt ??= entry.lastPrompt;\n\t\t\t\t\tlastPrompt = entry.lastPrompt;\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\trl.close();\n\t\t}\n\n\t\treturn { titles: titles, firstPrompt: firstPrompt, lastPrompt: lastPrompt };\n\t}\n\n\t/**\n\t * Lists the transcripts of sessions whose working directory is `cwd`, most-recently-active first,\n\t * summarizing until `limit` summarizable transcripts are found — not just the first `limit` on\n\t * disk, since junk transcripts (dropped below) would otherwise starve the result.\n\t * `excludeSessionIds` is skipped before `limit` applies, and excluded entries are also dropped\n\t * from `total` — they're already shown elsewhere (e.g. as live sessions), so a caller's \"N of M\"\n\t * count must not double-count them.\n\t *\n\t * Claude homes a transcript under the directory encoding the cwd from which the session started.\n\t * That directory is authoritative for where `claude --resume <id>` can find the transcript, but\n\t * Claude does not reliably move it when the session later changes worktrees. Callers that have a\n\t * durable session-to-worktree association can recover those moved sessions with\n\t * {@link listSessionsByIds}; this method intentionally remains the cheap legacy directory listing.\n\t *\n\t * Discovery (readdir + stat) covers the whole directory and is cheap; summarizing is not, so it's\n\t * capped. Entries whose summary yields neither a title nor a prompt are dropped — those are aborted\n\t * or empty transcripts with nothing to show or search on.\n\t */\n\tasync listSessions(\n\t\tcwd: string,\n\t\toptions?: { limit?: number; excludeSessionIds?: ReadonlySet<string> },\n\t): Promise<TranscriptSessionListing> {\n\t\tconst dir = await this.resolveProjectDir(cwd);\n\t\tif (dir == null) return { sessions: [], total: 0 };\n\n\t\tconst entries = await this.listEntries(dir);\n\t\tif (entries.length === 0) return { sessions: [], total: 0 };\n\n\t\tconst limit = options?.limit ?? defaultListLimit;\n\t\tconst exclude = options?.excludeSessionIds;\n\t\t// Zero-byte transcripts can never summarize — skip the read entirely and keep them out of\n\t\t// `total`, which otherwise counts sessions no `limit` could ever surface.\n\t\tconst candidates = entries.filter(e => e.size > 0 && exclude?.has(e.sessionId) !== true);\n\t\tconst { sessions, exhausted } = await this.collectSessions(candidates, limit, async entry => {\n\t\t\tconst summary = await this.resolveSummary(entry);\n\t\t\treturn summary != null && hasSummaryContent(summary) ? summary : undefined;\n\t\t});\n\n\t\t// An exhausted scan proved the exact valid count; otherwise unscanned candidates keep\n\t\t// `total` an upper bound so paging knows more MAY exist.\n\t\treturn { sessions: sessions, total: exhausted ? sessions.length : candidates.length };\n\t}\n\n\t/**\n\t * Finds a known set of session ids across every Claude project directory except `excludeCwd`'s.\n\t * This is the recovery path for durable CLI records whose worktree changed after Claude chose the\n\t * transcript's project directory. The caller supplies the association; transcript contents alone\n\t * are deliberately not used to guess which current worktree owns an arbitrary old session.\n\t *\n\t * Each result carries the project path encoded by the directory that actually holds the transcript,\n\t * so resuming it does not repeat discovery from the wrong worktree. Duplicate ids are counted once,\n\t * with the newest transcript winning.\n\t */\n\tasync listSessionsByIds(\n\t\tsessionIds: ReadonlySet<string>,\n\t\toptions?: { limit?: number; excludeCwd?: string },\n\t): Promise<ResumableTranscriptSessionListing> {\n\t\tif (sessionIds.size === 0) return { sessions: [], total: 0 };\n\n\t\tconst excludeDir = await this.resolveProjectDir(options?.excludeCwd);\n\t\t// Same zero-byte drop as `listSessions` — see the rationale there.\n\t\tconst entries = (await this.listEntriesByIds(sessionIds, excludeDir)).filter(e => e.size > 0);\n\t\tif (entries.length === 0) return { sessions: [], total: 0 };\n\n\t\tconst limit = options?.limit ?? defaultListLimit;\n\t\tconst { sessions, exhausted } = await this.collectSessions(entries, limit, async entry => {\n\t\t\tconst summary = await this.resolveSummary(entry);\n\t\t\tif (summary == null || !hasSummaryContent(summary)) return undefined;\n\n\t\t\tconst cwd = await this.resolveTranscriptCwd(entry);\n\t\t\treturn cwd != null ? { ...summary, cwd: cwd } : undefined;\n\t\t});\n\n\t\t// Exhausted scans prove the exact count — see `listSessions`.\n\t\treturn { sessions: sessions, total: exhausted ? sessions.length : entries.length };\n\t}\n\n\t/** Discovers every transcript in `dir`, newest first. The directory also holds one sibling\n\t * subdirectory per session (`<uuid>/subagents`, `<uuid>/tool-results`), so entries must be filtered\n\t * to files — a busy project has roughly as many subdirectories as transcripts. */\n\tprivate async listEntries(dir: string): Promise<TranscriptSessionEntry[]> {\n\t\tconst cached = this._listings.get(dir);\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.entries;\n\n\t\tconst gen = this._listingGen;\n\t\tlet names: string[];\n\t\ttry {\n\t\t\tnames = await this.listTranscriptNames(dir);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst entries = (await this.statEntries(dir, names)).sort((a, b) => b.lastActivityMs - a.lastActivityMs);\n\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._listings.set(dir, { entries: entries, resolvedAt: Date.now() });\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/** Stats `names` in `dir`, dropping the ones that failed — a transcript can vanish (or be\n\t * archived) between the readdir and the stat. Unsorted; callers order as they need.\n\t * Protected (like {@link readSummary}) so tests can interleave against the listing flow. */\n\tprotected async statEntries(dir: string, names: string[]): Promise<TranscriptSessionEntry[]> {\n\t\tconst settled = await Promise.allSettled(\n\t\t\tnames.map(async (name): Promise<TranscriptSessionEntry> => {\n\t\t\t\tconst path = join(dir, name);\n\t\t\t\tconst stats = await stat(path);\n\t\t\t\treturn {\n\t\t\t\t\tsessionId: basename(name, '.jsonl'),\n\t\t\t\t\tpath: path,\n\t\t\t\t\tlastActivityMs: stats.mtimeMs,\n\t\t\t\t\tsize: stats.size,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\n\t\treturn settled.filter(r => r.status === 'fulfilled').map(r => r.value);\n\t}\n\n\t/** The `.jsonl` file names in `dir`, cached for {@link listingCacheTtlMs}. Failures propagate\n\t * uncached: an unreadable directory means different things to different callers, and the next\n\t * call must be free to get a real answer. */\n\tprivate async listTranscriptNames(dir: string): Promise<string[]> {\n\t\tconst cached = this._names.get(dir);\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.names;\n\n\t\tconst gen = this._listingGen;\n\t\tconst dirents = await readdir(dir, { withFileTypes: true });\n\t\tconst names = dirents.filter(d => d.isFile() && d.name.endsWith('.jsonl')).map(d => d.name);\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._names.set(dir, { names: names, resolvedAt: Date.now() });\n\t\t}\n\n\t\treturn names;\n\t}\n\n\t/** Every project directory under the projects root, cached for {@link listingCacheTtlMs} — a\n\t * by-id recovery sweeps all of them, once per queried worktree. */\n\tprivate async listProjectDirs(): Promise<string[]> {\n\t\tconst cached = this._projectDirs;\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.dirs;\n\n\t\tconst gen = this._listingGen;\n\t\tconst root = this.getProjectsRoot();\n\t\tconst dirents = await readdir(root, { withFileTypes: true });\n\t\tconst dirs = dirents.filter(d => d.isDirectory()).map(d => join(root, d.name));\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._projectDirs = { dirs: dirs, resolvedAt: Date.now() };\n\t\t}\n\n\t\treturn dirs;\n\t}\n\n\t/** Scans project directory names once, then stats only requested transcript ids. */\n\tprivate async listEntriesByIds(\n\t\tsessionIds: ReadonlySet<string>,\n\t\texcludeDir: string | undefined,\n\t): Promise<TranscriptSessionEntry[]> {\n\t\tlet idsToFind = sessionIds;\n\t\tif (excludeDir != null) {\n\t\t\ttry {\n\t\t\t\tconst names = await this.listTranscriptNames(excludeDir);\n\t\t\t\tconst idsInExcludedDir = new Set(names.map(name => basename(name, '.jsonl')));\n\t\t\t\tidsToFind = new Set([...sessionIds].filter(id => !idsInExcludedDir.has(id)));\n\t\t\t\tif (idsToFind.size === 0) return [];\n\t\t\t} catch {\n\t\t\t\t// If the exact directory can't be inspected, don't risk returning a duplicate from a\n\t\t\t\t// stale transcript copy elsewhere. The ordinary listing still owns the exact path.\n\t\t\t\treturn [];\n\t\t\t}\n\t\t}\n\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await this.listProjectDirs();\n\t\t} catch (ex) {\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn [];\n\t\t}\n\n\t\tconst settledDirs = await Promise.allSettled(\n\t\t\tdirs.map(async dir => {\n\t\t\t\tif (dir === excludeDir) return [];\n\n\t\t\t\tconst names = (await this.listTranscriptNames(dir)).filter(name =>\n\t\t\t\t\tidsToFind.has(basename(name, '.jsonl')),\n\t\t\t\t);\n\t\t\t\treturn this.statEntries(dir, names);\n\t\t\t}),\n\t\t);\n\n\t\tconst bySessionId = new Map<string, TranscriptSessionEntry>();\n\t\tfor (const settledDir of settledDirs) {\n\t\t\tif (settledDir.status !== 'fulfilled') continue;\n\n\t\t\tfor (const entry of settledDir.value) {\n\t\t\t\tconst existing = bySessionId.get(entry.sessionId);\n\t\t\t\tif (existing == null || entry.lastActivityMs > existing.lastActivityMs) {\n\t\t\t\t\tbySessionId.set(entry.sessionId, entry);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn [...bySessionId.values()].sort((a, b) => b.lastActivityMs - a.lastActivityMs);\n\t}\n\n\t/** `exhausted` reports whether every candidate was scanned — an exhausted scan has proven the\n\t * exact valid count, so callers can report it instead of an upper bound that would keep a\n\t * \"Show More\" affordance alive with nothing left to show. */\n\tprivate async collectSessions<T>(\n\t\tcandidates: readonly TranscriptSessionEntry[],\n\t\tlimit: number,\n\t\tresolve: (entry: TranscriptSessionEntry) => Promise<T | undefined>,\n\t): Promise<{ sessions: T[]; exhausted: boolean }> {\n\t\tconst sessions: T[] = [];\n\t\tconst pushResolved = (settled: PromiseSettledResult<T | undefined>[]): void => {\n\t\t\tfor (const result of settled) {\n\t\t\t\tif (result.status !== 'fulfilled' || result.value == null) continue;\n\n\t\t\t\tsessions.push(result.value);\n\t\t\t}\n\t\t};\n\n\t\t// limit <= 0 means \"no ceiling\" — resolve every candidate.\n\t\tif (limit <= 0) {\n\t\t\tpushResolved(await Promise.allSettled(candidates.map(resolve)));\n\t\t\treturn { sessions: sessions, exhausted: true };\n\t\t}\n\n\t\t// Scans until `limit` results or the candidates run out — a fixed scan ceiling would let a run\n\t\t// of junk transcripts hide every older real session behind it (and the picker then claims there\n\t\t// are none at all). The cost stays bounded: callers pre-drop zero-byte candidates read-free, and\n\t\t// a junk summary is windowed, tiny, and LRU-cached, so the sweep is paid in syscalls, once.\n\t\tlet cursor = 0;\n\t\twhile (sessions.length < limit && cursor < candidates.length) {\n\t\t\t// Batched by remaining need so a hit-rich head stops the scan early instead of resolving\n\t\t\t// every candidate up front.\n\t\t\tconst batch = candidates.slice(cursor, cursor + (limit - sessions.length));\n\t\t\tcursor += batch.length;\n\t\t\tpushResolved(await Promise.allSettled(batch.map(resolve)));\n\t\t}\n\n\t\treturn { sessions: sessions, exhausted: cursor >= candidates.length };\n\t}\n\n\t/** Summarizes one transcript, reusing the cached result while its mtime and size are unchanged. */\n\tprivate async resolveSummary(entry: TranscriptSessionEntry): Promise<TranscriptSessionSummary | undefined> {\n\t\tconst cached = this._summaries.get(entry.sessionId);\n\t\tif (\n\t\t\tcached != null &&\n\t\t\tcached.path === entry.path &&\n\t\t\tcached.mtimeMs === entry.lastActivityMs &&\n\t\t\tcached.size === entry.size\n\t\t) {\n\t\t\t// Refresh recency for the LRU.\n\t\t\tthis._summaries.delete(entry.sessionId);\n\t\t\tthis._summaries.set(entry.sessionId, cached);\n\t\t\treturn cached.summary;\n\t\t}\n\n\t\tlet summary: TranscriptSessionSummary;\n\t\ttry {\n\t\t\tsummary = await this.readSummary(entry);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthis._summaries.set(entry.sessionId, {\n\t\t\tpath: entry.path,\n\t\t\tmtimeMs: entry.lastActivityMs,\n\t\t\tsize: entry.size,\n\t\t\tsummary: summary,\n\t\t});\n\t\tif (this._summaries.size > summaryCacheLimit) {\n\t\t\tconst oldest = this._summaries.keys().next();\n\t\t\tif (!oldest.done) {\n\t\t\t\tthis._summaries.delete(oldest.value);\n\t\t\t}\n\t\t}\n\t\treturn summary;\n\t}\n\n\t/** Recovers the project path represented by the transcript's containing directory. A resolved\n\t * answer is cached: it's a line-scan of the whole transcript, and the directory holding a given\n\t * path never changes. A miss isn't cached — a `cwd` line can still land on append. */\n\tprivate async resolveTranscriptCwd(entry: TranscriptSessionEntry): Promise<string | undefined> {\n\t\tconst cached = this._transcriptCwds.get(entry.path);\n\t\tif (cached != null) return cached;\n\n\t\tconst projectDir = dirname(entry.path);\n\t\tconst projectDirName = basename(projectDir);\n\t\tconst matchesProjectDir = (cwd: string): boolean =>\n\t\t\tencodeProjectDirName(cwd).toLowerCase() === projectDirName.toLowerCase();\n\n\t\ttry {\n\t\t\tconst index = JSON.parse(await readFile(join(projectDir, 'sessions-index.json'), 'utf8')) as {\n\t\t\t\tentries?: { sessionId?: unknown; projectPath?: unknown }[];\n\t\t\t};\n\t\t\tif (Array.isArray(index.entries)) {\n\t\t\t\tconst indexed = index.entries.find(item => item?.sessionId === entry.sessionId);\n\t\t\t\tif (typeof indexed?.projectPath === 'string' && matchesProjectDir(indexed.projectPath)) {\n\t\t\t\t\tthis.rememberTranscriptCwd(entry.path, indexed.projectPath);\n\t\t\t\t\treturn indexed.projectPath;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// The index is optional and can lag or be malformed. The transcript is authoritative below.\n\t\t}\n\n\t\tconst rl = createInterface({ input: createReadStream(entry.path, { encoding: 'utf8' }), crlfDelay: Infinity });\n\t\ttry {\n\t\t\tfor await (const line of rl) {\n\t\t\t\tif (!line.includes('\"cwd\"')) continue;\n\n\t\t\t\tlet parsed: { cwd?: unknown; sessionId?: unknown };\n\t\t\t\ttry {\n\t\t\t\t\tparsed = JSON.parse(line) as { cwd?: unknown; sessionId?: unknown };\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (parsed.sessionId != null && parsed.sessionId !== entry.sessionId) continue;\n\n\t\t\t\tif (typeof parsed.cwd === 'string' && matchesProjectDir(parsed.cwd)) {\n\t\t\t\t\tthis.rememberTranscriptCwd(entry.path, parsed.cwd);\n\t\t\t\t\treturn parsed.cwd;\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\trl.close();\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate rememberTranscriptCwd(path: string, cwd: string): void {\n\t\tthis._transcriptCwds.set(path, cwd);\n\t\tif (this._transcriptCwds.size > summaryCacheLimit) {\n\t\t\tconst oldest = this._transcriptCwds.keys().next();\n\t\t\tif (!oldest.done) {\n\t\t\t\tthis._transcriptCwds.delete(oldest.value);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Reads a summary from the first and last {@link summaryWindowSize} bytes of the transcript.\n\t *\n\t * Deliberately does not reuse `resolve` — that scans from the last-read offset and buffers the whole\n\t * range, which for a cold multi-MB transcript means loading the entire file. Titles are written early\n\t * and re-written as they're refined, while prompts land at the tail, so the two windows recover both\n\t * at a fraction of the bytes. The tail is parsed second so the newest value wins.\n\t */\n\tprotected async readSummary(entry: TranscriptSessionEntry): Promise<TranscriptSessionSummary> {\n\t\tconst titles: TranscriptTitles = {};\n\t\tlet lastPrompt: string | undefined;\n\n\t\tconst apply = (buffer: Buffer, dropPartialFirstLine: boolean): void => {\n\t\t\tconst text = buffer.toString('utf8');\n\t\t\tconst lines = text.split('\\n');\n\t\t\t// A window starting mid-file almost always opens mid-line; that fragment can't be parsed.\n\t\t\tif (dropPartialFirstLine) {\n\t\t\t\tlines.shift();\n\t\t\t}\n\t\t\tfor (const line of lines) {\n\t\t\t\tconst parsed = parseSummaryLine(line, entry.sessionId);\n\t\t\t\tif (parsed == null) continue;\n\n\t\t\t\tapplyTitleEntry(parsed, titles);\n\t\t\t\tif (parsed.lastPrompt != null && parsed.lastPrompt.length > 0) {\n\t\t\t\t\tlastPrompt = parsed.lastPrompt;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tconst handle = await open(entry.path, 'r');\n\t\ttry {\n\t\t\tif (entry.size <= summaryWindowSize * 2) {\n\t\t\t\tconst whole = Buffer.alloc(entry.size);\n\t\t\t\tawait handle.read(whole, 0, entry.size, 0);\n\t\t\t\tapply(whole, false);\n\t\t\t} else {\n\t\t\t\tconst head = Buffer.alloc(summaryWindowSize);\n\t\t\t\tawait handle.read(head, 0, summaryWindowSize, 0);\n\t\t\t\tapply(head, false);\n\n\t\t\t\tconst tail = Buffer.alloc(summaryWindowSize);\n\t\t\t\tawait handle.read(tail, 0, summaryWindowSize, entry.size - summaryWindowSize);\n\t\t\t\tapply(tail, true);\n\t\t\t}\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\n\t\treturn {\n\t\t\t...entry,\n\t\t\ttitles: titles,\n\t\t\tlastPrompt: lastPrompt,\n\t\t};\n\t}\n\n\tprotected getProjectsRoot(): string {\n\t\treturn join(homedir(), '.claude', 'projects');\n\t}\n\n\t/** Resolves the `~/.claude/projects` directory holding the sessions whose working directory is\n\t * `cwd`, or `undefined` when none exists. Falls back to a case-insensitive name match because on\n\t * Windows our\n\t * paths carry a lower-cased drive letter (`normalizePath`) while Claude encodes the OS-native\n\t * `C:\\...` — so the exact name never matches by case. */\n\tprotected async resolveProjectDir(cwd: string | undefined): Promise<string | undefined> {\n\t\tif (cwd == null || cwd.length === 0) return undefined;\n\n\t\tconst root = this.getProjectsRoot();\n\t\tconst encoded = encodeProjectDirName(cwd);\n\n\t\tconst exact = join(root, encoded);\n\t\tif (await directoryExists(exact)) return exact;\n\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await readdir(root);\n\t\t} catch (ex) {\n\t\t\t// Only a missing projects root means \"no transcript\". A transient failure propagates so the\n\t\t\t// caller retries instead of caching a permanent miss.\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst lowered = encoded.toLowerCase();\n\t\tconst match = dirs.find(d => d !== encoded && d.toLowerCase() === lowered);\n\t\treturn match != null ? join(root, match) : undefined;\n\t}\n\n\tprotected async locateTranscript(sessionId: string, cwd: string | undefined): Promise<string | undefined> {\n\t\tconst fileName = `${sessionId}.jsonl`;\n\n\t\tconst dir = await this.resolveProjectDir(cwd);\n\t\tif (dir != null) {\n\t\t\tconst candidate = join(dir, fileName);\n\t\t\tif (await fileExists(candidate)) return candidate;\n\t\t}\n\n\t\t// The recorded cwd drifts whenever the agent `cd`s, so it can encode to a directory the session\n\t\t// doesn't actually live in — scan every project for the file as a last resort.\n\t\tconst root = this.getProjectsRoot();\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await readdir(root);\n\t\t} catch (ex) {\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst candidates = await Promise.all(\n\t\t\tdirs.map(async d => {\n\t\t\t\tconst candidate = join(root, d, fileName);\n\t\t\t\treturn (await fileExists(candidate)) ? candidate : undefined;\n\t\t\t}),\n\t\t);\n\t\treturn candidates.find(c => c != null);\n\t}\n\n\t/**\n\t * Reads bytes `[start, end)` from `path` and applies any title entries it finds to a copy of\n\t * `baseTitles`. Returns the merged titles and the byte position immediately after the last\n\t * newline observed — partial trailing lines (writer hasn't flushed `\\n` yet) are intentionally\n\t * left unconsumed so they're re-read on the next pass.\n\t */\n\tprotected async readRange(\n\t\tpath: string,\n\t\tstart: number,\n\t\tend: number,\n\t\tsessionId: string,\n\t\tbaseTitles: TranscriptTitles,\n\t): Promise<{ titles: TranscriptTitles; consumedEnd: number }> {\n\t\tconst buffer = await readSlice(path, start, end);\n\t\tconst titles: TranscriptTitles = { ...baseTitles };\n\n\t\tlet lastNewlineEnd = start;\n\t\tlet lineStart = 0;\n\t\tfor (let i = 0; i < buffer.length; i++) {\n\t\t\tif (buffer[i] !== 0x0a) continue;\n\n\t\t\t// Slice the line (trim a trailing \\r for CRLF files), then advance the cursor past the \\n\n\t\t\t// regardless of whether the line yielded anything — we've fully observed those bytes.\n\t\t\tconst lineEnd = i > lineStart && buffer[i - 1] === 0x0d ? i - 1 : i;\n\t\t\tif (lineEnd > lineStart) {\n\t\t\t\tconst line = buffer.toString('utf8', lineStart, lineEnd);\n\t\t\t\tapplyTitleLine(line, sessionId, titles);\n\t\t\t}\n\t\t\tlineStart = i + 1;\n\t\t\tlastNewlineEnd = start + i + 1;\n\t\t}\n\n\t\treturn { titles: titles, consumedEnd: lastNewlineEnd };\n\t}\n}\n\nfunction applyTitleLine(line: string, sessionId: string, titles: TranscriptTitles): void {\n\tif (!isLikelyTitleLine(line)) return;\n\n\tlet entry: TitleEntry;\n\ttry {\n\t\tentry = JSON.parse(line) as TitleEntry;\n\t} catch {\n\t\treturn;\n\t}\n\tif (entry.sessionId != null && entry.sessionId !== sessionId) return;\n\n\tapplyTitleEntry(entry, titles);\n}\n\n/** Parses one transcript line for {@link ClaudeCodeTranscriptReader.readSummary}, rejecting entries\n * that belong to another session. Can't use the `-title` prefilter that {@link isLikelyTitleLine}\n * applies: `last-prompt` matches neither of its markers. */\nfunction parseSummaryLine(line: string, sessionId: string): TitleEntry | undefined {\n\tif (line.length === 0 || !line.includes('\"type\"')) return undefined;\n\n\tlet entry: TitleEntry;\n\ttry {\n\t\tentry = JSON.parse(line) as TitleEntry;\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (entry.sessionId != null && entry.sessionId !== sessionId) return undefined;\n\n\treturn entry;\n}\n\nfunction applyTitleEntry(entry: TitleEntry, titles: TranscriptTitles): void {\n\tswitch (entry.type) {\n\t\tcase 'custom-title':\n\t\t\tif (typeof entry.customTitle === 'string' && entry.customTitle.length > 0) {\n\t\t\t\ttitles.custom = entry.customTitle;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'ai-title':\n\t\t\tif (typeof entry.aiTitle === 'string' && entry.aiTitle.length > 0) {\n\t\t\t\ttitles.ai = entry.aiTitle;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'agent-name':\n\t\t\tif (typeof entry.agentName === 'string' && entry.agentName.length > 0) {\n\t\t\t\ttitles.agent = entry.agentName;\n\t\t\t}\n\t\t\tbreak;\n\t}\n}\n\n/** Encodes a working directory into its `~/.claude/projects` directory name. Claude replaces every\n * non-alphanumeric character — not just separators — preserving runs (`/home/e/.claude` →\n * `-home-e--claude`) and case. Separators-only would leave the dot in our own worktree convention\n * (`<repo>.worktrees/<name>`) intact and compute a directory that never exists. */\nexport function encodeProjectDirName(cwd: string): string {\n\treturn cwd.replace(/[^a-zA-Z0-9]/g, '-');\n}\n\nfunction isLikelyTitleLine(line: string): boolean {\n\tif (!line.includes('\"type\"')) return false;\n\treturn line.includes('-title') || line.includes('agent-name');\n}\n\n/** A transcript with no title and no prompt is an aborted or empty session — nothing to name it by,\n * nothing to search it on. */\nfunction hasSummaryContent(summary: TranscriptSessionSummary): boolean {\n\tconst { custom, ai, agent } = summary.titles;\n\treturn custom != null || ai != null || agent != null || summary.lastPrompt != null;\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n\ttry {\n\t\tconst stats = await stat(path);\n\t\treturn stats.isFile();\n\t} catch (ex) {\n\t\t// See {@link isMissingEntry}: only a genuine absence is an answer.\n\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\treturn false;\n\t}\n}\n\nasync function directoryExists(path: string): Promise<boolean> {\n\ttry {\n\t\tconst stats = await stat(path);\n\t\treturn stats.isDirectory();\n\t} catch (ex) {\n\t\t// \"Not there\" is an answer; anything else (EACCES, EIO, a mount hiccup) is a transient\n\t\t// failure the caller must be able to tell apart — see {@link isMissingEntry}.\n\t\tif (isMissingEntry(ex)) return false;\n\n\t\tthrow ex;\n\t}\n}\n\n/** True for the \"path simply isn't there\" errno codes, as opposed to a transient I/O or permission\n * failure. Callers cache a genuine absence as terminal, so a transient failure must not look like\n * one — it would pin a session to its fallback name until the window reloads. */\nfunction isMissingEntry(ex: unknown): boolean {\n\tconst code = (ex as NodeJS.ErrnoException | undefined)?.code;\n\treturn code === 'ENOENT' || code === 'ENOTDIR';\n}\n\nfunction readSlice(path: string, start: number, end: number): Promise<Buffer> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst chunks: Buffer[] = [];\n\t\tconst stream = createReadStream(path, { start: start, end: end - 1 });\n\t\tstream.on('data', chunk => chunks.push(chunk as Buffer));\n\t\tstream.on('end', () => resolve(Buffer.concat(chunks)));\n\t\tstream.on('error', reject);\n\t});\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CancellationError, isCancellationError } from "../../../utils/cancellation.js";
|
|
2
2
|
import { Logger as _logger, getLoggableName } from "../../../utils/logger.js";
|
|
3
3
|
import { maybeStartScopedLogger } from "../../../utils/logger.scoped.js";
|
|
4
|
-
import _usingCtx from "../../../_virtual/@oxc-project+runtime/helpers/esm/usingCtx.js";
|
|
5
4
|
import { uuid } from "../../../utils/crypto.js";
|
|
5
|
+
import _usingCtx from "../../../_virtual/@oxc-project+runtime/helpers/esm/usingCtx.js";
|
|
6
6
|
import { AIError } from "../errors.js";
|
|
7
7
|
import { getActionName, getReducedMaxInputTokens, getValidatedTemperature } from "../utils/ai.utils.js";
|
|
8
8
|
import { isResponseFormatRejected, rememberResponseFormatRejection } from "./responseFormatCache.js";
|
|
@@ -5,9 +5,9 @@ import { trace } from "../../../../utils/decorators/log.js";
|
|
|
5
5
|
import { PromiseCache } from "../../../../utils/promiseCache.js";
|
|
6
6
|
import __decorate from "../../../../_virtual/@oxc-project+runtime/helpers/esm/decorate.js";
|
|
7
7
|
import { AuthenticationError, RequestClientError, RequestNotFoundError } from "../../../../git/errors.js";
|
|
8
|
+
import { sha256, uuid } from "../../../../utils/crypto.js";
|
|
8
9
|
import { maybeStopWatch } from "../../../../utils/stopwatch.js";
|
|
9
10
|
import { base64 } from "../../../../utils/base64.js";
|
|
10
|
-
import { sha256, uuid } from "../../../../utils/crypto.js";
|
|
11
11
|
import { effectiveIssueSort } from "../../../../git/utils/issue.utils.js";
|
|
12
12
|
import { ProviderFetchError, isRateLimitResponse, toRateLimitError } from "../../errors.js";
|
|
13
13
|
import { azureWorkItemSearchResultLimit, parseAzureWorkItemSearchCursor, toAzureSearchPageSize, toAzureWorkItemSearchCursorKey, toAzureWorkItemSearchWiql } from "./search.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IssueSorting } from "../../../git/models/issue.js";
|
|
2
|
+
import "../providerFilters.js";
|
|
2
3
|
//#region ../plus/integrations/src/providers/issueSorts.d.ts
|
|
3
4
|
/** GitHub and GHE: one `search` channel serves all three issue reads, so all three order the same way. */
|
|
4
5
|
export declare const githubIssueSorts: IssueSorting[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PullRequest, PullRequestFilter, PullRequestStateFilter } from "../../../git/models/pullRequest.js";
|
|
2
2
|
import { IntegrationIds } from "../constants.js";
|
|
3
3
|
import { ProviderWarning } from "../results.js";
|
|
4
|
+
import "../providerFilters.js";
|
|
4
5
|
import { ProviderPullRequest, ProviderReposInput, ProviderRepository } from "../providers/models.js";
|
|
5
6
|
import { GitHostIntegration } from "../models/gitHostIntegration.js";
|
|
6
7
|
import { IntegrationResult } from "../models/integration.js";
|
|
@@ -2,10 +2,10 @@ import { getScopedLogger, maybeStartScopedLogger } from "../../logger.scoped.js"
|
|
|
2
2
|
import { normalizePath } from "../../path.js";
|
|
3
3
|
import { isWindows } from "./platform.js";
|
|
4
4
|
import { join } from "path";
|
|
5
|
-
import { exec, execFile, spawn } from "child_process";
|
|
6
|
-
import { access, constants } from "fs";
|
|
7
5
|
import { stat } from "fs/promises";
|
|
8
6
|
import * as process from "process";
|
|
7
|
+
import { exec, execFile, spawn } from "child_process";
|
|
8
|
+
import { access, constants } from "fs";
|
|
9
9
|
//#region ../utils/src/env/node/exec.ts
|
|
10
10
|
var RunError = class extends Error {
|
|
11
11
|
cmd;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitkraken/core-gitlens",
|
|
3
3
|
"description": "GitLens core — shared Git / AI / GitHub primitives for internal GitKraken consumption",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.6",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "GitKraken",
|