teleton 0.4.0 → 0.5.1

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 (33) hide show
  1. package/README.md +88 -13
  2. package/dist/BigInteger-DQ33LTTE.js +5 -0
  3. package/dist/chunk-4DU3C27M.js +30 -0
  4. package/dist/chunk-5WWR4CU3.js +124 -0
  5. package/dist/{chunk-7UPH62J2.js → chunk-BYTHCDZA.js} +261 -255
  6. package/dist/{chunk-E2NXSWOS.js → chunk-NUGDTPE4.js} +24 -64
  7. package/dist/{chunk-OA5L7GM6.js → chunk-O4R7V5Y2.js} +37 -5
  8. package/dist/chunk-QUAPFI2N.js +42 -0
  9. package/dist/{chunk-QU4ZOR35.js → chunk-RRB6BWU7.js} +3108 -3345
  10. package/dist/chunk-TSKJCWQQ.js +1263 -0
  11. package/dist/{chunk-B2PRMXOH.js → chunk-WL2Q3VRD.js} +0 -2
  12. package/dist/{chunk-OQGNS2FV.js → chunk-YBA6IBGT.js} +20 -5
  13. package/dist/cli/index.js +39 -172
  14. package/dist/{endpoint-FT2B2RZ2.js → endpoint-FLYNEZ2F.js} +1 -1
  15. package/dist/{get-my-gifts-AFKBG4YQ.js → get-my-gifts-KVULMBJ3.js} +1 -1
  16. package/dist/index.js +12 -12
  17. package/dist/{memory-SYTQ5P7P.js → memory-657W5AS6.js} +4 -5
  18. package/dist/{migrate-ITXMRRSZ.js → migrate-PMB2JVXH.js} +4 -5
  19. package/dist/server-BQY7CM2N.js +1120 -0
  20. package/dist/{task-dependency-resolver-GY6PEBIS.js → task-dependency-resolver-TRPILAHM.js} +2 -2
  21. package/dist/{task-executor-4QKTZZ3P.js → task-executor-N7XNVK5N.js} +1 -1
  22. package/dist/{tasks-M3QDPTGY.js → tasks-QSCWSMPS.js} +1 -1
  23. package/dist/{transcript-DF2Y6CFY.js → transcript-7V4UNID4.js} +1 -1
  24. package/dist/web/assets/index-CDMbujHf.css +1 -0
  25. package/dist/web/assets/index-DDX8oQ2z.js +67 -0
  26. package/dist/web/index.html +16 -0
  27. package/dist/web/logo_dark.png +0 -0
  28. package/package.json +23 -4
  29. package/dist/chunk-67QC5FBN.js +0 -60
  30. package/dist/chunk-A64NPEFL.js +0 -74
  31. package/dist/chunk-DUW5VBAZ.js +0 -133
  32. package/dist/chunk-QBGUCUOW.js +0 -16
  33. package/dist/scraper-SH7GS7TO.js +0 -282
@@ -1,17 +1,17 @@
1
1
  import {
2
- VOYAGE_API_URL,
3
- fetchWithTimeout
4
- } from "./chunk-A64NPEFL.js";
2
+ DEFAULT_FETCH_TIMEOUT_MS
3
+ } from "./chunk-4DU3C27M.js";
5
4
  import {
6
5
  EMBEDDING_CACHE_EVICTION_INTERVAL,
6
+ EMBEDDING_CACHE_EVICTION_RATIO,
7
7
  EMBEDDING_CACHE_MAX_ENTRIES,
8
8
  EMBEDDING_CACHE_TTL_DAYS,
9
- KNOWLEDGE_CHUNK_OVERLAP,
9
+ HYBRID_SEARCH_MIN_SCORE,
10
10
  KNOWLEDGE_CHUNK_SIZE,
11
11
  SQLITE_CACHE_SIZE_KB,
12
12
  SQLITE_MMAP_SIZE,
13
13
  VOYAGE_BATCH_SIZE
14
- } from "./chunk-OA5L7GM6.js";
14
+ } from "./chunk-O4R7V5Y2.js";
15
15
  import {
16
16
  TELETON_ROOT
17
17
  } from "./chunk-EYWNOHMJ.js";
