cogpit-memory 0.1.7 → 0.1.9
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 +184 -86
- package/dist/index.js +184 -86
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -951,19 +951,32 @@ function extractSessionMetadata(messages) {
|
|
|
951
951
|
}
|
|
952
952
|
return meta;
|
|
953
953
|
}
|
|
954
|
-
function
|
|
954
|
+
function emptyStats(turnCount) {
|
|
955
|
+
return {
|
|
956
|
+
totalInputTokens: 0,
|
|
957
|
+
totalOutputTokens: 0,
|
|
958
|
+
totalCacheCreationTokens: 0,
|
|
959
|
+
totalCacheReadTokens: 0,
|
|
960
|
+
totalCostUSD: 0,
|
|
961
|
+
toolCallCounts: {},
|
|
962
|
+
errorCount: 0,
|
|
963
|
+
totalDurationMs: 0,
|
|
964
|
+
turnCount
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
function parseSession(jsonlText, opts) {
|
|
955
968
|
if (isCodexSessionText(jsonlText)) {
|
|
956
969
|
return parseCodexSession(jsonlText);
|
|
957
970
|
}
|
|
958
971
|
const rawMessages = parseLines(jsonlText);
|
|
959
972
|
const metadata = extractSessionMetadata(rawMessages);
|
|
960
973
|
const turns = buildTurns(rawMessages);
|
|
961
|
-
const stats = computeStats(turns);
|
|
974
|
+
const stats = opts?.skipStats ? emptyStats(turns.length) : computeStats(turns);
|
|
962
975
|
return {
|
|
963
976
|
...metadata,
|
|
964
977
|
turns,
|
|
965
978
|
stats,
|
|
966
|
-
rawMessages,
|
|
979
|
+
rawMessages: opts?.skipStats ? [] : rawMessages,
|
|
967
980
|
agentKind: "claude"
|
|
968
981
|
};
|
|
969
982
|
}
|
|
@@ -974,6 +987,10 @@ function getUserMessageText(content) {
|
|
|
974
987
|
}
|
|
975
988
|
|
|
976
989
|
// src/lib/search-index.ts
|
|
990
|
+
var MAX_CONTENT_LEN = 4096;
|
|
991
|
+
function truncContent(text) {
|
|
992
|
+
return text.length > MAX_CONTENT_LEN ? text.slice(0, MAX_CONTENT_LEN) : text;
|
|
993
|
+
}
|
|
977
994
|
var SearchIndex = class {
|
|
978
995
|
db;
|
|
979
996
|
dbPath;
|
|
@@ -1007,7 +1024,7 @@ var SearchIndex = class {
|
|
|
1007
1024
|
source_file,
|
|
1008
1025
|
location,
|
|
1009
1026
|
content,
|
|
1010
|
-
tokenize = '
|
|
1027
|
+
tokenize = 'unicode61'
|
|
1011
1028
|
)`);
|
|
1012
1029
|
}
|
|
1013
1030
|
}
|
|
@@ -1034,6 +1051,56 @@ var SearchIndex = class {
|
|
|
1034
1051
|
lastUpdate: this._lastUpdate
|
|
1035
1052
|
};
|
|
1036
1053
|
}
|
|
1054
|
+
/** Index tool call inputs and results under the given location prefix. */
|
|
1055
|
+
insertToolCalls(insert, sessionId, filePath, prefix, toolCalls) {
|
|
1056
|
+
for (const tc of toolCalls) {
|
|
1057
|
+
const inputStr = JSON.stringify(tc.input);
|
|
1058
|
+
if (inputStr && inputStr !== "{}") {
|
|
1059
|
+
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, truncContent(inputStr));
|
|
1060
|
+
}
|
|
1061
|
+
if (tc.result) {
|
|
1062
|
+
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, truncContent(tc.result));
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Insert all searchable content from a parsed session into the FTS5 index.
|
|
1068
|
+
* Shared by both `indexFile` (single-file) and `buildFull` (batch).
|
|
1069
|
+
*/
|
|
1070
|
+
insertSessionContent(insert, sessionId, filePath, session) {
|
|
1071
|
+
for (let i = 0; i < session.turns.length; i++) {
|
|
1072
|
+
const turn = session.turns[i];
|
|
1073
|
+
const prefix = `turn/${i}`;
|
|
1074
|
+
const userText = getUserMessageText(turn.userMessage);
|
|
1075
|
+
if (userText.trim()) {
|
|
1076
|
+
insert.run(sessionId, filePath, `${prefix}/userMessage`, truncContent(userText));
|
|
1077
|
+
}
|
|
1078
|
+
const assistantJoined = turn.assistantText.join("\n\n").trim();
|
|
1079
|
+
if (assistantJoined) {
|
|
1080
|
+
insert.run(sessionId, filePath, `${prefix}/assistantMessage`, truncContent(assistantJoined));
|
|
1081
|
+
}
|
|
1082
|
+
const thinkingText = turn.thinking.filter((t) => t.thinking && t.thinking.length > 0).map((t) => t.thinking).join("\n\n").trim();
|
|
1083
|
+
if (thinkingText) {
|
|
1084
|
+
insert.run(sessionId, filePath, `${prefix}/thinking`, truncContent(thinkingText));
|
|
1085
|
+
}
|
|
1086
|
+
this.insertToolCalls(insert, sessionId, filePath, prefix, turn.toolCalls);
|
|
1087
|
+
for (const sa of turn.subAgentActivity) {
|
|
1088
|
+
const saPrefix = `agent/${sa.agentId}`;
|
|
1089
|
+
const saText = sa.text.join("\n\n").trim();
|
|
1090
|
+
if (saText) {
|
|
1091
|
+
insert.run(sessionId, filePath, `${saPrefix}/assistantMessage`, truncContent(saText));
|
|
1092
|
+
}
|
|
1093
|
+
const saThinking = sa.thinking.filter((t) => t.length > 0).join("\n\n").trim();
|
|
1094
|
+
if (saThinking) {
|
|
1095
|
+
insert.run(sessionId, filePath, `${saPrefix}/thinking`, truncContent(saThinking));
|
|
1096
|
+
}
|
|
1097
|
+
this.insertToolCalls(insert, sessionId, filePath, saPrefix, sa.toolCalls);
|
|
1098
|
+
}
|
|
1099
|
+
if (turn.compactionSummary) {
|
|
1100
|
+
insert.run(sessionId, filePath, `${prefix}/compactionSummary`, truncContent(turn.compactionSummary));
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1037
1104
|
/**
|
|
1038
1105
|
* Parse a JSONL file and insert all searchable content into the FTS5 index.
|
|
1039
1106
|
* Idempotent: deletes old data for the file before re-indexing.
|
|
@@ -1047,7 +1114,7 @@ var SearchIndex = class {
|
|
|
1047
1114
|
mtimeMs = (0, import_node_fs.statSync)(filePath).mtimeMs;
|
|
1048
1115
|
}
|
|
1049
1116
|
const content = (0, import_node_fs.readFileSync)(filePath, "utf-8");
|
|
1050
|
-
const session = parseSession(content);
|
|
1117
|
+
const session = parseSession(content, { skipStats: true });
|
|
1051
1118
|
const isSubagent = opts?.isSubagent ? 1 : 0;
|
|
1052
1119
|
const parentSessionId = opts?.parentSessionId ?? null;
|
|
1053
1120
|
const insert = this.db.prepare(
|
|
@@ -1065,54 +1132,7 @@ var SearchIndex = class {
|
|
|
1065
1132
|
const txn = this.db.transaction(() => {
|
|
1066
1133
|
deleteContent.run(filePath);
|
|
1067
1134
|
deleteFile.run(filePath);
|
|
1068
|
-
|
|
1069
|
-
const turn = session.turns[i];
|
|
1070
|
-
const prefix = `turn/${i}`;
|
|
1071
|
-
const userText = getUserMessageText(turn.userMessage);
|
|
1072
|
-
if (userText.trim()) {
|
|
1073
|
-
insert.run(sessionId, filePath, `${prefix}/userMessage`, userText);
|
|
1074
|
-
}
|
|
1075
|
-
const assistantJoined = turn.assistantText.join("\n\n").trim();
|
|
1076
|
-
if (assistantJoined) {
|
|
1077
|
-
insert.run(sessionId, filePath, `${prefix}/assistantMessage`, assistantJoined);
|
|
1078
|
-
}
|
|
1079
|
-
const thinkingText = turn.thinking.filter((t) => t.thinking && t.thinking.length > 0).map((t) => t.thinking).join("\n\n").trim();
|
|
1080
|
-
if (thinkingText) {
|
|
1081
|
-
insert.run(sessionId, filePath, `${prefix}/thinking`, thinkingText);
|
|
1082
|
-
}
|
|
1083
|
-
for (const tc of turn.toolCalls) {
|
|
1084
|
-
const inputStr = JSON.stringify(tc.input);
|
|
1085
|
-
if (inputStr && inputStr !== "{}") {
|
|
1086
|
-
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, inputStr);
|
|
1087
|
-
}
|
|
1088
|
-
if (tc.result) {
|
|
1089
|
-
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, tc.result);
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
for (const sa of turn.subAgentActivity) {
|
|
1093
|
-
const saPrefix = `agent/${sa.agentId}`;
|
|
1094
|
-
const saText = sa.text.join("\n\n").trim();
|
|
1095
|
-
if (saText) {
|
|
1096
|
-
insert.run(sessionId, filePath, `${saPrefix}/assistantMessage`, saText);
|
|
1097
|
-
}
|
|
1098
|
-
const saThinking = sa.thinking.filter((t) => t.length > 0).join("\n\n").trim();
|
|
1099
|
-
if (saThinking) {
|
|
1100
|
-
insert.run(sessionId, filePath, `${saPrefix}/thinking`, saThinking);
|
|
1101
|
-
}
|
|
1102
|
-
for (const tc of sa.toolCalls) {
|
|
1103
|
-
const inputStr = JSON.stringify(tc.input);
|
|
1104
|
-
if (inputStr && inputStr !== "{}") {
|
|
1105
|
-
insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/input`, inputStr);
|
|
1106
|
-
}
|
|
1107
|
-
if (tc.result) {
|
|
1108
|
-
insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/result`, tc.result);
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
if (turn.compactionSummary) {
|
|
1113
|
-
insert.run(sessionId, filePath, `${prefix}/compactionSummary`, turn.compactionSummary);
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1135
|
+
this.insertSessionContent(insert, sessionId, filePath, session);
|
|
1116
1136
|
insertFile.run(filePath, mtimeMs, sessionId, isSubagent, parentSessionId);
|
|
1117
1137
|
});
|
|
1118
1138
|
txn();
|
|
@@ -1121,7 +1141,7 @@ var SearchIndex = class {
|
|
|
1121
1141
|
/**
|
|
1122
1142
|
* Query the FTS5 index and return structured search results.
|
|
1123
1143
|
*
|
|
1124
|
-
* - FTS5
|
|
1144
|
+
* - FTS5 unicode61 tokenizer is case-insensitive by default.
|
|
1125
1145
|
* - When `caseSensitive` is true, a post-filter checks the original query
|
|
1126
1146
|
* against the snippet text (exact case match).
|
|
1127
1147
|
* - When `maxAgeMs` is provided, only files whose mtime in `indexed_files`
|
|
@@ -1163,7 +1183,7 @@ var SearchIndex = class {
|
|
|
1163
1183
|
location: row.location,
|
|
1164
1184
|
snippet: row.snippet,
|
|
1165
1185
|
matchCount: 1
|
|
1166
|
-
// FTS5
|
|
1186
|
+
// FTS5 doesn't expose per-row match count; 1 = "at least one match"
|
|
1167
1187
|
}));
|
|
1168
1188
|
if (caseSensitive) {
|
|
1169
1189
|
hits = hits.filter((h) => h.snippet.includes(query));
|
|
@@ -1202,19 +1222,67 @@ var SearchIndex = class {
|
|
|
1202
1222
|
* Structure: projectsDir/{projectName}/{sessionId}.jsonl
|
|
1203
1223
|
* Subagents: projectsDir/{projectName}/{sessionId}/subagents/agent-{id}.jsonl
|
|
1204
1224
|
*
|
|
1225
|
+
* Optimized: discovers all files first, then processes them in a single
|
|
1226
|
+
* SQLite transaction with pre-prepared statements. This avoids the overhead
|
|
1227
|
+
* of 3000+ individual transactions (each forcing a disk sync).
|
|
1228
|
+
*
|
|
1205
1229
|
* Stores `projectsDir` as a class field so `rebuild()` can reuse it.
|
|
1206
1230
|
*/
|
|
1207
1231
|
buildFull(projectsDir) {
|
|
1208
1232
|
this.projectsDir = projectsDir;
|
|
1209
|
-
this.db.
|
|
1210
|
-
|
|
1211
|
-
|
|
1233
|
+
this.db.close();
|
|
1234
|
+
try {
|
|
1235
|
+
(0, import_node_fs.unlinkSync)(this.dbPath);
|
|
1236
|
+
} catch {
|
|
1237
|
+
}
|
|
1238
|
+
try {
|
|
1239
|
+
(0, import_node_fs.unlinkSync)(this.dbPath + "-wal");
|
|
1240
|
+
} catch {
|
|
1241
|
+
}
|
|
1242
|
+
try {
|
|
1243
|
+
(0, import_node_fs.unlinkSync)(this.dbPath + "-shm");
|
|
1244
|
+
} catch {
|
|
1245
|
+
}
|
|
1246
|
+
this.db = new Database(this.dbPath);
|
|
1247
|
+
this.db.exec("PRAGMA journal_mode = WAL");
|
|
1248
|
+
this.db.exec("PRAGMA synchronous = OFF");
|
|
1249
|
+
this.db.exec("PRAGMA cache_size = -64000");
|
|
1250
|
+
this.db.exec("PRAGMA temp_store = MEMORY");
|
|
1251
|
+
this.db.exec("PRAGMA mmap_size = 268435456");
|
|
1252
|
+
this.initSchema();
|
|
1253
|
+
const files = [];
|
|
1254
|
+
this.discoverFiles(projectsDir, (filePath, sessionId, mtimeMs, isSubagent, parentSessionId) => {
|
|
1255
|
+
files.push({ path: filePath, sessionId, mtimeMs, isSubagent, parentSessionId });
|
|
1256
|
+
});
|
|
1257
|
+
const insert = this.db.prepare(
|
|
1258
|
+
"INSERT INTO search_content (session_id, source_file, location, content) VALUES (?, ?, ?, ?)"
|
|
1259
|
+
);
|
|
1260
|
+
const insertFile = this.db.prepare(
|
|
1261
|
+
"INSERT OR REPLACE INTO indexed_files (file_path, mtime_ms, session_id, is_subagent, parent_session_id) VALUES (?, ?, ?, ?, ?)"
|
|
1262
|
+
);
|
|
1263
|
+
const txn = this.db.transaction(() => {
|
|
1264
|
+
for (const file of files) {
|
|
1265
|
+
try {
|
|
1266
|
+
const content = (0, import_node_fs.readFileSync)(file.path, "utf-8");
|
|
1267
|
+
const session = parseSession(content, { skipStats: true });
|
|
1268
|
+
this.insertSessionContent(insert, file.sessionId, file.path, session);
|
|
1269
|
+
insertFile.run(file.path, file.mtimeMs, file.sessionId, file.isSubagent ? 1 : 0, file.parentSessionId);
|
|
1270
|
+
} catch {
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
txn();
|
|
1275
|
+
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
1212
1276
|
this._lastFullBuild = (/* @__PURE__ */ new Date()).toISOString();
|
|
1213
1277
|
this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
1214
1278
|
}
|
|
1215
1279
|
/**
|
|
1216
1280
|
* Incrementally re-index only files whose mtime has changed since last index.
|
|
1217
1281
|
* New files (not in indexed_files) are always indexed.
|
|
1282
|
+
*
|
|
1283
|
+
* WARNING: This walks ALL files under projectsDir and stats each one.
|
|
1284
|
+
* On large session stores (3000+ files) this can take minutes.
|
|
1285
|
+
* Prefer `updateRecent()` for CLI search paths.
|
|
1218
1286
|
*/
|
|
1219
1287
|
updateStale(projectsDir) {
|
|
1220
1288
|
this.projectsDir = projectsDir;
|
|
@@ -1241,6 +1309,46 @@ var SearchIndex = class {
|
|
|
1241
1309
|
this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
1242
1310
|
}
|
|
1243
1311
|
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Lightweight incremental update for CLI search paths.
|
|
1314
|
+
*
|
|
1315
|
+
* Still walks and stats all files via `discoverFiles`, but skips DB lookups
|
|
1316
|
+
* for files with mtime <= the high-water mark that are already indexed.
|
|
1317
|
+
* Caps re-indexing to `maxFiles` to prevent blocking on large backlogs
|
|
1318
|
+
* (run `index rebuild` for a full catch-up).
|
|
1319
|
+
*/
|
|
1320
|
+
updateRecent(projectsDir, maxFiles = 50) {
|
|
1321
|
+
this.projectsDir = projectsDir;
|
|
1322
|
+
const row = this.db.prepare(
|
|
1323
|
+
"SELECT MAX(mtime_ms) as max_mtime FROM indexed_files"
|
|
1324
|
+
).get();
|
|
1325
|
+
const highWater = row?.max_mtime ?? 0;
|
|
1326
|
+
const getIndexed = this.db.prepare(
|
|
1327
|
+
"SELECT mtime_ms FROM indexed_files WHERE file_path = ?"
|
|
1328
|
+
);
|
|
1329
|
+
const filesToIndex = [];
|
|
1330
|
+
this.discoverFiles(projectsDir, (filePath, sessionId, mtimeMs, isSubagent, parentSessionId) => {
|
|
1331
|
+
if (mtimeMs <= highWater) {
|
|
1332
|
+
const existing = getIndexed.get(filePath);
|
|
1333
|
+
if (existing && existing.mtime_ms >= mtimeMs) return;
|
|
1334
|
+
}
|
|
1335
|
+
filesToIndex.push({ path: filePath, sessionId, mtimeMs, isSubagent, parentSessionId });
|
|
1336
|
+
});
|
|
1337
|
+
filesToIndex.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1338
|
+
const batch = filesToIndex.slice(0, maxFiles);
|
|
1339
|
+
for (const file of batch) {
|
|
1340
|
+
try {
|
|
1341
|
+
this.indexFile(file.path, file.sessionId, file.mtimeMs, {
|
|
1342
|
+
isSubagent: file.isSubagent,
|
|
1343
|
+
parentSessionId: file.parentSessionId
|
|
1344
|
+
});
|
|
1345
|
+
} catch {
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
if (batch.length > 0) {
|
|
1349
|
+
this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1244
1352
|
/**
|
|
1245
1353
|
* Re-run buildFull using the previously stored projectsDir.
|
|
1246
1354
|
* No-op if projectsDir was never set.
|
|
@@ -1249,18 +1357,6 @@ var SearchIndex = class {
|
|
|
1249
1357
|
if (!this.projectsDir) return;
|
|
1250
1358
|
this.buildFull(this.projectsDir);
|
|
1251
1359
|
}
|
|
1252
|
-
/**
|
|
1253
|
-
* Walk all project directories under `projectsDir` and index every discovered
|
|
1254
|
-
* JSONL file (both sessions and subagents).
|
|
1255
|
-
*/
|
|
1256
|
-
indexProjectsDir(projectsDir) {
|
|
1257
|
-
this.discoverFiles(projectsDir, (filePath, sessionId, mtimeMs, isSubagent, parentSessionId) => {
|
|
1258
|
-
try {
|
|
1259
|
-
this.indexFile(filePath, sessionId, mtimeMs, { isSubagent, parentSessionId });
|
|
1260
|
-
} catch {
|
|
1261
|
-
}
|
|
1262
|
-
});
|
|
1263
|
-
}
|
|
1264
1360
|
/**
|
|
1265
1361
|
* Walk the projects directory tree and invoke `callback` for every JSONL file.
|
|
1266
1362
|
*
|
|
@@ -1276,29 +1372,23 @@ var SearchIndex = class {
|
|
|
1276
1372
|
discoverFiles(projectsDir, callback) {
|
|
1277
1373
|
let entries;
|
|
1278
1374
|
try {
|
|
1279
|
-
entries = (0, import_node_fs.readdirSync)(projectsDir);
|
|
1375
|
+
entries = (0, import_node_fs.readdirSync)(projectsDir, { withFileTypes: true });
|
|
1280
1376
|
} catch {
|
|
1281
1377
|
return;
|
|
1282
1378
|
}
|
|
1283
|
-
for (const
|
|
1284
|
-
if (
|
|
1285
|
-
const projectDir = (0, import_node_path.join)(projectsDir,
|
|
1286
|
-
try {
|
|
1287
|
-
const s = (0, import_node_fs.statSync)(projectDir);
|
|
1288
|
-
if (!s.isDirectory()) continue;
|
|
1289
|
-
} catch {
|
|
1290
|
-
continue;
|
|
1291
|
-
}
|
|
1379
|
+
for (const entry of entries) {
|
|
1380
|
+
if (entry.name === "memory" || !entry.isDirectory()) continue;
|
|
1381
|
+
const projectDir = (0, import_node_path.join)(projectsDir, entry.name);
|
|
1292
1382
|
let files;
|
|
1293
1383
|
try {
|
|
1294
|
-
files = (0, import_node_fs.readdirSync)(projectDir);
|
|
1384
|
+
files = (0, import_node_fs.readdirSync)(projectDir, { withFileTypes: true });
|
|
1295
1385
|
} catch {
|
|
1296
1386
|
continue;
|
|
1297
1387
|
}
|
|
1298
1388
|
for (const file of files) {
|
|
1299
|
-
if (!file.endsWith(".jsonl")) continue;
|
|
1300
|
-
const filePath = (0, import_node_path.join)(projectDir, file);
|
|
1301
|
-
const sessionId = (0, import_node_path.basename)(file, ".jsonl");
|
|
1389
|
+
if (!file.name.endsWith(".jsonl") || !file.isFile()) continue;
|
|
1390
|
+
const filePath = (0, import_node_path.join)(projectDir, file.name);
|
|
1391
|
+
const sessionId = (0, import_node_path.basename)(file.name, ".jsonl");
|
|
1302
1392
|
try {
|
|
1303
1393
|
const s = (0, import_node_fs.statSync)(filePath);
|
|
1304
1394
|
callback(filePath, sessionId, s.mtimeMs, false, null);
|
|
@@ -1544,11 +1634,19 @@ async function searchSessions(query, opts, searchIndex) {
|
|
|
1544
1634
|
const depth = Math.min(Math.max(1, opts.depth ?? 4), 4);
|
|
1545
1635
|
let index = searchIndex ?? null;
|
|
1546
1636
|
let ownedIndex = false;
|
|
1547
|
-
if (!index &&
|
|
1637
|
+
if (!index && searchIndex === void 0) {
|
|
1548
1638
|
try {
|
|
1639
|
+
const dbExists = (0, import_node_fs2.existsSync)(DEFAULT_DB_PATH);
|
|
1640
|
+
if (!dbExists) {
|
|
1641
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path4.dirname)(DEFAULT_DB_PATH), { recursive: true });
|
|
1642
|
+
}
|
|
1549
1643
|
index = new SearchIndex(DEFAULT_DB_PATH);
|
|
1550
1644
|
ownedIndex = true;
|
|
1551
|
-
|
|
1645
|
+
if (!dbExists) {
|
|
1646
|
+
index.buildFull(dirs.PROJECTS_DIR);
|
|
1647
|
+
} else {
|
|
1648
|
+
index.updateRecent(dirs.PROJECTS_DIR);
|
|
1649
|
+
}
|
|
1552
1650
|
} catch {
|
|
1553
1651
|
}
|
|
1554
1652
|
}
|
package/dist/index.js
CHANGED
|
@@ -967,19 +967,32 @@ function extractSessionMetadata(messages) {
|
|
|
967
967
|
}
|
|
968
968
|
return meta;
|
|
969
969
|
}
|
|
970
|
-
function
|
|
970
|
+
function emptyStats(turnCount) {
|
|
971
|
+
return {
|
|
972
|
+
totalInputTokens: 0,
|
|
973
|
+
totalOutputTokens: 0,
|
|
974
|
+
totalCacheCreationTokens: 0,
|
|
975
|
+
totalCacheReadTokens: 0,
|
|
976
|
+
totalCostUSD: 0,
|
|
977
|
+
toolCallCounts: {},
|
|
978
|
+
errorCount: 0,
|
|
979
|
+
totalDurationMs: 0,
|
|
980
|
+
turnCount
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function parseSession(jsonlText, opts) {
|
|
971
984
|
if (isCodexSessionText(jsonlText)) {
|
|
972
985
|
return parseCodexSession(jsonlText);
|
|
973
986
|
}
|
|
974
987
|
const rawMessages = parseLines(jsonlText);
|
|
975
988
|
const metadata = extractSessionMetadata(rawMessages);
|
|
976
989
|
const turns = buildTurns(rawMessages);
|
|
977
|
-
const stats = computeStats(turns);
|
|
990
|
+
const stats = opts?.skipStats ? emptyStats(turns.length) : computeStats(turns);
|
|
978
991
|
return {
|
|
979
992
|
...metadata,
|
|
980
993
|
turns,
|
|
981
994
|
stats,
|
|
982
|
-
rawMessages,
|
|
995
|
+
rawMessages: opts?.skipStats ? [] : rawMessages,
|
|
983
996
|
agentKind: "claude"
|
|
984
997
|
};
|
|
985
998
|
}
|
|
@@ -1072,6 +1085,10 @@ function getUserMessageImages(content) {
|
|
|
1072
1085
|
}
|
|
1073
1086
|
|
|
1074
1087
|
// src/lib/search-index.ts
|
|
1088
|
+
var MAX_CONTENT_LEN = 4096;
|
|
1089
|
+
function truncContent(text) {
|
|
1090
|
+
return text.length > MAX_CONTENT_LEN ? text.slice(0, MAX_CONTENT_LEN) : text;
|
|
1091
|
+
}
|
|
1075
1092
|
var SearchIndex = class {
|
|
1076
1093
|
db;
|
|
1077
1094
|
dbPath;
|
|
@@ -1105,7 +1122,7 @@ var SearchIndex = class {
|
|
|
1105
1122
|
source_file,
|
|
1106
1123
|
location,
|
|
1107
1124
|
content,
|
|
1108
|
-
tokenize = '
|
|
1125
|
+
tokenize = 'unicode61'
|
|
1109
1126
|
)`);
|
|
1110
1127
|
}
|
|
1111
1128
|
}
|
|
@@ -1132,6 +1149,56 @@ var SearchIndex = class {
|
|
|
1132
1149
|
lastUpdate: this._lastUpdate
|
|
1133
1150
|
};
|
|
1134
1151
|
}
|
|
1152
|
+
/** Index tool call inputs and results under the given location prefix. */
|
|
1153
|
+
insertToolCalls(insert, sessionId, filePath, prefix, toolCalls) {
|
|
1154
|
+
for (const tc of toolCalls) {
|
|
1155
|
+
const inputStr = JSON.stringify(tc.input);
|
|
1156
|
+
if (inputStr && inputStr !== "{}") {
|
|
1157
|
+
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, truncContent(inputStr));
|
|
1158
|
+
}
|
|
1159
|
+
if (tc.result) {
|
|
1160
|
+
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, truncContent(tc.result));
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Insert all searchable content from a parsed session into the FTS5 index.
|
|
1166
|
+
* Shared by both `indexFile` (single-file) and `buildFull` (batch).
|
|
1167
|
+
*/
|
|
1168
|
+
insertSessionContent(insert, sessionId, filePath, session) {
|
|
1169
|
+
for (let i = 0; i < session.turns.length; i++) {
|
|
1170
|
+
const turn = session.turns[i];
|
|
1171
|
+
const prefix = `turn/${i}`;
|
|
1172
|
+
const userText = getUserMessageText(turn.userMessage);
|
|
1173
|
+
if (userText.trim()) {
|
|
1174
|
+
insert.run(sessionId, filePath, `${prefix}/userMessage`, truncContent(userText));
|
|
1175
|
+
}
|
|
1176
|
+
const assistantJoined = turn.assistantText.join("\n\n").trim();
|
|
1177
|
+
if (assistantJoined) {
|
|
1178
|
+
insert.run(sessionId, filePath, `${prefix}/assistantMessage`, truncContent(assistantJoined));
|
|
1179
|
+
}
|
|
1180
|
+
const thinkingText = turn.thinking.filter((t) => t.thinking && t.thinking.length > 0).map((t) => t.thinking).join("\n\n").trim();
|
|
1181
|
+
if (thinkingText) {
|
|
1182
|
+
insert.run(sessionId, filePath, `${prefix}/thinking`, truncContent(thinkingText));
|
|
1183
|
+
}
|
|
1184
|
+
this.insertToolCalls(insert, sessionId, filePath, prefix, turn.toolCalls);
|
|
1185
|
+
for (const sa of turn.subAgentActivity) {
|
|
1186
|
+
const saPrefix = `agent/${sa.agentId}`;
|
|
1187
|
+
const saText = sa.text.join("\n\n").trim();
|
|
1188
|
+
if (saText) {
|
|
1189
|
+
insert.run(sessionId, filePath, `${saPrefix}/assistantMessage`, truncContent(saText));
|
|
1190
|
+
}
|
|
1191
|
+
const saThinking = sa.thinking.filter((t) => t.length > 0).join("\n\n").trim();
|
|
1192
|
+
if (saThinking) {
|
|
1193
|
+
insert.run(sessionId, filePath, `${saPrefix}/thinking`, truncContent(saThinking));
|
|
1194
|
+
}
|
|
1195
|
+
this.insertToolCalls(insert, sessionId, filePath, saPrefix, sa.toolCalls);
|
|
1196
|
+
}
|
|
1197
|
+
if (turn.compactionSummary) {
|
|
1198
|
+
insert.run(sessionId, filePath, `${prefix}/compactionSummary`, truncContent(turn.compactionSummary));
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1135
1202
|
/**
|
|
1136
1203
|
* Parse a JSONL file and insert all searchable content into the FTS5 index.
|
|
1137
1204
|
* Idempotent: deletes old data for the file before re-indexing.
|
|
@@ -1145,7 +1212,7 @@ var SearchIndex = class {
|
|
|
1145
1212
|
mtimeMs = (0, import_node_fs.statSync)(filePath).mtimeMs;
|
|
1146
1213
|
}
|
|
1147
1214
|
const content = (0, import_node_fs.readFileSync)(filePath, "utf-8");
|
|
1148
|
-
const session = parseSession(content);
|
|
1215
|
+
const session = parseSession(content, { skipStats: true });
|
|
1149
1216
|
const isSubagent = opts?.isSubagent ? 1 : 0;
|
|
1150
1217
|
const parentSessionId = opts?.parentSessionId ?? null;
|
|
1151
1218
|
const insert = this.db.prepare(
|
|
@@ -1163,54 +1230,7 @@ var SearchIndex = class {
|
|
|
1163
1230
|
const txn = this.db.transaction(() => {
|
|
1164
1231
|
deleteContent.run(filePath);
|
|
1165
1232
|
deleteFile.run(filePath);
|
|
1166
|
-
|
|
1167
|
-
const turn = session.turns[i];
|
|
1168
|
-
const prefix = `turn/${i}`;
|
|
1169
|
-
const userText = getUserMessageText(turn.userMessage);
|
|
1170
|
-
if (userText.trim()) {
|
|
1171
|
-
insert.run(sessionId, filePath, `${prefix}/userMessage`, userText);
|
|
1172
|
-
}
|
|
1173
|
-
const assistantJoined = turn.assistantText.join("\n\n").trim();
|
|
1174
|
-
if (assistantJoined) {
|
|
1175
|
-
insert.run(sessionId, filePath, `${prefix}/assistantMessage`, assistantJoined);
|
|
1176
|
-
}
|
|
1177
|
-
const thinkingText = turn.thinking.filter((t) => t.thinking && t.thinking.length > 0).map((t) => t.thinking).join("\n\n").trim();
|
|
1178
|
-
if (thinkingText) {
|
|
1179
|
-
insert.run(sessionId, filePath, `${prefix}/thinking`, thinkingText);
|
|
1180
|
-
}
|
|
1181
|
-
for (const tc of turn.toolCalls) {
|
|
1182
|
-
const inputStr = JSON.stringify(tc.input);
|
|
1183
|
-
if (inputStr && inputStr !== "{}") {
|
|
1184
|
-
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, inputStr);
|
|
1185
|
-
}
|
|
1186
|
-
if (tc.result) {
|
|
1187
|
-
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, tc.result);
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
for (const sa of turn.subAgentActivity) {
|
|
1191
|
-
const saPrefix = `agent/${sa.agentId}`;
|
|
1192
|
-
const saText = sa.text.join("\n\n").trim();
|
|
1193
|
-
if (saText) {
|
|
1194
|
-
insert.run(sessionId, filePath, `${saPrefix}/assistantMessage`, saText);
|
|
1195
|
-
}
|
|
1196
|
-
const saThinking = sa.thinking.filter((t) => t.length > 0).join("\n\n").trim();
|
|
1197
|
-
if (saThinking) {
|
|
1198
|
-
insert.run(sessionId, filePath, `${saPrefix}/thinking`, saThinking);
|
|
1199
|
-
}
|
|
1200
|
-
for (const tc of sa.toolCalls) {
|
|
1201
|
-
const inputStr = JSON.stringify(tc.input);
|
|
1202
|
-
if (inputStr && inputStr !== "{}") {
|
|
1203
|
-
insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/input`, inputStr);
|
|
1204
|
-
}
|
|
1205
|
-
if (tc.result) {
|
|
1206
|
-
insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/result`, tc.result);
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
if (turn.compactionSummary) {
|
|
1211
|
-
insert.run(sessionId, filePath, `${prefix}/compactionSummary`, turn.compactionSummary);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1233
|
+
this.insertSessionContent(insert, sessionId, filePath, session);
|
|
1214
1234
|
insertFile.run(filePath, mtimeMs, sessionId, isSubagent, parentSessionId);
|
|
1215
1235
|
});
|
|
1216
1236
|
txn();
|
|
@@ -1219,7 +1239,7 @@ var SearchIndex = class {
|
|
|
1219
1239
|
/**
|
|
1220
1240
|
* Query the FTS5 index and return structured search results.
|
|
1221
1241
|
*
|
|
1222
|
-
* - FTS5
|
|
1242
|
+
* - FTS5 unicode61 tokenizer is case-insensitive by default.
|
|
1223
1243
|
* - When `caseSensitive` is true, a post-filter checks the original query
|
|
1224
1244
|
* against the snippet text (exact case match).
|
|
1225
1245
|
* - When `maxAgeMs` is provided, only files whose mtime in `indexed_files`
|
|
@@ -1261,7 +1281,7 @@ var SearchIndex = class {
|
|
|
1261
1281
|
location: row.location,
|
|
1262
1282
|
snippet: row.snippet,
|
|
1263
1283
|
matchCount: 1
|
|
1264
|
-
// FTS5
|
|
1284
|
+
// FTS5 doesn't expose per-row match count; 1 = "at least one match"
|
|
1265
1285
|
}));
|
|
1266
1286
|
if (caseSensitive) {
|
|
1267
1287
|
hits = hits.filter((h) => h.snippet.includes(query));
|
|
@@ -1300,19 +1320,67 @@ var SearchIndex = class {
|
|
|
1300
1320
|
* Structure: projectsDir/{projectName}/{sessionId}.jsonl
|
|
1301
1321
|
* Subagents: projectsDir/{projectName}/{sessionId}/subagents/agent-{id}.jsonl
|
|
1302
1322
|
*
|
|
1323
|
+
* Optimized: discovers all files first, then processes them in a single
|
|
1324
|
+
* SQLite transaction with pre-prepared statements. This avoids the overhead
|
|
1325
|
+
* of 3000+ individual transactions (each forcing a disk sync).
|
|
1326
|
+
*
|
|
1303
1327
|
* Stores `projectsDir` as a class field so `rebuild()` can reuse it.
|
|
1304
1328
|
*/
|
|
1305
1329
|
buildFull(projectsDir) {
|
|
1306
1330
|
this.projectsDir = projectsDir;
|
|
1307
|
-
this.db.
|
|
1308
|
-
|
|
1309
|
-
|
|
1331
|
+
this.db.close();
|
|
1332
|
+
try {
|
|
1333
|
+
(0, import_node_fs.unlinkSync)(this.dbPath);
|
|
1334
|
+
} catch {
|
|
1335
|
+
}
|
|
1336
|
+
try {
|
|
1337
|
+
(0, import_node_fs.unlinkSync)(this.dbPath + "-wal");
|
|
1338
|
+
} catch {
|
|
1339
|
+
}
|
|
1340
|
+
try {
|
|
1341
|
+
(0, import_node_fs.unlinkSync)(this.dbPath + "-shm");
|
|
1342
|
+
} catch {
|
|
1343
|
+
}
|
|
1344
|
+
this.db = new Database(this.dbPath);
|
|
1345
|
+
this.db.exec("PRAGMA journal_mode = WAL");
|
|
1346
|
+
this.db.exec("PRAGMA synchronous = OFF");
|
|
1347
|
+
this.db.exec("PRAGMA cache_size = -64000");
|
|
1348
|
+
this.db.exec("PRAGMA temp_store = MEMORY");
|
|
1349
|
+
this.db.exec("PRAGMA mmap_size = 268435456");
|
|
1350
|
+
this.initSchema();
|
|
1351
|
+
const files = [];
|
|
1352
|
+
this.discoverFiles(projectsDir, (filePath, sessionId, mtimeMs, isSubagent, parentSessionId) => {
|
|
1353
|
+
files.push({ path: filePath, sessionId, mtimeMs, isSubagent, parentSessionId });
|
|
1354
|
+
});
|
|
1355
|
+
const insert = this.db.prepare(
|
|
1356
|
+
"INSERT INTO search_content (session_id, source_file, location, content) VALUES (?, ?, ?, ?)"
|
|
1357
|
+
);
|
|
1358
|
+
const insertFile = this.db.prepare(
|
|
1359
|
+
"INSERT OR REPLACE INTO indexed_files (file_path, mtime_ms, session_id, is_subagent, parent_session_id) VALUES (?, ?, ?, ?, ?)"
|
|
1360
|
+
);
|
|
1361
|
+
const txn = this.db.transaction(() => {
|
|
1362
|
+
for (const file of files) {
|
|
1363
|
+
try {
|
|
1364
|
+
const content = (0, import_node_fs.readFileSync)(file.path, "utf-8");
|
|
1365
|
+
const session = parseSession(content, { skipStats: true });
|
|
1366
|
+
this.insertSessionContent(insert, file.sessionId, file.path, session);
|
|
1367
|
+
insertFile.run(file.path, file.mtimeMs, file.sessionId, file.isSubagent ? 1 : 0, file.parentSessionId);
|
|
1368
|
+
} catch {
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
txn();
|
|
1373
|
+
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
1310
1374
|
this._lastFullBuild = (/* @__PURE__ */ new Date()).toISOString();
|
|
1311
1375
|
this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
1312
1376
|
}
|
|
1313
1377
|
/**
|
|
1314
1378
|
* Incrementally re-index only files whose mtime has changed since last index.
|
|
1315
1379
|
* New files (not in indexed_files) are always indexed.
|
|
1380
|
+
*
|
|
1381
|
+
* WARNING: This walks ALL files under projectsDir and stats each one.
|
|
1382
|
+
* On large session stores (3000+ files) this can take minutes.
|
|
1383
|
+
* Prefer `updateRecent()` for CLI search paths.
|
|
1316
1384
|
*/
|
|
1317
1385
|
updateStale(projectsDir) {
|
|
1318
1386
|
this.projectsDir = projectsDir;
|
|
@@ -1339,6 +1407,46 @@ var SearchIndex = class {
|
|
|
1339
1407
|
this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
1340
1408
|
}
|
|
1341
1409
|
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Lightweight incremental update for CLI search paths.
|
|
1412
|
+
*
|
|
1413
|
+
* Still walks and stats all files via `discoverFiles`, but skips DB lookups
|
|
1414
|
+
* for files with mtime <= the high-water mark that are already indexed.
|
|
1415
|
+
* Caps re-indexing to `maxFiles` to prevent blocking on large backlogs
|
|
1416
|
+
* (run `index rebuild` for a full catch-up).
|
|
1417
|
+
*/
|
|
1418
|
+
updateRecent(projectsDir, maxFiles = 50) {
|
|
1419
|
+
this.projectsDir = projectsDir;
|
|
1420
|
+
const row = this.db.prepare(
|
|
1421
|
+
"SELECT MAX(mtime_ms) as max_mtime FROM indexed_files"
|
|
1422
|
+
).get();
|
|
1423
|
+
const highWater = row?.max_mtime ?? 0;
|
|
1424
|
+
const getIndexed = this.db.prepare(
|
|
1425
|
+
"SELECT mtime_ms FROM indexed_files WHERE file_path = ?"
|
|
1426
|
+
);
|
|
1427
|
+
const filesToIndex = [];
|
|
1428
|
+
this.discoverFiles(projectsDir, (filePath, sessionId, mtimeMs, isSubagent, parentSessionId) => {
|
|
1429
|
+
if (mtimeMs <= highWater) {
|
|
1430
|
+
const existing = getIndexed.get(filePath);
|
|
1431
|
+
if (existing && existing.mtime_ms >= mtimeMs) return;
|
|
1432
|
+
}
|
|
1433
|
+
filesToIndex.push({ path: filePath, sessionId, mtimeMs, isSubagent, parentSessionId });
|
|
1434
|
+
});
|
|
1435
|
+
filesToIndex.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1436
|
+
const batch = filesToIndex.slice(0, maxFiles);
|
|
1437
|
+
for (const file of batch) {
|
|
1438
|
+
try {
|
|
1439
|
+
this.indexFile(file.path, file.sessionId, file.mtimeMs, {
|
|
1440
|
+
isSubagent: file.isSubagent,
|
|
1441
|
+
parentSessionId: file.parentSessionId
|
|
1442
|
+
});
|
|
1443
|
+
} catch {
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
if (batch.length > 0) {
|
|
1447
|
+
this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1342
1450
|
/**
|
|
1343
1451
|
* Re-run buildFull using the previously stored projectsDir.
|
|
1344
1452
|
* No-op if projectsDir was never set.
|
|
@@ -1347,18 +1455,6 @@ var SearchIndex = class {
|
|
|
1347
1455
|
if (!this.projectsDir) return;
|
|
1348
1456
|
this.buildFull(this.projectsDir);
|
|
1349
1457
|
}
|
|
1350
|
-
/**
|
|
1351
|
-
* Walk all project directories under `projectsDir` and index every discovered
|
|
1352
|
-
* JSONL file (both sessions and subagents).
|
|
1353
|
-
*/
|
|
1354
|
-
indexProjectsDir(projectsDir) {
|
|
1355
|
-
this.discoverFiles(projectsDir, (filePath, sessionId, mtimeMs, isSubagent, parentSessionId) => {
|
|
1356
|
-
try {
|
|
1357
|
-
this.indexFile(filePath, sessionId, mtimeMs, { isSubagent, parentSessionId });
|
|
1358
|
-
} catch {
|
|
1359
|
-
}
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
1458
|
/**
|
|
1363
1459
|
* Walk the projects directory tree and invoke `callback` for every JSONL file.
|
|
1364
1460
|
*
|
|
@@ -1374,29 +1470,23 @@ var SearchIndex = class {
|
|
|
1374
1470
|
discoverFiles(projectsDir, callback) {
|
|
1375
1471
|
let entries;
|
|
1376
1472
|
try {
|
|
1377
|
-
entries = (0, import_node_fs.readdirSync)(projectsDir);
|
|
1473
|
+
entries = (0, import_node_fs.readdirSync)(projectsDir, { withFileTypes: true });
|
|
1378
1474
|
} catch {
|
|
1379
1475
|
return;
|
|
1380
1476
|
}
|
|
1381
|
-
for (const
|
|
1382
|
-
if (
|
|
1383
|
-
const projectDir = (0, import_node_path.join)(projectsDir,
|
|
1384
|
-
try {
|
|
1385
|
-
const s = (0, import_node_fs.statSync)(projectDir);
|
|
1386
|
-
if (!s.isDirectory()) continue;
|
|
1387
|
-
} catch {
|
|
1388
|
-
continue;
|
|
1389
|
-
}
|
|
1477
|
+
for (const entry of entries) {
|
|
1478
|
+
if (entry.name === "memory" || !entry.isDirectory()) continue;
|
|
1479
|
+
const projectDir = (0, import_node_path.join)(projectsDir, entry.name);
|
|
1390
1480
|
let files;
|
|
1391
1481
|
try {
|
|
1392
|
-
files = (0, import_node_fs.readdirSync)(projectDir);
|
|
1482
|
+
files = (0, import_node_fs.readdirSync)(projectDir, { withFileTypes: true });
|
|
1393
1483
|
} catch {
|
|
1394
1484
|
continue;
|
|
1395
1485
|
}
|
|
1396
1486
|
for (const file of files) {
|
|
1397
|
-
if (!file.endsWith(".jsonl")) continue;
|
|
1398
|
-
const filePath = (0, import_node_path.join)(projectDir, file);
|
|
1399
|
-
const sessionId = (0, import_node_path.basename)(file, ".jsonl");
|
|
1487
|
+
if (!file.name.endsWith(".jsonl") || !file.isFile()) continue;
|
|
1488
|
+
const filePath = (0, import_node_path.join)(projectDir, file.name);
|
|
1489
|
+
const sessionId = (0, import_node_path.basename)(file.name, ".jsonl");
|
|
1400
1490
|
try {
|
|
1401
1491
|
const s = (0, import_node_fs.statSync)(filePath);
|
|
1402
1492
|
callback(filePath, sessionId, s.mtimeMs, false, null);
|
|
@@ -1647,11 +1737,19 @@ async function searchSessions(query, opts, searchIndex) {
|
|
|
1647
1737
|
const depth = Math.min(Math.max(1, opts.depth ?? 4), 4);
|
|
1648
1738
|
let index = searchIndex ?? null;
|
|
1649
1739
|
let ownedIndex = false;
|
|
1650
|
-
if (!index &&
|
|
1740
|
+
if (!index && searchIndex === void 0) {
|
|
1651
1741
|
try {
|
|
1742
|
+
const dbExists = (0, import_node_fs2.existsSync)(DEFAULT_DB_PATH);
|
|
1743
|
+
if (!dbExists) {
|
|
1744
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path4.dirname)(DEFAULT_DB_PATH), { recursive: true });
|
|
1745
|
+
}
|
|
1652
1746
|
index = new SearchIndex(DEFAULT_DB_PATH);
|
|
1653
1747
|
ownedIndex = true;
|
|
1654
|
-
|
|
1748
|
+
if (!dbExists) {
|
|
1749
|
+
index.buildFull(dirs.PROJECTS_DIR);
|
|
1750
|
+
} else {
|
|
1751
|
+
index.updateRecent(dirs.PROJECTS_DIR);
|
|
1752
|
+
}
|
|
1655
1753
|
} catch {
|
|
1656
1754
|
}
|
|
1657
1755
|
}
|
package/package.json
CHANGED