teleton 0.8.0 → 0.8.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 (32) hide show
  1. package/README.md +28 -11
  2. package/dist/{chunk-U56QTM46.js → chunk-3S4GGLLR.js} +28 -26
  3. package/dist/{chunk-NUGDTPE4.js → chunk-4L66JHQE.js} +2 -1
  4. package/dist/{chunk-H36RFKRI.js → chunk-5FNWBZ5K.js} +476 -143
  5. package/dist/{chunk-SD4NLLYG.js → chunk-7U7BOHCL.js} +80 -33
  6. package/dist/{chunk-QVBSUYVX.js → chunk-AYWEJCDB.js} +11 -3
  7. package/dist/{chunk-RQBAMUCV.js → chunk-CGOXE4WP.js} +1235 -201
  8. package/dist/{chunk-TVRZJIZX.js → chunk-KVXV7EF7.js} +3 -3
  9. package/dist/{chunk-WIKM24GZ.js → chunk-QBHRXLZS.js} +5 -0
  10. package/dist/{chunk-P36I6OIV.js → chunk-QV2GLOTK.js} +12 -1
  11. package/dist/{chunk-JHYZYFZJ.js → chunk-S6PHGKOC.js} +8 -1
  12. package/dist/{chunk-IJBWWQE4.js → chunk-UP55PXFH.js} +4 -0
  13. package/dist/cli/index.js +17 -16
  14. package/dist/{client-LNZTDQSA.js → client-MPHPIZB6.js} +2 -2
  15. package/dist/{get-my-gifts-OMGKOEPM.js → get-my-gifts-CC6HAVWB.js} +1 -1
  16. package/dist/index.js +11 -11
  17. package/dist/{memory-AS7WKGTW.js → memory-UBHM7ILG.js} +4 -4
  18. package/dist/{migrate-POHWYEIW.js → migrate-UBBEJ5BL.js} +4 -4
  19. package/dist/{server-H3QA252W.js → server-3FHI2SEB.js} +392 -51
  20. package/dist/{setup-server-QXED3D2L.js → setup-server-32XGDPE6.js} +157 -7
  21. package/dist/{store-GAFULOOX.js → store-M5IMUQCL.js} +5 -5
  22. package/dist/{task-dependency-resolver-3FIKQ7Z6.js → task-dependency-resolver-RR2O5S7B.js} +2 -2
  23. package/dist/{task-executor-RUTFG6VG.js → task-executor-6W5HRX5C.js} +2 -2
  24. package/dist/{tasks-BEZ4QRI2.js → tasks-WQIKXDX5.js} +1 -1
  25. package/dist/{tool-index-H3SHOJC3.js → tool-index-PMAOXWUA.js} +8 -5
  26. package/dist/{transcript-IMNE6KU3.js → transcript-NGDPSNIH.js} +1 -1
  27. package/dist/web/assets/index-BfYCdwLI.js +80 -0
  28. package/dist/web/assets/{index-BrVqauzj.css → index-DmlyQVhR.css} +1 -1
  29. package/dist/web/assets/{index.es-DkU1GvWU.js → index.es-DitvF-9H.js} +1 -1
  30. package/dist/web/index.html +2 -2
  31. package/package.json +11 -4
  32. package/dist/web/assets/index-DYeEkvJ6.js +0 -72
@@ -7,7 +7,7 @@ import {
7
7
  getWalletBalance,
8
8
  invalidateTonClientCache,
9
9
  loadWallet
10
- } from "./chunk-JHYZYFZJ.js";
10
+ } from "./chunk-S6PHGKOC.js";
11
11
  import {
12
12
  getOrCreateSession,
13
13
  getSession,
@@ -15,7 +15,7 @@ import {
15
15
  resetSessionWithPolicy,
16
16
  shouldResetSession,
17
17
  updateSession
18
- } from "./chunk-TVRZJIZX.js";
18
+ } from "./chunk-KVXV7EF7.js";
19
19
  import {
20
20
  Mi,
21
21
  Mn,
@@ -35,7 +35,7 @@ import {
35
35
  getProviderModel,
36
36
  getUtilityModel,
37
37
  loadContextFromTranscript
38
- } from "./chunk-QVBSUYVX.js";
38
+ } from "./chunk-AYWEJCDB.js";
39
39
  import {
40
40
  getProviderMetadata,
41
41
  getSupportedProviders
@@ -44,14 +44,14 @@ import {
44
44
  appendToTranscript,
45
45
  archiveTranscript,
46
46
  transcriptExists
47
- } from "./chunk-P36I6OIV.js";
47
+ } from "./chunk-QV2GLOTK.js";
48
48
  import {
49
49
  ContextBuilder,
50
50
  createDbWrapper,
51
51
  getDatabase,
52
52
  migrateFromMainDb,
53
53
  openModuleDb
54
- } from "./chunk-SD4NLLYG.js";
54
+ } from "./chunk-7U7BOHCL.js";
55
55
  import {
56
56
  GECKOTERMINAL_API_URL,
57
57
  tonapiFetch
@@ -80,11 +80,13 @@ import {
80
80
  OVERSIZED_MESSAGE_RATIO,
81
81
  PAYMENT_TOLERANCE_RATIO,
82
82
  RATE_LIMIT_MAX_RETRIES,
83
+ RESULT_TRUNCATION_KEEP_CHARS,
84
+ RESULT_TRUNCATION_THRESHOLD,
83
85
  SERVER_ERROR_MAX_RETRIES,
84
86
  SESSION_SLUG_MAX_TOKENS,
85
87
  SESSION_SLUG_RECENT_MESSAGES,
86
88
  TOKEN_ESTIMATE_SAFETY_MARGIN
87
- } from "./chunk-IJBWWQE4.js";
89
+ } from "./chunk-UP55PXFH.js";
88
90
  import {
89
91
  fetchWithTimeout
90
92
  } from "./chunk-XQUHC3JZ.js";
@@ -1130,7 +1132,6 @@ This session was compacted and migrated to a new session ID. The summary above p
1130
1132
  }
1131
1133
 
1132
1134
  // src/memory/compaction.ts
