teleton 0.8.0 → 0.8.2

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 (44) hide show
  1. package/README.md +28 -11
  2. package/dist/{chunk-H36RFKRI.js → chunk-2IZU3REP.js} +572 -174
  3. package/dist/chunk-3UFPFWYP.js +12 -0
  4. package/dist/{chunk-NUGDTPE4.js → chunk-4L66JHQE.js} +2 -1
  5. package/dist/{chunk-TVRZJIZX.js → chunk-55SKE6YH.js} +4 -4
  6. package/dist/{setup-server-QXED3D2L.js → chunk-57URFK6M.js} +161 -210
  7. package/dist/chunk-5SEMA47R.js +75 -0
  8. package/dist/{chunk-JHYZYFZJ.js → chunk-7YKSXOQQ.js} +17 -2
  9. package/dist/{chunk-IJBWWQE4.js → chunk-C4NKJT2Z.js} +12 -0
  10. package/dist/{chunk-RQBAMUCV.js → chunk-GGXJLMOH.js} +1451 -743
  11. package/dist/{chunk-WIKM24GZ.js → chunk-H7MFXJZK.js} +7 -2
  12. package/dist/{chunk-U56QTM46.js → chunk-HEDJCLA6.js} +85 -44
  13. package/dist/{chunk-QVBSUYVX.js → chunk-J73TA3UM.js} +17 -9
  14. package/dist/{chunk-P36I6OIV.js → chunk-LC4TV3KL.js} +13 -2
  15. package/dist/{chunk-RCMD3U65.js → chunk-NQ6FZKCE.js} +13 -0
  16. package/dist/{chunk-SD4NLLYG.js → chunk-VYKW7FMV.js} +224 -93
  17. package/dist/chunk-W25Z7CM6.js +487 -0
  18. package/dist/{chunk-OJCLKU5Z.js → chunk-WFTC3JJW.js} +16 -0
  19. package/dist/{server-H3QA252W.js → chunk-XBSCYMKM.js} +369 -374
  20. package/dist/{chunk-PHSAHTK4.js → chunk-YOSUPUAJ.js} +75 -7
  21. package/dist/cli/index.js +67 -22
  22. package/dist/{client-LNZTDQSA.js → client-YOOHI776.js} +4 -4
  23. package/dist/{get-my-gifts-OMGKOEPM.js → get-my-gifts-Y7EN7RK4.js} +3 -3
  24. package/dist/index.js +15 -14
  25. package/dist/{memory-AS7WKGTW.js → memory-Q6EWGK2S.js} +7 -5
  26. package/dist/memory-hook-WUXJNVT5.js +18 -0
  27. package/dist/{migrate-POHWYEIW.js → migrate-WFU6COBN.js} +5 -5
  28. package/dist/server-GYZXKIKU.js +787 -0
  29. package/dist/server-YODFBZKG.js +392 -0
  30. package/dist/setup-server-IZBUOJRU.js +215 -0
  31. package/dist/{store-GAFULOOX.js → store-7M4XV6M5.js} +6 -6
  32. package/dist/{task-dependency-resolver-3FIKQ7Z6.js → task-dependency-resolver-L6UUMTHK.js} +3 -3
  33. package/dist/{task-executor-RUTFG6VG.js → task-executor-XBNJLUCS.js} +3 -3
  34. package/dist/{tasks-BEZ4QRI2.js → tasks-WQIKXDX5.js} +1 -1
  35. package/dist/{tool-adapter-IH5VGBOO.js → tool-adapter-IVX2XQJE.js} +1 -1
  36. package/dist/{tool-index-H3SHOJC3.js → tool-index-NYH57UWP.js} +9 -6
  37. package/dist/{transcript-IMNE6KU3.js → transcript-IM7G25OS.js} +2 -2
  38. package/dist/web/assets/index-BfYCdwLI.js +80 -0
  39. package/dist/web/assets/{index-BrVqauzj.css → index-DmlyQVhR.css} +1 -1
  40. package/dist/web/assets/{index.es-DkU1GvWU.js → index.es-DitvF-9H.js} +1 -1
  41. package/dist/web/index.html +2 -2
  42. package/package.json +14 -5
  43. package/dist/chunk-XBE4JB7C.js +0 -8
  44. package/dist/web/assets/index-DYeEkvJ6.js +0 -72
