claude-sessions 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 (48) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +140 -0
  3. package/data/model-pricing.json +113 -0
  4. package/dist/cost-calculator.d.ts +49 -0
  5. package/dist/cost-calculator.d.ts.map +1 -0
  6. package/dist/cost-calculator.js +429 -0
  7. package/dist/cost-calculator.js.map +1 -0
  8. package/dist/index.d.ts +28 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +65 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/projects-service.d.ts +106 -0
  13. package/dist/projects-service.d.ts.map +1 -0
  14. package/dist/projects-service.js +317 -0
  15. package/dist/projects-service.js.map +1 -0
  16. package/dist/session-cache-store.d.ts +48 -0
  17. package/dist/session-cache-store.d.ts.map +1 -0
  18. package/dist/session-cache-store.js +231 -0
  19. package/dist/session-cache-store.js.map +1 -0
  20. package/dist/session-cache.d.ts +266 -0
  21. package/dist/session-cache.d.ts.map +1 -0
  22. package/dist/session-cache.js +1294 -0
  23. package/dist/session-cache.js.map +1 -0
  24. package/dist/session-parser.d.ts +265 -0
  25. package/dist/session-parser.d.ts.map +1 -0
  26. package/dist/session-parser.js +555 -0
  27. package/dist/session-parser.js.map +1 -0
  28. package/dist/session-reader.d.ts +87 -0
  29. package/dist/session-reader.d.ts.map +1 -0
  30. package/dist/session-reader.js +279 -0
  31. package/dist/session-reader.js.map +1 -0
  32. package/dist/tasks-service.d.ts +100 -0
  33. package/dist/tasks-service.d.ts.map +1 -0
  34. package/dist/tasks-service.js +290 -0
  35. package/dist/tasks-service.js.map +1 -0
  36. package/dist/teams-service.d.ts +30 -0
  37. package/dist/teams-service.d.ts.map +1 -0
  38. package/dist/teams-service.js +85 -0
  39. package/dist/teams-service.js.map +1 -0
  40. package/dist/types.d.ts +87 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +7 -0
  43. package/dist/types.js.map +1 -0
  44. package/dist/utils/path-utils.d.ts +80 -0
  45. package/dist/utils/path-utils.d.ts.map +1 -0
  46. package/dist/utils/path-utils.js +355 -0
  47. package/dist/utils/path-utils.js.map +1 -0
  48. package/package.json +42 -0
