codesesh 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -19,9 +19,11 @@ Your browser will open at `http://localhost:4321` with all your sessions ready t
19
19
  ## Features
20
20
 
21
21
  - **Unified Timeline** — Browse sessions across all your AI agents in a single, searchable interface
22
+ - **Full-Text Search** — Search across session titles and conversation content with highlighted matches
22
23
  - **Dashboard & Activity Trends** — See totals, daily activity, agent distribution, and recent sessions
23
24
  - **Full Conversation Replay** — Read every message, tool call, and reasoning step exactly as it happened
24
25
  - **Cost & Token Visibility** — See exactly how many tokens and dollars each session consumed
26
+ - **SQLite-Backed Cache & Search Index** — Restore session lists quickly and reuse the same local store for search
25
27
  - **Zero Configuration** — Just run it. CodeSesh auto-discovers everything on your filesystem
26
28
  - **100% Local & Private** — Nothing leaves your machine. No accounts, no cloud sync, no telemetry
27
29
  - **Live Refresh** — Local session changes are picked up automatically while the server is running
@@ -45,7 +47,7 @@ npx codesesh
45
47
  # Choose a custom port
46
48
  npx codesesh --port 8080
47
49
 
48
- # Only show sessions from the last 3 days
50
+ # Only show sessions active in the last 3 days
49
51
  npx codesesh --days 3
50
52
 
51
53
  # Jump directly to a session
@@ -69,11 +71,11 @@ npx codesesh --trace
69
71
  | Flag | Alias | Default | Description |
70
72
  | ----------- | ----- | ------- | ----------------------------------------------------------- |
71
73
  | `--port` | `-p` | `4321` | HTTP server port |
72
- | `--days` | `-d` | `7` | Only include sessions from the last N days (`0` = all time) |
74
+ | `--days` | `-d` | `7` | Only include sessions active in the last N days (`0` = all time) |
73
75
  | `--cwd` | — | — | Filter to sessions from a project directory |
74
76
  | `--agent` | `-a` | all | Filter to specific agent(s), comma-separated |
75
- | `--from` | — | — | Sessions created after this date `YYYY-MM-DD` |
76
- | `--to` | — | — | Sessions created before this date `YYYY-MM-DD` |
77
+ | `--from` | — | — | Sessions active after this date `YYYY-MM-DD` |
78
+ | `--to` | — | — | Sessions active before this date `YYYY-MM-DD` |
77
79
  | `--session` | `-s` | — | Directly open a session (`agent://session-id`) |
78
80
  | `--json` | `-j` | `false` | Output JSON and exit (no server) |
79
81
  | `--no-open` | — | `false` | Don't auto-open the browser |
@@ -10,16 +10,18 @@ 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";
25
27
  var registrations = [];
@@ -982,6 +984,19 @@ function openDbReadOnly(dbPath) {
982
984
  return null;
983
985
  }
984
986
  }
987
+ function openDb(dbPath) {
988
+ if (!DatabaseConstructor) return null;
989
+ try {
990
+ mkdirSync(dirname2(dbPath), { recursive: true });
991
+ const db = DatabaseConstructor(dbPath);
992
+ db.pragma("journal_mode = WAL");
993
+ db.pragma("synchronous = NORMAL");
994
+ db.pragma("foreign_keys = ON");
995
+ return db;
996
+ } catch {
997
+ return null;
998
+ }
999
+ }
985
1000
  function isSqliteAvailable() {
986
1001
  return DatabaseConstructor !== null;
987
1002
  }
