codesesh 0.2.0 → 0.4.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.
@@ -10,18 +10,22 @@ import { readFileSync } from "fs";
10
10
  import { basename } from "path";
11
11
  import { existsSync as existsSync3, statSync as statSync2 } from "fs";
12
12
  import { join as join3 } from "path";
13
+ import { mkdirSync } from "fs";
14
+ import { dirname as dirname2 } from "path";
13
15
  import { createRequire } from "module";
14
16
  import { createHash } from "crypto";
15
17
  import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
16
- import { join as join4, basename as basename3, dirname as dirname2 } from "path";
18
+ import { join as join4, basename as basename3, dirname as dirname3 } from "path";
17
19
  import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
18
20
  import { join as join5, basename as basename4 } from "path";
19
21
  import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
20
22
  import { join as join6, normalize } from "path";
21
23
  import { resolve, sep } from "path";
22
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync, mkdirSync } from "fs";
24
+ import { existsSync as existsSync7, rmSync, unlinkSync } from "fs";
23
25
  import { join as join7 } from "path";
24
26
  import { homedir as homedir2 } from "os";
27
+ import { homedir as homedir3, platform as platform2 } from "os";
28
+ import { join as join8 } from "path";
25
29
  var registrations = [];
26
30
  function registerAgent(reg) {
27
31
  registrations.push(reg);
@@ -306,6 +310,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
306
310
  let totalCost = 0;
307
311
  let totalInputTokens = 0;
308
312
  let totalOutputTokens = 0;
313
+ let totalCacheRead = 0;
314
+ let totalCacheCreate = 0;
309
315
  for (const record of parseJsonlLines(content)) {
310
316
  try {
311
317
  this.convertRecord(
@@ -323,6 +329,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
323
329
  totalCost += msg.cost ?? 0;
324
330
  totalInputTokens += msg.tokens?.input ?? 0;
325
331
  totalOutputTokens += msg.tokens?.output ?? 0;
332
+ totalCacheRead += msg.tokens?.cache_read ?? 0;
333
+ totalCacheCreate += msg.tokens?.cache_create ?? 0;
326
334
  }
327
335
  return {
328
336
  id: meta.id,
@@ -336,7 +344,9 @@ var ClaudeCodeAgent = class extends BaseAgent {
336
344
  message_count: messages.length,
337
345
  total_input_tokens: totalInputTokens,
338
346
  total_output_tokens: totalOutputTokens,
339
- total_cost: totalCost
347
+ total_cost: totalCost,
348
+ total_cache_read_tokens: totalCacheRead,
349
+ total_cache_create_tokens: totalCacheCreate
340
350
  },
341
351
  messages
342
352
  };
@@ -510,6 +520,9 @@ var ClaudeCodeAgent = class extends BaseAgent {
510
520
  let cwd = null;
511
521
  let totalInputTokens = 0;
512
522
  let totalOutputTokens = 0;
523
+ let totalCacheReadTokens = 0;
524
+ let totalCacheCreateTokens = 0;
525
+ const modelUsageMap = {};
513
526
  for (const line of lines) {
514
527
  try {
515
528
  const data = JSON.parse(line);
@@ -531,8 +544,20 @@ var ClaudeCodeAgent = class extends BaseAgent {
531
544
  if (role === "assistant") {
532
545
  const usage = msg["usage"];
533
546
  if (usage && typeof usage === "object") {
534
- totalInputTokens += (usage["input_tokens"] ?? 0) + (usage["cache_creation_input_tokens"] ?? 0) + (usage["cache_read_input_tokens"] ?? 0);
535
- totalOutputTokens += usage["output_tokens"] ?? 0;
547
+ const inputTokens = usage["input_tokens"] ?? 0;
548
+ const cacheRead = usage["cache_read_input_tokens"] ?? 0;
549
+ const cacheCreate = usage["cache_creation_input_tokens"] ?? 0;
550
+ const outputTokens = usage["output_tokens"] ?? 0;
551
+ totalInputTokens += inputTokens + cacheRead + cacheCreate;
552
+ totalOutputTokens += outputTokens;
553
+ totalCacheReadTokens += cacheRead;
554
+ totalCacheCreateTokens += cacheCreate;
555
+ const m = msg["model"];
556
+ if (typeof m === "string" && m.trim()) {
557
+ const name = m.trim();
558
+ const msgTotal = inputTokens + cacheRead + cacheCreate + outputTokens;
559
+ modelUsageMap[name] = (modelUsageMap[name] ?? 0) + msgTotal;
560
+ }
536
561
  }
537
562
  }
538
563
  }
@@ -543,6 +568,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
543
568
  const messageTitle = this.extractTitle(lines);
544
569
  const directoryTitle = basenameTitle(directory) || basenameTitle(projectDir);
545
570
  const title = resolveSessionTitle(explicitTitle, messageTitle, directoryTitle);
571
+ const hasModelUsage = Object.keys(modelUsageMap).length > 0;
546
572
  return {
547
573
  id: sessionId,
548
574
  slug: `claudecode/${sessionId}`,
@@ -554,8 +580,11 @@ var ClaudeCodeAgent = class extends BaseAgent {
554
580
  message_count: messageCount,
555
581
  total_input_tokens: totalInputTokens,
556
582
  total_output_tokens: totalOutputTokens,
557
- total_cost: 0
558
- }
583
+ total_cost: 0,
584
+ total_cache_read_tokens: totalCacheReadTokens,
585
+ total_cache_create_tokens: totalCacheCreateTokens
586
+ },
587
+ model_usage: hasModelUsage ? modelUsageMap : void 0
559
588
  };
560
589
  }
561
590
  extractTitle(lines) {
@@ -783,9 +812,13 @@ var ClaudeCodeAgent = class extends BaseAgent {
783
812
  const usage = msg["usage"];
784
813
  if (usage && typeof usage === "object" && !message.tokens) {
785
814
  const u = usage;
815
+ const cacheRead = u["cache_read_input_tokens"] ?? 0;
816
+ const cacheCreate = u["cache_creation_input_tokens"] ?? 0;
786
817
  message.tokens = {
787
- input: (u["input_tokens"] ?? 0) + (u["cache_creation_input_tokens"] ?? 0) + (u["cache_read_input_tokens"] ?? 0),
788
- output: u["output_tokens"] ?? 0
818
+ input: (u["input_tokens"] ?? 0) + cacheCreate + cacheRead,
819
+ output: u["output_tokens"] ?? 0,
820
+ cache_read: cacheRead,
821
+ cache_create: cacheCreate
789
822
  };
790
823
  }
791
824
  }
@@ -982,6 +1015,19 @@ function openDbReadOnly(dbPath) {
982
1015
  return null;
983
1016
  }
984
1017
  }
1018
+ function openDb(dbPath) {
1019
+ if (!DatabaseConstructor) return null;
1020
+ try {
1021
+ mkdirSync(dirname2(dbPath), { recursive: true });
1022
+ const db = DatabaseConstructor(dbPath);
1023
+ db.pragma("journal_mode = WAL");
1024
+ db.pragma("synchronous = NORMAL");
1025
+ db.pragma("foreign_keys = ON");
1026
+ return db;
1027
+ } catch {
1028
+ return null;
1029
+ }
1030
+ }
985
1031
  function isSqliteAvailable() {
986
1032
  return DatabaseConstructor !== null;
987
1033
  }
@@ -1340,7 +1386,7 @@ var KimiAgent = class extends BaseAgent {
1340
1386
  parseSessionDir(sessionDir) {
1341
1387
  try {
1342
1388
  const sessionId = basename3(sessionDir);
1343
- const projectHash = basename3(dirname2(sessionDir));
1389
+ const projectHash = basename3(dirname3(sessionDir));
1344
1390
  const contextFile = join4(sessionDir, "context.jsonl");
1345
1391
  const wireFile = join4(sessionDir, "wire.jsonl");
1346
1392
  if (!existsSync4(contextFile) && !existsSync4(wireFile)) return null;
@@ -3072,6 +3118,16 @@ var CursorAgent = class extends BaseAgent {
3072
3118
  }
3073
3119
  const messageCount = messages.length;
3074
3120
  const directory = workspacePathMap.get(composerId) ?? "";
3121
+ const modelUsageMap = {};
3122
+ for (const msg of messages) {
3123
+ if (msg.model) {
3124
+ const msgTokens = (msg.tokens?.input ?? 0) + (msg.tokens?.output ?? 0);
3125
+ if (msgTokens > 0) {
3126
+ modelUsageMap[msg.model] = (modelUsageMap[msg.model] ?? 0) + msgTokens;
3127
+ }
3128
+ }
3129
+ }
3130
+ const hasModelUsage = Object.keys(modelUsageMap).length > 0;
3075
3131
  heads.push({
3076
3132
  id: sessionId,
3077
3133
  slug: `cursor/${sessionId}`,
@@ -3084,7 +3140,8 @@ var CursorAgent = class extends BaseAgent {
3084
3140
  total_input_tokens: composer.inputTokenCount ?? 0,
3085
3141
  total_output_tokens: composer.outputTokenCount ?? 0,
3086
3142
  total_cost: 0
3087
- }
3143
+ },
3144
+ model_usage: hasModelUsage ? modelUsageMap : void 0
3088
3145
  });
3089
3146
  this.composerCache.set(sessionId, composer);
3090
3147
  this.composerCache.set(`__mapping__${composerId}`, {
@@ -3492,89 +3549,438 @@ registerAgent({
3492
3549
  icon: "/icon/agent/cursor.svg",
3493
3550
  create: () => new CursorAgent()
3494
3551
  });
3495
- var CACHE_VERSION = 2;
3496
- var CACHE_FILENAME = "scan-cache.json";
3552
+ var CACHE_VERSION = 4;
3553
+ var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
3554
+ var CACHE_FILENAME = "codesesh.db";
3555
+ var LEGACY_CACHE_FILENAME = "scan-cache.json";
3556
+ function getCacheDir() {
3557
+ return join7(homedir2(), ".cache", "codesesh");
3558
+ }
3497
3559
  function getCachePath() {
3498
- return join7(homedir2(), ".cache", "codesesh", CACHE_FILENAME);
3560
+ return join7(getCacheDir(), CACHE_FILENAME);
3499
3561
  }
3500
- function ensureCacheDir() {
3501
- const cacheDir = join7(homedir2(), ".cache", "codesesh");
3502
- if (!existsSync7(cacheDir)) {
3503
- mkdirSync(cacheDir, { recursive: true });
3504
- }
3562
+ function getLegacyCachePath() {
3563
+ return join7(getCacheDir(), LEGACY_CACHE_FILENAME);
3505
3564
  }
3506
- function loadCachedSessions(agentName) {
3565
+ function hasCacheStorage() {
3566
+ return existsSync7(getCachePath());
3567
+ }
3568
+ function withCacheDb(fn) {
3569
+ const db = openDb(getCachePath());
3570
+ if (!db) return null;
3507
3571
  try {
3508
- const cachePath = getCachePath();
3509
- if (!existsSync7(cachePath)) return null;
3510
- const data = JSON.parse(readFileSync6(cachePath, "utf-8"));
3511
- if (data.version !== CACHE_VERSION) return null;
3512
- const entry = data.entries[agentName];
3513
- if (!entry) return null;
3514
- const CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
3515
- if (Date.now() - entry.timestamp > CACHE_TTL) return null;
3516
- return { sessions: entry.sessions, meta: entry.meta || {}, timestamp: entry.timestamp };
3572
+ ensureSchema(db);
3573
+ return fn(db);
3517
3574
  } catch {
3518
3575
  return null;
3576
+ } finally {
3577
+ db.close();
3519
3578
  }
3520
3579
  }
3521
- function saveCachedSessions(agentName, sessions, meta = {}) {
3522
- try {
3523
- ensureCacheDir();
3524
- const cachePath = getCachePath();
3525
- let data;
3526
- if (existsSync7(cachePath)) {
3527
- try {
3528
- data = JSON.parse(readFileSync6(cachePath, "utf-8"));
3529
- if (data.version !== CACHE_VERSION) {
3530
- data = { version: CACHE_VERSION, entries: {}, lastScanTime: 0 };
3531
- }
3532
- } catch {
3533
- data = { version: CACHE_VERSION, entries: {}, lastScanTime: 0 };
3534
- }
3535
- } else {
3536
- data = { version: CACHE_VERSION, entries: {}, lastScanTime: 0 };
3580
+ function ensureSchema(db) {
3581
+ db.exec(`
3582
+ CREATE TABLE IF NOT EXISTS cache_meta (
3583
+ key TEXT PRIMARY KEY,
3584
+ value TEXT NOT NULL
3585
+ );
3586
+
3587
+ CREATE TABLE IF NOT EXISTS agent_cache (
3588
+ agent_name TEXT PRIMARY KEY,
3589
+ timestamp INTEGER NOT NULL
3590
+ );
3591
+
3592
+ CREATE TABLE IF NOT EXISTS cached_sessions (
3593
+ agent_name TEXT NOT NULL,
3594
+ session_id TEXT NOT NULL,
3595
+ session_json TEXT NOT NULL,
3596
+ meta_json TEXT,
3597
+ PRIMARY KEY (agent_name, session_id)
3598
+ );
3599
+
3600
+ CREATE TABLE IF NOT EXISTS session_documents (
3601
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3602
+ agent_name TEXT NOT NULL,
3603
+ session_id TEXT NOT NULL,
3604
+ slug TEXT NOT NULL,
3605
+ title TEXT NOT NULL,
3606
+ directory TEXT NOT NULL,
3607
+ time_created INTEGER NOT NULL,
3608
+ time_updated INTEGER,
3609
+ activity_time INTEGER NOT NULL,
3610
+ content_text TEXT NOT NULL,
3611
+ content_hash TEXT NOT NULL,
3612
+ indexed_at INTEGER NOT NULL,
3613
+ UNIQUE(agent_name, session_id)
3614
+ );
3615
+
3616
+ CREATE VIRTUAL TABLE IF NOT EXISTS session_documents_fts USING fts5(
3617
+ title,
3618
+ content_text,
3619
+ content='session_documents',
3620
+ content_rowid='id'
3621
+ );
3622
+
3623
+ CREATE TRIGGER IF NOT EXISTS session_documents_ai AFTER INSERT ON session_documents BEGIN
3624
+ INSERT INTO session_documents_fts(rowid, title, content_text)
3625
+ VALUES (new.id, new.title, new.content_text);
3626
+ END;
3627
+
3628
+ CREATE TRIGGER IF NOT EXISTS session_documents_ad AFTER DELETE ON session_documents BEGIN
3629
+ INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
3630
+ VALUES ('delete', old.id, old.title, old.content_text);
3631
+ END;
3632
+
3633
+ CREATE TRIGGER IF NOT EXISTS session_documents_au AFTER UPDATE ON session_documents BEGIN
3634
+ INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
3635
+ VALUES ('delete', old.id, old.title, old.content_text);
3636
+ INSERT INTO session_documents_fts(rowid, title, content_text)
3637
+ VALUES (new.id, new.title, new.content_text);
3638
+ END;
3639
+ `);
3640
+ const versionRow = db.prepare("SELECT value FROM cache_meta WHERE key = 'version'").get();
3641
+ const version = Number(versionRow?.value ?? 0);
3642
+ if (version === CACHE_VERSION) {
3643
+ return;
3644
+ }
3645
+ db.exec(`
3646
+ DELETE FROM agent_cache;
3647
+ DELETE FROM cached_sessions;
3648
+ DELETE FROM session_documents;
3649
+ INSERT INTO session_documents_fts(session_documents_fts) VALUES ('rebuild');
3650
+ INSERT INTO cache_meta(key, value)
3651
+ VALUES ('version', '${CACHE_VERSION}')
3652
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value;
3653
+ `);
3654
+ }
3655
+ function sessionContentHash(session) {
3656
+ return JSON.stringify([
3657
+ session.slug,
3658
+ session.title,
3659
+ session.directory,
3660
+ session.time_created,
3661
+ session.time_updated ?? session.time_created,
3662
+ session.stats.message_count,
3663
+ session.stats.total_input_tokens,
3664
+ session.stats.total_output_tokens,
3665
+ session.stats.total_cost,
3666
+ session.stats.total_tokens ?? 0
3667
+ ]);
3668
+ }
3669
+ function escapeFtsTerm(value) {
3670
+ return value.replaceAll('"', '""');
3671
+ }
3672
+ function toFtsQuery(input) {
3673
+ const tokens = input.match(/"[^"]+"|\S+/g) ?? [];
3674
+ return tokens.map((token) => {
3675
+ if (/^OR$/i.test(token)) {
3676
+ return "OR";
3537
3677
  }
3538
- data.entries[agentName] = {
3539
- sessions,
3540
- meta,
3541
- timestamp: Date.now(),
3542
- version: CACHE_VERSION
3543
- };
3544
- data.lastScanTime = Date.now();
3545
- writeFileSync(cachePath, JSON.stringify(data, null, 2), "utf-8");
3678
+ if (token.startsWith('"') && token.endsWith('"')) {
3679
+ return `"${escapeFtsTerm(token.slice(1, -1))}"`;
3680
+ }
3681
+ return `"${escapeFtsTerm(token)}"`;
3682
+ }).join(" ");
3683
+ }
3684
+ function appendPlainText(value, chunks) {
3685
+ if (value == null) return;
3686
+ if (typeof value === "string") {
3687
+ const normalized = value.trim();
3688
+ if (normalized) {
3689
+ chunks.push(normalized);
3690
+ }
3691
+ return;
3692
+ }
3693
+ if (typeof value === "number" || typeof value === "boolean") {
3694
+ chunks.push(String(value));
3695
+ return;
3696
+ }
3697
+ if (Array.isArray(value)) {
3698
+ for (const item of value) {
3699
+ appendPlainText(item, chunks);
3700
+ }
3701
+ return;
3702
+ }
3703
+ if (typeof value === "object") {
3704
+ for (const nested of Object.values(value)) {
3705
+ appendPlainText(nested, chunks);
3706
+ }
3707
+ }
3708
+ }
3709
+ function buildSessionContent(session) {
3710
+ const chunks = [];
3711
+ appendPlainText(session.title, chunks);
3712
+ for (const message of session.messages) {
3713
+ chunks.push(message.role);
3714
+ appendPlainText(message.agent, chunks);
3715
+ appendPlainText(message.model, chunks);
3716
+ for (const part of message.parts) {
3717
+ appendPlainText(part.type, chunks);
3718
+ appendPlainText(part.title, chunks);
3719
+ appendPlainText(part.nickname, chunks);
3720
+ appendPlainText(part.tool, chunks);
3721
+ appendPlainText(part.text, chunks);
3722
+ appendPlainText(part.input, chunks);
3723
+ appendPlainText(part.output, chunks);
3724
+ appendPlainText(part.state, chunks);
3725
+ }
3726
+ }
3727
+ return chunks.join("\n");
3728
+ }
3729
+ function deleteLegacyCacheFile() {
3730
+ const legacyPath = getLegacyCachePath();
3731
+ if (!existsSync7(legacyPath)) {
3732
+ return;
3733
+ }
3734
+ try {
3735
+ unlinkSync(legacyPath);
3546
3736
  } catch {
3547
3737
  }
3548
3738
  }
3739
+ function loadCachedSessions(agentName) {
3740
+ if (!hasCacheStorage()) {
3741
+ return null;
3742
+ }
3743
+ return withCacheDb((db) => {
3744
+ const timestampRow = db.prepare("SELECT timestamp AS value FROM agent_cache WHERE agent_name = ?").get(agentName);
3745
+ const timestamp = Number(timestampRow?.value ?? 0);
3746
+ if (!timestamp || Date.now() - timestamp > CACHE_TTL) {
3747
+ return null;
3748
+ }
3749
+ const rows = db.prepare(
3750
+ `
3751
+ SELECT session_json, meta_json
3752
+ FROM cached_sessions
3753
+ WHERE agent_name = ?
3754
+ ORDER BY rowid
3755
+ `
3756
+ ).all(agentName);
3757
+ const sessions = [];
3758
+ const meta = {};
3759
+ for (const row of rows) {
3760
+ if (!row.session_json) {
3761
+ continue;
3762
+ }
3763
+ const session = JSON.parse(row.session_json);
3764
+ sessions.push(session);
3765
+ if (row.meta_json) {
3766
+ meta[session.id] = JSON.parse(row.meta_json);
3767
+ }
3768
+ }
3769
+ return { sessions, meta, timestamp };
3770
+ });
3771
+ }
3772
+ function saveCachedSessions(agentName, sessions, meta = {}) {
3773
+ withCacheDb((db) => {
3774
+ const deleteAgent = db.prepare("DELETE FROM agent_cache WHERE agent_name = ?");
3775
+ const deleteSessions = db.prepare("DELETE FROM cached_sessions WHERE agent_name = ?");
3776
+ const upsertAgent = db.prepare(`
3777
+ INSERT INTO agent_cache(agent_name, timestamp)
3778
+ VALUES (?, ?)
3779
+ ON CONFLICT(agent_name) DO UPDATE SET timestamp = excluded.timestamp
3780
+ `);
3781
+ const insertSession = db.prepare(`
3782
+ INSERT INTO cached_sessions(agent_name, session_id, session_json, meta_json)
3783
+ VALUES (?, ?, ?, ?)
3784
+ `);
3785
+ const write = db.transaction(() => {
3786
+ const timestamp = Date.now();
3787
+ deleteAgent.run(agentName);
3788
+ deleteSessions.run(agentName);
3789
+ upsertAgent.run(agentName, timestamp);
3790
+ for (const session of sessions) {
3791
+ insertSession.run(
3792
+ agentName,
3793
+ session.id,
3794
+ JSON.stringify(session),
3795
+ meta[session.id] ? JSON.stringify(meta[session.id]) : null
3796
+ );
3797
+ }
3798
+ });
3799
+ write();
3800
+ deleteLegacyCacheFile();
3801
+ });
3802
+ }
3549
3803
  function clearCache() {
3550
- try {
3551
- const cachePath = getCachePath();
3552
- if (existsSync7(cachePath)) {
3553
- const data = {
3554
- version: CACHE_VERSION,
3555
- entries: {},
3556
- lastScanTime: 0
3557
- };
3558
- writeFileSync(cachePath, JSON.stringify(data, null, 2), "utf-8");
3804
+ if (!hasCacheStorage()) {
3805
+ deleteLegacyCacheFile();
3806
+ return;
3807
+ }
3808
+ withCacheDb((db) => {
3809
+ db.exec(`
3810
+ DELETE FROM agent_cache;
3811
+ DELETE FROM cached_sessions;
3812
+ `);
3813
+ });
3814
+ deleteLegacyCacheFile();
3815
+ const cachePath = getCachePath();
3816
+ const walPath = `${cachePath}-wal`;
3817
+ const shmPath = `${cachePath}-shm`;
3818
+ for (const filePath of [walPath, shmPath]) {
3819
+ if (!existsSync7(filePath)) {
3820
+ continue;
3821
+ }
3822
+ try {
3823
+ rmSync(filePath, { force: true });
3824
+ } catch {
3559
3825
  }
3560
- } catch {
3561
3826
  }
3562
3827
  }
3563
3828
  function getCacheInfo() {
3564
- try {
3565
- const cachePath = getCachePath();
3566
- if (!existsSync7(cachePath)) {
3567
- return { lastScanTime: null, size: 0 };
3568
- }
3569
- const data = JSON.parse(readFileSync6(cachePath, "utf-8"));
3570
- const size = Object.values(data.entries).reduce((sum, entry) => sum + entry.sessions.length, 0);
3571
- return {
3572
- lastScanTime: data.lastScanTime || null,
3573
- size
3574
- };
3575
- } catch {
3829
+ if (!hasCacheStorage()) {
3576
3830
  return { lastScanTime: null, size: 0 };
3577
3831
  }
3832
+ const info = withCacheDb((db) => {
3833
+ const timestampRow = db.prepare("SELECT MAX(timestamp) AS value FROM agent_cache").get();
3834
+ const sizeRow = db.prepare("SELECT COUNT(*) AS value FROM cached_sessions").get();
3835
+ const lastScanTime = Number(timestampRow?.value ?? 0) || null;
3836
+ const size = Number(sizeRow?.value ?? 0);
3837
+ return { lastScanTime, size };
3838
+ });
3839
+ return info ?? { lastScanTime: null, size: 0 };
3840
+ }
3841
+ function syncSessionSearchIndex(agentName, sessions, loadSessionData) {
3842
+ if (!hasCacheStorage()) {
3843
+ return;
3844
+ }
3845
+ withCacheDb((db) => {
3846
+ const existingRows = db.prepare(
3847
+ "SELECT session_id, content_hash FROM session_documents WHERE agent_name = ? ORDER BY id"
3848
+ ).all(agentName);
3849
+ const existingMap = new Map(
3850
+ existingRows.map((row) => [String(row.session_id), String(row.content_hash ?? "")])
3851
+ );
3852
+ const sessionMap = new Map(sessions.map((session) => [session.id, session]));
3853
+ const toDelete = existingRows.map((row) => String(row.session_id)).filter((sessionId) => !sessionMap.has(sessionId));
3854
+ const toUpsert = sessions.filter(
3855
+ (session) => existingMap.get(session.id) !== sessionContentHash(session)
3856
+ );
3857
+ const loaded = toUpsert.map((session) => {
3858
+ try {
3859
+ const data = loadSessionData(session.id);
3860
+ return {
3861
+ session,
3862
+ contentText: buildSessionContent(data),
3863
+ contentHash: sessionContentHash(session)
3864
+ };
3865
+ } catch {
3866
+ return null;
3867
+ }
3868
+ }).filter((entry) => entry !== null);
3869
+ const deleteRow = db.prepare(
3870
+ "DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
3871
+ );
3872
+ const upsertRow = db.prepare(`
3873
+ INSERT INTO session_documents(
3874
+ agent_name,
3875
+ session_id,
3876
+ slug,
3877
+ title,
3878
+ directory,
3879
+ time_created,
3880
+ time_updated,
3881
+ activity_time,
3882
+ content_text,
3883
+ content_hash,
3884
+ indexed_at
3885
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3886
+ ON CONFLICT(agent_name, session_id) DO UPDATE SET
3887
+ slug = excluded.slug,
3888
+ title = excluded.title,
3889
+ directory = excluded.directory,
3890
+ time_created = excluded.time_created,
3891
+ time_updated = excluded.time_updated,
3892
+ activity_time = excluded.activity_time,
3893
+ content_text = excluded.content_text,
3894
+ content_hash = excluded.content_hash,
3895
+ indexed_at = excluded.indexed_at
3896
+ `);
3897
+ const write = db.transaction(() => {
3898
+ for (const sessionId of toDelete) {
3899
+ deleteRow.run(agentName, sessionId);
3900
+ }
3901
+ for (const entry of loaded) {
3902
+ const activityTime = entry.session.time_updated ?? entry.session.time_created;
3903
+ upsertRow.run(
3904
+ agentName,
3905
+ entry.session.id,
3906
+ entry.session.slug,
3907
+ entry.session.title,
3908
+ entry.session.directory,
3909
+ entry.session.time_created,
3910
+ entry.session.time_updated ?? null,
3911
+ activityTime,
3912
+ entry.contentText,
3913
+ entry.contentHash,
3914
+ Date.now()
3915
+ );
3916
+ }
3917
+ });
3918
+ write();
3919
+ });
3920
+ }
3921
+ function searchSessions(query, options = {}) {
3922
+ const normalizedQuery = query.trim();
3923
+ if (!normalizedQuery || !hasCacheStorage()) {
3924
+ return [];
3925
+ }
3926
+ const ftsQuery = toFtsQuery(normalizedQuery);
3927
+ const results = withCacheDb((db) => {
3928
+ const rows = db.prepare(
3929
+ `
3930
+ SELECT
3931
+ d.agent_name,
3932
+ d.session_id,
3933
+ d.slug,
3934
+ d.title,
3935
+ d.directory,
3936
+ d.time_created,
3937
+ d.time_updated,
3938
+ COALESCE(
3939
+ NULLIF(snippet(session_documents_fts, 1, '<mark>', '</mark>', ' \u2026 ', 18), ''),
3940
+ highlight(session_documents_fts, 0, '<mark>', '</mark>')
3941
+ ) AS snippet
3942
+ FROM session_documents_fts
3943
+ JOIN session_documents d ON d.id = session_documents_fts.rowid
3944
+ WHERE session_documents_fts MATCH ?
3945
+ AND (? IS NULL OR d.agent_name = ?)
3946
+ AND (? IS NULL OR LOWER(d.directory) LIKE ?)
3947
+ AND (? IS NULL OR d.activity_time >= ?)
3948
+ AND (? IS NULL OR d.activity_time <= ?)
3949
+ ORDER BY bm25(session_documents_fts, 8.0, 1.0), d.activity_time DESC
3950
+ LIMIT ?
3951
+ `
3952
+ ).all(
3953
+ ftsQuery,
3954
+ options.agent ?? null,
3955
+ options.agent ?? null,
3956
+ options.cwd?.toLowerCase() ?? null,
3957
+ options.cwd ? `%${options.cwd.toLowerCase()}%` : null,
3958
+ options.from ?? null,
3959
+ options.from ?? null,
3960
+ options.to ?? null,
3961
+ options.to ?? null,
3962
+ options.limit ?? 50
3963
+ );
3964
+ return rows.map((row) => ({
3965
+ agentName: String(row.agent_name),
3966
+ session: {
3967
+ id: String(row.session_id),
3968
+ slug: String(row.slug),
3969
+ title: String(row.title),
3970
+ directory: String(row.directory),
3971
+ time_created: Number(row.time_created),
3972
+ time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
3973
+ stats: {
3974
+ message_count: 0,
3975
+ total_input_tokens: 0,
3976
+ total_output_tokens: 0,
3977
+ total_cost: 0
3978
+ }
3979
+ },
3980
+ snippet: String(row.snippet ?? "")
3981
+ }));
3982
+ });
3983
+ return results ?? [];
3578
3984
  }
3579
3985
  function isPathScopeMatch(queryPath, sessionPath) {
3580
3986
  if (!sessionPath) return false;
@@ -3710,6 +4116,221 @@ async function scanSessions(options = {}, onProgress) {
3710
4116
  async function scanSessionsAsync(options = {}, onProgress) {
3711
4117
  return scanSessions(options, onProgress);
3712
4118
  }
4119
+ var BOOKMARK_DB_FILENAME = "state.db";
4120
+ var BOOKMARK_DB_VERSION = 1;
4121
+ function getStateDir() {
4122
+ const p = platform2();
4123
+ if (p === "darwin") {
4124
+ return join8(homedir3(), "Library", "Application Support", "codesesh");
4125
+ }
4126
+ if (p === "win32") {
4127
+ const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
4128
+ return join8(appData ?? join8(homedir3(), "AppData", "Roaming"), "codesesh");
4129
+ }
4130
+ return join8(process.env.XDG_DATA_HOME ?? join8(homedir3(), ".local", "share"), "codesesh");
4131
+ }
4132
+ function getStateDbPath() {
4133
+ return join8(getStateDir(), BOOKMARK_DB_FILENAME);
4134
+ }
4135
+ function ensureSchema2(db) {
4136
+ db.exec(`
4137
+ CREATE TABLE IF NOT EXISTS state_meta (
4138
+ key TEXT PRIMARY KEY,
4139
+ value TEXT NOT NULL
4140
+ );
4141
+
4142
+ CREATE TABLE IF NOT EXISTS bookmarks (
4143
+ agent_name TEXT NOT NULL,
4144
+ session_id TEXT NOT NULL,
4145
+ slug TEXT NOT NULL,
4146
+ title TEXT NOT NULL,
4147
+ directory TEXT NOT NULL,
4148
+ time_created INTEGER NOT NULL,
4149
+ time_updated INTEGER,
4150
+ stats_json TEXT NOT NULL,
4151
+ bookmarked_at INTEGER NOT NULL,
4152
+ PRIMARY KEY (agent_name, session_id)
4153
+ );
4154
+ `);
4155
+ const row = db.prepare("SELECT value FROM state_meta WHERE key = 'version'").get();
4156
+ const version = Number(row?.value ?? 0);
4157
+ if (version === BOOKMARK_DB_VERSION) return;
4158
+ db.prepare(
4159
+ `
4160
+ INSERT INTO state_meta(key, value)
4161
+ VALUES ('version', ?)
4162
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
4163
+ `
4164
+ ).run(String(BOOKMARK_DB_VERSION));
4165
+ }
4166
+ function withStateDb(fn) {
4167
+ const db = openDb(getStateDbPath());
4168
+ if (!db) {
4169
+ throw new Error("SQLite state database is unavailable");
4170
+ }
4171
+ try {
4172
+ ensureSchema2(db);
4173
+ return fn(db);
4174
+ } finally {
4175
+ db.close();
4176
+ }
4177
+ }
4178
+ function toBookmarkRecord(row) {
4179
+ return {
4180
+ agentKey: String(row.agent_name ?? ""),
4181
+ sessionId: String(row.session_id ?? ""),
4182
+ fullPath: String(row.slug ?? ""),
4183
+ title: String(row.title ?? ""),
4184
+ directory: String(row.directory ?? ""),
4185
+ time_created: Number(row.time_created ?? 0),
4186
+ time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
4187
+ stats: JSON.parse(String(row.stats_json ?? "{}")),
4188
+ bookmarked_at: Number(row.bookmarked_at ?? 0)
4189
+ };
4190
+ }
4191
+ function listBookmarks() {
4192
+ return withStateDb((db) => {
4193
+ const rows = db.prepare(
4194
+ `
4195
+ SELECT
4196
+ agent_name,
4197
+ session_id,
4198
+ slug,
4199
+ title,
4200
+ directory,
4201
+ time_created,
4202
+ time_updated,
4203
+ stats_json,
4204
+ bookmarked_at
4205
+ FROM bookmarks
4206
+ ORDER BY COALESCE(time_updated, time_created) DESC, bookmarked_at DESC
4207
+ `
4208
+ ).all();
4209
+ return rows.map(toBookmarkRecord);
4210
+ });
4211
+ }
4212
+ function upsertBookmark(bookmark) {
4213
+ return withStateDb((db) => {
4214
+ const existing = db.prepare(
4215
+ `
4216
+ SELECT bookmarked_at
4217
+ FROM bookmarks
4218
+ WHERE agent_name = ? AND session_id = ?
4219
+ `
4220
+ ).get(bookmark.agentKey, bookmark.sessionId);
4221
+ const bookmarkedAt = Number(existing?.bookmarked_at ?? Date.now());
4222
+ db.prepare(
4223
+ `
4224
+ INSERT INTO bookmarks(
4225
+ agent_name,
4226
+ session_id,
4227
+ slug,
4228
+ title,
4229
+ directory,
4230
+ time_created,
4231
+ time_updated,
4232
+ stats_json,
4233
+ bookmarked_at
4234
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
4235
+ ON CONFLICT(agent_name, session_id) DO UPDATE SET
4236
+ slug = excluded.slug,
4237
+ title = excluded.title,
4238
+ directory = excluded.directory,
4239
+ time_created = excluded.time_created,
4240
+ time_updated = excluded.time_updated,
4241
+ stats_json = excluded.stats_json
4242
+ `
4243
+ ).run(
4244
+ bookmark.agentKey,
4245
+ bookmark.sessionId,
4246
+ bookmark.fullPath,
4247
+ bookmark.title,
4248
+ bookmark.directory,
4249
+ bookmark.time_created,
4250
+ bookmark.time_updated ?? null,
4251
+ JSON.stringify(bookmark.stats),
4252
+ bookmarkedAt
4253
+ );
4254
+ return { ...bookmark, bookmarked_at: bookmarkedAt };
4255
+ });
4256
+ }
4257
+ function importBookmarks(bookmarks) {
4258
+ return withStateDb((db) => {
4259
+ const existingRows = db.prepare("SELECT agent_name, session_id, bookmarked_at FROM bookmarks").all();
4260
+ const existingTimes = new Map(
4261
+ existingRows.map((row) => [
4262
+ `${String(row.agent_name ?? "")}:${String(row.session_id ?? "")}`,
4263
+ Number(row.bookmarked_at ?? 0)
4264
+ ])
4265
+ );
4266
+ const upsert = db.prepare(
4267
+ `
4268
+ INSERT INTO bookmarks(
4269
+ agent_name,
4270
+ session_id,
4271
+ slug,
4272
+ title,
4273
+ directory,
4274
+ time_created,
4275
+ time_updated,
4276
+ stats_json,
4277
+ bookmarked_at
4278
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
4279
+ ON CONFLICT(agent_name, session_id) DO UPDATE SET
4280
+ slug = excluded.slug,
4281
+ title = excluded.title,
4282
+ directory = excluded.directory,
4283
+ time_created = excluded.time_created,
4284
+ time_updated = excluded.time_updated,
4285
+ stats_json = excluded.stats_json
4286
+ `
4287
+ );
4288
+ const write = db.transaction(() => {
4289
+ for (const bookmark of bookmarks) {
4290
+ const key = `${bookmark.agentKey}:${bookmark.sessionId}`;
4291
+ upsert.run(
4292
+ bookmark.agentKey,
4293
+ bookmark.sessionId,
4294
+ bookmark.fullPath,
4295
+ bookmark.title,
4296
+ bookmark.directory,
4297
+ bookmark.time_created,
4298
+ bookmark.time_updated ?? null,
4299
+ JSON.stringify(bookmark.stats),
4300
+ existingTimes.get(key) ?? Date.now()
4301
+ );
4302
+ }
4303
+ });
4304
+ write();
4305
+ const rows = db.prepare(
4306
+ `
4307
+ SELECT
4308
+ agent_name,
4309
+ session_id,
4310
+ slug,
4311
+ title,
4312
+ directory,
4313
+ time_created,
4314
+ time_updated,
4315
+ stats_json,
4316
+ bookmarked_at
4317
+ FROM bookmarks
4318
+ ORDER BY COALESCE(time_updated, time_created) DESC, bookmarked_at DESC
4319
+ `
4320
+ ).all();
4321
+ return rows.map(toBookmarkRecord);
4322
+ });
4323
+ }
4324
+ function deleteBookmark(agentKey, sessionId) {
4325
+ withStateDb((db) => {
4326
+ db.prepare(
4327
+ `
4328
+ DELETE FROM bookmarks
4329
+ WHERE agent_name = ? AND session_id = ?
4330
+ `
4331
+ ).run(agentKey, sessionId);
4332
+ });
4333
+ }
3713
4334
 
3714
4335
  export {
3715
4336
  registerAgent,
@@ -3728,13 +4349,20 @@ export {
3728
4349
  resolveSessionTitle,
3729
4350
  perf,
3730
4351
  openDbReadOnly,
4352
+ openDb,
3731
4353
  isSqliteAvailable,
3732
4354
  loadCachedSessions,
3733
4355
  saveCachedSessions,
3734
4356
  clearCache,
3735
4357
  getCacheInfo,
4358
+ syncSessionSearchIndex,
4359
+ searchSessions,
3736
4360
  filterSessions,
3737
4361
  scanSessions,
3738
- scanSessionsAsync
4362
+ scanSessionsAsync,
4363
+ listBookmarks,
4364
+ upsertBookmark,
4365
+ importBookmarks,
4366
+ deleteBookmark
3739
4367
  };
3740
- //# sourceMappingURL=chunk-2UNXB2D3.js.map
4368
+ //# sourceMappingURL=chunk-EIIG7J6V.js.map