@@ -0,0 +1,1294 @@
1
+ "use strict";
2
+ /**
3
+ * Session Cache Layer — LMDB-backed
4
+ *
5
+ * Provides efficient caching for Claude Code session JSONL files.
6
+ * Uses LMDB (memory-mapped database) as the storage backend for
7
+ * instant reads with zero warmup on server startup.
8
+ *
9
+ * Key optimizations:
10
+ * 1. LMDB memory-mapped reads — sync ~0ms via OS page cache
11
+ * 2. Incremental parsing — only parse new lines when file grows (append-only)
12
+ * 3. Line index tracking for efficient delta updates
13
+ * 4. Separate sub-database for raw messages (optional, large)
14
+ * 5. Async batched writes via lmdb-js
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.SessionCache = void 0;
51
+ exports.classifyUserPrompt = classifyUserPrompt;
52
+ exports.isRealUserPrompt = isRealUserPrompt;
53
+ exports.createSessionCache = createSessionCache;
54
+ const fs = __importStar(require("fs"));
55
+ const path = __importStar(require("path"));
56
+ const os = __importStar(require("os"));
57
+ const readline = __importStar(require("readline"));
58
+ const session_cache_store_1 = require("./session-cache-store");
59
+ const path_utils_1 = require("./utils/path-utils");
60
+ const cost_calculator_1 = require("./cost-calculator");
61
+ let chokidarWatch = null;
62
+ try {
63
+ const chokidar = require('chokidar');
64
+ chokidarWatch = chokidar.watch || chokidar.default?.watch;
65
+ }
66
+ catch {
67
+ // chokidar not installed — file watching will be disabled
68
+ }
69
+ function classifyUserPrompt(text, isMeta) {
70
+ const trimmed = text.trimStart();
71
+ if (isMeta || trimmed.startsWith('<local-command-caveat>'))
72
+ return 'system_caveat';
73
+ if (trimmed.startsWith('<command-name>'))
74
+ return 'command';
75
+ if (trimmed.startsWith('<local-command-stdout>'))
76
+ return 'command_output';
77
+ if (trimmed.startsWith('<user-prompt-submit-hook>'))
78
+ return 'hook_result';
79
+ return 'user';
80
+ }
81
+ function isRealUserPrompt(prompt) {
82
+ return !prompt.promptType || prompt.promptType === 'user';
83
+ }
84
+ // ─── Constants ──────────────────────────────────────────────────
85
+ const CACHE_VERSION = 9;
86
+ // ─── SessionCache Class ──────────────────────────────────────────────────
87
+ class SessionCache {
88
+ store;
89
+ watcher = null;
90
+ watchedPaths = new Set();
91
+ pendingUpdates = new Map();
92
+ updateDebounceMs = 500;
93
+ isWatching = false;
94
+ onChangeCallbacks = [];
95
+ onFileEventCallbacks = [];
96
+ constructor(cacheDir) {
97
+ const dir = cacheDir || path.join(process.env.CLAUDE_SESSIONS_CACHE_DIR ||
98
+ path.join(os.homedir(), '.claude-sessions'), 'session-cache');
99
+ this.store = new session_cache_store_1.SessionCacheStore(dir);
100
+ }
101
+ onSessionChange(cb) {
102
+ this.onChangeCallbacks.push(cb);
103
+ }
104
+ onFileEvent(cb) {
105
+ this.onFileEventCallbacks.push(cb);
106
+ }
107
+ fireFileEvent(event, filePath) {
108
+ for (const cb of this.onFileEventCallbacks) {
109
+ try {
110
+ cb(event, filePath);
111
+ }
112
+ catch { /* ignore */ }
113
+ }
114
+ }
115
+ fireOnChange(cacheData) {
116
+ if (this.onChangeCallbacks.length === 0 || !cacheData.sessionId)
117
+ return;
118
+ for (const cb of this.onChangeCallbacks) {
119
+ try {
120
+ cb(cacheData.sessionId, cacheData);
121
+ }
122
+ catch { /* ignore */ }
123
+ }
124
+ }
125
+ /**
126
+ * Start watching session directories for file changes.
127
+ * Requires chokidar to be installed (optional dependency).
128
+ * Returns false if chokidar is not available.
129
+ */
130
+ startWatching(projectPaths) {
131
+ if (this.isWatching)
132
+ return true;
133
+ if (!chokidarWatch)
134
+ return false;
135
+ const projectsDir = path.join(os.homedir(), '.claude', 'projects');
136
+ const watchPaths = [];
137
+ if (projectPaths && projectPaths.length > 0) {
138
+ for (const projectPath of projectPaths) {
139
+ const projectKey = (0, path_utils_1.legacyEncodeProjectPath)(projectPath);
140
+ const projectDir = path.join(projectsDir, projectKey);
141
+ if (fs.existsSync(projectDir)) {
142
+ watchPaths.push(projectDir);
143
+ }
144
+ }
145
+ }
146
+ else {
147
+ if (fs.existsSync(projectsDir)) {
148
+ watchPaths.push(projectsDir);
149
+ }
150
+ }
151
+ if (watchPaths.length === 0) {
152
+ return false;
153
+ }
154
+ this.watcher = chokidarWatch(watchPaths, {
155
+ persistent: true,
156
+ ignoreInitial: true,
157
+ depth: 3,
158
+ ignored: [
159
+ /node_modules/,
160
+ /\.git/,
161
+ /\.lmdb/,
162
+ ],
163
+ awaitWriteFinish: {
164
+ stabilityThreshold: 300,
165
+ pollInterval: 100,
166
+ },
167
+ });
168
+ const watcher = this.watcher;
169
+ watcher.on('add', (filePath) => {
170
+ if (filePath.endsWith('.jsonl')) {
171
+ this.fireFileEvent('add', filePath);
172
+ this.scheduleUpdate(filePath);
173
+ }
174
+ });
175
+ watcher.on('change', (filePath) => {
176
+ if (filePath.endsWith('.jsonl')) {
177
+ this.fireFileEvent('change', filePath);
178
+ this.scheduleUpdate(filePath);
179
+ }
180
+ });
181
+ watcher.on('unlink', (filePath) => {
182
+ if (filePath.endsWith('.jsonl')) {
183
+ this.fireFileEvent('unlink', filePath);
184
+ }
185
+ });
186
+ watcher.on('error', (error) => {
187
+ console.error('[SessionCache] Watcher error:', error);
188
+ });
189
+ this.isWatching = true;
190
+ watchPaths.forEach(p => this.watchedPaths.add(p));
191
+ return true;
192
+ }
193
+ stopWatching() {
194
+ if (this.watcher) {
195
+ this.watcher.close();
196
+ this.watcher = null;
197
+ }
198
+ this.isWatching = false;
199
+ this.watchedPaths.clear();
200
+ for (const timeout of this.pendingUpdates.values()) {
201
+ clearTimeout(timeout);
202
+ }
203
+ this.pendingUpdates.clear();
204
+ }
205
+ scheduleUpdate(filePath) {
206
+ const existing = this.pendingUpdates.get(filePath);
207
+ if (existing) {
208
+ clearTimeout(existing);
209
+ }
210
+ const timeout = setTimeout(() => {
211
+ this.pendingUpdates.delete(filePath);
212
+ this.warmCache(filePath).catch(() => { });
213
+ }, this.updateDebounceMs);
214
+ this.pendingUpdates.set(filePath, timeout);
215
+ }
216
+ async warmCache(filePath) {
217
+ if (!fs.existsSync(filePath))
218
+ return;
219
+ try {
220
+ await this.getSessionData(filePath);
221
+ }
222
+ catch {
223
+ // Silently ignore errors during background warming
224
+ }
225
+ }
226
+ async warmProjectCache(projectPath) {
227
+ const projectKey = (0, path_utils_1.legacyEncodeProjectPath)(projectPath);
228
+ const projectDir = path.join(os.homedir(), '.claude', 'projects', projectKey);
229
+ if (!fs.existsSync(projectDir)) {
230
+ return { warmed: 0, errors: 0 };
231
+ }
232
+ let warmed = 0;
233
+ let errors = 0;
234
+ const files = [];
235
+ const mainFiles = fs.readdirSync(projectDir)
236
+ .filter(f => f.endsWith('.jsonl'))
237
+ .map(f => path.join(projectDir, f));
238
+ files.push(...mainFiles);
239
+ const entries = fs.readdirSync(projectDir, { withFileTypes: true });
240
+ for (const entry of entries) {
241
+ if (entry.isDirectory()) {
242
+ const subagentsDir = path.join(projectDir, entry.name, 'subagents');
243
+ if (fs.existsSync(subagentsDir)) {
244
+ const agentFiles = fs.readdirSync(subagentsDir)
245
+ .filter(f => f.endsWith('.jsonl'))
246
+ .map(f => path.join(subagentsDir, f));
247
+ files.push(...agentFiles);
248
+ }
249
+ }
250
+ }
251
+ const CONCURRENCY = 10;
252
+ for (let i = 0; i < files.length; i += CONCURRENCY) {
253
+ const batch = files.slice(i, i + CONCURRENCY);
254
+ const results = await Promise.allSettled(batch.map(f => this.warmCache(f)));
255
+ for (const result of results) {
256
+ if (result.status === 'fulfilled') {
257
+ warmed++;
258
+ }
259
+ else {
260
+ errors++;
261
+ }
262
+ }
263
+ }
264
+ return { warmed, errors };
265
+ }
266
+ getCacheStats() {
267
+ return {
268
+ memoryCacheSize: this.store.sessionCount,
269
+ rawMemoryCacheSize: this.store.rawCount,
270
+ isWatching: this.isWatching,
271
+ watchedPaths: Array.from(this.watchedPaths),
272
+ pendingUpdates: this.pendingUpdates.size,
273
+ lmdb: {
274
+ sessionCount: this.store.sessionCount,
275
+ rawCount: this.store.rawCount,
276
+ },
277
+ };
278
+ }
279
+ isCacheValid(cache, stats) {
280
+ if (!cache)
281
+ return 'invalid';
282
+ if (cache.version !== CACHE_VERSION)
283
+ return 'invalid';
284
+ const fileMtime = stats.mtime.getTime();
285
+ const fileSize = stats.size;
286
+ if (fileSize < cache.fileSize)
287
+ return 'invalid';
288
+ if (fileMtime < cache.fileMtime)
289
+ return 'invalid';
290
+ if (fileSize === cache.fileSize && fileMtime === cache.fileMtime)
291
+ return 'valid';
292
+ if (fileSize > cache.fileSize)
293
+ return 'append';
294
+ return 'invalid';
295
+ }
296
+ async parseNewLines(sessionPath, startLineIndex, startTurnIndex, existingCache) {
297
+ const newMessages = [];
298
+ const cachedLineIndexes = new Set();
299
+ for (const p of existingCache.userPrompts)
300
+ cachedLineIndexes.add(p.lineIndex);
301
+ for (const t of existingCache.toolUses)
302
+ cachedLineIndexes.add(t.lineIndex);
303
+ for (const r of existingCache.responses)
304
+ cachedLineIndexes.add(r.lineIndex);
305
+ for (const tb of existingCache.thinkingBlocks)
306
+ cachedLineIndexes.add(tb.lineIndex);
307
+ let lineIndex;
308
+ let turnIndex = startTurnIndex;
309
+ let byteOffset;
310
+ if (existingCache.lastByteOffset && existingCache.lastByteOffset > 0) {
311
+ const fileStream = fs.createReadStream(sessionPath, { start: existingCache.lastByteOffset });
312
+ const rl = readline.createInterface({
313
+ input: fileStream,
314
+ crlfDelay: Infinity,
315
+ });
316
+ lineIndex = startLineIndex + 1;
317
+ byteOffset = existingCache.lastByteOffset;
318
+ for await (const line of rl) {
319
+ byteOffset += Buffer.byteLength(line, 'utf8') + 1;
320
+ if (line.trim() && !cachedLineIndexes.has(lineIndex)) {
321
+ try {
322
+ const msg = JSON.parse(line);
323
+ newMessages.push({ ...msg, lineIndex });
324
+ if (msg.type === 'assistant') {
325
+ turnIndex++;
326
+ }
327
+ }
328
+ catch {
329
+ // Skip invalid JSON
330
+ }
331
+ }
332
+ lineIndex++;
333
+ }
334
+ }
335
+ else {
336
+ const fileStream = fs.createReadStream(sessionPath);
337
+ const rl = readline.createInterface({
338
+ input: fileStream,
339
+ crlfDelay: Infinity,
340
+ });
341
+ lineIndex = 0;
342
+ byteOffset = 0;
343
+ for await (const line of rl) {
344
+ byteOffset += Buffer.byteLength(line, 'utf8') + 1;
345
+ if (lineIndex > startLineIndex && line.trim() && !cachedLineIndexes.has(lineIndex)) {
346
+ try {
347
+ const msg = JSON.parse(line);
348
+ newMessages.push({ ...msg, lineIndex });
349
+ if (msg.type === 'assistant') {
350
+ turnIndex++;
351
+ }
352
+ }
353
+ catch {
354
+ // Skip invalid JSON
355
+ }
356
+ }
357
+ lineIndex++;
358
+ }
359
+ }
360
+ return {
361
+ newMessages,
362
+ lastLineIndex: lineIndex - 1,
363
+ lastTurnIndex: turnIndex,
364
+ lastByteOffset: byteOffset,
365
+ };
366
+ }
367
+ mergeNewMessages(cache, newMessages, stats) {
368
+ const updated = JSON.parse(JSON.stringify(cache));
369
+ updated.fileSize = stats.size;
370
+ updated.fileMtime = stats.mtime.getTime();
371
+ if (updated.cumulativeCostUsd === undefined)
372
+ updated.cumulativeCostUsd = 0;
373
+ if (!updated.modelUsage)
374
+ updated.modelUsage = {};
375
+ let turnIndex = cache.lastTurnIndex;
376
+ let lastTimestamp = cache.lastTimestamp;
377
+ for (const msg of newMessages) {
378
+ if (msg.timestamp) {
379
+ lastTimestamp = msg.timestamp;
380
+ }
381
+ if (!updated.sessionId && msg.sessionId) {
382
+ const fileBasedId = path.basename(updated.filePath, '.jsonl');
383
+ if (msg.sessionId !== fileBasedId && !fileBasedId.startsWith('agent-')) {
384
+ updated.forkedFromSessionId = msg.sessionId;
385
+ updated.forkPointUuid = msg.parentUuid || undefined;
386
+ }
387
+ else if (msg.forkedFrom?.sessionId) {
388
+ updated.forkedFromSessionId = msg.forkedFrom.sessionId;
389
+ updated.forkPointUuid = msg.forkedFrom.messageUuid || undefined;
390
+ }
391
+ updated.sessionId = msg.sessionId;
392
+ }
393
+ if (!updated.cwd && msg.cwd) {
394
+ updated.cwd = msg.cwd;
395
+ }
396
+ if (!updated.claudeCodeVersion && msg.version) {
397
+ updated.claudeCodeVersion = msg.version;
398
+ }
399
+ if (!updated.teamName && msg.teamName) {
400
+ updated.teamName = msg.teamName;
401
+ }
402
+ if (msg.type === 'system' && msg.subtype === 'init') {
403
+ updated.sessionId = msg.session_id || updated.sessionId;
404
+ updated.cwd = msg.cwd || updated.cwd;
405
+ updated.model = msg.model || updated.model;
406
+ updated.claudeCodeVersion = msg.claude_code_version || updated.claudeCodeVersion;
407
+ updated.permissionMode = msg.permissionMode || updated.permissionMode;
408
+ updated.tools = msg.tools || updated.tools;
409
+ updated.mcpServers = msg.mcp_servers || updated.mcpServers;
410
+ }
411
+ if (msg.type === 'user') {
412
+ const content = msg.message?.content;
413
+ let text = '';
414
+ let images = 0;
415
+ if (Array.isArray(content)) {
416
+ for (const block of content) {
417
+ if (block.type === 'text')
418
+ text += block.text;
419
+ if (block.type === 'image')
420
+ images++;
421
+ }
422
+ }
423
+ else if (typeof content === 'string') {
424
+ text = content;
425
+ }
426
+ if (text || images > 0) {
427
+ const promptType = classifyUserPrompt(text, msg.isMeta);
428
+ updated.userPrompts.push({
429
+ turnIndex,
430
+ lineIndex: msg.lineIndex,
431
+ text,
432
+ images: images > 0 ? images : undefined,
433
+ timestamp: msg.timestamp,
434
+ promptType: promptType !== 'user' ? promptType : undefined,
435
+ });
436
+ }
437
+ if (Array.isArray(content)) {
438
+ for (const block of content) {
439
+ if (block.type === 'tool_result' && block.tool_use_id) {
440
+ let resultText = '';
441
+ if (typeof block.content === 'string') {
442
+ resultText = block.content;
443
+ }
444
+ else if (Array.isArray(block.content)) {
445
+ resultText = block.content
446
+ .filter((b) => b.type === 'text' && b.text)
447
+ .map((b) => b.text)
448
+ .join('\n');
449
+ }
450
+ const taskCreateMatch = resultText.match(/Task #(\d+) created successfully/);
451
+ if (taskCreateMatch) {
452
+ const assignedId = taskCreateMatch[1];
453
+ const tempId = `temp-${block.tool_use_id}`;
454
+ const tempIdx = updated.tasks.findIndex(t => t.id === tempId);
455
+ if (tempIdx >= 0) {
456
+ updated.tasks[tempIdx].id = assignedId;
457
+ }
458
+ }
459
+ const subagent = updated.subagents.find(s => s.toolUseId === block.tool_use_id);
460
+ if (subagent && subagent.status !== 'completed' && resultText) {
461
+ subagent.status = block.is_error ? 'error' : 'completed';
462
+ subagent.result = resultText;
463
+ subagent.completedAt = msg.timestamp || new Date().toISOString();
464
+ }
465
+ }
466
+ }
467
+ }
468
+ }
469
+ if (msg.type === 'assistant') {
470
+ turnIndex++;
471
+ if (msg.message?.model) {
472
+ updated.model = msg.message.model;
473
+ }
474
+ const isApiError = !!(msg.isApiErrorMessage || msg.error);
475
+ const content = msg.message?.content;
476
+ if (Array.isArray(content)) {
477
+ for (const block of content) {
478
+ if (block.type === 'text' && block.text) {
479
+ if (isApiError) {
480
+ let requestId;
481
+ const reqMatch = block.text.match(/"request_id"\s*:\s*"([^"]+)"/);
482
+ if (reqMatch)
483
+ requestId = reqMatch[1];
484
+ updated.responses.push({
485
+ turnIndex,
486
+ lineIndex: msg.lineIndex,
487
+ text: block.text,
488
+ isApiError: true,
489
+ requestId,
490
+ });
491
+ }
492
+ else {
493
+ updated.responses.push({
494
+ turnIndex,
495
+ lineIndex: msg.lineIndex,
496
+ text: block.text,
497
+ });
498
+ }
499
+ }
500
+ if (block.type === 'tool_use' && block.id && block.name) {
501
+ updated.toolUses.push({
502
+ id: block.id,
503
+ name: block.name,
504
+ input: block.input,
505
+ turnIndex,
506
+ lineIndex: msg.lineIndex,
507
+ });
508
+ if (!updated.tools.includes(block.name)) {
509
+ updated.tools.push(block.name);
510
+ }
511
+ if (block.name === 'TaskCreate' && block.input) {
512
+ const input = block.input;
513
+ const tempId = `temp-${block.id}`;
514
+ updated.tasks.push({
515
+ id: tempId,
516
+ subject: input.subject || '',
517
+ description: input.description,
518
+ activeForm: input.activeForm,
519
+ status: 'pending',
520
+ blocks: [],
521
+ blockedBy: input.blockedBy || [],
522
+ owner: input.owner,
523
+ metadata: input.metadata,
524
+ turnIndex,
525
+ lineIndex: msg.lineIndex,
526
+ });
527
+ }
528
+ if (block.name === 'TaskUpdate' && block.input) {
529
+ const input = block.input;
530
+ const taskId = input.taskId;
531
+ if (taskId) {
532
+ const existingIdx = updated.tasks.findIndex(t => t.id === taskId);
533
+ if (existingIdx >= 0) {
534
+ const existing = updated.tasks[existingIdx];
535
+ if (input.status)
536
+ existing.status = input.status;
537
+ if (input.subject)
538
+ existing.subject = input.subject;
539
+ if (input.description)
540
+ existing.description = input.description;
541
+ if (input.activeForm)
542
+ existing.activeForm = input.activeForm;
543
+ if (input.owner !== undefined)
544
+ existing.owner = input.owner;
545
+ if (input.addBlocks)
546
+ existing.blocks.push(...input.addBlocks);
547
+ if (input.addBlockedBy)
548
+ existing.blockedBy.push(...input.addBlockedBy);
549
+ if (input.metadata) {
550
+ existing.metadata = existing.metadata || {};
551
+ for (const [k, v] of Object.entries(input.metadata)) {
552
+ if (v === null) {
553
+ delete existing.metadata[k];
554
+ }
555
+ else {
556
+ existing.metadata[k] = v;
557
+ }
558
+ }
559
+ }
560
+ existing.turnIndex = turnIndex;
561
+ existing.lineIndex = msg.lineIndex;
562
+ }
563
+ else if (input.status !== 'deleted') {
564
+ updated.tasks.push({
565
+ id: taskId,
566
+ subject: input.subject || `Task #${taskId}`,
567
+ description: input.description,
568
+ activeForm: input.activeForm,
569
+ status: input.status || 'pending',
570
+ blocks: input.addBlocks || [],
571
+ blockedBy: input.addBlockedBy || [],
572
+ owner: input.owner,
573
+ metadata: input.metadata,
574
+ turnIndex,
575
+ lineIndex: msg.lineIndex,
576
+ });
577
+ }
578
+ }
579
+ }
580
+ if (block.name === 'EnterPlanMode') {
581
+ updated.plans.push({
582
+ toolUseId: block.id,
583
+ status: 'entering',
584
+ turnIndex,
585
+ lineIndex: msg.lineIndex,
586
+ });
587
+ }
588
+ if (block.name === 'ExitPlanMode' && block.input) {
589
+ const input = block.input;
590
+ const planContent = input.plan || '';
591
+ const titleMatch = planContent.match(/^#\s+(.+)/m);
592
+ const planTitle = titleMatch ? titleMatch[1].trim() : undefined;
593
+ const planSummary = planContent.length > 300
594
+ ? planContent.slice(0, 300) + '...'
595
+ : planContent || undefined;
596
+ const pendingFile = updated._pendingPlanFile;
597
+ updated.plans.push({
598
+ toolUseId: block.id,
599
+ status: 'approved',
600
+ planFile: pendingFile,
601
+ planTitle,
602
+ planSummary,
603
+ allowedPrompts: input.allowedPrompts,
604
+ turnIndex,
605
+ lineIndex: msg.lineIndex,
606
+ });
607
+ delete updated._pendingPlanFile;
608
+ }
609
+ if ((block.name === 'Write' || block.name === 'Edit') && block.input) {
610
+ const filePath = block.input.file_path || '';
611
+ const planMatch = filePath.match(/\.claude\/plans\/([^\s/]+\.md)$/);
612
+ if (planMatch) {
613
+ const planFileName = planMatch[1];
614
+ let attached = false;
615
+ for (let pi = updated.plans.length - 1; pi >= 0; pi--) {
616
+ if (updated.plans[pi].status === 'approved' && !updated.plans[pi].planFile) {
617
+ updated.plans[pi].planFile = planFileName;
618
+ attached = true;
619
+ break;
620
+ }
621
+ }
622
+ if (!attached) {
623
+ updated._pendingPlanFile = planFileName;
624
+ }
625
+ }
626
+ }
627
+ if (block.name === 'Task' && block.input) {
628
+ const input = block.input;
629
+ updated.subagents.push({
630
+ agentId: '',
631
+ toolUseId: block.id,
632
+ type: input.subagent_type || input.type || 'general-purpose',
633
+ prompt: input.prompt || '',
634
+ description: input.description,
635
+ model: input.model,
636
+ turnIndex,
637
+ lineIndex: msg.lineIndex,
638
+ userPromptIndex: Math.max(0, updated.userPrompts.length - 1),
639
+ startedAt: msg.timestamp,
640
+ status: 'pending',
641
+ runInBackground: input.run_in_background,
642
+ });
643
+ }
644
+ if (block.name === 'Teammate' && block.input) {
645
+ const input = block.input;
646
+ const op = (input.operation || 'spawnTeam');
647
+ updated.teamOperations.push({
648
+ operation: op,
649
+ teamName: input.team_name,
650
+ description: input.description,
651
+ turnIndex,
652
+ lineIndex: msg.lineIndex,
653
+ });
654
+ if (op === 'spawnTeam' && input.team_name) {
655
+ if (!updated.allTeams.includes(input.team_name)) {
656
+ updated.allTeams.push(input.team_name);
657
+ }
658
+ }
659
+ }
660
+ if (block.name === 'SendMessage' && block.input) {
661
+ const input = block.input;
662
+ updated.teamMessages.push({
663
+ messageType: (input.type || 'message'),
664
+ recipient: input.recipient,
665
+ content: input.content,
666
+ summary: input.summary,
667
+ requestId: input.request_id,
668
+ approve: input.approve,
669
+ turnIndex,
670
+ lineIndex: msg.lineIndex,
671
+ });
672
+ }
673
+ }
674
+ if (block.type === 'thinking' && block.thinking) {
675
+ updated.thinkingBlocks.push({
676
+ turnIndex,
677
+ lineIndex: msg.lineIndex,
678
+ thinking: block.thinking,
679
+ });
680
+ }
681
+ }
682
+ }
683
+ if (msg.message?.usage) {
684
+ const u = msg.message.usage;
685
+ const inputToks = u.input_tokens || 0;
686
+ const outputToks = u.output_tokens || 0;
687
+ const cacheCreateToks = u.cache_creation_input_tokens || 0;
688
+ const cacheReadToks = u.cache_read_input_tokens || 0;
689
+ updated.usage.inputTokens += inputToks;
690
+ updated.usage.outputTokens += outputToks;
691
+ updated.usage.cacheCreationInputTokens += cacheCreateToks;
692
+ updated.usage.cacheReadInputTokens += cacheReadToks;
693
+ const modelName = msg.message?.model;
694
+ if (modelName && modelName !== '<synthetic>') {
695
+ if (!updated.modelUsage) {
696
+ updated.modelUsage = {};
697
+ }
698
+ if (!updated.modelUsage[modelName]) {
699
+ updated.modelUsage[modelName] = {
700
+ inputTokens: 0,
701
+ outputTokens: 0,
702
+ cacheCreationInputTokens: 0,
703
+ cacheReadInputTokens: 0,
704
+ costUsd: 0,
705
+ messageCount: 0,
706
+ };
707
+ }
708
+ const mu = updated.modelUsage[modelName];
709
+ mu.inputTokens += inputToks;
710
+ mu.outputTokens += outputToks;
711
+ mu.cacheCreationInputTokens += cacheCreateToks;
712
+ mu.cacheReadInputTokens += cacheReadToks;
713
+ mu.messageCount++;
714
+ }
715
+ }
716
+ if (typeof msg.costUSD === 'number' && msg.costUSD > 0) {
717
+ updated.cumulativeCostUsd += msg.costUSD;
718
+ const modelName = msg.message?.model;
719
+ if (modelName && modelName !== '<synthetic>' && updated.modelUsage?.[modelName]) {
720
+ updated.modelUsage[modelName].costUsd += msg.costUSD;
721
+ }
722
+ }
723
+ }
724
+ if (msg.type === 'result') {
725
+ updated.result = msg.result;
726
+ if (msg.subtype === 'error' || msg.error) {
727
+ updated.errors = updated.errors || [];
728
+ updated.errors.push(msg.error || msg.result || 'Unknown error');
729
+ }
730
+ updated.success = msg.subtype === 'success';
731
+ if (msg.duration_ms)
732
+ updated.durationMs = msg.duration_ms;
733
+ if (msg.duration_api_ms)
734
+ updated.durationApiMs = msg.duration_api_ms;
735
+ if (msg.total_cost_usd)
736
+ updated.totalCostUsd = msg.total_cost_usd;
737
+ if (msg.usage) {
738
+ updated.usage = {
739
+ inputTokens: msg.usage.input_tokens || 0,
740
+ outputTokens: msg.usage.output_tokens || 0,
741
+ cacheCreationInputTokens: msg.usage.cache_creation_input_tokens || 0,
742
+ cacheReadInputTokens: msg.usage.cache_read_input_tokens || 0,
743
+ };
744
+ }
745
+ if (msg.modelUsage && typeof msg.modelUsage === 'object') {
746
+ if (!updated.modelUsage) {
747
+ updated.modelUsage = {};
748
+ }
749
+ for (const [modelName, mu] of Object.entries(msg.modelUsage)) {
750
+ if (modelName === '<synthetic>' || !mu)
751
+ continue;
752
+ const existingMessageCount = updated.modelUsage[modelName]?.messageCount || 0;
753
+ updated.modelUsage[modelName] = {
754
+ inputTokens: mu.input_tokens || mu.inputTokens || 0,
755
+ outputTokens: mu.output_tokens || mu.outputTokens || 0,
756
+ cacheCreationInputTokens: mu.cache_creation_input_tokens || mu.cacheCreationInputTokens || 0,
757
+ cacheReadInputTokens: mu.cache_read_input_tokens || mu.cacheReadInputTokens || 0,
758
+ costUsd: mu.costUSD || mu.costUsd || 0,
759
+ messageCount: existingMessageCount,
760
+ };
761
+ }
762
+ }
763
+ }
764
+ if (msg.type === 'progress' && msg.data?.type === 'agent_progress' && msg.data.agentId) {
765
+ const agentId = msg.data.agentId;
766
+ const parentToolUseId = msg.parentToolUseID;
767
+ const parentUuid = msg.parentUuid;
768
+ if (parentToolUseId) {
769
+ const subagent = updated.subagents.find(s => s.toolUseId === parentToolUseId);
770
+ if (subagent && !subagent.agentId) {
771
+ subagent.agentId = agentId;
772
+ subagent.status = 'running';
773
+ if (parentUuid) {
774
+ subagent.parentUuid = parentUuid;
775
+ }
776
+ }
777
+ }
778
+ }
779
+ }
780
+ updated.lastLineIndex = newMessages.length > 0
781
+ ? newMessages[newMessages.length - 1].lineIndex
782
+ : cache.lastLineIndex;
783
+ updated.lastTurnIndex = turnIndex;
784
+ updated.lastTimestamp = lastTimestamp;
785
+ updated.numTurns = turnIndex;
786
+ return updated;
787
+ }
788
+ async createInitialCache(sessionPath, stats) {
789
+ const messages = [];
790
+ const fileStream = fs.createReadStream(sessionPath);
791
+ const rl = readline.createInterface({
792
+ input: fileStream,
793
+ crlfDelay: Infinity,
794
+ });
795
+ let lineIndex = 0;
796
+ for await (const line of rl) {
797
+ if (line.trim()) {
798
+ try {
799
+ const msg = JSON.parse(line);
800
+ messages.push({ ...msg, lineIndex });
801
+ }
802
+ catch {
803
+ // Skip invalid JSON
804
+ }
805
+ }
806
+ lineIndex++;
807
+ }
808
+ const cache = {
809
+ version: CACHE_VERSION,
810
+ sessionId: '',
811
+ filePath: sessionPath,
812
+ fileSize: stats.size,
813
+ fileMtime: stats.mtime.getTime(),
814
+ lastLineIndex: -1,
815
+ lastTurnIndex: 0,
816
+ lastByteOffset: stats.size,
817
+ createdAt: Date.now(),
818
+ cwd: '',
819
+ model: '',
820
+ claudeCodeVersion: '',
821
+ permissionMode: '',
822
+ tools: [],
823
+ mcpServers: [],
824
+ userPrompts: [],
825
+ toolUses: [],
826
+ responses: [],
827
+ thinkingBlocks: [],
828
+ tasks: [],
829
+ todos: [],
830
+ subagents: [],
831
+ subagentProgress: [],
832
+ plans: [],
833
+ allTeams: [],
834
+ teamOperations: [],
835
+ teamMessages: [],
836
+ numTurns: 0,
837
+ durationMs: 0,
838
+ durationApiMs: 0,
839
+ totalCostUsd: 0,
840
+ cumulativeCostUsd: 0,
841
+ usage: {
842
+ inputTokens: 0,
843
+ outputTokens: 0,
844
+ cacheCreationInputTokens: 0,
845
+ cacheReadInputTokens: 0,
846
+ },
847
+ modelUsage: {},
848
+ success: false,
849
+ };
850
+ return this.mergeNewMessages(cache, messages, stats);
851
+ }
852
+ getSessionDataFromMemory(sessionPath) {
853
+ return this.store.getSessionData(sessionPath) || null;
854
+ }
855
+ async getSessionData(sessionPath) {
856
+ if (!fs.existsSync(sessionPath)) {
857
+ return null;
858
+ }
859
+ const stats = fs.statSync(sessionPath);
860
+ let cache = this.store.getSessionData(sessionPath);
861
+ const validity = this.isCacheValid(cache || null, stats);
862
+ if (validity === 'valid' && cache) {
863
+ return cache;
864
+ }
865
+ if (validity === 'append' && cache) {
866
+ const { newMessages, lastLineIndex, lastTurnIndex, lastByteOffset } = await this.parseNewLines(sessionPath, cache.lastLineIndex, cache.lastTurnIndex, cache);
867
+ if (newMessages.length > 0) {
868
+ cache = this.mergeNewMessages(cache, newMessages, stats);
869
+ cache.lastLineIndex = lastLineIndex;
870
+ cache.lastTurnIndex = lastTurnIndex;
871
+ cache.lastByteOffset = lastByteOffset;
872
+ await this.store.putSessionData(sessionPath, cache);
873
+ this.fireOnChange(cache);
874
+ }
875
+ else {
876
+ cache.lastByteOffset = lastByteOffset;
877
+ cache.fileSize = stats.size;
878
+ cache.fileMtime = stats.mtime.getTime();
879
+ await this.store.putSessionData(sessionPath, cache);
880
+ }
881
+ return cache;
882
+ }
883
+ cache = await this.createInitialCache(sessionPath, stats);
884
+ await this.store.putSessionData(sessionPath, cache);
885
+ this.fireOnChange(cache);
886
+ return cache;
887
+ }
888
+ async getRawMessages(sessionPath) {
889
+ if (!fs.existsSync(sessionPath)) {
890
+ return null;
891
+ }
892
+ const stats = fs.statSync(sessionPath);
893
+ let cache = this.store.getRawMessages(sessionPath);
894
+ const validity = this.isRawCacheValid(cache || null, stats);
895
+ if (validity === 'valid' && cache) {
896
+ return cache.messages;
897
+ }
898
+ if (validity === 'append' && cache) {
899
+ const { messages: newMessages, lastByteOffset } = await this.parseRawNewLines(sessionPath, cache.lastLineIndex, cache.lastByteOffset);
900
+ if (newMessages.length > 0) {
901
+ cache.messages.push(...newMessages);
902
+ cache.lastLineIndex = newMessages[newMessages.length - 1].lineIndex;
903
+ cache.lastByteOffset = lastByteOffset;
904
+ cache.fileSize = stats.size;
905
+ cache.fileMtime = stats.mtime.getTime();
906
+ await this.store.putRawMessages(sessionPath, cache);
907
+ }
908
+ else {
909
+ cache.lastByteOffset = lastByteOffset;
910
+ cache.fileSize = stats.size;
911
+ cache.fileMtime = stats.mtime.getTime();
912
+ await this.store.putRawMessages(sessionPath, cache);
913
+ }
914
+ return cache.messages;
915
+ }
916
+ const messages = [];
917
+ const fileStream = fs.createReadStream(sessionPath);
918
+ const rl = readline.createInterface({
919
+ input: fileStream,
920
+ crlfDelay: Infinity,
921
+ });
922
+ let lineIndex = 0;
923
+ for await (const line of rl) {
924
+ if (line.trim()) {
925
+ try {
926
+ const msg = JSON.parse(line);
927
+ messages.push({ ...msg, lineIndex });
928
+ }
929
+ catch {
930
+ // Skip invalid JSON
931
+ }
932
+ }
933
+ lineIndex++;
934
+ }
935
+ const rawCache = {
936
+ version: CACHE_VERSION,
937
+ sessionId: path.basename(sessionPath, '.jsonl'),
938
+ fileSize: stats.size,
939
+ fileMtime: stats.mtime.getTime(),
940
+ lastLineIndex: lineIndex - 1,
941
+ lastByteOffset: stats.size,
942
+ messages,
943
+ };
944
+ await this.store.putRawMessages(sessionPath, rawCache);
945
+ return messages;
946
+ }
947
+ isRawCacheValid(cache, stats) {
948
+ if (!cache)
949
+ return 'invalid';
950
+ if (cache.version !== CACHE_VERSION)
951
+ return 'invalid';
952
+ const fileMtime = stats.mtime.getTime();
953
+ const fileSize = stats.size;
954
+ if (fileSize < cache.fileSize)
955
+ return 'invalid';
956
+ if (fileMtime < cache.fileMtime)
957
+ return 'invalid';
958
+ if (fileSize === cache.fileSize && fileMtime === cache.fileMtime)
959
+ return 'valid';
960
+ if (fileSize > cache.fileSize)
961
+ return 'append';
962
+ return 'invalid';
963
+ }
964
+ async parseRawNewLines(sessionPath, startLineIndex, lastByteOffset) {
965
+ const newMessages = [];
966
+ let lineIndex;
967
+ let byteOffset;
968
+ if (lastByteOffset && lastByteOffset > 0) {
969
+ const fileStream = fs.createReadStream(sessionPath, { start: lastByteOffset });
970
+ const rl = readline.createInterface({
971
+ input: fileStream,
972
+ crlfDelay: Infinity,
973
+ });
974
+ lineIndex = startLineIndex + 1;
975
+ byteOffset = lastByteOffset;
976
+ for await (const line of rl) {
977
+ byteOffset += Buffer.byteLength(line, 'utf8') + 1;
978
+ if (line.trim()) {
979
+ try {
980
+ const msg = JSON.parse(line);
981
+ newMessages.push({ ...msg, lineIndex });
982
+ }
983
+ catch {
984
+ // Skip invalid JSON
985
+ }
986
+ }
987
+ lineIndex++;
988
+ }
989
+ }
990
+ else {
991
+ const fileStream = fs.createReadStream(sessionPath);
992
+ const rl = readline.createInterface({
993
+ input: fileStream,
994
+ crlfDelay: Infinity,
995
+ });
996
+ lineIndex = 0;
997
+ byteOffset = 0;
998
+ for await (const line of rl) {
999
+ byteOffset += Buffer.byteLength(line, 'utf8') + 1;
1000
+ if (lineIndex > startLineIndex && line.trim()) {
1001
+ try {
1002
+ const msg = JSON.parse(line);
1003
+ newMessages.push({ ...msg, lineIndex });
1004
+ }
1005
+ catch {
1006
+ // Skip invalid JSON
1007
+ }
1008
+ }
1009
+ lineIndex++;
1010
+ }
1011
+ }
1012
+ return { messages: newMessages, lastByteOffset: byteOffset };
1013
+ }
1014
+ getSessionDataSync(sessionPath) {
1015
+ if (!fs.existsSync(sessionPath)) {
1016
+ return null;
1017
+ }
1018
+ const stats = fs.statSync(sessionPath);
1019
+ let cache = this.store.getSessionData(sessionPath);
1020
+ const validity = this.isCacheValid(cache || null, stats);
1021
+ if (validity === 'valid' && cache) {
1022
+ return cache;
1023
+ }
1024
+ if (validity === 'append' && cache) {
1025
+ const content = fs.readFileSync(sessionPath, 'utf-8');
1026
+ const lines = content.split('\n');
1027
+ const newMessages = [];
1028
+ let turnIdx = cache.lastTurnIndex;
1029
+ for (let li = cache.lastLineIndex + 1; li < lines.length; li++) {
1030
+ const line = lines[li];
1031
+ if (line.trim()) {
1032
+ try {
1033
+ const msg = JSON.parse(line);
1034
+ newMessages.push({ ...msg, lineIndex: li });
1035
+ if (msg.type === 'assistant') {
1036
+ turnIdx++;
1037
+ }
1038
+ }
1039
+ catch {
1040
+ // Skip invalid JSON
1041
+ }
1042
+ }
1043
+ }
1044
+ if (newMessages.length > 0) {
1045
+ cache = this.mergeNewMessages(cache, newMessages, stats);
1046
+ cache.lastLineIndex = lines.length - 1;
1047
+ cache.lastTurnIndex = turnIdx;
1048
+ this.store.putSessionData(sessionPath, cache).catch(() => { });
1049
+ }
1050
+ return cache;
1051
+ }
1052
+ const content = fs.readFileSync(sessionPath, 'utf-8');
1053
+ const lines = content.split('\n');
1054
+ const messages = [];
1055
+ for (let li = 0; li < lines.length; li++) {
1056
+ const line = lines[li];
1057
+ if (line.trim()) {
1058
+ try {
1059
+ const msg = JSON.parse(line);
1060
+ messages.push({ ...msg, lineIndex: li });
1061
+ }
1062
+ catch {
1063
+ // Skip invalid JSON
1064
+ }
1065
+ }
1066
+ }
1067
+ cache = {
1068
+ version: CACHE_VERSION,
1069
+ sessionId: '',
1070
+ filePath: sessionPath,
1071
+ fileSize: stats.size,
1072
+ fileMtime: stats.mtime.getTime(),
1073
+ lastLineIndex: -1,
1074
+ lastTurnIndex: 0,
1075
+ createdAt: Date.now(),
1076
+ cwd: '',
1077
+ model: '',
1078
+ claudeCodeVersion: '',
1079
+ permissionMode: '',
1080
+ tools: [],
1081
+ mcpServers: [],
1082
+ userPrompts: [],
1083
+ toolUses: [],
1084
+ responses: [],
1085
+ thinkingBlocks: [],
1086
+ tasks: [],
1087
+ todos: [],
1088
+ subagents: [],
1089
+ subagentProgress: [],
1090
+ plans: [],
1091
+ allTeams: [],
1092
+ teamOperations: [],
1093
+ teamMessages: [],
1094
+ numTurns: 0,
1095
+ durationMs: 0,
1096
+ durationApiMs: 0,
1097
+ totalCostUsd: 0,
1098
+ cumulativeCostUsd: 0,
1099
+ usage: {
1100
+ inputTokens: 0,
1101
+ outputTokens: 0,
1102
+ cacheCreationInputTokens: 0,
1103
+ cacheReadInputTokens: 0,
1104
+ },
1105
+ modelUsage: {},
1106
+ success: false,
1107
+ };
1108
+ cache = this.mergeNewMessages(cache, messages, stats);
1109
+ this.store.putSessionData(sessionPath, cache).catch(() => { });
1110
+ return cache;
1111
+ }
1112
+ clearCache(sessionPath) {
1113
+ this.store.clear(sessionPath).catch(() => { });
1114
+ }
1115
+ clearAllCaches() {
1116
+ this.store.clear().catch(() => { });
1117
+ }
1118
+ async compactCache() {
1119
+ const wasWatching = this.isWatching;
1120
+ const watchedPaths = [...this.watchedPaths];
1121
+ if (wasWatching) {
1122
+ this.stopWatching();
1123
+ }
1124
+ const result = await this.store.compact();
1125
+ if (wasWatching) {
1126
+ this.startWatching(watchedPaths.length > 0 ? watchedPaths : undefined);
1127
+ }
1128
+ return result;
1129
+ }
1130
+ getAllSessionsFromCache() {
1131
+ const results = [];
1132
+ for (const { key: filePath, value: cacheData } of this.store.allSessions()) {
1133
+ const normalizedFilePath = filePath.replace(/\\/g, '/');
1134
+ if (normalizedFilePath.includes('/subagents/'))
1135
+ continue;
1136
+ const sessionId = path.basename(filePath, '.jsonl');
1137
+ if (sessionId.startsWith('agent-'))
1138
+ continue;
1139
+ results.push({ sessionId, filePath, cacheData });
1140
+ }
1141
+ return results;
1142
+ }
1143
+ getProjectSessionsFromCache(projectPath) {
1144
+ const projectKey = (0, path_utils_1.legacyEncodeProjectPath)(projectPath);
1145
+ const results = [];
1146
+ for (const { key: filePath, value: cacheData } of this.store.allSessions()) {
1147
+ const normalizedFilePath = filePath.replace(/\\/g, '/');
1148
+ if (normalizedFilePath.includes(`/projects/${projectKey}/`) && !normalizedFilePath.includes('/subagents/')) {
1149
+ const sessionId = path.basename(filePath, '.jsonl');
1150
+ if (!sessionId.startsWith('agent-')) {
1151
+ results.push({ sessionId, filePath, cacheData });
1152
+ }
1153
+ }
1154
+ }
1155
+ return results;
1156
+ }
1157
+ getPerProjectCosts() {
1158
+ let calc = null;
1159
+ const costMap = new Map();
1160
+ for (const { key: filePath, value: cacheData } of this.store.allSessions()) {
1161
+ const normalized = filePath.replace(/\\/g, '/');
1162
+ const projMatch = normalized.match(/\/projects\/([^/]+)\//);
1163
+ if (!projMatch)
1164
+ continue;
1165
+ const encodedKey = projMatch[1];
1166
+ let cost = cacheData.totalCostUsd || cacheData.cumulativeCostUsd || 0;
1167
+ if (!cost && cacheData.usage && cacheData.usage.inputTokens > 0) {
1168
+ if (!calc)
1169
+ calc = new cost_calculator_1.CostCalculator();
1170
+ cost = calc.calculateCost(cacheData.usage, cacheData.model || '').totalCost;
1171
+ }
1172
+ if (cost > 0) {
1173
+ costMap.set(encodedKey, (costMap.get(encodedKey) || 0) + cost);
1174
+ }
1175
+ }
1176
+ return costMap;
1177
+ }
1178
+ getRawMessagesSync(sessionPath) {
1179
+ if (!fs.existsSync(sessionPath)) {
1180
+ return null;
1181
+ }
1182
+ const stats = fs.statSync(sessionPath);
1183
+ let cache = this.store.getRawMessages(sessionPath);
1184
+ const validity = this.isRawCacheValid(cache || null, stats);
1185
+ if (validity === 'valid' && cache) {
1186
+ return cache.messages;
1187
+ }
1188
+ if (validity === 'append' && cache) {
1189
+ const content = fs.readFileSync(sessionPath, 'utf-8');
1190
+ const lines = content.split('\n');
1191
+ const newMessages = [];
1192
+ for (let lineIndex = cache.lastLineIndex + 1; lineIndex < lines.length; lineIndex++) {
1193
+ const line = lines[lineIndex];
1194
+ if (line.trim()) {
1195
+ try {
1196
+ const msg = JSON.parse(line);
1197
+ newMessages.push({ ...msg, lineIndex });
1198
+ }
1199
+ catch {
1200
+ // Skip invalid JSON
1201
+ }
1202
+ }
1203
+ }
1204
+ if (newMessages.length > 0) {
1205
+ cache.messages.push(...newMessages);
1206
+ cache.lastLineIndex = lines.length - 1;
1207
+ cache.fileSize = stats.size;
1208
+ cache.fileMtime = stats.mtime.getTime();
1209
+ this.store.putRawMessages(sessionPath, cache).catch(() => { });
1210
+ }
1211
+ return cache.messages;
1212
+ }
1213
+ const content = fs.readFileSync(sessionPath, 'utf-8');
1214
+ const lines = content.split('\n');
1215
+ const messages = [];
1216
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
1217
+ const line = lines[lineIndex];
1218
+ if (line.trim()) {
1219
+ try {
1220
+ const msg = JSON.parse(line);
1221
+ messages.push({ ...msg, lineIndex });
1222
+ }
1223
+ catch {
1224
+ // Skip invalid JSON
1225
+ }
1226
+ }
1227
+ }
1228
+ const rawCache = {
1229
+ version: CACHE_VERSION,
1230
+ sessionId: path.basename(sessionPath, '.jsonl'),
1231
+ fileSize: stats.size,
1232
+ fileMtime: stats.mtime.getTime(),
1233
+ lastLineIndex: lines.length - 1,
1234
+ messages,
1235
+ };
1236
+ this.store.putRawMessages(sessionPath, rawCache).catch(() => { });
1237
+ return messages;
1238
+ }
1239
+ getSubagentSessionsFromCache(projectPath, sessionId) {
1240
+ const projectKey = (0, path_utils_1.legacyEncodeProjectPath)(projectPath);
1241
+ const subagentsDir = path.join(os.homedir(), '.claude', 'projects', projectKey, sessionId, 'subagents');
1242
+ if (!fs.existsSync(subagentsDir))
1243
+ return [];
1244
+ let agentFiles;
1245
+ try {
1246
+ agentFiles = fs.readdirSync(subagentsDir)
1247
+ .filter(f => f.startsWith('agent-') && f.endsWith('.jsonl'));
1248
+ }
1249
+ catch {
1250
+ return [];
1251
+ }
1252
+ const results = [];
1253
+ for (const file of agentFiles) {
1254
+ const filePath = path.join(subagentsDir, file);
1255
+ const cacheData = this.getSessionDataSync(filePath);
1256
+ if (cacheData) {
1257
+ results.push({ filePath, cacheData });
1258
+ }
1259
+ }
1260
+ return results;
1261
+ }
1262
+ isProjectCached(projectPath) {
1263
+ const projectKey = (0, path_utils_1.legacyEncodeProjectPath)(projectPath);
1264
+ const projectsDir = path.join(os.homedir(), '.claude', 'projects', projectKey);
1265
+ if (!fs.existsSync(projectsDir))
1266
+ return true;
1267
+ const files = fs.readdirSync(projectsDir)
1268
+ .filter(f => f.endsWith('.jsonl') && !f.startsWith('agent-'));
1269
+ for (const file of files) {
1270
+ const filePath = path.join(projectsDir, file);
1271
+ if (!this.store.getSessionData(filePath)) {
1272
+ return false;
1273
+ }
1274
+ }
1275
+ return true;
1276
+ }
1277
+ getStats() {
1278
+ return {
1279
+ memoryCacheSize: this.store.sessionCount + this.store.rawCount,
1280
+ diskCacheCount: this.store.sessionCount + this.store.rawCount,
1281
+ diskCacheSize: 0,
1282
+ };
1283
+ }
1284
+ close() {
1285
+ this.stopWatching();
1286
+ this.store.close();
1287
+ }
1288
+ }
1289
+ exports.SessionCache = SessionCache;
1290
+ function createSessionCache(cacheDir) {
1291
+ return new SessionCache(cacheDir);
1292
+ }
1293
+ exports.default = SessionCache;
1294
+ //# sourceMappingURL=session-cache.js.map