@gitkraken/core-gitlens 0.12.0 → 0.13.0
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 +57 -1
- package/dist/git/cache.d.ts +84 -29
- package/dist/git/cache.js +188 -54
- package/dist/git/cache.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.js +4 -0
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/branch.d.ts +15 -2
- package/dist/git/models/branch.js +27 -5
- package/dist/git/models/branch.js.map +1 -1
- package/dist/git/models/status.js +1 -1
- package/dist/git/models/worktree.d.ts +4 -1
- package/dist/git/models/worktree.js +4 -2
- package/dist/git/models/worktree.js.map +1 -1
- package/dist/git/providers/branches.d.ts +73 -8
- package/dist/git/providers/pausedOperations.d.ts +6 -0
- package/dist/git/providers/provider.d.ts +34 -2
- package/dist/git/providers/status.d.ts +8 -2
- package/dist/git/remotes/azure-devops.js +61 -53
- package/dist/git/remotes/azure-devops.js.map +1 -1
- package/dist/git/service.d.ts +8 -5
- package/dist/git/service.js +16 -8
- package/dist/git/service.js.map +1 -1
- package/dist/git/utils/branch.utils.d.ts +8 -1
- package/dist/git/utils/branch.utils.js +30 -1
- package/dist/git/utils/branch.utils.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +13 -2
- package/dist/git/watching/changeEvent.js +17 -3
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/gitDirPoller.d.ts +111 -0
- package/dist/git/watching/gitDirPoller.js +266 -0
- package/dist/git/watching/gitDirPoller.js.map +1 -0
- package/dist/git/watching/provider.d.ts +16 -0
- package/dist/git/watching/watchGroup.d.ts +40 -4
- package/dist/git/watching/watchGroup.js +143 -17
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +70 -1
- package/dist/git/watching/watchService.js +158 -13
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +44 -0
- package/dist/git/watching/watchSession.js +91 -6
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.d.ts +21 -0
- package/dist/git/watching/watcherPatterns.js +67 -1
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +34 -7
- package/dist/git/watching/worktreeSummary.js +39 -12
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +34 -2
- package/dist/git-cli/cliGitProvider.js +119 -17
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/commandClassifier.d.ts +15 -0
- package/dist/git-cli/exec/commandClassifier.js +48 -2
- package/dist/git-cli/exec/commandClassifier.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +15 -3
- package/dist/git-cli/exec/git.js +38 -17
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +2 -0
- package/dist/git-cli/exec/gitQueue.js +2 -1
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/exec/indexLock.d.ts +38 -0
- package/dist/git-cli/exec/indexLock.js +150 -0
- package/dist/git-cli/exec/indexLock.js.map +1 -0
- package/dist/git-cli/parsers/worktreeParser.js +42 -2
- package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +109 -10
- package/dist/git-cli/providers/branches.js +624 -131
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +9 -5
- package/dist/git-cli/providers/config.js +60 -27
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +17 -0
- package/dist/git-cli/providers/operations.js +153 -96
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +8 -0
- package/dist/git-cli/providers/pausedOperations.js +60 -40
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.js +11 -8
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/remotes.js +19 -10
- package/dist/git-cli/providers/remotes.js.map +1 -1
- package/dist/git-cli/providers/staging.js +40 -15
- package/dist/git-cli/providers/staging.js.map +1 -1
- package/dist/git-cli/providers/stash.d.ts +2 -0
- package/dist/git-cli/providers/stash.js +33 -18
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/status.d.ts +3 -1
- package/dist/git-cli/providers/status.js +38 -20
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/git-cli/providers/tags.js +6 -4
- package/dist/git-cli/providers/tags.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +1 -0
- package/dist/git-cli/providers/worktrees.js +60 -31
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.d.ts +6 -0
- package/dist/git-cli/service.js +5 -2
- package/dist/git-cli/service.js.map +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/git-github/providers/github/status.d.ts +3 -1
- package/dist/plus/git-github/providers/github/status.js +1 -1
- package/dist/plus/git-github/providers/github/status.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +13 -2
- package/dist/plus/integrations/collectionMetadata.js +14 -2
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +2 -2
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +1 -0
- package/dist/plus/integrations/integrationService.js +1 -1
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +1 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +41 -5
- package/dist/plus/integrations/models/gitHostIntegration.js +106 -29
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +17 -4
- package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
- package/dist/plus/integrations/providers/apiClients.js +7 -1
- package/dist/plus/integrations/providers/apiClients.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +5 -4
- package/dist/plus/integrations/providers/azure/azure.js +37 -77
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.utils.js +20 -17
- package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +2 -2
- package/dist/plus/integrations/providers/azure/models.js +4 -4
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +18 -14
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +69 -17
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +44 -0
- package/dist/plus/integrations/providers/azureDevOps.js +108 -17
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +45 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +121 -6
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/jira-server.js +18 -14
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.js +18 -14
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +1 -2
- package/dist/plus/integrations/providers/models.js +9 -2
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +7 -17
- package/dist/plus/integrations/providers/providersApi.js +85 -78
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.d.ts +22 -0
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js +69 -9
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js +8 -2
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/reads/filters.d.ts +3 -3
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +2 -1
- package/dist/plus/integrations/reads/issueBatch.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +23 -14
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +2 -1
- package/dist/plus/integrations/reads/issues.js +18 -3
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequestBatch.js +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +12 -3
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +11 -4
- package/dist/plus/integrations/reads/warnings.js +15 -5
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +36 -2
- package/dist/plus/integrations/results.js +33 -13
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/utils/env/node/exec.js +2 -2
- package/dist/utils/promiseCache.d.ts +10 -0
- package/dist/utils/promiseCache.js +17 -2
- package/dist/utils/promiseCache.js.map +1 -1
- package/docs/integrations.md +52 -20
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { basename, dirname, join } from "path";
|
|
2
|
-
import {
|
|
3
|
-
import { open, readFile, readdir as readdir$1, stat } from "fs/promises";
|
|
2
|
+
import { open, readFile, readdir, stat } from "fs/promises";
|
|
4
3
|
import { homedir } from "os";
|
|
4
|
+
import { createReadStream } from "fs";
|
|
5
5
|
import { createInterface } from "readline";
|
|
6
6
|
//#region ../plus/agents/src/providers/claudeCodeTranscript.ts
|
|
7
7
|
/** Bytes read from each end of a transcript when summarizing. Titles and prompts cluster at both
|
|
@@ -269,7 +269,7 @@ var ClaudeCodeTranscriptReader = class {
|
|
|
269
269
|
const cached = this._names.get(dir);
|
|
270
270
|
if (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.names;
|
|
271
271
|
const gen = this._listingGen;
|
|
272
|
-
const names = (await readdir
|
|
272
|
+
const names = (await readdir(dir, { withFileTypes: true })).filter((d) => d.isFile() && d.name.endsWith(".jsonl")).map((d) => d.name);
|
|
273
273
|
if (gen === this._listingGen) this._names.set(dir, {
|
|
274
274
|
names,
|
|
275
275
|
resolvedAt: Date.now()
|
|
@@ -283,7 +283,7 @@ var ClaudeCodeTranscriptReader = class {
|
|
|
283
283
|
if (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.dirs;
|
|
284
284
|
const gen = this._listingGen;
|
|
285
285
|
const root = this.getProjectsRoot();
|
|
286
|
-
const dirs = (await readdir
|
|
286
|
+
const dirs = (await readdir(root, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => join(root, d.name));
|
|
287
287
|
if (gen === this._listingGen) this._projectDirs = {
|
|
288
288
|
dirs,
|
|
289
289
|
resolvedAt: Date.now()
|
|
@@ -487,7 +487,7 @@ var ClaudeCodeTranscriptReader = class {
|
|
|
487
487
|
if (await directoryExists(exact)) return exact;
|
|
488
488
|
let dirs;
|
|
489
489
|
try {
|
|
490
|
-
dirs = await readdir
|
|
490
|
+
dirs = await readdir(root);
|
|
491
491
|
} catch (ex) {
|
|
492
492
|
if (!isMissingEntry(ex)) throw ex;
|
|
493
493
|
return;
|
|
@@ -506,7 +506,7 @@ var ClaudeCodeTranscriptReader = class {
|
|
|
506
506
|
const root = this.getProjectsRoot();
|
|
507
507
|
let dirs;
|
|
508
508
|
try {
|
|
509
|
-
dirs = await readdir
|
|
509
|
+
dirs = await readdir(root);
|
|
510
510
|
} catch (ex) {
|
|
511
511
|
if (!isMissingEntry(ex)) throw ex;
|
|
512
512
|
return;
|
|
@@ -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":";;;;;;;;AAsFA,MAAM,oBAAoB;;;AAG1B,MAAM,oBAAoB;;;;;;AAM1B,MAAM,oBAAoB;AAC1B,MAAM,mBAAmB;;;;;;;;;;;;;AAczB,IAAa,6BAAb,MAAwC;CACvC,yBAA0B,IAAI,IAAwB;CACtD,+BAAgC,IAAI,IAAoB;CACxD,4BAA6B,IAAI,IAA+B;;;CAGhE,yBAA0B,IAAI,IAA4B;;CAE1D;;CAEA,6BAA8B,IAAI,IAA+B;;CAEjE,kCAAmC,IAAI,IAAoB;CAC3D,WAAmB;;;CAGnB,cAAsB;;;;;;;;CAStB,qBAA2B;EAC1B,KAAK;EACL,KAAK,UAAU,MAAM;EACrB,KAAK,OAAO,MAAM;EAClB,KAAK,eAAe,KAAA;CACrB;CAEA,MAAM,QAAQ,WAAmB,KAAgE;EAChG,MAAM,MAAM,EAAE,KAAK;EACnB,KAAK,aAAa,IAAI,WAAW,GAAG;EAEpC,MAAM,SAAS,KAAK,OAAO,IAAI,SAAS;EACxC,MAAM,OAAO,QAAQ,QAAS,MAAM,KAAK,iBAAiB,WAAW,GAAG;EACxE,IAAI,QAAQ,MAAM,OAAO,KAAA;EAEzB,IAAI;EACJ,IAAI;GACH,QAAQ,MAAM,KAAK,IAAI;EACxB,QAAQ;GAGP,IAAI,KAAK,aAAa,IAAI,SAAS,MAAM,KAAK;IAC7C,KAAK,OAAO,OAAO,SAAS;IAC5B,KAAK,aAAa,OAAO,SAAS;GACnC;GACA;EACD;EAEA,IAAI,MAAM,YAAY,QAAQ,WAAW,MAAM,SAAS,QAAQ,MAC/D,OAAO,OAAO;EAIf,MAAM,mBAAmB,UAAU,QAAQ,MAAM,OAAO,OAAO,cAAc,MAAM,UAAU,OAAO;EACpG,MAAM,cAAc,mBAAmB,IAAI,OAAO;EAClD,MAAM,aAA+B,mBAAmB,CAAC,IAAI,EAAE,GAAG,OAAO,OAAO;EAEhF,MAAM,EAAE,QAAQ,gBACf,MAAM,OAAO,cACV,MAAM,KAAK,UAAU,MAAM,aAAa,MAAM,MAAM,WAAW,UAAU,IACzE;GAAE,QAAQ;GAAY,aAAa;EAAY;EAInD,IAAI,KAAK,aAAa,IAAI,SAAS,MAAM,KAAK,OAAO;EAErD,MAAM,QAAoB;GACnB;GACN,SAAS,MAAM;GACf,MAAM,MAAM;GACZ,YAAY;GACJ;EACT;EACA,KAAK,OAAO,IAAI,WAAW,KAAK;EAChC,OAAO;CACR;CAEA,OAAO,WAAyB;EAC/B,KAAK,OAAO,OAAO,SAAS;EAC5B,KAAK,aAAa,OAAO,SAAS;CACnC;;;;;;;;;;;;;;;;CAiBA,MAAM,oBAAoB,WAAmB,KAAsE;EAClH,MAAM,OAAO,MAAM,KAAK,iBAAiB,WAAW,GAAG;EACvD,IAAI,QAAQ,MAAM,OAAO,KAAA;EAEzB,IAAI;EACJ,IAAI;GACH,QAAQ,MAAM,KAAK,IAAI;EACxB,SAAS,IAAI;GAGZ,IAAI,CAAC,eAAe,EAAE,GAAG,MAAM;GAE/B;EACD;EACA,IAAI,MAAM,SAAS,GAAG,OAAO,KAAA;EAE7B,MAAM,SAA2B,CAAC;EAClC,IAAI;EACJ,IAAI;EAEJ,MAAM,KAAK,gBAAgB;GAAE,OAAO,iBAAiB,MAAM,EAAE,UAAU,OAAO,CAAC;GAAG,WAAW;EAAS,CAAC;EACvG,IAAI;GACH,WAAW,MAAM,QAAQ,IAAI;IAC5B,eAAe,MAAM,WAAW,MAAM;IACtC,IAAI,CAAC,KAAK,SAAS,aAAa,GAAG;IAEnC,MAAM,QAAQ,iBAAiB,MAAM,SAAS;IAC9C,IAAI,OAAO,SAAS,iBAAiB,MAAM,cAAc,QAAQ,MAAM,WAAW,SAAS,GAAG;KAC7F,gBAAgB,MAAM;KACtB,aAAa,MAAM;IACpB;GACD;EACD,UAAU;GACT,GAAG,MAAM;EACV;EAEA,OAAO;GAAU;GAAqB;GAAyB;EAAW;CAC3E;;;;;;;;;;;;;;;;;;;CAoBA,MAAM,aACL,KACA,SACoC;EACpC,MAAM,MAAM,MAAM,KAAK,kBAAkB,GAAG;EAC5C,IAAI,OAAO,MAAM,OAAO;GAAE,UAAU,CAAC;GAAG,OAAO;EAAE;EAEjD,MAAM,UAAU,MAAM,KAAK,YAAY,GAAG;EAC1C,IAAI,QAAQ,WAAW,GAAG,OAAO;GAAE,UAAU,CAAC;GAAG,OAAO;EAAE;EAE1D,MAAM,QAAQ,SAAS,SAAS;EAChC,MAAM,UAAU,SAAS;EAGzB,MAAM,aAAa,QAAQ,QAAO,MAAK,EAAE,OAAO,KAAK,SAAS,IAAI,EAAE,SAAS,MAAM,IAAI;EACvF,MAAM,EAAE,UAAU,cAAc,MAAM,KAAK,gBAAgB,YAAY,OAAO,OAAM,UAAS;GAC5F,MAAM,UAAU,MAAM,KAAK,eAAe,KAAK;GAC/C,OAAO,WAAW,QAAQ,kBAAkB,OAAO,IAAI,UAAU,KAAA;EAClE,CAAC;EAID,OAAO;GAAY;GAAU,OAAO,YAAY,SAAS,SAAS,WAAW;EAAO;CACrF;;;;;;;;;;;CAYA,MAAM,kBACL,YACA,SAC6C;EAC7C,IAAI,WAAW,SAAS,GAAG,OAAO;GAAE,UAAU,CAAC;GAAG,OAAO;EAAE;EAE3D,MAAM,aAAa,MAAM,KAAK,kBAAkB,SAAS,UAAU;EAEnE,MAAM,WAAW,MAAM,KAAK,iBAAiB,YAAY,UAAU,EAAA,CAAG,QAAO,MAAK,EAAE,OAAO,CAAC;EAC5F,IAAI,QAAQ,WAAW,GAAG,OAAO;GAAE,UAAU,CAAC;GAAG,OAAO;EAAE;EAE1D,MAAM,QAAQ,SAAS,SAAS;EAChC,MAAM,EAAE,UAAU,cAAc,MAAM,KAAK,gBAAgB,SAAS,OAAO,OAAM,UAAS;GACzF,MAAM,UAAU,MAAM,KAAK,eAAe,KAAK;GAC/C,IAAI,WAAW,QAAQ,CAAC,kBAAkB,OAAO,GAAG,OAAO,KAAA;GAE3D,MAAM,MAAM,MAAM,KAAK,qBAAqB,KAAK;GACjD,OAAO,OAAO,OAAO;IAAE,GAAG;IAAc;GAAI,IAAI,KAAA;EACjD,CAAC;EAGD,OAAO;GAAY;GAAU,OAAO,YAAY,SAAS,SAAS,QAAQ;EAAO;CAClF;;;;CAKA,MAAc,YAAY,KAAgD;EACzE,MAAM,SAAS,KAAK,UAAU,IAAI,GAAG;EACrC,IAAI,UAAU,QAAQ,KAAK,IAAI,IAAI,OAAO,aAAa,mBAAmB,OAAO,OAAO;EAExF,MAAM,MAAM,KAAK;EACjB,IAAI;EACJ,IAAI;GACH,QAAQ,MAAM,KAAK,oBAAoB,GAAG;EAC3C,QAAQ;GACP,OAAO,CAAC;EACT;EAEA,MAAM,WAAW,MAAM,KAAK,YAAY,KAAK,KAAK,EAAA,CAAG,MAAM,GAAG,MAAM,EAAE,iBAAiB,EAAE,cAAc;EAEvG,IAAI,QAAQ,KAAK,aAChB,KAAK,UAAU,IAAI,KAAK;GAAW;GAAS,YAAY,KAAK,IAAI;EAAE,CAAC;EAGrE,OAAO;CACR;;;;CAKA,MAAgB,YAAY,KAAa,OAAoD;EAc5F,QAAO,MAbe,QAAQ,WAC7B,MAAM,IAAI,OAAO,SAA0C;GAC1D,MAAM,OAAO,KAAK,KAAK,IAAI;GAC3B,MAAM,QAAQ,MAAM,KAAK,IAAI;GAC7B,OAAO;IACN,WAAW,SAAS,MAAM,QAAQ;IAC5B;IACN,gBAAgB,MAAM;IACtB,MAAM,MAAM;GACb;EACD,CAAC,CACF,EAAA,CAEe,QAAO,MAAK,EAAE,WAAW,WAAW,CAAC,CAAC,KAAI,MAAK,EAAE,KAAK;CACtE;;;;CAKA,MAAc,oBAAoB,KAAgC;EACjE,MAAM,SAAS,KAAK,OAAO,IAAI,GAAG;EAClC,IAAI,UAAU,QAAQ,KAAK,IAAI,IAAI,OAAO,aAAa,mBAAmB,OAAO,OAAO;EAExF,MAAM,MAAM,KAAK;EAEjB,MAAM,SAAQ,MADQ,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC,EAAA,CACpC,QAAO,MAAK,EAAE,OAAO,KAAK,EAAE,KAAK,SAAS,QAAQ,CAAC,CAAC,CAAC,KAAI,MAAK,EAAE,IAAI;EAC1F,IAAI,QAAQ,KAAK,aAChB,KAAK,OAAO,IAAI,KAAK;GAAS;GAAO,YAAY,KAAK,IAAI;EAAE,CAAC;EAG9D,OAAO;CACR;;;CAIA,MAAc,kBAAqC;EAClD,MAAM,SAAS,KAAK;EACpB,IAAI,UAAU,QAAQ,KAAK,IAAI,IAAI,OAAO,aAAa,mBAAmB,OAAO,OAAO;EAExF,MAAM,MAAM,KAAK;EACjB,MAAM,OAAO,KAAK,gBAAgB;EAElC,MAAM,QAAO,MADS,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC,EAAA,CACtC,QAAO,MAAK,EAAE,YAAY,CAAC,CAAC,CAAC,KAAI,MAAK,KAAK,MAAM,EAAE,IAAI,CAAC;EAC7E,IAAI,QAAQ,KAAK,aAChB,KAAK,eAAe;GAAQ;GAAM,YAAY,KAAK,IAAI;EAAE;EAG1D,OAAO;CACR;;CAGA,MAAc,iBACb,YACA,YACoC;EACpC,IAAI,YAAY;EAChB,IAAI,cAAc,MACjB,IAAI;GACH,MAAM,QAAQ,MAAM,KAAK,oBAAoB,UAAU;GACvD,MAAM,mBAAmB,IAAI,IAAI,MAAM,KAAI,SAAQ,SAAS,MAAM,QAAQ,CAAC,CAAC;GAC5E,YAAY,IAAI,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,QAAO,OAAM,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;GAC3E,IAAI,UAAU,SAAS,GAAG,OAAO,CAAC;EACnC,QAAQ;GAGP,OAAO,CAAC;EACT;EAGD,IAAI;EACJ,IAAI;GACH,OAAO,MAAM,KAAK,gBAAgB;EACnC,SAAS,IAAI;GACZ,IAAI,CAAC,eAAe,EAAE,GAAG,MAAM;GAE/B,OAAO,CAAC;EACT;EAEA,MAAM,cAAc,MAAM,QAAQ,WACjC,KAAK,IAAI,OAAM,QAAO;GACrB,IAAI,QAAQ,YAAY,OAAO,CAAC;GAEhC,MAAM,SAAS,MAAM,KAAK,oBAAoB,GAAG,EAAA,CAAG,QAAO,SAC1D,UAAU,IAAI,SAAS,MAAM,QAAQ,CAAC,CACvC;GACA,OAAO,KAAK,YAAY,KAAK,KAAK;EACnC,CAAC,CACF;EAEA,MAAM,8BAAc,IAAI,IAAoC;EAC5D,KAAK,MAAM,cAAc,aAAa;GACrC,IAAI,WAAW,WAAW,aAAa;GAEvC,KAAK,MAAM,SAAS,WAAW,OAAO;IACrC,MAAM,WAAW,YAAY,IAAI,MAAM,SAAS;IAChD,IAAI,YAAY,QAAQ,MAAM,iBAAiB,SAAS,gBACvD,YAAY,IAAI,MAAM,WAAW,KAAK;GAExC;EACD;EAEA,OAAO,CAAC,GAAG,YAAY,OAAO,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,iBAAiB,EAAE,cAAc;CACpF;;;;CAKA,MAAc,gBACb,YACA,OACA,SACiD;EACjD,MAAM,WAAgB,CAAC;EACvB,MAAM,gBAAgB,YAAyD;GAC9E,KAAK,MAAM,UAAU,SAAS;IAC7B,IAAI,OAAO,WAAW,eAAe,OAAO,SAAS,MAAM;IAE3D,SAAS,KAAK,OAAO,KAAK;GAC3B;EACD;EAGA,IAAI,SAAS,GAAG;GACf,aAAa,MAAM,QAAQ,WAAW,WAAW,IAAI,OAAO,CAAC,CAAC;GAC9D,OAAO;IAAY;IAAU,WAAW;GAAK;EAC9C;EAMA,IAAI,SAAS;EACb,OAAO,SAAS,SAAS,SAAS,SAAS,WAAW,QAAQ;GAG7D,MAAM,QAAQ,WAAW,MAAM,QAAQ,UAAU,QAAQ,SAAS,OAAO;GACzE,UAAU,MAAM;GAChB,aAAa,MAAM,QAAQ,WAAW,MAAM,IAAI,OAAO,CAAC,CAAC;EAC1D;EAEA,OAAO;GAAY;GAAU,WAAW,UAAU,WAAW;EAAO;CACrE;;CAGA,MAAc,eAAe,OAA8E;EAC1G,MAAM,SAAS,KAAK,WAAW,IAAI,MAAM,SAAS;EAClD,IACC,UAAU,QACV,OAAO,SAAS,MAAM,QACtB,OAAO,YAAY,MAAM,kBACzB,OAAO,SAAS,MAAM,MACrB;GAED,KAAK,WAAW,OAAO,MAAM,SAAS;GACtC,KAAK,WAAW,IAAI,MAAM,WAAW,MAAM;GAC3C,OAAO,OAAO;EACf;EAEA,IAAI;EACJ,IAAI;GACH,UAAU,MAAM,KAAK,YAAY,KAAK;EACvC,QAAQ;GACP;EACD;EAEA,KAAK,WAAW,IAAI,MAAM,WAAW;GACpC,MAAM,MAAM;GACZ,SAAS,MAAM;GACf,MAAM,MAAM;GACH;EACV,CAAC;EACD,IAAI,KAAK,WAAW,OAAO,mBAAmB;GAC7C,MAAM,SAAS,KAAK,WAAW,KAAK,CAAC,CAAC,KAAK;GAC3C,IAAI,CAAC,OAAO,MACX,KAAK,WAAW,OAAO,OAAO,KAAK;EAErC;EACA,OAAO;CACR;;;;CAKA,MAAc,qBAAqB,OAA4D;EAC9F,MAAM,SAAS,KAAK,gBAAgB,IAAI,MAAM,IAAI;EAClD,IAAI,UAAU,MAAM,OAAO;EAE3B,MAAM,aAAa,QAAQ,MAAM,IAAI;EACrC,MAAM,iBAAiB,SAAS,UAAU;EAC1C,MAAM,qBAAqB,QAC1B,qBAAqB,GAAG,CAAC,CAAC,YAAY,MAAM,eAAe,YAAY;EAExE,IAAI;GACH,MAAM,QAAQ,KAAK,MAAM,MAAM,SAAS,KAAK,YAAY,qBAAqB,GAAG,MAAM,CAAC;GAGxF,IAAI,MAAM,QAAQ,MAAM,OAAO,GAAG;IACjC,MAAM,UAAU,MAAM,QAAQ,MAAK,SAAQ,MAAM,cAAc,MAAM,SAAS;IAC9E,IAAI,OAAO,SAAS,gBAAgB,YAAY,kBAAkB,QAAQ,WAAW,GAAG;KACvF,KAAK,sBAAsB,MAAM,MAAM,QAAQ,WAAW;KAC1D,OAAO,QAAQ;IAChB;GACD;EACD,QAAQ,CAER;EAEA,MAAM,KAAK,gBAAgB;GAAE,OAAO,iBAAiB,MAAM,MAAM,EAAE,UAAU,OAAO,CAAC;GAAG,WAAW;EAAS,CAAC;EAC7G,IAAI;GACH,WAAW,MAAM,QAAQ,IAAI;IAC5B,IAAI,CAAC,KAAK,SAAS,SAAO,GAAG;IAE7B,IAAI;IACJ,IAAI;KACH,SAAS,KAAK,MAAM,IAAI;IACzB,QAAQ;KACP;IACD;IACA,IAAI,OAAO,aAAa,QAAQ,OAAO,cAAc,MAAM,WAAW;IAEtE,IAAI,OAAO,OAAO,QAAQ,YAAY,kBAAkB,OAAO,GAAG,GAAG;KACpE,KAAK,sBAAsB,MAAM,MAAM,OAAO,GAAG;KACjD,OAAO,OAAO;IACf;GACD;EACD,UAAU;GACT,GAAG,MAAM;EACV;CAGD;CAEA,sBAA8B,MAAc,KAAmB;EAC9D,KAAK,gBAAgB,IAAI,MAAM,GAAG;EAClC,IAAI,KAAK,gBAAgB,OAAO,mBAAmB;GAClD,MAAM,SAAS,KAAK,gBAAgB,KAAK,CAAC,CAAC,KAAK;GAChD,IAAI,CAAC,OAAO,MACX,KAAK,gBAAgB,OAAO,OAAO,KAAK;EAE1C;CACD;;;;;;;;;CAUA,MAAgB,YAAY,OAAkE;EAC7F,MAAM,SAA2B,CAAC;EAClC,IAAI;EAEJ,MAAM,SAAS,QAAgB,yBAAwC;GAEtE,MAAM,QADO,OAAO,SAAS,MACZ,CAAC,CAAC,MAAM,IAAI;GAE7B,IAAI,sBACH,MAAM,MAAM;GAEb,KAAK,MAAM,QAAQ,OAAO;IACzB,MAAM,SAAS,iBAAiB,MAAM,MAAM,SAAS;IACrD,IAAI,UAAU,MAAM;IAEpB,gBAAgB,QAAQ,MAAM;IAC9B,IAAI,OAAO,cAAc,QAAQ,OAAO,WAAW,SAAS,GAC3D,aAAa,OAAO;GAEtB;EACD;EAEA,MAAM,SAAS,MAAM,KAAK,MAAM,MAAM,GAAG;EACzC,IAAI;GACH,IAAI,MAAM,QAAQ,oBAAoB,GAAG;IACxC,MAAM,QAAQ,OAAO,MAAM,MAAM,IAAI;IACrC,MAAM,OAAO,KAAK,OAAO,GAAG,MAAM,MAAM,CAAC;IACzC,MAAM,OAAO,KAAK;GACnB,OAAO;IACN,MAAM,OAAO,OAAO,MAAM,iBAAiB;IAC3C,MAAM,OAAO,KAAK,MAAM,GAAG,mBAAmB,CAAC;IAC/C,MAAM,MAAM,KAAK;IAEjB,MAAM,OAAO,OAAO,MAAM,iBAAiB;IAC3C,MAAM,OAAO,KAAK,MAAM,GAAG,mBAAmB,MAAM,OAAO,iBAAiB;IAC5E,MAAM,MAAM,IAAI;GACjB;EACD,UAAU;GACT,MAAM,OAAO,MAAM;EACpB;EAEA,OAAO;GACN,GAAG;GACK;GACI;EACb;CACD;CAEA,kBAAoC;EACnC,OAAO,KAAK,QAAQ,GAAG,WAAW,UAAU;CAC7C;;;;;;CAOA,MAAgB,kBAAkB,KAAsD;EACvF,IAAI,OAAO,QAAQ,IAAI,WAAW,GAAG,OAAO,KAAA;EAE5C,MAAM,OAAO,KAAK,gBAAgB;EAClC,MAAM,UAAU,qBAAqB,GAAG;EAExC,MAAM,QAAQ,KAAK,MAAM,OAAO;EAChC,IAAI,MAAM,gBAAgB,KAAK,GAAG,OAAO;EAEzC,IAAI;EACJ,IAAI;GACH,OAAO,MAAM,QAAQ,IAAI;EAC1B,SAAS,IAAI;GAGZ,IAAI,CAAC,eAAe,EAAE,GAAG,MAAM;GAE/B;EACD;EAEA,MAAM,UAAU,QAAQ,YAAY;EACpC,MAAM,QAAQ,KAAK,MAAK,MAAK,MAAM,WAAW,EAAE,YAAY,MAAM,OAAO;EACzE,OAAO,SAAS,OAAO,KAAK,MAAM,KAAK,IAAI,KAAA;CAC5C;CAEA,MAAgB,iBAAiB,WAAmB,KAAsD;EACzG,MAAM,WAAW,GAAG,UAAU;EAE9B,MAAM,MAAM,MAAM,KAAK,kBAAkB,GAAG;EAC5C,IAAI,OAAO,MAAM;GAChB,MAAM,YAAY,KAAK,KAAK,QAAQ;GACpC,IAAI,MAAM,WAAW,SAAS,GAAG,OAAO;EACzC;EAIA,MAAM,OAAO,KAAK,gBAAgB;EAClC,IAAI;EACJ,IAAI;GACH,OAAO,MAAM,QAAQ,IAAI;EAC1B,SAAS,IAAI;GACZ,IAAI,CAAC,eAAe,EAAE,GAAG,MAAM;GAE/B;EACD;EAQA,QAAO,MANkB,QAAQ,IAChC,KAAK,IAAI,OAAM,MAAK;GACnB,MAAM,YAAY,KAAK,MAAM,GAAG,QAAQ;GACxC,OAAQ,MAAM,WAAW,SAAS,IAAK,YAAY,KAAA;EACpD,CAAC,CACF,EAAA,CACkB,MAAK,MAAK,KAAK,IAAI;CACtC;;;;;;;CAQA,MAAgB,UACf,MACA,OACA,KACA,WACA,YAC6D;EAC7D,MAAM,SAAS,MAAM,UAAU,MAAM,OAAO,GAAG;EAC/C,MAAM,SAA2B,EAAE,GAAG,WAAW;EAEjD,IAAI,iBAAiB;EACrB,IAAI,YAAY;EAChB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;GACvC,IAAI,OAAO,OAAO,IAAM;GAIxB,MAAM,UAAU,IAAI,aAAa,OAAO,IAAI,OAAO,KAAO,IAAI,IAAI;GAClE,IAAI,UAAU,WAEb,eADa,OAAO,SAAS,QAAQ,WAAW,OAC9B,GAAG,WAAW,MAAM;GAEvC,YAAY,IAAI;GAChB,iBAAiB,QAAQ,IAAI;EAC9B;EAEA,OAAO;GAAU;GAAQ,aAAa;EAAe;CACtD;AACD;AAEA,SAAS,eAAe,MAAc,WAAmB,QAAgC;CACxF,IAAI,CAAC,kBAAkB,IAAI,GAAG;CAE9B,IAAI;CACJ,IAAI;EACH,QAAQ,KAAK,MAAM,IAAI;CACxB,QAAQ;EACP;CACD;CACA,IAAI,MAAM,aAAa,QAAQ,MAAM,cAAc,WAAW;CAE9D,gBAAgB,OAAO,MAAM;AAC9B;;;;AAKA,SAAS,iBAAiB,MAAc,WAA2C;CAClF,IAAI,KAAK,WAAW,KAAK,CAAC,KAAK,SAAS,UAAQ,GAAG,OAAO,KAAA;CAE1D,IAAI;CACJ,IAAI;EACH,QAAQ,KAAK,MAAM,IAAI;CACxB,QAAQ;EACP;CACD;CACA,IAAI,MAAM,aAAa,QAAQ,MAAM,cAAc,WAAW,OAAO,KAAA;CAErE,OAAO;AACR;AAEA,SAAS,gBAAgB,OAAmB,QAAgC;CAC3E,QAAQ,MAAM,MAAd;EACC,KAAK;GACJ,IAAI,OAAO,MAAM,gBAAgB,YAAY,MAAM,YAAY,SAAS,GACvE,OAAO,SAAS,MAAM;GAEvB;EACD,KAAK;GACJ,IAAI,OAAO,MAAM,YAAY,YAAY,MAAM,QAAQ,SAAS,GAC/D,OAAO,KAAK,MAAM;GAEnB;EACD,KAAK,cACJ,IAAI,OAAO,MAAM,cAAc,YAAY,MAAM,UAAU,SAAS,GACnE,OAAO,QAAQ,MAAM;CAGxB;AACD;;;;;AAMA,SAAgB,qBAAqB,KAAqB;CACzD,OAAO,IAAI,QAAQ,iBAAiB,GAAG;AACxC;AAEA,SAAS,kBAAkB,MAAuB;CACjD,IAAI,CAAC,KAAK,SAAS,UAAQ,GAAG,OAAO;CACrC,OAAO,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,YAAY;AAC7D;;;AAIA,SAAS,kBAAkB,SAA4C;CACtE,MAAM,EAAE,QAAQ,IAAI,UAAU,QAAQ;CACtC,OAAO,UAAU,QAAQ,MAAM,QAAQ,SAAS,QAAQ,QAAQ,cAAc;AAC/E;AAEA,eAAe,WAAW,MAAgC;CACzD,IAAI;EAEH,QAAO,MADa,KAAK,IAAI,EAAA,CAChB,OAAO;CACrB,SAAS,IAAI;EAEZ,IAAI,CAAC,eAAe,EAAE,GAAG,MAAM;EAE/B,OAAO;CACR;AACD;AAEA,eAAe,gBAAgB,MAAgC;CAC9D,IAAI;EAEH,QAAO,MADa,KAAK,IAAI,EAAA,CAChB,YAAY;CAC1B,SAAS,IAAI;EAGZ,IAAI,eAAe,EAAE,GAAG,OAAO;EAE/B,MAAM;CACP;AACD;;;;AAKA,SAAS,eAAe,IAAsB;CAC7C,MAAM,OAAQ,IAA0C;CACxD,OAAO,SAAS,YAAY,SAAS;AACtC;AAEA,SAAS,UAAU,MAAc,OAAe,KAA8B;CAC7E,OAAO,IAAI,SAAS,SAAS,WAAW;EACvC,MAAM,SAAmB,CAAC;EAC1B,MAAM,SAAS,iBAAiB,MAAM;GAAS;GAAO,KAAK,MAAM;EAAE,CAAC;EACpE,OAAO,GAAG,SAAQ,UAAS,OAAO,KAAK,KAAe,CAAC;EACvD,OAAO,GAAG,aAAa,QAAQ,OAAO,OAAO,MAAM,CAAC,CAAC;EACrD,OAAO,GAAG,SAAS,MAAM;CAC1B,CAAC;AACF"}
|
|
@@ -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";
|
|
@@ -20,7 +20,9 @@ export declare class StatusGitSubProvider implements GitStatusSubProvider {
|
|
|
20
20
|
getWorkingChangesState(_repoPath: string, _cancellation?: AbortSignal): Promise<GitWorkingChangesState>;
|
|
21
21
|
hasConflictingFiles(_repoPath: string, _cancellation?: AbortSignal): Promise<boolean>;
|
|
22
22
|
getConflictingFiles(_repoPath: string, _cancellation?: AbortSignal): Promise<GitConflictFile[]>;
|
|
23
|
-
getUntrackedFiles(_repoPath: string,
|
|
23
|
+
getUntrackedFiles(_repoPath: string, _options?: {
|
|
24
|
+
paths?: readonly string[];
|
|
25
|
+
}, _cancellation?: AbortSignal): Promise<GitFile[]>;
|
|
24
26
|
}
|
|
25
27
|
//#endregion
|
|
26
28
|
//# sourceMappingURL=status.d.ts.map
|
|
@@ -40,7 +40,7 @@ var StatusGitSubProvider = class {
|
|
|
40
40
|
getConflictingFiles(_repoPath, _cancellation) {
|
|
41
41
|
return Promise.resolve([]);
|
|
42
42
|
}
|
|
43
|
-
getUntrackedFiles(_repoPath, _cancellation) {
|
|
43
|
+
getUntrackedFiles(_repoPath, _options, _cancellation) {
|
|
44
44
|
return Promise.resolve([]);
|
|
45
45
|
}
|
|
46
46
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","names":[],"sources":["../../../../../../plus/git-github/src/providers/github/status.ts"],"sourcesContent":["import type { GitFile } from '@gitlens/git/models/file.js';\nimport type { GitConflictFile } from '@gitlens/git/models/staging.js';\nimport { GitStatus } from '@gitlens/git/models/status.js';\nimport type { GitStatusSubProvider, GitWorkingChangesState } from '@gitlens/git/providers/status.js';\nimport type { GitCommandPriority } from '@gitlens/git/run.types.js';\nimport { gate } from '@gitlens/utils/decorators/gate.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { HeadType } from '../../context.js';\nimport type { GitHubGitProviderInternal } from '../githubProvider.js';\n\nexport class StatusGitSubProvider implements GitStatusSubProvider {\n\tconstructor(private readonly provider: GitHubGitProviderInternal) {}\n\n\t@gate()\n\t@debug()\n\tasync getStatus(\n\t\trepoPath: string | undefined,\n\t\t_options?: { priority?: GitCommandPriority },\n\t\t_cancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tif (repoPath == null) return undefined;\n\n\t\tconst context = await this.provider.ensureRepositoryContext(repoPath);\n\t\tif (context == null) return undefined;\n\n\t\tconst revision = await context.metadata.getRevision();\n\t\tif (revision == null) return undefined;\n\n\t\treturn new GitStatus(\n\t\t\trepoPath,\n\t\t\trevision.name,\n\t\t\trevision.revision,\n\t\t\t[],\n\t\t\trevision.type === HeadType.Branch || revision.type === HeadType.RemoteBranch\n\t\t\t\t? { name: `origin/${revision.name}`, missing: false, state: { ahead: 0, behind: 0 } }\n\t\t\t\t: undefined,\n\t\t);\n\t}\n\n\thasWorkingChanges(\n\t\t_repoPath: string,\n\t\t_options?: {\n\t\t\tstaged?: boolean;\n\t\t\tunstaged?: boolean;\n\t\t\tuntracked?: boolean;\n\t\t\tpriority?: GitCommandPriority;\n\t\t},\n\t\t_cancellation?: AbortSignal,\n\t): Promise<boolean> {\n\t\treturn Promise.resolve(false);\n\t}\n\n\tgetWorkingChangesState(_repoPath: string, _cancellation?: AbortSignal): Promise<GitWorkingChangesState> {\n\t\treturn Promise.resolve({ staged: false, unstaged: false, untracked: false });\n\t}\n\n\thasConflictingFiles(_repoPath: string, _cancellation?: AbortSignal): Promise<boolean> {\n\t\treturn Promise.resolve(false);\n\t}\n\n\tgetConflictingFiles(_repoPath: string, _cancellation?: AbortSignal): Promise<GitConflictFile[]> {\n\t\treturn Promise.resolve([]);\n\t}\n\n\tgetUntrackedFiles(
|
|
1
|
+
{"version":3,"file":"status.js","names":[],"sources":["../../../../../../plus/git-github/src/providers/github/status.ts"],"sourcesContent":["import type { GitFile } from '@gitlens/git/models/file.js';\nimport type { GitConflictFile } from '@gitlens/git/models/staging.js';\nimport { GitStatus } from '@gitlens/git/models/status.js';\nimport type { GitStatusSubProvider, GitWorkingChangesState } from '@gitlens/git/providers/status.js';\nimport type { GitCommandPriority } from '@gitlens/git/run.types.js';\nimport { gate } from '@gitlens/utils/decorators/gate.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { HeadType } from '../../context.js';\nimport type { GitHubGitProviderInternal } from '../githubProvider.js';\n\nexport class StatusGitSubProvider implements GitStatusSubProvider {\n\tconstructor(private readonly provider: GitHubGitProviderInternal) {}\n\n\t@gate()\n\t@debug()\n\tasync getStatus(\n\t\trepoPath: string | undefined,\n\t\t_options?: { priority?: GitCommandPriority },\n\t\t_cancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tif (repoPath == null) return undefined;\n\n\t\tconst context = await this.provider.ensureRepositoryContext(repoPath);\n\t\tif (context == null) return undefined;\n\n\t\tconst revision = await context.metadata.getRevision();\n\t\tif (revision == null) return undefined;\n\n\t\treturn new GitStatus(\n\t\t\trepoPath,\n\t\t\trevision.name,\n\t\t\trevision.revision,\n\t\t\t[],\n\t\t\trevision.type === HeadType.Branch || revision.type === HeadType.RemoteBranch\n\t\t\t\t? { name: `origin/${revision.name}`, missing: false, state: { ahead: 0, behind: 0 } }\n\t\t\t\t: undefined,\n\t\t);\n\t}\n\n\thasWorkingChanges(\n\t\t_repoPath: string,\n\t\t_options?: {\n\t\t\tstaged?: boolean;\n\t\t\tunstaged?: boolean;\n\t\t\tuntracked?: boolean;\n\t\t\tpriority?: GitCommandPriority;\n\t\t},\n\t\t_cancellation?: AbortSignal,\n\t): Promise<boolean> {\n\t\treturn Promise.resolve(false);\n\t}\n\n\tgetWorkingChangesState(_repoPath: string, _cancellation?: AbortSignal): Promise<GitWorkingChangesState> {\n\t\treturn Promise.resolve({ staged: false, unstaged: false, untracked: false });\n\t}\n\n\thasConflictingFiles(_repoPath: string, _cancellation?: AbortSignal): Promise<boolean> {\n\t\treturn Promise.resolve(false);\n\t}\n\n\tgetConflictingFiles(_repoPath: string, _cancellation?: AbortSignal): Promise<GitConflictFile[]> {\n\t\treturn Promise.resolve([]);\n\t}\n\n\tgetUntrackedFiles(\n\t\t_repoPath: string,\n\t\t_options?: { paths?: readonly string[] },\n\t\t_cancellation?: AbortSignal,\n\t): Promise<GitFile[]> {\n\t\treturn Promise.resolve([]);\n\t}\n}\n"],"mappings":";;;;;;AAUA,IAAa,uBAAb,MAAkE;CACpC;CAA7B,YAAY,UAAsD;EAArC,KAAA,WAAA;CAAsC;CAEnE,MAEM,UACL,UACA,UACA,eACiC;EACjC,IAAI,YAAY,MAAM,OAAO,KAAA;EAE7B,MAAM,UAAU,MAAM,KAAK,SAAS,wBAAwB,QAAQ;EACpE,IAAI,WAAW,MAAM,OAAO,KAAA;EAE5B,MAAM,WAAW,MAAM,QAAQ,SAAS,YAAY;EACpD,IAAI,YAAY,MAAM,OAAO,KAAA;EAE7B,OAAO,IAAI,UACV,UACA,SAAS,MACT,SAAS,UACT,CAAC,GACD,SAAS,SAAS,SAAS,UAAU,SAAS,SAAS,SAAS,eAC7D;GAAE,MAAM,UAAU,SAAS;GAAQ,SAAS;GAAO,OAAO;IAAE,OAAO;IAAG,QAAQ;GAAE;EAAE,IAClF,KAAA,CACJ;CACD;CAEA,kBACC,WACA,UAMA,eACmB;EACnB,OAAO,QAAQ,QAAQ,KAAK;CAC7B;CAEA,uBAAuB,WAAmB,eAA8D;EACvG,OAAO,QAAQ,QAAQ;GAAE,QAAQ;GAAO,UAAU;GAAO,WAAW;EAAM,CAAC;CAC5E;CAEA,oBAAoB,WAAmB,eAA+C;EACrF,OAAO,QAAQ,QAAQ,KAAK;CAC7B;CAEA,oBAAoB,WAAmB,eAAyD;EAC/F,OAAO,QAAQ,QAAQ,CAAC,CAAC;CAC1B;CAEA,kBACC,WACA,UACA,eACqB;EACrB,OAAO,QAAQ,QAAQ,CAAC,CAAC;CAC1B;AACD;AA1DE,WAAA,CAAA,KAAK,GACL,MAAM,CAAA,GAAA,qBAAA,WAAA,aAAA,IAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TokenWithInfo } from "./authentication/models.js";
|
|
2
2
|
import { IntegrationIds } from "./constants.js";
|
|
3
|
-
import { ProviderWarning, ProviderWarningCause, ProviderWarningScope } from "./results.js";
|
|
3
|
+
import { ProviderWarning, ProviderWarningCause, ProviderWarningScope, ProviderWarningUnreadableItems } from "./results.js";
|
|
4
4
|
import { CollectionMetadata, CollectionScopeFailure } from "@gitkraken/provider-apis";
|
|
5
5
|
//#region ../plus/integrations/src/collectionMetadata.d.ts
|
|
6
6
|
/**
|
|
@@ -56,6 +56,11 @@ export interface ProviderScopeFailure extends CollectionScopeFailure {
|
|
|
56
56
|
cause?: ProviderWarningCause;
|
|
57
57
|
/** The provider pinned the refusal on the credential itself, so it is published for the connection, unscoped. */
|
|
58
58
|
credentialRefused?: true;
|
|
59
|
+
/**
|
|
60
|
+
* The failure is items a page left out because they could not be read, the rest of its scope having been read. It
|
|
61
|
+
* is published as an `'unreadable-items'` warning naming them, see `ProviderWarning.unreadableItems`.
|
|
62
|
+
*/
|
|
63
|
+
unreadableItems?: ProviderWarningUnreadableItems;
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
66
|
* Builds a structured SDK scope failure from a caught GitLens request error.
|
|
@@ -98,10 +103,16 @@ export declare function attributeScopedAuthFailures(metadata: CollectionMetadata
|
|
|
98
103
|
* object cannot be truncated at one layer and complete at another.
|
|
99
104
|
*/
|
|
100
105
|
export declare function isIncompleteCollection(metadata: CollectionMetadata | undefined): boolean;
|
|
101
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* Converts internal SDK collection metadata into neutral provider facade signals.
|
|
108
|
+
*
|
|
109
|
+
* `requestFailed` is `fetchFailed` without the failures that only report items a read left unread: those fail the read,
|
|
110
|
+
* since the items are missing, but every request answered, so the omissions the read reported still hold.
|
|
111
|
+
*/
|
|
102
112
|
export declare function assessCollectionMetadata(providerId: IntegrationIds, domain: string | undefined, connectionId: string | undefined, metadata: CollectionMetadata | undefined): {
|
|
103
113
|
warnings: ProviderWarning[];
|
|
104
114
|
fetchFailed: boolean;
|
|
115
|
+
requestFailed: boolean;
|
|
105
116
|
truncated: boolean;
|
|
106
117
|
};
|
|
107
118
|
/**
|
|
@@ -107,6 +107,7 @@ function getCollectionFailureStatus(message) {
|
|
|
107
107
|
return match != null ? Number(match[1]) : void 0;
|
|
108
108
|
}
|
|
109
109
|
function toCollectionFailureWarningKind(failure) {
|
|
110
|
+
if (failure.unreadableItems != null) return "unreadable-items";
|
|
110
111
|
const mapped = collectionFailureKindToWarningKind(failure.kind);
|
|
111
112
|
if (mapped !== "other" || failure.kind !== "provider" && failure.kind !== "unknown") return mapped;
|
|
112
113
|
const status = getCollectionFailureStatus(failure.message);
|
|
@@ -307,11 +308,17 @@ function isIncompleteCollection(metadata) {
|
|
|
307
308
|
if (metadata == null) return false;
|
|
308
309
|
return metadata.completeness !== "complete" || (metadata.omissions?.length ?? 0) > 0;
|
|
309
310
|
}
|
|
310
|
-
/**
|
|
311
|
+
/**
|
|
312
|
+
* Converts internal SDK collection metadata into neutral provider facade signals.
|
|
313
|
+
*
|
|
314
|
+
* `requestFailed` is `fetchFailed` without the failures that only report items a read left unread: those fail the read,
|
|
315
|
+
* since the items are missing, but every request answered, so the omissions the read reported still hold.
|
|
316
|
+
*/
|
|
311
317
|
function assessCollectionMetadata(providerId, domain, connectionId, metadata) {
|
|
312
318
|
if (metadata == null) return {
|
|
313
319
|
warnings: [],
|
|
314
320
|
fetchFailed: false,
|
|
321
|
+
requestFailed: false,
|
|
315
322
|
truncated: false
|
|
316
323
|
};
|
|
317
324
|
const warnings = [];
|
|
@@ -332,7 +339,11 @@ function assessCollectionMetadata(providerId, domain, connectionId, metadata) {
|
|
|
332
339
|
kind,
|
|
333
340
|
isAuth: kind === "auth",
|
|
334
341
|
...scope != null ? { scope } : {},
|
|
335
|
-
...cause != null ? { cause: { ...cause } } : {}
|
|
342
|
+
...cause != null ? { cause: { ...cause } } : {},
|
|
343
|
+
...failure.unreadableItems != null ? { unreadableItems: {
|
|
344
|
+
keys: [...failure.unreadableItems.keys],
|
|
345
|
+
complete: failure.unreadableItems.complete
|
|
346
|
+
} } : {}
|
|
336
347
|
});
|
|
337
348
|
}
|
|
338
349
|
const omissions = metadata.omissions ?? [];
|
|
@@ -357,6 +368,7 @@ function assessCollectionMetadata(providerId, domain, connectionId, metadata) {
|
|
|
357
368
|
return {
|
|
358
369
|
warnings,
|
|
359
370
|
fetchFailed: failures.length > 0,
|
|
371
|
+
requestFailed: failures.some((failure) => failure.unreadableItems == null),
|
|
360
372
|
truncated: incomplete
|
|
361
373
|
};
|
|
362
374
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collectionMetadata.js","names":[],"sources":["../../../../plus/integrations/src/collectionMetadata.ts"],"sourcesContent":["import type {\n\tCollectionMetadata,\n\tCollectionOmission,\n\tCollectionScope,\n\tCollectionScopeFailure,\n} from '@gitkraken/provider-apis';\n// Matched by its `code` discriminator rather than by `instanceof`: the SDK ships one bundle per entry point, so\n// the class reached through the root is not the same object as the one reached through `/providers`.\nimport { isInvalidRequestError, isUnsupportedSortError } from '@gitkraken/provider-apis';\nimport {\n\tAuthenticationError,\n\tRequestClientError,\n\tRequestNotFoundError,\n\tRequestRateLimitError,\n} from '@gitlens/git/errors.js';\nimport type { TokenWithInfo } from './authentication/models.js';\nimport type { IntegrationIds } from './constants.js';\nimport type { ResponseHeaders } from './errors.js';\nimport { getResponseHeader, isProviderUnreachableError, isRateLimitResponse } from './errors.js';\nimport { getProviderResponseBodyMessage } from './providers/providerErrors.js';\nimport type {\n\tProviderWarning,\n\tProviderWarningCause,\n\tProviderWarningOmission,\n\tProviderWarningScope,\n} from './results.js';\nimport { appendDedupedWarning, providerWarningMessage } from './results.js';\n\n/**\n * Re-throws an error that is a fact about the CALL rather than about one scope of a fan-out.\n *\n * Every provider fan-out catches per-scope rejections and records them as {@link CollectionScopeFailure}s, which is\n * right for auth, rate limits and a missing project: those really did happen to one scope, and the siblings that\n * succeeded must survive. A caller-contract error is the opposite — it is identical for every scope and was decided\n * before any request went out — so degrading it would hand back N indistinguishable failures and an empty `partial`\n * page, describing an invalid call as an incomplete read.\n *\n * Today that means the SDK's `UnsupportedSortError` and `InvalidRequestError` — the two codes it lists in its own\n * `CALLER_CONTRACT_ERROR_CODES`, both documented as \"never degraded into a `CollectionScopeFailure` or an\n * omission\". The second covers arguments rejected before the first request goes out: a project key list longer\n * than one Jira search can carry (or an empty one), a non-integer `pageSize`, an unknown current-user filter, or a\n * Jira Server read with no `baseUrl` to address. Called from every fan-out rather than spelled out in each, so a\n * fan-out added later inherits the rule instead of silently omitting it — which is exactly how Azure's\n * account-wide drain came to degrade it while its three siblings did not.\n */\nexport function throwIfCallerContractError(ex: unknown): void {\n\tif (isUnsupportedSortError(ex) || isInvalidRequestError(ex)) throw ex;\n}\n\n/**\n * Whether a failed query that covered several scopes would fail each scope's own read the same way: a rejected or\n * rate-limited token, or a call the SDK refused before sending. Anything else (a query the provider rejects because\n * one scope is gone or no longer visible, a server error) may be one scope's problem, so the scopes are worth\n * reading on their own.\n */\nexport function failsEveryScope(ex: unknown): boolean {\n\tconst kind = toCollectionFailureKind(ex);\n\treturn (\n\t\tkind === 'authentication' || kind === 'rate-limit' || isUnsupportedSortError(ex) || isInvalidRequestError(ex)\n\t);\n}\n\n/**\n * Maps a caught GitLens request error to the SDK collection failure vocabulary used inside provider fan-outs.\n */\nexport function toCollectionFailureKind(ex: unknown): CollectionScopeFailure['kind'] {\n\tif (ex instanceof AuthenticationError) return 'authentication';\n\tif (ex instanceof RequestRateLimitError) return 'rate-limit';\n\tif (ex instanceof RequestNotFoundError) return 'not-found';\n\treturn 'provider';\n}\n\n/**\n * What an authentication refusal's response said, kept on the failure so the provider can name its cause once the\n * credential behind it is confirmed (see `IntegrationBase.confirmScopedAuthFailures`). Only these facts are kept,\n * never the response: its headers carry the provider's session cookies.\n */\nexport interface ProviderRefusal {\n\tstatus: number;\n\t/** The provider's own explanation: Azure DevOps' `X-TFS-ServiceError`, or the error body's message. */\n\tdetail?: string;\n\t/** The provider's error type, when the body names one (Azure DevOps `typeKey`). */\n\ttypeKey?: string;\n\t/**\n\t * The OAuth app the refused token belongs to, when the provider says (GitHub's `X-OAuth-Client-Id`). Not a\n\t * secret: it is the app's public client id, which the page where a user asks an organization to approve the app\n\t * is addressed by.\n\t */\n\toauthClientId?: string;\n}\n\n/** A scope failure as recorded here: the SDK's shape, plus what the refusal said and, once it is known, why. */\nexport interface ProviderScopeFailure extends CollectionScopeFailure {\n\trefusal?: ProviderRefusal;\n\tcause?: ProviderWarningCause;\n\t/** The provider pinned the refusal on the credential itself, so it is published for the connection, unscoped. */\n\tcredentialRefused?: true;\n}\n\n/**\n * Reads the refusal off an `AuthenticationError` that carries the provider's response: the SDK adapter's plain\n * `{ status, headers, body }` (`providersApi.ts`), Octokit's `{ status, headers, data }` (the GitHub client's REST\n * reads), or the `Response` of a `ProviderFetchError`, whose body was already read into its message.\n */\nfunction toProviderRefusal(ex: unknown): ProviderRefusal | undefined {\n\tif (!(ex instanceof AuthenticationError)) return undefined;\n\n\tconst response = (ex.original as { response?: unknown } | undefined)?.response as\n\t\t| { status?: unknown; headers?: ResponseHeaders; body?: unknown; data?: unknown }\n\t\t| undefined;\n\tif (typeof response?.status !== 'number') return undefined;\n\n\t// Only a structured body: a string one is a page (a proxy's, a sign-in form), not an explanation.\n\tconst raw = response.body ?? response.data;\n\tconst body = typeof raw === 'object' && raw != null ? raw : undefined;\n\tconst detail =\n\t\tdecodeServiceError(getResponseHeader(response.headers, 'x-tfs-serviceerror')) ??\n\t\tgetProviderResponseBodyMessage(body);\n\tconst typeKey = body != null && 'typeKey' in body ? body.typeKey : undefined;\n\tconst oauthClientId = getResponseHeader(response.headers, 'x-oauth-client-id');\n\n\treturn {\n\t\tstatus: response.status,\n\t\t...(detail ? { detail: detail } : {}),\n\t\t...(typeof typeKey === 'string' ? { typeKey: typeKey } : {}),\n\t\t...(typeof oauthClientId === 'string' && oauthClientId ? { oauthClientId: oauthClientId } : {}),\n\t};\n}\n\n/** Azure DevOps sends `X-TFS-ServiceError` percent-encoded. */\nfunction decodeServiceError(value: unknown): string | undefined {\n\tif (typeof value !== 'string' || !value) return undefined;\n\n\ttry {\n\t\treturn decodeURIComponent(value).trim() || undefined;\n\t} catch {\n\t\treturn value.trim() || undefined;\n\t}\n}\n\n/**\n * Builds a structured SDK scope failure from a caught GitLens request error.\n *\n * An authentication refusal is described in the provider's own words when it gives any, and otherwise by the\n * error the provider's response raised, sanitized like any warning's (a page becomes its status), rather than by\n * `AuthenticationError`'s generic \"credentials are either invalid or expired\": a scope refusing a sound credential\n * is the case this failure exists to tell apart, and that sentence says the opposite.\n */\nexport function toCollectionScopeFailure(scope: CollectionScopeFailure['scope'], ex: unknown): ProviderScopeFailure {\n\tconst kind = toCollectionFailureKind(ex);\n\tconst refusal = toProviderRefusal(ex);\n\tconst message =\n\t\trefusal != null ? (refusal.detail ?? providerWarningMessage((ex as AuthenticationError).original)) : undefined;\n\treturn {\n\t\tscope: scope,\n\t\tkind: kind,\n\t\t...(message ? { message: message } : ex instanceof Error && ex.message ? { message: ex.message } : {}),\n\t\t...(refusal != null ? { refusal: refusal } : {}),\n\t\t...(kind === 'provider' && isProviderUnreachableError(ex) ? { cause: { reason: 'unreachable' as const } } : {}),\n\t};\n}\n\nfunction collectionFailureKindToWarningKind(kind: CollectionScopeFailure['kind']): ProviderWarning['kind'] {\n\tswitch (kind) {\n\t\tcase 'authentication':\n\t\t\treturn 'auth';\n\t\tcase 'rate-limit':\n\t\t\treturn 'rate-limit';\n\t\tcase 'not-found':\n\t\t\treturn 'not-found';\n\t\tdefault:\n\t\t\treturn 'other';\n\t}\n}\n\nfunction getCollectionFailureStatus(message: string | undefined): number | undefined {\n\tif (message == null) return undefined;\n\n\tconst match = /\\((\\d{3})\\)/.exec(message) ?? /^(?:HTTP\\s+)?(\\d{3})\\b/i.exec(message.trim());\n\treturn match != null ? Number(match[1]) : undefined;\n}\n\nfunction toCollectionFailureWarningKind(failure: CollectionScopeFailure): ProviderWarning['kind'] {\n\tconst mapped = collectionFailureKindToWarningKind(failure.kind);\n\tif (mapped !== 'other' || (failure.kind !== 'provider' && failure.kind !== 'unknown')) return mapped;\n\n\t// provider-apis currently groups several HTTP statuses under `provider` in partial fan-outs. The custom\n\t// fetch adapter retains the status in `message`, so recover the same neutral classification the thrown-error\n\t// path exposes instead of downgrading these structured failures to `other`.\n\tconst status = getCollectionFailureStatus(failure.message);\n\tswitch (status) {\n\t\tcase 401:\n\t\t\treturn 'auth';\n\t\tcase 403:\n\t\t\treturn isRateLimitResponse({ status: status, message: failure.message }) ? 'rate-limit' : 'auth';\n\t\tcase 404:\n\t\tcase 410:\n\t\tcase 422:\n\t\t\treturn 'not-found';\n\t\tcase 429:\n\t\t\treturn 'rate-limit';\n\t\tdefault:\n\t\t\treturn mapped;\n\t}\n}\n\nfunction toCollectionFailureCause(\n\tfailure: ProviderScopeFailure,\n\tkind: ProviderWarning['kind'],\n): ProviderWarningCause | undefined {\n\treturn (\n\t\tfailure.cause ??\n\t\t(kind === 'other' &&\n\t\tisProviderUnreachableError({ ...failure, status: getCollectionFailureStatus(failure.message) })\n\t\t\t? { reason: 'unreachable' }\n\t\t\t: undefined)\n\t);\n}\n\nexport function toCollectionFailureError(failure: ProviderScopeFailure, tokenWithInfo: TokenWithInfo): Error {\n\tconst kind = toCollectionFailureWarningKind(failure);\n\tconst error = new Error(failure.message ?? 'Provider request failed', {\n\t\tcause: toCollectionFailureCause(failure, kind),\n\t});\n\tconst { accessToken, ...tokenInfo } = tokenWithInfo;\n\tif (kind === 'auth') return new AuthenticationError(tokenInfo, error.message, error);\n\tif (kind === 'rate-limit') return new RequestRateLimitError(error, accessToken, undefined);\n\tif (kind === 'not-found') return new RequestNotFoundError(error);\n\n\tconst status = getCollectionFailureStatus(failure.message);\n\tif (kind === 'other' && status != null && status >= 400 && status < 500) {\n\t\treturn new RequestClientError(error);\n\t}\n\n\treturn error;\n}\n\n/** ` (resource r, project p, repository o/n)` for the scope IDs present; empty when the scope names none. */\nfunction collectionScopeText(scope: CollectionScope | undefined): string {\n\tconst parts: string[] = [];\n\tif (scope?.resourceId != null) {\n\t\tparts.push(`resource ${scope.resourceId}`);\n\t}\n\tif (scope?.projectId != null) {\n\t\tparts.push(`project ${scope.projectId}`);\n\t}\n\tif (scope?.repositoryId != null) {\n\t\tparts.push(`repository ${scope.repositoryId}`);\n\t}\n\n\treturn parts.length ? ` (${parts.join(', ')})` : '';\n}\n\n/** The cause in the consumer's terms, for the prose that goes with the structured {@link ProviderWarningCause}. */\nfunction causeText(cause: ProviderWarningCause): string {\n\tswitch (cause.reason) {\n\t\tcase 'oauth-app-not-allowed':\n\t\t\treturn 'the organization does not allow third-party OAuth apps';\n\t\tcase 'access-denied':\n\t\t\treturn 'the account has no access to it';\n\t\tcase 'conditional-access':\n\t\t\treturn 'a Conditional Access policy blocked the request';\n\t\tcase 'unreachable':\n\t\t\treturn 'the provider server could not be reached';\n\t\tdefault:\n\t\t\tcause.reason satisfies never;\n\t\t\treturn 'the provider refused the request';\n\t}\n}\n\nfunction collectionFailureMessage(failure: ProviderScopeFailure): string {\n\tconst text = failure.cause != null ? causeText(failure.cause) : failure.message;\n\tconst detail = text != null ? `: ${text}` : '';\n\treturn `Failed to read ${failure.kind} scope${collectionScopeText(failure.scope)}${detail}`;\n}\n\n/**\n * Forwards the part of a failure's scope below the provider, so a consumer can tell \"one organization refused\n * this token\" from \"the connection's token is dead\" without parsing {@link collectionFailureMessage}.\n *\n * `providerId` is dropped: the warning already names its provider, and a scope naming nothing below it is a\n * failure of the connection itself. That is the distinction `getOrganizationsForUserResult` also draws before\n * counting an auth failure against the connection, though it reads a scope with no keys but `providerId`, so an\n * absent scope does not count there. Forwarding such a scope would make an account-wide failure look confined.\n *\n * Copied field by field, for the reason {@link toProviderWarningOmission} copies its scope.\n */\nfunction toProviderWarningScope(scope: CollectionScope | undefined): ProviderWarningScope | undefined {\n\tif (scope == null) return undefined;\n\n\tconst { resourceId, projectId, repositoryId } = scope;\n\tif (resourceId == null && projectId == null && repositoryId == null) return undefined;\n\n\treturn {\n\t\t...(resourceId != null ? { resourceId: resourceId } : {}),\n\t\t...(projectId != null ? { projectId: projectId } : {}),\n\t\t...(repositoryId != null ? { repositoryId: repositoryId } : {}),\n\t};\n}\n\n/**\n * Whether `metadata` reports authentication failures only for scopes below the provider, with none for the\n * connection itself. A credential revoked since a provider cached its discovery produces exactly this shape,\n * the same one a scope's own refusal does; `IntegrationBase.confirmScopedAuthFailures` tells them apart.\n *\n * Classified exactly as {@link assessCollectionMetadata} classifies the warnings, so it agrees with what a\n * consumer would be shown.\n */\nexport function hasOnlyScopedAuthFailures(metadata: CollectionMetadata | undefined): boolean {\n\tlet scoped = false;\n\tfor (const failure of metadata?.failures ?? []) {\n\t\tif (toCollectionFailureWarningKind(failure) !== 'auth') continue;\n\t\tif (toProviderWarningScope(failure.scope) == null || (failure as ProviderScopeFailure).credentialRefused) {\n\t\t\treturn false;\n\t\t}\n\n\t\tscoped = true;\n\t}\n\treturn scoped;\n}\n\n/** The scoped authentication failures in `metadata` that kept their refusal, with the scope they are forwarded as. */\nfunction* refusedScopes(\n\tmetadata: CollectionMetadata | undefined,\n): Generator<{ failure: ProviderScopeFailure & { refusal: ProviderRefusal }; scope: ProviderWarningScope }> {\n\tfor (const failure of (metadata?.failures ?? []) as ProviderScopeFailure[]) {\n\t\tif (\n\t\t\tfailure.refusal == null ||\n\t\t\tfailure.credentialRefused ||\n\t\t\ttoCollectionFailureWarningKind(failure) !== 'auth'\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst scope = toProviderWarningScope(failure.scope);\n\t\tif (scope == null) continue;\n\n\t\tyield { failure: failure as ProviderScopeFailure & { refusal: ProviderRefusal }, scope: scope };\n\t}\n}\n\n/**\n * Marks each scoped authentication failure in `metadata` whose refusal the provider pins on the credential, so it is\n * published for the connection rather than for its scope. Returns whether any was.\n */\nexport function markCredentialRefusals(\n\tmetadata: CollectionMetadata | undefined,\n\tisCredentialRefusal: (refusal: ProviderRefusal) => boolean,\n): boolean {\n\tlet marked = false;\n\tfor (const { failure } of refusedScopes(metadata)) {\n\t\tif (!isCredentialRefusal(failure.refusal)) continue;\n\n\t\tfailure.credentialRefused = true;\n\t\tmarked = true;\n\t}\n\treturn marked;\n}\n\n/**\n * Names the cause of each scoped authentication failure in `metadata` that kept its refusal. Only for a read whose\n * credential was just confirmed: before that, the refusals this can name look exactly like a dead credential.\n */\nexport function attributeScopedAuthFailures(\n\tmetadata: CollectionMetadata | undefined,\n\tdescribe: (refusal: ProviderRefusal, scope: ProviderWarningScope) => ProviderWarningCause | undefined,\n): void {\n\tfor (const { failure, scope } of refusedScopes(metadata)) {\n\t\tif (failure.cause != null) continue;\n\n\t\tconst cause = describe(failure.refusal, scope);\n\t\tif (cause != null) {\n\t\t\tfailure.cause = cause;\n\t\t}\n\t}\n}\n\n/**\n * Explains one omission in the consumer's terms — what was left out and, where the SDK reports it, how much.\n *\n * An omission is a completeness fact, never a failure: the read succeeded, and the provider (or the SDK's own\n * recovery budget) is what withheld results. That is why these never contribute to `fetchFailed`. Whether a\n * retry would recover anything is a separate question this layer cannot answer — see the `recovery` note on\n * {@link toProviderWarningOmission}.\n */\nfunction collectionOmissionMessage(omission: CollectionOmission): string {\n\tconst scopeText = collectionScopeText(omission.scope);\n\tswitch (omission.kind) {\n\t\tcase 'provider-limit':\n\t\t\t// `totalCount` is the only figure saying how much was withheld; both fields are optional, so degrade\n\t\t\t// through the shapes the SDK can actually emit rather than printing `undefined`.\n\t\t\tif (omission.totalCount != null && omission.limit != null) {\n\t\t\t\treturn `Search${scopeText} matched ${omission.totalCount} results, but the provider exposes at most ${omission.limit}`;\n\t\t\t}\n\t\t\tif (omission.limit != null) {\n\t\t\t\treturn `Search${scopeText} exceeded the provider limit of ${omission.limit} results`;\n\t\t\t}\n\t\t\treturn `Search${scopeText} exceeded the provider's result limit`;\n\t\tcase 'recovery-budget':\n\t\t\treturn `Stopped recovering omitted results${scopeText} after reaching the request budget${\n\t\t\t\tomission.limit != null ? ` of ${omission.limit} requests` : ''\n\t\t\t}`;\n\t\tcase 'pagination-incomplete':\n\t\t\treturn `More results are available${scopeText} than this read returned`;\n\t\tdefault:\n\t\t\t// `CollectionOmissionKind` is a closed union, so this is unreachable today. Kept as a compile-time\n\t\t\t// guard rather than a silent fallback: an SDK bump that adds a kind breaks the build here instead\n\t\t\t// of quietly degrading it to a vague message.\n\t\t\tomission.kind satisfies never;\n\t\t\treturn `Some results were omitted${scopeText}; the read is incomplete`;\n\t}\n}\n\n/**\n * Forwards an SDK omission as the structured signal consumers read instead of parsing {@link\n * collectionOmissionMessage}'s prose.\n *\n * `results.ts` re-spells `CollectionOmissionKind` rather than importing it, so the published warning surface\n * carries no `@gitkraken/provider-apis` types (see the export block in `index.ts`). The return type is what\n * keeps the two unions honest: an SDK bump that adds a member fails to compile here, alongside\n * `collectionOmissionMessage`'s `satisfies never`.\n *\n * `scope` is copied because the SDK's own object is retained and re-merged across drained pages\n * (`providerPaging.ts`), and this one crosses the package boundary.\n *\n * `recovery` is always `'none'`, deliberately, and a scoped `pagination-incomplete` is why it looks wrong: the\n * SDK emits that one shape from situations with opposite remedies and says so itself — \"Either the read\n * deliberately took one page per scope, or the provider advertised another page it gave no way to reach.\" Its\n * `collectAcrossScopes` producers (Azure, Bitbucket, Bitbucket Server) are the first, its `drainAcrossScopes`\n * producers (GitLab, Jira) the second, and those report an omission only when a cursor STALLED — so re-reading\n * that scope stalls at the identical page. Nothing in `CollectionOmission` separates the two, so claiming\n * recoverability would ship the dead-end button `recovery` exists to prevent. Making the recoverable case\n * claimable needs provider-apis to say whether a scope was drained or merely sampled: a fact to forward, not\n * to guess here.\n */\nfunction toProviderWarningOmission(omission: CollectionOmission): ProviderWarningOmission {\n\treturn {\n\t\tkind: omission.kind,\n\t\trecovery: 'none',\n\t\t...(omission.limit != null ? { limit: omission.limit } : {}),\n\t\t// The SDK types this `number | null | undefined`; normalize to one absence for consumers.\n\t\t...(omission.totalCount != null ? { totalCount: omission.totalCount } : {}),\n\t\t...(omission.scope != null ? { scope: { ...omission.scope } } : {}),\n\t};\n}\n\n/**\n * Whether SDK metadata describes a read that may be missing results.\n *\n * A reported omission counts on its own rather than deferring to `completeness`. The SDK does couple the\n * two — every site that emits an omission degrades completeness in the same call — so today the omission\n * clause changes no outcome. It is defensive: the two facts arrive as independent fields, and a read that\n * names what it left out must not be publishable as whole if a future producer sets only one of them.\n *\n * This is the single definition of \"may be missing results\", shared with `getPagedResult`, so one metadata\n * object cannot be truncated at one layer and complete at another.\n */\nexport function isIncompleteCollection(metadata: CollectionMetadata | undefined): boolean {\n\tif (metadata == null) return false;\n\n\treturn metadata.completeness !== 'complete' || (metadata.omissions?.length ?? 0) > 0;\n}\n\n/** Converts internal SDK collection metadata into neutral provider facade signals. */\nexport function assessCollectionMetadata(\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmetadata: CollectionMetadata | undefined,\n): { warnings: ProviderWarning[]; fetchFailed: boolean; truncated: boolean } {\n\tif (metadata == null) return { warnings: [], fetchFailed: false, truncated: false };\n\n\tconst warnings: ProviderWarning[] = [];\n\tconst failures: ProviderScopeFailure[] = metadata.failures ?? [];\n\tfor (const failure of failures) {\n\t\tconst kind = toCollectionFailureWarningKind(failure);\n\t\tconst cause = toCollectionFailureCause(failure, kind);\n\t\tconst scope = failure.credentialRefused ? undefined : toProviderWarningScope(failure.scope);\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\t// A credential-level refusal names no scope in prose either, so every scope's copy of it is one warning.\n\t\t\tmessage: collectionFailureMessage({\n\t\t\t\t...failure,\n\t\t\t\tcause: cause,\n\t\t\t\t...(failure.credentialRefused ? { scope: undefined } : {}),\n\t\t\t}),\n\t\t\tkind: kind,\n\t\t\tisAuth: kind === 'auth',\n\t\t\t...(scope != null ? { scope: scope } : {}),\n\t\t\t// Copied for the reason the scope is: the failure object is retained and re-merged across pages.\n\t\t\t...(cause != null ? { cause: { ...cause } } : {}),\n\t\t});\n\t}\n\n\t// Omissions explain WHY a read is incomplete when nothing failed — a provider cap, an exhausted recovery\n\t// budget, an undrained scope. They classify as `other`, never `auth`, and are deliberately excluded from\n\t// `fetchFailed` below: the request itself succeeded. `omission` carries that same fact structurally, so a\n\t// consumer can act on it without parsing the message.\n\tconst omissions = metadata.omissions ?? [];\n\tfor (const omission of omissions) {\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\tmessage: collectionOmissionMessage(omission),\n\t\t\tkind: 'other',\n\t\t\tisAuth: false,\n\t\t\tomission: toProviderWarningOmission(omission),\n\t\t});\n\t}\n\n\tconst incomplete = isIncompleteCollection(metadata);\n\t// Only fall back to the generic message when nothing more specific was reported; an omission already\n\t// explains the incompleteness in the consumer's terms, so adding this on top would be noise.\n\t//\n\t// This one deliberately carries no `omission`: it fires precisely when the SDK reported incompleteness\n\t// WITHOUT saying what was left out, so there is no structured fact to forward and synthesizing one would\n\t// assert a specificity this layer does not have.\n\tif (incomplete && failures.length === 0 && omissions.length === 0) {\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\tmessage:\n\t\t\t\tmetadata.completeness === 'partial'\n\t\t\t\t\t? 'Some results were omitted; the read is incomplete'\n\t\t\t\t\t: 'Result completeness could not be confirmed',\n\t\t\tkind: 'other',\n\t\t\tisAuth: false,\n\t\t});\n\t}\n\n\treturn { warnings: warnings, fetchFailed: failures.length > 0, truncated: incomplete };\n}\n\n/**\n * Appends neutral warnings derived from SDK metadata and returns its failure/truncation assessment.\n *\n * `reported` says whether the metadata described this read's incompleteness at all. A caller that would\n * otherwise add its own generic \"this was truncated\" warning uses it to stay quiet — the same rule\n * {@link assessCollectionMetadata} applies to its own generic fallback, one layer up: whatever the metadata\n * reported already names this provider and, unless it was that same fallback, says something a consumer can\n * act on. Restating it in a weaker vocabulary is how one gap becomes two warnings with two different verdicts.\n *\n * `truncated` is the near-synonym that must NOT be used for that question: it comes from\n * {@link isIncompleteCollection}, which does not consider failures, so metadata carrying a typed failure\n * alongside `completeness: 'complete'` reports `truncated: false` while having already appended the warning\n * that explains everything. Gating on it would restate exactly the case this field exists to keep quiet.\n */\nexport function mergeAssessmentInto(\n\twarnings: ProviderWarning[],\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmetadata: CollectionMetadata | undefined,\n): { fetchFailed: boolean; truncated: boolean; reported: boolean } {\n\tconst assessment = assessCollectionMetadata(providerId, domain, connectionId, metadata);\n\tfor (const warning of assessment.warnings) {\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\treturn {\n\t\tfetchFailed: assessment.fetchFailed,\n\t\ttruncated: assessment.truncated,\n\t\t// What the assessment PRODUCED, not what `appendDedupedWarning` accepted: a warning dropped as a\n\t\t// duplicate is still one the consumer will see, so it still makes a restatement redundant.\n\t\treported: assessment.warnings.length > 0,\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"collectionMetadata.js","names":[],"sources":["../../../../plus/integrations/src/collectionMetadata.ts"],"sourcesContent":["import type {\n\tCollectionMetadata,\n\tCollectionOmission,\n\tCollectionScope,\n\tCollectionScopeFailure,\n} from '@gitkraken/provider-apis';\n// Matched by its `code` discriminator rather than by `instanceof`: the SDK ships one bundle per entry point, so\n// the class reached through the root is not the same object as the one reached through `/providers`.\nimport { isInvalidRequestError, isUnsupportedSortError } from '@gitkraken/provider-apis';\nimport {\n\tAuthenticationError,\n\tRequestClientError,\n\tRequestNotFoundError,\n\tRequestRateLimitError,\n} from '@gitlens/git/errors.js';\nimport type { TokenWithInfo } from './authentication/models.js';\nimport type { IntegrationIds } from './constants.js';\nimport type { ResponseHeaders } from './errors.js';\nimport { getResponseHeader, isProviderUnreachableError, isRateLimitResponse } from './errors.js';\nimport { getProviderResponseBodyMessage } from './providers/providerErrors.js';\nimport type {\n\tProviderWarning,\n\tProviderWarningCause,\n\tProviderWarningOmission,\n\tProviderWarningScope,\n\tProviderWarningUnreadableItems,\n} from './results.js';\nimport { appendDedupedWarning, providerWarningMessage } from './results.js';\n\n/**\n * Re-throws an error that is a fact about the CALL rather than about one scope of a fan-out.\n *\n * Every provider fan-out catches per-scope rejections and records them as {@link CollectionScopeFailure}s, which is\n * right for auth, rate limits and a missing project: those really did happen to one scope, and the siblings that\n * succeeded must survive. A caller-contract error is the opposite — it is identical for every scope and was decided\n * before any request went out — so degrading it would hand back N indistinguishable failures and an empty `partial`\n * page, describing an invalid call as an incomplete read.\n *\n * Today that means the SDK's `UnsupportedSortError` and `InvalidRequestError` — the two codes it lists in its own\n * `CALLER_CONTRACT_ERROR_CODES`, both documented as \"never degraded into a `CollectionScopeFailure` or an\n * omission\". The second covers arguments rejected before the first request goes out: a project key list longer\n * than one Jira search can carry (or an empty one), a non-integer `pageSize`, an unknown current-user filter, or a\n * Jira Server read with no `baseUrl` to address. Called from every fan-out rather than spelled out in each, so a\n * fan-out added later inherits the rule instead of silently omitting it — which is exactly how Azure's\n * account-wide drain came to degrade it while its three siblings did not.\n */\nexport function throwIfCallerContractError(ex: unknown): void {\n\tif (isUnsupportedSortError(ex) || isInvalidRequestError(ex)) throw ex;\n}\n\n/**\n * Whether a failed query that covered several scopes would fail each scope's own read the same way: a rejected or\n * rate-limited token, or a call the SDK refused before sending. Anything else (a query the provider rejects because\n * one scope is gone or no longer visible, a server error) may be one scope's problem, so the scopes are worth\n * reading on their own.\n */\nexport function failsEveryScope(ex: unknown): boolean {\n\tconst kind = toCollectionFailureKind(ex);\n\treturn (\n\t\tkind === 'authentication' || kind === 'rate-limit' || isUnsupportedSortError(ex) || isInvalidRequestError(ex)\n\t);\n}\n\n/**\n * Maps a caught GitLens request error to the SDK collection failure vocabulary used inside provider fan-outs.\n */\nexport function toCollectionFailureKind(ex: unknown): CollectionScopeFailure['kind'] {\n\tif (ex instanceof AuthenticationError) return 'authentication';\n\tif (ex instanceof RequestRateLimitError) return 'rate-limit';\n\tif (ex instanceof RequestNotFoundError) return 'not-found';\n\treturn 'provider';\n}\n\n/**\n * What an authentication refusal's response said, kept on the failure so the provider can name its cause once the\n * credential behind it is confirmed (see `IntegrationBase.confirmScopedAuthFailures`). Only these facts are kept,\n * never the response: its headers carry the provider's session cookies.\n */\nexport interface ProviderRefusal {\n\tstatus: number;\n\t/** The provider's own explanation: Azure DevOps' `X-TFS-ServiceError`, or the error body's message. */\n\tdetail?: string;\n\t/** The provider's error type, when the body names one (Azure DevOps `typeKey`). */\n\ttypeKey?: string;\n\t/**\n\t * The OAuth app the refused token belongs to, when the provider says (GitHub's `X-OAuth-Client-Id`). Not a\n\t * secret: it is the app's public client id, which the page where a user asks an organization to approve the app\n\t * is addressed by.\n\t */\n\toauthClientId?: string;\n}\n\n/** A scope failure as recorded here: the SDK's shape, plus what the refusal said and, once it is known, why. */\nexport interface ProviderScopeFailure extends CollectionScopeFailure {\n\trefusal?: ProviderRefusal;\n\tcause?: ProviderWarningCause;\n\t/** The provider pinned the refusal on the credential itself, so it is published for the connection, unscoped. */\n\tcredentialRefused?: true;\n\t/**\n\t * The failure is items a page left out because they could not be read, the rest of its scope having been read. It\n\t * is published as an `'unreadable-items'` warning naming them, see `ProviderWarning.unreadableItems`.\n\t */\n\tunreadableItems?: ProviderWarningUnreadableItems;\n}\n\n/**\n * Reads the refusal off an `AuthenticationError` that carries the provider's response: the SDK adapter's plain\n * `{ status, headers, body }` (`providersApi.ts`), Octokit's `{ status, headers, data }` (the GitHub client's REST\n * reads), or the `Response` of a `ProviderFetchError`, whose body was already read into its message.\n */\nfunction toProviderRefusal(ex: unknown): ProviderRefusal | undefined {\n\tif (!(ex instanceof AuthenticationError)) return undefined;\n\n\tconst response = (ex.original as { response?: unknown } | undefined)?.response as\n\t\t| { status?: unknown; headers?: ResponseHeaders; body?: unknown; data?: unknown }\n\t\t| undefined;\n\tif (typeof response?.status !== 'number') return undefined;\n\n\t// Only a structured body: a string one is a page (a proxy's, a sign-in form), not an explanation.\n\tconst raw = response.body ?? response.data;\n\tconst body = typeof raw === 'object' && raw != null ? raw : undefined;\n\tconst detail =\n\t\tdecodeServiceError(getResponseHeader(response.headers, 'x-tfs-serviceerror')) ??\n\t\tgetProviderResponseBodyMessage(body);\n\tconst typeKey = body != null && 'typeKey' in body ? body.typeKey : undefined;\n\tconst oauthClientId = getResponseHeader(response.headers, 'x-oauth-client-id');\n\n\treturn {\n\t\tstatus: response.status,\n\t\t...(detail ? { detail: detail } : {}),\n\t\t...(typeof typeKey === 'string' ? { typeKey: typeKey } : {}),\n\t\t...(typeof oauthClientId === 'string' && oauthClientId ? { oauthClientId: oauthClientId } : {}),\n\t};\n}\n\n/** Azure DevOps sends `X-TFS-ServiceError` percent-encoded. */\nfunction decodeServiceError(value: unknown): string | undefined {\n\tif (typeof value !== 'string' || !value) return undefined;\n\n\ttry {\n\t\treturn decodeURIComponent(value).trim() || undefined;\n\t} catch {\n\t\treturn value.trim() || undefined;\n\t}\n}\n\n/**\n * Builds a structured SDK scope failure from a caught GitLens request error.\n *\n * An authentication refusal is described in the provider's own words when it gives any, and otherwise by the\n * error the provider's response raised, sanitized like any warning's (a page becomes its status), rather than by\n * `AuthenticationError`'s generic \"credentials are either invalid or expired\": a scope refusing a sound credential\n * is the case this failure exists to tell apart, and that sentence says the opposite.\n */\nexport function toCollectionScopeFailure(scope: CollectionScopeFailure['scope'], ex: unknown): ProviderScopeFailure {\n\tconst kind = toCollectionFailureKind(ex);\n\tconst refusal = toProviderRefusal(ex);\n\tconst message =\n\t\trefusal != null ? (refusal.detail ?? providerWarningMessage((ex as AuthenticationError).original)) : undefined;\n\treturn {\n\t\tscope: scope,\n\t\tkind: kind,\n\t\t...(message ? { message: message } : ex instanceof Error && ex.message ? { message: ex.message } : {}),\n\t\t...(refusal != null ? { refusal: refusal } : {}),\n\t\t...(kind === 'provider' && isProviderUnreachableError(ex) ? { cause: { reason: 'unreachable' as const } } : {}),\n\t};\n}\n\nfunction collectionFailureKindToWarningKind(kind: CollectionScopeFailure['kind']): ProviderWarning['kind'] {\n\tswitch (kind) {\n\t\tcase 'authentication':\n\t\t\treturn 'auth';\n\t\tcase 'rate-limit':\n\t\t\treturn 'rate-limit';\n\t\tcase 'not-found':\n\t\t\treturn 'not-found';\n\t\tdefault:\n\t\t\treturn 'other';\n\t}\n}\n\nfunction getCollectionFailureStatus(message: string | undefined): number | undefined {\n\tif (message == null) return undefined;\n\n\tconst match = /\\((\\d{3})\\)/.exec(message) ?? /^(?:HTTP\\s+)?(\\d{3})\\b/i.exec(message.trim());\n\treturn match != null ? Number(match[1]) : undefined;\n}\n\nfunction toCollectionFailureWarningKind(failure: ProviderScopeFailure): ProviderWarning['kind'] {\n\t// First: the skipped items' own error text is embedded in the message and may well carry a status.\n\tif (failure.unreadableItems != null) return 'unreadable-items';\n\n\tconst mapped = collectionFailureKindToWarningKind(failure.kind);\n\tif (mapped !== 'other' || (failure.kind !== 'provider' && failure.kind !== 'unknown')) return mapped;\n\n\t// provider-apis currently groups several HTTP statuses under `provider` in partial fan-outs. The custom\n\t// fetch adapter retains the status in `message`, so recover the same neutral classification the thrown-error\n\t// path exposes instead of downgrading these structured failures to `other`.\n\tconst status = getCollectionFailureStatus(failure.message);\n\tswitch (status) {\n\t\tcase 401:\n\t\t\treturn 'auth';\n\t\tcase 403:\n\t\t\treturn isRateLimitResponse({ status: status, message: failure.message }) ? 'rate-limit' : 'auth';\n\t\tcase 404:\n\t\tcase 410:\n\t\tcase 422:\n\t\t\treturn 'not-found';\n\t\tcase 429:\n\t\t\treturn 'rate-limit';\n\t\tdefault:\n\t\t\treturn mapped;\n\t}\n}\n\nfunction toCollectionFailureCause(\n\tfailure: ProviderScopeFailure,\n\tkind: ProviderWarning['kind'],\n): ProviderWarningCause | undefined {\n\treturn (\n\t\tfailure.cause ??\n\t\t(kind === 'other' &&\n\t\tisProviderUnreachableError({ ...failure, status: getCollectionFailureStatus(failure.message) })\n\t\t\t? { reason: 'unreachable' }\n\t\t\t: undefined)\n\t);\n}\n\nexport function toCollectionFailureError(failure: ProviderScopeFailure, tokenWithInfo: TokenWithInfo): Error {\n\tconst kind = toCollectionFailureWarningKind(failure);\n\tconst error = new Error(failure.message ?? 'Provider request failed', {\n\t\tcause: toCollectionFailureCause(failure, kind),\n\t});\n\tconst { accessToken, ...tokenInfo } = tokenWithInfo;\n\tif (kind === 'auth') return new AuthenticationError(tokenInfo, error.message, error);\n\tif (kind === 'rate-limit') return new RequestRateLimitError(error, accessToken, undefined);\n\tif (kind === 'not-found') return new RequestNotFoundError(error);\n\n\tconst status = getCollectionFailureStatus(failure.message);\n\tif (kind === 'other' && status != null && status >= 400 && status < 500) {\n\t\treturn new RequestClientError(error);\n\t}\n\n\treturn error;\n}\n\n/** ` (resource r, project p, repository o/n)` for the scope IDs present; empty when the scope names none. */\nfunction collectionScopeText(scope: CollectionScope | undefined): string {\n\tconst parts: string[] = [];\n\tif (scope?.resourceId != null) {\n\t\tparts.push(`resource ${scope.resourceId}`);\n\t}\n\tif (scope?.projectId != null) {\n\t\tparts.push(`project ${scope.projectId}`);\n\t}\n\tif (scope?.repositoryId != null) {\n\t\tparts.push(`repository ${scope.repositoryId}`);\n\t}\n\n\treturn parts.length ? ` (${parts.join(', ')})` : '';\n}\n\n/** The cause in the consumer's terms, for the prose that goes with the structured {@link ProviderWarningCause}. */\nfunction causeText(cause: ProviderWarningCause): string {\n\tswitch (cause.reason) {\n\t\tcase 'oauth-app-not-allowed':\n\t\t\treturn 'the organization does not allow third-party OAuth apps';\n\t\tcase 'access-denied':\n\t\t\treturn 'the account has no access to it';\n\t\tcase 'conditional-access':\n\t\t\treturn 'a Conditional Access policy blocked the request';\n\t\tcase 'unreachable':\n\t\t\treturn 'the provider server could not be reached';\n\t\tdefault:\n\t\t\tcause.reason satisfies never;\n\t\t\treturn 'the provider refused the request';\n\t}\n}\n\nfunction collectionFailureMessage(failure: ProviderScopeFailure): string {\n\tconst text = failure.cause != null ? causeText(failure.cause) : failure.message;\n\tconst detail = text != null ? `: ${text}` : '';\n\treturn `Failed to read ${failure.kind} scope${collectionScopeText(failure.scope)}${detail}`;\n}\n\n/**\n * Forwards the part of a failure's scope below the provider, so a consumer can tell \"one organization refused\n * this token\" from \"the connection's token is dead\" without parsing {@link collectionFailureMessage}.\n *\n * `providerId` is dropped: the warning already names its provider, and a scope naming nothing below it is a\n * failure of the connection itself. That is the distinction `getOrganizationsForUserResult` also draws before\n * counting an auth failure against the connection, though it reads a scope with no keys but `providerId`, so an\n * absent scope does not count there. Forwarding such a scope would make an account-wide failure look confined.\n *\n * Copied field by field, for the reason {@link toProviderWarningOmission} copies its scope.\n */\nfunction toProviderWarningScope(scope: CollectionScope | undefined): ProviderWarningScope | undefined {\n\tif (scope == null) return undefined;\n\n\tconst { resourceId, projectId, repositoryId } = scope;\n\tif (resourceId == null && projectId == null && repositoryId == null) return undefined;\n\n\treturn {\n\t\t...(resourceId != null ? { resourceId: resourceId } : {}),\n\t\t...(projectId != null ? { projectId: projectId } : {}),\n\t\t...(repositoryId != null ? { repositoryId: repositoryId } : {}),\n\t};\n}\n\n/**\n * Whether `metadata` reports authentication failures only for scopes below the provider, with none for the\n * connection itself. A credential revoked since a provider cached its discovery produces exactly this shape,\n * the same one a scope's own refusal does; `IntegrationBase.confirmScopedAuthFailures` tells them apart.\n *\n * Classified exactly as {@link assessCollectionMetadata} classifies the warnings, so it agrees with what a\n * consumer would be shown.\n */\nexport function hasOnlyScopedAuthFailures(metadata: CollectionMetadata | undefined): boolean {\n\tlet scoped = false;\n\tfor (const failure of metadata?.failures ?? []) {\n\t\tif (toCollectionFailureWarningKind(failure) !== 'auth') continue;\n\t\tif (toProviderWarningScope(failure.scope) == null || (failure as ProviderScopeFailure).credentialRefused) {\n\t\t\treturn false;\n\t\t}\n\n\t\tscoped = true;\n\t}\n\treturn scoped;\n}\n\n/** The scoped authentication failures in `metadata` that kept their refusal, with the scope they are forwarded as. */\nfunction* refusedScopes(\n\tmetadata: CollectionMetadata | undefined,\n): Generator<{ failure: ProviderScopeFailure & { refusal: ProviderRefusal }; scope: ProviderWarningScope }> {\n\tfor (const failure of (metadata?.failures ?? []) as ProviderScopeFailure[]) {\n\t\tif (\n\t\t\tfailure.refusal == null ||\n\t\t\tfailure.credentialRefused ||\n\t\t\ttoCollectionFailureWarningKind(failure) !== 'auth'\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst scope = toProviderWarningScope(failure.scope);\n\t\tif (scope == null) continue;\n\n\t\tyield { failure: failure as ProviderScopeFailure & { refusal: ProviderRefusal }, scope: scope };\n\t}\n}\n\n/**\n * Marks each scoped authentication failure in `metadata` whose refusal the provider pins on the credential, so it is\n * published for the connection rather than for its scope. Returns whether any was.\n */\nexport function markCredentialRefusals(\n\tmetadata: CollectionMetadata | undefined,\n\tisCredentialRefusal: (refusal: ProviderRefusal) => boolean,\n): boolean {\n\tlet marked = false;\n\tfor (const { failure } of refusedScopes(metadata)) {\n\t\tif (!isCredentialRefusal(failure.refusal)) continue;\n\n\t\tfailure.credentialRefused = true;\n\t\tmarked = true;\n\t}\n\treturn marked;\n}\n\n/**\n * Names the cause of each scoped authentication failure in `metadata` that kept its refusal. Only for a read whose\n * credential was just confirmed: before that, the refusals this can name look exactly like a dead credential.\n */\nexport function attributeScopedAuthFailures(\n\tmetadata: CollectionMetadata | undefined,\n\tdescribe: (refusal: ProviderRefusal, scope: ProviderWarningScope) => ProviderWarningCause | undefined,\n): void {\n\tfor (const { failure, scope } of refusedScopes(metadata)) {\n\t\tif (failure.cause != null) continue;\n\n\t\tconst cause = describe(failure.refusal, scope);\n\t\tif (cause != null) {\n\t\t\tfailure.cause = cause;\n\t\t}\n\t}\n}\n\n/**\n * Explains one omission in the consumer's terms — what was left out and, where the SDK reports it, how much.\n *\n * An omission is a completeness fact, never a failure: the read succeeded, and the provider (or the SDK's own\n * recovery budget) is what withheld results. That is why these never contribute to `fetchFailed`. Whether a\n * retry would recover anything is a separate question this layer cannot answer — see the `recovery` note on\n * {@link toProviderWarningOmission}.\n */\nfunction collectionOmissionMessage(omission: CollectionOmission): string {\n\tconst scopeText = collectionScopeText(omission.scope);\n\tswitch (omission.kind) {\n\t\tcase 'provider-limit':\n\t\t\t// `totalCount` is the only figure saying how much was withheld; both fields are optional, so degrade\n\t\t\t// through the shapes the SDK can actually emit rather than printing `undefined`.\n\t\t\tif (omission.totalCount != null && omission.limit != null) {\n\t\t\t\treturn `Search${scopeText} matched ${omission.totalCount} results, but the provider exposes at most ${omission.limit}`;\n\t\t\t}\n\t\t\tif (omission.limit != null) {\n\t\t\t\treturn `Search${scopeText} exceeded the provider limit of ${omission.limit} results`;\n\t\t\t}\n\t\t\treturn `Search${scopeText} exceeded the provider's result limit`;\n\t\tcase 'recovery-budget':\n\t\t\treturn `Stopped recovering omitted results${scopeText} after reaching the request budget${\n\t\t\t\tomission.limit != null ? ` of ${omission.limit} requests` : ''\n\t\t\t}`;\n\t\tcase 'pagination-incomplete':\n\t\t\treturn `More results are available${scopeText} than this read returned`;\n\t\tdefault:\n\t\t\t// `CollectionOmissionKind` is a closed union, so this is unreachable today. Kept as a compile-time\n\t\t\t// guard rather than a silent fallback: an SDK bump that adds a kind breaks the build here instead\n\t\t\t// of quietly degrading it to a vague message.\n\t\t\tomission.kind satisfies never;\n\t\t\treturn `Some results were omitted${scopeText}; the read is incomplete`;\n\t}\n}\n\n/**\n * Forwards an SDK omission as the structured signal consumers read instead of parsing {@link\n * collectionOmissionMessage}'s prose.\n *\n * `results.ts` re-spells `CollectionOmissionKind` rather than importing it, so the published warning surface\n * carries no `@gitkraken/provider-apis` types (see the export block in `index.ts`). The return type is what\n * keeps the two unions honest: an SDK bump that adds a member fails to compile here, alongside\n * `collectionOmissionMessage`'s `satisfies never`.\n *\n * `scope` is copied because the SDK's own object is retained and re-merged across drained pages\n * (`providerPaging.ts`), and this one crosses the package boundary.\n *\n * `recovery` is always `'none'`, deliberately, and a scoped `pagination-incomplete` is why it looks wrong: the\n * SDK emits that one shape from situations with opposite remedies and says so itself — \"Either the read\n * deliberately took one page per scope, or the provider advertised another page it gave no way to reach.\" Its\n * `collectAcrossScopes` producers (Azure, Bitbucket, Bitbucket Server) are the first, its `drainAcrossScopes`\n * producers (GitLab, Jira) the second, and those report an omission only when a cursor STALLED — so re-reading\n * that scope stalls at the identical page. Nothing in `CollectionOmission` separates the two, so claiming\n * recoverability would ship the dead-end button `recovery` exists to prevent. Making the recoverable case\n * claimable needs provider-apis to say whether a scope was drained or merely sampled: a fact to forward, not\n * to guess here.\n */\nfunction toProviderWarningOmission(omission: CollectionOmission): ProviderWarningOmission {\n\treturn {\n\t\tkind: omission.kind,\n\t\trecovery: 'none',\n\t\t...(omission.limit != null ? { limit: omission.limit } : {}),\n\t\t// The SDK types this `number | null | undefined`; normalize to one absence for consumers.\n\t\t...(omission.totalCount != null ? { totalCount: omission.totalCount } : {}),\n\t\t...(omission.scope != null ? { scope: { ...omission.scope } } : {}),\n\t};\n}\n\n/**\n * Whether SDK metadata describes a read that may be missing results.\n *\n * A reported omission counts on its own rather than deferring to `completeness`. The SDK does couple the\n * two — every site that emits an omission degrades completeness in the same call — so today the omission\n * clause changes no outcome. It is defensive: the two facts arrive as independent fields, and a read that\n * names what it left out must not be publishable as whole if a future producer sets only one of them.\n *\n * This is the single definition of \"may be missing results\", shared with `getPagedResult`, so one metadata\n * object cannot be truncated at one layer and complete at another.\n */\nexport function isIncompleteCollection(metadata: CollectionMetadata | undefined): boolean {\n\tif (metadata == null) return false;\n\n\treturn metadata.completeness !== 'complete' || (metadata.omissions?.length ?? 0) > 0;\n}\n\n/**\n * Converts internal SDK collection metadata into neutral provider facade signals.\n *\n * `requestFailed` is `fetchFailed` without the failures that only report items a read left unread: those fail the read,\n * since the items are missing, but every request answered, so the omissions the read reported still hold.\n */\nexport function assessCollectionMetadata(\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmetadata: CollectionMetadata | undefined,\n): { warnings: ProviderWarning[]; fetchFailed: boolean; requestFailed: boolean; truncated: boolean } {\n\tif (metadata == null) return { warnings: [], fetchFailed: false, requestFailed: false, truncated: false };\n\n\tconst warnings: ProviderWarning[] = [];\n\tconst failures: ProviderScopeFailure[] = metadata.failures ?? [];\n\tfor (const failure of failures) {\n\t\tconst kind = toCollectionFailureWarningKind(failure);\n\t\tconst cause = toCollectionFailureCause(failure, kind);\n\t\tconst scope = failure.credentialRefused ? undefined : toProviderWarningScope(failure.scope);\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\t// A credential-level refusal names no scope in prose either, so every scope's copy of it is one warning.\n\t\t\tmessage: collectionFailureMessage({\n\t\t\t\t...failure,\n\t\t\t\tcause: cause,\n\t\t\t\t...(failure.credentialRefused ? { scope: undefined } : {}),\n\t\t\t}),\n\t\t\tkind: kind,\n\t\t\tisAuth: kind === 'auth',\n\t\t\t...(scope != null ? { scope: scope } : {}),\n\t\t\t// Copied for the reason the scope is: the failure object is retained and re-merged across pages.\n\t\t\t...(cause != null ? { cause: { ...cause } } : {}),\n\t\t\t...(failure.unreadableItems != null\n\t\t\t\t? {\n\t\t\t\t\t\tunreadableItems: {\n\t\t\t\t\t\t\tkeys: [...failure.unreadableItems.keys],\n\t\t\t\t\t\t\tcomplete: failure.unreadableItems.complete,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t});\n\t}\n\n\t// Omissions explain WHY a read is incomplete when nothing failed — a provider cap, an exhausted recovery\n\t// budget, an undrained scope. They classify as `other`, never `auth`, and are deliberately excluded from\n\t// `fetchFailed` below: the request itself succeeded. `omission` carries that same fact structurally, so a\n\t// consumer can act on it without parsing the message.\n\tconst omissions = metadata.omissions ?? [];\n\tfor (const omission of omissions) {\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\tmessage: collectionOmissionMessage(omission),\n\t\t\tkind: 'other',\n\t\t\tisAuth: false,\n\t\t\tomission: toProviderWarningOmission(omission),\n\t\t});\n\t}\n\n\tconst incomplete = isIncompleteCollection(metadata);\n\t// Only fall back to the generic message when nothing more specific was reported; an omission already\n\t// explains the incompleteness in the consumer's terms, so adding this on top would be noise.\n\t//\n\t// This one deliberately carries no `omission`: it fires precisely when the SDK reported incompleteness\n\t// WITHOUT saying what was left out, so there is no structured fact to forward and synthesizing one would\n\t// assert a specificity this layer does not have.\n\tif (incomplete && failures.length === 0 && omissions.length === 0) {\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\tmessage:\n\t\t\t\tmetadata.completeness === 'partial'\n\t\t\t\t\t? 'Some results were omitted; the read is incomplete'\n\t\t\t\t\t: 'Result completeness could not be confirmed',\n\t\t\tkind: 'other',\n\t\t\tisAuth: false,\n\t\t});\n\t}\n\n\treturn {\n\t\twarnings: warnings,\n\t\tfetchFailed: failures.length > 0,\n\t\trequestFailed: failures.some(failure => failure.unreadableItems == null),\n\t\ttruncated: incomplete,\n\t};\n}\n\n/**\n * Appends neutral warnings derived from SDK metadata and returns its failure/truncation assessment.\n *\n * `reported` says whether the metadata described this read's incompleteness at all. A caller that would\n * otherwise add its own generic \"this was truncated\" warning uses it to stay quiet — the same rule\n * {@link assessCollectionMetadata} applies to its own generic fallback, one layer up: whatever the metadata\n * reported already names this provider and, unless it was that same fallback, says something a consumer can\n * act on. Restating it in a weaker vocabulary is how one gap becomes two warnings with two different verdicts.\n *\n * `truncated` is the near-synonym that must NOT be used for that question: it comes from\n * {@link isIncompleteCollection}, which does not consider failures, so metadata carrying a typed failure\n * alongside `completeness: 'complete'` reports `truncated: false` while having already appended the warning\n * that explains everything. Gating on it would restate exactly the case this field exists to keep quiet.\n */\nexport function mergeAssessmentInto(\n\twarnings: ProviderWarning[],\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmetadata: CollectionMetadata | undefined,\n): { fetchFailed: boolean; truncated: boolean; reported: boolean } {\n\tconst assessment = assessCollectionMetadata(providerId, domain, connectionId, metadata);\n\tfor (const warning of assessment.warnings) {\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\treturn {\n\t\tfetchFailed: assessment.fetchFailed,\n\t\ttruncated: assessment.truncated,\n\t\t// What the assessment PRODUCED, not what `appendDedupedWarning` accepted: a warning dropped as a\n\t\t// duplicate is still one the consumer will see, so it still makes a restatement redundant.\n\t\treported: assessment.warnings.length > 0,\n\t};\n}\n"],"mappings":"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"}
|