@threadbase-sh/scanner 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -898,8 +898,16 @@ async function saveProfiles(profiles, configPath) {
898
898
  var import_fast_glob = __toESM(require("fast-glob"), 1);
899
899
  var import_fs4 = require("fs");
900
900
  var import_promises2 = require("fs/promises");
901
- var import_path5 = require("path");
901
+ var import_path6 = require("path");
902
902
  var import_readline2 = require("readline");
903
+
904
+ // src/canonical-path.ts
905
+ var import_path5 = require("path");
906
+ function canonicalPath(p) {
907
+ return import_path5.sep === "\\" ? p.replace(/\//g, "\\") : p;
908
+ }
909
+
910
+ // src/providers/codex-cli.ts
903
911
  var CodexCliProvider = class {
904
912
  name = CODEX_CLI_PROVIDER;
905
913
  async discover(roots) {
@@ -921,7 +929,7 @@ var CodexCliProvider = class {
921
929
  for (const filePath of paths) {
922
930
  try {
923
931
  const s = await (0, import_promises2.stat)(filePath);
924
- if (s.size > 0) results.push({ filePath, account: "codex" });
932
+ if (s.size > 0) results.push({ filePath: canonicalPath(filePath), account: "codex" });
925
933
  } catch (err) {
926
934
  log.warn({ filePath, err }, "codex discovery: stat failed");
927
935
  }
@@ -1032,7 +1040,7 @@ function reduceCodexEntry(acc, entry, tier) {
1032
1040
  }
1033
1041
  function finalizeCodexMeta(acc, filePath, account, tier) {
1034
1042
  if (acc.messageCount === 0) return null;
1035
- const sessionId = acc.sessionId || (0, import_path5.basename)(filePath, ".jsonl");
1043
+ const sessionId = acc.sessionId || (0, import_path6.basename)(filePath, ".jsonl");
1036
1044
  const projectPath = acc.cwd;
1037
1045
  const kind = acc.lastAssistant === null && acc.toolNames.length > 0 ? "task" : "conversation";
1038
1046
  return {
@@ -1112,7 +1120,7 @@ async function parseCodexConversation(filePath, account) {
1112
1120
  filePath,
1113
1121
  projectPath: cwd,
1114
1122
  projectName: getShortProjectName3(cwd),
1115
- sessionId: sessionId || (0, import_path5.basename)(filePath, ".jsonl"),
1123
+ sessionId: sessionId || (0, import_path6.basename)(filePath, ".jsonl"),
1116
1124
  sessionName: "",
1117
1125
  messages,
1118
1126
  fullText: textParts.join(" "),
@@ -1164,7 +1172,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
1164
1172
  if (size < 0) {
1165
1173
  skippedInaccessible++;
1166
1174
  } else if (size > 0) {
1167
- results.push({ filePath, account });
1175
+ results.push({ filePath: canonicalPath(filePath), account });
1168
1176
  kept++;
1169
1177
  } else {
1170
1178
  skippedEmpty++;
@@ -1229,7 +1237,7 @@ var ThreadbaseProvider = class {
1229
1237
  var import_events = require("events");
1230
1238
  var import_fs11 = require("fs");
1231
1239
  var import_os2 = require("os");
1232
- var import_path9 = require("path");
1240
+ var import_path11 = require("path");
1233
1241
 
1234
1242
  // src/cache.ts
1235
1243
  var LRUCache = class {
@@ -1268,7 +1276,7 @@ var LRUCache = class {
1268
1276
  };
1269
1277
 
1270
1278
  // src/persistent/conversation-stream.ts
1271
- var import_path6 = require("path");
1279
+ var import_path7 = require("path");
1272
1280
 
1273
1281
  // src/persistent/paged-reader.ts
1274
1282
  var import_fs6 = require("fs");
@@ -1426,7 +1434,7 @@ function assemble(filePath, account, messages, fullText, turnDurations, state) {
1426
1434
  filePath,
1427
1435
  projectPath: state.cwd,
1428
1436
  projectName: getShortProjectName2(state.cwd),
1429
- sessionId: state.sessionId || (0, import_path6.basename)(filePath, ".jsonl"),
1437
+ sessionId: state.sessionId || (0, import_path7.basename)(filePath, ".jsonl"),
1430
1438
  sessionName: state.sessionName,
1431
1439
  messages,
1432
1440
  fullText,
@@ -1577,7 +1585,7 @@ function resolveTier(tierName, customTiers) {
1577
1585
  // src/persistent/db.ts
1578
1586
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
1579
1587
  var import_fs9 = require("fs");
1580
- var import_path7 = require("path");
1588
+ var import_path8 = require("path");
1581
1589
 
1582
1590
  // src/persistent/schema.ts
1583
1591
  var SCHEMA_VERSION = 4;
@@ -1770,7 +1778,7 @@ function hasColumn(db, table, column) {
1770
1778
  // src/persistent/db.ts
1771
1779
  function openDatabase(dbPath) {
1772
1780
  if (dbPath !== ":memory:") {
1773
- (0, import_fs9.mkdirSync)((0, import_path7.dirname)(dbPath), { recursive: true });
1781
+ (0, import_fs9.mkdirSync)((0, import_path8.dirname)(dbPath), { recursive: true });
1774
1782
  }
1775
1783
  const db = new import_better_sqlite3.default(dbPath);
1776
1784
  db.pragma("journal_mode = WAL");
@@ -1784,6 +1792,7 @@ function openDatabase(dbPath) {
1784
1792
 
1785
1793
  // src/persistent/dir-watermark.ts
1786
1794
  var import_promises6 = require("fs/promises");
1795
+ var import_path9 = require("path");
1787
1796
  var FULL_RECONCILE_EVERY_N_SCANS = 20;
1788
1797
  async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
1789
1798
  const log = getLogger();
@@ -1793,7 +1802,7 @@ async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
1793
1802
  const results = [];
1794
1803
  for (const rawDir of dirs) {
1795
1804
  const { account } = rawDir;
1796
- const projectsDir = rawDir.projectsDir.replace(/\\/g, "/");
1805
+ const projectsDir = canonicalPath(rawDir.projectsDir);
1797
1806
  const resolved = await resolveProjectDirs(projectsDir, scannedDirs);
1798
1807
  if (resolved === null) continue;
1799
1808
  for (const projectDir of resolved.entries) {
@@ -1854,11 +1863,10 @@ async function resolveProjectDirs(projectsDir, scannedDirs) {
1854
1863
  return { entries, commitRoot: { mtimeMs: rootStat.mtimeMs } };
1855
1864
  }
1856
1865
  function dirnameOf(filePath) {
1857
- const idx = filePath.lastIndexOf("/");
1858
- return idx === -1 ? filePath : filePath.slice(0, idx);
1866
+ return (0, import_path9.dirname)(filePath);
1859
1867
  }
1860
1868
  function joinPath(dir, name) {
1861
- return dir.endsWith("/") ? `${dir}${name}` : `${dir}/${name}`;
1869
+ return (0, import_path9.join)(dir, name);
1862
1870
  }
1863
1871
 
1864
1872
  // src/persistent/repositories/checkpoints.repo.ts
@@ -1868,15 +1876,16 @@ var CheckpointsRepo = class {
1868
1876
  }
1869
1877
  db;
1870
1878
  replaceAll(sourcePath, checkpoints) {
1879
+ const path = canonicalPath(sourcePath);
1871
1880
  const tx = this.db.transaction(() => {
1872
- this.db.prepare("DELETE FROM message_checkpoints WHERE source_path = ?").run(sourcePath);
1881
+ this.db.prepare("DELETE FROM message_checkpoints WHERE source_path = ?").run(path);
1873
1882
  const insert = this.db.prepare(
1874
1883
  `INSERT INTO message_checkpoints
1875
1884
  (source_path, message_index, byte_offset, line_number, parser_state)
1876
1885
  VALUES (?, ?, ?, ?, ?)`
1877
1886
  );
1878
1887
  for (const c of checkpoints) {
1879
- insert.run(sourcePath, c.messageIndex, c.byteOffset, c.lineNumber, JSON.stringify(c.state));
1888
+ insert.run(path, c.messageIndex, c.byteOffset, c.lineNumber, JSON.stringify(c.state));
1880
1889
  }
1881
1890
  });
1882
1891
  tx();
@@ -1885,6 +1894,7 @@ var CheckpointsRepo = class {
1885
1894
  // the chain covering the immutable prefix (Kafka sparse-index style); rows are
1886
1895
  // only ever removed on truncation/replace or deletion.
1887
1896
  append(sourcePath, checkpoints) {
1897
+ const path = canonicalPath(sourcePath);
1888
1898
  const tx = this.db.transaction(() => {
1889
1899
  const insert = this.db.prepare(
1890
1900
  `INSERT INTO message_checkpoints
@@ -1892,7 +1902,7 @@ var CheckpointsRepo = class {
1892
1902
  VALUES (?, ?, ?, ?, ?)`
1893
1903
  );
1894
1904
  for (const c of checkpoints) {
1895
- insert.run(sourcePath, c.messageIndex, c.byteOffset, c.lineNumber, JSON.stringify(c.state));
1905
+ insert.run(path, c.messageIndex, c.byteOffset, c.lineNumber, JSON.stringify(c.state));
1896
1906
  }
1897
1907
  });
1898
1908
  tx();
@@ -1905,7 +1915,7 @@ var CheckpointsRepo = class {
1905
1915
  FROM message_checkpoints
1906
1916
  WHERE source_path = ? AND message_index <= ?
1907
1917
  ORDER BY message_index DESC LIMIT 1`
1908
- ).get(sourcePath, messageIndex);
1918
+ ).get(canonicalPath(sourcePath), messageIndex);
1909
1919
  return row ? toCheckpoint(row) : null;
1910
1920
  }
1911
1921
  // The highest-index checkpoint for a file, or null if none. The resume point
@@ -1916,14 +1926,14 @@ var CheckpointsRepo = class {
1916
1926
  FROM message_checkpoints
1917
1927
  WHERE source_path = ?
1918
1928
  ORDER BY message_index DESC LIMIT 1`
1919
- ).get(sourcePath);
1929
+ ).get(canonicalPath(sourcePath));
1920
1930
  return row ? toCheckpoint(row) : null;
1921
1931
  }
1922
1932
  count(sourcePath) {
1923
- return this.db.prepare("SELECT COUNT(*) AS n FROM message_checkpoints WHERE source_path = ?").get(sourcePath).n;
1933
+ return this.db.prepare("SELECT COUNT(*) AS n FROM message_checkpoints WHERE source_path = ?").get(canonicalPath(sourcePath)).n;
1924
1934
  }
1925
1935
  remove(sourcePath) {
1926
- this.db.prepare("DELETE FROM message_checkpoints WHERE source_path = ?").run(sourcePath);
1936
+ this.db.prepare("DELETE FROM message_checkpoints WHERE source_path = ?").run(canonicalPath(sourcePath));
1927
1937
  }
1928
1938
  };
1929
1939
  function toCheckpoint(row) {
@@ -1936,24 +1946,30 @@ function toCheckpoint(row) {
1936
1946
  }
1937
1947
 
1938
1948
  // src/persistent/repositories/conversation-files.repo.ts
1939
- var import_path8 = require("path");
1949
+ var import_path10 = require("path");
1940
1950
  var ConversationFilesRepo = class {
1941
1951
  constructor(db) {
1942
1952
  this.db = db;
1943
1953
  }
1944
1954
  db;
1945
1955
  getByPath(absolutePath) {
1946
- return this.db.prepare("SELECT * FROM conversation_files WHERE absolute_path = ?").get(absolutePath);
1956
+ return this.db.prepare("SELECT * FROM conversation_files WHERE absolute_path = ?").get(canonicalPath(absolutePath));
1947
1957
  }
1948
1958
  // Insert a freshly-discovered file at offset 0; returns its row id. Existing
1949
1959
  // path is left untouched (returns the existing id) so a re-discovery is safe.
1960
+ //
1961
+ // absolute_path/parent_dir are stored canonicalized so a caller that built the
1962
+ // path with the native separator (watcher, path.join) and one that got it from
1963
+ // fast-glob (forward slashes, even on Windows) land on the same row instead of
1964
+ // inserting a duplicate.
1950
1965
  ensure(absolutePath, account) {
1951
- const existing = this.getByPath(absolutePath);
1966
+ const canonical = canonicalPath(absolutePath);
1967
+ const existing = this.getByPath(canonical);
1952
1968
  if (existing) return existing.id;
1953
1969
  const info = this.db.prepare(
1954
1970
  `INSERT INTO conversation_files (absolute_path, parent_dir, file_name, account)
1955
1971
  VALUES (?, ?, ?, ?)`
1956
- ).run(absolutePath, (0, import_path8.dirname)(absolutePath), (0, import_path8.basename)(absolutePath), account);
1972
+ ).run(canonical, (0, import_path10.dirname)(canonical), (0, import_path10.basename)(canonical), account);
1957
1973
  return Number(info.lastInsertRowid);
1958
1974
  }
1959
1975
  // Advance the cursor + persisted reducer state after a successful index pass.
@@ -2015,7 +2031,7 @@ var ConversationFilesRepo = class {
2015
2031
  activePathsByParentDir(parentDir) {
2016
2032
  return this.db.prepare(
2017
2033
  "SELECT absolute_path, account FROM conversation_files WHERE parent_dir = ? AND status != 'deleted'"
2018
- ).all(parentDir);
2034
+ ).all(canonicalPath(parentDir));
2019
2035
  }
2020
2036
  };
2021
2037
 
@@ -2110,7 +2126,10 @@ var ConversationsRepo = class {
2110
2126
  updated_at = CURRENT_TIMESTAMP`
2111
2127
  ).run({
2112
2128
  file_id: fileId,
2113
- source_path: meta.id,
2129
+ // Canonical form, matching conversation_files.absolute_path — otherwise
2130
+ // a native-separator caller writes a second spelling of the same file
2131
+ // that every by-path lookup (and the deletion-reconcile) then misses.
2132
+ source_path: canonicalPath(meta.id),
2114
2133
  provider: meta.provider ?? CLAUDE_CODE_PROVIDER,
2115
2134
  kind: meta.kind ?? null,
2116
2135
  external_session_id: meta.externalSessionId ?? null,
@@ -2140,7 +2159,7 @@ var ConversationsRepo = class {
2140
2159
  });
2141
2160
  }
2142
2161
  getBySourcePath(sourcePath) {
2143
- const row = this.db.prepare("SELECT * FROM conversations WHERE source_path = ? AND status = 'active'").get(sourcePath);
2162
+ const row = this.db.prepare("SELECT * FROM conversations WHERE source_path = ? AND status = 'active'").get(canonicalPath(sourcePath));
2144
2163
  return row ? rowToMeta(row) : null;
2145
2164
  }
2146
2165
  // Dual lookup matching scanner.getConversation: resolve by source_path (the
@@ -2204,7 +2223,7 @@ var ConversationsRepo = class {
2204
2223
  pageMessageCount(sourcePath) {
2205
2224
  const row = this.db.prepare(
2206
2225
  "SELECT page_message_count AS n FROM conversations WHERE source_path = ? AND status = 'active'"
2207
- ).get(sourcePath);
2226
+ ).get(canonicalPath(sourcePath));
2208
2227
  return row?.n ?? 0;
2209
2228
  }
2210
2229
  count() {
@@ -2219,14 +2238,15 @@ var FtsRepo = class {
2219
2238
  }
2220
2239
  db;
2221
2240
  upsert(meta) {
2241
+ const sourcePath = canonicalPath(meta.id);
2222
2242
  const tx = this.db.transaction(() => {
2223
- this.db.prepare("DELETE FROM conversation_messages_fts WHERE source_path = ?").run(meta.id);
2243
+ this.db.prepare("DELETE FROM conversation_messages_fts WHERE source_path = ?").run(sourcePath);
2224
2244
  this.db.prepare(
2225
2245
  `INSERT INTO conversation_messages_fts
2226
2246
  (source_path, content, project_name, session_id, session_name, account, model, branch, tool_names)
2227
2247
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
2228
2248
  ).run(
2229
- meta.id,
2249
+ sourcePath,
2230
2250
  meta.contentSnippet ?? "",
2231
2251
  meta.projectName ?? "",
2232
2252
  meta.sessionId ?? "",
@@ -2240,7 +2260,7 @@ var FtsRepo = class {
2240
2260
  tx();
2241
2261
  }
2242
2262
  remove(sourcePath) {
2243
- this.db.prepare("DELETE FROM conversation_messages_fts WHERE source_path = ?").run(sourcePath);
2263
+ this.db.prepare("DELETE FROM conversation_messages_fts WHERE source_path = ?").run(canonicalPath(sourcePath));
2244
2264
  }
2245
2265
  // Ranked source_paths matching the query, best first. Returns [] on an empty
2246
2266
  // query (callers fall back to a recency listing).
@@ -2272,11 +2292,11 @@ var ScannedDirsRepo = class {
2272
2292
  }
2273
2293
  db;
2274
2294
  get(path) {
2275
- return this.db.prepare("SELECT * FROM scanned_dirs WHERE path = ?").get(path);
2295
+ return this.db.prepare("SELECT * FROM scanned_dirs WHERE path = ?").get(canonicalPath(path));
2276
2296
  }
2277
2297
  // Known project subdirectories under a root, path ascending (stable order).
2278
2298
  childrenOf(parentRoot) {
2279
- return this.db.prepare("SELECT * FROM scanned_dirs WHERE parent_root = ? ORDER BY path ASC").all(parentRoot);
2299
+ return this.db.prepare("SELECT * FROM scanned_dirs WHERE parent_root = ? ORDER BY path ASC").all(canonicalPath(parentRoot));
2280
2300
  }
2281
2301
  upsert(path, parentRoot, mtimeMs, hasNested) {
2282
2302
  this.db.prepare(
@@ -2287,11 +2307,16 @@ var ScannedDirsRepo = class {
2287
2307
  mtime_ms = excluded.mtime_ms,
2288
2308
  has_nested = excluded.has_nested,
2289
2309
  updated_at = CURRENT_TIMESTAMP`
2290
- ).run(path, parentRoot, mtimeMs, hasNested ? 1 : 0);
2310
+ ).run(
2311
+ canonicalPath(path),
2312
+ parentRoot === null ? null : canonicalPath(parentRoot),
2313
+ mtimeMs,
2314
+ hasNested ? 1 : 0
2315
+ );
2291
2316
  }
2292
2317
  // Drop a project subdirectory's watermark row (it vanished from disk).
2293
2318
  remove(path) {
2294
- this.db.prepare("DELETE FROM scanned_dirs WHERE path = ?").run(path);
2319
+ this.db.prepare("DELETE FROM scanned_dirs WHERE path = ?").run(canonicalPath(path));
2295
2320
  }
2296
2321
  };
2297
2322
 
@@ -2391,7 +2416,7 @@ var PersistentEngine = class {
2391
2416
  if (enabled.includes(CODEX_CLI_PROVIDER) && (options.codexRoots?.length ?? 0) > 0) {
2392
2417
  coveredAccounts.add("codex");
2393
2418
  }
2394
- const seen = new Set(discovered.map((d) => d.filePath));
2419
+ const seen = new Set(discovered.map((d) => canonicalPath(d.filePath)));
2395
2420
  for (const path of this.files.activePathsByAccounts([...coveredAccounts])) {
2396
2421
  if (!seen.has(path)) this.markDeleted(path);
2397
2422
  }
@@ -2405,9 +2430,10 @@ var PersistentEngine = class {
2405
2430
  // half-written row or an over-advanced cursor. Returns the classification
2406
2431
  // alongside the meta so callers (refreshFile) can keep, extend, or evict
2407
2432
  // their own per-file caches without re-stat'ing the file (racy) themselves.
2408
- async indexFile(filePath, account, tierName, customTiers, resolveGitBranch, force = false, provider) {
2433
+ async indexFile(rawFilePath, account, tierName, customTiers, resolveGitBranch, force = false, provider) {
2409
2434
  const log = getLogger();
2410
2435
  const tier = resolveTier(tierName, customTiers);
2436
+ const filePath = canonicalPath(rawFilePath);
2411
2437
  const existing = this.files.getByPath(filePath);
2412
2438
  const { change, stat: stat4 } = classify(filePath, existing);
2413
2439
  if (change === "vanished" || !stat4) {
@@ -2718,7 +2744,7 @@ var IndexQueue = class {
2718
2744
  var BATCH_SIZE2 = 12;
2719
2745
  var DEFAULT_CONFIG_PATH = "~/.config/threadbase-scanner";
2720
2746
  function defaultDbPath() {
2721
- return process.env.TB_SCANNER_DB ?? (0, import_path9.join)((0, import_os2.homedir)(), ".config", "threadbase-scanner", "index.db");
2747
+ return process.env.TB_SCANNER_DB ?? (0, import_path11.join)((0, import_os2.homedir)(), ".config", "threadbase-scanner", "index.db");
2722
2748
  }
2723
2749
  var ConversationScanner = class {
2724
2750
  metadataCache = /* @__PURE__ */ new Map();
@@ -3011,12 +3037,13 @@ var ConversationScanner = class {
3011
3037
  }
3012
3038
  async getConversation(id, _options) {
3013
3039
  const log = getLogger();
3014
- const cached = this.conversationLRU.get(id);
3040
+ const cid = canonicalPath(id);
3041
+ const cached = this.conversationLRU.get(cid);
3015
3042
  if (cached) {
3016
3043
  log.debug({ id }, "getConversation: cache hit");
3017
3044
  return cached.conversation;
3018
3045
  }
3019
- const meta = this.persistent ? this.engine().getByIdOrSession(id) : this.metadataCache.get(id) ?? this.resolveSessionId(id);
3046
+ const meta = this.persistent ? this.engine().getByIdOrSession(cid) : this.metadataCache.get(cid) ?? this.resolveSessionId(cid);
3020
3047
  if (!meta) {
3021
3048
  log.debug({ id }, "getConversation: not found in metadata");
3022
3049
  return null;
@@ -3025,12 +3052,12 @@ var ConversationScanner = class {
3025
3052
  try {
3026
3053
  if (this.persistent && meta.provider !== CODEX_CLI_PROVIDER) {
3027
3054
  const parsed = await parseConversationResumable(meta.filePath, meta.account);
3028
- if (parsed) this.conversationLRU.set(id, parsed);
3055
+ if (parsed) this.conversationLRU.set(cid, parsed);
3029
3056
  return parsed?.conversation ?? null;
3030
3057
  }
3031
3058
  const conversation = meta.provider === CODEX_CLI_PROVIDER ? await parseCodexConversation(meta.filePath, meta.account) : await parseConversation(meta.filePath, meta.account);
3032
3059
  if (conversation) {
3033
- this.conversationLRU.set(id, { conversation });
3060
+ this.conversationLRU.set(cid, { conversation });
3034
3061
  }
3035
3062
  return conversation;
3036
3063
  } catch (err) {
@@ -3058,7 +3085,7 @@ var ConversationScanner = class {
3058
3085
  // which is identical by construction.
3059
3086
  async getConversationPage(id, options) {
3060
3087
  if (this.persistent) {
3061
- return this.engine().getPage(id, options);
3088
+ return this.engine().getPage(canonicalPath(id), options);
3062
3089
  }
3063
3090
  const conversation = await this.getConversation(id);
3064
3091
  if (!conversation) return null;
@@ -3108,14 +3135,15 @@ var ConversationScanner = class {
3108
3135
  // each re-reading the file.
3109
3136
  refreshesInFlight = /* @__PURE__ */ new Map();
3110
3137
  refreshFile(filePath, account) {
3111
- const inFlight = this.refreshesInFlight.get(filePath);
3138
+ const cpath = canonicalPath(filePath);
3139
+ const inFlight = this.refreshesInFlight.get(cpath);
3112
3140
  if (inFlight) return inFlight;
3113
- const refresh = this.doRefreshFile(filePath, account).finally(() => {
3114
- if (this.refreshesInFlight.get(filePath) === refresh) {
3115
- this.refreshesInFlight.delete(filePath);
3141
+ const refresh = this.doRefreshFile(cpath, account).finally(() => {
3142
+ if (this.refreshesInFlight.get(cpath) === refresh) {
3143
+ this.refreshesInFlight.delete(cpath);
3116
3144
  }
3117
3145
  });
3118
- this.refreshesInFlight.set(filePath, refresh);
3146
+ this.refreshesInFlight.set(cpath, refresh);
3119
3147
  return refresh;
3120
3148
  }
3121
3149
  async doRefreshFile(filePath, account) {