@@ -3,20 +3,24 @@ import {
3
3
  createEmbeddingProvider,
4
4
  hashText,
5
5
  serializeEmbedding
6
- } from "./chunk-U56QTM46.js";
6
+ } from "./chunk-HEDJCLA6.js";
7
7
  import {
8
8
  FEED_MESSAGE_MAX_CHARS,
9
9
  HYBRID_SEARCH_MIN_SCORE,
10
10
  KNOWLEDGE_CHUNK_SIZE,
11
+ RECENCY_DECAY_FACTOR,
12
+ RECENCY_WEIGHT,
13
+ SECONDS_PER_DAY,
14
+ SECONDS_PER_HOUR,
11
15
  SQLITE_CACHE_SIZE_KB,
12
16
  SQLITE_MMAP_SIZE
13
- } from "./chunk-IJBWWQE4.js";
17
+ } from "./chunk-C4NKJT2Z.js";
14
18
  import {
15
19
  TELETON_ROOT
16
20
  } from "./chunk-EYWNOHMJ.js";
17
21
  import {
18
22
  createLogger
19
- } from "./chunk-RCMD3U65.js";
23
+ } from "./chunk-NQ6FZKCE.js";
20
24
 
21
25
  // src/memory/database.ts
22
26
  import Database2 from "better-sqlite3";
@@ -133,11 +137,7 @@ function migrateFromMainDb(moduleDb, tables) {
133
137
  `INSERT OR IGNORE INTO ${table} (${cols}) SELECT ${cols} FROM main_db.${table}`
134
138
  );
135
139
  totalMigrated += src.c;
136
- try {
137
- moduleDb.exec(`DROP TABLE main_db.${table}`);
138
- } catch {
139
- }
140
- log.info(`Migrated ${src.c} rows from memory.db \u2192 ${table} (source dropped)`);
140
+ log.info(`Migrated ${src.c} rows from memory.db \u2192 ${table}`);
141
141
  } catch (e) {
142
142
  log.warn({ err: e }, `Could not migrate table ${table}`);
143
143
  }