@@ -428,7 +428,7 @@ function setSchemaVersion(db, version) {
428
428
  `
429
429
  ).run(version);
430
430
  }
431
- var CURRENT_SCHEMA_VERSION = "1.9.0";
431
+ var CURRENT_SCHEMA_VERSION = "1.10.1";
432
432
  function runMigrations(db) {
433
433
  const currentVersion = getSchemaVersion(db);
434
434
  if (!currentVersion || versionLessThan(currentVersion, "1.1.0")) {
@@ -535,6 +535,47 @@ function runMigrations(db) {
535
535
  throw error;
536
536
  }
537
537
  }
538
+ if (!currentVersion || versionLessThan(currentVersion, "1.10.0")) {
539
+ console.log("\u{1F504} Running migration 1.10.0: Add tool_config table for runtime tool management");
540
+ try {
541
+ db.exec(`
542
+ CREATE TABLE IF NOT EXISTS tool_config (
543
+ tool_name TEXT PRIMARY KEY,
544
+ enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0, 1)),
545
+ scope TEXT CHECK(scope IN ('always', 'dm-only', 'group-only', 'admin-only')),
546
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
547
+ updated_by INTEGER
548
+ );
549
+ `);
550
+ console.log("\u2705 Migration 1.10.0 complete: tool_config table created");
551
+ } catch (error) {
552
+ console.error("\u274C Migration 1.10.0 failed:", error);
553
+ throw error;
554
+ }
555
+ }
556
+ if (!currentVersion || versionLessThan(currentVersion, "1.10.1")) {
557
+ console.log(
558
+ "\u{1F504} Running migration 1.10.1: Fix tool_config scope CHECK constraint (add admin-only)"
559
+ );
560
+ try {
561
+ db.exec(`
562
+ CREATE TABLE IF NOT EXISTS tool_config_new (
563
+ tool_name TEXT PRIMARY KEY,
564
+ enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0, 1)),
565
+ scope TEXT CHECK(scope IN ('always', 'dm-only', 'group-only', 'admin-only')),
566
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
567
+ updated_by INTEGER
568
+ );
569
+ INSERT OR IGNORE INTO tool_config_new SELECT * FROM tool_config;
570
+ DROP TABLE tool_config;
571
+ ALTER TABLE tool_config_new RENAME TO tool_config;
572
+ `);
573
+ console.log("\u2705 Migration 1.10.1 complete: tool_config CHECK constraint updated");
574
+ } catch (error) {
575
+ console.error("\u274C Migration 1.10.1 failed:", error);
576
+ throw error;
577
+ }
578
+ }
538
579
  setSchemaVersion(db, CURRENT_SCHEMA_VERSION);
539
580
  }
540
581
 
@@ -603,33 +644,18 @@ var MemoryDatabase = class {
603
644
  ensureSchema(this.db);
604
645
  console.log("Migration complete");
605
646
  }
606
- /**
607
- * Get the underlying better-sqlite3 database
608
- */
609
647
  getDb() {
610
648
  return this.db;
611
649
  }
612
- /**
613
- * Check if vector search is available
614
- */
615
650
  isVectorSearchReady() {
616
651
  return this.vectorReady;
617
652
  }
618
- /**
619
- * Get vector dimensions
620
- */
621
653
  getVectorDimensions() {
622
654
  return this.config.vectorDimensions;
623
655
  }
624
- /**
625
- * Execute a function in a transaction
626
- */
627
656
  transaction(fn) {
628
657
  return this.db.transaction(fn)();
629
658
  }
630
- /**
631
- * Execute an async function in a transaction
632
- */
633
659
  async asyncTransaction(fn) {
634
660
  const beginTrans = this.db.prepare("BEGIN");
635
661
  const commitTrans = this.db.prepare("COMMIT");
@@ -644,9 +670,6 @@ var MemoryDatabase = class {
644
670
  throw error;
645
671
  }
646
672
  }
647
- /**
648
- * Get database stats
649
- */
650
673
  getStats() {
651
674
  const knowledge = this.db.prepare(`SELECT COUNT(*) as c FROM knowledge`).get();
652
675
  const sessions = this.db.prepare(`SELECT COUNT(*) as c FROM sessions`).get();
@@ -666,21 +689,15 @@ var MemoryDatabase = class {
666
689
  vectorSearchEnabled: this.vectorReady
667
690
  };
668
691
  }
669
- /**
670
- * Vacuum the database to reclaim space
671
- */
672
692
  vacuum() {
673
693
  this.db.exec("VACUUM");
674
694
  }
675
- /**
676
- * Optimize the database (ANALYZE)
677
- */
678
695
  optimize() {
679
696
  this.db.exec("ANALYZE");
680
697
  }
681
698
  /**
682
- * Rebuild FTS indexes from existing data
683
- * Call this if FTS triggers didn't fire correctly
699
+ * Rebuild FTS indexes from existing data.
700
+ * Call this if FTS triggers didn't fire correctly.
684
701
  */
685
702
  rebuildFtsIndexes() {
686
703
  this.db.exec(`DELETE FROM knowledge_fts`);
@@ -703,9 +720,6 @@ var MemoryDatabase = class {
703
720
  }
704
721
  return { knowledge: knowledgeRows.length, messages: messageRows.length };
705
722
  }
706
- /**
707
- * Close the database connection
708
- */
709
723
  close() {
710
724
  if (this.db.open) {
711
725
  this.db.close();
@@ -745,6 +759,63 @@ var NoopEmbeddingProvider = class {
745
759
  }
746
760
  };
747
761
 
762
+ // src/utils/fetch.ts
763
+ var DEFAULT_TIMEOUT_MS = DEFAULT_FETCH_TIMEOUT_MS;
764
+ function fetchWithTimeout(url, init) {
765
+ const { timeoutMs = DEFAULT_TIMEOUT_MS, ...fetchInit } = init ?? {};
766
+ if (fetchInit.signal) {
767
+ return fetch(url, fetchInit);
768
+ }
769
+ return fetch(url, {
770
+ ...fetchInit,
771
+ signal: AbortSignal.timeout(timeoutMs)
772
+ });
773
+ }
774
+
775
+ // src/constants/api-endpoints.ts
776
+ var TONAPI_BASE_URL = "https://tonapi.io/v2";
777
+ var _tonapiKey;
778
+ function setTonapiKey(key) {
779
+ _tonapiKey = key;
780
+ }
781
+ function tonapiHeaders() {
782
+ const headers = { Accept: "application/json" };
783
+ if (_tonapiKey) {
784
+ headers["Authorization"] = `Bearer ${_tonapiKey}`;
785
+ }
786
+ return headers;
787
+ }
788
+ var TONAPI_MAX_RPS = 5;
789
+ var _tonapiTimestamps = [];
790
+ async function waitForTonapiSlot() {
791
+ const clean = () => {
792
+ const cutoff = Date.now() - 1e3;
793
+ while (_tonapiTimestamps.length > 0 && _tonapiTimestamps[0] <= cutoff) {
794
+ _tonapiTimestamps.shift();
795
+ }
796
+ };
797
+ clean();
798
+ if (_tonapiTimestamps.length >= TONAPI_MAX_RPS) {
799
+ const waitMs = _tonapiTimestamps[0] + 1e3 - Date.now() + 50;
800
+ if (waitMs > 0) await new Promise((r) => setTimeout(r, waitMs));
801
+ clean();
802
+ }
803
+ _tonapiTimestamps.push(Date.now());
804
+ }
805
+ async function tonapiFetch(path, init) {
806
+ await waitForTonapiSlot();
807
+ return fetchWithTimeout(`${TONAPI_BASE_URL}${path}`, {
808
+ ...init,
809
+ headers: { ...tonapiHeaders(), ...init?.headers }
810
+ });
811
+ }
812
+ var STONFI_API_BASE_URL = "https://api.ston.fi/v1";
813
+ var GECKOTERMINAL_API_URL = "https://api.geckoterminal.com/api/v2";
814
+ var COINGECKO_API_URL = "https://api.coingecko.com/api/v3";
815
+ var OPENAI_TTS_URL = "https://api.openai.com/v1/audio/speech";
816
+ var ELEVENLABS_TTS_URL = "https://api.elevenlabs.io/v1/text-to-speech";
817
+ var VOYAGE_API_URL = "https://api.voyageai.com/v1";
818
+
748
819
  // src/memory/embeddings/anthropic.ts
749
820
  var AnthropicEmbeddingProvider = class {
750
821
  id = "anthropic";
@@ -813,6 +884,10 @@ function getExtractor(model) {
813
884
  }).then((ext) => {
814
885
  console.log(`\u2705 Local embedding model ready`);
815
886
  return ext;
887
+ }).catch((err) => {
888
+ console.error(`\u274C Failed to load embedding model: ${err.message}`);
889
+ extractorPromise = null;
890
+ throw err;
816
891
  });
817
892
  }
818
893
  return extractorPromise;
@@ -945,7 +1020,7 @@ var CachedEmbeddingProvider = class {
945
1020
  this.db.prepare(`DELETE FROM embedding_cache WHERE accessed_at < ?`).run(cutoff);
946
1021
  const count = this.db.prepare(`SELECT COUNT(*) as cnt FROM embedding_cache`).get().cnt;
947
1022
  if (count > EMBEDDING_CACHE_MAX_ENTRIES) {
948
- const toDelete = Math.ceil(count * 0.1);
1023
+ const toDelete = Math.ceil(count * EMBEDDING_CACHE_EVICTION_RATIO);
949
1024
  this.db.prepare(
950
1025
  `DELETE FROM embedding_cache WHERE (hash, model, provider) IN (
951
1026
  SELECT hash, model, provider FROM embedding_cache ORDER BY accessed_at ASC LIMIT ?
@@ -1008,9 +1083,6 @@ var KnowledgeIndexer = class {
1008
1083
  this.embedder = embedder;
1009
1084
  this.vectorEnabled = vectorEnabled;
1010
1085
  }
1011
- /**
1012
- * Index all memory files
1013
- */
1014
1086
  async indexAll() {
1015
1087
  const files = this.listMemoryFiles();
1016
1088
  let indexed = 0;
@@ -1025,9 +1097,6 @@ var KnowledgeIndexer = class {
1025
1097
  }
1026
1098
  return { indexed, skipped };
1027
1099
  }
1028
- /**
1029
- * Index a single file
1030
- */
1031
1100
  async indexFile(absPath) {
1032
1101
  if (!existsSync3(absPath) || !absPath.endsWith(".md")) {
1033
1102
  return false;
@@ -1039,43 +1108,42 @@ var KnowledgeIndexer = class {
1039
1108
  if (existing?.hash === fileHash) {
1040
1109
  return false;
1041
1110
  }
1042
- if (this.vectorEnabled) {
1043
- this.db.prepare(
1044
- `DELETE FROM knowledge_vec WHERE id IN (
1045
- SELECT id FROM knowledge WHERE path = ? AND source = 'memory'
1046
- )`
1047
- ).run(relPath);
1048
- }
1049
- this.db.prepare(`DELETE FROM knowledge WHERE path = ? AND source = 'memory'`).run(relPath);
1050
1111
  const chunks = this.chunkMarkdown(content, relPath);
1051
1112
  const texts = chunks.map((c) => c.text);
1052
1113
  const embeddings = await this.embedder.embedBatch(texts);
1053
- const insert = this.db.prepare(`
1054
- INSERT INTO knowledge (id, source, path, text, embedding, start_line, end_line, hash)
1055
- VALUES (?, 'memory', ?, ?, ?, ?, ?, ?)
1056
- `);
1057
- const insertVec = this.vectorEnabled ? this.db.prepare(`INSERT INTO knowledge_vec (id, embedding) VALUES (?, ?)`) : null;
1058
- for (let i = 0; i < chunks.length; i++) {
1059
- const chunk = chunks[i];
1060
- const embedding = embeddings[i] ?? [];
1061
- insert.run(
1062
- chunk.id,
1063
- chunk.path,
1064
- chunk.text,
1065
- serializeEmbedding(embedding),
1066
- chunk.startLine,
1067
- chunk.endLine,
1068
- chunk.hash
1069
- );
1070
- if (insertVec && embedding.length > 0) {
1071
- insertVec.run(chunk.id, serializeEmbedding(embedding));
1114
+ this.db.transaction(() => {
1115
+ if (this.vectorEnabled) {
1116
+ this.db.prepare(
1117
+ `DELETE FROM knowledge_vec WHERE id IN (
1118
+ SELECT id FROM knowledge WHERE path = ? AND source = 'memory'
1119
+ )`
1120
+ ).run(relPath);
1072
1121
  }
1073
- }
1122
+ this.db.prepare(`DELETE FROM knowledge WHERE path = ? AND source = 'memory'`).run(relPath);
1123
+ const insert = this.db.prepare(`
1124
+ INSERT INTO knowledge (id, source, path, text, embedding, start_line, end_line, hash)
1125
+ VALUES (?, 'memory', ?, ?, ?, ?, ?, ?)
1126
+ `);
1127
+ const insertVec = this.vectorEnabled ? this.db.prepare(`INSERT INTO knowledge_vec (id, embedding) VALUES (?, ?)`) : null;
1128
+ for (let i = 0; i < chunks.length; i++) {
1129
+ const chunk = chunks[i];
1130
+ const embedding = embeddings[i] ?? [];
1131
+ insert.run(
1132
+ chunk.id,
1133
+ chunk.path,
1134
+ chunk.text,
1135
+ serializeEmbedding(embedding),
1136
+ chunk.startLine,
1137
+ chunk.endLine,
1138
+ chunk.hash
1139
+ );
1140
+ if (insertVec && embedding.length > 0) {
1141
+ insertVec.run(chunk.id, serializeEmbedding(embedding));
1142
+ }
1143
+ }
1144
+ })();
1074
1145
  return true;
1075
1146
  }
1076
- /**
1077
- * List all memory files
1078
- */
1079
1147
  listMemoryFiles() {
1080
1148
  const files = [];
1081
1149
  const memoryMd = join2(this.workspaceDir, "MEMORY.md");
@@ -1095,49 +1163,58 @@ var KnowledgeIndexer = class {
1095
1163
  return files;
1096
1164
  }
1097
1165
  /**
1098
- * Chunk markdown content
1166
+ * Chunk markdown content with structure awareness.
1167
+ * Respects heading boundaries, code blocks, and list groups.
1168
+ * Target: KNOWLEDGE_CHUNK_SIZE chars, hard max: 2x target.
1099
1169
  */
1100
1170
  chunkMarkdown(content, path) {
1101
1171
  const lines = content.split("\n");
1102
1172
  const chunks = [];
1103
- const chunkSize = KNOWLEDGE_CHUNK_SIZE;
1104
- const overlap = KNOWLEDGE_CHUNK_OVERLAP;
1173
+ const targetSize = KNOWLEDGE_CHUNK_SIZE;
1174
+ const hardMax = targetSize * 2;
1105
1175
  let currentChunk = "";
1106
1176
  let startLine = 1;
1107
1177
  let currentLine = 1;
1178
+ let inCodeBlock = false;
1179
+ const flushChunk = () => {
1180
+ const text = currentChunk.trim();
1181
+ if (text.length > 0) {
1182
+ chunks.push({
1183
+ id: hashText(`${path}:${startLine}:${currentLine - 1}`),
1184
+ source: "memory",
1185
+ path,
1186
+ text,
1187
+ startLine,
1188
+ endLine: currentLine - 1,
1189
+ hash: hashText(text)
1190
+ });
1191
+ }
1192
+ currentChunk = "";
1193
+ startLine = currentLine;
1194
+ };
1108
1195
  for (const line of lines) {
1109
- currentChunk += line + "\n";
1110
- if (currentChunk.length >= chunkSize) {
1111
- const text2 = currentChunk.trim();
1112
- if (text2.length > 0) {
1113
- chunks.push({
1114
- id: hashText(`${path}:${startLine}:${currentLine}`),
1115
- source: "memory",
1116
- path,
1117
- text: text2,
1118
- startLine,
1119
- endLine: currentLine,
1120
- hash: hashText(text2)
1121
- });
1196
+ if (line.trimStart().startsWith("```")) {
1197
+ inCodeBlock = !inCodeBlock;
1198
+ }
1199
+ if (!inCodeBlock && currentChunk.length >= targetSize) {
1200
+ const isHeading = /^#{1,6}\s/.test(line);
1201
+ const isBlankLine = line.trim() === "";
1202
+ const isHorizontalRule = /^(-{3,}|\*{3,}|_{3,})\s*$/.test(line.trim());
1203
+ if (isHeading) {
1204
+ flushChunk();
1205
+ } else if ((isBlankLine || isHorizontalRule) && currentChunk.length >= targetSize) {
1206
+ currentChunk += line + "\n";
1207
+ currentLine++;
1208
+ flushChunk();
1209
+ continue;
1210
+ } else if (currentChunk.length >= hardMax) {
1211
+ flushChunk();
1122
1212
  }
1123
- const overlapText = currentChunk.slice(-overlap);
1124
- currentChunk = overlapText;
1125
- startLine = currentLine + 1;
1126
1213
  }
1214
+ currentChunk += line + "\n";
1127
1215
  currentLine++;
1128
1216
  }
1129
- const text = currentChunk.trim();
1130
- if (text.length > 0) {
1131
- chunks.push({
1132
- id: hashText(`${path}:${startLine}:${currentLine}`),
1133
- source: "memory",
1134
- path,
1135
- text,
1136
- startLine,
1137
- endLine: currentLine,
1138
- hash: hashText(text)
1139
- });
1140
- }
1217
+ flushChunk();
1141
1218
  return chunks;
1142
1219
  }
1143
1220
  };
@@ -1150,9 +1227,6 @@ var SessionStore = class {
1150
1227
  this.embedder = embedder;
1151
1228
  this.vectorEnabled = vectorEnabled;
1152
1229
  }
1153
- /**
1154
- * Create a new session
1155
- */
1156
1230
  createSession(chatId) {
1157
1231
  const id = randomUUID();
1158
1232
  const now = Math.floor(Date.now() / 1e3);
@@ -1170,9 +1244,6 @@ var SessionStore = class {
1170
1244
  tokensUsed: 0
1171
1245
  };
1172
1246
  }
1173
- /**
1174
- * End a session with summary
1175
- */
1176
1247
  endSession(sessionId, summary, tokensUsed = 0) {
1177
1248
  const now = Math.floor(Date.now() / 1e3);
1178
1249
  this.db.prepare(
@@ -1183,9 +1254,6 @@ var SessionStore = class {
1183
1254
  `
1184
1255
  ).run(now, summary, tokensUsed, sessionId);
1185
1256
  }
1186
- /**
1187
- * Update message count for a session
1188
- */
1189
1257
  incrementMessageCount(sessionId, count = 1) {
1190
1258
  this.db.prepare(
1191
1259
  `
@@ -1195,9 +1263,6 @@ var SessionStore = class {
1195
1263
  `
1196
1264
  ).run(count, sessionId);
1197
1265
  }
1198
- /**
1199
- * Get a session by ID
1200
- */
1201
1266
  getSession(id) {
1202
1267
  const row = this.db.prepare(`SELECT * FROM sessions WHERE id = ?`).get(id);
1203
1268
  if (!row) return void 0;
@@ -1206,14 +1271,11 @@ var SessionStore = class {
1206
1271
  chatId: row.chat_id,
1207
1272
  startedAt: new Date(row.started_at * 1e3),
1208
1273
  endedAt: row.ended_at ? new Date(row.ended_at * 1e3) : void 0,
1209
- summary: row.summary,
1274
+ summary: row.summary ?? void 0,
1210
1275
  messageCount: row.message_count,
1211
1276
  tokensUsed: row.tokens_used
1212
1277
  };
1213
1278
  }
1214
- /**
1215
- * Get active (not ended) sessions
1216
- */
1217
1279
  getActiveSessions() {
1218
1280
  const rows = this.db.prepare(
1219
1281
  `
@@ -1227,14 +1289,11 @@ var SessionStore = class {
1227
1289
  chatId: row.chat_id,
1228
1290
  startedAt: new Date(row.started_at * 1e3),
1229
1291
  endedAt: void 0,
1230
- summary: row.summary,
1292
+ summary: row.summary ?? void 0,
1231
1293
  messageCount: row.message_count,
1232
1294
  tokensUsed: row.tokens_used
1233
1295
  }));
1234
1296
  }
1235
- /**
1236
- * Get sessions for a specific chat
1237
- */
1238
1297
  getSessionsByChat(chatId, limit = 50) {
1239
1298
  const rows = this.db.prepare(
1240
1299
  `
@@ -1249,14 +1308,14 @@ var SessionStore = class {
1249
1308
  chatId: row.chat_id,
1250
1309
  startedAt: new Date(row.started_at * 1e3),
1251
1310
  endedAt: row.ended_at ? new Date(row.ended_at * 1e3) : void 0,
1252
- summary: row.summary,
1311
+ summary: row.summary ?? void 0,
1253
1312
  messageCount: row.message_count,
1254
1313
  tokensUsed: row.tokens_used
1255
1314
  }));
1256
1315
  }
1257
1316
  /**
1258
- * Index a session for search (after ending)
1259
- * This creates a knowledge entry from the session summary
1317
+ * Index a session for search after ending.
1318
+ * Creates a knowledge entry from the session summary for future retrieval.
1260
1319
  */
1261
1320
  async indexSession(sessionId) {
1262
1321
  const session = this.getSession(sessionId);
@@ -1291,9 +1350,6 @@ ${session.summary}`;
1291
1350
  console.error("Error indexing session:", error);
1292
1351
  }
1293
1352
  }
1294
- /**
1295
- * Delete a session
1296
- */
1297
1353
  deleteSession(sessionId) {
1298
1354
  const knowledgeId = `session:${sessionId}`;
1299
1355
  if (this.vectorEnabled) {
@@ -1311,18 +1367,12 @@ var MessageStore = class {
1311
1367
  this.embedder = embedder;
1312
1368
  this.vectorEnabled = vectorEnabled;
1313
1369
  }
1314
- /**
1315
- * Ensure chat exists in database
1316
- */
1317
1370
  ensureChat(chatId, isGroup = false) {
1318
1371
  const existing = this.db.prepare(`SELECT id FROM tg_chats WHERE id = ?`).get(chatId);
1319
1372
  if (!existing) {
1320
1373
  this.db.prepare(`INSERT INTO tg_chats (id, type, is_monitored) VALUES (?, ?, 1)`).run(chatId, isGroup ? "group" : "dm");
1321
1374
  }
1322
1375
  }
1323
- /**
1324
- * Ensure user exists in database
1325
- */
1326
1376
  ensureUser(userId) {
1327
1377
  if (!userId) return;
1328
1378
  const existing = this.db.prepare(`SELECT id FROM tg_users WHERE id = ?`).get(userId);
@@ -1330,9 +1380,6 @@ var MessageStore = class {
1330
1380
  this.db.prepare(`INSERT INTO tg_users (id) VALUES (?)`).run(userId);
1331
1381
  }
1332
1382
  }
1333
- /**
1334
- * Store a message
1335
- */
1336
1383
  async storeMessage(message) {
1337
1384
  this.ensureChat(message.chatId);
1338
1385
  if (message.senderId) {
@@ -1340,34 +1387,33 @@ var MessageStore = class {
1340
1387
  }
1341
1388
  const embedding = this.vectorEnabled && message.text ? await this.embedder.embedQuery(message.text) : [];
1342
1389
  const embeddingBuffer = serializeEmbedding(embedding);
1343
- this.db.prepare(
1390
+ this.db.transaction(() => {
1391
+ this.db.prepare(
1392
+ `
1393
+ INSERT OR REPLACE INTO tg_messages (
1394
+ id, chat_id, sender_id, text, embedding, reply_to_id,
1395
+ is_from_agent, has_media, media_type, timestamp
1396
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1344
1397
  `
1345
- INSERT OR REPLACE INTO tg_messages (
1346
- id, chat_id, sender_id, text, embedding, reply_to_id,
1347
- is_from_agent, has_media, media_type, timestamp
1348
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1349
- `
1350
- ).run(
1351
- message.id,
1352
- message.chatId,
1353
- message.senderId,
1354
- message.text,
1355
- embeddingBuffer,
1356
- message.replyToId,
1357
- message.isFromAgent ? 1 : 0,
1358
- message.hasMedia ? 1 : 0,
1359
- message.mediaType,
1360
- message.timestamp
1361
- );
1362
- if (this.vectorEnabled && embedding.length > 0 && message.text) {
1363
- this.db.prepare(`DELETE FROM tg_messages_vec WHERE id = ?`).run(message.id);
1364
- this.db.prepare(`INSERT INTO tg_messages_vec (id, embedding) VALUES (?, ?)`).run(message.id, embeddingBuffer);
1365
- }
1366
- this.db.prepare(`UPDATE tg_chats SET last_message_at = ?, last_message_id = ? WHERE id = ?`).run(message.timestamp, message.id, message.chatId);
1398
+ ).run(
1399
+ message.id,
1400
+ message.chatId,
1401
+ message.senderId,
1402
+ message.text,
1403
+ embeddingBuffer,
1404
+ message.replyToId,
1405
+ message.isFromAgent ? 1 : 0,
1406
+ message.hasMedia ? 1 : 0,
1407
+ message.mediaType,
1408
+ message.timestamp
1409
+ );
1410
+ if (this.vectorEnabled && embedding.length > 0 && message.text) {
1411
+ this.db.prepare(`DELETE FROM tg_messages_vec WHERE id = ?`).run(message.id);
1412
+ this.db.prepare(`INSERT INTO tg_messages_vec (id, embedding) VALUES (?, ?)`).run(message.id, embeddingBuffer);
1413
+ }
1414
+ this.db.prepare(`UPDATE tg_chats SET last_message_at = ?, last_message_id = ? WHERE id = ?`).run(message.timestamp, message.id, message.chatId);
1415
+ })();
1367
1416
  }
1368
- /**
1369
- * Get recent messages from a chat
1370
- */
1371
1417
  getRecentMessages(chatId, limit = 20) {
1372
1418
  const rows = this.db.prepare(
1373
1419
  `
@@ -1397,9 +1443,6 @@ var ChatStore = class {
1397
1443
  constructor(db) {
1398
1444
  this.db = db;
1399
1445
  }
1400
- /**
1401
- * Create or update a chat
1402
- */
1403
1446
  upsertChat(chat) {
1404
1447
  const now = Math.floor(Date.now() / 1e3);
1405
1448
  this.db.prepare(
@@ -1431,9 +1474,6 @@ var ChatStore = class {
1431
1474
  now
1432
1475
  );
1433
1476
  }
1434
- /**
1435
- * Get a chat by ID
1436
- */
1437
1477
  getChat(id) {
1438
1478
  const row = this.db.prepare(
1439
1479
  `
@@ -1444,20 +1484,17 @@ var ChatStore = class {
1444
1484
  return {
1445
1485
  id: row.id,
1446
1486
  type: row.type,
1447
- title: row.title,
1448
- username: row.username,
1449
- memberCount: row.member_count,
1487
+ title: row.title ?? void 0,
1488
+ username: row.username ?? void 0,
1489
+ memberCount: row.member_count ?? void 0,
1450
1490
  isMonitored: Boolean(row.is_monitored),
1451
1491
  isArchived: Boolean(row.is_archived),
1452
- lastMessageId: row.last_message_id,
1492
+ lastMessageId: row.last_message_id ?? void 0,
1453
1493
  lastMessageAt: row.last_message_at ? new Date(row.last_message_at * 1e3) : void 0,
1454
1494
  createdAt: new Date(row.created_at * 1e3),
1455
1495
  updatedAt: new Date(row.updated_at * 1e3)
1456
1496
  };
1457
1497
  }
1458
- /**
1459
- * Get active (monitored, non-archived) chats
1460
- */
1461
1498
  getActiveChats(limit = 50) {
1462
1499
  const rows = this.db.prepare(
1463
1500
  `
@@ -1470,20 +1507,17 @@ var ChatStore = class {
1470
1507
  return rows.map((row) => ({
1471
1508
  id: row.id,
1472
1509
  type: row.type,
1473
- title: row.title,
1474
- username: row.username,
1475
- memberCount: row.member_count,
1510
+ title: row.title ?? void 0,
1511
+ username: row.username ?? void 0,
1512
+ memberCount: row.member_count ?? void 0,
1476
1513
  isMonitored: Boolean(row.is_monitored),
1477
1514
  isArchived: Boolean(row.is_archived),
1478
- lastMessageId: row.last_message_id,
1515
+ lastMessageId: row.last_message_id ?? void 0,
1479
1516
  lastMessageAt: row.last_message_at ? new Date(row.last_message_at * 1e3) : void 0,
1480
1517
  createdAt: new Date(row.created_at * 1e3),
1481
1518
  updatedAt: new Date(row.updated_at * 1e3)
1482
1519
  }));
1483
1520
  }
1484
- /**
1485
- * Update last message info
1486
- */
1487
1521
  updateLastMessage(chatId, messageId, timestamp) {
1488
1522
  this.db.prepare(
1489
1523
  `
@@ -1493,9 +1527,6 @@ var ChatStore = class {
1493
1527
  `
1494
1528
  ).run(messageId, Math.floor(timestamp.getTime() / 1e3), chatId);
1495
1529
  }
1496
- /**
1497
- * Archive a chat
1498
- */
1499
1530
  archiveChat(chatId) {
1500
1531
  this.db.prepare(
1501
1532
  `
@@ -1505,9 +1536,6 @@ var ChatStore = class {
1505
1536
  `
1506
1537
  ).run(chatId);
1507
1538
  }
1508
- /**
1509
- * Unarchive a chat
1510
- */
1511
1539
  unarchiveChat(chatId) {
1512
1540
  this.db.prepare(
1513
1541
  `
@@ -1517,9 +1545,6 @@ var ChatStore = class {
1517
1545
  `
1518
1546
  ).run(chatId);
1519
1547
  }
1520
- /**
1521
- * Set monitoring status
1522
- */
1523
1548
  setMonitored(chatId, monitored) {
1524
1549
  this.db.prepare(
1525
1550
  `
@@ -1536,9 +1561,6 @@ var UserStore = class {
1536
1561
  constructor(db) {
1537
1562
  this.db = db;
1538
1563
  }
1539
- /**
1540
- * Create or update a user
1541
- */
1542
1564
  upsertUser(user) {
1543
1565
  const now = Math.floor(Date.now() / 1e3);
1544
1566
  const existing = this.db.prepare(`SELECT id FROM tg_users WHERE id = ?`).get(user.id);
@@ -1577,17 +1599,14 @@ var UserStore = class {
1577
1599
  );
1578
1600
  }
1579
1601
  }
1580
- /**
1581
- * Get a user by ID
1582
- */
1583
1602
  getUser(id) {
1584
1603
  const row = this.db.prepare(`SELECT * FROM tg_users WHERE id = ?`).get(id);
1585
1604
  if (!row) return void 0;
1586
1605
  return {
1587
1606
  id: row.id,
1588
- username: row.username,
1589
- firstName: row.first_name,
1590
- lastName: row.last_name,
1607
+ username: row.username ?? void 0,
1608
+ firstName: row.first_name ?? void 0,
1609
+ lastName: row.last_name ?? void 0,
1591
1610
  isBot: Boolean(row.is_bot),
1592
1611
  isAdmin: Boolean(row.is_admin),
1593
1612
  isAllowed: Boolean(row.is_allowed),
@@ -1596,17 +1615,14 @@ var UserStore = class {
1596
1615
  messageCount: row.message_count
1597
1616
  };
1598
1617
  }
1599
- /**
1600
- * Get a user by username
1601
- */
1602
1618
  getUserByUsername(username) {
1603
1619
  const row = this.db.prepare(`SELECT * FROM tg_users WHERE username = ?`).get(username.replace("@", ""));
1604
1620
  if (!row) return void 0;
1605
1621
  return {
1606
1622
  id: row.id,
1607
- username: row.username,
1608
- firstName: row.first_name,
1609
- lastName: row.last_name,
1623
+ username: row.username ?? void 0,
1624
+ firstName: row.first_name ?? void 0,
1625
+ lastName: row.last_name ?? void 0,
1610
1626
  isBot: Boolean(row.is_bot),
1611
1627
  isAdmin: Boolean(row.is_admin),
1612
1628
  isAllowed: Boolean(row.is_allowed),
@@ -1615,9 +1631,6 @@ var UserStore = class {
1615
1631
  messageCount: row.message_count
1616
1632
  };
1617
1633
  }
1618
- /**
1619
- * Update last seen timestamp
1620
- */
1621
1634
  updateLastSeen(userId) {
1622
1635
  this.db.prepare(
1623
1636
  `
@@ -1627,9 +1640,6 @@ var UserStore = class {
1627
1640
  `
1628
1641
  ).run(userId);
1629
1642
  }
1630
- /**
1631
- * Increment message count
1632
- */
1633
1643
  incrementMessageCount(userId) {
1634
1644
  this.db.prepare(
1635
1645
  `
@@ -1639,9 +1649,6 @@ var UserStore = class {
1639
1649
  `
1640
1650
  ).run(userId);
1641
1651
  }
1642
- /**
1643
- * Set admin status
1644
- */
1645
1652
  setAdmin(userId, isAdmin) {
1646
1653
  this.db.prepare(
1647
1654
  `
@@ -1651,9 +1658,6 @@ var UserStore = class {
1651
1658
  `
1652
1659
  ).run(isAdmin ? 1 : 0, userId);
1653
1660
  }
1654
- /**
1655
- * Set allowed status
1656
- */
1657
1661
  setAllowed(userId, isAllowed) {
1658
1662
  this.db.prepare(
1659
1663
  `
@@ -1663,9 +1667,6 @@ var UserStore = class {
1663
1667
  `
1664
1668
  ).run(isAllowed ? 1 : 0, userId);
1665
1669
  }
1666
- /**
1667
- * Get all admins
1668
- */
1669
1670
  getAdmins() {
1670
1671
  const rows = this.db.prepare(
1671
1672
  `
@@ -1676,9 +1677,9 @@ var UserStore = class {
1676
1677
  ).all();
1677
1678
  return rows.map((row) => ({
1678
1679
  id: row.id,
1679
- username: row.username,
1680
- firstName: row.first_name,
1681
- lastName: row.last_name,
1680
+ username: row.username ?? void 0,
1681
+ firstName: row.first_name ?? void 0,
1682
+ lastName: row.last_name ?? void 0,
1682
1683
  isBot: Boolean(row.is_bot),
1683
1684
  isAdmin: Boolean(row.is_admin),
1684
1685
  isAllowed: Boolean(row.is_allowed),
@@ -1687,9 +1688,6 @@ var UserStore = class {
1687
1688
  messageCount: row.message_count
1688
1689
  }));
1689
1690
  }
1690
- /**
1691
- * Get recently active users
1692
- */
1693
1691
  getRecentUsers(limit = 50) {
1694
1692
  const rows = this.db.prepare(
1695
1693
  `
@@ -1700,9 +1698,9 @@ var UserStore = class {
1700
1698
  ).all(limit);
1701
1699
  return rows.map((row) => ({
1702
1700
  id: row.id,
1703
- username: row.username,
1704
- firstName: row.first_name,
1705
- lastName: row.last_name,
1701
+ username: row.username ?? void 0,
1702
+ firstName: row.first_name ?? void 0,
1703
+ lastName: row.last_name ?? void 0,
1706
1704
  isBot: Boolean(row.is_bot),
1707
1705
  isAdmin: Boolean(row.is_admin),
1708
1706
  isAllowed: Boolean(row.is_allowed),
@@ -1722,30 +1720,20 @@ var HybridSearch = class {
1722
1720
  this.db = db;
1723
1721
  this.vectorEnabled = vectorEnabled;
1724
1722
  }
1725
- /**
1726
- * Search in knowledge base
1727
- */
1728
1723
  async searchKnowledge(query, queryEmbedding, options = {}) {
1729
1724
  const limit = options.limit ?? 10;
1730
- const vectorWeight = options.vectorWeight ?? 0.7;
1731
- const keywordWeight = options.keywordWeight ?? 0.3;
1732
- const vectorResults = this.vectorEnabled ? this.vectorSearchKnowledge(queryEmbedding, Math.ceil(limit * 1.5)) : [];
1733
- const keywordResults = this.keywordSearchKnowledge(query, Math.ceil(limit * 1.5));
1725
+ const vectorWeight = options.vectorWeight ?? 0.5;
1726
+ const keywordWeight = options.keywordWeight ?? 0.5;
1727
+ const vectorResults = this.vectorEnabled ? this.vectorSearchKnowledge(queryEmbedding, Math.ceil(limit * 3)) : [];
1728
+ const keywordResults = this.keywordSearchKnowledge(query, Math.ceil(limit * 3));
1734
1729
  return this.mergeResults(vectorResults, keywordResults, vectorWeight, keywordWeight, limit);
1735
1730
  }
1736
- /**
1737
- * Search in Telegram messages
1738
- */
1739
1731
  async searchMessages(query, queryEmbedding, options = {}) {
1740
1732
  const limit = options.limit ?? 10;
1741
- const vectorWeight = options.vectorWeight ?? 0.7;
1742
- const keywordWeight = options.keywordWeight ?? 0.3;
1743
- const vectorResults = this.vectorEnabled ? this.vectorSearchMessages(queryEmbedding, Math.ceil(limit * 1.5), options.chatId) : [];
1744
- const keywordResults = this.keywordSearchMessages(
1745
- query,
1746
- Math.ceil(limit * 1.5),
1747
- options.chatId
1748
- );
1733
+ const vectorWeight = options.vectorWeight ?? 0.5;
1734
+ const keywordWeight = options.keywordWeight ?? 0.5;
1735
+ const vectorResults = this.vectorEnabled ? this.vectorSearchMessages(queryEmbedding, Math.ceil(limit * 3), options.chatId) : [];
1736
+ const keywordResults = this.keywordSearchMessages(query, Math.ceil(limit * 3), options.chatId);
1749
1737
  return this.mergeResults(vectorResults, keywordResults, vectorWeight, keywordWeight, limit);
1750
1738
  }
1751
1739
  vectorSearchKnowledge(embedding, limit) {
@@ -1877,10 +1865,14 @@ var HybridSearch = class {
1877
1865
  byId.set(r.id, { ...r, score: keywordWeight * (r.keywordScore ?? 0) });
1878
1866
  }
1879
1867
  }
1880
- return Array.from(byId.values()).sort((a, b) => b.score - a.score).slice(0, limit);
1868
+ return Array.from(byId.values()).filter((r) => r.score >= HYBRID_SEARCH_MIN_SCORE).sort((a, b) => b.score - a.score).slice(0, limit);
1881
1869
  }
1870
+ /**
1871
+ * Convert BM25 rank to normalized score.
1872
+ * FTS5 rank is negative; more negative = better match.
1873
+ */
1882
1874
  bm25ToScore(rank) {
1883
- return 1 / (1 + Math.abs(rank));
1875
+ return 1 / (1 + Math.exp(rank));
1884
1876
  }
1885
1877
  };
1886
1878
 
@@ -1921,6 +1913,9 @@ var ContextBuilder = class {
1921
1913
  console.warn("Knowledge search failed:", error);
1922
1914
  }
1923
1915
  }
1916
+ const recentTextsSet = new Set(
1917
+ recentTgMessages.filter((m) => m.text && m.text.length > 0).map((m) => m.text)
1918
+ );
1924
1919
  const relevantFeed = [];
1925
1920
  if (includeFeedHistory) {
1926
1921
  try {
@@ -1928,10 +1923,13 @@ var ContextBuilder = class {
1928
1923
  chatId,
1929
1924
  limit: maxRelevantChunks
1930
1925
  });
1931
- relevantFeed.push(...feedResults.map((r) => r.text));
1926
+ for (const r of feedResults) {
1927
+ if (!recentTextsSet.has(r.text)) {
1928
+ relevantFeed.push(r.text);
1929
+ }
1930
+ }
1932
1931
  if (searchAllChats) {
1933
1932
  const globalResults = await this.hybridSearch.searchMessages(query, queryEmbedding, {
1934
- // No chatId = search all chats
1935
1933
  limit: maxRelevantChunks
1936
1934
  });
1937
1935
  const existingTexts = new Set(relevantFeed);
@@ -1995,6 +1993,14 @@ export {
1995
1993
  getDatabase,
1996
1994
  closeDatabase,
1997
1995
  NoopEmbeddingProvider,
1996
+ fetchWithTimeout,
1997
+ setTonapiKey,
1998
+ tonapiFetch,
1999
+ STONFI_API_BASE_URL,
2000
+ GECKOTERMINAL_API_URL,
2001
+ COINGECKO_API_URL,
2002
+ OPENAI_TTS_URL,
2003
+ ELEVENLABS_TTS_URL,
1998
2004
  AnthropicEmbeddingProvider,
1999
2005
  LocalEmbeddingProvider,
2000
2006
  CachedEmbeddingProvider,