@threadbase-sh/scanner 0.11.0 → 0.11.2
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/cli.js +69 -43
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +77 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +70 -42
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
3138
|
+
const cpath = canonicalPath(filePath);
|
|
3139
|
+
const inFlight = this.refreshesInFlight.get(cpath);
|
|
3112
3140
|
if (inFlight) return inFlight;
|
|
3113
|
-
const refresh = this.doRefreshFile(
|
|
3114
|
-
if (this.refreshesInFlight.get(
|
|
3115
|
-
this.refreshesInFlight.delete(
|
|
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(
|
|
3146
|
+
this.refreshesInFlight.set(cpath, refresh);
|
|
3119
3147
|
return refresh;
|
|
3120
3148
|
}
|
|
3121
3149
|
async doRefreshFile(filePath, account) {
|