@@ -1340,7 +1355,7 @@ var KimiAgent = class extends BaseAgent {
1340
1355
  parseSessionDir(sessionDir) {
1341
1356
  try {
1342
1357
  const sessionId = basename3(sessionDir);
1343
- const projectHash = basename3(dirname2(sessionDir));
1358
+ const projectHash = basename3(dirname3(sessionDir));
1344
1359
  const contextFile = join4(sessionDir, "context.jsonl");
1345
1360
  const wireFile = join4(sessionDir, "wire.jsonl");
1346
1361
  if (!existsSync4(contextFile) && !existsSync4(wireFile)) return null;
@@ -3492,89 +3507,438 @@ registerAgent({
3492
3507
  icon: "/icon/agent/cursor.svg",
3493
3508
  create: () => new CursorAgent()
3494
3509
  });
3495
- var CACHE_VERSION = 2;
3496
- var CACHE_FILENAME = "scan-cache.json";
3510
+ var CACHE_VERSION = 3;
3511
+ var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
3512
+ var CACHE_FILENAME = "codesesh.db";
3513
+ var LEGACY_CACHE_FILENAME = "scan-cache.json";
3514
+ function getCacheDir() {
3515
+ return join7(homedir2(), ".cache", "codesesh");
3516
+ }
3497
3517
  function getCachePath() {
3498
- return join7(homedir2(), ".cache", "codesesh", CACHE_FILENAME);
3518
+ return join7(getCacheDir(), CACHE_FILENAME);
3499
3519
  }
3500
- function ensureCacheDir() {
3501
- const cacheDir = join7(homedir2(), ".cache", "codesesh");
3502
- if (!existsSync7(cacheDir)) {
3503
- mkdirSync(cacheDir, { recursive: true });
3504
- }
3520
+ function getLegacyCachePath() {
3521
+ return join7(getCacheDir(), LEGACY_CACHE_FILENAME);
3505
3522
  }
3506
- function loadCachedSessions(agentName) {
3523
+ function hasCacheStorage() {
3524
+ return existsSync7(getCachePath());
3525
+ }
3526
+ function withCacheDb(fn) {
3527
+ const db = openDb(getCachePath());
3528
+ if (!db) return null;
3507
3529
  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 };
3530
+ ensureSchema(db);
3531
+ return fn(db);
3517
3532
  } catch {
3518
3533
  return null;
3534
+ } finally {
3535
+ db.close();
3519
3536
  }
3520
3537
  }
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 };
3538
+ function ensureSchema(db) {
3539
+ db.exec(`
3540
+ CREATE TABLE IF NOT EXISTS cache_meta (
3541
+ key TEXT PRIMARY KEY,
3542
+ value TEXT NOT NULL
3543
+ );
3544
+
3545
+ CREATE TABLE IF NOT EXISTS agent_cache (
3546
+ agent_name TEXT PRIMARY KEY,
3547
+ timestamp INTEGER NOT NULL
3548
+ );
3549
+
3550
+ CREATE TABLE IF NOT EXISTS cached_sessions (
3551
+ agent_name TEXT NOT NULL,
3552
+ session_id TEXT NOT NULL,
3553
+ session_json TEXT NOT NULL,
3554
+ meta_json TEXT,
3555
+ PRIMARY KEY (agent_name, session_id)
3556
+ );
3557
+
3558
+ CREATE TABLE IF NOT EXISTS session_documents (
3559
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3560
+ agent_name TEXT NOT NULL,
3561
+ session_id TEXT NOT NULL,
3562
+ slug TEXT NOT NULL,
3563
+ title TEXT NOT NULL,
3564
+ directory TEXT NOT NULL,
3565
+ time_created INTEGER NOT NULL,
3566
+ time_updated INTEGER,
3567
+ activity_time INTEGER NOT NULL,
3568
+ content_text TEXT NOT NULL,
3569
+ content_hash TEXT NOT NULL,
3570
+ indexed_at INTEGER NOT NULL,
3571
+ UNIQUE(agent_name, session_id)
3572
+ );
3573
+
3574
+ CREATE VIRTUAL TABLE IF NOT EXISTS session_documents_fts USING fts5(
3575
+ title,
3576
+ content_text,
3577
+ content='session_documents',
3578
+ content_rowid='id'
3579
+ );
3580
+
3581
+ CREATE TRIGGER IF NOT EXISTS session_documents_ai AFTER INSERT ON session_documents BEGIN
3582
+ INSERT INTO session_documents_fts(rowid, title, content_text)
3583
+ VALUES (new.id, new.title, new.content_text);
3584
+ END;
3585
+
3586
+ CREATE TRIGGER IF NOT EXISTS session_documents_ad AFTER DELETE ON session_documents BEGIN
3587
+ INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
3588
+ VALUES ('delete', old.id, old.title, old.content_text);
3589
+ END;
3590
+
3591
+ CREATE TRIGGER IF NOT EXISTS session_documents_au AFTER UPDATE ON session_documents BEGIN
3592
+ INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
3593
+ VALUES ('delete', old.id, old.title, old.content_text);
3594
+ INSERT INTO session_documents_fts(rowid, title, content_text)
3595
+ VALUES (new.id, new.title, new.content_text);
3596
+ END;
3597
+ `);
3598
+ const versionRow = db.prepare("SELECT value FROM cache_meta WHERE key = 'version'").get();
3599
+ const version = Number(versionRow?.value ?? 0);
3600
+ if (version === CACHE_VERSION) {
3601
+ return;
3602
+ }
3603
+ db.exec(`
3604
+ DELETE FROM agent_cache;
3605
+ DELETE FROM cached_sessions;
3606
+ DELETE FROM session_documents;
3607
+ INSERT INTO session_documents_fts(session_documents_fts) VALUES ('rebuild');
3608
+ INSERT INTO cache_meta(key, value)
3609
+ VALUES ('version', '${CACHE_VERSION}')
3610
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value;
3611
+ `);
3612
+ }
3613
+ function sessionContentHash(session) {
3614
+ return JSON.stringify([
3615
+ session.slug,
3616
+ session.title,
3617
+ session.directory,
3618
+ session.time_created,
3619
+ session.time_updated ?? session.time_created,
3620
+ session.stats.message_count,
3621
+ session.stats.total_input_tokens,
3622
+ session.stats.total_output_tokens,
3623
+ session.stats.total_cost,
3624
+ session.stats.total_tokens ?? 0
3625
+ ]);
3626
+ }
3627
+ function escapeFtsTerm(value) {
3628
+ return value.replaceAll('"', '""');
3629
+ }
3630
+ function toFtsQuery(input) {
3631
+ const tokens = input.match(/"[^"]+"|\S+/g) ?? [];
3632
+ return tokens.map((token) => {
3633
+ if (/^OR$/i.test(token)) {
3634
+ return "OR";
3537
3635
  }
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");
3636
+ if (token.startsWith('"') && token.endsWith('"')) {
3637
+ return `"${escapeFtsTerm(token.slice(1, -1))}"`;
3638
+ }
3639
+ return `"${escapeFtsTerm(token)}"`;
3640
+ }).join(" ");
3641
+ }
3642
+ function appendPlainText(value, chunks) {
3643
+ if (value == null) return;
3644
+ if (typeof value === "string") {
3645
+ const normalized = value.trim();
3646
+ if (normalized) {
3647
+ chunks.push(normalized);
3648
+ }
3649
+ return;
3650
+ }
3651
+ if (typeof value === "number" || typeof value === "boolean") {
3652
+ chunks.push(String(value));
3653
+ return;
3654
+ }
3655
+ if (Array.isArray(value)) {
3656
+ for (const item of value) {
3657
+ appendPlainText(item, chunks);
3658
+ }
3659
+ return;
3660
+ }
3661
+ if (typeof value === "object") {
3662
+ for (const nested of Object.values(value)) {
3663
+ appendPlainText(nested, chunks);
3664
+ }
3665
+ }
3666
+ }
3667
+ function buildSessionContent(session) {
3668
+ const chunks = [];
3669
+ appendPlainText(session.title, chunks);
3670
+ for (const message of session.messages) {
3671
+ chunks.push(message.role);
3672
+ appendPlainText(message.agent, chunks);
3673
+ appendPlainText(message.model, chunks);
3674
+ for (const part of message.parts) {
3675
+ appendPlainText(part.type, chunks);
3676
+ appendPlainText(part.title, chunks);
3677
+ appendPlainText(part.nickname, chunks);
3678
+ appendPlainText(part.tool, chunks);
3679
+ appendPlainText(part.text, chunks);
3680
+ appendPlainText(part.input, chunks);
3681
+ appendPlainText(part.output, chunks);
3682
+ appendPlainText(part.state, chunks);
3683
+ }
3684
+ }
3685
+ return chunks.join("\n");
3686
+ }
3687
+ function deleteLegacyCacheFile() {
3688
+ const legacyPath = getLegacyCachePath();
3689
+ if (!existsSync7(legacyPath)) {
3690
+ return;
3691
+ }
3692
+ try {
3693
+ unlinkSync(legacyPath);
3546
3694
  } catch {
3547
3695
  }
3548
3696
  }
3697
+ function loadCachedSessions(agentName) {
3698
+ if (!hasCacheStorage()) {
3699
+ return null;
3700
+ }
3701
+ return withCacheDb((db) => {
3702
+ const timestampRow = db.prepare("SELECT timestamp AS value FROM agent_cache WHERE agent_name = ?").get(agentName);
3703
+ const timestamp = Number(timestampRow?.value ?? 0);
3704
+ if (!timestamp || Date.now() - timestamp > CACHE_TTL) {
3705
+ return null;
3706
+ }
3707
+ const rows = db.prepare(
3708
+ `
3709
+ SELECT session_json, meta_json
3710
+ FROM cached_sessions
3711
+ WHERE agent_name = ?
3712
+ ORDER BY rowid
3713
+ `
3714
+ ).all(agentName);
3715
+ const sessions = [];
3716
+ const meta = {};
3717
+ for (const row of rows) {
3718
+ if (!row.session_json) {
3719
+ continue;
3720
+ }
3721
+ const session = JSON.parse(row.session_json);
3722
+ sessions.push(session);
3723
+ if (row.meta_json) {
3724
+ meta[session.id] = JSON.parse(row.meta_json);
3725
+ }
3726
+ }
3727
+ return { sessions, meta, timestamp };
3728
+ });
3729
+ }
3730
+ function saveCachedSessions(agentName, sessions, meta = {}) {
3731
+ withCacheDb((db) => {
3732
+ const deleteAgent = db.prepare("DELETE FROM agent_cache WHERE agent_name = ?");
3733
+ const deleteSessions = db.prepare("DELETE FROM cached_sessions WHERE agent_name = ?");
3734
+ const upsertAgent = db.prepare(`
3735
+ INSERT INTO agent_cache(agent_name, timestamp)
3736
+ VALUES (?, ?)
3737
+ ON CONFLICT(agent_name) DO UPDATE SET timestamp = excluded.timestamp
3738
+ `);
3739
+ const insertSession = db.prepare(`
3740
+ INSERT INTO cached_sessions(agent_name, session_id, session_json, meta_json)
3741
+ VALUES (?, ?, ?, ?)
3742
+ `);
3743
+ const write = db.transaction(() => {
3744
+ const timestamp = Date.now();
3745
+ deleteAgent.run(agentName);
3746
+ deleteSessions.run(agentName);
3747
+ upsertAgent.run(agentName, timestamp);
3748
+ for (const session of sessions) {
3749
+ insertSession.run(
3750
+ agentName,
3751
+ session.id,
3752
+ JSON.stringify(session),
3753
+ meta[session.id] ? JSON.stringify(meta[session.id]) : null
3754
+ );
3755
+ }
3756
+ });
3757
+ write();
3758
+ deleteLegacyCacheFile();
3759
+ });
3760
+ }
3549
3761
  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");
3762
+ if (!hasCacheStorage()) {
3763
+ deleteLegacyCacheFile();
3764
+ return;
3765
+ }
3766
+ withCacheDb((db) => {
3767
+ db.exec(`
3768
+ DELETE FROM agent_cache;
3769
+ DELETE FROM cached_sessions;
3770
+ `);
3771
+ });
3772
+ deleteLegacyCacheFile();
3773
+ const cachePath = getCachePath();
3774
+ const walPath = `${cachePath}-wal`;
3775
+ const shmPath = `${cachePath}-shm`;
3776
+ for (const filePath of [walPath, shmPath]) {
3777
+ if (!existsSync7(filePath)) {
3778
+ continue;
3779
+ }
3780
+ try {
3781
+ rmSync(filePath, { force: true });
3782
+ } catch {
3559
3783
  }
3560
- } catch {
3561
3784
  }
3562
3785
  }
3563
3786
  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 {
3787
+ if (!hasCacheStorage()) {
3576
3788
  return { lastScanTime: null, size: 0 };
3577
3789
  }
3790
+ const info = withCacheDb((db) => {
3791
+ const timestampRow = db.prepare("SELECT MAX(timestamp) AS value FROM agent_cache").get();
3792
+ const sizeRow = db.prepare("SELECT COUNT(*) AS value FROM cached_sessions").get();
3793
+ const lastScanTime = Number(timestampRow?.value ?? 0) || null;
3794
+ const size = Number(sizeRow?.value ?? 0);
3795
+ return { lastScanTime, size };
3796
+ });
3797
+ return info ?? { lastScanTime: null, size: 0 };
3798
+ }
3799
+ function syncSessionSearchIndex(agentName, sessions, loadSessionData) {
3800
+ if (!hasCacheStorage()) {
3801
+ return;
3802
+ }
3803
+ withCacheDb((db) => {
3804
+ const existingRows = db.prepare(
3805
+ "SELECT session_id, content_hash FROM session_documents WHERE agent_name = ? ORDER BY id"
3806
+ ).all(agentName);
3807
+ const existingMap = new Map(
3808
+ existingRows.map((row) => [String(row.session_id), String(row.content_hash ?? "")])
3809
+ );
3810
+ const sessionMap = new Map(sessions.map((session) => [session.id, session]));
3811
+ const toDelete = existingRows.map((row) => String(row.session_id)).filter((sessionId) => !sessionMap.has(sessionId));
3812
+ const toUpsert = sessions.filter(
3813
+ (session) => existingMap.get(session.id) !== sessionContentHash(session)
3814
+ );
3815
+ const loaded = toUpsert.map((session) => {
3816
+ try {
3817
+ const data = loadSessionData(session.id);
3818
+ return {
3819
+ session,
3820
+ contentText: buildSessionContent(data),
3821
+ contentHash: sessionContentHash(session)
3822
+ };
3823
+ } catch {
3824
+ return null;
3825
+ }
3826
+ }).filter((entry) => entry !== null);
3827
+ const deleteRow = db.prepare(
3828
+ "DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
3829
+ );
3830
+ const upsertRow = db.prepare(`
3831
+ INSERT INTO session_documents(
3832
+ agent_name,
3833
+ session_id,
3834
+ slug,
3835
+ title,
3836
+ directory,
3837
+ time_created,
3838
+ time_updated,
3839
+ activity_time,
3840
+ content_text,
3841
+ content_hash,
3842
+ indexed_at
3843
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3844
+ ON CONFLICT(agent_name, session_id) DO UPDATE SET
3845
+ slug = excluded.slug,
3846
+ title = excluded.title,
3847
+ directory = excluded.directory,
3848
+ time_created = excluded.time_created,
3849
+ time_updated = excluded.time_updated,
3850
+ activity_time = excluded.activity_time,
3851
+ content_text = excluded.content_text,
3852
+ content_hash = excluded.content_hash,
3853
+ indexed_at = excluded.indexed_at
3854
+ `);
3855
+ const write = db.transaction(() => {
3856
+ for (const sessionId of toDelete) {
3857
+ deleteRow.run(agentName, sessionId);
3858
+ }
3859
+ for (const entry of loaded) {
3860
+ const activityTime = entry.session.time_updated ?? entry.session.time_created;
3861
+ upsertRow.run(
3862
+ agentName,
3863
+ entry.session.id,
3864
+ entry.session.slug,
3865
+ entry.session.title,
3866
+ entry.session.directory,
3867
+ entry.session.time_created,
3868
+ entry.session.time_updated ?? null,
3869
+ activityTime,
3870
+ entry.contentText,
3871
+ entry.contentHash,
3872
+ Date.now()
3873
+ );
3874
+ }
3875
+ });
3876
+ write();
3877
+ });
3878
+ }
3879
+ function searchSessions(query, options = {}) {
3880
+ const normalizedQuery = query.trim();
3881
+ if (!normalizedQuery || !hasCacheStorage()) {
3882
+ return [];
3883
+ }
3884
+ const ftsQuery = toFtsQuery(normalizedQuery);
3885
+ const results = withCacheDb((db) => {
3886
+ const rows = db.prepare(
3887
+ `
3888
+ SELECT
3889
+ d.agent_name,
3890
+ d.session_id,
3891
+ d.slug,
3892
+ d.title,
3893
+ d.directory,
3894
+ d.time_created,
3895
+ d.time_updated,
3896
+ COALESCE(
3897
+ NULLIF(snippet(session_documents_fts, 1, '<mark>', '</mark>', ' \u2026 ', 18), ''),
3898
+ highlight(session_documents_fts, 0, '<mark>', '</mark>')
3899
+ ) AS snippet
3900
+ FROM session_documents_fts
3901
+ JOIN session_documents d ON d.id = session_documents_fts.rowid
3902
+ WHERE session_documents_fts MATCH ?
3903
+ AND (? IS NULL OR d.agent_name = ?)
3904
+ AND (? IS NULL OR LOWER(d.directory) LIKE ?)
3905
+ AND (? IS NULL OR d.activity_time >= ?)
3906
+ AND (? IS NULL OR d.activity_time <= ?)
3907
+ ORDER BY bm25(session_documents_fts, 8.0, 1.0), d.activity_time DESC
3908
+ LIMIT ?
3909
+ `
3910
+ ).all(
3911
+ ftsQuery,
3912
+ options.agent ?? null,
3913
+ options.agent ?? null,
3914
+ options.cwd?.toLowerCase() ?? null,
3915
+ options.cwd ? `%${options.cwd.toLowerCase()}%` : null,
3916
+ options.from ?? null,
3917
+ options.from ?? null,
3918
+ options.to ?? null,
3919
+ options.to ?? null,
3920
+ options.limit ?? 50
3921
+ );
3922
+ return rows.map((row) => ({
3923
+ agentName: String(row.agent_name),
3924
+ session: {
3925
+ id: String(row.session_id),
3926
+ slug: String(row.slug),
3927
+ title: String(row.title),
3928
+ directory: String(row.directory),
3929
+ time_created: Number(row.time_created),
3930
+ time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
3931
+ stats: {
3932
+ message_count: 0,
3933
+ total_input_tokens: 0,
3934
+ total_output_tokens: 0,
3935
+ total_cost: 0
3936
+ }
3937
+ },
3938
+ snippet: String(row.snippet ?? "")
3939
+ }));
3940
+ });
3941
+ return results ?? [];
3578
3942
  }
3579
3943
  function isPathScopeMatch(queryPath, sessionPath) {
3580
3944
  if (!sessionPath) return false;
@@ -3728,13 +4092,16 @@ export {
3728
4092
  resolveSessionTitle,
3729
4093
  perf,
3730
4094
  openDbReadOnly,
4095
+ openDb,
3731
4096
  isSqliteAvailable,
3732
4097
  loadCachedSessions,
3733
4098
  saveCachedSessions,
3734
4099
  clearCache,
3735
4100
  getCacheInfo,
4101
+ syncSessionSearchIndex,
4102
+ searchSessions,
3736
4103
  filterSessions,
3737
4104
  scanSessions,
3738
4105
  scanSessionsAsync
3739
4106
  };
3740
- //# sourceMappingURL=chunk-2UNXB2D3.js.map
4107
+ //# sourceMappingURL=chunk-UQI7CTEK.js.map