teleton 0.4.0 → 0.5.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 (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-E2NXSWOS.js → chunk-NUGDTPE4.js} +24 -64
  6. package/dist/{chunk-OA5L7GM6.js → chunk-O4R7V5Y2.js} +37 -5
  7. package/dist/chunk-QUAPFI2N.js +42 -0
  8. package/dist/chunk-TSKJCWQQ.js +1263 -0
  9. package/dist/{chunk-B2PRMXOH.js → chunk-WL2Q3VRD.js} +0 -2
  10. package/dist/{chunk-QU4ZOR35.js → chunk-WOXBZOQX.js} +3179 -3368
  11. package/dist/{chunk-7UPH62J2.js → chunk-WUTMT6DW.js} +293 -261
  12. package/dist/{chunk-OQGNS2FV.js → chunk-YBA6IBGT.js} +20 -5
  13. package/dist/cli/index.js +41 -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-Y5J7CXAR.js} +4 -5
  18. package/dist/{migrate-ITXMRRSZ.js → migrate-UEQCDWL2.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 -6
  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";
@@ -803,16 +874,22 @@ var AnthropicEmbeddingProvider = class {
803
874
  };
804
875
 
805
876
  // src/memory/embeddings/local.ts
806
- import { pipeline } from "@huggingface/transformers";
877
+ import { pipeline, env } from "@huggingface/transformers";
878
+ import { join as join2 } from "path";
879
+ env.cacheDir = join2(TELETON_ROOT, "models");
807
880
  var extractorPromise = null;
808
881
  function getExtractor(model) {
809
882
  if (!extractorPromise) {
810
- console.log(`\u{1F4E6} Loading local embedding model: ${model} \u2026`);
883
+ console.log(`\u{1F4E6} Loading local embedding model: ${model} (cache: ${env.cacheDir})`);
811
884
  extractorPromise = pipeline("feature-extraction", model, {
812
885
  dtype: "fp32"
813
886
  }).then((ext) => {
814
887
  console.log(`\u2705 Local embedding model ready`);
815
888
  return ext;
889
+ }).catch((err) => {
890
+ console.error(`\u274C Failed to load embedding model: ${err.message}`);
891
+ extractorPromise = null;
892
+ throw err;
816
893
  });
817
894
  }
818
895
  return extractorPromise;
@@ -821,16 +898,37 @@ var LocalEmbeddingProvider = class {
821
898
  id = "local";
822
899
  model;
823
900
  dimensions;
901
+ _disabled = false;
824
902
  constructor(config) {
825
903
  this.model = config.model || "Xenova/all-MiniLM-L6-v2";
826
904
  this.dimensions = 384;
827
905
  }
906
+ /**
907
+ * Pre-download and load the model at startup.
908
+ * If loading fails, marks this provider as disabled (returns empty embeddings).
909
+ * Call this once during app init — avoids retry spam on every message.
910
+ * @returns true if model loaded successfully, false if fallback to noop
911
+ */
912
+ async warmup() {
913
+ try {
914
+ await getExtractor(this.model);
915
+ return true;
916
+ } catch (err) {
917
+ console.warn(
918
+ `\u26A0\uFE0F Local embedding model unavailable \u2014 falling back to FTS5-only search (no vector embeddings)`
919
+ );
920
+ this._disabled = true;
921
+ return false;
922
+ }
923
+ }
828
924
  async embedQuery(text) {
925
+ if (this._disabled) return [];
829
926
  const extractor = await getExtractor(this.model);
830
927
  const output = await extractor(text, { pooling: "mean", normalize: true });
831
928
  return Array.from(output.data);
832
929
  }
833
930
  async embedBatch(texts) {
931
+ if (this._disabled) return [];
834
932
  if (texts.length === 0) return [];
835
933
  const extractor = await getExtractor(this.model);
836
934
  const output = await extractor(texts, { pooling: "mean", normalize: true });
@@ -875,6 +973,9 @@ var CachedEmbeddingProvider = class {
875
973
  `UPDATE embedding_cache SET accessed_at = unixepoch() WHERE hash = ? AND model = ? AND provider = ?`
876
974
  ).run(hash, this.model, this.id);
877
975
  }
976
+ async warmup() {
977
+ return this.inner.warmup?.() ?? true;
978
+ }
878
979
  async embedQuery(text) {
879
980
  const hash = hashText(text);
880
981
  const row = this.cacheGet(hash);
@@ -945,7 +1046,7 @@ var CachedEmbeddingProvider = class {
945
1046
  this.db.prepare(`DELETE FROM embedding_cache WHERE accessed_at < ?`).run(cutoff);
946
1047
  const count = this.db.prepare(`SELECT COUNT(*) as cnt FROM embedding_cache`).get().cnt;
947
1048
  if (count > EMBEDDING_CACHE_MAX_ENTRIES) {
948
- const toDelete = Math.ceil(count * 0.1);
1049
+ const toDelete = Math.ceil(count * EMBEDDING_CACHE_EVICTION_RATIO);
949
1050
  this.db.prepare(
950
1051
  `DELETE FROM embedding_cache WHERE (hash, model, provider) IN (
951
1052
  SELECT hash, model, provider FROM embedding_cache ORDER BY accessed_at ASC LIMIT ?
@@ -1000,7 +1101,7 @@ function deserializeEmbedding(data) {
1000
1101
 
1001
1102
  // src/memory/agent/knowledge.ts
1002
1103
  import { readFileSync, existsSync as existsSync3, readdirSync, statSync } from "fs";
1003
- import { join as join2 } from "path";
1104
+ import { join as join3 } from "path";
1004
1105
  var KnowledgeIndexer = class {
1005
1106
  constructor(db, workspaceDir, embedder, vectorEnabled) {
1006
1107
  this.db = db;
@@ -1008,9 +1109,6 @@ var KnowledgeIndexer = class {
1008
1109
  this.embedder = embedder;
1009
1110
  this.vectorEnabled = vectorEnabled;
1010
1111
  }
1011
- /**
1012
- * Index all memory files
1013
- */
1014
1112
  async indexAll() {
1015
1113
  const files = this.listMemoryFiles();
1016
1114
  let indexed = 0;
@@ -1025,9 +1123,6 @@ var KnowledgeIndexer = class {
1025
1123
  }
1026
1124
  return { indexed, skipped };
1027
1125
  }
1028
- /**
1029
- * Index a single file
1030
- */
1031
1126
  async indexFile(absPath) {
1032
1127
  if (!existsSync3(absPath) || !absPath.endsWith(".md")) {
1033
1128
  return false;
@@ -1039,54 +1134,53 @@ var KnowledgeIndexer = class {
1039
1134
  if (existing?.hash === fileHash) {
1040
1135
  return false;
1041
1136
  }
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
1137
  const chunks = this.chunkMarkdown(content, relPath);
1051
1138
  const texts = chunks.map((c) => c.text);
1052
1139
  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));
1140
+ this.db.transaction(() => {
1141
+ if (this.vectorEnabled) {
1142
+ this.db.prepare(
1143
+ `DELETE FROM knowledge_vec WHERE id IN (
1144
+ SELECT id FROM knowledge WHERE path = ? AND source = 'memory'
1145
+ )`
1146
+ ).run(relPath);
1072
1147
  }
1073
- }
1148
+ this.db.prepare(`DELETE FROM knowledge WHERE path = ? AND source = 'memory'`).run(relPath);
1149
+ const insert = this.db.prepare(`
1150
+ INSERT INTO knowledge (id, source, path, text, embedding, start_line, end_line, hash)
1151
+ VALUES (?, 'memory', ?, ?, ?, ?, ?, ?)
1152
+ `);
1153
+ const insertVec = this.vectorEnabled ? this.db.prepare(`INSERT INTO knowledge_vec (id, embedding) VALUES (?, ?)`) : null;
1154
+ for (let i = 0; i < chunks.length; i++) {
1155
+ const chunk = chunks[i];
1156
+ const embedding = embeddings[i] ?? [];
1157
+ insert.run(
1158
+ chunk.id,
1159
+ chunk.path,
1160
+ chunk.text,
1161
+ serializeEmbedding(embedding),
1162
+ chunk.startLine,
1163
+ chunk.endLine,
1164
+ chunk.hash
1165
+ );
1166
+ if (insertVec && embedding.length > 0) {
1167
+ insertVec.run(chunk.id, serializeEmbedding(embedding));
1168
+ }
1169
+ }
1170
+ })();
1074
1171
  return true;
1075
1172
  }
1076
- /**
1077
- * List all memory files
1078
- */
1079
1173
  listMemoryFiles() {
1080
1174
  const files = [];
1081
- const memoryMd = join2(this.workspaceDir, "MEMORY.md");
1175
+ const memoryMd = join3(this.workspaceDir, "MEMORY.md");
1082
1176
  if (existsSync3(memoryMd)) {
1083
1177
  files.push(memoryMd);
1084
1178
  }
1085
- const memoryDir = join2(this.workspaceDir, "memory");
1179
+ const memoryDir = join3(this.workspaceDir, "memory");
1086
1180
  if (existsSync3(memoryDir)) {
1087
1181
  const entries = readdirSync(memoryDir);
1088
1182
  for (const entry of entries) {
1089
- const absPath = join2(memoryDir, entry);
1183
+ const absPath = join3(memoryDir, entry);
1090
1184
  if (statSync(absPath).isFile() && entry.endsWith(".md")) {
1091
1185
  files.push(absPath);
1092
1186
  }
@@ -1095,49 +1189,58 @@ var KnowledgeIndexer = class {
1095
1189
  return files;
1096
1190
  }
1097
1191
  /**
1098
- * Chunk markdown content
1192
+ * Chunk markdown content with structure awareness.
1193
+ * Respects heading boundaries, code blocks, and list groups.
1194
+ * Target: KNOWLEDGE_CHUNK_SIZE chars, hard max: 2x target.
1099
1195
  */
1100
1196
  chunkMarkdown(content, path) {
1101
1197
  const lines = content.split("\n");
1102
1198
  const chunks = [];
1103
- const chunkSize = KNOWLEDGE_CHUNK_SIZE;
1104
- const overlap = KNOWLEDGE_CHUNK_OVERLAP;
1199
+ const targetSize = KNOWLEDGE_CHUNK_SIZE;
1200
+ const hardMax = targetSize * 2;
1105
1201
  let currentChunk = "";
1106
1202
  let startLine = 1;
1107
1203
  let currentLine = 1;
1204
+ let inCodeBlock = false;
1205
+ const flushChunk = () => {
1206
+ const text = currentChunk.trim();
1207
+ if (text.length > 0) {
1208
+ chunks.push({
1209
+ id: hashText(`${path}:${startLine}:${currentLine - 1}`),
1210
+ source: "memory",
1211
+ path,
1212
+ text,
1213
+ startLine,
1214
+ endLine: currentLine - 1,
1215
+ hash: hashText(text)
1216
+ });
1217
+ }
1218
+ currentChunk = "";
1219
+ startLine = currentLine;
1220
+ };
1108
1221
  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
- });
1222
+ if (line.trimStart().startsWith("```")) {
1223
+ inCodeBlock = !inCodeBlock;
1224
+ }
1225
+ if (!inCodeBlock && currentChunk.length >= targetSize) {
1226
+ const isHeading = /^#{1,6}\s/.test(line);
1227
+ const isBlankLine = line.trim() === "";
1228
+ const isHorizontalRule = /^(-{3,}|\*{3,}|_{3,})\s*$/.test(line.trim());
1229
+ if (isHeading) {
1230
+ flushChunk();
1231
+ } else if ((isBlankLine || isHorizontalRule) && currentChunk.length >= targetSize) {
1232
+ currentChunk += line + "\n";
1233
+ currentLine++;
1234
+ flushChunk();
1235
+ continue;
1236
+ } else if (currentChunk.length >= hardMax) {
1237
+ flushChunk();
1122
1238
  }
1123
- const overlapText = currentChunk.slice(-overlap);
1124
- currentChunk = overlapText;
1125
- startLine = currentLine + 1;
1126
1239
  }
1240
+ currentChunk += line + "\n";
1127
1241
  currentLine++;
1128
1242
  }
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
- }
1243
+ flushChunk();
1141
1244
  return chunks;
1142
1245
  }
1143
1246
  };
@@ -1150,9 +1253,6 @@ var SessionStore = class {
1150
1253
  this.embedder = embedder;
1151
1254
  this.vectorEnabled = vectorEnabled;
1152
1255
  }
1153
- /**
1154
- * Create a new session
1155
- */
1156
1256
  createSession(chatId) {
1157
1257
  const id = randomUUID();
1158
1258
  const now = Math.floor(Date.now() / 1e3);
@@ -1170,9 +1270,6 @@ var SessionStore = class {
1170
1270
  tokensUsed: 0
1171
1271
  };
1172
1272
  }
1173
- /**
1174
- * End a session with summary
1175
- */
1176
1273
  endSession(sessionId, summary, tokensUsed = 0) {
1177
1274
  const now = Math.floor(Date.now() / 1e3);
1178
1275
  this.db.prepare(
@@ -1183,9 +1280,6 @@ var SessionStore = class {
1183
1280
  `
1184
1281
  ).run(now, summary, tokensUsed, sessionId);
1185
1282
  }
1186
- /**
1187
- * Update message count for a session
1188
- */
1189
1283
  incrementMessageCount(sessionId, count = 1) {
1190
1284
  this.db.prepare(
1191
1285
  `
@@ -1195,9 +1289,6 @@ var SessionStore = class {
1195
1289
  `
1196
1290
  ).run(count, sessionId);
1197
1291
  }
1198
- /**
1199
- * Get a session by ID
1200
- */
1201
1292
  getSession(id) {
1202
1293
  const row = this.db.prepare(`SELECT * FROM sessions WHERE id = ?`).get(id);
1203
1294
  if (!row) return void 0;
@@ -1206,14 +1297,11 @@ var SessionStore = class {
1206
1297
  chatId: row.chat_id,
1207
1298
  startedAt: new Date(row.started_at * 1e3),
1208
1299
  endedAt: row.ended_at ? new Date(row.ended_at * 1e3) : void 0,
1209
- summary: row.summary,
1300
+ summary: row.summary ?? void 0,
1210
1301
  messageCount: row.message_count,
1211
1302
  tokensUsed: row.tokens_used
1212
1303
  };
1213
1304
  }
1214
- /**
1215
- * Get active (not ended) sessions
1216
- */
1217
1305
  getActiveSessions() {
1218
1306
  const rows = this.db.prepare(
1219
1307
  `
@@ -1227,14 +1315,11 @@ var SessionStore = class {
1227
1315
  chatId: row.chat_id,
1228
1316
  startedAt: new Date(row.started_at * 1e3),
1229
1317
  endedAt: void 0,
1230
- summary: row.summary,
1318
+ summary: row.summary ?? void 0,
1231
1319
  messageCount: row.message_count,
1232
1320
  tokensUsed: row.tokens_used
1233
1321
  }));
1234
1322
  }
1235
- /**
1236
- * Get sessions for a specific chat
1237
- */
1238
1323
  getSessionsByChat(chatId, limit = 50) {
1239
1324
  const rows = this.db.prepare(
1240
1325
  `
@@ -1249,14 +1334,14 @@ var SessionStore = class {
1249
1334
  chatId: row.chat_id,
1250
1335
  startedAt: new Date(row.started_at * 1e3),
1251
1336
  endedAt: row.ended_at ? new Date(row.ended_at * 1e3) : void 0,
1252
- summary: row.summary,
1337
+ summary: row.summary ?? void 0,
1253
1338
  messageCount: row.message_count,
1254
1339
  tokensUsed: row.tokens_used
1255
1340
  }));
1256
1341
  }
1257
1342
  /**
1258
- * Index a session for search (after ending)
1259
- * This creates a knowledge entry from the session summary
1343
+ * Index a session for search after ending.
1344
+ * Creates a knowledge entry from the session summary for future retrieval.
1260
1345
  */
1261
1346
  async indexSession(sessionId) {
1262
1347
  const session = this.getSession(sessionId);
@@ -1291,9 +1376,6 @@ ${session.summary}`;
1291
1376
  console.error("Error indexing session:", error);
1292
1377
  }
1293
1378
  }
1294
- /**
1295
- * Delete a session
1296
- */
1297
1379
  deleteSession(sessionId) {
1298
1380
  const knowledgeId = `session:${sessionId}`;
1299
1381
  if (this.vectorEnabled) {
@@ -1311,18 +1393,12 @@ var MessageStore = class {
1311
1393
  this.embedder = embedder;
1312
1394
  this.vectorEnabled = vectorEnabled;
1313
1395
  }
1314
- /**
1315
- * Ensure chat exists in database
1316
- */
1317
1396
  ensureChat(chatId, isGroup = false) {
1318
1397
  const existing = this.db.prepare(`SELECT id FROM tg_chats WHERE id = ?`).get(chatId);
1319
1398
  if (!existing) {
1320
1399
  this.db.prepare(`INSERT INTO tg_chats (id, type, is_monitored) VALUES (?, ?, 1)`).run(chatId, isGroup ? "group" : "dm");
1321
1400
  }
1322
1401
  }
1323
- /**
1324
- * Ensure user exists in database
1325
- */
1326
1402
  ensureUser(userId) {
1327
1403
  if (!userId) return;
1328
1404
  const existing = this.db.prepare(`SELECT id FROM tg_users WHERE id = ?`).get(userId);
@@ -1330,9 +1406,6 @@ var MessageStore = class {
1330
1406
  this.db.prepare(`INSERT INTO tg_users (id) VALUES (?)`).run(userId);
1331
1407
  }
1332
1408
  }
1333
- /**
1334
- * Store a message
1335
- */
1336
1409
  async storeMessage(message) {
1337
1410
  this.ensureChat(message.chatId);
1338
1411
  if (message.senderId) {
@@ -1340,34 +1413,33 @@ var MessageStore = class {
1340
1413
  }
1341
1414
  const embedding = this.vectorEnabled && message.text ? await this.embedder.embedQuery(message.text) : [];
1342
1415
  const embeddingBuffer = serializeEmbedding(embedding);
1343
- this.db.prepare(
1416
+ this.db.transaction(() => {
1417
+ this.db.prepare(
1418
+ `
1419
+ INSERT OR REPLACE INTO tg_messages (
1420
+ id, chat_id, sender_id, text, embedding, reply_to_id,
1421
+ is_from_agent, has_media, media_type, timestamp
1422
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1344
1423
  `
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);
1424
+ ).run(
1425
+ message.id,
1426
+ message.chatId,
1427
+ message.senderId,
1428
+ message.text,
1429
+ embeddingBuffer,
1430
+ message.replyToId,
1431
+ message.isFromAgent ? 1 : 0,
1432
+ message.hasMedia ? 1 : 0,
1433
+ message.mediaType,
1434
+ message.timestamp
1435
+ );
1436
+ if (this.vectorEnabled && embedding.length > 0 && message.text) {
1437
+ this.db.prepare(`DELETE FROM tg_messages_vec WHERE id = ?`).run(message.id);
1438
+ this.db.prepare(`INSERT INTO tg_messages_vec (id, embedding) VALUES (?, ?)`).run(message.id, embeddingBuffer);
1439
+ }
1440
+ this.db.prepare(`UPDATE tg_chats SET last_message_at = ?, last_message_id = ? WHERE id = ?`).run(message.timestamp, message.id, message.chatId);
1441
+ })();
1367
1442
  }
1368
- /**
1369
- * Get recent messages from a chat
1370
- */
1371
1443
  getRecentMessages(chatId, limit = 20) {
1372
1444
  const rows = this.db.prepare(
1373
1445
  `
@@ -1397,9 +1469,6 @@ var ChatStore = class {
1397
1469
  constructor(db) {
1398
1470
  this.db = db;
1399
1471
  }
1400
- /**
1401
- * Create or update a chat
1402
- */
1403
1472
  upsertChat(chat) {
1404
1473
  const now = Math.floor(Date.now() / 1e3);
1405
1474
  this.db.prepare(
@@ -1431,9 +1500,6 @@ var ChatStore = class {
1431
1500
  now
1432
1501
  );
1433
1502
  }
1434
- /**
1435
- * Get a chat by ID
1436
- */
1437
1503
  getChat(id) {
1438
1504
  const row = this.db.prepare(
1439
1505
  `
@@ -1444,20 +1510,17 @@ var ChatStore = class {
1444
1510
  return {
1445
1511
  id: row.id,
1446
1512
  type: row.type,
1447
- title: row.title,
1448
- username: row.username,
1449
- memberCount: row.member_count,
1513
+ title: row.title ?? void 0,
1514
+ username: row.username ?? void 0,
1515
+ memberCount: row.member_count ?? void 0,
1450
1516
  isMonitored: Boolean(row.is_monitored),
1451
1517
  isArchived: Boolean(row.is_archived),
1452
- lastMessageId: row.last_message_id,
1518
+ lastMessageId: row.last_message_id ?? void 0,
1453
1519
  lastMessageAt: row.last_message_at ? new Date(row.last_message_at * 1e3) : void 0,
1454
1520
  createdAt: new Date(row.created_at * 1e3),
1455
1521
  updatedAt: new Date(row.updated_at * 1e3)
1456
1522
  };
1457
1523
  }
1458
- /**
1459
- * Get active (monitored, non-archived) chats
1460
- */
1461
1524
  getActiveChats(limit = 50) {
1462
1525
  const rows = this.db.prepare(
1463
1526
  `
@@ -1470,20 +1533,17 @@ var ChatStore = class {
1470
1533
  return rows.map((row) => ({
1471
1534
  id: row.id,
1472
1535
  type: row.type,
1473
- title: row.title,
1474
- username: row.username,
1475
- memberCount: row.member_count,
1536
+ title: row.title ?? void 0,
1537
+ username: row.username ?? void 0,
1538
+ memberCount: row.member_count ?? void 0,
1476
1539
  isMonitored: Boolean(row.is_monitored),
1477
1540
  isArchived: Boolean(row.is_archived),
1478
- lastMessageId: row.last_message_id,
1541
+ lastMessageId: row.last_message_id ?? void 0,
1479
1542
  lastMessageAt: row.last_message_at ? new Date(row.last_message_at * 1e3) : void 0,
1480
1543
  createdAt: new Date(row.created_at * 1e3),
1481
1544
  updatedAt: new Date(row.updated_at * 1e3)
1482
1545
  }));
1483
1546
  }
1484
- /**
1485
- * Update last message info
1486
- */
1487
1547
  updateLastMessage(chatId, messageId, timestamp) {
1488
1548
  this.db.prepare(
1489
1549
  `
@@ -1493,9 +1553,6 @@ var ChatStore = class {
1493
1553
  `
1494
1554
  ).run(messageId, Math.floor(timestamp.getTime() / 1e3), chatId);
1495
1555
  }
1496
- /**
1497
- * Archive a chat
1498
- */
1499
1556
  archiveChat(chatId) {
1500
1557
  this.db.prepare(
1501
1558
  `
@@ -1505,9 +1562,6 @@ var ChatStore = class {
1505
1562
  `
1506
1563
  ).run(chatId);
1507
1564
  }
1508
- /**
1509
- * Unarchive a chat
1510
- */
1511
1565
  unarchiveChat(chatId) {
1512
1566
  this.db.prepare(
1513
1567
  `
@@ -1517,9 +1571,6 @@ var ChatStore = class {
1517
1571
  `
1518
1572
  ).run(chatId);
1519
1573
  }
1520
- /**
1521
- * Set monitoring status
1522
- */
1523
1574
  setMonitored(chatId, monitored) {
1524
1575
  this.db.prepare(
1525
1576
  `
@@ -1536,9 +1587,6 @@ var UserStore = class {
1536
1587
  constructor(db) {
1537
1588
  this.db = db;
1538
1589
  }
1539
- /**
1540
- * Create or update a user
1541
- */
1542
1590
  upsertUser(user) {
1543
1591
  const now = Math.floor(Date.now() / 1e3);
1544
1592
  const existing = this.db.prepare(`SELECT id FROM tg_users WHERE id = ?`).get(user.id);
@@ -1577,17 +1625,14 @@ var UserStore = class {
1577
1625
  );
1578
1626
  }
1579
1627
  }
1580
- /**
1581
- * Get a user by ID
1582
- */
1583
1628
  getUser(id) {
1584
1629
  const row = this.db.prepare(`SELECT * FROM tg_users WHERE id = ?`).get(id);
1585
1630
  if (!row) return void 0;
1586
1631
  return {
1587
1632
  id: row.id,
1588
- username: row.username,
1589
- firstName: row.first_name,
1590
- lastName: row.last_name,
1633
+ username: row.username ?? void 0,
1634
+ firstName: row.first_name ?? void 0,
1635
+ lastName: row.last_name ?? void 0,
1591
1636
  isBot: Boolean(row.is_bot),
1592
1637
  isAdmin: Boolean(row.is_admin),
1593
1638
  isAllowed: Boolean(row.is_allowed),
@@ -1596,17 +1641,14 @@ var UserStore = class {
1596
1641
  messageCount: row.message_count
1597
1642
  };
1598
1643
  }
1599
- /**
1600
- * Get a user by username
1601
- */
1602
1644
  getUserByUsername(username) {
1603
1645
  const row = this.db.prepare(`SELECT * FROM tg_users WHERE username = ?`).get(username.replace("@", ""));
1604
1646
  if (!row) return void 0;
1605
1647
  return {
1606
1648
  id: row.id,
1607
- username: row.username,
1608
- firstName: row.first_name,
1609
- lastName: row.last_name,
1649
+ username: row.username ?? void 0,
1650
+ firstName: row.first_name ?? void 0,
1651
+ lastName: row.last_name ?? void 0,
1610
1652
  isBot: Boolean(row.is_bot),
1611
1653
  isAdmin: Boolean(row.is_admin),
1612
1654
  isAllowed: Boolean(row.is_allowed),
@@ -1615,9 +1657,6 @@ var UserStore = class {
1615
1657
  messageCount: row.message_count
1616
1658
  };
1617
1659
  }
1618
- /**
1619
- * Update last seen timestamp
1620
- */
1621
1660
  updateLastSeen(userId) {
1622
1661
  this.db.prepare(
1623
1662
  `
@@ -1627,9 +1666,6 @@ var UserStore = class {
1627
1666
  `
1628
1667
  ).run(userId);
1629
1668
  }
1630
- /**
1631
- * Increment message count
1632
- */
1633
1669
  incrementMessageCount(userId) {
1634
1670
  this.db.prepare(
1635
1671
  `
@@ -1639,9 +1675,6 @@ var UserStore = class {
1639
1675
  `
1640
1676
  ).run(userId);
1641
1677
  }
1642
- /**
1643
- * Set admin status
1644
- */
1645
1678
  setAdmin(userId, isAdmin) {
1646
1679
  this.db.prepare(
1647
1680
  `
@@ -1651,9 +1684,6 @@ var UserStore = class {
1651
1684
  `
1652
1685
  ).run(isAdmin ? 1 : 0, userId);
1653
1686
  }
1654
- /**
1655
- * Set allowed status
1656
- */
1657
1687
  setAllowed(userId, isAllowed) {
1658
1688
  this.db.prepare(
1659
1689
  `
@@ -1663,9 +1693,6 @@ var UserStore = class {
1663
1693
  `
1664
1694
  ).run(isAllowed ? 1 : 0, userId);
1665
1695
  }
1666
- /**
1667
- * Get all admins
1668
- */
1669
1696
  getAdmins() {
1670
1697
  const rows = this.db.prepare(
1671
1698
  `
@@ -1676,9 +1703,9 @@ var UserStore = class {
1676
1703
  ).all();
1677
1704
  return rows.map((row) => ({
1678
1705
  id: row.id,
1679
- username: row.username,
1680
- firstName: row.first_name,
1681
- lastName: row.last_name,
1706
+ username: row.username ?? void 0,
1707
+ firstName: row.first_name ?? void 0,
1708
+ lastName: row.last_name ?? void 0,
1682
1709
  isBot: Boolean(row.is_bot),
1683
1710
  isAdmin: Boolean(row.is_admin),
1684
1711
  isAllowed: Boolean(row.is_allowed),
@@ -1687,9 +1714,6 @@ var UserStore = class {
1687
1714
  messageCount: row.message_count
1688
1715
  }));
1689
1716
  }
1690
- /**
1691
- * Get recently active users
1692
- */
1693
1717
  getRecentUsers(limit = 50) {
1694
1718
  const rows = this.db.prepare(
1695
1719
  `
@@ -1700,9 +1724,9 @@ var UserStore = class {
1700
1724
  ).all(limit);
1701
1725
  return rows.map((row) => ({
1702
1726
  id: row.id,
1703
- username: row.username,
1704
- firstName: row.first_name,
1705
- lastName: row.last_name,
1727
+ username: row.username ?? void 0,
1728
+ firstName: row.first_name ?? void 0,
1729
+ lastName: row.last_name ?? void 0,
1706
1730
  isBot: Boolean(row.is_bot),
1707
1731
  isAdmin: Boolean(row.is_admin),
1708
1732
  isAllowed: Boolean(row.is_allowed),
@@ -1722,30 +1746,20 @@ var HybridSearch = class {
1722
1746
  this.db = db;
1723
1747
  this.vectorEnabled = vectorEnabled;
1724
1748
  }
1725
- /**
1726
- * Search in knowledge base
1727
- */
1728
1749
  async searchKnowledge(query, queryEmbedding, options = {}) {
1729
1750
  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));
1751
+ const vectorWeight = options.vectorWeight ?? 0.5;
1752
+ const keywordWeight = options.keywordWeight ?? 0.5;
1753
+ const vectorResults = this.vectorEnabled ? this.vectorSearchKnowledge(queryEmbedding, Math.ceil(limit * 3)) : [];
1754
+ const keywordResults = this.keywordSearchKnowledge(query, Math.ceil(limit * 3));
1734
1755
  return this.mergeResults(vectorResults, keywordResults, vectorWeight, keywordWeight, limit);
1735
1756
  }
1736
- /**
1737
- * Search in Telegram messages
1738
- */
1739
1757
  async searchMessages(query, queryEmbedding, options = {}) {
1740
1758
  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
- );
1759
+ const vectorWeight = options.vectorWeight ?? 0.5;
1760
+ const keywordWeight = options.keywordWeight ?? 0.5;
1761
+ const vectorResults = this.vectorEnabled ? this.vectorSearchMessages(queryEmbedding, Math.ceil(limit * 3), options.chatId) : [];
1762
+ const keywordResults = this.keywordSearchMessages(query, Math.ceil(limit * 3), options.chatId);
1749
1763
  return this.mergeResults(vectorResults, keywordResults, vectorWeight, keywordWeight, limit);
1750
1764
  }
1751
1765
  vectorSearchKnowledge(embedding, limit) {
@@ -1877,10 +1891,14 @@ var HybridSearch = class {
1877
1891
  byId.set(r.id, { ...r, score: keywordWeight * (r.keywordScore ?? 0) });
1878
1892
  }
1879
1893
  }
1880
- return Array.from(byId.values()).sort((a, b) => b.score - a.score).slice(0, limit);
1894
+ return Array.from(byId.values()).filter((r) => r.score >= HYBRID_SEARCH_MIN_SCORE).sort((a, b) => b.score - a.score).slice(0, limit);
1881
1895
  }
1896
+ /**
1897
+ * Convert BM25 rank to normalized score.
1898
+ * FTS5 rank is negative; more negative = better match.
1899
+ */
1882
1900
  bm25ToScore(rank) {
1883
- return 1 / (1 + Math.abs(rank));
1901
+ return 1 / (1 + Math.exp(rank));
1884
1902
  }
1885
1903
  };
1886
1904
 
@@ -1921,6 +1939,9 @@ var ContextBuilder = class {
1921
1939
  console.warn("Knowledge search failed:", error);
1922
1940
  }
1923
1941
  }
1942
+ const recentTextsSet = new Set(
1943
+ recentTgMessages.filter((m) => m.text && m.text.length > 0).map((m) => m.text)
1944
+ );
1924
1945
  const relevantFeed = [];
1925
1946
  if (includeFeedHistory) {
1926
1947
  try {
@@ -1928,10 +1949,13 @@ var ContextBuilder = class {
1928
1949
  chatId,
1929
1950
  limit: maxRelevantChunks
1930
1951
  });
1931
- relevantFeed.push(...feedResults.map((r) => r.text));
1952
+ for (const r of feedResults) {
1953
+ if (!recentTextsSet.has(r.text)) {
1954
+ relevantFeed.push(r.text);
1955
+ }
1956
+ }
1932
1957
  if (searchAllChats) {
1933
1958
  const globalResults = await this.hybridSearch.searchMessages(query, queryEmbedding, {
1934
- // No chatId = search all chats
1935
1959
  limit: maxRelevantChunks
1936
1960
  });
1937
1961
  const existingTexts = new Set(relevantFeed);
@@ -1995,6 +2019,14 @@ export {
1995
2019
  getDatabase,
1996
2020
  closeDatabase,
1997
2021
  NoopEmbeddingProvider,
2022
+ fetchWithTimeout,
2023
+ setTonapiKey,
2024
+ tonapiFetch,
2025
+ STONFI_API_BASE_URL,
2026
+ GECKOTERMINAL_API_URL,
2027
+ COINGECKO_API_URL,
2028
+ OPENAI_TTS_URL,
2029
+ ELEVENLABS_TTS_URL,
1998
2030
  AnthropicEmbeddingProvider,
1999
2031
  LocalEmbeddingProvider,
2000
2032
  CachedEmbeddingProvider,