1133
- import { encodingForModel } from "js-tiktoken";
1134
1135
  var DEFAULT_COMPACTION_CONFIG = {
1135
1136
  enabled: true,
1136
1137
  maxMessages: COMPACTION_MAX_MESSAGES,
@@ -1375,56 +1376,119 @@ var CompactionManager = class {
1375
1376
  // src/memory/observation-masking.ts
1376
1377
  var DEFAULT_MASKING_CONFIG = {
1377
1378
  keepRecentCount: MASKING_KEEP_RECENT_COUNT,
1378
- keepErrorResults: true
1379
+ keepErrorResults: true,
1380
+ truncationThreshold: RESULT_TRUNCATION_THRESHOLD,
1381
+ truncationKeepChars: RESULT_TRUNCATION_KEEP_CHARS
1379
1382
  };
1380
1383
  var isCocoonToolResult = (msg) => msg.role === "user" && Array.isArray(msg.content) && msg.content.some((c2) => c2.type === "text" && c2.text.includes("<tool_response>"));
1381
- function maskOldToolResults(messages, config = DEFAULT_MASKING_CONFIG, toolRegistry) {
1384
+ function isExempt(toolMsg, config, toolRegistry) {
1385
+ if (config.keepErrorResults && toolMsg.isError) return true;
1386
+ if (toolRegistry && toolRegistry.getToolCategory(toolMsg.toolName) === "data-bearing")
1387
+ return true;
1388
+ return false;
1389
+ }
1390
+ function truncateToolResult(text, keepChars) {
1391
+ try {
1392
+ const parsed = JSON.parse(text);
1393
+ if (parsed.data?.summary) {
1394
+ return JSON.stringify({
1395
+ success: parsed.success,
1396
+ data: { summary: parsed.data.summary, _truncated: true }
1397
+ });
1398
+ }
1399
+ if (parsed.data?.message) {
1400
+ return JSON.stringify({
1401
+ success: parsed.success,
1402
+ data: { summary: parsed.data.message, _truncated: true }
1403
+ });
1404
+ }
1405
+ } catch {
1406
+ }
1407
+ return text.slice(0, keepChars) + `
1408
+ ...[truncated, original: ${text.length} chars]`;
1409
+ }
1410
+ function maskOldToolResults(messages, options) {
1411
+ const config = options?.config ?? DEFAULT_MASKING_CONFIG;
1412
+ const toolRegistry = options?.toolRegistry;
1413
+ const iterStart = options?.currentIterationStartIndex;
1382
1414
  const toolResults = messages.map((msg, index) => ({ msg, index })).filter(({ msg }) => msg.role === "toolResult" || isCocoonToolResult(msg));
1383
- if (toolResults.length <= config.keepRecentCount) {
1415
+ const needsMasking = toolResults.length > config.keepRecentCount;
1416
+ const needsTruncation = iterStart !== void 0 && config.truncationThreshold > 0;
1417
+ if (!needsMasking && !needsTruncation) {
1384
1418
  return messages;
1385
1419
  }
1386
- const toMask = toolResults.slice(0, -config.keepRecentCount);
1387
1420
  const result = [...messages];
1388
- for (const { msg, index } of toMask) {
1389
- if (isCocoonToolResult(msg)) {
1421
+ if (needsMasking) {
1422
+ const toMask = toolResults.slice(0, -config.keepRecentCount);
1423
+ for (const { msg, index } of toMask) {
1424
+ if (isCocoonToolResult(msg)) {
1425
+ result[index] = {
1426
+ ...msg,
1427
+ content: [{ type: "text", text: "[Tool response masked]" }]
1428
+ };
1429
+ continue;
1430
+ }
1431
+ const toolMsg = msg;
1432
+ if (isExempt(toolMsg, config, toolRegistry)) continue;
1433
+ let summaryText = "";
1434
+ try {
1435
+ const textBlock = toolMsg.content.find((c2) => c2.type === "text");
1436
+ if (textBlock) {
1437
+ const parsed = JSON.parse(textBlock.text);
1438
+ if (parsed.data?.summary) {
1439
+ summaryText = ` - ${parsed.data.summary}`;
1440
+ } else if (parsed.data?.message) {
1441
+ summaryText = ` - ${parsed.data.message}`;
1442
+ }
1443
+ }
1444
+ } catch {
1445
+ }
1390
1446
  result[index] = {
1391
- ...msg,
1392
- content: [{ type: "text", text: "[Tool response masked]" }]
1447
+ ...toolMsg,
1448
+ content: [
1449
+ {
1450
+ type: "text",
1451
+ text: `[Tool: ${toolMsg.toolName} - ${toolMsg.isError ? "ERROR" : "OK"}${summaryText}]`
1452
+ }
1453
+ ]
1393
1454
  };
1394
- continue;
1395
- }
1396
- const toolMsg = msg;
1397
- if (config.keepErrorResults && toolMsg.isError) {
1398
- continue;
1399
1455
  }
1400
- if (toolRegistry) {
1401
- const category = toolRegistry.getToolCategory(toolMsg.toolName);
1402
- if (category === "data-bearing") {
1456
+ }
1457
+ if (needsTruncation) {
1458
+ const recentResults = needsMasking ? toolResults.slice(-config.keepRecentCount) : toolResults;
1459
+ for (const { msg, index } of recentResults) {
1460
+ if (index >= iterStart) continue;
1461
+ if (isCocoonToolResult(msg)) {
1462
+ const userMsg = msg;
1463
+ if (!Array.isArray(userMsg.content)) continue;
1464
+ const textBlock2 = userMsg.content.find((c2) => c2.type === "text");
1465
+ if (textBlock2 && textBlock2.text.length > config.truncationThreshold) {
1466
+ result[index] = {
1467
+ ...userMsg,
1468
+ content: [
1469
+ {
1470
+ type: "text",
1471
+ text: truncateToolResult(textBlock2.text, config.truncationKeepChars)
1472
+ }
1473
+ ]
1474
+ };
1475
+ }
1403
1476
  continue;
1404
1477
  }
1405
- }
1406
- let summaryText = "";
1407
- try {
1478
+ const toolMsg = msg;
1479
+ if (isExempt(toolMsg, config, toolRegistry)) continue;
1408
1480
  const textBlock = toolMsg.content.find((c2) => c2.type === "text");
1409
- if (textBlock) {
1410
- const parsed = JSON.parse(textBlock.text);
1411
- if (parsed.data?.summary) {
1412
- summaryText = ` - ${parsed.data.summary}`;
1413
- } else if (parsed.data?.message) {
1414
- summaryText = ` - ${parsed.data.message}`;
1415
- }
1416
- }
1417
- } catch {
1481
+ if (!textBlock || textBlock.text.length <= config.truncationThreshold) continue;
1482
+ result[index] = {
1483
+ ...toolMsg,
1484
+ content: [
1485
+ {
1486
+ type: "text",
1487
+ text: truncateToolResult(textBlock.text, config.truncationKeepChars)
1488
+ }
1489
+ ]
1490
+ };
1418
1491
  }
1419
- result[index] = {
1420
- ...toolMsg,
1421
- content: [
1422
- {
1423
- type: "text",
1424
- text: `[Tool: ${toolMsg.toolName} - ${toolMsg.isError ? "ERROR" : "OK"}${summaryText}]`
1425
- }
1426
- ]
1427
- };
1428
1492
  }
1429
1493
  return result;
1430
1494
  }
@@ -1484,6 +1548,8 @@ var AgentRuntime = class {
1484
1548
  contextBuilder = null;
1485
1549
  toolRegistry = null;
1486
1550
  embedder = null;
1551
+ hookRunner;
1552
+ userHookEvaluator;
1487
1553
  constructor(config, soul, toolRegistry) {
1488
1554
  this.config = config;
1489
1555
  this.soul = soul ?? "";
@@ -1504,6 +1570,12 @@ var AgentRuntime = class {
1504
1570
  this.compactionManager = new CompactionManager(DEFAULT_COMPACTION_CONFIG);
1505
1571
  }
1506
1572
  }
1573
+ setHookRunner(runner) {
1574
+ this.hookRunner = runner;
1575
+ }
1576
+ setUserHookEvaluator(evaluator) {
1577
+ this.userHookEvaluator = evaluator;
1578
+ }
1507
1579
  initializeContextBuilder(embedder, vectorEnabled) {
1508
1580
  this.embedder = embedder;
1509
1581
  const db = getDatabase().getDb();
@@ -1528,12 +1600,59 @@ var AgentRuntime = class {
1528
1600
  messageId,
1529
1601
  replyContext
1530
1602
  } = opts;
1603
+ const effectiveIsGroup = isGroup ?? false;
1604
+ const processStartTime = Date.now();
1531
1605
  try {
1606
+ let userHookContext = "";
1607
+ if (this.userHookEvaluator) {
1608
+ const hookResult = this.userHookEvaluator.evaluate(userMessage);
1609
+ if (hookResult.blocked) {
1610
+ log6.info("Message blocked by keyword filter");
1611
+ return { content: hookResult.blockMessage ?? "", toolCalls: [] };
1612
+ }
1613
+ if (hookResult.additionalContext) {
1614
+ userHookContext = sanitizeForContext(hookResult.additionalContext);
1615
+ }
1616
+ }
1617
+ let effectiveMessage = userMessage;
1618
+ let hookMessageContext = "";
1619
+ if (this.hookRunner) {
1620
+ const msgEvent = {
1621
+ chatId,
1622
+ senderId: toolContext?.senderId ? String(toolContext.senderId) : chatId,
1623
+ senderName: userName ?? "",
1624
+ isGroup: effectiveIsGroup,
1625
+ isReply: !!replyContext,
1626
+ replyToMessageId: replyContext ? messageId : void 0,
1627
+ messageId: messageId ?? 0,
1628
+ timestamp: timestamp ?? Date.now(),
1629
+ text: userMessage,
1630
+ block: false,
1631
+ blockReason: "",
1632
+ additionalContext: ""
1633
+ };
1634
+ await this.hookRunner.runModifyingHook("message:receive", msgEvent);
1635
+ if (msgEvent.block) {
1636
+ log6.info(`\u{1F6AB} Message blocked by hook: ${msgEvent.blockReason || "no reason"}`);
1637
+ return { content: "", toolCalls: [] };
1638
+ }
1639
+ effectiveMessage = sanitizeForContext(msgEvent.text);
1640
+ if (msgEvent.additionalContext) {
1641
+ hookMessageContext = sanitizeForContext(msgEvent.additionalContext);
1642
+ }
1643
+ }
1532
1644
  let session = getOrCreateSession(chatId);
1533
1645
  const now = timestamp ?? Date.now();
1534
1646
  const resetPolicy = this.config.agent.session_reset_policy;
1535
1647
  if (shouldResetSession(session, resetPolicy)) {
1536
1648
  log6.info(`\u{1F504} Auto-resetting session based on policy`);
1649
+ if (this.hookRunner) {
1650
+ await this.hookRunner.runObservingHook("session:end", {
1651
+ sessionId: session.sessionId,
1652
+ chatId,
1653
+ messageCount: session.messageCount
1654
+ });
1655
+ }
1537
1656
  if (transcriptExists(session.sessionId)) {
1538
1657
  try {
1539
1658
  log6.info(`\u{1F4BE} Saving memory before daily reset...`);
@@ -1555,11 +1674,19 @@ var AgentRuntime = class {
1555
1674
  session = resetSessionWithPolicy(chatId, resetPolicy);
1556
1675
  }
1557
1676
  let context = loadContextFromTranscript(session.sessionId);
1558
- if (context.messages.length > 0) {
1677
+ const isNewSession = context.messages.length === 0;
1678
+ if (!isNewSession) {
1559
1679
  log6.info(`\u{1F4D6} Loading existing session: ${session.sessionId}`);
1560
1680
  } else {
1561
1681
  log6.info(`\u{1F195} Starting new session: ${session.sessionId}`);
1562
1682
  }
1683
+ if (this.hookRunner) {
1684
+ await this.hookRunner.runObservingHook("session:start", {
1685
+ sessionId: session.sessionId,
1686
+ chatId,
1687
+ isResume: !isNewSession
1688
+ });
1689
+ }
1563
1690
  const previousTimestamp = session.updatedAt;
1564
1691
  let formattedMessage = formatMessageEnvelope({
1565
1692
  channel: "Telegram",
@@ -1569,8 +1696,8 @@ var AgentRuntime = class {
1569
1696
  senderRank,
1570
1697
  timestamp: now,
1571
1698
  previousTimestamp,
1572
- body: userMessage,
1573
- isGroup: isGroup ?? false,
1699
+ body: effectiveMessage,
1700
+ isGroup: effectiveIsGroup,
1574
1701
  hasMedia,
1575
1702
  mediaType,
1576
1703
  messageId,
@@ -1589,10 +1716,10 @@ ${formattedMessage}`;
1589
1716
  log6.info(`\u{1F4E8} ${msgType}: "${preview}${formattedMessage.length > 50 ? "..." : ""}"`);
1590
1717
  let relevantContext = "";
1591
1718
  let queryEmbedding;
1592
- const isNonTrivial = !isTrivialMessage(userMessage);
1719
+ const isNonTrivial = !isTrivialMessage(effectiveMessage);
1593
1720
  if (this.embedder && isNonTrivial) {
1594
1721
  try {
1595
- queryEmbedding = await this.embedder.embedQuery(userMessage);
1722
+ queryEmbedding = await this.embedder.embedQuery(effectiveMessage);
1596
1723
  } catch (error) {
1597
1724
  log6.warn({ err: error }, "Embedding computation failed");
1598
1725
  }
@@ -1600,7 +1727,7 @@ ${formattedMessage}`;
1600
1727
  if (this.contextBuilder && isNonTrivial) {
1601
1728
  try {
1602
1729
  const dbContext = await this.contextBuilder.buildContext({
1603
- query: userMessage,
1730
+ query: effectiveMessage,
1604
1731
  chatId,
1605
1732
  includeAgentMemory: true,
1606
1733
  includeFeedHistory: true,
@@ -1645,8 +1772,23 @@ ${statsContext}
1645
1772
  ${relevantContext}` : `You are in a Telegram conversation with chat ID: ${chatId}. Maintain conversation continuity.
1646
1773
 
1647
1774
  ${statsContext}`;
1775
+ let hookAdditionalContext = "";
1776
+ if (this.hookRunner) {
1777
+ const promptEvent = {
1778
+ chatId,
1779
+ sessionId: session.sessionId,
1780
+ isGroup: effectiveIsGroup,
1781
+ additionalContext: ""
1782
+ };
1783
+ await this.hookRunner.runModifyingHook("prompt:before", promptEvent);
1784
+ hookAdditionalContext = sanitizeForContext(promptEvent.additionalContext);
1785
+ }
1648
1786
  const compactionConfig = this.compactionManager.getConfig();
1649
1787
  const needsMemoryFlush = compactionConfig.enabled && compactionConfig.memoryFlushEnabled && context.messages.length > Math.floor((compactionConfig.maxMessages ?? 200) * 0.75);
1788
+ const allHookContext = [userHookContext, hookAdditionalContext, hookMessageContext].filter(Boolean).join("\n\n");
1789
+ const finalContext = additionalContext + (allHookContext ? `
1790
+
1791
+ ${allHookContext}` : "");
1650
1792
  const systemPrompt = buildSystemPrompt({
1651
1793
  soul: this.soul,
1652
1794
  userName,
@@ -1654,11 +1796,23 @@ ${statsContext}`;
1654
1796
  senderId: toolContext?.senderId,
1655
1797
  ownerName: this.config.telegram.owner_name,
1656
1798
  ownerUsername: this.config.telegram.owner_username,
1657
- context: additionalContext,
1658
- includeMemory: !isGroup,
1659
- includeStrategy: !isGroup,
1799
+ context: finalContext,
1800
+ includeMemory: !effectiveIsGroup,
1801
+ includeStrategy: !effectiveIsGroup,
1660
1802
  memoryFlushWarning: needsMemoryFlush
1661
1803
  });
1804
+ if (this.hookRunner) {
1805
+ const promptAfterEvent = {
1806
+ chatId,
1807
+ sessionId: session.sessionId,
1808
+ isGroup: effectiveIsGroup,
1809
+ promptLength: systemPrompt.length,
1810
+ sectionCount: (systemPrompt.match(/^#{1,3} /gm) || []).length,
1811
+ ragContextLength: relevantContext.length,
1812
+ hookContextLength: allHookContext.length
1813
+ };
1814
+ await this.hookRunner.runObservingHook("prompt:after", promptAfterEvent);
1815
+ }
1662
1816
  const userMsg = {
1663
1817
  role: "user",
1664
1818
  content: formattedMessage,
@@ -1686,12 +1840,12 @@ ${statsContext}`;
1686
1840
  let tools;
1687
1841
  {
1688
1842
  const toolIndex = this.toolRegistry?.getToolIndex();
1689
- const useRAG = toolIndex?.isIndexed && this.config.tool_rag?.enabled !== false && !isTrivialMessage(userMessage) && !(providerMeta.toolLimit === null && this.config.tool_rag?.skip_unlimited_providers !== false);
1843
+ const useRAG = toolIndex?.isIndexed && this.config.tool_rag?.enabled !== false && !isTrivialMessage(effectiveMessage) && !(providerMeta.toolLimit === null && this.config.tool_rag?.skip_unlimited_providers !== false);
1690
1844
  if (useRAG && this.toolRegistry && queryEmbedding) {
1691
1845
  tools = await this.toolRegistry.getForContextWithRAG(
1692
- userMessage,
1846
+ effectiveMessage,
1693
1847
  queryEmbedding,
1694
- isGroup ?? false,
1848
+ effectiveIsGroup,
1695
1849
  providerMeta.toolLimit,
1696
1850
  chatId,
1697
1851
  isAdmin
@@ -1699,7 +1853,7 @@ ${statsContext}`;
1699
1853
  log6.info(`\u{1F50D} Tool RAG: ${tools.length}/${this.toolRegistry.count} tools selected`);
1700
1854
  } else {
1701
1855
  tools = this.toolRegistry?.getForContext(
1702
- isGroup ?? false,
1856
+ effectiveIsGroup,
1703
1857
  providerMeta.toolLimit,
1704
1858
  chatId,
1705
1859
  isAdmin
@@ -1718,11 +1872,11 @@ ${statsContext}`;
1718
1872
  while (iteration < maxIterations) {
1719
1873
  iteration++;
1720
1874
  log6.debug(`\u{1F504} Agentic iteration ${iteration}/${maxIterations}`);
1721
- const maskedMessages = maskOldToolResults(
1722
- context.messages,
1723
- void 0,
1724
- this.toolRegistry ?? void 0
1725
- );
1875
+ const iterationStartIndex = context.messages.length;
1876
+ const maskedMessages = maskOldToolResults(context.messages, {
1877
+ toolRegistry: this.toolRegistry ?? void 0,
1878
+ currentIterationStartIndex: iterationStartIndex
1879
+ });
1726
1880
  const maskedContext = { ...context, messages: maskedMessages };
1727
1881
  const response2 = await chatWithContext(this.config.agent, {
1728
1882
  systemPrompt,
@@ -1734,6 +1888,21 @@ ${statsContext}`;
1734
1888
  const assistantMsg = response2.message;
1735
1889
  if (assistantMsg.stopReason === "error") {
1736
1890
  const errorMsg = assistantMsg.errorMessage || "";
1891
+ if (this.hookRunner) {
1892
+ const errorCode = errorMsg.includes("429") || errorMsg.toLowerCase().includes("rate") ? "RATE_LIMIT" : isContextOverflowError(errorMsg) ? "CONTEXT_OVERFLOW" : errorMsg.includes("500") || errorMsg.includes("502") || errorMsg.includes("503") ? "PROVIDER_ERROR" : "UNKNOWN";
1893
+ const responseErrorEvent = {
1894
+ chatId,
1895
+ sessionId: session.sessionId,
1896
+ isGroup: effectiveIsGroup,
1897
+ error: errorMsg,
1898
+ errorCode,
1899
+ provider,
1900
+ model: this.config.agent.model,
1901
+ retryCount: rateLimitRetries + serverErrorRetries,
1902
+ durationMs: Date.now() - processStartTime
1903
+ };
1904
+ await this.hookRunner.runObservingHook("response:error", responseErrorEvent);
1905
+ }
1737
1906
  if (isContextOverflowError(errorMsg)) {
1738
1907
  overflowResets++;
1739
1908
  if (overflowResets > 1) {
@@ -1822,33 +1991,103 @@ ${statsContext}`;
1822
1991
  const fullContext = {
1823
1992
  ...toolContext,
1824
1993
  chatId,
1825
- isGroup: isGroup ?? false
1994
+ isGroup: effectiveIsGroup
1826
1995
  };
1827
- const result = await this.toolRegistry.execute(block, fullContext);
1996
+ let toolParams = block.arguments ?? {};
1997
+ let blocked = false;
1998
+ let blockReason = "";
1999
+ if (this.hookRunner) {
2000
+ const beforeEvent = {
2001
+ toolName: block.name,
2002
+ params: structuredClone(toolParams),
2003
+ chatId,
2004
+ isGroup: effectiveIsGroup,
2005
+ block: false,
2006
+ blockReason: ""
2007
+ };
2008
+ await this.hookRunner.runModifyingHook("tool:before", beforeEvent);
2009
+ if (beforeEvent.block) {
2010
+ blocked = true;
2011
+ blockReason = beforeEvent.blockReason || "Blocked by plugin hook";
2012
+ } else {
2013
+ toolParams = structuredClone(beforeEvent.params);
2014
+ }
2015
+ }
2016
+ let result;
2017
+ if (blocked) {
2018
+ result = { success: false, error: blockReason };
2019
+ if (this.hookRunner) {
2020
+ const afterEvent = {
2021
+ toolName: block.name,
2022
+ params: structuredClone(toolParams),
2023
+ result: { success: false, error: blockReason },
2024
+ durationMs: 0,
2025
+ chatId,
2026
+ isGroup: effectiveIsGroup,
2027
+ blocked: true,
2028
+ blockReason
2029
+ };
2030
+ await this.hookRunner.runObservingHook("tool:after", afterEvent);
2031
+ }
2032
+ } else {
2033
+ const startTime = Date.now();
2034
+ try {
2035
+ result = await this.toolRegistry.execute(
2036
+ { ...block, arguments: toolParams },
2037
+ fullContext
2038
+ );
2039
+ } catch (execErr) {
2040
+ const durationMs2 = Date.now() - startTime;
2041
+ const errMsg = execErr instanceof Error ? execErr.message : String(execErr);
2042
+ const errStack = execErr instanceof Error ? execErr.stack : void 0;
2043
+ result = { success: false, error: errMsg };
2044
+ if (this.hookRunner) {
2045
+ const errorEvent = {
2046
+ toolName: block.name,
2047
+ params: structuredClone(toolParams),
2048
+ error: errMsg,
2049
+ // Note: stack traces are exposed to plugins for debugging — accepted tradeoff
2050
+ stack: errStack,
2051
+ chatId,
2052
+ isGroup: effectiveIsGroup,
2053
+ durationMs: durationMs2
2054
+ };
2055
+ await this.hookRunner.runObservingHook("tool:error", errorEvent);
2056
+ }
2057
+ }
2058
+ const durationMs = Date.now() - startTime;
2059
+ if (this.hookRunner) {
2060
+ const afterEvent = {
2061
+ toolName: block.name,
2062
+ params: structuredClone(toolParams),
2063
+ result: { success: result.success, data: result.data, error: result.error },
2064
+ durationMs,
2065
+ chatId,
2066
+ isGroup: effectiveIsGroup
2067
+ };
2068
+ await this.hookRunner.runObservingHook("tool:after", afterEvent);
2069
+ }
2070
+ }
1828
2071
  log6.debug(`${block.name}: ${result.success ? "\u2713" : "\u2717"} ${result.error || ""}`);
1829
2072
  iterationToolNames.push(`${block.name} ${result.success ? "\u2713" : "\u2717"}`);
1830
2073
  totalToolCalls.push({
1831
2074
  name: block.name,
1832
2075
  input: block.arguments
1833
2076
  });
1834
- let resultText = JSON.stringify(result, null, 2);
2077
+ let resultText = JSON.stringify(result);
1835
2078
  if (resultText.length > MAX_TOOL_RESULT_SIZE) {
1836
2079
  log6.warn(`\u26A0\uFE0F Tool result too large (${resultText.length} chars), truncating...`);
1837
2080
  const data = result.data;
1838
2081
  if (data?.summary || data?.message) {
1839
- resultText = JSON.stringify(
1840
- {
1841
- success: result.success,
1842
- data: {
1843
- summary: data.summary || data.message,
1844
- _truncated: true,
1845
- _originalSize: resultText.length,
1846
- _message: "Full data truncated. Use limit parameter for smaller results."
1847
- }
1848
- },
1849
- null,
1850
- 2
1851
- );
2082
+ resultText = JSON.stringify({
2083
+ success: result.success,
2084
+ data: {
2085
+ summary: data.summary || data.message,
2086
+ _truncated: true,
2087
+ _originalSize: resultText.length,
2088
+ _message: "Full data truncated. Use limit parameter for smaller results."
2089
+ }
2090
+ });
1852
2091
  } else {
1853
2092
  resultText = resultText.slice(0, MAX_TOOL_RESULT_SIZE) + "\n...[TRUNCATED]";
1854
2093
  }
@@ -1947,6 +2186,42 @@ ${statsContext}`;
1947
2186
  log6.warn("\u26A0\uFE0F Empty response with zero tokens - possible API issue");
1948
2187
  content = "I couldn't process your request. Please try again.";
1949
2188
  }
2189
+ let responseMetadata = {};
2190
+ if (this.hookRunner) {
2191
+ const responseBeforeEvent = {
2192
+ chatId,
2193
+ sessionId: session.sessionId,
2194
+ isGroup: effectiveIsGroup,
2195
+ originalText: content,
2196
+ text: content,
2197
+ block: false,
2198
+ blockReason: "",
2199
+ metadata: {}
2200
+ };
2201
+ await this.hookRunner.runModifyingHook("response:before", responseBeforeEvent);
2202
+ if (responseBeforeEvent.block) {
2203
+ log6.info(
2204
+ `\u{1F6AB} Response blocked by hook: ${responseBeforeEvent.blockReason || "no reason"}`
2205
+ );
2206
+ content = "";
2207
+ } else {
2208
+ content = responseBeforeEvent.text;
2209
+ }
2210
+ responseMetadata = responseBeforeEvent.metadata;
2211
+ }
2212
+ if (this.hookRunner) {
2213
+ const responseAfterEvent = {
2214
+ chatId,
2215
+ sessionId: session.sessionId,
2216
+ isGroup: effectiveIsGroup,
2217
+ text: content,
2218
+ durationMs: Date.now() - processStartTime,
2219
+ toolsUsed: totalToolCalls.map((tc) => tc.name),
2220
+ tokenUsage: accumulatedUsage.input > 0 || accumulatedUsage.output > 0 ? { input: accumulatedUsage.input, output: accumulatedUsage.output } : void 0,
2221
+ metadata: responseMetadata
2222
+ };
2223
+ await this.hookRunner.runObservingHook("response:after", responseAfterEvent);
2224
+ }
1950
2225
  return {
1951
2226
  content,
1952
2227
  toolCalls: totalToolCalls
@@ -2124,6 +2399,7 @@ function parseHtml(html) {
2124
2399
  new Api2.MessageEntityCustomEmoji({
2125
2400
  offset: open.offset,
2126
2401
  length,
2402
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- GramJS BigInteger compat
2127
2403
  documentId: BigInt(open.emojiId)
2128
2404
  })
2129
2405
  );
@@ -2262,11 +2538,14 @@ var InlineRouter = class {
2262
2538
  }
2263
2539
  async handleInlineQuery(ctx, pluginName, query, plugin) {
2264
2540
  try {
2541
+ const inlineQuery = ctx.inlineQuery;
2542
+ const from = ctx.from;
2543
+ if (!inlineQuery || !from || !plugin.onInlineQuery) return;
2265
2544
  const iqCtx = {
2266
2545
  query,
2267
- queryId: ctx.inlineQuery.id,
2268
- userId: ctx.from.id,
2269
- offset: ctx.inlineQuery.offset
2546
+ queryId: inlineQuery.id,
2547
+ userId: from.id,
2548
+ offset: inlineQuery.offset
2270
2549
  };
2271
2550
  const results = await withTimeout(
2272
2551
  plugin.onInlineQuery(iqCtx),
@@ -2291,7 +2570,7 @@ var InlineRouter = class {
2291
2570
  try {
2292
2571
  let matchedHandler;
2293
2572
  let matchGroups = [];
2294
- for (const entry of plugin.onCallback) {
2573
+ for (const entry of plugin.onCallback ?? []) {
2295
2574
  const groups = globMatch(entry.regex, strippedData);
2296
2575
  if (groups !== null) {
2297
2576
  matchedHandler = entry.handler;
@@ -2304,14 +2583,17 @@ var InlineRouter = class {
2304
2583
  return;
2305
2584
  }
2306
2585
  const gramjsBotRef = this.gramjsBot;
2586
+ const callbackQuery = ctx.callbackQuery;
2587
+ const from = ctx.from;
2588
+ if (!from || !callbackQuery) return;
2307
2589
  const cbCtx = {
2308
2590
  data: strippedData,
2309
2591
  match: matchGroups,
2310
- userId: ctx.from.id,
2311
- username: ctx.from.username,
2312
- inlineMessageId: ctx.callbackQuery.inline_message_id,
2592
+ userId: from.id,
2593
+ username: from.username,
2594
+ inlineMessageId: callbackQuery.inline_message_id,
2313
2595
  chatId: ctx.chat?.id?.toString(),
2314
- messageId: ctx.callbackQuery.message?.message_id,
2596
+ messageId: callbackQuery.message?.message_id,
2315
2597
  async answer(text, alert) {
2316
2598
  if (!answered) {
2317
2599
  answered = true;
@@ -2320,7 +2602,7 @@ var InlineRouter = class {
2320
2602
  },
2321
2603
  async editMessage(text, opts) {
2322
2604
  const styledButtons = opts?.keyboard ? prefixButtons(opts.keyboard, pluginName) : void 0;
2323
- const inlineMsgId = ctx.callbackQuery.inline_message_id;
2605
+ const inlineMsgId = ctx.callbackQuery?.inline_message_id;
2324
2606
  if (inlineMsgId && gramjsBotRef?.isConnected() && styledButtons) {
2325
2607
  try {
2326
2608
  const strippedHtml = stripCustomEmoji(text);
@@ -2334,10 +2616,9 @@ var InlineRouter = class {
2334
2616
  });
2335
2617
  return;
2336
2618
  } catch (error) {
2337
- if (error?.errorMessage === "MESSAGE_NOT_MODIFIED") return;
2338
- log7.debug(
2339
- `GramJS edit failed, falling back to Grammy: ${error?.errorMessage || error}`
2340
- );
2619
+ const errMsg = error?.errorMessage;
2620
+ if (errMsg === "MESSAGE_NOT_MODIFIED") return;
2621
+ log7.debug(`GramJS edit failed, falling back to Grammy: ${errMsg || error}`);
2341
2622
  }
2342
2623
  }
2343
2624
  const replyMarkup = styledButtons ? toGrammyKeyboard(styledButtons) : void 0;
@@ -2368,13 +2649,15 @@ var InlineRouter = class {
2368
2649
  }
2369
2650
  async handleChosenResult(ctx, pluginName, plugin) {
2370
2651
  try {
2371
- const resultId = ctx.chosenInlineResult.result_id;
2652
+ const chosenResult = ctx.chosenInlineResult;
2653
+ if (!chosenResult || !plugin.onChosenResult) return;
2654
+ const resultId = chosenResult.result_id;
2372
2655
  const colonIdx = resultId.indexOf(":");
2373
2656
  const strippedResultId = colonIdx > 0 ? resultId.slice(colonIdx + 1) : resultId;
2374
2657
  const crCtx = {
2375
2658
  resultId: strippedResultId,
2376
- inlineMessageId: ctx.chosenInlineResult.inline_message_id,
2377
- query: ctx.chosenInlineResult.query
2659
+ inlineMessageId: chosenResult.inline_message_id,
2660
+ query: chosenResult.query
2378
2661
  };
2379
2662
  await plugin.onChosenResult(crCtx);
2380
2663
  } catch (error) {
@@ -2460,6 +2743,30 @@ import { readdirSync as readdirSync2, readFileSync as readFileSync5, existsSync
2460
2743
  import { join as join5 } from "path";
2461
2744
  import { pathToFileURL } from "url";
2462
2745
  import { execFile } from "child_process";
2746
+
2747
+ // src/agent/tools/plugin-config-store.ts
2748
+ function getPluginPriorities(db) {
2749
+ const rows = db.prepare("SELECT plugin_name, priority FROM plugin_config").all();
2750
+ const map = /* @__PURE__ */ new Map();
2751
+ for (const row of rows) {
2752
+ map.set(row.plugin_name, row.priority);
2753
+ }
2754
+ return map;
2755
+ }
2756
+ function setPluginPriority(db, pluginName, priority) {
2757
+ db.prepare(
2758
+ `INSERT INTO plugin_config (plugin_name, priority, updated_at)
2759
+ VALUES (?, ?, datetime('now'))
2760
+ ON CONFLICT(plugin_name) DO UPDATE SET
2761
+ priority = excluded.priority,
2762
+ updated_at = excluded.updated_at`
2763
+ ).run(pluginName, priority);
2764
+ }
2765
+ function resetPluginPriority(db, pluginName) {
2766
+ db.prepare("DELETE FROM plugin_config WHERE plugin_name = ?").run(pluginName);
2767
+ }
2768
+
2769
+ // src/agent/tools/plugin-loader.ts
2463
2770
  import { promisify } from "util";
2464
2771
 
2465
2772
  // src/agent/tools/plugin-validator.ts
@@ -2491,7 +2798,14 @@ var ManifestSchema = z.object({
2491
2798
  inlinePerMinute: z.number().positive().optional(),
2492
2799
  callbackPerMinute: z.number().positive().optional()
2493
2800
  }).optional()
2494
- }).optional()
2801
+ }).optional(),
2802
+ hooks: z.array(
2803
+ z.object({
2804
+ name: z.string().min(1).max(64),
2805
+ priority: z.number().optional(),
2806
+ description: z.string().max(256).optional()
2807
+ })
2808
+ ).optional()
2495
2809
  });
2496
2810
  function validateManifest(raw) {
2497
2811
  return ManifestSchema.parse(raw);
@@ -2607,8 +2921,9 @@ async function sendTon(params) {
2607
2921
  log9.info(`Sent ${amount} TON to ${toAddress2.slice(0, 8)}... - seqno: ${seqno}`);
2608
2922
  return pseudoHash;
2609
2923
  } catch (error) {
2610
- const status = error?.status || error?.response?.status;
2611
- if (status === 429 || status >= 500) {
2924
+ const err = error;
2925
+ const status = err?.status || err?.response?.status;
2926
+ if (status === 429 || status !== void 0 && status >= 500) {
2612
2927
  invalidateTonClientCache();
2613
2928
  }
2614
2929
  log9.error({ err: error }, "Error sending TON");
@@ -3142,9 +3457,9 @@ var retryStatusCodes = /* @__PURE__ */ new Set([
3142
3457
  var nullBodyResponses = /* @__PURE__ */ new Set([101, 204, 205, 304]);
3143
3458
  function createFetch(globalOptions = {}) {
3144
3459
  const {
3145
- fetch: fetch2 = globalThis.fetch,
3460
+ fetch: fetch3 = globalThis.fetch,
3146
3461
  Headers: Headers2 = globalThis.Headers,
3147
- AbortController: AbortController2 = globalThis.AbortController
3462
+ AbortController: AbortController3 = globalThis.AbortController
3148
3463
  } = globalOptions;
3149
3464
  async function onError(context) {
3150
3465
  const isAbort = context.error && context.error.name === "AbortError" && !context.options.timeout || false;
@@ -3228,7 +3543,7 @@ function createFetch(globalOptions = {}) {
3228
3543
  }
3229
3544
  let abortTimeout;
3230
3545
  if (!context.options.signal && context.options.timeout) {
3231
- const controller = new AbortController2();
3546
+ const controller = new AbortController3();
3232
3547
  abortTimeout = setTimeout(() => {
3233
3548
  const error = new Error(
3234
3549
  "[TimeoutError]: The operation was aborted due to timeout"
@@ -3240,7 +3555,7 @@ function createFetch(globalOptions = {}) {
3240
3555
  context.options.signal = controller.signal;
3241
3556
  }
3242
3557
  try {
3243
- context.response = await fetch2(
3558
+ context.response = await fetch3(
3244
3559
  context.request,
3245
3560
  context.options
3246
3561
  );
@@ -3302,7 +3617,7 @@ function createFetch(globalOptions = {}) {
3302
3617
  return r3._data;
3303
3618
  };
3304
3619
  $fetch.raw = $fetchRaw;
3305
- $fetch.native = (...args) => fetch2(...args);
3620
+ $fetch.native = (...args) => fetch3(...args);
3306
3621
  $fetch.create = (defaultOptions = {}, customGlobalOptions = {}) => createFetch({
3307
3622
  ...globalOptions,
3308
3623
  ...customGlobalOptions,
@@ -3333,10 +3648,10 @@ function createNodeFetch() {
3333
3648
  return r(input, { ...nodeFetchOptions, ...init });
3334
3649
  };
3335
3650
  }
3336
- var fetch = globalThis.fetch ? (...args) => globalThis.fetch(...args) : createNodeFetch();
3651
+ var fetch2 = globalThis.fetch ? (...args) => globalThis.fetch(...args) : createNodeFetch();
3337
3652
  var Headers = globalThis.Headers || n;
3338
- var AbortController = globalThis.AbortController || T;
3339
- var ofetch = createFetch({ fetch, Headers, AbortController });
3653
+ var AbortController2 = globalThis.AbortController || T;
3654
+ var ofetch = createFetch({ fetch: fetch2, Headers, AbortController: AbortController2 });
3340
3655
 
3341
3656
  // node_modules/@ston-fi/api/dist/esm/index.js
3342
3657
  var __create = Object.create;
@@ -6192,7 +6507,7 @@ var stonApiClient = new StonApiClient();
6192
6507
  function isTon(asset) {
6193
6508
  return asset.toLowerCase() === "ton";
6194
6509
  }
6195
- async function getStonfiQuote(fromAsset, toAsset, amount, slippage, log13) {
6510
+ async function getStonfiQuote(fromAsset, toAsset, amount, slippage, log15) {
6196
6511
  try {
6197
6512
  const isTonInput = isTon(fromAsset);
6198
6513
  const isTonOutput = isTon(toAsset);
@@ -6223,11 +6538,11 @@ async function getStonfiQuote(fromAsset, toAsset, amount, slippage, log13) {
6223
6538
  fee: feeAmount.toFixed(6)
6224
6539
  };
6225
6540
  } catch (err) {
6226
- log13.debug("dex.quoteSTONfi() failed:", err);
6541
+ log15.debug("dex.quoteSTONfi() failed:", err);
6227
6542
  return null;
6228
6543
  }
6229
6544
  }
6230
- async function getDedustQuote(fromAsset, toAsset, amount, slippage, log13) {
6545
+ async function getDedustQuote(fromAsset, toAsset, amount, slippage, log15) {
6231
6546
  try {
6232
6547
  const isTonInput = isTon(fromAsset);
6233
6548
  const isTonOutput = isTon(toAsset);
@@ -6261,11 +6576,11 @@ async function getDedustQuote(fromAsset, toAsset, amount, slippage, log13) {
6261
6576
  poolType
6262
6577
  };
6263
6578
  } catch (err) {
6264
- log13.debug("dex.quoteDeDust() failed:", err);
6579
+ log15.debug("dex.quoteDeDust() failed:", err);
6265
6580
  return null;
6266
6581
  }
6267
6582
  }
6268
- async function executeSTONfiSwap(params, log13) {
6583
+ async function executeSTONfiSwap(params, _log) {
6269
6584
  const { fromAsset, toAsset, amount, slippage = 0.01 } = params;
6270
6585
  const walletData = loadWallet();
6271
6586
  if (!walletData) {
@@ -6352,7 +6667,7 @@ async function executeSTONfiSwap(params, log13) {
6352
6667
  };
6353
6668
  });
6354
6669
  }
6355
- async function executeDedustSwap(params, log13) {
6670
+ async function executeDedustSwap(params, _log) {
6356
6671
  const { fromAsset, toAsset, amount, slippage = 0.01 } = params;
6357
6672
  const walletData = loadWallet();
6358
6673
  if (!walletData) {
@@ -6372,7 +6687,7 @@ async function executeDedustSwap(params, log13) {
6372
6687
  const fromDecimals = await getDecimals(isTonInput ? "ton" : fromAsset);
6373
6688
  const toDecimals = await getDecimals(isTonOutput ? "ton" : toAsset);
6374
6689
  const amountIn = toUnits(amount, fromDecimals);
6375
- const { amountOut, tradeFee } = await pool.getEstimatedSwapOut({
6690
+ const { amountOut } = await pool.getEstimatedSwapOut({
6376
6691
  assetIn: fromAssetObj,
6377
6692
  amountIn
6378
6693
  });
@@ -6433,14 +6748,14 @@ function validateDexParams(amount, slippage) {
6433
6748
  throw new PluginSDKError("Slippage must be between 0 and 1", "OPERATION_FAILED");
6434
6749
  }
6435
6750
  }
6436
- function createDexSDK(log13) {
6751
+ function createDexSDK(log15) {
6437
6752
  return {
6438
6753
  async quote(params) {
6439
6754
  validateDexParams(params.amount, params.slippage);
6440
6755
  const slippage = params.slippage ?? 0.01;
6441
6756
  const [stonfi, dedust] = await Promise.all([
6442
- getStonfiQuote(params.fromAsset, params.toAsset, params.amount, slippage, log13),
6443
- getDedustQuote(params.fromAsset, params.toAsset, params.amount, slippage, log13)
6757
+ getStonfiQuote(params.fromAsset, params.toAsset, params.amount, slippage, log15),
6758
+ getDedustQuote(params.fromAsset, params.toAsset, params.amount, slippage, log15)
6444
6759
  ]);
6445
6760
  if (!stonfi && !dedust) {
6446
6761
  throw new PluginSDKError("No DEX has liquidity for this pair", "OPERATION_FAILED");
@@ -6474,7 +6789,7 @@ function createDexSDK(log13) {
6474
6789
  params.toAsset,
6475
6790
  params.amount,
6476
6791
  params.slippage ?? 0.01,
6477
- log13
6792
+ log15
6478
6793
  );
6479
6794
  },
6480
6795
  async quoteDeDust(params) {
@@ -6483,25 +6798,25 @@ function createDexSDK(log13) {
6483
6798
  params.toAsset,
6484
6799
  params.amount,
6485
6800
  params.slippage ?? 0.01,
6486
- log13
6801
+ log15
6487
6802
  );
6488
6803
  },
6489
6804
  async swap(params) {
6490
6805
  validateDexParams(params.amount, params.slippage);
6491
6806
  if (params.dex === "stonfi") {
6492
- return executeSTONfiSwap(params, log13);
6807
+ return executeSTONfiSwap(params, log15);
6493
6808
  }
6494
6809
  if (params.dex === "dedust") {
6495
- return executeDedustSwap(params, log13);
6810
+ return executeDedustSwap(params, log15);
6496
6811
  }
6497
6812
  const quoteResult = await this.quote(params);
6498
- return quoteResult.recommended === "stonfi" ? executeSTONfiSwap(params, log13) : executeDedustSwap(params, log13);
6813
+ return quoteResult.recommended === "stonfi" ? executeSTONfiSwap(params, log15) : executeDedustSwap(params, log15);
6499
6814
  },
6500
6815
  async swapSTONfi(params) {
6501
- return executeSTONfiSwap(params, log13);
6816
+ return executeSTONfiSwap(params, log15);
6502
6817
  },
6503
6818
  async swapDeDust(params) {
6504
- return executeDedustSwap(params, log13);
6819
+ return executeDedustSwap(params, log15);
6505
6820
  }
6506
6821
  };
6507
6822
  }
@@ -6538,7 +6853,7 @@ function normalizeDomain(domain) {
6538
6853
  if (!d.endsWith(".ton")) d += ".ton";
6539
6854
  return d;
6540
6855
  }
6541
- function createDnsSDK(log13) {
6856
+ function createDnsSDK(log15) {
6542
6857
  return {
6543
6858
  async check(domain) {
6544
6859
  const normalized = normalizeDomain(domain);
@@ -6561,7 +6876,7 @@ function createDnsSDK(log13) {
6561
6876
  };
6562
6877
  } catch (err) {
6563
6878
  if (err instanceof PluginSDKError) throw err;
6564
- log13.debug("dns.check() failed:", err);
6879
+ log15.debug("dns.check() failed:", err);
6565
6880
  throw new PluginSDKError(
6566
6881
  `Failed to check domain: ${err instanceof Error ? err.message : String(err)}`,
6567
6882
  "OPERATION_FAILED"
@@ -6574,7 +6889,7 @@ function createDnsSDK(log13) {
6574
6889
  const response = await tonapiFetch(`/dns/${encodeURIComponent(normalized)}`);
6575
6890
  if (response.status === 404) return null;
6576
6891
  if (!response.ok) {
6577
- log13.debug(`dns.resolve() TonAPI error: ${response.status}`);
6892
+ log15.debug(`dns.resolve() TonAPI error: ${response.status}`);
6578
6893
  return null;
6579
6894
  }
6580
6895
  const data = await response.json();
@@ -6586,7 +6901,7 @@ function createDnsSDK(log13) {
6586
6901
  expirationDate: data.expiring_at || void 0
6587
6902
  };
6588
6903
  } catch (err) {
6589
- log13.debug("dns.resolve() failed:", err);
6904
+ log15.debug("dns.resolve() failed:", err);
6590
6905
  return null;
6591
6906
  }
6592
6907
  },
@@ -6596,7 +6911,7 @@ function createDnsSDK(log13) {
6596
6911
  `/dns/auctions?tld=ton&limit=${Math.min(limit ?? 20, 100)}`
6597
6912
  );
6598
6913
  if (!response.ok) {
6599
- log13.debug(`dns.getAuctions() TonAPI error: ${response.status}`);
6914
+ log15.debug(`dns.getAuctions() TonAPI error: ${response.status}`);
6600
6915
  return [];
6601
6916
  }
6602
6917
  const data = await response.json();
@@ -6609,7 +6924,7 @@ function createDnsSDK(log13) {
6609
6924
  bids: a.bids || 0
6610
6925
  }));
6611
6926
  } catch (err) {
6612
- log13.debug("dns.getAuctions() failed:", err);
6927
+ log15.debug("dns.getAuctions() failed:", err);
6613
6928
  return [];
6614
6929
  }
6615
6930
  },
@@ -6648,7 +6963,10 @@ function createDnsSDK(log13) {
6648
6963
  throw new PluginSDKError(`No active auction found for ${normalized}`, "OPERATION_FAILED");
6649
6964
  }
6650
6965
  try {
6651
- await sendWalletMessage(Address6.parse(checkResult.nftAddress), toNano15(amount.toString()));
6966
+ await sendWalletMessage(
6967
+ Address6.parse(checkResult.nftAddress),
6968
+ toNano15(amount.toString())
6969
+ );
6652
6970
  return { domain: normalized, bidAmount: amount.toString(), success: true };
6653
6971
  } catch (err) {
6654
6972
  if (err instanceof PluginSDKError) throw err;
@@ -6745,7 +7063,7 @@ import {
6745
7063
  WalletContractV5R1 as WalletContractV5R14,
6746
7064
  internal as internal4
6747
7065
  } from "@ton/ton";
6748
- import { Address as TonAddress, beginCell as beginCell13, SendMode as SendMode4 } from "@ton/core";
7066
+ import { Address as TonAddress, beginCell as beginCell13, SendMode as SendMode4, storeMessage } from "@ton/core";
6749
7067
 
6750
7068
  // src/ton/format-transactions.ts
6751
7069
  import { fromNano } from "@ton/ton";
@@ -6920,25 +7238,25 @@ function findJettonBalance(balances, jettonAddress) {
6920
7238
  }
6921
7239
  });
6922
7240
  }
6923
- function cleanupOldTransactions(db, retentionDays, log13) {
7241
+ function cleanupOldTransactions(db, retentionDays, log15) {
6924
7242
  if (Math.random() > CLEANUP_PROBABILITY) return;
6925
7243
  try {
6926
7244
  const cutoff = Math.floor(Date.now() / 1e3) - retentionDays * 24 * 60 * 60;
6927
7245
  const result = db.prepare("DELETE FROM used_transactions WHERE used_at < ?").run(cutoff);
6928
7246
  if (result.changes > 0) {
6929
- log13.debug(`Cleaned up ${result.changes} old transaction records (>${retentionDays}d)`);
7247
+ log15.debug(`Cleaned up ${result.changes} old transaction records (>${retentionDays}d)`);
6930
7248
  }
6931
7249
  } catch (err) {
6932
- log13.error("Transaction cleanup failed:", err);
7250
+ log15.error("Transaction cleanup failed:", err);
6933
7251
  }
6934
7252
  }
6935
- function createTonSDK(log13, db) {
7253
+ function createTonSDK(log15, db) {
6936
7254
  return {
6937
7255
  getAddress() {
6938
7256
  try {
6939
7257
  return getWalletAddress();
6940
7258
  } catch (err) {
6941
- log13.error("ton.getAddress() failed:", err);
7259
+ log15.error("ton.getAddress() failed:", err);
6942
7260
  return null;
6943
7261
  }
6944
7262
  },
@@ -6948,7 +7266,7 @@ function createTonSDK(log13, db) {
6948
7266
  if (!addr) return null;
6949
7267
  return await getWalletBalance(addr);
6950
7268
  } catch (err) {
6951
- log13.error("ton.getBalance() failed:", err);
7269
+ log15.error("ton.getBalance() failed:", err);
6952
7270
  return null;
6953
7271
  }
6954
7272
  },
@@ -6956,7 +7274,7 @@ function createTonSDK(log13, db) {
6956
7274
  try {
6957
7275
  return await getTonPrice();
6958
7276
  } catch (err) {
6959
- log13.error("ton.getPrice() failed:", err);
7277
+ log15.error("ton.getPrice() failed:", err);
6960
7278
  return null;
6961
7279
  }
6962
7280
  },
@@ -7007,7 +7325,7 @@ function createTonSDK(log13, db) {
7007
7325
  );
7008
7326
  return formatTransactions(transactions);
7009
7327
  } catch (err) {
7010
- log13.error("ton.getTransactions() failed:", err);
7328
+ log15.error("ton.getTransactions() failed:", err);
7011
7329
  return [];
7012
7330
  }
7013
7331
  },
@@ -7030,7 +7348,7 @@ function createTonSDK(log13, db) {
7030
7348
  throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
7031
7349
  }
7032
7350
  const maxAgeMinutes = params.maxAgeMinutes ?? DEFAULT_MAX_AGE_MINUTES;
7033
- cleanupOldTransactions(db, DEFAULT_TX_RETENTION_DAYS, log13);
7351
+ cleanupOldTransactions(db, DEFAULT_TX_RETENTION_DAYS, log15);
7034
7352
  try {
7035
7353
  const txs = await this.getTransactions(address4, 20);
7036
7354
  for (const tx of txs) {
@@ -7064,7 +7382,7 @@ function createTonSDK(log13, db) {
7064
7382
  };
7065
7383
  } catch (err) {
7066
7384
  if (err instanceof PluginSDKError) throw err;
7067
- log13.error("ton.verifyPayment() failed:", err);
7385
+ log15.error("ton.verifyPayment() failed:", err);
7068
7386
  return {
7069
7387
  verified: false,
7070
7388
  error: `Verification failed: ${err instanceof Error ? err.message : String(err)}`
@@ -7078,7 +7396,7 @@ function createTonSDK(log13, db) {
7078
7396
  if (!addr) return [];
7079
7397
  const response = await tonapiFetch(`/accounts/${encodeURIComponent(addr)}/jettons`);
7080
7398
  if (!response.ok) {
7081
- log13.error(`ton.getJettonBalances() TonAPI error: ${response.status}`);
7399
+ log15.error(`ton.getJettonBalances() TonAPI error: ${response.status}`);
7082
7400
  return [];
7083
7401
  }
7084
7402
  const data = await response.json();
@@ -7102,7 +7420,7 @@ function createTonSDK(log13, db) {
7102
7420
  }
7103
7421
  return balances;
7104
7422
  } catch (err) {
7105
- log13.error("ton.getJettonBalances() failed:", err);
7423
+ log15.error("ton.getJettonBalances() failed:", err);
7106
7424
  return [];
7107
7425
  }
7108
7426
  },
@@ -7111,7 +7429,7 @@ function createTonSDK(log13, db) {
7111
7429
  const response = await tonapiFetch(`/jettons/${encodeURIComponent(jettonAddress)}`);
7112
7430
  if (response.status === 404) return null;
7113
7431
  if (!response.ok) {
7114
- log13.error(`ton.getJettonInfo() TonAPI error: ${response.status}`);
7432
+ log15.error(`ton.getJettonInfo() TonAPI error: ${response.status}`);
7115
7433
  return null;
7116
7434
  }
7117
7435
  const data = await response.json();
@@ -7129,7 +7447,7 @@ function createTonSDK(log13, db) {
7129
7447
  image: data.preview || metadata.image || void 0
7130
7448
  };
7131
7449
  } catch (err) {
7132
- log13.error("ton.getJettonInfo() failed:", err);
7450
+ log15.error("ton.getJettonInfo() failed:", err);
7133
7451
  return null;
7134
7452
  }
7135
7453
  },
@@ -7221,7 +7539,7 @@ function createTonSDK(log13, db) {
7221
7539
  if (status === 429 || status && status >= 500) {
7222
7540
  invalidateTonClientCache();
7223
7541
  if (attempt < MAX_SEND_ATTEMPTS) {
7224
- log13.warn(
7542
+ log15.warn(
7225
7543
  `sendJetton attempt ${attempt} failed (${status}): ${JSON.stringify(respData ?? err.message)}, retrying...`
7226
7544
  );
7227
7545
  await new Promise((r3) => setTimeout(r3, 1e3 * attempt));
@@ -7250,17 +7568,197 @@ function createTonSDK(log13, db) {
7250
7568
  try {
7251
7569
  const response = await tonapiFetch(`/accounts/${encodeURIComponent(ownerAddress)}/jettons`);
7252
7570
  if (!response.ok) {
7253
- log13.error(`ton.getJettonWalletAddress() TonAPI error: ${response.status}`);
7571
+ log15.error(`ton.getJettonWalletAddress() TonAPI error: ${response.status}`);
7254
7572
  return null;
7255
7573
  }
7256
7574
  const data = await response.json();
7257
7575
  const match = findJettonBalance(data.balances ?? [], jettonAddress);
7258
7576
  return match ? match.wallet_address.address : null;
7259
7577
  } catch (err) {
7260
- log13.error("ton.getJettonWalletAddress() failed:", err);
7578
+ log15.error("ton.getJettonWalletAddress() failed:", err);
7579
+ return null;
7580
+ }
7581
+ },
7582
+ // ─── Signed Transfers (no broadcast) ──────────────────────────
7583
+ async createTransfer(to, amount, comment) {
7584
+ const walletData = loadWallet();
7585
+ if (!walletData) {
7586
+ throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
7587
+ }
7588
+ if (!Number.isFinite(amount) || amount <= 0) {
7589
+ throw new PluginSDKError("Amount must be a positive number", "OPERATION_FAILED");
7590
+ }
7591
+ try {
7592
+ TonAddress.parse(to);
7593
+ } catch {
7594
+ throw new PluginSDKError("Invalid TON address format", "INVALID_ADDRESS");
7595
+ }
7596
+ try {
7597
+ const keyPair = await getKeyPair();
7598
+ if (!keyPair) {
7599
+ throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
7600
+ }
7601
+ const boc = await withTxLock(async () => {
7602
+ const wallet = WalletContractV5R14.create({
7603
+ workchain: 0,
7604
+ publicKey: keyPair.publicKey
7605
+ });
7606
+ const client = await getCachedTonClient();
7607
+ const contract = client.open(wallet);
7608
+ const seqno = await contract.getSeqno();
7609
+ const transferCell = wallet.createTransfer({
7610
+ seqno,
7611
+ secretKey: keyPair.secretKey,
7612
+ sendMode: SendMode4.PAY_GAS_SEPARATELY,
7613
+ messages: [
7614
+ internal4({
7615
+ to: TonAddress.parse(to),
7616
+ value: tonToNano(amount),
7617
+ body: comment || "",
7618
+ bounce: false
7619
+ })
7620
+ ]
7621
+ });
7622
+ const extMsg = beginCell13().store(
7623
+ storeMessage({
7624
+ info: {
7625
+ type: "external-in",
7626
+ dest: wallet.address,
7627
+ importFee: 0n
7628
+ },
7629
+ init: seqno === 0 ? wallet.init : void 0,
7630
+ body: transferCell
7631
+ })
7632
+ ).endCell();
7633
+ return extMsg.toBoc().toString("base64");
7634
+ });
7635
+ return {
7636
+ boc,
7637
+ publicKey: walletData.publicKey,
7638
+ walletVersion: "v5r1"
7639
+ };
7640
+ } catch (err) {
7641
+ if (err instanceof PluginSDKError) throw err;
7642
+ throw new PluginSDKError(
7643
+ `Failed to create transfer: ${err instanceof Error ? err.message : String(err)}`,
7644
+ "OPERATION_FAILED"
7645
+ );
7646
+ }
7647
+ },
7648
+ async createJettonTransfer(jettonAddress, to, amount, opts) {
7649
+ const walletData = loadWallet();
7650
+ if (!walletData) {
7651
+ throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
7652
+ }
7653
+ if (!Number.isFinite(amount) || amount <= 0) {
7654
+ throw new PluginSDKError("Amount must be a positive number", "OPERATION_FAILED");
7655
+ }
7656
+ try {
7657
+ TonAddress.parse(to);
7658
+ } catch {
7659
+ throw new PluginSDKError("Invalid recipient address", "INVALID_ADDRESS");
7660
+ }
7661
+ try {
7662
+ const jettonsResponse = await tonapiFetch(
7663
+ `/accounts/${encodeURIComponent(walletData.address)}/jettons`
7664
+ );
7665
+ if (!jettonsResponse.ok) {
7666
+ throw new PluginSDKError(
7667
+ `Failed to fetch jetton balances: ${jettonsResponse.status}`,
7668
+ "OPERATION_FAILED"
7669
+ );
7670
+ }
7671
+ const jettonsData = await jettonsResponse.json();
7672
+ const jettonBalance = findJettonBalance(jettonsData.balances ?? [], jettonAddress);
7673
+ if (!jettonBalance) {
7674
+ throw new PluginSDKError(
7675
+ `You don't own any of this jetton: ${jettonAddress}`,
7676
+ "OPERATION_FAILED"
7677
+ );
7678
+ }
7679
+ const senderJettonWallet = jettonBalance.wallet_address.address;
7680
+ const decimals = jettonBalance.jetton.decimals ?? 9;
7681
+ const currentBalance = BigInt(jettonBalance.balance);
7682
+ const amountStr = amount.toFixed(decimals);
7683
+ const [whole, frac = ""] = amountStr.split(".");
7684
+ const amountInUnits = BigInt(whole + (frac + "0".repeat(decimals)).slice(0, decimals));
7685
+ if (amountInUnits > currentBalance) {
7686
+ const balStr = formatTokenBalance(currentBalance, decimals);
7687
+ throw new PluginSDKError(
7688
+ `Insufficient balance. Have ${balStr}, need ${amount}`,
7689
+ "OPERATION_FAILED"
7690
+ );
7691
+ }
7692
+ const comment = opts?.comment;
7693
+ let forwardPayload = beginCell13().endCell();
7694
+ if (comment) {
7695
+ forwardPayload = beginCell13().storeUint(0, 32).storeStringTail(comment).endCell();
7696
+ }
7697
+ const JETTON_TRANSFER_OP = 260734629;
7698
+ const messageBody = beginCell13().storeUint(JETTON_TRANSFER_OP, 32).storeUint(0, 64).storeCoins(amountInUnits).storeAddress(TonAddress.parse(to)).storeAddress(TonAddress.parse(walletData.address)).storeBit(false).storeCoins(comment ? tonToNano("0.01") : BigInt(1)).storeBit(comment ? 1 : 0).storeRef(comment ? forwardPayload : beginCell13().endCell()).endCell();
7699
+ const keyPair = await getKeyPair();
7700
+ if (!keyPair) {
7701
+ throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
7702
+ }
7703
+ const boc = await withTxLock(async () => {
7704
+ const wallet = WalletContractV5R14.create({
7705
+ workchain: 0,
7706
+ publicKey: keyPair.publicKey
7707
+ });
7708
+ const client = await getCachedTonClient();
7709
+ const walletContract = client.open(wallet);
7710
+ const seqno = await walletContract.getSeqno();
7711
+ const transferCell = wallet.createTransfer({
7712
+ seqno,
7713
+ secretKey: keyPair.secretKey,
7714
+ sendMode: SendMode4.PAY_GAS_SEPARATELY,
7715
+ messages: [
7716
+ internal4({
7717
+ to: TonAddress.parse(senderJettonWallet),
7718
+ value: tonToNano("0.05"),
7719
+ body: messageBody,
7720
+ bounce: true
7721
+ })
7722
+ ]
7723
+ });
7724
+ const extMsg = beginCell13().store(
7725
+ storeMessage({
7726
+ info: {
7727
+ type: "external-in",
7728
+ dest: wallet.address,
7729
+ importFee: 0n
7730
+ },
7731
+ init: seqno === 0 ? wallet.init : void 0,
7732
+ body: transferCell
7733
+ })
7734
+ ).endCell();
7735
+ return extMsg.toBoc().toString("base64");
7736
+ });
7737
+ return {
7738
+ boc,
7739
+ publicKey: walletData.publicKey,
7740
+ walletVersion: "v5r1"
7741
+ };
7742
+ } catch (err) {
7743
+ if (err instanceof PluginSDKError) throw err;
7744
+ throw new PluginSDKError(
7745
+ `Failed to create jetton transfer: ${err instanceof Error ? err.message : String(err)}`,
7746
+ "OPERATION_FAILED"
7747
+ );
7748
+ }
7749
+ },
7750
+ getPublicKey() {
7751
+ try {
7752
+ const wallet = loadWallet();
7753
+ return wallet?.publicKey ?? null;
7754
+ } catch (err) {
7755
+ log15.error("ton.getPublicKey() failed:", err);
7261
7756
  return null;
7262
7757
  }
7263
7758
  },
7759
+ getWalletVersion() {
7760
+ return "v5r1";
7761
+ },
7264
7762
  // ─── NFT ─────────────────────────────────────────────────────
7265
7763
  async getNftItems(ownerAddress) {
7266
7764
  try {
@@ -7270,14 +7768,14 @@ function createTonSDK(log13, db) {
7270
7768
  `/accounts/${encodeURIComponent(addr)}/nfts?limit=100&indirect_ownership=true`
7271
7769
  );
7272
7770
  if (!response.ok) {
7273
- log13.error(`ton.getNftItems() TonAPI error: ${response.status}`);
7771
+ log15.error(`ton.getNftItems() TonAPI error: ${response.status}`);
7274
7772
  return [];
7275
7773
  }
7276
7774
  const data = await response.json();
7277
7775
  if (!Array.isArray(data.nft_items)) return [];
7278
7776
  return data.nft_items.filter((item) => item.trust !== "blacklist").map((item) => mapNftItem(item));
7279
7777
  } catch (err) {
7280
- log13.error("ton.getNftItems() failed:", err);
7778
+ log15.error("ton.getNftItems() failed:", err);
7281
7779
  return [];
7282
7780
  }
7283
7781
  },
@@ -7286,13 +7784,13 @@ function createTonSDK(log13, db) {
7286
7784
  const response = await tonapiFetch(`/nfts/${encodeURIComponent(nftAddress)}`);
7287
7785
  if (response.status === 404) return null;
7288
7786
  if (!response.ok) {
7289
- log13.error(`ton.getNftInfo() TonAPI error: ${response.status}`);
7787
+ log15.error(`ton.getNftInfo() TonAPI error: ${response.status}`);
7290
7788
  return null;
7291
7789
  }
7292
7790
  const item = await response.json();
7293
7791
  return mapNftItem(item);
7294
7792
  } catch (err) {
7295
- log13.error("ton.getNftInfo() failed:", err);
7793
+ log15.error("ton.getNftInfo() failed:", err);
7296
7794
  return null;
7297
7795
  }
7298
7796
  },
@@ -7325,7 +7823,7 @@ function createTonSDK(log13, db) {
7325
7823
  `/rates?tokens=${encodeURIComponent(jettonAddress)}&currencies=usd,ton`
7326
7824
  );
7327
7825
  if (!response.ok) {
7328
- log13.debug(`ton.getJettonPrice() TonAPI error: ${response.status}`);
7826
+ log15.debug(`ton.getJettonPrice() TonAPI error: ${response.status}`);
7329
7827
  return null;
7330
7828
  }
7331
7829
  const data = await response.json();
@@ -7339,7 +7837,7 @@ function createTonSDK(log13, db) {
7339
7837
  change30d: rateData.diff_30d?.USD ?? null
7340
7838
  };
7341
7839
  } catch (err) {
7342
- log13.debug("ton.getJettonPrice() failed:", err);
7840
+ log15.debug("ton.getJettonPrice() failed:", err);
7343
7841
  return null;
7344
7842
  }
7345
7843
  },
@@ -7353,7 +7851,7 @@ function createTonSDK(log13, db) {
7353
7851
  tonapiFetch(`/jettons/${encodeURIComponent(jettonAddress)}`)
7354
7852
  ]);
7355
7853
  if (!holdersResponse.ok) {
7356
- log13.debug(`ton.getJettonHolders() TonAPI error: ${holdersResponse.status}`);
7854
+ log15.debug(`ton.getJettonHolders() TonAPI error: ${holdersResponse.status}`);
7357
7855
  return [];
7358
7856
  }
7359
7857
  const data = await holdersResponse.json();
@@ -7373,7 +7871,7 @@ function createTonSDK(log13, db) {
7373
7871
  };
7374
7872
  });
7375
7873
  } catch (err) {
7376
- log13.debug("ton.getJettonHolders() failed:", err);
7874
+ log15.debug("ton.getJettonHolders() failed:", err);
7377
7875
  return [];
7378
7876
  }
7379
7877
  },
@@ -7445,13 +7943,13 @@ function createTonSDK(log13, db) {
7445
7943
  holders: holdersCount
7446
7944
  };
7447
7945
  } catch (err) {
7448
- log13.debug("ton.getJettonHistory() failed:", err);
7946
+ log15.debug("ton.getJettonHistory() failed:", err);
7449
7947
  return null;
7450
7948
  }
7451
7949
  },
7452
7950
  // ─── Sub-namespaces ───────────────────────────────────────────
7453
- dex: Object.freeze(createDexSDK(log13)),
7454
- dns: Object.freeze(createDnsSDK(log13))
7951
+ dex: Object.freeze(createDexSDK(log15)),
7952
+ dns: Object.freeze(createDnsSDK(log15))
7455
7953
  };
7456
7954
  }
7457
7955
  function mapNftItem(item) {
@@ -7500,6 +7998,7 @@ function toSimpleMessage(msg) {
7500
7998
  return {
7501
7999
  id: msg.id,
7502
8000
  text: msg.message ?? "",
8001
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- GramJS fromId is a union of untyped peer types
7503
8002
  senderId: Number(msg.fromId?.userId ?? msg.fromId?.channelId ?? 0),
7504
8003
  timestamp: new Date(msg.date * 1e3)
7505
8004
  };
@@ -7513,7 +8012,7 @@ async function getApi() {
7513
8012
  }
7514
8013
 
7515
8014
  // src/sdk/telegram-messages.ts
7516
- function createTelegramMessagesSDK(bridge, log13) {
8015
+ function createTelegramMessagesSDK(bridge, log15) {
7517
8016
  function requireBridge2() {
7518
8017
  requireBridge(bridge);
7519
8018
  }
@@ -7620,7 +8119,7 @@ function createTelegramMessagesSDK(bridge, log13) {
7620
8119
  return (resultData.messages ?? []).map(toSimpleMessage);
7621
8120
  } catch (err) {
7622
8121
  if (err instanceof PluginSDKError) throw err;
7623
- log13.error("telegram.searchMessages() failed:", err);
8122
+ log15.error("telegram.searchMessages() failed:", err);
7624
8123
  return [];
7625
8124
  }
7626
8125
  },
@@ -7657,6 +8156,7 @@ function createTelegramMessagesSDK(bridge, log13) {
7657
8156
  limit,
7658
8157
  maxId: 0,
7659
8158
  minId: 0,
8159
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- GramJS BigInteger compat requires bigint cast
7660
8160
  hash: 0n
7661
8161
  })
7662
8162
  );
@@ -7847,6 +8347,7 @@ function createTelegramMessagesSDK(bridge, log13) {
7847
8347
  const result = await gramJsClient.invoke(
7848
8348
  new Api4.messages.GetScheduledHistory({
7849
8349
  peer,
8350
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- GramJS BigInteger compat requires bigint cast
7850
8351
  hash: 0n
7851
8352
  })
7852
8353
  );
@@ -7861,7 +8362,7 @@ function createTelegramMessagesSDK(bridge, log13) {
7861
8362
  return messages;
7862
8363
  } catch (err) {
7863
8364
  if (err instanceof PluginSDKError) throw err;
7864
- log13.error("telegram.getScheduledMessages() failed:", err);
8365
+ log15.error("telegram.getScheduledMessages() failed:", err);
7865
8366
  return [];
7866
8367
  }
7867
8368
  },
@@ -7922,7 +8423,7 @@ function createTelegramMessagesSDK(bridge, log13) {
7922
8423
  }
7923
8424
 
7924
8425
  // src/sdk/telegram-social.ts
7925
- function createTelegramSocialSDK(bridge, log13) {
8426
+ function createTelegramSocialSDK(bridge, log15) {
7926
8427
  function requireBridge2() {
7927
8428
  requireBridge(bridge);
7928
8429
  }
@@ -7997,7 +8498,7 @@ function createTelegramSocialSDK(bridge, log13) {
7997
8498
  return null;
7998
8499
  } catch (err) {
7999
8500
  if (err instanceof PluginSDKError) throw err;
8000
- log13.error("telegram.getChatInfo() failed:", err);
8501
+ log15.error("telegram.getChatInfo() failed:", err);
8001
8502
  return null;
8002
8503
  }
8003
8504
  },
@@ -8005,7 +8506,6 @@ function createTelegramSocialSDK(bridge, log13) {
8005
8506
  requireBridge2();
8006
8507
  try {
8007
8508
  const client = getClient2();
8008
- const Api4 = await getApi();
8009
8509
  let entity;
8010
8510
  try {
8011
8511
  const id = typeof userId === "string" ? userId.replace("@", "") : userId.toString();
@@ -8112,7 +8612,7 @@ function createTelegramSocialSDK(bridge, log13) {
8112
8612
  });
8113
8613
  } catch (err) {
8114
8614
  if (err instanceof PluginSDKError) throw err;
8115
- log13.error("telegram.getParticipants() failed:", err);
8615
+ log15.error("telegram.getParticipants() failed:", err);
8116
8616
  return [];
8117
8617
  }
8118
8618
  },
@@ -8474,7 +8974,7 @@ function createTelegramSocialSDK(bridge, log13) {
8474
8974
  }));
8475
8975
  } catch (err) {
8476
8976
  if (err instanceof PluginSDKError) throw err;
8477
- log13.error("telegram.getDialogs() failed:", err);
8977
+ log15.error("telegram.getDialogs() failed:", err);
8478
8978
  return [];
8479
8979
  }
8480
8980
  },
@@ -8488,7 +8988,7 @@ function createTelegramSocialSDK(bridge, log13) {
8488
8988
  return messages.map(toSimpleMessage);
8489
8989
  } catch (err) {
8490
8990
  if (err instanceof PluginSDKError) throw err;
8491
- log13.error("telegram.getHistory() failed:", err);
8991
+ log15.error("telegram.getHistory() failed:", err);
8492
8992
  return [];
8493
8993
  }
8494
8994
  },
@@ -8519,7 +9019,7 @@ function createTelegramSocialSDK(bridge, log13) {
8519
9019
  }));
8520
9020
  } catch (err) {
8521
9021
  if (err instanceof PluginSDKError) throw err;
8522
- log13.error("telegram.getStarsTransactions() failed:", err);
9022
+ log15.error("telegram.getStarsTransactions() failed:", err);
8523
9023
  return [];
8524
9024
  }
8525
9025
  },
@@ -8624,7 +9124,7 @@ function createTelegramSocialSDK(bridge, log13) {
8624
9124
  };
8625
9125
  } catch (err) {
8626
9126
  if (err instanceof PluginSDKError) throw err;
8627
- log13.error("telegram.getCollectibleInfo() failed:", err);
9127
+ log15.error("telegram.getCollectibleInfo() failed:", err);
8628
9128
  return null;
8629
9129
  }
8630
9130
  },
@@ -8662,7 +9162,7 @@ function createTelegramSocialSDK(bridge, log13) {
8662
9162
  } catch (err) {
8663
9163
  if (err.errorMessage === "STARGIFT_SLUG_INVALID") return null;
8664
9164
  if (err instanceof PluginSDKError) throw err;
8665
- log13.error("telegram.getUniqueGift() failed:", err);
9165
+ log15.error("telegram.getUniqueGift() failed:", err);
8666
9166
  return null;
8667
9167
  }
8668
9168
  },
@@ -8688,7 +9188,7 @@ function createTelegramSocialSDK(bridge, log13) {
8688
9188
  } catch (err) {
8689
9189
  if (err.errorMessage === "STARGIFT_SLUG_INVALID") return null;
8690
9190
  if (err instanceof PluginSDKError) throw err;
8691
- log13.error("telegram.getUniqueGiftValue() failed:", err);
9191
+ log15.error("telegram.getUniqueGiftValue() failed:", err);
8692
9192
  return null;
8693
9193
  }
8694
9194
  },
@@ -8723,7 +9223,7 @@ function createTelegramSocialSDK(bridge, log13) {
8723
9223
  const client = getClient2();
8724
9224
  const { Api: Api4, helpers } = await import("telegram");
8725
9225
  const { CustomFile } = await import("telegram/client/uploads.js");
8726
- const { readFileSync: readFileSync7, statSync: statSync2 } = await import("fs");
9226
+ const { readFileSync: readFileSync8, statSync: statSync2 } = await import("fs");
8727
9227
  const { basename: basename2 } = await import("path");
8728
9228
  const { resolve: resolve2, normalize: normalize2 } = await import("path");
8729
9229
  const { homedir: homedir3 } = await import("os");
@@ -8748,7 +9248,7 @@ function createTelegramSocialSDK(bridge, log13) {
8748
9248
  }
8749
9249
  const fileName = basename2(filePath);
8750
9250
  const fileSize = statSync2(filePath).size;
8751
- const fileBuffer = readFileSync7(filePath);
9251
+ const fileBuffer = readFileSync8(filePath);
8752
9252
  const isVideo = filePath.toLowerCase().match(/\.(mp4|mov|avi|webm|mkv|m4v)$/);
8753
9253
  const customFile = new CustomFile(fileName, fileSize, filePath, fileBuffer);
8754
9254
  const uploadedFile = await client.uploadFile({
@@ -8799,7 +9299,7 @@ function createTelegramSocialSDK(bridge, log13) {
8799
9299
  }
8800
9300
 
8801
9301
  // src/sdk/telegram.ts
8802
- function createTelegramSDK(bridge, log13) {
9302
+ function createTelegramSDK(bridge, log15) {
8803
9303
  function requireBridge2() {
8804
9304
  requireBridge(bridge);
8805
9305
  }
@@ -8903,7 +9403,7 @@ function createTelegramSDK(bridge, log13) {
8903
9403
  timestamp: m.timestamp
8904
9404
  }));
8905
9405
  } catch (err) {
8906
- log13.error("telegram.getMessages() failed:", err);
9406
+ log15.error("telegram.getMessages() failed:", err);
8907
9407
  return [];
8908
9408
  }
8909
9409
  },
@@ -8925,7 +9425,7 @@ function createTelegramSDK(bridge, log13) {
8925
9425
  return bridge.isAvailable();
8926
9426
  },
8927
9427
  getRawClient() {
8928
- log13.warn("getRawClient() called \u2014 this bypasses SDK sandbox guarantees");
9428
+ log15.warn("getRawClient() called \u2014 this bypasses SDK sandbox guarantees");
8929
9429
  if (!bridge.isAvailable()) return null;
8930
9430
  try {
8931
9431
  return bridge.getClient().getClient();
@@ -8934,8 +9434,8 @@ function createTelegramSDK(bridge, log13) {
8934
9434
  }
8935
9435
  },
8936
9436
  // Spread extended methods from sub-modules
8937
- ...createTelegramMessagesSDK(bridge, log13),
8938
- ...createTelegramSocialSDK(bridge, log13)
9437
+ ...createTelegramMessagesSDK(bridge, log15),
9438
+ ...createTelegramSocialSDK(bridge, log15)
8939
9439
  };
8940
9440
  }
8941
9441
 
@@ -8976,23 +9476,23 @@ function deletePluginSecret(pluginName, key) {
8976
9476
  function listPluginSecretKeys(pluginName) {
8977
9477
  return Object.keys(readSecretsFile(pluginName));
8978
9478
  }
8979
- function createSecretsSDK(pluginName, pluginConfig, log13) {
9479
+ function createSecretsSDK(pluginName, pluginConfig, log15) {
8980
9480
  const envPrefix = pluginName.replace(/-/g, "_").toUpperCase();
8981
9481
  function get(key) {
8982
9482
  const envKey = `${envPrefix}_${key.toUpperCase()}`;
8983
9483
  const envValue = process.env[envKey];
8984
9484
  if (envValue) {
8985
- log13.debug(`Secret "${key}" resolved from env var ${envKey}`);
9485
+ log15.debug(`Secret "${key}" resolved from env var ${envKey}`);
8986
9486
  return envValue;
8987
9487
  }
8988
9488
  const stored = readSecretsFile(pluginName);
8989
9489
  if (key in stored && stored[key]) {
8990
- log13.debug(`Secret "${key}" resolved from secrets store`);
9490
+ log15.debug(`Secret "${key}" resolved from secrets store`);
8991
9491
  return stored[key];
8992
9492
  }
8993
9493
  const configValue = pluginConfig[key];
8994
9494
  if (configValue !== void 0 && configValue !== null) {
8995
- log13.debug(`Secret "${key}" resolved from pluginConfig`);
9495
+ log15.debug(`Secret "${key}" resolved from pluginConfig`);
8996
9496
  return String(configValue);
8997
9497
  }
8998
9498
  return void 0;
@@ -9094,7 +9594,7 @@ function createStorageSDK(db) {
9094
9594
  }
9095
9595
 
9096
9596
  // src/sdk/bot.ts
9097
- function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLimiter, log13) {
9597
+ function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLimiter, log15) {
9098
9598
  if (!router || !manifest || !manifest.inline && !manifest.callbacks) {
9099
9599
  return null;
9100
9600
  }
@@ -9102,7 +9602,7 @@ function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLi
9102
9602
  const callbackLimit = manifest.rateLimits?.callbackPerMinute ?? 60;
9103
9603
  const handlers = {};
9104
9604
  function syncToRouter() {
9105
- router.registerPlugin(pluginName, { ...handlers });
9605
+ router?.registerPlugin(pluginName, { ...handlers });
9106
9606
  }
9107
9607
  const sdk = {
9108
9608
  get isAvailable() {
@@ -9117,7 +9617,7 @@ function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLi
9117
9617
  },
9118
9618
  onInlineQuery(handler) {
9119
9619
  if (handlers.onInlineQuery) {
9120
- log13.warn("onInlineQuery called again \u2014 overwriting previous handler");
9620
+ log15.warn("onInlineQuery called again \u2014 overwriting previous handler");
9121
9621
  }
9122
9622
  handlers.onInlineQuery = async (ctx) => {
9123
9623
  if (rateLimiter) {
@@ -9163,7 +9663,7 @@ function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLi
9163
9663
  return;
9164
9664
  } catch (error) {
9165
9665
  if (error?.errorMessage === "MESSAGE_NOT_MODIFIED") return;
9166
- log13.warn(`GramJS edit failed, falling back to Grammy: ${error?.errorMessage || error}`);
9666
+ log15.warn(`GramJS edit failed, falling back to Grammy: ${error?.errorMessage || error}`);
9167
9667
  }
9168
9668
  }
9169
9669
  if (grammyBot) {
@@ -9176,7 +9676,7 @@ function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLi
9176
9676
  });
9177
9677
  } catch (error) {
9178
9678
  if (error?.description?.includes("message is not modified")) return;
9179
- log13.error(`Failed to edit inline message: ${error?.description || error}`);
9679
+ log15.error(`Failed to edit inline message: ${error?.description || error}`);
9180
9680
  }
9181
9681
  }
9182
9682
  },
@@ -9235,13 +9735,13 @@ function createSafeDb(db) {
9235
9735
  });
9236
9736
  }
9237
9737
  function createPluginSDK(deps, opts) {
9238
- const log13 = createLogger2(opts.pluginName);
9738
+ const log15 = createLogger2(opts.pluginName);
9239
9739
  const safeDb = opts.db ? createSafeDb(opts.db) : null;
9240
- const ton = Object.freeze(createTonSDK(log13, safeDb));
9241
- const telegram = Object.freeze(createTelegramSDK(deps.bridge, log13));
9242
- const secrets = Object.freeze(createSecretsSDK(opts.pluginName, opts.pluginConfig, log13));
9740
+ const ton = Object.freeze(createTonSDK(log15, safeDb));
9741
+ const telegram = Object.freeze(createTelegramSDK(deps.bridge, log15));
9742
+ const secrets = Object.freeze(createSecretsSDK(opts.pluginName, opts.pluginConfig, log15));
9243
9743
  const storage = safeDb ? Object.freeze(createStorageSDK(safeDb)) : null;
9244
- const frozenLog = Object.freeze(log13);
9744
+ const frozenLog = Object.freeze(log15);
9245
9745
  const frozenConfig = Object.freeze(JSON.parse(JSON.stringify(opts.sanitizedConfig ?? {})));
9246
9746
  const frozenPluginConfig = Object.freeze(JSON.parse(JSON.stringify(opts.pluginConfig ?? {})));
9247
9747
  let cachedBot;
@@ -9269,6 +9769,36 @@ function createPluginSDK(deps, opts) {
9269
9769
  );
9270
9770
  if (result) cachedBot = result;
9271
9771
  return result;
9772
+ },
9773
+ on(hookName, handler, onOpts) {
9774
+ if (!opts.hookRegistry) {
9775
+ log15.warn(`Hook registration unavailable \u2014 sdk.on() ignored`);
9776
+ return;
9777
+ }
9778
+ if (opts.declaredHooks) {
9779
+ const declared = opts.declaredHooks.some((h2) => h2.name === hookName);
9780
+ if (!declared) {
9781
+ log15.warn(`Hook "${hookName}" not declared in manifest \u2014 registration rejected`);
9782
+ return;
9783
+ }
9784
+ }
9785
+ const rawPriority = Number(onOpts?.priority) || 0;
9786
+ const clampedPriority = Math.max(-1e3, Math.min(1e3, rawPriority));
9787
+ if (rawPriority !== clampedPriority) {
9788
+ log15.debug(`Hook "${hookName}" priority ${rawPriority} clamped to ${clampedPriority}`);
9789
+ }
9790
+ const registered = opts.hookRegistry.register({
9791
+ pluginId: opts.pluginName,
9792
+ hookName,
9793
+ handler,
9794
+ priority: clampedPriority,
9795
+ globalPriority: opts.globalPriority ?? 0
9796
+ });
9797
+ if (!registered) {
9798
+ log15.warn(
9799
+ `Hook registration limit reached for plugin "${opts.pluginName}" \u2014 "${hookName}" rejected`
9800
+ );
9801
+ }
9272
9802
  }
9273
9803
  };
9274
9804
  return Object.freeze(sdk);
@@ -9329,11 +9859,64 @@ function semverSatisfies(current, range) {
9329
9859
  return cur.major === req.major && cur.minor === req.minor && cur.patch === req.patch;
9330
9860
  }
9331
9861
 
9862
+ // src/sdk/hooks/registry.ts
9863
+ var MAX_HOOKS_PER_PLUGIN = 100;
9864
+ var HookRegistry = class {
9865
+ hooks = [];
9866
+ hookMap = /* @__PURE__ */ new Map();
9867
+ rebuildMap() {
9868
+ this.hookMap.clear();
9869
+ for (const h2 of this.hooks) {
9870
+ let arr = this.hookMap.get(h2.hookName);
9871
+ if (!arr) {
9872
+ arr = [];
9873
+ this.hookMap.set(h2.hookName, arr);
9874
+ }
9875
+ arr.push(h2);
9876
+ }
9877
+ for (const arr of this.hookMap.values()) {
9878
+ arr.sort((a, b) => {
9879
+ const aPrio = a.globalPriority + a.priority;
9880
+ const bPrio = b.globalPriority + b.priority;
9881
+ return aPrio - bPrio;
9882
+ });
9883
+ }
9884
+ }
9885
+ register(reg) {
9886
+ const pluginHookCount = this.hooks.filter((h2) => h2.pluginId === reg.pluginId).length;
9887
+ if (pluginHookCount >= MAX_HOOKS_PER_PLUGIN) {
9888
+ return false;
9889
+ }
9890
+ this.hooks.push({ ...reg, globalPriority: reg.globalPriority ?? 0 });
9891
+ this.rebuildMap();
9892
+ return true;
9893
+ }
9894
+ getHooks(name) {
9895
+ return this.hookMap.get(name) ?? [];
9896
+ }
9897
+ hasHooks(name) {
9898
+ return (this.hookMap.get(name)?.length ?? 0) > 0;
9899
+ }
9900
+ hasAnyHooks() {
9901
+ return this.hooks.length > 0;
9902
+ }
9903
+ unregister(pluginId) {
9904
+ const before = this.hooks.length;
9905
+ this.hooks = this.hooks.filter((h2) => h2.pluginId !== pluginId);
9906
+ this.rebuildMap();
9907
+ return before - this.hooks.length;
9908
+ }
9909
+ clear() {
9910
+ this.hooks = [];
9911
+ this.hookMap.clear();
9912
+ }
9913
+ };
9914
+
9332
9915
  // src/agent/tools/plugin-loader.ts
9333
9916
  var execFileAsync = promisify(execFile);
9334
9917
  var log12 = createLogger("PluginLoader");
9335
9918
  var PLUGIN_DATA_DIR = join5(TELETON_ROOT, "plugins", "data");
9336
- function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
9919
+ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps, hookRegistry, pluginPriorities) {
9337
9920
  let manifest = null;
9338
9921
  if (raw.manifest) {
9339
9922
  try {
@@ -9363,6 +9946,7 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
9363
9946
  }
9364
9947
  const pluginName = manifest?.name ?? entryName.replace(/\.js$/, "");
9365
9948
  const pluginVersion = manifest?.version ?? "0.0.0";
9949
+ const globalPriority = pluginPriorities?.get(pluginName) ?? 0;
9366
9950
  if (manifest?.dependencies) {
9367
9951
  for (const dep of manifest.dependencies) {
9368
9952
  if (!loadedModuleNames.includes(dep)) {
@@ -9425,7 +10009,7 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
9425
10009
  const dbPath = join5(PLUGIN_DATA_DIR, `${pluginName}.db`);
9426
10010
  pluginDb = openModuleDb(dbPath);
9427
10011
  if (hasMigrate) {
9428
- raw.migrate(pluginDb);
10012
+ raw.migrate?.(pluginDb);
9429
10013
  const pluginTables = pluginDb.prepare(
9430
10014
  `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`
9431
10015
  ).all().map((t) => t.name).filter((n2) => n2 !== "_kv");
@@ -9453,7 +10037,10 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
9453
10037
  db: pluginDb,
9454
10038
  sanitizedConfig,
9455
10039
  pluginConfig,
9456
- botManifest: manifest?.bot
10040
+ botManifest: manifest?.bot,
10041
+ hookRegistry,
10042
+ declaredHooks: manifest?.hooks,
10043
+ globalPriority
9457
10044
  });
9458
10045
  toolDefs = raw.tools(sdk);
9459
10046
  } else if (Array.isArray(raw.tools)) {
@@ -9550,12 +10137,20 @@ async function ensurePluginDeps(pluginDir, pluginEntry) {
9550
10137
  log12.error(`[${pluginEntry}] Failed to install deps: ${String(err).slice(0, 300)}`);
9551
10138
  }
9552
10139
  }
9553
- async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
10140
+ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps, db) {
10141
+ const hookRegistry = new HookRegistry();
9554
10142
  const pluginsDir = WORKSPACE_PATHS.PLUGINS_DIR;
9555
10143
  if (!existsSync6(pluginsDir)) {
9556
- return [];
10144
+ return { modules: [], hookRegistry };
9557
10145
  }
9558
- const entries = readdirSync2(pluginsDir);
10146
+ let pluginPriorities = /* @__PURE__ */ new Map();
10147
+ if (db) {
10148
+ try {
10149
+ pluginPriorities = getPluginPriorities(db);
10150
+ } catch {
10151
+ }
10152
+ }
10153
+ const entries = readdirSync2(pluginsDir).sort();
9559
10154
  const modules = [];
9560
10155
  const loadedNames = /* @__PURE__ */ new Set();
9561
10156
  const pluginPaths = [];
@@ -9603,7 +10198,15 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
9603
10198
  log12.warn(`Plugin "${entry}": no 'tools' array or function exported, skipping`);
9604
10199
  continue;
9605
10200
  }
9606
- const adapted = adaptPlugin(mod, entry, config, loadedModuleNames, sdkDeps);
10201
+ const adapted = adaptPlugin(
10202
+ mod,
10203
+ entry,
10204
+ config,
10205
+ loadedModuleNames,
10206
+ sdkDeps,
10207
+ hookRegistry,
10208
+ pluginPriorities
10209
+ );
9607
10210
  if (loadedNames.has(adapted.name)) {
9608
10211
  log12.warn(`Plugin "${adapted.name}" already loaded, skipping duplicate from "${entry}"`);
9609
10212
  continue;
@@ -9614,7 +10217,7 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
9614
10217
  log12.error(`Plugin "${entry}" failed to adapt: ${err instanceof Error ? err.message : err}`);
9615
10218
  }
9616
10219
  }
9617
- return modules;
10220
+ return { modules, hookRegistry };
9618
10221
  }
9619
10222
 
9620
10223
  // src/config/configurable-keys.ts
@@ -10122,6 +10725,40 @@ var CONFIGURABLE_KEYS = {
10122
10725
  mask: identity,
10123
10726
  parse: (v) => Number(v)
10124
10727
  },
10728
+ // ─── TON Proxy ────────────────────────────────────────────────────
10729
+ "ton_proxy.enabled": {
10730
+ type: "boolean",
10731
+ category: "TON Proxy",
10732
+ label: "TON Proxy Enabled",
10733
+ description: "Enable Tonutils-Proxy for .ton site access (auto-downloads binary on first run)",
10734
+ sensitive: false,
10735
+ hotReload: "instant",
10736
+ validate: enumValidator(["true", "false"]),
10737
+ mask: identity,
10738
+ parse: (v) => v === "true"
10739
+ },
10740
+ "ton_proxy.port": {
10741
+ type: "number",
10742
+ category: "TON Proxy",
10743
+ label: "Proxy Port",
10744
+ description: "HTTP proxy port for .ton sites (default: 8080)",
10745
+ sensitive: false,
10746
+ hotReload: "restart",
10747
+ validate: numberInRange(1, 65535),
10748
+ mask: identity,
10749
+ parse: (v) => Number(v)
10750
+ },
10751
+ "ton_proxy.binary_path": {
10752
+ type: "string",
10753
+ category: "TON Proxy",
10754
+ label: "Binary Path",
10755
+ description: "Custom path to tonutils-proxy-cli (leave empty for auto-download)",
10756
+ sensitive: false,
10757
+ hotReload: "restart",
10758
+ validate: noValidation,
10759
+ mask: identity,
10760
+ parse: identity
10761
+ },
10125
10762
  // ─── Capabilities ──────────────────────────────────────────────────
10126
10763
  "capabilities.exec.mode": {
10127
10764
  type: "enum",
@@ -10227,6 +10864,392 @@ function writeRawConfig(raw, configPath) {
10227
10864
  writeFileSync3(fullPath, stringify2(raw), { encoding: "utf-8", mode: 384 });
10228
10865
  }
10229
10866
 
10867
+ // src/ton-proxy/manager.ts
10868
+ import { spawn, execSync } from "child_process";
10869
+ import {
10870
+ existsSync as existsSync8,
10871
+ chmodSync,
10872
+ createWriteStream,
10873
+ readFileSync as readFileSync7,
10874
+ writeFileSync as writeFileSync4,
10875
+ unlinkSync
10876
+ } from "fs";
10877
+ import { mkdir as mkdir2 } from "fs/promises";
10878
+ import { join as join6 } from "path";
10879
+ import { pipeline } from "stream/promises";
10880
+ var log13 = createLogger("TonProxy");
10881
+ var GITHUB_REPO = "xssnick/Tonutils-Proxy";
10882
+ var BINARY_DIR = join6(TELETON_ROOT, "bin");
10883
+ var PID_FILE = join6(TELETON_ROOT, "ton-proxy.pid");
10884
+ var HEALTH_CHECK_INTERVAL_MS = 3e4;
10885
+ var HEALTH_CHECK_TIMEOUT_MS = 5e3;
10886
+ var KILL_GRACE_MS = 5e3;
10887
+ var TonProxyManager = class {
10888
+ process = null;
10889
+ healthInterval = null;
10890
+ config;
10891
+ restartCount = 0;
10892
+ maxRestarts = 3;
10893
+ constructor(config) {
10894
+ this.config = config;
10895
+ }
10896
+ /** Resolve the binary path — user-specified or auto-detected */
10897
+ getBinaryPath() {
10898
+ if (this.config.binary_path) return this.config.binary_path;
10899
+ return join6(BINARY_DIR, getBinaryName());
10900
+ }
10901
+ /** Check if the binary exists on disk */
10902
+ isInstalled() {
10903
+ return existsSync8(this.getBinaryPath());
10904
+ }
10905
+ /** Whether the proxy process is currently running */
10906
+ isRunning() {
10907
+ return this.process !== null && this.process.exitCode === null;
10908
+ }
10909
+ /**
10910
+ * Download the latest CLI binary from GitHub releases.
10911
+ * Fetches the latest release tag, then downloads the platform-appropriate binary.
10912
+ */
10913
+ async install() {
10914
+ const binaryName = getBinaryName();
10915
+ log13.info(`Downloading TON Proxy binary (${binaryName})...`);
10916
+ await mkdir2(BINARY_DIR, { recursive: true });
10917
+ const releaseUrl = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
10918
+ const releaseRes = await fetch(releaseUrl, {
10919
+ headers: { Accept: "application/vnd.github.v3+json" }
10920
+ });
10921
+ if (!releaseRes.ok) {
10922
+ throw new Error(`Failed to fetch latest release: ${releaseRes.status}`);
10923
+ }
10924
+ const release = await releaseRes.json();
10925
+ const tag = release.tag_name;
10926
+ const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/${tag}/${binaryName}`;
10927
+ log13.info(`Downloading ${downloadUrl}`);
10928
+ const res = await fetch(downloadUrl);
10929
+ if (!res.ok || !res.body) {
10930
+ throw new Error(`Download failed: ${res.status} ${res.statusText}`);
10931
+ }
10932
+ const dest = this.getBinaryPath();
10933
+ const fileStream = createWriteStream(dest);
10934
+ await pipeline(res.body, fileStream);
10935
+ chmodSync(dest, 493);
10936
+ log13.info(`TON Proxy installed: ${dest} (${tag})`);
10937
+ }
10938
+ /** Kill any orphan proxy process from a previous session */
10939
+ killOrphan() {
10940
+ if (existsSync8(PID_FILE)) {
10941
+ try {
10942
+ const pid = parseInt(readFileSync7(PID_FILE, "utf-8").trim(), 10);
10943
+ if (pid && !isNaN(pid)) {
10944
+ try {
10945
+ process.kill(pid, 0);
10946
+ log13.warn(`Killing orphan TON Proxy (PID ${pid}) from previous session`);
10947
+ process.kill(pid, "SIGTERM");
10948
+ } catch {
10949
+ }
10950
+ }
10951
+ unlinkSync(PID_FILE);
10952
+ } catch {
10953
+ }
10954
+ }
10955
+ try {
10956
+ const out = execSync(`ss -tlnp 2>/dev/null | grep ':${this.config.port} ' || true`, {
10957
+ encoding: "utf-8",
10958
+ timeout: 3e3
10959
+ });
10960
+ const pidMatch = out.match(/pid=(\d+)/);
10961
+ if (pidMatch) {
10962
+ const pid = parseInt(pidMatch[1], 10);
10963
+ log13.warn(`Port ${this.config.port} occupied by PID ${pid}, killing it`);
10964
+ try {
10965
+ process.kill(pid, "SIGTERM");
10966
+ } catch {
10967
+ }
10968
+ execSync("sleep 0.5");
10969
+ }
10970
+ } catch {
10971
+ }
10972
+ }
10973
+ /** Write PID to file for orphan detection */
10974
+ writePidFile(pid) {
10975
+ try {
10976
+ writeFileSync4(PID_FILE, String(pid), { mode: 384 });
10977
+ } catch {
10978
+ log13.warn("Failed to write TON Proxy PID file");
10979
+ }
10980
+ }
10981
+ /** Remove PID file */
10982
+ removePidFile() {
10983
+ try {
10984
+ if (existsSync8(PID_FILE)) unlinkSync(PID_FILE);
10985
+ } catch {
10986
+ }
10987
+ }
10988
+ /** Start the proxy process */
10989
+ async start() {
10990
+ if (this.isRunning()) {
10991
+ log13.warn("TON Proxy is already running");
10992
+ return;
10993
+ }
10994
+ this.restartCount = 0;
10995
+ this.maxRestarts = 3;
10996
+ this.killOrphan();
10997
+ if (!this.isInstalled()) {
10998
+ await this.install();
10999
+ }
11000
+ const binaryPath = this.getBinaryPath();
11001
+ const port = String(this.config.port);
11002
+ log13.info(`Starting TON Proxy on 127.0.0.1:${port}`);
11003
+ this.process = spawn(binaryPath, ["-addr", `127.0.0.1:${port}`], {
11004
+ cwd: BINARY_DIR,
11005
+ stdio: ["ignore", "pipe", "pipe"],
11006
+ detached: false
11007
+ });
11008
+ this.process.stdout?.on("data", (chunk) => {
11009
+ const line = chunk.toString().trim();
11010
+ if (line) log13.debug(`[proxy] ${line}`);
11011
+ });
11012
+ this.process.stderr?.on("data", (chunk) => {
11013
+ const line = chunk.toString().trim();
11014
+ if (line) log13.warn(`[proxy:err] ${line}`);
11015
+ });
11016
+ this.process.on("exit", (code, signal) => {
11017
+ log13.info(`TON Proxy exited (code=${code}, signal=${signal})`);
11018
+ this.process = null;
11019
+ this.removePidFile();
11020
+ if (code !== 0 && code !== null && this.restartCount < this.maxRestarts) {
11021
+ this.restartCount++;
11022
+ log13.warn(`Auto-restarting TON Proxy (attempt ${this.restartCount}/${this.maxRestarts})`);
11023
+ this.start().catch((err) => log13.error({ err }, "Failed to auto-restart TON Proxy"));
11024
+ }
11025
+ });
11026
+ this.process.on("error", (err) => {
11027
+ log13.error({ err }, "TON Proxy process error");
11028
+ this.process = null;
11029
+ });
11030
+ this.startHealthCheck();
11031
+ await new Promise((resolve2, reject) => {
11032
+ const timer = setTimeout(() => {
11033
+ if (this.isRunning()) {
11034
+ resolve2();
11035
+ } else {
11036
+ reject(new Error("TON Proxy process exited immediately"));
11037
+ }
11038
+ }, 1e3);
11039
+ this.process?.on("exit", () => {
11040
+ clearTimeout(timer);
11041
+ reject(new Error("TON Proxy process exited during startup"));
11042
+ });
11043
+ });
11044
+ if (this.process?.pid) this.writePidFile(this.process.pid);
11045
+ log13.info(`TON Proxy running on 127.0.0.1:${port} (PID ${this.process?.pid})`);
11046
+ }
11047
+ /** Stop the proxy process gracefully */
11048
+ async stop() {
11049
+ this.stopHealthCheck();
11050
+ if (!this.process) return;
11051
+ this.maxRestarts = 0;
11052
+ log13.info("Stopping TON Proxy...");
11053
+ return new Promise((resolve2) => {
11054
+ if (!this.process) {
11055
+ resolve2();
11056
+ return;
11057
+ }
11058
+ const forceKill = setTimeout(() => {
11059
+ if (this.process) {
11060
+ log13.warn("TON Proxy did not exit gracefully, sending SIGKILL");
11061
+ this.process.kill("SIGKILL");
11062
+ }
11063
+ }, KILL_GRACE_MS);
11064
+ this.process.on("exit", () => {
11065
+ clearTimeout(forceKill);
11066
+ this.process = null;
11067
+ this.removePidFile();
11068
+ resolve2();
11069
+ });
11070
+ this.process.kill("SIGTERM");
11071
+ });
11072
+ }
11073
+ /** Remove the downloaded binary from disk */
11074
+ async uninstall() {
11075
+ if (this.isRunning()) {
11076
+ await this.stop();
11077
+ }
11078
+ const binaryPath = this.getBinaryPath();
11079
+ if (existsSync8(binaryPath)) {
11080
+ const { unlink } = await import("fs/promises");
11081
+ await unlink(binaryPath);
11082
+ log13.info(`TON Proxy binary removed: ${binaryPath}`);
11083
+ }
11084
+ }
11085
+ /** Get proxy status for WebUI / tools */
11086
+ getStatus() {
11087
+ return {
11088
+ running: this.isRunning(),
11089
+ port: this.config.port,
11090
+ installed: this.isInstalled(),
11091
+ pid: this.process?.pid
11092
+ };
11093
+ }
11094
+ startHealthCheck() {
11095
+ this.stopHealthCheck();
11096
+ this.healthInterval = setInterval(() => {
11097
+ void this.checkHealth();
11098
+ }, HEALTH_CHECK_INTERVAL_MS);
11099
+ }
11100
+ stopHealthCheck() {
11101
+ if (this.healthInterval) {
11102
+ clearInterval(this.healthInterval);
11103
+ this.healthInterval = null;
11104
+ }
11105
+ }
11106
+ async checkHealth() {
11107
+ if (!this.isRunning()) return;
11108
+ try {
11109
+ const controller = new AbortController();
11110
+ const timeout = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);
11111
+ const res = await fetch(`http://127.0.0.1:${this.config.port}/`, {
11112
+ signal: controller.signal
11113
+ }).catch(() => null);
11114
+ clearTimeout(timeout);
11115
+ if (!res) {
11116
+ log13.warn("TON Proxy health check failed (no response)");
11117
+ }
11118
+ } catch {
11119
+ }
11120
+ }
11121
+ };
11122
+ function getBinaryName() {
11123
+ const platform = process.platform;
11124
+ const arch = process.arch;
11125
+ let os;
11126
+ switch (platform) {
11127
+ case "linux":
11128
+ os = "linux";
11129
+ break;
11130
+ case "darwin":
11131
+ os = "darwin";
11132
+ break;
11133
+ case "win32":
11134
+ os = "windows";
11135
+ break;
11136
+ default:
11137
+ throw new Error(`Unsupported platform: ${platform}`);
11138
+ }
11139
+ let cpuArch;
11140
+ switch (arch) {
11141
+ case "x64":
11142
+ cpuArch = "amd64";
11143
+ break;
11144
+ case "arm64":
11145
+ cpuArch = "arm64";
11146
+ break;
11147
+ default:
11148
+ throw new Error(`Unsupported architecture: ${arch}`);
11149
+ }
11150
+ const ext = platform === "win32" ? ".exe" : "";
11151
+ return `tonutils-proxy-cli-${os}-${cpuArch}${ext}`;
11152
+ }
11153
+
11154
+ // src/ton-proxy/tools.ts
11155
+ import { Type } from "@sinclair/typebox";
11156
+ var proxyManager = null;
11157
+ function setProxyManager(mgr) {
11158
+ proxyManager = mgr;
11159
+ }
11160
+ var tonProxyStatusTool = {
11161
+ name: "ton_proxy_status",
11162
+ description: "Check the status of the TON Proxy (Tonutils-Proxy). Returns whether the proxy is running, installed, the port, and PID.",
11163
+ parameters: Type.Object({})
11164
+ };
11165
+ var tonProxyStatusExecutor = async () => {
11166
+ if (!proxyManager) {
11167
+ return { success: true, data: { enabled: false, message: "TON Proxy is not configured" } };
11168
+ }
11169
+ return { success: true, data: proxyManager.getStatus() };
11170
+ };
11171
+
11172
+ // src/ton-proxy/module.ts
11173
+ var log14 = createLogger("TonProxyModule");
11174
+ var manager = null;
11175
+ function getTonProxyManager() {
11176
+ return manager;
11177
+ }
11178
+ function setTonProxyManager(mgr) {
11179
+ manager = mgr;
11180
+ setProxyManager(mgr);
11181
+ }
11182
+ var tonProxyModule = {
11183
+ name: "ton-proxy",
11184
+ version: "1.0.0",
11185
+ tools(config) {
11186
+ if (!config.ton_proxy?.enabled) return [];
11187
+ return [{ tool: tonProxyStatusTool, executor: tonProxyStatusExecutor }];
11188
+ },
11189
+ async start(context) {
11190
+ if (!context.config.ton_proxy?.enabled) return;
11191
+ const proxyConfig = context.config.ton_proxy;
11192
+ manager = new TonProxyManager({
11193
+ enabled: proxyConfig.enabled,
11194
+ port: proxyConfig.port,
11195
+ binary_path: proxyConfig.binary_path
11196
+ });
11197
+ setProxyManager(manager);
11198
+ try {
11199
+ await manager.start();
11200
+ log14.info(`TON Proxy started on port ${proxyConfig.port}`);
11201
+ } catch (err) {
11202
+ log14.error({ err }, "Failed to start TON Proxy");
11203
+ manager = null;
11204
+ }
11205
+ },
11206
+ async stop() {
11207
+ if (manager) {
11208
+ await manager.stop();
11209
+ manager = null;
11210
+ setProxyManager(null);
11211
+ }
11212
+ }
11213
+ };
11214
+ var module_default = tonProxyModule;
11215
+
11216
+ // src/agent/hooks/user-hook-store.ts
11217
+ function getUserHookConfig(db, key) {
11218
+ const row = db.prepare("SELECT value FROM user_hook_config WHERE key = ?").get(key);
11219
+ return row?.value ?? null;
11220
+ }
11221
+ function setUserHookConfig(db, key, value) {
11222
+ db.prepare(
11223
+ `INSERT INTO user_hook_config (key, value, updated_at)
11224
+ VALUES (?, ?, datetime('now'))
11225
+ ON CONFLICT(key) DO UPDATE SET
11226
+ value = excluded.value,
11227
+ updated_at = excluded.updated_at`
11228
+ ).run(key, value);
11229
+ }
11230
+ function getBlocklistConfig(db) {
11231
+ const enabled = getUserHookConfig(db, "blocklist.enabled");
11232
+ const keywords = getUserHookConfig(db, "blocklist.keywords");
11233
+ const message = getUserHookConfig(db, "blocklist.message");
11234
+ return {
11235
+ enabled: enabled === "true",
11236
+ keywords: keywords ? JSON.parse(keywords) : [],
11237
+ message: message ?? ""
11238
+ };
11239
+ }
11240
+ function setBlocklistConfig(db, config) {
11241
+ setUserHookConfig(db, "blocklist.enabled", String(config.enabled));
11242
+ setUserHookConfig(db, "blocklist.keywords", JSON.stringify(config.keywords));
11243
+ setUserHookConfig(db, "blocklist.message", config.message);
11244
+ }
11245
+ function getTriggersConfig(db) {
11246
+ const raw = getUserHookConfig(db, "triggers");
11247
+ return raw ? JSON.parse(raw) : [];
11248
+ }
11249
+ function setTriggersConfig(db, triggers) {
11250
+ setUserHookConfig(db, "triggers", JSON.stringify(triggers));
11251
+ }
11252
+
10230
11253
  export {
10231
11254
  loadConfig,
10232
11255
  configExists,
@@ -10268,6 +11291,9 @@ export {
10268
11291
  toUnits,
10269
11292
  fromUnits,
10270
11293
  dexFactory,
11294
+ getPluginPriorities,
11295
+ setPluginPriority,
11296
+ resetPluginPriority,
10271
11297
  InlineRouter,
10272
11298
  adaptPlugin,
10273
11299
  ensurePluginDeps,
@@ -10277,5 +11303,13 @@ export {
10277
11303
  setNestedValue,
10278
11304
  deleteNestedValue,
10279
11305
  readRawConfig,
10280
- writeRawConfig
11306
+ writeRawConfig,
11307
+ TonProxyManager,
11308
+ getTonProxyManager,
11309
+ setTonProxyManager,
11310
+ module_default,
11311
+ getBlocklistConfig,
11312
+ setBlocklistConfig,
11313
+ getTriggersConfig,
11314
+ setTriggersConfig
10281
11315
  };