cogpit-memory 0.1.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.
Files changed (4) hide show
  1. package/README.md +147 -0
  2. package/dist/cli.js +2228 -0
  3. package/dist/index.js +2175 -0
  4. package/package.json +27 -0
package/dist/index.js ADDED
@@ -0,0 +1,2175 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DEFAULT_DB_PATH: () => DEFAULT_DB_PATH,
34
+ SearchIndex: () => SearchIndex,
35
+ currentSession: () => currentSession,
36
+ dirs: () => dirs,
37
+ getAgentOverview: () => getAgentOverview,
38
+ getAgentTurnDetail: () => getAgentTurnDetail,
39
+ getSessionOverview: () => getSessionOverview,
40
+ getTurnDetail: () => getTurnDetail,
41
+ getUserMessageImages: () => getUserMessageImages,
42
+ getUserMessageText: () => getUserMessageText,
43
+ indexRebuild: () => indexRebuild,
44
+ indexStats: () => indexStats,
45
+ listSessions: () => listSessions,
46
+ parseSession: () => parseSession,
47
+ parseSessionAppend: () => parseSessionAppend,
48
+ searchSessions: () => searchSessions
49
+ });
50
+ module.exports = __toCommonJS(index_exports);
51
+
52
+ // src/lib/sqlite-node-shim.ts
53
+ var import_better_sqlite3 = __toESM(require("better-sqlite3"));
54
+ var Database = import_better_sqlite3.default;
55
+
56
+ // src/lib/search-index.ts
57
+ var import_node_fs = require("node:fs");
58
+ var import_node_path = require("node:path");
59
+
60
+ // src/lib/turnBuilder.ts
61
+ function isUserMessage(msg) {
62
+ return msg.type === "user";
63
+ }
64
+ function isAssistantMessage(msg) {
65
+ return msg.type === "assistant";
66
+ }
67
+ function isProgressMessage(msg) {
68
+ return msg.type === "progress";
69
+ }
70
+ function isSystemMessage(msg) {
71
+ return msg.type === "system";
72
+ }
73
+ function isSummaryMessage(msg) {
74
+ return msg.type === "summary";
75
+ }
76
+ function extractTextFromContent(content) {
77
+ if (typeof content === "string") return content;
78
+ return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
79
+ }
80
+ function extractToolResultText(content) {
81
+ if (!content) return "";
82
+ if (typeof content === "string") return content;
83
+ return content.map((b) => {
84
+ if (b.type === "text") return b.text;
85
+ return "";
86
+ }).filter(Boolean).join("\n");
87
+ }
88
+ function mergeTokenUsage(existing, incoming) {
89
+ if (!existing) {
90
+ return { ...incoming };
91
+ }
92
+ return {
93
+ input_tokens: existing.input_tokens + incoming.input_tokens,
94
+ output_tokens: existing.output_tokens + incoming.output_tokens,
95
+ cache_creation_input_tokens: (existing.cache_creation_input_tokens ?? 0) + (incoming.cache_creation_input_tokens ?? 0),
96
+ cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0)
97
+ };
98
+ }
99
+ function buildCompactionSummary(turns, title) {
100
+ if (turns.length === 0) return title;
101
+ const toolCounts = {};
102
+ for (const turn of turns) {
103
+ for (const tc of turn.toolCalls) {
104
+ toolCounts[tc.name] = (toolCounts[tc.name] || 0) + 1;
105
+ }
106
+ }
107
+ const prompts = [];
108
+ for (const turn of turns) {
109
+ if (!turn.userMessage) continue;
110
+ const text = extractTextFromContent(
111
+ typeof turn.userMessage === "string" ? turn.userMessage : turn.userMessage
112
+ );
113
+ const firstLine = text.split("\n")[0].trim();
114
+ if (firstLine.length > 0) {
115
+ prompts.push(firstLine.length > 120 ? firstLine.slice(0, 117) + "..." : firstLine);
116
+ }
117
+ }
118
+ const topTools = Object.entries(toolCounts).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, count]) => `${name} x${count}`).join(", ");
119
+ const parts = [`**${title}**`, `${turns.length} turns compacted`];
120
+ if (topTools) parts.push(`Tools: ${topTools}`);
121
+ if (prompts.length > 0) {
122
+ parts.push("Prompts:");
123
+ const shown = prompts.slice(0, 6);
124
+ for (const p of shown) parts.push(`- ${p}`);
125
+ if (prompts.length > 6) parts.push(`- ...and ${prompts.length - 6} more`);
126
+ }
127
+ return parts.join("\n");
128
+ }
129
+ function buildTurns(messages) {
130
+ const turns = [];
131
+ let current = null;
132
+ let pendingCompaction = null;
133
+ const pendingToolUses = /* @__PURE__ */ new Map();
134
+ const seenMessageIds = /* @__PURE__ */ new Set();
135
+ const subAgentMap = /* @__PURE__ */ new Map();
136
+ const agentBlockMap = /* @__PURE__ */ new Map();
137
+ const backgroundAgentParentIds = /* @__PURE__ */ new Set();
138
+ const taskMetaMap = /* @__PURE__ */ new Map();
139
+ function flushSubAgentMessages(parentId) {
140
+ if (!current) return;
141
+ const agentMsgs = subAgentMap.get(parentId);
142
+ if (!agentMsgs || agentMsgs.length === 0) return;
143
+ current.subAgentActivity.push(...agentMsgs);
144
+ subAgentMap.delete(parentId);
145
+ const kind = backgroundAgentParentIds.has(parentId) ? "background_agent" : "sub_agent";
146
+ const existingBlock = agentBlockMap.get(parentId);
147
+ if (existingBlock) {
148
+ existingBlock.messages.push(...agentMsgs);
149
+ } else {
150
+ const block = { kind, messages: [...agentMsgs] };
151
+ current.contentBlocks.push(block);
152
+ agentBlockMap.set(parentId, block);
153
+ }
154
+ }
155
+ function finalizeTurn() {
156
+ if (!current) return;
157
+ for (const tc of current.toolCalls) {
158
+ flushSubAgentMessages(tc.id);
159
+ }
160
+ for (const [parentId] of subAgentMap) {
161
+ flushSubAgentMessages(parentId);
162
+ }
163
+ turns.push(current);
164
+ current = null;
165
+ agentBlockMap.clear();
166
+ }
167
+ for (const msg of messages) {
168
+ if (isSummaryMessage(msg)) {
169
+ finalizeTurn();
170
+ pendingCompaction = buildCompactionSummary(
171
+ turns,
172
+ msg.summary ?? "Conversation compacted"
173
+ );
174
+ continue;
175
+ }
176
+ if (isUserMessage(msg) && !msg.isMeta) {
177
+ const content = msg.message.content;
178
+ if (typeof content !== "string" && Array.isArray(content)) {
179
+ const hasToolResult = content.some((b) => b.type === "tool_result");
180
+ if (hasToolResult && current) {
181
+ for (const block of content) {
182
+ if (block.type === "tool_result") {
183
+ const pending = pendingToolUses.get(block.tool_use_id);
184
+ if (pending) {
185
+ pending.turn.toolCalls[pending.index].result = extractToolResultText(block.content);
186
+ pending.turn.toolCalls[pending.index].isError = block.is_error;
187
+ pendingToolUses.delete(block.tool_use_id);
188
+ }
189
+ }
190
+ }
191
+ const toolUseResult = msg.toolUseResult;
192
+ if (toolUseResult?.agentId) {
193
+ const toolUseId = content.find((b) => b.type === "tool_result")?.tool_use_id ?? "";
194
+ const taskMeta = taskMetaMap.get(toolUseId);
195
+ const isBackground = backgroundAgentParentIds.has(toolUseId);
196
+ const resultText = [];
197
+ if (Array.isArray(toolUseResult.content)) {
198
+ for (const block of toolUseResult.content) {
199
+ if (block.type === "text") resultText.push(block.text);
200
+ }
201
+ }
202
+ const agentMsg = {
203
+ agentId: toolUseResult.agentId,
204
+ agentName: taskMeta?.name ?? null,
205
+ subagentType: taskMeta?.subagentType ?? null,
206
+ type: "assistant",
207
+ content: toolUseResult.content,
208
+ toolCalls: [],
209
+ thinking: [],
210
+ text: resultText,
211
+ timestamp: msg.timestamp ?? "",
212
+ tokenUsage: toolUseResult.usage ? {
213
+ input_tokens: toolUseResult.usage.input_tokens ?? 0,
214
+ output_tokens: toolUseResult.usage.output_tokens ?? 0,
215
+ cache_creation_input_tokens: toolUseResult.usage.cache_creation_input_tokens ?? 0,
216
+ cache_read_input_tokens: toolUseResult.usage.cache_read_input_tokens ?? 0
217
+ } : null,
218
+ model: null,
219
+ isBackground,
220
+ prompt: toolUseResult.prompt,
221
+ status: toolUseResult.status,
222
+ durationMs: toolUseResult.totalDurationMs,
223
+ toolUseCount: toolUseResult.totalToolUseCount
224
+ };
225
+ const existingBlock = agentBlockMap.get(toolUseId);
226
+ if (!existingBlock) {
227
+ current.subAgentActivity.push(agentMsg);
228
+ const kind = isBackground ? "background_agent" : "sub_agent";
229
+ const block = { kind, messages: [agentMsg] };
230
+ current.contentBlocks.push(block);
231
+ agentBlockMap.set(toolUseId, block);
232
+ }
233
+ }
234
+ continue;
235
+ }
236
+ }
237
+ finalizeTurn();
238
+ current = {
239
+ id: msg.uuid ?? crypto.randomUUID(),
240
+ userMessage: msg.message.content,
241
+ contentBlocks: [],
242
+ thinking: [],
243
+ assistantText: [],
244
+ toolCalls: [],
245
+ subAgentActivity: [],
246
+ timestamp: msg.timestamp ?? "",
247
+ durationMs: null,
248
+ tokenUsage: null,
249
+ model: null
250
+ };
251
+ if (pendingCompaction) {
252
+ current.compactionSummary = pendingCompaction;
253
+ pendingCompaction = null;
254
+ }
255
+ continue;
256
+ }
257
+ if (isAssistantMessage(msg)) {
258
+ let flushToolCalls2 = function() {
259
+ if (msgToolCalls.length > 0) {
260
+ activeTurn.contentBlocks.push({ kind: "tool_calls", toolCalls: [...msgToolCalls], timestamp: msgTs });
261
+ msgToolCalls.length = 0;
262
+ }
263
+ }, flushThinking2 = function() {
264
+ if (msgThinking.length > 0) {
265
+ const last = activeTurn.contentBlocks[activeTurn.contentBlocks.length - 1];
266
+ if (last && last.kind === "thinking") {
267
+ last.blocks.push(...msgThinking);
268
+ } else {
269
+ activeTurn.contentBlocks.push({ kind: "thinking", blocks: [...msgThinking], timestamp: msgTs });
270
+ }
271
+ msgThinking.length = 0;
272
+ }
273
+ };
274
+ var flushToolCalls = flushToolCalls2, flushThinking = flushThinking2;
275
+ if (!current) {
276
+ current = {
277
+ id: msg.uuid ?? crypto.randomUUID(),
278
+ userMessage: null,
279
+ contentBlocks: [],
280
+ thinking: [],
281
+ assistantText: [],
282
+ toolCalls: [],
283
+ subAgentActivity: [],
284
+ timestamp: msg.timestamp ?? "",
285
+ durationMs: null,
286
+ tokenUsage: null,
287
+ model: null
288
+ };
289
+ }
290
+ current.model = msg.message.model;
291
+ const msgId = msg.message.id;
292
+ if (!seenMessageIds.has(msgId)) {
293
+ seenMessageIds.add(msgId);
294
+ current.tokenUsage = mergeTokenUsage(current.tokenUsage, msg.message.usage);
295
+ }
296
+ const msgTs = msg.timestamp ?? "";
297
+ const msgThinking = [];
298
+ const msgToolCalls = [];
299
+ const activeTurn = current;
300
+ for (const block of msg.message.content) {
301
+ if (block.type === "thinking") {
302
+ flushToolCalls2();
303
+ const tb = block;
304
+ current.thinking.push(tb);
305
+ msgThinking.push(tb);
306
+ } else if (block.type === "text") {
307
+ flushToolCalls2();
308
+ flushThinking2();
309
+ const thinkingRegex = /<thinking>([\s\S]*?)<\/thinking>/g;
310
+ let remaining = block.text;
311
+ let match;
312
+ while ((match = thinkingRegex.exec(block.text)) !== null) {
313
+ const thinkingText = match[1].trim();
314
+ if (thinkingText) {
315
+ const tb = { type: "thinking", thinking: thinkingText, signature: "" };
316
+ current.thinking.push(tb);
317
+ current.contentBlocks.push({ kind: "thinking", blocks: [tb], timestamp: msgTs });
318
+ }
319
+ remaining = remaining.replace(match[0], "");
320
+ }
321
+ remaining = remaining.trim();
322
+ if (remaining) {
323
+ current.assistantText.push(remaining);
324
+ const last = current.contentBlocks[current.contentBlocks.length - 1];
325
+ if (last && last.kind === "text") {
326
+ last.text.push(remaining);
327
+ } else {
328
+ current.contentBlocks.push({ kind: "text", text: [remaining], timestamp: msgTs });
329
+ }
330
+ }
331
+ } else if (block.type === "tool_use") {
332
+ flushThinking2();
333
+ const tc = {
334
+ id: block.id,
335
+ name: block.name,
336
+ input: block.input,
337
+ result: null,
338
+ isError: false,
339
+ timestamp: msg.timestamp ?? ""
340
+ };
341
+ const idx = current.toolCalls.length;
342
+ current.toolCalls.push(tc);
343
+ msgToolCalls.push(tc);
344
+ pendingToolUses.set(block.id, { turn: current, index: idx });
345
+ if (block.name === "Task" || block.name === "Agent") {
346
+ const input = block.input;
347
+ if (input.run_in_background === true) {
348
+ backgroundAgentParentIds.add(block.id);
349
+ }
350
+ taskMetaMap.set(block.id, {
351
+ name: input.name ?? null,
352
+ subagentType: input.subagent_type ?? null
353
+ });
354
+ }
355
+ }
356
+ }
357
+ flushThinking2();
358
+ flushToolCalls2();
359
+ continue;
360
+ }
361
+ if (isProgressMessage(msg) && msg.data.type === "agent_progress") {
362
+ const data = msg.data;
363
+ const parentId = msg.parentToolUseID ?? "";
364
+ let subAgentUsage = null;
365
+ if (data.message.type === "assistant") {
366
+ const innerMsg = data.message.message;
367
+ const msgId = innerMsg.id;
368
+ const usage = innerMsg.usage;
369
+ if (usage && msgId && !seenMessageIds.has(msgId)) {
370
+ seenMessageIds.add(msgId);
371
+ subAgentUsage = {
372
+ input_tokens: usage.input_tokens ?? 0,
373
+ output_tokens: usage.output_tokens ?? 0,
374
+ cache_creation_input_tokens: usage.cache_creation_input_tokens ?? 0,
375
+ cache_read_input_tokens: usage.cache_read_input_tokens ?? 0
376
+ };
377
+ }
378
+ }
379
+ const innerModel = data.message.type === "assistant" ? data.message.message.model ?? null : null;
380
+ const taskMeta = taskMetaMap.get(parentId);
381
+ const agentMsg = {
382
+ agentId: data.agentId,
383
+ agentName: taskMeta?.name ?? null,
384
+ subagentType: taskMeta?.subagentType ?? null,
385
+ type: data.message.type,
386
+ content: data.message.message.content,
387
+ toolCalls: [],
388
+ thinking: [],
389
+ text: [],
390
+ timestamp: data.message.timestamp ?? msg.timestamp ?? "",
391
+ tokenUsage: subAgentUsage,
392
+ model: innerModel,
393
+ isBackground: backgroundAgentParentIds.has(parentId)
394
+ };
395
+ if (data.message.type === "assistant") {
396
+ const innerContent = data.message.message.content;
397
+ if (Array.isArray(innerContent)) {
398
+ for (const block of innerContent) {
399
+ if (block.type === "thinking") {
400
+ agentMsg.thinking.push(block.thinking);
401
+ } else if (block.type === "text") {
402
+ agentMsg.text.push(block.text);
403
+ } else if (block.type === "tool_use") {
404
+ agentMsg.toolCalls.push({
405
+ id: block.id,
406
+ name: block.name,
407
+ input: block.input,
408
+ result: null,
409
+ isError: false,
410
+ timestamp: data.message.timestamp ?? msg.timestamp ?? ""
411
+ });
412
+ }
413
+ }
414
+ }
415
+ } else if (data.message.type === "user") {
416
+ const innerContent = data.message.message.content;
417
+ if (Array.isArray(innerContent)) {
418
+ for (const block of innerContent) {
419
+ if (block.type === "tool_result") {
420
+ const existing = subAgentMap.get(parentId);
421
+ if (existing) {
422
+ for (const prev of existing) {
423
+ const match = prev.toolCalls.find(
424
+ (tc) => tc.id === block.tool_use_id
425
+ );
426
+ if (match) {
427
+ match.result = extractToolResultText(block.content);
428
+ match.isError = block.is_error;
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }
434
+ }
435
+ }
436
+ let agentMsgs = subAgentMap.get(parentId);
437
+ if (!agentMsgs) {
438
+ agentMsgs = [];
439
+ subAgentMap.set(parentId, agentMsgs);
440
+ }
441
+ agentMsgs.push(agentMsg);
442
+ if (current) {
443
+ flushSubAgentMessages(parentId);
444
+ }
445
+ continue;
446
+ }
447
+ if (isSystemMessage(msg) && msg.subtype === "turn_duration" && current) {
448
+ current.durationMs = msg.durationMs ?? null;
449
+ continue;
450
+ }
451
+ }
452
+ finalizeTurn();
453
+ return turns;
454
+ }
455
+
456
+ // src/lib/pricingTiers.ts
457
+ var EXTENDED_CONTEXT_THRESHOLD = 2e5;
458
+ var TIER_HAIKU_35 = { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08, webSearch: 0.01 };
459
+ var TIER_HAIKU_45 = { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
460
+ var TIER_SONNET_LEGACY = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3, webSearch: 0.01 };
461
+ var TIER_SONNET_LATEST = { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
462
+ var TIER_OPUS_LEGACY = { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5, webSearch: 0.01 };
463
+ var TIER_SONNET_LEGACY_EXT = { input: 6, output: 22.5, cacheWrite: 7.5, cacheRead: 0.6, webSearch: 0.01 };
464
+ var TIER_EXTENDED = { input: 10, output: 37.5, cacheWrite: 12.5, cacheRead: 1, webSearch: 0.01 };
465
+ var MODEL_TIERS = [
466
+ // Haiku
467
+ { match: "haiku-4-5", tier: TIER_HAIKU_45 },
468
+ { match: "haiku-4-0", tier: TIER_HAIKU_35 },
469
+ { match: "3-5-haiku", tier: TIER_HAIKU_35 },
470
+ // Sonnet latest (4.5+)
471
+ { match: "sonnet-4-6", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
472
+ { match: "sonnet-4-5", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
473
+ // Sonnet legacy (3.5, 3.7, 4.0)
474
+ { match: "sonnet-4-0", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
475
+ { match: "3-7-sonnet", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
476
+ { match: "3-5-sonnet", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
477
+ // Opus latest (4.5+) — same tier as sonnet latest
478
+ { match: "opus-4-6", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
479
+ { match: "opus-4-5", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
480
+ // Opus legacy (4.0, 4.1)
481
+ { match: "opus-4-1", tier: TIER_OPUS_LEGACY },
482
+ { match: "opus-4-0", tier: TIER_OPUS_LEGACY }
483
+ ];
484
+ var DEFAULT_TIER = TIER_SONNET_LATEST;
485
+ var DEFAULT_EXTENDED_TIER = TIER_EXTENDED;
486
+ var FAMILY_FALLBACKS = [
487
+ { match: "haiku", tier: TIER_HAIKU_45 },
488
+ { match: "sonnet", tier: TIER_SONNET_LATEST },
489
+ { match: "opus", tier: TIER_SONNET_LATEST }
490
+ ];
491
+ function resolveTier(model, totalInputTokens) {
492
+ const isExtended = (totalInputTokens ?? 0) > EXTENDED_CONTEXT_THRESHOLD;
493
+ for (const entry of MODEL_TIERS) {
494
+ if (model.includes(entry.match)) {
495
+ return isExtended && entry.extendedTier ? entry.extendedTier : entry.tier;
496
+ }
497
+ }
498
+ for (const entry of FAMILY_FALLBACKS) {
499
+ if (model.includes(entry.match)) return entry.tier;
500
+ }
501
+ return isExtended ? DEFAULT_EXTENDED_TIER : DEFAULT_TIER;
502
+ }
503
+
504
+ // src/lib/token-costs.ts
505
+ var CHARS_PER_TOKEN = 4;
506
+ function calculateCost(c) {
507
+ const totalInput = c.inputTokens + c.cacheWriteTokens + c.cacheReadTokens;
508
+ const p = resolveTier(c.model ?? "", totalInput);
509
+ return c.inputTokens / 1e6 * p.input + c.outputTokens / 1e6 * p.output + c.cacheWriteTokens / 1e6 * p.cacheWrite + c.cacheReadTokens / 1e6 * p.cacheRead + (c.webSearchRequests ?? 0) * p.webSearch;
510
+ }
511
+ function charsToTokens(chars) {
512
+ return Math.ceil(chars / CHARS_PER_TOKEN);
513
+ }
514
+ function totalLength(strings) {
515
+ let n = 0;
516
+ for (const s of strings) n += s.length;
517
+ return n;
518
+ }
519
+ function totalToolInputLength(toolCalls) {
520
+ let n = 0;
521
+ for (const tc of toolCalls) n += JSON.stringify(tc.input).length;
522
+ return n;
523
+ }
524
+ function estimateThinkingTokens(turn) {
525
+ return charsToTokens(totalLength(turn.thinking.map((b) => b.thinking)));
526
+ }
527
+ function estimateVisibleOutputTokens(turn) {
528
+ return charsToTokens(totalLength(turn.assistantText) + totalToolInputLength(turn.toolCalls));
529
+ }
530
+ function estimateTotalOutputTokens(turn) {
531
+ const estimated = estimateThinkingTokens(turn) + estimateVisibleOutputTokens(turn);
532
+ return Math.max(estimated, turn.tokenUsage?.output_tokens ?? 0);
533
+ }
534
+ function estimateSubAgentOutput(sa) {
535
+ const chars = totalLength(sa.thinking) + totalLength(sa.text) + totalToolInputLength(sa.toolCalls);
536
+ return Math.max(charsToTokens(chars), sa.tokenUsage?.output_tokens ?? 0);
537
+ }
538
+ function calculateTurnCostEstimated(turn) {
539
+ if (!turn.tokenUsage) return 0;
540
+ const u = turn.tokenUsage;
541
+ return calculateCost({
542
+ model: turn.model,
543
+ inputTokens: u.input_tokens,
544
+ outputTokens: estimateTotalOutputTokens(turn),
545
+ cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
546
+ cacheReadTokens: u.cache_read_input_tokens ?? 0
547
+ });
548
+ }
549
+ function calculateSubAgentCostEstimated(sa) {
550
+ if (!sa.tokenUsage) return 0;
551
+ const u = sa.tokenUsage;
552
+ return calculateCost({
553
+ model: sa.model,
554
+ inputTokens: u.input_tokens,
555
+ outputTokens: estimateSubAgentOutput(sa),
556
+ cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
557
+ cacheReadTokens: u.cache_read_input_tokens ?? 0
558
+ });
559
+ }
560
+
561
+ // src/lib/sessionStats.ts
562
+ function countToolCalls(toolCalls, counts) {
563
+ let errors = 0;
564
+ for (const tc of toolCalls) {
565
+ counts[tc.name] = (counts[tc.name] ?? 0) + 1;
566
+ if (tc.isError) errors++;
567
+ }
568
+ return errors;
569
+ }
570
+ function addUsageToStats(stats, usage, estimatedOutput, cost) {
571
+ stats.totalInputTokens += usage.input_tokens;
572
+ stats.totalOutputTokens += estimatedOutput;
573
+ stats.totalCacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
574
+ stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
575
+ stats.totalCostUSD += cost;
576
+ }
577
+ function computeStats(turns) {
578
+ const stats = {
579
+ totalInputTokens: 0,
580
+ totalOutputTokens: 0,
581
+ totalCacheCreationTokens: 0,
582
+ totalCacheReadTokens: 0,
583
+ totalCostUSD: 0,
584
+ toolCallCounts: {},
585
+ errorCount: 0,
586
+ totalDurationMs: 0,
587
+ turnCount: turns.length
588
+ };
589
+ for (const turn of turns) {
590
+ if (turn.tokenUsage) {
591
+ addUsageToStats(stats, turn.tokenUsage, estimateTotalOutputTokens(turn), calculateTurnCostEstimated(turn));
592
+ }
593
+ if (turn.durationMs) stats.totalDurationMs += turn.durationMs;
594
+ stats.errorCount += countToolCalls(turn.toolCalls, stats.toolCallCounts);
595
+ for (const sa of turn.subAgentActivity) {
596
+ stats.errorCount += countToolCalls(sa.toolCalls, stats.toolCallCounts);
597
+ if (sa.tokenUsage) {
598
+ addUsageToStats(stats, sa.tokenUsage, estimateSubAgentOutput(sa), calculateSubAgentCostEstimated(sa));
599
+ }
600
+ }
601
+ }
602
+ return stats;
603
+ }
604
+
605
+ // src/lib/parser.ts
606
+ function isAssistantMessage2(msg) {
607
+ return msg.type === "assistant";
608
+ }
609
+ function extractTextFromContent2(content) {
610
+ if (typeof content === "string") return content;
611
+ return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
612
+ }
613
+ function parseLines(jsonlText) {
614
+ const messages = [];
615
+ for (const line of jsonlText.split("\n")) {
616
+ const trimmed = line.trim();
617
+ if (!trimmed) continue;
618
+ try {
619
+ messages.push(JSON.parse(trimmed));
620
+ } catch {
621
+ }
622
+ }
623
+ return messages;
624
+ }
625
+ function extractSessionMetadata(messages) {
626
+ const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", model: "", branchedFrom: void 0 };
627
+ for (const msg of messages) {
628
+ if (msg.sessionId && !meta.sessionId) meta.sessionId = msg.sessionId;
629
+ if (msg.version && !meta.version) meta.version = msg.version;
630
+ if (msg.gitBranch && !meta.gitBranch) meta.gitBranch = msg.gitBranch;
631
+ if (msg.cwd && !meta.cwd) meta.cwd = msg.cwd;
632
+ if (msg.slug && !meta.slug) meta.slug = msg.slug;
633
+ if (msg.branchedFrom && !meta.branchedFrom) {
634
+ meta.branchedFrom = msg.branchedFrom;
635
+ }
636
+ if (isAssistantMessage2(msg) && msg.message.model && !meta.model) {
637
+ meta.model = msg.message.model;
638
+ }
639
+ if (meta.sessionId && meta.version && meta.gitBranch && meta.cwd && meta.model) break;
640
+ }
641
+ return meta;
642
+ }
643
+ function parseSession(jsonlText) {
644
+ const rawMessages = parseLines(jsonlText);
645
+ const metadata = extractSessionMetadata(rawMessages);
646
+ const turns = buildTurns(rawMessages);
647
+ const stats = computeStats(turns);
648
+ return {
649
+ ...metadata,
650
+ turns,
651
+ stats,
652
+ rawMessages
653
+ };
654
+ }
655
+ function parseSessionAppend(existing, newJsonlText) {
656
+ const newMessages = parseLines(newJsonlText);
657
+ if (newMessages.length === 0) return existing;
658
+ const allRawMessages = [...existing.rawMessages, ...newMessages];
659
+ let lastTurnStartIdx = existing.rawMessages.length;
660
+ let turnsToKeep = existing.turns.length > 0 ? existing.turns.length - 1 : 0;
661
+ if (existing.turns.length > 0) {
662
+ let userMsgCount = 0;
663
+ for (let i = existing.rawMessages.length - 1; i >= 0; i--) {
664
+ const msg = existing.rawMessages[i];
665
+ if (msg.type === "user" && !msg.isMeta) {
666
+ const content = msg.message?.content;
667
+ if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
668
+ continue;
669
+ }
670
+ userMsgCount++;
671
+ if (userMsgCount === 1) {
672
+ lastTurnStartIdx = i;
673
+ break;
674
+ }
675
+ }
676
+ }
677
+ const progressParentIds = /* @__PURE__ */ new Set();
678
+ for (const msg of newMessages) {
679
+ if (msg.type === "progress") {
680
+ const parentId = msg.parentToolUseID;
681
+ if (parentId) progressParentIds.add(parentId);
682
+ }
683
+ }
684
+ if (progressParentIds.size > 0) {
685
+ for (let t = existing.turns.length - 2; t >= 0; t--) {
686
+ const turn = existing.turns[t];
687
+ const ownsProgressParent = turn.toolCalls.some((tc) => progressParentIds.has(tc.id));
688
+ if (ownsProgressParent) {
689
+ turnsToKeep = t;
690
+ let found = 0;
691
+ for (let i = 0; i < existing.rawMessages.length; i++) {
692
+ const msg = existing.rawMessages[i];
693
+ if (msg.type === "user" && !msg.isMeta) {
694
+ const content = msg.message?.content;
695
+ if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
696
+ continue;
697
+ }
698
+ if (found === t) {
699
+ lastTurnStartIdx = i;
700
+ break;
701
+ }
702
+ found++;
703
+ }
704
+ }
705
+ break;
706
+ }
707
+ }
708
+ }
709
+ }
710
+ const keptTurns = existing.turns.slice(0, turnsToKeep);
711
+ const tailMessages = allRawMessages.slice(lastTurnStartIdx);
712
+ const tailTurns = buildTurns(tailMessages);
713
+ const allTurns = [...keptTurns, ...tailTurns];
714
+ const stats = computeStats(allTurns);
715
+ return {
716
+ sessionId: existing.sessionId,
717
+ version: existing.version,
718
+ gitBranch: existing.gitBranch,
719
+ cwd: existing.cwd,
720
+ slug: existing.slug,
721
+ model: existing.model || (allTurns.length > 0 ? allTurns[allTurns.length - 1].model || "" : ""),
722
+ turns: allTurns,
723
+ stats,
724
+ rawMessages: allRawMessages,
725
+ branchedFrom: existing.branchedFrom
726
+ };
727
+ }
728
+ function getUserMessageText(content) {
729
+ if (content === null) return "";
730
+ if (typeof content === "string") return content;
731
+ return extractTextFromContent2(content);
732
+ }
733
+ function getUserMessageImages(content) {
734
+ if (content === null || typeof content === "string") return [];
735
+ return content.filter((b) => b.type === "image");
736
+ }
737
+
738
+ // src/lib/search-index.ts
739
+ var SearchIndex = class {
740
+ db;
741
+ dbPath;
742
+ projectsDir = null;
743
+ _watcherRunning = false;
744
+ _lastFullBuild = null;
745
+ _lastUpdate = null;
746
+ watcher = null;
747
+ debounceTimers = /* @__PURE__ */ new Map();
748
+ constructor(dbPath) {
749
+ this.dbPath = dbPath;
750
+ this.db = new Database(dbPath);
751
+ this.db.exec("PRAGMA journal_mode = WAL");
752
+ this.db.exec("PRAGMA synchronous = NORMAL");
753
+ this.initSchema();
754
+ }
755
+ initSchema() {
756
+ this.db.exec(`CREATE TABLE IF NOT EXISTS indexed_files (
757
+ file_path TEXT PRIMARY KEY,
758
+ mtime_ms REAL NOT NULL,
759
+ session_id TEXT NOT NULL,
760
+ is_subagent INTEGER NOT NULL DEFAULT 0,
761
+ parent_session_id TEXT
762
+ )`);
763
+ const ftsExists = this.db.prepare(
764
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='search_content'"
765
+ ).get();
766
+ if (!ftsExists) {
767
+ this.db.exec(`CREATE VIRTUAL TABLE search_content USING fts5(
768
+ session_id,
769
+ source_file,
770
+ location,
771
+ content,
772
+ tokenize = 'trigram'
773
+ )`);
774
+ }
775
+ }
776
+ getStats() {
777
+ const { count: indexedFiles } = this.db.prepare("SELECT COUNT(*) as count FROM indexed_files").get();
778
+ const { count: indexedSessions } = this.db.prepare("SELECT COUNT(*) as count FROM indexed_files WHERE is_subagent = 0").get();
779
+ const { count: indexedSubagents } = this.db.prepare("SELECT COUNT(*) as count FROM indexed_files WHERE is_subagent = 1").get();
780
+ const { count: totalRows } = this.db.prepare("SELECT COUNT(*) as count FROM search_content").get();
781
+ let dbSizeBytes = 0;
782
+ try {
783
+ dbSizeBytes = (0, import_node_fs.statSync)(this.dbPath).size;
784
+ } catch {
785
+ }
786
+ return {
787
+ dbPath: this.dbPath,
788
+ dbSizeBytes,
789
+ dbSizeMB: Math.round(dbSizeBytes / 1024 / 1024 * 10) / 10,
790
+ indexedFiles,
791
+ indexedSessions,
792
+ indexedSubagents,
793
+ totalRows,
794
+ watcherRunning: this._watcherRunning,
795
+ lastFullBuild: this._lastFullBuild,
796
+ lastUpdate: this._lastUpdate
797
+ };
798
+ }
799
+ /**
800
+ * Parse a JSONL file and insert all searchable content into the FTS5 index.
801
+ * Idempotent: deletes old data for the file before re-indexing.
802
+ * All inserts run in a single transaction for performance.
803
+ */
804
+ indexFile(filePath, sessionId, mtimeMs, opts) {
805
+ if (!sessionId) {
806
+ sessionId = (0, import_node_path.basename)(filePath, ".jsonl");
807
+ }
808
+ if (mtimeMs == null) {
809
+ mtimeMs = (0, import_node_fs.statSync)(filePath).mtimeMs;
810
+ }
811
+ const content = (0, import_node_fs.readFileSync)(filePath, "utf-8");
812
+ const session = parseSession(content);
813
+ const isSubagent = opts?.isSubagent ? 1 : 0;
814
+ const parentSessionId = opts?.parentSessionId ?? null;
815
+ const insert = this.db.prepare(
816
+ "INSERT INTO search_content (session_id, source_file, location, content) VALUES (?, ?, ?, ?)"
817
+ );
818
+ const deleteContent = this.db.prepare(
819
+ "DELETE FROM search_content WHERE source_file = ?"
820
+ );
821
+ const deleteFile = this.db.prepare(
822
+ "DELETE FROM indexed_files WHERE file_path = ?"
823
+ );
824
+ const insertFile = this.db.prepare(
825
+ "INSERT OR REPLACE INTO indexed_files (file_path, mtime_ms, session_id, is_subagent, parent_session_id) VALUES (?, ?, ?, ?, ?)"
826
+ );
827
+ const txn = this.db.transaction(() => {
828
+ deleteContent.run(filePath);
829
+ deleteFile.run(filePath);
830
+ for (let i = 0; i < session.turns.length; i++) {
831
+ const turn = session.turns[i];
832
+ const prefix = `turn/${i}`;
833
+ const userText = getUserMessageText(turn.userMessage);
834
+ if (userText.trim()) {
835
+ insert.run(sessionId, filePath, `${prefix}/userMessage`, userText);
836
+ }
837
+ const assistantJoined = turn.assistantText.join("\n\n").trim();
838
+ if (assistantJoined) {
839
+ insert.run(sessionId, filePath, `${prefix}/assistantMessage`, assistantJoined);
840
+ }
841
+ const thinkingText = turn.thinking.filter((t) => t.thinking && t.thinking.length > 0).map((t) => t.thinking).join("\n\n").trim();
842
+ if (thinkingText) {
843
+ insert.run(sessionId, filePath, `${prefix}/thinking`, thinkingText);
844
+ }
845
+ for (const tc of turn.toolCalls) {
846
+ const inputStr = JSON.stringify(tc.input);
847
+ if (inputStr && inputStr !== "{}") {
848
+ insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, inputStr);
849
+ }
850
+ if (tc.result) {
851
+ insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, tc.result);
852
+ }
853
+ }
854
+ for (const sa of turn.subAgentActivity) {
855
+ const saPrefix = `agent/${sa.agentId}`;
856
+ const saText = sa.text.join("\n\n").trim();
857
+ if (saText) {
858
+ insert.run(sessionId, filePath, `${saPrefix}/assistantMessage`, saText);
859
+ }
860
+ const saThinking = sa.thinking.filter((t) => t.length > 0).join("\n\n").trim();
861
+ if (saThinking) {
862
+ insert.run(sessionId, filePath, `${saPrefix}/thinking`, saThinking);
863
+ }
864
+ for (const tc of sa.toolCalls) {
865
+ const inputStr = JSON.stringify(tc.input);
866
+ if (inputStr && inputStr !== "{}") {
867
+ insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/input`, inputStr);
868
+ }
869
+ if (tc.result) {
870
+ insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/result`, tc.result);
871
+ }
872
+ }
873
+ }
874
+ if (turn.compactionSummary) {
875
+ insert.run(sessionId, filePath, `${prefix}/compactionSummary`, turn.compactionSummary);
876
+ }
877
+ }
878
+ insertFile.run(filePath, mtimeMs, sessionId, isSubagent, parentSessionId);
879
+ });
880
+ txn();
881
+ this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
882
+ }
883
+ /**
884
+ * Query the FTS5 index and return structured search results.
885
+ *
886
+ * - FTS5 trigram tokenizer is case-insensitive by default.
887
+ * - When `caseSensitive` is true, a post-filter checks the original query
888
+ * against the snippet text (exact case match).
889
+ * - When `maxAgeMs` is provided, only files whose mtime in `indexed_files`
890
+ * falls within the window are included (join on source_file).
891
+ * - `sessionId` restricts results to a single session.
892
+ * - `limit` defaults to 200 and is clamped to a max of 200.
893
+ */
894
+ search(query, opts) {
895
+ const limit = Math.min(Math.max(1, opts?.limit ?? 200), 200);
896
+ const sessionId = opts?.sessionId;
897
+ const maxAgeMs = opts?.maxAgeMs;
898
+ const caseSensitive = opts?.caseSensitive ?? false;
899
+ const ftsQuery = `"${query.replace(/"/g, '""')}"`;
900
+ let sql = `
901
+ SELECT sc.session_id, sc.location,
902
+ snippet(search_content, 3, '', '', '...', 40) as snippet
903
+ FROM search_content sc
904
+ `;
905
+ const params = [];
906
+ const conditions = ["sc.content MATCH ?"];
907
+ params.push(ftsQuery);
908
+ if (maxAgeMs != null) {
909
+ sql += " JOIN indexed_files fi ON fi.file_path = sc.source_file";
910
+ conditions.push("fi.mtime_ms >= ?");
911
+ params.push(Date.now() - maxAgeMs);
912
+ }
913
+ if (sessionId) {
914
+ conditions.push("sc.session_id = ?");
915
+ params.push(sessionId);
916
+ }
917
+ sql += " WHERE " + conditions.join(" AND ");
918
+ sql += " ORDER BY sc.rowid DESC";
919
+ sql += " LIMIT ?";
920
+ params.push(limit);
921
+ const rows = this.db.prepare(sql).all(...params);
922
+ let hits = rows.map((row) => ({
923
+ sessionId: row.session_id,
924
+ location: row.location,
925
+ snippet: row.snippet,
926
+ matchCount: 1
927
+ // FTS5 trigram doesn't expose per-row match count; 1 = "at least one match"
928
+ }));
929
+ if (caseSensitive) {
930
+ hits = hits.filter((h) => h.snippet.includes(query));
931
+ }
932
+ return hits;
933
+ }
934
+ /**
935
+ * Count total matching rows and distinct sessions for a query (without LIMIT).
936
+ * Used by the route to report accurate totalHits and sessionsSearched.
937
+ */
938
+ countMatches(query, opts) {
939
+ const ftsQuery = `"${query.replace(/"/g, '""')}"`;
940
+ let sql = `
941
+ SELECT COUNT(*) as total,
942
+ COUNT(DISTINCT sc.session_id) as sessions
943
+ FROM search_content sc
944
+ `;
945
+ const params = [];
946
+ const conditions = ["sc.content MATCH ?"];
947
+ params.push(ftsQuery);
948
+ if (opts?.maxAgeMs != null) {
949
+ sql += " JOIN indexed_files fi ON fi.file_path = sc.source_file";
950
+ conditions.push("fi.mtime_ms >= ?");
951
+ params.push(Date.now() - opts.maxAgeMs);
952
+ }
953
+ if (opts?.sessionId) {
954
+ conditions.push("sc.session_id = ?");
955
+ params.push(opts.sessionId);
956
+ }
957
+ sql += " WHERE " + conditions.join(" AND ");
958
+ const row = this.db.prepare(sql).get(...params);
959
+ return { totalHits: row.total, sessionsSearched: row.sessions };
960
+ }
961
+ /**
962
+ * Clear all indexed data and re-index every JSONL file under `projectsDir`.
963
+ * Structure: projectsDir/{projectName}/{sessionId}.jsonl
964
+ * Subagents: projectsDir/{projectName}/{sessionId}/subagents/agent-{id}.jsonl
965
+ *
966
+ * Stores `projectsDir` as a class field so `rebuild()` can reuse it.
967
+ */
968
+ buildFull(projectsDir) {
969
+ this.projectsDir = projectsDir;
970
+ this.db.exec("DELETE FROM search_content");
971
+ this.db.exec("DELETE FROM indexed_files");
972
+ this.indexProjectsDir(projectsDir);
973
+ this._lastFullBuild = (/* @__PURE__ */ new Date()).toISOString();
974
+ this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
975
+ }
976
+ /**
977
+ * Incrementally re-index only files whose mtime has changed since last index.
978
+ * New files (not in indexed_files) are always indexed.
979
+ */
980
+ updateStale(projectsDir) {
981
+ this.projectsDir = projectsDir;
982
+ const getIndexed = this.db.prepare(
983
+ "SELECT mtime_ms FROM indexed_files WHERE file_path = ?"
984
+ );
985
+ const filesToIndex = [];
986
+ this.discoverFiles(projectsDir, (filePath, sessionId, mtimeMs, isSubagent, parentSessionId) => {
987
+ const existing = getIndexed.get(filePath);
988
+ if (!existing || existing.mtime_ms < mtimeMs) {
989
+ filesToIndex.push({ path: filePath, sessionId, mtimeMs, isSubagent, parentSessionId });
990
+ }
991
+ });
992
+ for (const file of filesToIndex) {
993
+ try {
994
+ this.indexFile(file.path, file.sessionId, file.mtimeMs, {
995
+ isSubagent: file.isSubagent,
996
+ parentSessionId: file.parentSessionId
997
+ });
998
+ } catch {
999
+ }
1000
+ }
1001
+ if (filesToIndex.length > 0) {
1002
+ this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
1003
+ }
1004
+ }
1005
+ /**
1006
+ * Re-run buildFull using the previously stored projectsDir.
1007
+ * No-op if projectsDir was never set.
1008
+ */
1009
+ rebuild() {
1010
+ if (!this.projectsDir) return;
1011
+ this.buildFull(this.projectsDir);
1012
+ }
1013
+ /**
1014
+ * Walk all project directories under `projectsDir` and index every discovered
1015
+ * JSONL file (both sessions and subagents).
1016
+ */
1017
+ indexProjectsDir(projectsDir) {
1018
+ this.discoverFiles(projectsDir, (filePath, sessionId, mtimeMs, isSubagent, parentSessionId) => {
1019
+ try {
1020
+ this.indexFile(filePath, sessionId, mtimeMs, { isSubagent, parentSessionId });
1021
+ } catch {
1022
+ }
1023
+ });
1024
+ }
1025
+ /**
1026
+ * Walk the projects directory tree and invoke `callback` for every JSONL file.
1027
+ *
1028
+ * Directory structure:
1029
+ * projectsDir/
1030
+ * {projectName}/
1031
+ * {sessionId}.jsonl <- session file
1032
+ * {sessionId}/subagents/
1033
+ * agent-{agentId}.jsonl <- subagent file (recursive)
1034
+ *
1035
+ * Skips the "memory" directory.
1036
+ */
1037
+ discoverFiles(projectsDir, callback) {
1038
+ let entries;
1039
+ try {
1040
+ entries = (0, import_node_fs.readdirSync)(projectsDir);
1041
+ } catch {
1042
+ return;
1043
+ }
1044
+ for (const projectName of entries) {
1045
+ if (projectName === "memory") continue;
1046
+ const projectDir = (0, import_node_path.join)(projectsDir, projectName);
1047
+ try {
1048
+ const s = (0, import_node_fs.statSync)(projectDir);
1049
+ if (!s.isDirectory()) continue;
1050
+ } catch {
1051
+ continue;
1052
+ }
1053
+ let files;
1054
+ try {
1055
+ files = (0, import_node_fs.readdirSync)(projectDir);
1056
+ } catch {
1057
+ continue;
1058
+ }
1059
+ for (const file of files) {
1060
+ if (!file.endsWith(".jsonl")) continue;
1061
+ const filePath = (0, import_node_path.join)(projectDir, file);
1062
+ const sessionId = (0, import_node_path.basename)(file, ".jsonl");
1063
+ try {
1064
+ const s = (0, import_node_fs.statSync)(filePath);
1065
+ callback(filePath, sessionId, s.mtimeMs, false, null);
1066
+ } catch {
1067
+ continue;
1068
+ }
1069
+ this.discoverSubagents(filePath, sessionId, callback, 0, 4);
1070
+ }
1071
+ }
1072
+ }
1073
+ /**
1074
+ * Recursively discover subagent JSONL files under the subagents directory
1075
+ * that corresponds to `parentPath`.
1076
+ *
1077
+ * For a parent at `/path/to/session-1.jsonl`, looks for subagents at
1078
+ * `/path/to/session-1/subagents/agent-*.jsonl`.
1079
+ *
1080
+ * Recurses up to `maxDepth` levels (default 4).
1081
+ */
1082
+ discoverSubagents(parentPath, parentSessionId, callback, depth, maxDepth) {
1083
+ if (depth >= maxDepth) return;
1084
+ const subDir = parentPath.replace(/\.jsonl$/, "") + "/subagents";
1085
+ let files;
1086
+ try {
1087
+ files = (0, import_node_fs.readdirSync)(subDir);
1088
+ } catch {
1089
+ return;
1090
+ }
1091
+ for (const file of files) {
1092
+ if (!file.startsWith("agent-") || !file.endsWith(".jsonl")) continue;
1093
+ const filePath = (0, import_node_path.join)(subDir, file);
1094
+ try {
1095
+ const s = (0, import_node_fs.statSync)(filePath);
1096
+ callback(filePath, parentSessionId, s.mtimeMs, true, parentSessionId);
1097
+ } catch {
1098
+ continue;
1099
+ }
1100
+ this.discoverSubagents(filePath, parentSessionId, callback, depth + 1, maxDepth);
1101
+ }
1102
+ }
1103
+ /**
1104
+ * Start watching `projectsDir` for JSONL file changes.
1105
+ * Runs `updateStale()` immediately for an initial sync, then sets up
1106
+ * `fs.watch` with `{ recursive: true }` (macOS-compatible) to detect
1107
+ * subsequent file changes and trigger debounced re-indexing.
1108
+ */
1109
+ startWatching(projectsDir) {
1110
+ this.projectsDir = projectsDir;
1111
+ this.updateStale(projectsDir);
1112
+ try {
1113
+ this.watcher = (0, import_node_fs.watch)(projectsDir, { recursive: true }, (_event, filename) => {
1114
+ if (!filename || !filename.endsWith(".jsonl")) return;
1115
+ this.debouncedReindex((0, import_node_path.join)(projectsDir, filename));
1116
+ });
1117
+ this._watcherRunning = true;
1118
+ } catch (err) {
1119
+ console.warn("[search-index] fs.watch failed (recursive may not be supported):", err);
1120
+ this._watcherRunning = false;
1121
+ }
1122
+ }
1123
+ /**
1124
+ * Stop the file watcher and clear any pending debounce timers.
1125
+ * Safe to call even when not watching (no-op).
1126
+ */
1127
+ stopWatching() {
1128
+ if (this.watcher) {
1129
+ this.watcher.close();
1130
+ this.watcher = null;
1131
+ }
1132
+ for (const timer of this.debounceTimers.values()) {
1133
+ clearTimeout(timer);
1134
+ }
1135
+ this.debounceTimers.clear();
1136
+ this._watcherRunning = false;
1137
+ }
1138
+ /**
1139
+ * Private helper: debounce re-indexing of a single file.
1140
+ * Waits 2 seconds after the last change event for a given file path
1141
+ * before actually calling `indexFile()`. This coalesces rapid writes
1142
+ * (e.g. streaming JSONL appends) into a single index operation.
1143
+ */
1144
+ debouncedReindex(filePath) {
1145
+ const existing = this.debounceTimers.get(filePath);
1146
+ if (existing) clearTimeout(existing);
1147
+ this.debounceTimers.set(
1148
+ filePath,
1149
+ setTimeout(() => {
1150
+ this.debounceTimers.delete(filePath);
1151
+ try {
1152
+ const s = (0, import_node_fs.statSync)(filePath);
1153
+ const parts = filePath.split("/");
1154
+ const fileName = parts[parts.length - 1];
1155
+ const isSubagent = parts.includes("subagents");
1156
+ let sessionId;
1157
+ let parentSessionId = null;
1158
+ if (isSubagent) {
1159
+ const subagentsIdx = parts.lastIndexOf("subagents");
1160
+ const parentDir = parts[subagentsIdx - 1];
1161
+ sessionId = parentDir;
1162
+ parentSessionId = parentDir;
1163
+ } else {
1164
+ sessionId = (0, import_node_path.basename)(fileName, ".jsonl");
1165
+ }
1166
+ this.indexFile(filePath, sessionId, s.mtimeMs, {
1167
+ isSubagent,
1168
+ parentSessionId
1169
+ });
1170
+ } catch {
1171
+ }
1172
+ }, 2e3)
1173
+ );
1174
+ }
1175
+ close() {
1176
+ this.stopWatching();
1177
+ this.db.close();
1178
+ }
1179
+ };
1180
+
1181
+ // src/lib/dirs.ts
1182
+ var import_node_path2 = require("node:path");
1183
+ var import_node_os = require("node:os");
1184
+ var dirs = {
1185
+ PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
1186
+ TEAMS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "teams"),
1187
+ TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks")
1188
+ };
1189
+ var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
1190
+
1191
+ // src/commands/search.ts
1192
+ var import_node_fs2 = require("node:fs");
1193
+ var import_promises2 = require("node:fs/promises");
1194
+ var import_node_path4 = require("node:path");
1195
+
1196
+ // src/lib/response.ts
1197
+ function parseMaxAge(raw) {
1198
+ const match = raw.match(/^(\d+)([dhm])$/);
1199
+ if (!match) return 5 * 24 * 60 * 60 * 1e3;
1200
+ const value = parseInt(match[1], 10);
1201
+ const unit = match[2];
1202
+ switch (unit) {
1203
+ case "d":
1204
+ return value * 24 * 60 * 60 * 1e3;
1205
+ case "h":
1206
+ return value * 60 * 60 * 1e3;
1207
+ case "m":
1208
+ return value * 60 * 1e3;
1209
+ default:
1210
+ return 5 * 24 * 60 * 60 * 1e3;
1211
+ }
1212
+ }
1213
+
1214
+ // src/lib/helpers.ts
1215
+ var import_promises = require("node:fs/promises");
1216
+ var import_node_path3 = require("node:path");
1217
+ var import_node_os2 = require("node:os");
1218
+ async function findJsonlPath(sessionId) {
1219
+ const targetFile = `${sessionId}.jsonl`;
1220
+ try {
1221
+ const entries = await (0, import_promises.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
1222
+ for (const entry of entries) {
1223
+ if (!entry.isDirectory() || entry.name === "memory") continue;
1224
+ const projectDir = (0, import_node_path3.join)(dirs.PROJECTS_DIR, entry.name);
1225
+ try {
1226
+ const files = await (0, import_promises.readdir)(projectDir);
1227
+ if (files.includes(targetFile)) {
1228
+ return (0, import_node_path3.join)(projectDir, targetFile);
1229
+ }
1230
+ } catch {
1231
+ continue;
1232
+ }
1233
+ }
1234
+ } catch {
1235
+ }
1236
+ return null;
1237
+ }
1238
+ async function matchSubagentToMember(leadSessionId, subagentFileName, members) {
1239
+ const entries = await (0, import_promises.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
1240
+ for (const entry of entries) {
1241
+ if (!entry.isDirectory() || entry.name === "memory") continue;
1242
+ const filePath = (0, import_node_path3.join)(
1243
+ dirs.PROJECTS_DIR,
1244
+ entry.name,
1245
+ leadSessionId,
1246
+ "subagents",
1247
+ subagentFileName
1248
+ );
1249
+ try {
1250
+ const fh = await (0, import_promises.open)(filePath, "r");
1251
+ try {
1252
+ const buf = Buffer.alloc(16384);
1253
+ const { bytesRead } = await fh.read(buf, 0, 16384, 0);
1254
+ const firstLine = buf.subarray(0, bytesRead).toString("utf-8").split("\n")[0] || "";
1255
+ for (const member of members) {
1256
+ if (member.agentType === "team-lead") continue;
1257
+ const prompt = member.prompt || "";
1258
+ const snippet = prompt.slice(0, 120);
1259
+ const terms = [
1260
+ member.name,
1261
+ member.name.replace(/-/g, " "),
1262
+ ...snippet ? [snippet, snippet.replace(/"/g, '\\"')] : []
1263
+ ];
1264
+ if (terms.some((t) => firstLine.includes(t))) {
1265
+ return member.name;
1266
+ }
1267
+ }
1268
+ } finally {
1269
+ await fh.close();
1270
+ }
1271
+ } catch {
1272
+ continue;
1273
+ }
1274
+ }
1275
+ return null;
1276
+ }
1277
+ var HOME_PREFIX = (0, import_node_os2.homedir)().replace(/\//g, "-").replace(/^-/, "").toLowerCase();
1278
+
1279
+ // src/commands/search.ts
1280
+ async function searchSessions(query, opts, searchIndex) {
1281
+ if (!query || query.length < 2) {
1282
+ return { error: "Query must be at least 2 characters" };
1283
+ }
1284
+ const limit = Math.min(Math.max(1, opts.limit ?? 20), 200);
1285
+ const caseSensitive = opts.caseSensitive ?? false;
1286
+ const maxAgeMs = parseMaxAge(opts.maxAge ?? "5d");
1287
+ const depth = Math.min(Math.max(1, opts.depth ?? 4), 4);
1288
+ let index = searchIndex ?? null;
1289
+ let ownedIndex = false;
1290
+ if (!index && (0, import_node_fs2.existsSync)(DEFAULT_DB_PATH)) {
1291
+ try {
1292
+ index = new SearchIndex(DEFAULT_DB_PATH);
1293
+ ownedIndex = true;
1294
+ } catch {
1295
+ }
1296
+ }
1297
+ if (index) {
1298
+ try {
1299
+ const hits = index.search(query, {
1300
+ limit,
1301
+ sessionId: opts.sessionId,
1302
+ maxAgeMs,
1303
+ caseSensitive
1304
+ });
1305
+ const grouped = /* @__PURE__ */ new Map();
1306
+ for (const hit of hits) {
1307
+ let sessionHits = grouped.get(hit.sessionId);
1308
+ if (!sessionHits) {
1309
+ sessionHits = [];
1310
+ grouped.set(hit.sessionId, sessionHits);
1311
+ }
1312
+ sessionHits.push({
1313
+ location: hit.location,
1314
+ snippet: hit.snippet,
1315
+ matchCount: hit.matchCount
1316
+ });
1317
+ }
1318
+ const results = [];
1319
+ for (const [sid, sessionHits] of grouped) {
1320
+ results.push({ sessionId: sid, hits: sessionHits });
1321
+ }
1322
+ let totalHits = hits.length;
1323
+ let sessionsSearched = grouped.size;
1324
+ if (hits.length >= limit) {
1325
+ const counts = index.countMatches(query, {
1326
+ sessionId: opts.sessionId,
1327
+ maxAgeMs
1328
+ });
1329
+ totalHits = counts.totalHits;
1330
+ sessionsSearched = counts.sessionsSearched;
1331
+ }
1332
+ if (ownedIndex) index.close();
1333
+ return {
1334
+ query,
1335
+ totalHits,
1336
+ returnedHits: hits.length,
1337
+ sessionsSearched,
1338
+ results
1339
+ };
1340
+ } catch {
1341
+ if (ownedIndex) index.close();
1342
+ }
1343
+ }
1344
+ return rawScanSearch(query, opts.sessionId ?? null, maxAgeMs, limit, caseSensitive, depth);
1345
+ }
1346
+ var SNIPPET_WINDOW = 150;
1347
+ function generateSnippet(text, matchIdx, queryLen) {
1348
+ if (matchIdx === -1) return text.slice(0, SNIPPET_WINDOW);
1349
+ const halfWindow = Math.floor((SNIPPET_WINDOW - queryLen) / 2);
1350
+ const start = Math.max(0, matchIdx - halfWindow);
1351
+ const end = Math.min(text.length, start + SNIPPET_WINDOW);
1352
+ const adjustedStart = Math.max(0, end - SNIPPET_WINDOW);
1353
+ let snippet = text.slice(adjustedStart, end);
1354
+ if (adjustedStart > 0) snippet = "..." + snippet;
1355
+ if (end < text.length) snippet = snippet + "...";
1356
+ return snippet;
1357
+ }
1358
+ function countMatches(haystack, needle) {
1359
+ let count = 0;
1360
+ let pos = 0;
1361
+ while ((pos = haystack.indexOf(needle, pos)) !== -1) {
1362
+ count++;
1363
+ pos += needle.length;
1364
+ }
1365
+ return count;
1366
+ }
1367
+ function searchField(text, location, query, caseSensitive, extras) {
1368
+ if (!text) return null;
1369
+ const haystack = caseSensitive ? text : text.toLowerCase();
1370
+ const needle = caseSensitive ? query : query.toLowerCase();
1371
+ const idx = haystack.indexOf(needle);
1372
+ if (idx === -1) return null;
1373
+ return {
1374
+ location,
1375
+ snippet: generateSnippet(text, idx, query.length),
1376
+ matchCount: countMatches(haystack, needle),
1377
+ ...extras?.toolName && { toolName: extras.toolName },
1378
+ ...extras?.agentName && { agentName: extras.agentName }
1379
+ };
1380
+ }
1381
+ async function discoverSingleSession(sessionId) {
1382
+ const jsonlPath = await findJsonlPath(sessionId);
1383
+ if (!jsonlPath) return [];
1384
+ const s = await (0, import_promises2.stat)(jsonlPath);
1385
+ return [{ path: jsonlPath, mtimeMs: s.mtimeMs }];
1386
+ }
1387
+ async function discoverAllSessions(maxAgeMs) {
1388
+ const cutoff = Date.now() - maxAgeMs;
1389
+ let entries;
1390
+ try {
1391
+ entries = await (0, import_promises2.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
1392
+ } catch {
1393
+ return [];
1394
+ }
1395
+ const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path4.join)(dirs.PROJECTS_DIR, e.name));
1396
+ const nested = await Promise.all(
1397
+ projectDirs.map(async (projectDir) => {
1398
+ try {
1399
+ const files = await (0, import_promises2.readdir)(projectDir);
1400
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
1401
+ const statResults = await Promise.all(
1402
+ jsonlFiles.map(async (f) => {
1403
+ const filePath = (0, import_node_path4.join)(projectDir, f);
1404
+ try {
1405
+ const s = await (0, import_promises2.stat)(filePath);
1406
+ return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
1407
+ } catch {
1408
+ return null;
1409
+ }
1410
+ })
1411
+ );
1412
+ return statResults.filter((r) => r !== null);
1413
+ } catch {
1414
+ return [];
1415
+ }
1416
+ })
1417
+ );
1418
+ const results = nested.flat();
1419
+ results.sort((a, b) => b.mtimeMs - a.mtimeMs);
1420
+ return results;
1421
+ }
1422
+ async function rawTextMatch(filePath, query, caseSensitive) {
1423
+ try {
1424
+ const content = await (0, import_promises2.readFile)(filePath, "utf-8");
1425
+ const haystack = caseSensitive ? content : content.toLowerCase();
1426
+ const needle = caseSensitive ? query : query.toLowerCase();
1427
+ if (haystack.includes(needle)) return content;
1428
+ return null;
1429
+ } catch {
1430
+ return null;
1431
+ }
1432
+ }
1433
+ function walkSession(session, query, caseSensitive, locationPrefix = "") {
1434
+ const hits = [];
1435
+ for (let i = 0; i < session.turns.length; i++) {
1436
+ const turn = session.turns[i];
1437
+ const prefix = `${locationPrefix}turn/${i}`;
1438
+ const userText = getUserMessageText(turn.userMessage);
1439
+ const userHit = searchField(userText || null, `${prefix}/userMessage`, query, caseSensitive);
1440
+ if (userHit) hits.push(userHit);
1441
+ const assistantText = turn.assistantText.length > 0 ? turn.assistantText.join("\n\n") : null;
1442
+ const assistantHit = searchField(assistantText, `${prefix}/assistantMessage`, query, caseSensitive);
1443
+ if (assistantHit) hits.push(assistantHit);
1444
+ for (const tb of turn.thinking) {
1445
+ if (!tb.thinking) continue;
1446
+ const thinkHit = searchField(tb.thinking, `${prefix}/thinking`, query, caseSensitive);
1447
+ if (thinkHit) {
1448
+ hits.push(thinkHit);
1449
+ break;
1450
+ }
1451
+ }
1452
+ for (const tc of turn.toolCalls) {
1453
+ const inputStr = JSON.stringify(tc.input);
1454
+ const inputHit = searchField(inputStr, `${prefix}/toolCall/${tc.id}/input`, query, caseSensitive, { toolName: tc.name });
1455
+ if (inputHit) hits.push(inputHit);
1456
+ const resultHit = searchField(tc.result, `${prefix}/toolCall/${tc.id}/result`, query, caseSensitive, { toolName: tc.name });
1457
+ if (resultHit) hits.push(resultHit);
1458
+ }
1459
+ for (const sa of turn.subAgentActivity) {
1460
+ const saPrefix = `${locationPrefix}agent/${sa.agentId}/`;
1461
+ const saText = sa.text.length > 0 ? sa.text.join("\n\n") : null;
1462
+ const saTextHit = searchField(saText, `${saPrefix}assistantMessage`, query, caseSensitive, {
1463
+ agentName: sa.agentName ?? void 0
1464
+ });
1465
+ if (saTextHit) hits.push(saTextHit);
1466
+ for (const t of sa.thinking) {
1467
+ const tHit = searchField(t, `${saPrefix}thinking`, query, caseSensitive, { agentName: sa.agentName ?? void 0 });
1468
+ if (tHit) {
1469
+ hits.push(tHit);
1470
+ break;
1471
+ }
1472
+ }
1473
+ for (const tc of sa.toolCalls) {
1474
+ const inputStr = JSON.stringify(tc.input);
1475
+ const inputHit = searchField(inputStr, `${saPrefix}toolCall/${tc.id}/input`, query, caseSensitive, {
1476
+ toolName: tc.name,
1477
+ agentName: sa.agentName ?? void 0
1478
+ });
1479
+ if (inputHit) hits.push(inputHit);
1480
+ const resultHit = searchField(tc.result, `${saPrefix}toolCall/${tc.id}/result`, query, caseSensitive, {
1481
+ toolName: tc.name,
1482
+ agentName: sa.agentName ?? void 0
1483
+ });
1484
+ if (resultHit) hits.push(resultHit);
1485
+ }
1486
+ }
1487
+ if (turn.compactionSummary) {
1488
+ const compHit = searchField(turn.compactionSummary, `${prefix}/compactionSummary`, query, caseSensitive);
1489
+ if (compHit) hits.push(compHit);
1490
+ }
1491
+ }
1492
+ return hits;
1493
+ }
1494
+ async function walkSubagentFiles(parentJsonlPath, query, caseSensitive, currentDepth, maxDepth) {
1495
+ if (currentDepth >= maxDepth) return [];
1496
+ const subDir = parentJsonlPath.replace(/\.jsonl$/, "") + "/subagents";
1497
+ let files;
1498
+ try {
1499
+ files = await (0, import_promises2.readdir)(subDir);
1500
+ } catch {
1501
+ return [];
1502
+ }
1503
+ const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => ({
1504
+ agentId: f.slice("agent-".length, -".jsonl".length),
1505
+ filePath: (0, import_node_path4.join)(subDir, f)
1506
+ }));
1507
+ const matched = await Promise.all(
1508
+ agentFiles.map(async ({ agentId, filePath }) => ({
1509
+ agentId,
1510
+ filePath,
1511
+ rawContent: await rawTextMatch(filePath, query, caseSensitive)
1512
+ }))
1513
+ );
1514
+ const hitArrays = await Promise.all(
1515
+ matched.filter((m) => m.rawContent).map(async ({ agentId, filePath, rawContent }) => {
1516
+ const subSession = parseSession(rawContent);
1517
+ const subHits = walkSession(subSession, query, caseSensitive, `agent/${agentId}/`);
1518
+ const nestedHits = await walkSubagentFiles(filePath, query, caseSensitive, currentDepth + 1, maxDepth);
1519
+ return [...subHits, ...nestedHits];
1520
+ })
1521
+ );
1522
+ return hitArrays.flat();
1523
+ }
1524
+ async function rawScanSearch(query, sessionId, maxAgeMs, limit, caseSensitive, depth) {
1525
+ try {
1526
+ const files = sessionId ? await discoverSingleSession(sessionId) : await discoverAllSessions(maxAgeMs);
1527
+ let totalHits = 0;
1528
+ let returnedHits = 0;
1529
+ const results = [];
1530
+ let sessionsSearched = 0;
1531
+ for (const file of files) {
1532
+ if (returnedHits >= limit) break;
1533
+ const rawContent = await rawTextMatch(file.path, query, caseSensitive);
1534
+ sessionsSearched++;
1535
+ if (!rawContent) continue;
1536
+ const session = parseSession(rawContent);
1537
+ const sessionHits = walkSession(session, query, caseSensitive);
1538
+ const subagentHits = await walkSubagentFiles(file.path, query, caseSensitive, 0, depth);
1539
+ const allHits = [...sessionHits, ...subagentHits];
1540
+ if (allHits.length === 0) continue;
1541
+ totalHits += allHits.length;
1542
+ const sessionResult = {
1543
+ sessionId: session.sessionId || (0, import_node_path4.basename)(file.path, ".jsonl"),
1544
+ hits: []
1545
+ };
1546
+ for (const hit of allHits) {
1547
+ if (returnedHits < limit) {
1548
+ sessionResult.hits.push(hit);
1549
+ returnedHits++;
1550
+ }
1551
+ }
1552
+ if (sessionResult.hits.length > 0) {
1553
+ results.push(sessionResult);
1554
+ }
1555
+ }
1556
+ return {
1557
+ query,
1558
+ totalHits,
1559
+ returnedHits,
1560
+ sessionsSearched,
1561
+ results
1562
+ };
1563
+ } catch (err) {
1564
+ return {
1565
+ query,
1566
+ totalHits: 0,
1567
+ returnedHits: 0,
1568
+ sessionsSearched: 0,
1569
+ results: []
1570
+ };
1571
+ }
1572
+ }
1573
+
1574
+ // src/commands/context.ts
1575
+ var import_promises3 = require("node:fs/promises");
1576
+ var import_node_path5 = require("node:path");
1577
+ var RESULT_TRUNCATE_LIMIT = 1e4;
1578
+ var L1_RESPONSE_LIMIT = 15e4;
1579
+ function extractUserMessageText(userMessage) {
1580
+ if (userMessage === null) return null;
1581
+ if (typeof userMessage === "string") return userMessage;
1582
+ const parts = [];
1583
+ for (const block of userMessage) {
1584
+ if (block.type === "text") parts.push(block.text);
1585
+ else if (block.type === "image") parts.push("[image attached]");
1586
+ }
1587
+ return parts.length > 0 ? parts.join("\n") : null;
1588
+ }
1589
+ function truncateResult(result) {
1590
+ if (result === null) return { result: null, resultTruncated: false };
1591
+ if (result.length <= RESULT_TRUNCATE_LIMIT) return { result, resultTruncated: false };
1592
+ return { result: result.slice(0, RESULT_TRUNCATE_LIMIT), resultTruncated: true };
1593
+ }
1594
+ function mapSubAgentSummary(msg) {
1595
+ return {
1596
+ agentId: msg.agentId,
1597
+ name: msg.agentName ?? null,
1598
+ type: msg.subagentType ?? null,
1599
+ status: msg.status ?? null,
1600
+ durationMs: msg.durationMs ?? null,
1601
+ toolUseCount: msg.toolUseCount ?? null,
1602
+ isBackground: msg.isBackground
1603
+ };
1604
+ }
1605
+ function mapSubAgentDetail(msg) {
1606
+ return {
1607
+ ...mapSubAgentSummary(msg),
1608
+ prompt: msg.prompt ?? null,
1609
+ resultText: msg.text.join("\n\n") || null
1610
+ };
1611
+ }
1612
+ function mapSessionToOverview(session) {
1613
+ const turns = session.turns.map((turn, i) => mapTurnToSummary(turn, i));
1614
+ const compacted = session.turns.some((t) => t.compactionSummary != null);
1615
+ const overview = {
1616
+ sessionId: session.sessionId,
1617
+ cwd: session.cwd,
1618
+ model: session.model,
1619
+ branchedFrom: session.branchedFrom ?? null,
1620
+ compacted,
1621
+ turns,
1622
+ stats: {
1623
+ totalTurns: session.stats.turnCount,
1624
+ totalToolCalls: Object.values(session.stats.toolCallCounts).reduce((a, b) => a + b, 0),
1625
+ totalTokens: {
1626
+ input: session.stats.totalInputTokens,
1627
+ output: session.stats.totalOutputTokens
1628
+ }
1629
+ }
1630
+ };
1631
+ let serialized = JSON.stringify(overview);
1632
+ if (serialized.length > L1_RESPONSE_LIMIT) {
1633
+ const entries = [];
1634
+ for (const t of overview.turns) {
1635
+ if (t.assistantMessage) entries.push({ turn: t, field: "assistantMessage", len: t.assistantMessage.length });
1636
+ if (t.userMessage) entries.push({ turn: t, field: "userMessage", len: t.userMessage.length });
1637
+ }
1638
+ entries.sort((a, b) => b.len - a.len);
1639
+ for (const entry of entries) {
1640
+ const current = entry.turn[entry.field];
1641
+ if (current && current.length > 200) {
1642
+ entry.turn[entry.field] = current.slice(0, 200) + "... [truncated, use L2 for full text]";
1643
+ serialized = JSON.stringify(overview);
1644
+ if (serialized.length <= L1_RESPONSE_LIMIT) break;
1645
+ }
1646
+ }
1647
+ }
1648
+ return overview;
1649
+ }
1650
+ function mapTurnToSummary(turn, turnIndex) {
1651
+ const toolSummary = {};
1652
+ for (const tc of turn.toolCalls) {
1653
+ toolSummary[tc.name] = (toolSummary[tc.name] ?? 0) + 1;
1654
+ }
1655
+ const agentById = /* @__PURE__ */ new Map();
1656
+ for (const msg of turn.subAgentActivity) {
1657
+ agentById.set(msg.agentId, msg);
1658
+ }
1659
+ const subAgents = [...agentById.values()].map(mapSubAgentSummary);
1660
+ return {
1661
+ turnIndex,
1662
+ userMessage: extractUserMessageText(turn.userMessage),
1663
+ assistantMessage: turn.assistantText.length > 0 ? turn.assistantText.join("\n\n") : null,
1664
+ toolSummary,
1665
+ subAgents,
1666
+ hasThinking: turn.thinking.length > 0,
1667
+ isError: turn.toolCalls.some((tc) => tc.isError),
1668
+ compactionSummary: turn.compactionSummary ?? null
1669
+ };
1670
+ }
1671
+ function mapTurnToDetail(session, turnIndex) {
1672
+ const turn = session.turns[turnIndex];
1673
+ const contentBlocks = turn.contentBlocks.map((block) => mapContentBlock(block));
1674
+ return {
1675
+ sessionId: session.sessionId,
1676
+ turnIndex,
1677
+ userMessage: extractUserMessageText(turn.userMessage),
1678
+ contentBlocks,
1679
+ tokenUsage: turn.tokenUsage ? { input: turn.tokenUsage.input_tokens, output: turn.tokenUsage.output_tokens } : null,
1680
+ model: turn.model,
1681
+ durationMs: turn.durationMs
1682
+ };
1683
+ }
1684
+ function mapContentBlock(block) {
1685
+ switch (block.kind) {
1686
+ case "thinking": {
1687
+ const text = block.blocks.filter((b) => b.thinking.length > 0).map((b) => b.thinking).join("\n\n");
1688
+ return { kind: "thinking", text, timestamp: block.timestamp ?? null };
1689
+ }
1690
+ case "text":
1691
+ return { kind: "text", text: block.text.join("\n\n"), timestamp: block.timestamp ?? null };
1692
+ case "tool_calls":
1693
+ return {
1694
+ kind: "tool_calls",
1695
+ toolCalls: block.toolCalls.map((tc) => {
1696
+ const { result, resultTruncated } = truncateResult(tc.result);
1697
+ return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
1698
+ }),
1699
+ timestamp: block.timestamp ?? null
1700
+ };
1701
+ case "sub_agent":
1702
+ case "background_agent":
1703
+ return {
1704
+ kind: block.kind,
1705
+ agents: block.messages.map(mapSubAgentDetail),
1706
+ timestamp: block.timestamp ?? null
1707
+ };
1708
+ }
1709
+ }
1710
+ async function findSubagentFile(parentJsonlPath, agentId) {
1711
+ const subagentsDir = parentJsonlPath.replace(/\.jsonl$/, "") + "/subagents";
1712
+ try {
1713
+ const files = await (0, import_promises3.readdir)(subagentsDir);
1714
+ for (const f of files) {
1715
+ if (!f.startsWith("agent-") || !f.endsWith(".jsonl")) continue;
1716
+ const fileAgentId = f.replace("agent-", "").replace(".jsonl", "");
1717
+ if (fileAgentId === agentId) {
1718
+ return { filePath: (0, import_node_path5.join)(subagentsDir, f), fileName: f };
1719
+ }
1720
+ }
1721
+ } catch {
1722
+ }
1723
+ return null;
1724
+ }
1725
+ async function findTeamContext(sessionId, subagentFileName) {
1726
+ try {
1727
+ const teamEntries = await (0, import_promises3.readdir)(dirs.TEAMS_DIR, { withFileTypes: true });
1728
+ for (const entry of teamEntries) {
1729
+ if (!entry.isDirectory()) continue;
1730
+ const configPath = (0, import_node_path5.join)(dirs.TEAMS_DIR, entry.name, "config.json");
1731
+ try {
1732
+ const raw = await (0, import_promises3.readFile)(configPath, "utf-8");
1733
+ const config = JSON.parse(raw);
1734
+ if (config.leadSessionId !== sessionId) continue;
1735
+ const members = config.members ?? [];
1736
+ const memberName = await matchSubagentToMember(sessionId, subagentFileName, members);
1737
+ if (!memberName) continue;
1738
+ let currentTask = null;
1739
+ try {
1740
+ const taskDir = (0, import_node_path5.join)(dirs.TASKS_DIR, entry.name);
1741
+ const taskFiles = await (0, import_promises3.readdir)(taskDir);
1742
+ for (const tf of taskFiles) {
1743
+ if (!tf.endsWith(".json")) continue;
1744
+ const taskRaw = await (0, import_promises3.readFile)((0, import_node_path5.join)(taskDir, tf), "utf-8");
1745
+ const task = JSON.parse(taskRaw);
1746
+ if (task.owner === memberName && task.status === "in_progress") {
1747
+ currentTask = { id: task.id, subject: task.subject, status: task.status };
1748
+ break;
1749
+ }
1750
+ }
1751
+ } catch {
1752
+ }
1753
+ return { teamName: entry.name, role: memberName, currentTask };
1754
+ } catch {
1755
+ }
1756
+ }
1757
+ } catch {
1758
+ }
1759
+ return null;
1760
+ }
1761
+ function findParentToolCallId(session, agentId) {
1762
+ for (const turn of session.turns) {
1763
+ for (const msg of turn.subAgentActivity) {
1764
+ if (msg.agentId === agentId) {
1765
+ for (const tc of turn.toolCalls) {
1766
+ if (tc.name === "Task" || tc.name === "Agent") {
1767
+ return tc.id;
1768
+ }
1769
+ }
1770
+ }
1771
+ }
1772
+ }
1773
+ return null;
1774
+ }
1775
+ function findAgentMetadata(session, agentId) {
1776
+ for (const turn of session.turns) {
1777
+ for (const msg of turn.subAgentActivity) {
1778
+ if (msg.agentId === agentId) {
1779
+ return {
1780
+ name: msg.agentName,
1781
+ type: msg.subagentType,
1782
+ isBackground: msg.isBackground
1783
+ };
1784
+ }
1785
+ }
1786
+ }
1787
+ return { name: null, type: null, isBackground: false };
1788
+ }
1789
+ async function getSessionOverview(sessionId) {
1790
+ const jsonlPath = await findJsonlPath(sessionId);
1791
+ if (!jsonlPath) return { error: "Session not found" };
1792
+ const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
1793
+ const session = parseSession(content);
1794
+ return mapSessionToOverview(session);
1795
+ }
1796
+ async function getTurnDetail(sessionId, turnIndex) {
1797
+ const jsonlPath = await findJsonlPath(sessionId);
1798
+ if (!jsonlPath) return { error: "Session not found" };
1799
+ const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
1800
+ const session = parseSession(content);
1801
+ if (turnIndex < 0 || turnIndex >= session.turns.length) return { error: "Turn not found" };
1802
+ return mapTurnToDetail(session, turnIndex);
1803
+ }
1804
+ async function getAgentOverview(sessionId, agentId) {
1805
+ const jsonlPath = await findJsonlPath(sessionId);
1806
+ if (!jsonlPath) return { error: "Session not found" };
1807
+ const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
1808
+ const session = parseSession(content);
1809
+ const subagentFile = await findSubagentFile(jsonlPath, agentId);
1810
+ if (!subagentFile) return { error: "Agent not found" };
1811
+ const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
1812
+ const subagentSession = parseSession(subagentContent);
1813
+ const metadata = findAgentMetadata(session, agentId);
1814
+ const parentToolCallId = findParentToolCallId(session, agentId);
1815
+ const teamContext = await findTeamContext(sessionId, subagentFile.fileName);
1816
+ return {
1817
+ sessionId,
1818
+ agentId,
1819
+ name: metadata.name,
1820
+ type: metadata.type,
1821
+ parentToolCallId,
1822
+ isBackground: metadata.isBackground,
1823
+ teamContext,
1824
+ overview: mapSessionToOverview(subagentSession)
1825
+ };
1826
+ }
1827
+ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
1828
+ const jsonlPath = await findJsonlPath(sessionId);
1829
+ if (!jsonlPath) return { error: "Session not found" };
1830
+ const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
1831
+ const subagentFile = await findSubagentFile(jsonlPath, agentId);
1832
+ if (!subagentFile) return { error: "Agent not found" };
1833
+ const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
1834
+ const subagentSession = parseSession(subagentContent);
1835
+ if (turnIndex < 0 || turnIndex >= subagentSession.turns.length) return { error: "Turn not found" };
1836
+ return mapTurnToDetail(subagentSession, turnIndex);
1837
+ }
1838
+
1839
+ // src/commands/sessions.ts
1840
+ var import_promises5 = require("node:fs/promises");
1841
+ var import_node_path6 = require("node:path");
1842
+
1843
+ // src/lib/metadata.ts
1844
+ var import_promises4 = require("node:fs/promises");
1845
+
1846
+ // src/lib/sessionStatus.ts
1847
+ function deriveSessionStatus(rawMessages) {
1848
+ let pendingEnqueues = 0;
1849
+ function result(status, toolName) {
1850
+ const info = { status, pendingQueue: Math.max(0, pendingEnqueues) };
1851
+ if (toolName) info.toolName = toolName;
1852
+ return info;
1853
+ }
1854
+ for (let i = rawMessages.length - 1; i >= 0; i--) {
1855
+ const msg = rawMessages[i];
1856
+ if (msg.type === "queue-operation") {
1857
+ const op = msg.operation;
1858
+ if (op === "enqueue") pendingEnqueues++;
1859
+ else if (op === "dequeue" || op === "remove") pendingEnqueues--;
1860
+ continue;
1861
+ }
1862
+ if (msg.type === "assistant") {
1863
+ const message = msg.message;
1864
+ const stopReason = message?.stop_reason;
1865
+ if (stopReason === "end_turn") {
1866
+ let hasActivity = false;
1867
+ for (let j = i - 1; j >= 0; j--) {
1868
+ const m = rawMessages[j];
1869
+ if (m.type === "user" && !m.isMeta) {
1870
+ hasActivity = true;
1871
+ break;
1872
+ }
1873
+ }
1874
+ return result(hasActivity ? "completed" : "idle");
1875
+ }
1876
+ if (stopReason === "tool_use") {
1877
+ const content = message?.content;
1878
+ const toolUseBlock = content?.findLast?.((b) => b.type === "tool_use");
1879
+ return result("tool_use", toolUseBlock?.name);
1880
+ }
1881
+ return result("thinking");
1882
+ }
1883
+ if (msg.type === "user") {
1884
+ const isMeta = msg.isMeta;
1885
+ if (isMeta) continue;
1886
+ return result("processing");
1887
+ }
1888
+ if (msg.type === "summary") return result("compacting");
1889
+ }
1890
+ return { status: "idle" };
1891
+ }
1892
+
1893
+ // src/lib/metadata.ts
1894
+ async function getSessionMeta(filePath) {
1895
+ let lines;
1896
+ let isPartialRead = false;
1897
+ const fileStat = await (0, import_promises4.stat)(filePath);
1898
+ if (fileStat.size > 65536) {
1899
+ const fh = await (0, import_promises4.open)(filePath, "r");
1900
+ try {
1901
+ const buf = Buffer.alloc(32768);
1902
+ const { bytesRead } = await fh.read(buf, 0, 32768, 0);
1903
+ const text = buf.subarray(0, bytesRead).toString("utf-8");
1904
+ const lastNewline = text.lastIndexOf("\n");
1905
+ lines = (lastNewline > 0 ? text.slice(0, lastNewline) : text).split("\n").filter(Boolean);
1906
+ isPartialRead = true;
1907
+ } finally {
1908
+ await fh.close();
1909
+ }
1910
+ } else {
1911
+ const content = await (0, import_promises4.readFile)(filePath, "utf-8");
1912
+ lines = content.split("\n").filter(Boolean);
1913
+ }
1914
+ let sessionId = "";
1915
+ let version = "";
1916
+ let gitBranch = "";
1917
+ let model = "";
1918
+ let slug = "";
1919
+ let cwd = "";
1920
+ let firstUserMessage = "";
1921
+ let lastUserMessage = "";
1922
+ let timestamp = "";
1923
+ let turnCount = 0;
1924
+ let branchedFrom;
1925
+ for (const line of lines) {
1926
+ try {
1927
+ const obj = JSON.parse(line);
1928
+ if (obj.sessionId && !sessionId) sessionId = obj.sessionId;
1929
+ if (obj.version && !version) version = obj.version;
1930
+ if (obj.gitBranch && !gitBranch) gitBranch = obj.gitBranch;
1931
+ if (obj.slug && !slug) slug = obj.slug;
1932
+ if (obj.cwd && !cwd) cwd = obj.cwd;
1933
+ if (obj.branchedFrom && !branchedFrom) branchedFrom = obj.branchedFrom;
1934
+ if (obj.type === "assistant" && obj.message?.model && !model) {
1935
+ model = obj.message.model;
1936
+ }
1937
+ if (obj.type === "user" && !obj.isMeta && !timestamp) {
1938
+ timestamp = obj.timestamp || "";
1939
+ }
1940
+ if (obj.type === "user" && !obj.isMeta) {
1941
+ const c = obj.message?.content;
1942
+ let extracted = "";
1943
+ if (typeof c === "string") {
1944
+ const cleaned = c.replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g, "").trim();
1945
+ if (cleaned && cleaned.length > 5) extracted = cleaned.slice(0, 120);
1946
+ } else if (Array.isArray(c)) {
1947
+ for (const block of c) {
1948
+ if (block.type === "text") {
1949
+ const cleaned = block.text.replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g, "").trim();
1950
+ if (cleaned && cleaned.length > 5) {
1951
+ extracted = cleaned.slice(0, 120);
1952
+ break;
1953
+ }
1954
+ }
1955
+ }
1956
+ }
1957
+ if (extracted) {
1958
+ if (!firstUserMessage) firstUserMessage = extracted;
1959
+ lastUserMessage = extracted;
1960
+ }
1961
+ turnCount++;
1962
+ }
1963
+ } catch {
1964
+ }
1965
+ }
1966
+ return {
1967
+ sessionId,
1968
+ version,
1969
+ gitBranch,
1970
+ model,
1971
+ slug,
1972
+ cwd,
1973
+ firstUserMessage,
1974
+ lastUserMessage,
1975
+ timestamp,
1976
+ turnCount,
1977
+ lineCount: isPartialRead ? Math.round(fileStat.size / (32768 / lines.length)) : lines.length,
1978
+ branchedFrom
1979
+ };
1980
+ }
1981
+ async function getSessionStatus(filePath) {
1982
+ const CHUNK = 4096;
1983
+ const MAX_CHUNKS = 64;
1984
+ try {
1985
+ const fileStat = await (0, import_promises4.stat)(filePath);
1986
+ if (fileStat.size === 0) return { status: "idle" };
1987
+ const fh = await (0, import_promises4.open)(filePath, "r");
1988
+ try {
1989
+ const meaningful = [];
1990
+ let cursor = fileStat.size;
1991
+ let leftover = "";
1992
+ for (let chunk = 0; chunk < MAX_CHUNKS && cursor > 0; chunk++) {
1993
+ const readSize = Math.min(CHUNK, cursor);
1994
+ cursor -= readSize;
1995
+ const buf = Buffer.alloc(readSize);
1996
+ const { bytesRead } = await fh.read(buf, 0, readSize, cursor);
1997
+ const text = buf.subarray(0, bytesRead).toString("utf-8") + leftover;
1998
+ const lines = text.split("\n");
1999
+ leftover = cursor > 0 ? lines[0] : "";
2000
+ const startIdx = cursor > 0 ? 1 : 0;
2001
+ for (let i = lines.length - 1; i >= startIdx; i--) {
2002
+ const line = lines[i];
2003
+ if (!line) continue;
2004
+ let obj;
2005
+ try {
2006
+ obj = JSON.parse(line);
2007
+ } catch {
2008
+ continue;
2009
+ }
2010
+ if (obj.type === "assistant" || obj.type === "user" || obj.type === "queue-operation") {
2011
+ meaningful.unshift(obj);
2012
+ const isEndTurn = obj.type === "assistant" && obj.message?.stop_reason === "end_turn";
2013
+ const canDerive = obj.type === "assistant" && !isEndTurn || obj.type === "user" && !obj.isMeta;
2014
+ if (canDerive) return deriveSessionStatus(meaningful);
2015
+ }
2016
+ }
2017
+ }
2018
+ return meaningful.length > 0 ? deriveSessionStatus(meaningful) : { status: "idle" };
2019
+ } finally {
2020
+ await fh.close();
2021
+ }
2022
+ } catch {
2023
+ return { status: "idle" };
2024
+ }
2025
+ }
2026
+
2027
+ // src/commands/sessions.ts
2028
+ async function listSessions(opts = {}) {
2029
+ const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
2030
+ const maxAgeMs = parseMaxAge(opts.maxAge ?? "7d");
2031
+ const cutoff = Date.now() - maxAgeMs;
2032
+ let entries;
2033
+ try {
2034
+ entries = await (0, import_promises5.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
2035
+ } catch {
2036
+ return [];
2037
+ }
2038
+ const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path6.join)(dirs.PROJECTS_DIR, e.name));
2039
+ const nested = await Promise.all(
2040
+ projectDirs.map(async (projectDir) => {
2041
+ try {
2042
+ const files = await (0, import_promises5.readdir)(projectDir);
2043
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2044
+ const statResults = await Promise.all(
2045
+ jsonlFiles.map(async (f) => {
2046
+ const filePath = (0, import_node_path6.join)(projectDir, f);
2047
+ try {
2048
+ const s = await (0, import_promises5.stat)(filePath);
2049
+ return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
2050
+ } catch {
2051
+ return null;
2052
+ }
2053
+ })
2054
+ );
2055
+ return statResults.filter((r) => r !== null);
2056
+ } catch {
2057
+ return [];
2058
+ }
2059
+ })
2060
+ );
2061
+ const allFiles = nested.flat();
2062
+ allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
2063
+ const results = [];
2064
+ for (const file of allFiles) {
2065
+ if (results.length >= limit) break;
2066
+ try {
2067
+ const [meta, statusInfo] = await Promise.all([
2068
+ getSessionMeta(file.path),
2069
+ getSessionStatus(file.path)
2070
+ ]);
2071
+ if (opts.cwd && meta.cwd !== opts.cwd) continue;
2072
+ results.push({
2073
+ sessionId: meta.sessionId,
2074
+ timestamp: meta.timestamp,
2075
+ model: meta.model,
2076
+ cwd: meta.cwd,
2077
+ gitBranch: meta.gitBranch,
2078
+ slug: meta.slug,
2079
+ firstMessage: meta.firstUserMessage,
2080
+ lastMessage: meta.lastUserMessage,
2081
+ turnCount: meta.turnCount,
2082
+ status: statusInfo.status,
2083
+ mtime: file.mtimeMs
2084
+ });
2085
+ } catch {
2086
+ }
2087
+ }
2088
+ return results;
2089
+ }
2090
+ async function currentSession(cwd) {
2091
+ const projectDirName = cwd.replace(/[/.]/g, "-");
2092
+ const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, projectDirName);
2093
+ let files;
2094
+ try {
2095
+ files = await (0, import_promises5.readdir)(projectDir);
2096
+ } catch {
2097
+ return null;
2098
+ }
2099
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2100
+ if (jsonlFiles.length === 0) return null;
2101
+ const statResults = await Promise.all(
2102
+ jsonlFiles.map(async (f) => {
2103
+ const filePath = (0, import_node_path6.join)(projectDir, f);
2104
+ try {
2105
+ const s = await (0, import_promises5.stat)(filePath);
2106
+ return { path: filePath, mtimeMs: s.mtimeMs };
2107
+ } catch {
2108
+ return null;
2109
+ }
2110
+ })
2111
+ );
2112
+ const valid = statResults.filter((r) => r !== null);
2113
+ valid.sort((a, b) => b.mtimeMs - a.mtimeMs);
2114
+ const latest = valid[0];
2115
+ if (!latest) return null;
2116
+ const [meta, statusInfo] = await Promise.all([
2117
+ getSessionMeta(latest.path),
2118
+ getSessionStatus(latest.path)
2119
+ ]);
2120
+ return {
2121
+ sessionId: meta.sessionId,
2122
+ timestamp: meta.timestamp,
2123
+ model: meta.model,
2124
+ cwd: meta.cwd,
2125
+ gitBranch: meta.gitBranch,
2126
+ slug: meta.slug,
2127
+ firstMessage: meta.firstUserMessage,
2128
+ lastMessage: meta.lastUserMessage,
2129
+ turnCount: meta.turnCount,
2130
+ status: statusInfo.status,
2131
+ mtime: latest.mtimeMs
2132
+ };
2133
+ }
2134
+
2135
+ // src/commands/index-cmd.ts
2136
+ var import_node_fs3 = require("node:fs");
2137
+ var import_node_path7 = require("node:path");
2138
+ async function indexStats(dbPath) {
2139
+ const path = dbPath ?? DEFAULT_DB_PATH;
2140
+ if (!(0, import_node_fs3.existsSync)(path)) {
2141
+ return { error: `Database not found at ${path}. Run 'cogpit-memory index rebuild' to create it.` };
2142
+ }
2143
+ const index = new SearchIndex(path);
2144
+ const stats = index.getStats();
2145
+ index.close();
2146
+ return stats;
2147
+ }
2148
+ async function indexRebuild(dbPath) {
2149
+ const path = dbPath ?? DEFAULT_DB_PATH;
2150
+ (0, import_node_fs3.mkdirSync)((0, import_node_path7.dirname)(path), { recursive: true });
2151
+ const index = new SearchIndex(path);
2152
+ index.buildFull(dirs.PROJECTS_DIR);
2153
+ const stats = index.getStats();
2154
+ index.close();
2155
+ return { status: "rebuilt", stats };
2156
+ }
2157
+ // Annotate the CommonJS export names for ESM import in node:
2158
+ 0 && (module.exports = {
2159
+ DEFAULT_DB_PATH,
2160
+ SearchIndex,
2161
+ currentSession,
2162
+ dirs,
2163
+ getAgentOverview,
2164
+ getAgentTurnDetail,
2165
+ getSessionOverview,
2166
+ getTurnDetail,
2167
+ getUserMessageImages,
2168
+ getUserMessageText,
2169
+ indexRebuild,
2170
+ indexStats,
2171
+ listSessions,
2172
+ parseSession,
2173
+ parseSessionAppend,
2174
+ searchSessions
2175
+ });