@@ -427,6 +427,26 @@ function ensureSchema(db) {
427
427
  CREATE INDEX IF NOT EXISTS idx_exec_audit_timestamp ON exec_audit(timestamp DESC);
428
428
  CREATE INDEX IF NOT EXISTS idx_exec_audit_user ON exec_audit(user_id);
429
429
 
430
+ -- =====================================================
431
+ -- PLUGIN CONFIG (Plugin Priority Order)
432
+ -- =====================================================
433
+
434
+ CREATE TABLE IF NOT EXISTS plugin_config (
435
+ plugin_name TEXT PRIMARY KEY,
436
+ priority INTEGER NOT NULL DEFAULT 0,
437
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
438
+ );
439
+
440
+ -- =====================================================
441
+ -- USER HOOK CONFIG (Keyword Blocklist + Context Triggers)
442
+ -- =====================================================
443
+
444
+ CREATE TABLE IF NOT EXISTS user_hook_config (
445
+ key TEXT PRIMARY KEY,
446
+ value TEXT NOT NULL,
447
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
448
+ );
449
+
430
450
  -- =====================================================
431
451
  -- JOURNAL (Trading & Business Operations)
432
452
  -- =====================================================
@@ -440,9 +460,11 @@ function ensureVectorTables(db, dimensions) {
440
460
  WHERE type='table' AND name='knowledge_vec'
441
461
  `
442
462
  ).get();
463
+ let dimensionsChanged = false;
443
464
  if (existingDims?.sql && !existingDims.sql.includes(`[${dimensions}]`)) {
444
465
  db.exec(`DROP TABLE IF EXISTS knowledge_vec`);
445
466
  db.exec(`DROP TABLE IF EXISTS tg_messages_vec`);
467
+ dimensionsChanged = true;
446
468
  }
447
469
  db.exec(`
448
470
  CREATE VIRTUAL TABLE IF NOT EXISTS knowledge_vec USING vec0(
@@ -455,6 +477,7 @@ function ensureVectorTables(db, dimensions) {
455
477
  embedding FLOAT[${dimensions}] distance_metric=cosine
456
478
  );
457
479
  `);
480
+ return dimensionsChanged;
458
481
  }
459
482
  function getSchemaVersion(db) {
460
483
  const row = db.prepare(`SELECT value FROM meta WHERE key = 'schema_version'`).get();
@@ -469,7 +492,7 @@ function setSchemaVersion(db, version) {
469
492
  `
470
493
  ).run(version);
471
494
  }
472
- var CURRENT_SCHEMA_VERSION = "1.13.0";
495
+ var CURRENT_SCHEMA_VERSION = "1.15.0";
473
496
  function runMigrations(db) {
474
497
  const currentVersion = getSchemaVersion(db);
475
498
  if (!currentVersion || versionLessThan(currentVersion, "1.1.0")) {
@@ -523,7 +546,7 @@ function runMigrations(db) {
523
546
  try {
524
547
  db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
525
548
  } catch (e) {
526
- if (!e.message.includes("duplicate column name")) {
549
+ if (!(e instanceof Error) || !e.message.includes("duplicate column name")) {
527
550
  throw e;
528
551
  }
529
552
  }
@@ -692,7 +715,7 @@ function runMigrations(db) {
692
715
  try {
693
716
  db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
694
717
  } catch (e) {
695
- if (!e.message.includes("duplicate column name")) {
718
+ if (!(e instanceof Error) || !e.message.includes("duplicate column name")) {
696
719
  throw e;
697
720
  }
698
721
  }
@@ -705,6 +728,38 @@ function runMigrations(db) {
705
728
  throw error;
706
729
  }
707
730
  }
731
+ if (!currentVersion || versionLessThan(currentVersion, "1.14.0")) {
732
+ log2.info("Running migration 1.14.0: Add plugin_config table for plugin priority");
733
+ try {
734
+ db.exec(`
735
+ CREATE TABLE IF NOT EXISTS plugin_config (
736
+ plugin_name TEXT PRIMARY KEY,
737
+ priority INTEGER NOT NULL DEFAULT 0,
738
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
739
+ );
740
+ `);
741
+ log2.info("Migration 1.14.0 complete: plugin_config table created");
742
+ } catch (error) {
743
+ log2.error({ err: error }, "Migration 1.14.0 failed");
744
+ throw error;
745
+ }
746
+ }
747
+ if (!currentVersion || versionLessThan(currentVersion, "1.15.0")) {
748
+ log2.info("Running migration 1.15.0: Add user_hook_config table");
749
+ try {
750
+ db.exec(`
751
+ CREATE TABLE IF NOT EXISTS user_hook_config (
752
+ key TEXT PRIMARY KEY,
753
+ value TEXT NOT NULL,
754
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
755
+ );
756
+ `);
757
+ log2.info("Migration 1.15.0 complete: user_hook_config table created");
758
+ } catch (error) {
759
+ log2.error({ err: error }, "Migration 1.15.0 failed");
760
+ throw error;
761
+ }
762
+ }
708
763
  setSchemaVersion(db, CURRENT_SCHEMA_VERSION);
709
764
  }
710
765
 
@@ -714,6 +769,7 @@ var MemoryDatabase = class {
714
769
  db;
715
770
  config;
716
771
  vectorReady = false;
772
+ _dimensionsChanged = false;
717
773
  constructor(config) {
718
774
  this.config = config;
719
775
  const dir = dirname2(config.path);
@@ -725,7 +781,8 @@ var MemoryDatabase = class {
725
781
  });
726
782
  try {
727
783
  chmodSync2(config.path, 384);
728
- } catch {
784
+ } catch (err) {
785
+ log3.warn({ err, path: config.path }, "Failed to set DB file permissions to 0o600");
729
786
  }
730
787
  this.db.pragma("journal_mode = WAL");
731
788
  this.db.pragma("synchronous = NORMAL");
@@ -739,7 +796,8 @@ var MemoryDatabase = class {
739
796
  let currentVersion = null;
740
797
  try {
741
798
  currentVersion = getSchemaVersion(this.db);
742
- } catch {
799
+ } catch (err) {
800
+ log3.warn({ err }, "Could not read schema version, assuming fresh database");
743
801
  currentVersion = null;
744
802
  }
745
803
  if (!currentVersion) {
@@ -758,7 +816,7 @@ var MemoryDatabase = class {
758
816
  sqliteVec.load(this.db);
759
817
  this.db.prepare("SELECT vec_version() as vec_version").get();
760
818
  const dims = this.config.vectorDimensions ?? 512;
761
- ensureVectorTables(this.db, dims);
819
+ this._dimensionsChanged = ensureVectorTables(this.db, dims);
762
820
  this.vectorReady = true;
763
821
  } catch (error) {
764
822
  log3.warn(`sqlite-vec not available, vector search disabled: ${error.message}`);
@@ -778,6 +836,9 @@ var MemoryDatabase = class {
778
836
  isVectorSearchReady() {
779
837
  return this.vectorReady;
780
838
  }
839
+ didDimensionsChange() {
840
+ return this._dimensionsChanged;
841
+ }
781
842
  getVectorDimensions() {
782
843
  return this.config.vectorDimensions;
783
844
  }
@@ -799,21 +860,24 @@ var MemoryDatabase = class {
799
860
  }
800
861
  }
801
862
  getStats() {
802
- const knowledge = this.db.prepare(`SELECT COUNT(*) as c FROM knowledge`).get();
803
- const sessions = this.db.prepare(`SELECT COUNT(*) as c FROM sessions`).get();
804
- const tasks = this.db.prepare(`SELECT COUNT(*) as c FROM tasks`).get();
805
- const tgChats = this.db.prepare(`SELECT COUNT(*) as c FROM tg_chats`).get();
806
- const tgUsers = this.db.prepare(`SELECT COUNT(*) as c FROM tg_users`).get();
807
- const tgMessages = this.db.prepare(`SELECT COUNT(*) as c FROM tg_messages`).get();
808
- const embeddingCache = this.db.prepare(`SELECT COUNT(*) as c FROM embedding_cache`).get();
863
+ const counts = this.db.prepare(
864
+ `SELECT
865
+ (SELECT COUNT(*) FROM knowledge) as knowledge,
866
+ (SELECT COUNT(*) FROM sessions) as sessions,
867
+ (SELECT COUNT(*) FROM tasks) as tasks,
868
+ (SELECT COUNT(*) FROM tg_chats) as tg_chats,
869
+ (SELECT COUNT(*) FROM tg_users) as tg_users,
870
+ (SELECT COUNT(*) FROM tg_messages) as tg_messages,
871
+ (SELECT COUNT(*) FROM embedding_cache) as embedding_cache`
872
+ ).get();
809
873
  return {
810
- knowledge: knowledge.c,
811
- sessions: sessions.c,
812
- tasks: tasks.c,
813
- tgChats: tgChats.c,
814
- tgUsers: tgUsers.c,
815
- tgMessages: tgMessages.c,
816
- embeddingCache: embeddingCache.c,
874
+ knowledge: counts.knowledge,
875
+ sessions: counts.sessions,
876
+ tasks: counts.tasks,
877
+ tgChats: counts.tg_chats,
878
+ tgUsers: counts.tg_users,
879
+ tgMessages: counts.tg_messages,
880
+ embeddingCache: counts.embedding_cache,
817
881
  vectorSearchEnabled: this.vectorReady
818
882
  };
819
883
  }
@@ -881,12 +945,12 @@ var KnowledgeIndexer = class {
881
945
  this.embedder = embedder;
882
946
  this.vectorEnabled = vectorEnabled;
883
947
  }
884
- async indexAll() {
948
+ async indexAll(options) {
885
949
  const files = this.listMemoryFiles();
886
950
  let indexed = 0;
887
951
  let skipped = 0;
888
952
  for (const file of files) {
889
- const wasIndexed = await this.indexFile(file);
953
+ const wasIndexed = await this.indexFile(file, options?.force);
890
954
  if (wasIndexed) {
891
955
  indexed++;
892
956
  } else {
@@ -895,16 +959,18 @@ var KnowledgeIndexer = class {
895
959
  }
896
960
  return { indexed, skipped };
897
961
  }
898
- async indexFile(absPath) {
962
+ async indexFile(absPath, force) {
899
963
  if (!existsSync3(absPath) || !absPath.endsWith(".md")) {
900
964
  return false;
901
965
  }
902
966
  const content = readFileSync(absPath, "utf-8");
903
967
  const relPath = absPath.replace(this.workspaceDir + "/", "");
904
968
  const fileHash = hashText(content);
905
- const existing = this.db.prepare(`SELECT hash FROM knowledge WHERE path = ? AND source = 'memory' LIMIT 1`).get(relPath);
906
- if (existing?.hash === fileHash) {
907
- return false;
969
+ if (!force) {
970
+ const existing = this.db.prepare(`SELECT hash FROM knowledge WHERE path = ? AND source = 'memory' LIMIT 1`).get(relPath);
971
+ if (existing?.hash === fileHash) {
972
+ return false;
973
+ }
908
974
  }
909
975
  const chunks = this.chunkMarkdown(content, relPath);
910
976
  const texts = chunks.map((c) => c.text);
@@ -933,7 +999,7 @@ var KnowledgeIndexer = class {
933
999
  serializeEmbedding(embedding),
934
1000
  chunk.startLine,
935
1001
  chunk.endLine,
936
- chunk.hash
1002
+ fileHash
937
1003
  );
938
1004
  if (insertVec && embedding.length > 0) {
939
1005
  insertVec.run(chunk.id, serializeEmbedding(embedding));
@@ -974,6 +1040,7 @@ var KnowledgeIndexer = class {
974
1040
  let startLine = 1;
975
1041
  let currentLine = 1;
976
1042
  let inCodeBlock = false;
1043
+ let overlapPrefix = "";
977
1044
  const flushChunk = () => {
978
1045
  const text = currentChunk.trim();
979
1046
  if (text.length > 0) {
@@ -986,8 +1053,10 @@ var KnowledgeIndexer = class {
986
1053
  endLine: currentLine - 1,
987
1054
  hash: hashText(text)
988
1055
  });
1056
+ const nonEmpty = text.split("\n").filter((l) => l.trim());
1057
+ overlapPrefix = nonEmpty.length > 0 ? nonEmpty.slice(-2).join("\n") + "\n" : "";
989
1058
  }
990
- currentChunk = "";
1059
+ currentChunk = overlapPrefix;
991
1060
  startLine = currentLine;
992
1061
  };
993
1062
  for (const line of lines) {
@@ -1511,6 +1580,33 @@ var UserStore = class {
1511
1580
 
1512
1581
  // src/memory/search/hybrid.ts
1513
1582
  var log5 = createLogger("Memory");
1583
+ var UNIT_SECONDS = {
1584
+ hour: SECONDS_PER_HOUR,
1585
+ day: SECONDS_PER_DAY,
1586
+ week: 7 * SECONDS_PER_DAY,
1587
+ month: 30 * SECONDS_PER_DAY
1588
+ };
1589
+ function parseTemporalIntent(query) {
1590
+ const now = Math.floor(Date.now() / 1e3);
1591
+ const lower = query.toLowerCase();
1592
+ const agoMatch = lower.match(/(\d+)\s*(day|hour|week|month)s?\s*ago/);
1593
+ if (agoMatch) {
1594
+ const n = parseInt(agoMatch[1], 10);
1595
+ return { afterTimestamp: now - n * (UNIT_SECONDS[agoMatch[2]] ?? SECONDS_PER_DAY) };
1596
+ }
1597
+ const lastNMatch = lower.match(/last\s+(\d+)\s*(day|hour|week|month)s?/);
1598
+ if (lastNMatch) {
1599
+ const n = parseInt(lastNMatch[1], 10);
1600
+ return { afterTimestamp: now - n * (UNIT_SECONDS[lastNMatch[2]] ?? SECONDS_PER_DAY) };
1601
+ }
1602
+ if (/\btoday\b/.test(lower)) return { afterTimestamp: now - SECONDS_PER_DAY };
1603
+ if (/\byesterday\b/.test(lower)) return { afterTimestamp: now - 2 * SECONDS_PER_DAY };
1604
+ if (/\blast\s+week\b/.test(lower)) return { afterTimestamp: now - 7 * SECONDS_PER_DAY };
1605
+ if (/\bthis\s+week\b/.test(lower)) return { afterTimestamp: now - 7 * SECONDS_PER_DAY };
1606
+ if (/\blast\s+month\b/.test(lower)) return { afterTimestamp: now - 30 * SECONDS_PER_DAY };
1607
+ if (/\brecently?\b/.test(lower)) return { afterTimestamp: now - 3 * SECONDS_PER_DAY };
1608
+ return {};
1609
+ }
1514
1610
  function escapeFts5Query(query) {
1515
1611
  return query.replace(/["\*\-\+\(\)\:\^\~\?\.\@\#\$\%\&\!\[\]\{\}\|\\\/<>=,;'`]/g, " ").replace(/\s+/g, " ").trim();
1516
1612
  }
@@ -1531,8 +1627,18 @@ var HybridSearch = class {
1531
1627
  const limit = options.limit ?? 10;
1532
1628
  const vectorWeight = options.vectorWeight ?? 0.5;
1533
1629
  const keywordWeight = options.keywordWeight ?? 0.5;
1534
- const vectorResults = this.vectorEnabled ? this.vectorSearchMessages(queryEmbedding, Math.ceil(limit * 3), options.chatId) : [];
1535
- const keywordResults = this.keywordSearchMessages(query, Math.ceil(limit * 3), options.chatId);
1630
+ const vectorResults = this.vectorEnabled ? this.vectorSearchMessages(
1631
+ queryEmbedding,
1632
+ Math.ceil(limit * 3),
1633
+ options.chatId,
1634
+ options.afterTimestamp
1635
+ ) : [];
1636
+ const keywordResults = this.keywordSearchMessages(
1637
+ query,
1638
+ Math.ceil(limit * 3),
1639
+ options.chatId,
1640
+ options.afterTimestamp
1641
+ );
1536
1642
  return this.mergeResults(vectorResults, keywordResults, vectorWeight, keywordWeight, limit);
1537
1643
  }
1538
1644
  vectorSearchKnowledge(embedding, limit) {
@@ -1541,7 +1647,7 @@ var HybridSearch = class {
1541
1647
  const embeddingBuffer = serializeEmbedding(embedding);
1542
1648
  const rows = this.db.prepare(
1543
1649
  `
1544
- SELECT kv.id, k.text, k.source, kv.distance
1650
+ SELECT kv.id, k.text, k.source, kv.distance, k.created_at
1545
1651
  FROM (
1546
1652
  SELECT id, distance
1547
1653
  FROM knowledge_vec
@@ -1555,7 +1661,8 @@ var HybridSearch = class {
1555
1661
  text: row.text,
1556
1662
  source: row.source,
1557
1663
  score: 1 - row.distance,
1558
- vectorScore: 1 - row.distance
1664
+ vectorScore: 1 - row.distance,
1665
+ createdAt: row.created_at ?? void 0
1559
1666
  }));
1560
1667
  } catch (error) {
1561
1668
  log5.error({ err: error }, "Vector search error (knowledge)");
@@ -1568,7 +1675,7 @@ var HybridSearch = class {
1568
1675
  try {
1569
1676
  const rows = this.db.prepare(
1570
1677
  `
1571
- SELECT k.id, k.text, k.source, rank as score
1678
+ SELECT k.id, k.text, k.source, rank as score, k.created_at
1572
1679
  FROM knowledge_fts kf
1573
1680
  JOIN knowledge k ON k.rowid = kf.rowid
1574
1681
  WHERE knowledge_fts MATCH ?
@@ -1578,72 +1685,82 @@ var HybridSearch = class {
1578
1685
  ).all(safeQuery, limit);
1579
1686
  return rows.map((row) => ({
1580
1687
  ...row,
1581
- keywordScore: this.bm25ToScore(row.score)
1688
+ keywordScore: this.bm25ToScore(row.score),
1689
+ createdAt: row.created_at ?? void 0
1582
1690
  }));
1583
1691
  } catch (error) {
1584
1692
  log5.error({ err: error }, "FTS5 search error (knowledge)");
1585
1693
  return [];
1586
1694
  }
1587
1695
  }
1588
- vectorSearchMessages(embedding, limit, chatId) {
1696
+ vectorSearchMessages(embedding, limit, chatId, afterTimestamp) {
1589
1697
  if (!this.vectorEnabled || embedding.length === 0) return [];
1590
1698
  try {
1591
1699
  const embeddingBuffer = serializeEmbedding(embedding);
1592
- const sql = chatId ? `
1593
- SELECT mv.id, m.text, m.chat_id as source, mv.distance
1594
- FROM (
1595
- SELECT id, distance
1596
- FROM tg_messages_vec
1597
- WHERE embedding MATCH ? AND k = ?
1598
- ) mv
1599
- JOIN tg_messages m ON m.id = mv.id
1600
- WHERE m.chat_id = ?
1601
- ` : `
1602
- SELECT mv.id, m.text, m.chat_id as source, mv.distance
1700
+ const conditions = [];
1701
+ const params = [embeddingBuffer, limit];
1702
+ if (chatId) {
1703
+ conditions.push("m.chat_id = ?");
1704
+ params.push(chatId);
1705
+ }
1706
+ if (afterTimestamp) {
1707
+ conditions.push("m.timestamp >= ?");
1708
+ params.push(afterTimestamp);
1709
+ }
1710
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1711
+ const sql = `
1712
+ SELECT mv.id, m.text, m.chat_id as source, mv.distance, m.timestamp
1603
1713
  FROM (
1604
1714
  SELECT id, distance
1605
1715
  FROM tg_messages_vec
1606
1716
  WHERE embedding MATCH ? AND k = ?
1607
1717
  ) mv
1608
1718
  JOIN tg_messages m ON m.id = mv.id
1719
+ ${whereClause}
1609
1720
  `;
1610
- const rows = chatId ? this.db.prepare(sql).all(embeddingBuffer, limit, chatId) : this.db.prepare(sql).all(embeddingBuffer, limit);
1721
+ const rows = this.db.prepare(sql).all(...params);
1611
1722
  return rows.map((row) => ({
1612
1723
  id: row.id,
1613
1724
  text: row.text ?? "",
1614
1725
  source: row.source,
1615
1726
  score: 1 - row.distance,
1616
- vectorScore: 1 - row.distance
1727
+ vectorScore: 1 - row.distance,
1728
+ createdAt: row.timestamp ?? void 0
1617
1729
  }));
1618
1730
  } catch (error) {
1619
1731
  log5.error({ err: error }, "Vector search error (messages)");
1620
1732
  return [];
1621
1733
  }
1622
1734
  }
1623
- keywordSearchMessages(query, limit, chatId) {
1735
+ keywordSearchMessages(query, limit, chatId, afterTimestamp) {
1624
1736
  const safeQuery = escapeFts5Query(query);
1625
1737
  if (!safeQuery) return [];
1626
1738
  try {
1627
- const sql = chatId ? `
1628
- SELECT m.id, m.text, m.chat_id as source, rank as score
1629
- FROM tg_messages_fts mf
1630
- JOIN tg_messages m ON m.rowid = mf.rowid
1631
- WHERE tg_messages_fts MATCH ? AND m.chat_id = ?
1632
- ORDER BY rank
1633
- LIMIT ?
1634
- ` : `
1635
- SELECT m.id, m.text, m.chat_id as source, rank as score
1739
+ const conditions = ["tg_messages_fts MATCH ?"];
1740
+ const params = [safeQuery];
1741
+ if (chatId) {
1742
+ conditions.push("m.chat_id = ?");
1743
+ params.push(chatId);
1744
+ }
1745
+ if (afterTimestamp) {
1746
+ conditions.push("m.timestamp >= ?");
1747
+ params.push(afterTimestamp);
1748
+ }
1749
+ params.push(limit);
1750
+ const sql = `
1751
+ SELECT m.id, m.text, m.chat_id as source, rank as score, m.timestamp
1636
1752
  FROM tg_messages_fts mf
1637
1753
  JOIN tg_messages m ON m.rowid = mf.rowid
1638
- WHERE tg_messages_fts MATCH ?
1754
+ WHERE ${conditions.join(" AND ")}
1639
1755
  ORDER BY rank
1640
1756
  LIMIT ?
1641
1757
  `;
1642
- const rows = chatId ? this.db.prepare(sql).all(safeQuery, chatId, limit) : this.db.prepare(sql).all(safeQuery, limit);
1758
+ const rows = this.db.prepare(sql).all(...params);
1643
1759
  return rows.map((row) => ({
1644
1760
  ...row,
1645
1761
  text: row.text ?? "",
1646
- keywordScore: this.bm25ToScore(row.score)
1762
+ keywordScore: this.bm25ToScore(row.score),
1763
+ createdAt: row.timestamp ?? void 0
1647
1764
  }));
1648
1765
  } catch (error) {
1649
1766
  log5.error({ err: error }, "FTS5 search error (messages)");
@@ -1664,7 +1781,16 @@ var HybridSearch = class {
1664
1781
  byId.set(r.id, { ...r, score: keywordWeight * (r.keywordScore ?? 0) });
1665
1782
  }
1666
1783
  }
1667
- return Array.from(byId.values()).filter((r) => r.score >= HYBRID_SEARCH_MIN_SCORE).sort((a, b) => b.score - a.score).slice(0, limit);
1784
+ const now = Math.floor(Date.now() / 1e3);
1785
+ const results = Array.from(byId.values());
1786
+ for (const r of results) {
1787
+ if (r.createdAt) {
1788
+ const ageDays = Math.max(0, (now - r.createdAt) / SECONDS_PER_DAY);
1789
+ const boost = 1 / (1 + ageDays * RECENCY_DECAY_FACTOR);
1790
+ r.score *= 1 - RECENCY_WEIGHT + RECENCY_WEIGHT * boost;
1791
+ }
1792
+ }
1793
+ return results.filter((r) => r.score >= HYBRID_SEARCH_MIN_SCORE).sort((a, b) => b.score - a.score).slice(0, limit);
1668
1794
  }
1669
1795
  /**
1670
1796
  * Convert BM25 rank to normalized score.
@@ -1720,33 +1846,37 @@ var ContextBuilder = class {
1720
1846
  role: m.isFromAgent ? "assistant" : "user",
1721
1847
  content: m.text ?? ""
1722
1848
  }));
1723
- const relevantKnowledge = [];
1724
- if (includeAgentMemory) {
1725
- try {
1726
- const knowledgeResults = await this.hybridSearch.searchKnowledge(query, queryEmbedding, {
1727
- limit: maxRelevantChunks
1728
- });
1729
- relevantKnowledge.push(...reorderForEdges(knowledgeResults.map((r) => r.text)));
1730
- } catch (error) {
1731
- log6.warn({ err: error }, "Knowledge search failed");
1732
- }
1733
- }
1734
1849
  const recentTextsSet = new Set(
1850
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- filtered for non-null text above
1735
1851
  recentTgMessages.filter((m) => m.text && m.text.length > 0).map((m) => m.text)
1736
1852
  );
1853
+ const knowledgePromise = includeAgentMemory ? this.hybridSearch.searchKnowledge(query, queryEmbedding, { limit: maxRelevantChunks }).catch((error) => {
1854
+ log6.warn({ err: error }, "Knowledge search failed");
1855
+ return [];
1856
+ }) : Promise.resolve([]);
1857
+ const { afterTimestamp } = parseTemporalIntent(query);
1858
+ const feedPromise = includeFeedHistory ? this.hybridSearch.searchMessages(query, queryEmbedding, {
1859
+ chatId,
1860
+ limit: maxRelevantChunks,
1861
+ afterTimestamp
1862
+ }).catch((error) => {
1863
+ log6.warn({ err: error }, "Feed search failed");
1864
+ return [];
1865
+ }) : Promise.resolve([]);
1866
+ const [knowledgeResults, feedResults] = await Promise.all([knowledgePromise, feedPromise]);
1867
+ const relevantKnowledge = [];
1868
+ if (knowledgeResults.length > 0) {
1869
+ relevantKnowledge.push(...reorderForEdges(knowledgeResults.map((r) => r.text)));
1870
+ }
1737
1871
  const relevantFeed = [];
1738
1872
  if (includeFeedHistory) {
1739
- try {
1740
- const feedResults = await this.hybridSearch.searchMessages(query, queryEmbedding, {
1741
- chatId,
1742
- limit: maxRelevantChunks
1743
- });
1744
- for (const r of feedResults) {
1745
- if (!recentTextsSet.has(r.text)) {
1746
- relevantFeed.push(truncateFeedMessage(r.text));
1747
- }
1873
+ for (const r of feedResults) {
1874
+ if (!recentTextsSet.has(r.text)) {
1875
+ relevantFeed.push(truncateFeedMessage(r.text));
1748
1876
  }
1749
- if (searchAllChats) {
1877
+ }
1878
+ if (searchAllChats) {
1879
+ try {
1750
1880
  const globalResults = await this.hybridSearch.searchMessages(query, queryEmbedding, {
1751
1881
  limit: maxRelevantChunks
1752
1882
  });
@@ -1757,9 +1887,9 @@ var ContextBuilder = class {
1757
1887
  relevantFeed.push(`[From chat ${r.source}]: ${truncated}`);
1758
1888
  }
1759
1889
  }
1890
+ } catch (error) {
1891
+ log6.warn({ err: error }, "Global feed search failed");
1760
1892
  }
1761
- } catch (error) {
1762
- log6.warn({ err: error }, "Feed search failed");
1763
1893
  }
1764
1894
  if (relevantFeed.length === 0 && recentTgMessages.length > 0) {
1765
1895
  const recentTexts = recentTgMessages.filter((m) => m.text && m.text.length > 0).slice(-maxRelevantChunks).map((m) => {
@@ -1816,6 +1946,7 @@ export {
1816
1946
  MessageStore,
1817
1947
  ChatStore,
1818
1948
  UserStore,
1949
+ parseTemporalIntent,
1819
1950
  HybridSearch,
1820
1951
  ContextBuilder,
1821
1952
  initializeMemory