teleton 0.7.4 → 0.7.5

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 (35) hide show
  1. package/README.md +36 -26
  2. package/dist/{chunk-XDYDA2KV.js → chunk-2GLHOJ5C.js} +268 -59
  3. package/dist/chunk-5UVXJMOX.js +292 -0
  4. package/dist/{chunk-BGC2IUM5.js → chunk-AVDWXYQ7.js} +65 -20
  5. package/dist/{chunk-RMLQS3X6.js → chunk-CB2Y45HA.js} +106 -1
  6. package/dist/{chunk-5PLZ3KSO.js → chunk-DMXTIRUW.js} +5 -6
  7. package/dist/{chunk-YFG2QHLA.js → chunk-G2LLMJXJ.js} +1578 -115
  8. package/dist/{chunk-EK7M5K26.js → chunk-LCCVZ4D2.js} +3 -3
  9. package/dist/{chunk-LAQOUFOJ.js → chunk-OGMVWDVU.js} +3517 -3620
  10. package/dist/{chunk-4DU3C27M.js → chunk-R4YSJ4EY.js} +5 -1
  11. package/dist/{chunk-XBKSS6DM.js → chunk-VFA7QMCZ.js} +5 -3
  12. package/dist/{chunk-VAUJSSD3.js → chunk-XQUHC3JZ.js} +1 -1
  13. package/dist/{chunk-RO62LO6Z.js → chunk-YP25WTQK.js} +2 -0
  14. package/dist/cli/index.js +92 -28
  15. package/dist/{client-RTNALK7W.js → client-O37XDCJB.js} +4 -5
  16. package/dist/index.js +12 -13
  17. package/dist/{memory-JQZ6MTRU.js → memory-KQALFUV3.js} +6 -7
  18. package/dist/{migrate-GS5ACQDA.js → migrate-UV3WEL5D.js} +6 -7
  19. package/dist/{server-TCJOBV3D.js → server-BHHJGUDF.js} +35 -9
  20. package/dist/{setup-server-YHYJLAMA.js → setup-server-G7UG2DI3.js} +21 -9
  21. package/dist/store-H4XPNGC2.js +34 -0
  22. package/dist/{task-dependency-resolver-WKZWJLLM.js → task-dependency-resolver-VMEVJRPO.js} +2 -2
  23. package/dist/{task-executor-PD3H4MLO.js → task-executor-WWSPBJ4V.js} +1 -1
  24. package/dist/{tool-index-6HBRVXVG.js → tool-index-2KH3OB6X.js} +5 -5
  25. package/dist/web/assets/index-BrVqauzj.css +1 -0
  26. package/dist/web/assets/index-Bx8JW3gV.js +72 -0
  27. package/dist/web/assets/{index.es-CqZHj0tz.js → index.es-Pet5-M13.js} +1 -1
  28. package/dist/web/index.html +2 -2
  29. package/package.json +2 -2
  30. package/dist/chunk-JQDLW7IE.js +0 -107
  31. package/dist/chunk-UCN6TI25.js +0 -143
  32. package/dist/web/assets/index-B6M9knfJ.css +0 -1
  33. package/dist/web/assets/index-DAGeQfVZ.js +0 -72
  34. package/scripts/patch-gramjs.sh +0 -46
  35. package/scripts/postinstall.mjs +0 -16
@@ -7,27 +7,75 @@ import {
7
7
  getWalletBalance,
8
8
  invalidateTonClientCache,
9
9
  loadWallet
10
- } from "./chunk-BGC2IUM5.js";
10
+ } from "./chunk-AVDWXYQ7.js";
11
+ import {
12
+ getOrCreateSession,
13
+ getSession,
14
+ resetSession,
15
+ resetSessionWithPolicy,
16
+ shouldResetSession,
17
+ updateSession
18
+ } from "./chunk-5UVXJMOX.js";
11
19
  import {
12
20
  require_BigInteger
13
21
  } from "./chunk-TSKJCWQQ.js";
14
22
  import {
15
23
  getErrorMessage
16
24
  } from "./chunk-XBE4JB7C.js";
25
+ import {
26
+ chatWithContext,
27
+ getEffectiveApiKey,
28
+ getProviderModel,
29
+ getUtilityModel,
30
+ loadContextFromTranscript
31
+ } from "./chunk-DMXTIRUW.js";
17
32
  import {
18
33
  getProviderMetadata
19
- } from "./chunk-RMLQS3X6.js";
34
+ } from "./chunk-CB2Y45HA.js";
35
+ import {
36
+ appendToTranscript,
37
+ archiveTranscript,
38
+ transcriptExists
39
+ } from "./chunk-OCLG5GKI.js";
20
40
  import {
41
+ ContextBuilder,
21
42
  createDbWrapper,
43
+ getDatabase,
22
44
  migrateFromMainDb,
23
45
  openModuleDb
24
- } from "./chunk-UCN6TI25.js";
46
+ } from "./chunk-2GLHOJ5C.js";
25
47
  import {
26
48
  tonapiFetch
27
- } from "./chunk-XBKSS6DM.js";
49
+ } from "./chunk-VFA7QMCZ.js";
28
50
  import {
29
- PAYMENT_TOLERANCE_RATIO
30
- } from "./chunk-RO62LO6Z.js";
51
+ ADAPTIVE_CHUNK_RATIO_BASE,
52
+ ADAPTIVE_CHUNK_RATIO_MIN,
53
+ ADAPTIVE_CHUNK_RATIO_TRIGGER,
54
+ CHARS_PER_TOKEN_ESTIMATE,
55
+ COMPACTION_KEEP_RECENT,
56
+ COMPACTION_MAX_MESSAGES,
57
+ COMPACTION_MAX_TOKENS_RATIO,
58
+ COMPACTION_SOFT_THRESHOLD_RATIO,
59
+ CONTEXT_MAX_RECENT_MESSAGES,
60
+ CONTEXT_MAX_RELEVANT_CHUNKS,
61
+ CONTEXT_OVERFLOW_SUMMARY_MESSAGES,
62
+ DEFAULT_CONTEXT_WINDOW,
63
+ DEFAULT_MAX_SUMMARY_TOKENS,
64
+ DEFAULT_MAX_TOKENS,
65
+ DEFAULT_SOFT_THRESHOLD_TOKENS,
66
+ DEFAULT_SUMMARY_FALLBACK_TOKENS,
67
+ FALLBACK_SOFT_THRESHOLD_TOKENS,
68
+ MASKING_KEEP_RECENT_COUNT,
69
+ MAX_TOOL_RESULT_SIZE,
70
+ MEMORY_FLUSH_RECENT_MESSAGES,
71
+ OVERSIZED_MESSAGE_RATIO,
72
+ PAYMENT_TOLERANCE_RATIO,
73
+ RATE_LIMIT_MAX_RETRIES,
74
+ SERVER_ERROR_MAX_RETRIES,
75
+ SESSION_SLUG_MAX_TOKENS,
76
+ SESSION_SLUG_RECENT_MESSAGES,
77
+ TOKEN_ESTIMATE_SAFETY_MARGIN
78
+ } from "./chunk-YP25WTQK.js";
31
79
  import {
32
80
  RETRY_BLOCKCHAIN_BASE_DELAY_MS,
33
81
  RETRY_BLOCKCHAIN_MAX_DELAY_MS,
@@ -36,7 +84,7 @@ import {
36
84
  RETRY_DEFAULT_MAX_ATTEMPTS,
37
85
  RETRY_DEFAULT_MAX_DELAY_MS,
38
86
  RETRY_DEFAULT_TIMEOUT_MS
39
- } from "./chunk-4DU3C27M.js";
87
+ } from "./chunk-R4YSJ4EY.js";
40
88
  import {
41
89
  ALLOWED_EXTENSIONS,
42
90
  TELETON_ROOT,
@@ -150,6 +198,9 @@ Run 'teleton setup' to create one.`);
150
198
  if (process.env.TELETON_TONAPI_KEY) {
151
199
  config.tonapi_key = process.env.TELETON_TONAPI_KEY;
152
200
  }
201
+ if (process.env.TELETON_TONCENTER_API_KEY) {
202
+ config.toncenter_api_key = process.env.TELETON_TONCENTER_API_KEY;
203
+ }
153
204
  return config;
154
205
  }
155
206
  function configExists(configPath = DEFAULT_CONFIG_PATH) {
@@ -573,16 +624,1379 @@ Your conversation context is approaching the limit and may be compacted soon.
573
624
  return parts.join("\n");
574
625
  }
575
626
 
627
+ // src/constants/tools.ts
628
+ var TELEGRAM_SEND_TOOLS = /* @__PURE__ */ new Set([
629
+ "telegram_send_message",
630
+ "telegram_send_gif",
631
+ "telegram_send_voice",
632
+ "telegram_send_sticker",
633
+ "telegram_send_document",
634
+ "telegram_send_photo",
635
+ "telegram_send_video",
636
+ "telegram_send_poll",
637
+ "telegram_forward_message",
638
+ "telegram_reply_message",
639
+ "deal_propose"
640
+ ]);
641
+
642
+ // src/memory/envelope.ts
643
+ function formatElapsed(elapsedMs) {
644
+ if (!Number.isFinite(elapsedMs) || elapsedMs < 0) {
645
+ return "";
646
+ }
647
+ const seconds = Math.floor(elapsedMs / 1e3);
648
+ if (seconds < 60) {
649
+ return `${seconds}s`;
650
+ }
651
+ const minutes = Math.floor(seconds / 60);
652
+ if (minutes < 60) {
653
+ return `${minutes}m`;
654
+ }
655
+ const hours = Math.floor(minutes / 60);
656
+ if (hours < 24) {
657
+ return `${hours}h`;
658
+ }
659
+ const days = Math.floor(hours / 24);
660
+ return `${days}d`;
661
+ }
662
+ function formatTimestamp(timestamp) {
663
+ const date = new Date(timestamp);
664
+ const yyyy = date.getFullYear();
665
+ const mm = String(date.getMonth() + 1).padStart(2, "0");
666
+ const dd = String(date.getDate()).padStart(2, "0");
667
+ const hh = String(date.getHours()).padStart(2, "0");
668
+ const min = String(date.getMinutes()).padStart(2, "0");
669
+ const tz = Intl.DateTimeFormat("en", {
670
+ timeZoneName: "short"
671
+ }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value;
672
+ return `${yyyy}-${mm}-${dd} ${hh}:${min}${tz ? ` ${tz}` : ""}`;
673
+ }
674
+ function buildSenderLabel(params) {
675
+ const name = params.senderName ? sanitizeForPrompt(params.senderName) : void 0;
676
+ const username = params.senderUsername ? `@${sanitizeForPrompt(params.senderUsername)}` : void 0;
677
+ const idTag = params.senderId ? `id:${params.senderId}` : void 0;
678
+ const primary = name || username;
679
+ const meta = [username, idTag].filter((v) => v && v !== primary);
680
+ if (primary) {
681
+ return meta.length > 0 ? `${primary} (${meta.join(", ")})` : primary;
682
+ }
683
+ return idTag || "unknown";
684
+ }
685
+ function formatMessageEnvelope(params) {
686
+ const parts = [params.channel];
687
+ const senderLabel = buildSenderLabel(params);
688
+ if (!params.isGroup) {
689
+ parts.push(senderLabel);
690
+ }
691
+ if (params.previousTimestamp) {
692
+ const elapsed = formatElapsed(params.timestamp - params.previousTimestamp);
693
+ if (elapsed) {
694
+ parts.push(`+${elapsed}`);
695
+ }
696
+ }
697
+ const ts = formatTimestamp(params.timestamp);
698
+ parts.push(ts);
699
+ const header = `[${parts.join(" ")}]`;
700
+ const safeBody = params.body.replace(/<\/?user_message>/gi, "");
701
+ let body = params.isGroup ? `${senderLabel}: <user_message>${safeBody}</user_message>` : `<user_message>${safeBody}</user_message>`;
702
+ if (params.hasMedia && params.mediaType) {
703
+ const mediaEmoji = {
704
+ photo: "\u{1F4F7}",
705
+ video: "\u{1F3AC}",
706
+ audio: "\u{1F3B5}",
707
+ voice: "\u{1F3A4}",
708
+ document: "\u{1F4CE}",
709
+ sticker: "\u{1F3A8}"
710
+ }[params.mediaType] || "\u{1F4CE}";
711
+ const msgIdHint = params.messageId ? ` msg_id=${params.messageId}` : "";
712
+ body = `[${mediaEmoji} ${params.mediaType}${msgIdHint}] ${body}`;
713
+ }
714
+ if (params.replyContext) {
715
+ const sender = params.replyContext.isAgent ? "agent" : sanitizeForPrompt(params.replyContext.senderName ?? "unknown");
716
+ let quotedText = sanitizeForContext(params.replyContext.text);
717
+ if (quotedText.length > 200) quotedText = quotedText.slice(0, 200) + "...";
718
+ return `${header}
719
+ [\u21A9 reply to ${sender}: "${quotedText}"]
720
+ ${body}`;
721
+ }
722
+ return `${header} ${body}`;
723
+ }
724
+
725
+ // src/memory/compaction.ts
726
+ import { randomUUID } from "crypto";
727
+
728
+ // src/memory/ai-summarization.ts
729
+ import {
730
+ complete
731
+ } from "@mariozechner/pi-ai";
732
+ var log3 = createLogger("Memory");
733
+ function estimateMessageTokens(content) {
734
+ return Math.ceil(content.length / CHARS_PER_TOKEN_ESTIMATE * TOKEN_ESTIMATE_SAFETY_MARGIN);
735
+ }
736
+ function splitMessagesByTokens(messages, maxChunkTokens) {
737
+ if (messages.length === 0) {
738
+ return [];
739
+ }
740
+ const chunks = [];
741
+ let currentChunk = [];
742
+ let currentTokens = 0;
743
+ for (const message of messages) {
744
+ const content = extractMessageContent(message);
745
+ const messageTokens = estimateMessageTokens(content);
746
+ if (currentChunk.length > 0 && currentTokens + messageTokens > maxChunkTokens) {
747
+ chunks.push(currentChunk);
748
+ currentChunk = [];
749
+ currentTokens = 0;
750
+ }
751
+ currentChunk.push(message);
752
+ currentTokens += messageTokens;
753
+ if (messageTokens > maxChunkTokens && currentChunk.length === 1) {
754
+ chunks.push(currentChunk);
755
+ currentChunk = [];
756
+ currentTokens = 0;
757
+ }
758
+ }
759
+ if (currentChunk.length > 0) {
760
+ chunks.push(currentChunk);
761
+ }
762
+ return chunks;
763
+ }
764
+ function extractMessageContent(message) {
765
+ if (message.role === "user") {
766
+ return typeof message.content === "string" ? message.content : "[complex content]";
767
+ } else if (message.role === "assistant") {
768
+ return message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
769
+ }
770
+ return "";
771
+ }
772
+ function formatMessagesForSummary(messages) {
773
+ const formatted = [];
774
+ for (const msg of messages) {
775
+ if (msg.role === "user") {
776
+ const content = typeof msg.content === "string" ? msg.content : "[complex]";
777
+ const bodyMatch = content.match(/\] (.+)/s);
778
+ const body = bodyMatch ? bodyMatch[1] : content;
779
+ formatted.push(`User: ${body}`);
780
+ } else if (msg.role === "assistant") {
781
+ const textBlocks = msg.content.filter((b) => b.type === "text");
782
+ if (textBlocks.length > 0) {
783
+ const text = textBlocks.map((b) => b.text).join("\n");
784
+ formatted.push(`Assistant: ${text}`);
785
+ }
786
+ const toolCalls = msg.content.filter((b) => b.type === "toolCall");
787
+ if (toolCalls.length > 0) {
788
+ const toolNames = toolCalls.map((b) => b.name).join(", ");
789
+ formatted.push(`[Used tools: ${toolNames}]`);
790
+ }
791
+ } else if (msg.role === "toolResult") {
792
+ formatted.push(`[Tool result: ${msg.toolName}]`);
793
+ }
794
+ }
795
+ return formatted.join("\n\n");
796
+ }
797
+ function isOversizedForSummary(message, contextWindow) {
798
+ const content = extractMessageContent(message);
799
+ const tokens = estimateMessageTokens(content);
800
+ return tokens > contextWindow * OVERSIZED_MESSAGE_RATIO;
801
+ }
802
+ function computeAdaptiveChunkRatio(messages, contextWindow) {
803
+ const BASE_CHUNK_RATIO = ADAPTIVE_CHUNK_RATIO_BASE;
804
+ const MIN_CHUNK_RATIO = ADAPTIVE_CHUNK_RATIO_MIN;
805
+ if (messages.length === 0) {
806
+ return BASE_CHUNK_RATIO;
807
+ }
808
+ let totalTokens = 0;
809
+ for (const msg of messages) {
810
+ const content = extractMessageContent(msg);
811
+ totalTokens += estimateMessageTokens(content);
812
+ }
813
+ const avgTokens = totalTokens / messages.length;
814
+ const avgRatio = avgTokens / contextWindow;
815
+ if (avgRatio > ADAPTIVE_CHUNK_RATIO_TRIGGER) {
816
+ const reduction = Math.min(avgRatio * 2, BASE_CHUNK_RATIO - MIN_CHUNK_RATIO);
817
+ return Math.max(MIN_CHUNK_RATIO, BASE_CHUNK_RATIO - reduction);
818
+ }
819
+ return BASE_CHUNK_RATIO;
820
+ }
821
+ async function summarizeViaClaude(params) {
822
+ const provider = params.provider || "anthropic";
823
+ const model = getUtilityModel(provider, params.utilityModel);
824
+ const maxTokens = params.maxSummaryTokens ?? DEFAULT_SUMMARY_FALLBACK_TOKENS;
825
+ const formatted = formatMessagesForSummary(params.messages);
826
+ if (!formatted.trim()) {
827
+ return "No conversation content to summarize.";
828
+ }
829
+ const defaultInstructions = `Summarize this conversation concisely. Focus on:
830
+ - Key decisions made
831
+ - Action items and TODOs
832
+ - Open questions
833
+ - Important context and constraints
834
+ - Technical details that matter
835
+
836
+ Be specific but concise. Preserve critical information.`;
837
+ const instructions = params.customInstructions ? `${defaultInstructions}
838
+
839
+ Additional focus:
840
+ ${params.customInstructions}` : defaultInstructions;
841
+ try {
842
+ const context = {
843
+ messages: [
844
+ {
845
+ role: "user",
846
+ content: `${instructions}
847
+
848
+ Conversation:
849
+ ${formatted}`,
850
+ timestamp: Date.now()
851
+ }
852
+ ]
853
+ };
854
+ const response = await complete(model, context, {
855
+ apiKey: params.apiKey,
856
+ maxTokens
857
+ });
858
+ const textContent = response.content.find((block) => block.type === "text");
859
+ const summary = textContent?.type === "text" ? textContent.text : "";
860
+ return summary.trim() || "Unable to generate summary.";
861
+ } catch (error) {
862
+ log3.error({ err: error }, "Summarization error");
863
+ throw new Error(`Summarization failed: ${getErrorMessage(error)}`);
864
+ }
865
+ }
866
+ async function summarizeInChunks(params) {
867
+ if (params.messages.length === 0) {
868
+ return {
869
+ summary: "No messages to summarize.",
870
+ tokensUsed: 0,
871
+ chunksProcessed: 0
872
+ };
873
+ }
874
+ const chunks = splitMessagesByTokens(params.messages, params.maxChunkTokens);
875
+ log3.info(`Splitting into ${chunks.length} chunks for summarization`);
876
+ if (chunks.length === 1) {
877
+ const summary = await summarizeViaClaude({
878
+ messages: chunks[0],
879
+ apiKey: params.apiKey,
880
+ maxSummaryTokens: params.maxSummaryTokens,
881
+ customInstructions: params.customInstructions,
882
+ provider: params.provider,
883
+ utilityModel: params.utilityModel
884
+ });
885
+ return {
886
+ summary,
887
+ tokensUsed: estimateMessageTokens(summary),
888
+ chunksProcessed: 1
889
+ };
890
+ }
891
+ const partialSummaries = [];
892
+ for (let i = 0; i < chunks.length; i++) {
893
+ log3.info(`Summarizing chunk ${i + 1}/${chunks.length} (${chunks[i].length} messages)`);
894
+ const partial = await summarizeViaClaude({
895
+ messages: chunks[i],
896
+ apiKey: params.apiKey,
897
+ maxSummaryTokens: Math.floor(
898
+ (params.maxSummaryTokens ?? DEFAULT_SUMMARY_FALLBACK_TOKENS) / 2
899
+ ),
900
+ customInstructions: params.customInstructions,
901
+ provider: params.provider,
902
+ utilityModel: params.utilityModel
903
+ });
904
+ partialSummaries.push(partial);
905
+ }
906
+ log3.info(`Merging ${partialSummaries.length} partial summaries`);
907
+ const provider = params.provider || "anthropic";
908
+ const model = getUtilityModel(provider, params.utilityModel);
909
+ const mergeContext = {
910
+ messages: [
911
+ {
912
+ role: "user",
913
+ content: `Merge these partial conversation summaries into one cohesive summary.
914
+ Preserve all key decisions, action items, open questions, and important context.
915
+ Do not add new information - only synthesize what's provided.
916
+
917
+ Partial summaries:
918
+
919
+ ${partialSummaries.map((s, i) => `Part ${i + 1}:
920
+ ${s}`).join("\n\n---\n\n")}`,
921
+ timestamp: Date.now()
922
+ }
923
+ ]
924
+ };
925
+ const mergeResponse = await complete(model, mergeContext, {
926
+ apiKey: params.apiKey,
927
+ maxTokens: params.maxSummaryTokens ?? DEFAULT_SUMMARY_FALLBACK_TOKENS
928
+ });
929
+ const textContent = mergeResponse.content.find((block) => block.type === "text");
930
+ const merged = textContent?.type === "text" ? textContent.text : "";
931
+ return {
932
+ summary: merged.trim() || "Unable to merge summaries.",
933
+ tokensUsed: estimateMessageTokens(merged),
934
+ chunksProcessed: chunks.length
935
+ };
936
+ }
937
+ async function summarizeWithFallback(params) {
938
+ if (params.messages.length === 0) {
939
+ return {
940
+ summary: "No messages to summarize.",
941
+ tokensUsed: 0,
942
+ chunksProcessed: 0
943
+ };
944
+ }
945
+ const chunkRatio = computeAdaptiveChunkRatio(params.messages, params.contextWindow);
946
+ const maxChunkTokens = Math.floor(params.contextWindow * chunkRatio);
947
+ log3.info(
948
+ `AI Summarization: ${params.messages.length} messages, chunk ratio: ${(chunkRatio * 100).toFixed(0)}%`
949
+ );
950
+ try {
951
+ return await summarizeInChunks({
952
+ messages: params.messages,
953
+ apiKey: params.apiKey,
954
+ maxChunkTokens,
955
+ maxSummaryTokens: params.maxSummaryTokens,
956
+ customInstructions: params.customInstructions,
957
+ provider: params.provider,
958
+ utilityModel: params.utilityModel
959
+ });
960
+ } catch (fullError) {
961
+ log3.warn(
962
+ `Full summarization failed: ${fullError instanceof Error ? fullError.message : String(fullError)}`
963
+ );
964
+ }
965
+ const smallMessages = [];
966
+ const oversizedNotes = [];
967
+ for (const msg of params.messages) {
968
+ if (isOversizedForSummary(msg, params.contextWindow)) {
969
+ const content = extractMessageContent(msg);
970
+ const tokens = estimateMessageTokens(content);
971
+ oversizedNotes.push(
972
+ `[Large ${msg.role} message (~${Math.round(tokens / 1e3)}K tokens) omitted from summary]`
973
+ );
974
+ } else {
975
+ smallMessages.push(msg);
976
+ }
977
+ }
978
+ log3.info(
979
+ `Fallback: Processing ${smallMessages.length} messages, skipping ${oversizedNotes.length} oversized`
980
+ );
981
+ if (smallMessages.length > 0) {
982
+ try {
983
+ const result = await summarizeInChunks({
984
+ messages: smallMessages,
985
+ apiKey: params.apiKey,
986
+ maxChunkTokens,
987
+ maxSummaryTokens: params.maxSummaryTokens,
988
+ customInstructions: params.customInstructions,
989
+ provider: params.provider,
990
+ utilityModel: params.utilityModel
991
+ });
992
+ const notes = oversizedNotes.length > 0 ? `
993
+
994
+ ${oversizedNotes.join("\n")}` : "";
995
+ return {
996
+ summary: result.summary + notes,
997
+ tokensUsed: result.tokensUsed,
998
+ chunksProcessed: result.chunksProcessed
999
+ };
1000
+ } catch (partialError) {
1001
+ log3.warn(
1002
+ `Partial summarization also failed: ${partialError instanceof Error ? partialError.message : String(partialError)}`
1003
+ );
1004
+ }
1005
+ }
1006
+ const note = `Context contained ${params.messages.length} messages (${oversizedNotes.length} were oversized). AI summarization unavailable due to size constraints. Recent conversation history was preserved.`;
1007
+ return {
1008
+ summary: note,
1009
+ tokensUsed: estimateMessageTokens(note),
1010
+ chunksProcessed: 0
1011
+ };
1012
+ }
1013
+
1014
+ // src/session/memory-hook.ts
1015
+ import { writeFile, mkdir } from "fs/promises";
1016
+ import { join as join3 } from "path";
1017
+ import { complete as complete2 } from "@mariozechner/pi-ai";
1018
+ var log4 = createLogger("Session");
1019
+ async function generateSlugViaClaude(params) {
1020
+ const provider = params.provider || "anthropic";
1021
+ const model = getUtilityModel(provider, params.utilityModel);
1022
+ const formatted = formatMessagesForSummary(params.messages.slice(-SESSION_SLUG_RECENT_MESSAGES));
1023
+ if (!formatted.trim()) {
1024
+ return "empty-session";
1025
+ }
1026
+ try {
1027
+ const context = {
1028
+ messages: [
1029
+ {
1030
+ role: "user",
1031
+ content: `Generate a short, descriptive slug (2-4 words, kebab-case) for this conversation.
1032
+ Examples: "gift-transfer-fix", "context-overflow-debug", "telegram-integration"
1033
+
1034
+ Conversation:
1035
+ ${formatted}
1036
+
1037
+ Slug:`,
1038
+ timestamp: Date.now()
1039
+ }
1040
+ ]
1041
+ };
1042
+ const response = await complete2(model, context, {
1043
+ apiKey: params.apiKey,
1044
+ maxTokens: SESSION_SLUG_MAX_TOKENS
1045
+ });
1046
+ const textContent = response.content.find((block) => block.type === "text");
1047
+ const slug = textContent?.type === "text" ? textContent.text.trim() : "";
1048
+ return slug.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 50) || "session";
1049
+ } catch (error) {
1050
+ log4.warn({ err: error }, "Slug generation failed, using fallback");
1051
+ const now = /* @__PURE__ */ new Date();
1052
+ return `session-${now.getHours().toString().padStart(2, "0")}${now.getMinutes().toString().padStart(2, "0")}`;
1053
+ }
1054
+ }
1055
+ async function saveSessionMemory(params) {
1056
+ try {
1057
+ const { TELETON_ROOT: TELETON_ROOT2 } = await import("./paths-TMNTEDDD.js");
1058
+ const memoryDir = join3(TELETON_ROOT2, "memory");
1059
+ await mkdir(memoryDir, { recursive: true });
1060
+ const now = /* @__PURE__ */ new Date();
1061
+ const dateStr = now.toISOString().split("T")[0];
1062
+ log4.info("Generating semantic slug for session memory...");
1063
+ const slug = await generateSlugViaClaude({
1064
+ messages: params.context.messages,
1065
+ apiKey: params.apiKey,
1066
+ provider: params.provider,
1067
+ utilityModel: params.utilityModel
1068
+ });
1069
+ const filename = `${dateStr}-${slug}.md`;
1070
+ const filepath = join3(memoryDir, filename);
1071
+ const timeStr = now.toISOString().split("T")[1].split(".")[0];
1072
+ log4.info("Generating session summary...");
1073
+ let summary;
1074
+ try {
1075
+ summary = await summarizeViaClaude({
1076
+ messages: params.context.messages,
1077
+ apiKey: params.apiKey,
1078
+ maxSummaryTokens: DEFAULT_MAX_SUMMARY_TOKENS,
1079
+ customInstructions: "Summarize this session comprehensively. Include key topics, decisions made, problems solved, and important context.",
1080
+ provider: params.provider,
1081
+ utilityModel: params.utilityModel
1082
+ });
1083
+ } catch (error) {
1084
+ log4.warn({ err: error }, "Session summary generation failed");
1085
+ summary = `Session contained ${params.context.messages.length} messages. Summary generation failed.`;
1086
+ }
1087
+ const content = `# Session Memory: ${dateStr} ${timeStr} UTC
1088
+
1089
+ ## Metadata
1090
+
1091
+ - **Old Session ID**: \`${params.oldSessionId}\`
1092
+ - **New Session ID**: \`${params.newSessionId}\`
1093
+ - **Chat ID**: \`${params.chatId}\`
1094
+ - **Timestamp**: ${now.toISOString()}
1095
+ - **Message Count**: ${params.context.messages.length}
1096
+
1097
+ ## Session Summary
1098
+
1099
+ ${summary}
1100
+
1101
+ ## Context
1102
+
1103
+ This session was compacted and migrated to a new session ID. The summary above preserves key information for continuity.
1104
+
1105
+ ---
1106
+
1107
+ *Generated automatically by Teleton-AI session memory hook*
1108
+ `;
1109
+ await writeFile(filepath, content, "utf-8");
1110
+ const relPath = filepath.replace(TELETON_ROOT2, "~/.teleton");
1111
+ log4.info(`Session memory saved: ${relPath}`);
1112
+ } catch (error) {
1113
+ log4.error({ err: error }, "Failed to save session memory");
1114
+ }
1115
+ }
1116
+
1117
+ // src/memory/compaction.ts
1118
+ import { encodingForModel } from "js-tiktoken";
1119
+ var DEFAULT_COMPACTION_CONFIG = {
1120
+ enabled: true,
1121
+ maxMessages: COMPACTION_MAX_MESSAGES,
1122
+ maxTokens: DEFAULT_MAX_TOKENS,
1123
+ keepRecentMessages: COMPACTION_KEEP_RECENT,
1124
+ memoryFlushEnabled: true,
1125
+ softThresholdTokens: DEFAULT_SOFT_THRESHOLD_TOKENS
1126
+ };
1127
+ var log5 = createLogger("Memory");
1128
+ var tokenizer = null;
1129
+ function getTokenizer() {
1130
+ if (!tokenizer) {
1131
+ tokenizer = encodingForModel("gpt-4");
1132
+ }
1133
+ return tokenizer;
1134
+ }
1135
+ function estimateTokens(content) {
1136
+ try {
1137
+ const enc = getTokenizer();
1138
+ return enc.encode(content).length;
1139
+ } catch (error) {
1140
+ log5.warn({ err: error }, "Token encoding failed, using fallback");
1141
+ return Math.ceil(content.length / 4);
1142
+ }
1143
+ }
1144
+ function calculateContextTokens(context) {
1145
+ let total = 0;
1146
+ if (context.systemPrompt) {
1147
+ total += estimateTokens(context.systemPrompt);
1148
+ }
1149
+ for (const message of context.messages) {
1150
+ if (message.role === "user") {
1151
+ if (typeof message.content === "string") {
1152
+ total += estimateTokens(message.content);
1153
+ } else if (Array.isArray(message.content)) {
1154
+ for (const block of message.content) {
1155
+ if (block.type === "text") total += estimateTokens(block.text);
1156
+ }
1157
+ }
1158
+ } else if (message.role === "assistant") {
1159
+ for (const block of message.content) {
1160
+ if (block.type === "text") {
1161
+ total += estimateTokens(block.text);
1162
+ }
1163
+ }
1164
+ }
1165
+ }
1166
+ return total;
1167
+ }
1168
+ function shouldFlushMemory(context, config, tokenCount) {
1169
+ if (!config.enabled || !config.memoryFlushEnabled) {
1170
+ return false;
1171
+ }
1172
+ const tokens = tokenCount ?? calculateContextTokens(context);
1173
+ const softThreshold = config.softThresholdTokens ?? FALLBACK_SOFT_THRESHOLD_TOKENS;
1174
+ if (tokens >= softThreshold) {
1175
+ log5.info(`Memory flush needed: ~${tokens} tokens (soft threshold: ${softThreshold})`);
1176
+ return true;
1177
+ }
1178
+ return false;
1179
+ }
1180
+ function flushMemoryToDailyLog(context) {
1181
+ const recentMessages = context.messages.slice(-MEMORY_FLUSH_RECENT_MESSAGES);
1182
+ const summary = [];
1183
+ summary.push("**Recent Context:**\n");
1184
+ for (const msg of recentMessages) {
1185
+ if (msg.role === "user") {
1186
+ const content = typeof msg.content === "string" ? msg.content : "[complex content]";
1187
+ summary.push(`- User: ${content.substring(0, 100)}${content.length > 100 ? "..." : ""}`);
1188
+ } else if (msg.role === "assistant") {
1189
+ const textBlocks = msg.content.filter((b) => b.type === "text");
1190
+ if (textBlocks.length > 0) {
1191
+ const text = textBlocks[0].text || "";
1192
+ summary.push(`- Assistant: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`);
1193
+ }
1194
+ }
1195
+ }
1196
+ writeSummaryToDailyLog(summary.join("\n"));
1197
+ log5.info(`Memory flushed to daily log`);
1198
+ }
1199
+ function shouldCompact(context, config, tokenCount) {
1200
+ if (!config.enabled) {
1201
+ return false;
1202
+ }
1203
+ const messageCount = context.messages.length;
1204
+ if (config.maxMessages && messageCount >= config.maxMessages) {
1205
+ log5.info(`Compaction needed: ${messageCount} messages (max: ${config.maxMessages})`);
1206
+ return true;
1207
+ }
1208
+ if (config.maxTokens) {
1209
+ const tokens = tokenCount ?? calculateContextTokens(context);
1210
+ if (tokens >= config.maxTokens) {
1211
+ log5.info(`Compaction needed: ~${tokens} tokens (max: ${config.maxTokens})`);
1212
+ return true;
1213
+ }
1214
+ }
1215
+ return false;
1216
+ }
1217
+ async function compactContext(context, config, apiKey, provider, utilityModel) {
1218
+ const keepCount = config.keepRecentMessages ?? 10;
1219
+ if (context.messages.length <= keepCount) {
1220
+ return context;
1221
+ }
1222
+ let cutIndex = context.messages.length - keepCount;
1223
+ const collectToolUseIds = (msgs) => {
1224
+ const ids = /* @__PURE__ */ new Set();
1225
+ for (const msg of msgs) {
1226
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
1227
+ for (const block of msg.content) {
1228
+ if (block.type === "toolCall") {
1229
+ if (block.id) ids.add(block.id);
1230
+ }
1231
+ }
1232
+ }
1233
+ }
1234
+ return ids;
1235
+ };
1236
+ const hasOrphanedToolResults = (msgs) => {
1237
+ const toolUseIds = collectToolUseIds(msgs);
1238
+ for (const msg of msgs) {
1239
+ if (msg.role === "toolResult") {
1240
+ if (msg.toolCallId && !toolUseIds.has(msg.toolCallId)) {
1241
+ return true;
1242
+ }
1243
+ }
1244
+ }
1245
+ return false;
1246
+ };
1247
+ let iterations = 0;
1248
+ while (cutIndex > 0 && iterations < 50) {
1249
+ const keptMessages = context.messages.slice(cutIndex);
1250
+ if (!hasOrphanedToolResults(keptMessages)) {
1251
+ break;
1252
+ }
1253
+ cutIndex--;
1254
+ iterations++;
1255
+ }
1256
+ if (hasOrphanedToolResults(context.messages.slice(cutIndex))) {
1257
+ log5.warn(`Compaction: couldn't find clean cut point, keeping all messages`);
1258
+ return context;
1259
+ }
1260
+ const recentMessages = context.messages.slice(cutIndex);
1261
+ const oldMessages = context.messages.slice(0, cutIndex);
1262
+ log5.info(
1263
+ `Compacting ${oldMessages.length} old messages, keeping ${recentMessages.length} recent (cut at clean boundary)`
1264
+ );
1265
+ try {
1266
+ const result = await summarizeWithFallback({
1267
+ messages: oldMessages,
1268
+ apiKey,
1269
+ contextWindow: config.maxTokens ?? DEFAULT_CONTEXT_WINDOW,
1270
+ maxSummaryTokens: DEFAULT_MAX_SUMMARY_TOKENS,
1271
+ customInstructions: `Output a structured summary using EXACTLY these sections:
1272
+
1273
+ ## User Intent
1274
+ What the user is trying to accomplish (1-2 sentences).
1275
+
1276
+ ## Key Decisions
1277
+ Bullet list of decisions made and commitments agreed upon.
1278
+
1279
+ ## Important Context
1280
+ Critical facts, preferences, constraints, or technical details needed for continuity.
1281
+
1282
+ ## Actions Taken
1283
+ What was done: tools used, messages sent, transactions made (with specific values/addresses if relevant).
1284
+
1285
+ ## Open Items
1286
+ Unfinished tasks, pending questions, or next steps.
1287
+
1288
+ Keep each section concise. Omit a section if empty. Preserve specific names, numbers, and identifiers.`,
1289
+ provider,
1290
+ utilityModel
1291
+ });
1292
+ log5.info(`AI Summary: ${result.tokensUsed} tokens, ${result.chunksProcessed} chunks processed`);
1293
+ const summaryText = `[Auto-compacted ${oldMessages.length} messages]
1294
+
1295
+ ${result.summary}`;
1296
+ const summaryMessage = {
1297
+ role: "user",
1298
+ content: summaryText,
1299
+ timestamp: oldMessages[0]?.timestamp ?? Date.now()
1300
+ };
1301
+ return {
1302
+ ...context,
1303
+ messages: [summaryMessage, ...recentMessages]
1304
+ };
1305
+ } catch (error) {
1306
+ log5.error({ err: error }, "AI summarization failed, using fallback");
1307
+ const summaryText = `[Auto-compacted: ${oldMessages.length} earlier messages from this conversation]`;
1308
+ const summaryMessage = {
1309
+ role: "user",
1310
+ content: summaryText,
1311
+ timestamp: oldMessages[0]?.timestamp ?? Date.now()
1312
+ };
1313
+ return {
1314
+ ...context,
1315
+ messages: [summaryMessage, ...recentMessages]
1316
+ };
1317
+ }
1318
+ }
1319
+ async function compactAndSaveTranscript(sessionId, context, config, apiKey, chatId, provider, utilityModel) {
1320
+ const newSessionId = randomUUID();
1321
+ log5.info(`Creating compacted transcript: ${sessionId} \u2192 ${newSessionId}`);
1322
+ if (chatId) {
1323
+ await saveSessionMemory({
1324
+ oldSessionId: sessionId,
1325
+ newSessionId,
1326
+ context,
1327
+ chatId,
1328
+ apiKey,
1329
+ provider,
1330
+ utilityModel
1331
+ });
1332
+ }
1333
+ const compactedContext = await compactContext(context, config, apiKey, provider, utilityModel);
1334
+ for (const message of compactedContext.messages) {
1335
+ appendToTranscript(newSessionId, message);
1336
+ }
1337
+ return newSessionId;
1338
+ }
1339
+ var CompactionManager = class {
1340
+ config;
1341
+ constructor(config = DEFAULT_COMPACTION_CONFIG) {
1342
+ this.config = config;
1343
+ }
1344
+ async checkAndCompact(sessionId, context, apiKey, chatId, provider, utilityModel) {
1345
+ const tokenCount = calculateContextTokens(context);
1346
+ if (shouldFlushMemory(context, this.config, tokenCount)) {
1347
+ flushMemoryToDailyLog(context);
1348
+ }
1349
+ if (!shouldCompact(context, this.config, tokenCount)) {
1350
+ return null;
1351
+ }
1352
+ if (this.config.memoryFlushEnabled) {
1353
+ flushMemoryToDailyLog(context);
1354
+ }
1355
+ log5.info(`Auto-compacting session ${sessionId}`);
1356
+ const newSessionId = await compactAndSaveTranscript(
1357
+ sessionId,
1358
+ context,
1359
+ this.config,
1360
+ apiKey,
1361
+ chatId,
1362
+ provider,
1363
+ utilityModel
1364
+ );
1365
+ log5.info(`Compaction complete: ${newSessionId}`);
1366
+ return newSessionId;
1367
+ }
1368
+ updateConfig(config) {
1369
+ this.config = { ...this.config, ...config };
1370
+ }
1371
+ getConfig() {
1372
+ return { ...this.config };
1373
+ }
1374
+ };
1375
+
1376
+ // src/memory/observation-masking.ts
1377
+ var DEFAULT_MASKING_CONFIG = {
1378
+ keepRecentCount: MASKING_KEEP_RECENT_COUNT,
1379
+ keepErrorResults: true
1380
+ };
1381
+ var isCocoonToolResult = (msg) => msg.role === "user" && Array.isArray(msg.content) && msg.content.some((c) => c.type === "text" && c.text.includes("<tool_response>"));
1382
+ function maskOldToolResults(messages, config = DEFAULT_MASKING_CONFIG, toolRegistry) {
1383
+ const toolResults = messages.map((msg, index) => ({ msg, index })).filter(({ msg }) => msg.role === "toolResult" || isCocoonToolResult(msg));
1384
+ if (toolResults.length <= config.keepRecentCount) {
1385
+ return messages;
1386
+ }
1387
+ const toMask = toolResults.slice(0, -config.keepRecentCount);
1388
+ const result = [...messages];
1389
+ for (const { msg, index } of toMask) {
1390
+ if (isCocoonToolResult(msg)) {
1391
+ result[index] = {
1392
+ ...msg,
1393
+ content: [{ type: "text", text: "[Tool response masked]" }]
1394
+ };
1395
+ continue;
1396
+ }
1397
+ const toolMsg = msg;
1398
+ if (config.keepErrorResults && toolMsg.isError) {
1399
+ continue;
1400
+ }
1401
+ if (toolRegistry) {
1402
+ const category = toolRegistry.getToolCategory(toolMsg.toolName);
1403
+ if (category === "data-bearing") {
1404
+ continue;
1405
+ }
1406
+ }
1407
+ let summaryText = "";
1408
+ try {
1409
+ const textBlock = toolMsg.content.find((c) => c.type === "text");
1410
+ if (textBlock) {
1411
+ const parsed = JSON.parse(textBlock.text);
1412
+ if (parsed.data?.summary) {
1413
+ summaryText = ` - ${parsed.data.summary}`;
1414
+ } else if (parsed.data?.message) {
1415
+ summaryText = ` - ${parsed.data.message}`;
1416
+ }
1417
+ }
1418
+ } catch {
1419
+ }
1420
+ result[index] = {
1421
+ ...toolMsg,
1422
+ content: [
1423
+ {
1424
+ type: "text",
1425
+ text: `[Tool: ${toolMsg.toolName} - ${toolMsg.isError ? "ERROR" : "OK"}${summaryText}]`
1426
+ }
1427
+ ]
1428
+ };
1429
+ }
1430
+ return result;
1431
+ }
1432
+
1433
+ // src/agent/runtime.ts
1434
+ var log6 = createLogger("Agent");
1435
+ var globalTokenUsage = { totalTokens: 0, totalCost: 0 };
1436
+ function getTokenUsage() {
1437
+ return { ...globalTokenUsage };
1438
+ }
1439
+ function isContextOverflowError(errorMessage) {
1440
+ if (!errorMessage) return false;
1441
+ const lower = errorMessage.toLowerCase();
1442
+ return lower.includes("prompt is too long") || lower.includes("context length exceeded") || lower.includes("maximum context length") || lower.includes("too many tokens") || lower.includes("request_too_large") || lower.includes("exceeds") && lower.includes("maximum") || lower.includes("context") && lower.includes("limit");
1443
+ }
1444
+ function isTrivialMessage(text) {
1445
+ const stripped = text.trim();
1446
+ if (!stripped) return true;
1447
+ if (!/[a-zA-Z0-9а-яА-ЯёЁ]/.test(stripped)) return true;
1448
+ const trivial = /^(ok|okay|k|oui|non|yes|no|yep|nope|sure|thanks|merci|thx|ty|lol|haha|cool|nice|wow|bravo|top|parfait|d'accord|alright|fine|got it|np|gg)\.?!?$/i;
1449
+ return trivial.test(stripped);
1450
+ }
1451
+ function extractContextSummary(context, maxMessages = 10) {
1452
+ const recentMessages = context.messages.slice(-maxMessages);
1453
+ const summaryParts = [];
1454
+ summaryParts.push("### Session Summary (Auto-saved before overflow reset)\n");
1455
+ for (const msg of recentMessages) {
1456
+ if (msg.role === "user") {
1457
+ const content = typeof msg.content === "string" ? msg.content : "[complex]";
1458
+ const bodyMatch = content.match(/\] (.+)/s);
1459
+ const body = bodyMatch ? bodyMatch[1] : content;
1460
+ summaryParts.push(`- **User**: ${body.substring(0, 150)}${body.length > 150 ? "..." : ""}`);
1461
+ } else if (msg.role === "assistant") {
1462
+ const textBlocks = msg.content.filter((b) => b.type === "text");
1463
+ const toolBlocks = msg.content.filter((b) => b.type === "toolCall");
1464
+ if (textBlocks.length > 0) {
1465
+ const text = textBlocks[0].text || "";
1466
+ summaryParts.push(
1467
+ `- **Agent**: ${text.substring(0, 150)}${text.length > 150 ? "..." : ""}`
1468
+ );
1469
+ }
1470
+ if (toolBlocks.length > 0) {
1471
+ const toolNames = toolBlocks.map((b) => b.name).join(", ");
1472
+ summaryParts.push(` - *Tools used: ${toolNames}*`);
1473
+ }
1474
+ } else if (msg.role === "toolResult") {
1475
+ const status = msg.isError ? "ERROR" : "OK";
1476
+ summaryParts.push(` - *Tool result: ${msg.toolName} \u2192 ${status}*`);
1477
+ }
1478
+ }
1479
+ return summaryParts.join("\n");
1480
+ }
1481
+ var AgentRuntime = class {
1482
+ config;
1483
+ soul;
1484
+ compactionManager;
1485
+ contextBuilder = null;
1486
+ toolRegistry = null;
1487
+ embedder = null;
1488
+ constructor(config, soul, toolRegistry) {
1489
+ this.config = config;
1490
+ this.soul = soul ?? "";
1491
+ this.toolRegistry = toolRegistry ?? null;
1492
+ const provider = config.agent.provider || "anthropic";
1493
+ try {
1494
+ const model = getProviderModel(provider, config.agent.model);
1495
+ const ctx = model.contextWindow;
1496
+ this.compactionManager = new CompactionManager({
1497
+ enabled: true,
1498
+ maxMessages: COMPACTION_MAX_MESSAGES,
1499
+ maxTokens: Math.floor(ctx * COMPACTION_MAX_TOKENS_RATIO),
1500
+ keepRecentMessages: COMPACTION_KEEP_RECENT,
1501
+ memoryFlushEnabled: true,
1502
+ softThresholdTokens: Math.floor(ctx * COMPACTION_SOFT_THRESHOLD_RATIO)
1503
+ });
1504
+ } catch {
1505
+ this.compactionManager = new CompactionManager(DEFAULT_COMPACTION_CONFIG);
1506
+ }
1507
+ }
1508
+ initializeContextBuilder(embedder, vectorEnabled) {
1509
+ this.embedder = embedder;
1510
+ const db = getDatabase().getDb();
1511
+ this.contextBuilder = new ContextBuilder(db, embedder, vectorEnabled);
1512
+ }
1513
+ getToolRegistry() {
1514
+ return this.toolRegistry;
1515
+ }
1516
+ async processMessage(chatId, userMessage, userName, timestamp, isGroup, pendingContext, toolContext, senderUsername, hasMedia, mediaType, messageId, replyContext) {
1517
+ try {
1518
+ let session = getOrCreateSession(chatId);
1519
+ const now = timestamp ?? Date.now();
1520
+ const resetPolicy = this.config.agent.session_reset_policy;
1521
+ if (shouldResetSession(session, resetPolicy)) {
1522
+ log6.info(`\u{1F504} Auto-resetting session based on policy`);
1523
+ if (transcriptExists(session.sessionId)) {
1524
+ try {
1525
+ log6.info(`\u{1F4BE} Saving memory before daily reset...`);
1526
+ const oldContext = loadContextFromTranscript(session.sessionId);
1527
+ await saveSessionMemory({
1528
+ oldSessionId: session.sessionId,
1529
+ newSessionId: "pending",
1530
+ context: oldContext,
1531
+ chatId,
1532
+ apiKey: getEffectiveApiKey(this.config.agent.provider, this.config.agent.api_key),
1533
+ provider: this.config.agent.provider,
1534
+ utilityModel: this.config.agent.utility_model
1535
+ });
1536
+ log6.info(`\u2705 Memory saved before reset`);
1537
+ } catch (error) {
1538
+ log6.warn({ err: error }, `\u26A0\uFE0F Failed to save memory before reset`);
1539
+ }
1540
+ }
1541
+ session = resetSessionWithPolicy(chatId, resetPolicy);
1542
+ }
1543
+ let context = loadContextFromTranscript(session.sessionId);
1544
+ if (context.messages.length > 0) {
1545
+ log6.info(`\u{1F4D6} Loading existing session: ${session.sessionId}`);
1546
+ } else {
1547
+ log6.info(`\u{1F195} Starting new session: ${session.sessionId}`);
1548
+ }
1549
+ const previousTimestamp = session.updatedAt;
1550
+ let formattedMessage = formatMessageEnvelope({
1551
+ channel: "Telegram",
1552
+ senderId: toolContext?.senderId ? String(toolContext.senderId) : chatId,
1553
+ senderName: userName,
1554
+ senderUsername,
1555
+ timestamp: now,
1556
+ previousTimestamp,
1557
+ body: userMessage,
1558
+ isGroup: isGroup ?? false,
1559
+ hasMedia,
1560
+ mediaType,
1561
+ messageId,
1562
+ replyContext
1563
+ });
1564
+ if (pendingContext) {
1565
+ formattedMessage = `${pendingContext}
1566
+
1567
+ ${formattedMessage}`;
1568
+ log6.debug(`\u{1F4CB} Including ${pendingContext.split("\n").length - 1} pending messages`);
1569
+ }
1570
+ log6.debug(`\u{1F4E8} Formatted message: ${formattedMessage.substring(0, 100)}...`);
1571
+ const preview = formattedMessage.slice(0, 50).replace(/\n/g, " ");
1572
+ const who = senderUsername ? `@${senderUsername}` : userName;
1573
+ const msgType = isGroup ? `Group ${chatId} ${who}` : `DM ${who}`;
1574
+ log6.info(`\u{1F4E8} ${msgType}: "${preview}${formattedMessage.length > 50 ? "..." : ""}"`);
1575
+ let relevantContext = "";
1576
+ if (this.contextBuilder && !isTrivialMessage(userMessage)) {
1577
+ try {
1578
+ const dbContext = await this.contextBuilder.buildContext({
1579
+ query: userMessage,
1580
+ chatId,
1581
+ includeAgentMemory: true,
1582
+ includeFeedHistory: true,
1583
+ searchAllChats: !isGroup,
1584
+ maxRecentMessages: CONTEXT_MAX_RECENT_MESSAGES,
1585
+ maxRelevantChunks: CONTEXT_MAX_RELEVANT_CHUNKS
1586
+ });
1587
+ const contextParts = [];
1588
+ if (dbContext.relevantKnowledge.length > 0) {
1589
+ const sanitizedKnowledge = dbContext.relevantKnowledge.map(
1590
+ (chunk) => sanitizeForContext(chunk)
1591
+ );
1592
+ contextParts.push(
1593
+ `[Relevant knowledge from memory]
1594
+ ${sanitizedKnowledge.join("\n---\n")}`
1595
+ );
1596
+ }
1597
+ if (dbContext.relevantFeed.length > 0) {
1598
+ const sanitizedFeed = dbContext.relevantFeed.map((msg) => sanitizeForContext(msg));
1599
+ contextParts.push(
1600
+ `[Relevant messages from Telegram feed]
1601
+ ${sanitizedFeed.join("\n")}`
1602
+ );
1603
+ }
1604
+ if (contextParts.length > 0) {
1605
+ relevantContext = contextParts.join("\n\n");
1606
+ log6.debug(
1607
+ `\u{1F50D} Found ${dbContext.relevantKnowledge.length} knowledge chunks, ${dbContext.relevantFeed.length} feed messages`
1608
+ );
1609
+ }
1610
+ } catch (error) {
1611
+ log6.warn({ err: error }, "Context building failed");
1612
+ }
1613
+ }
1614
+ const memoryStats = this.getMemoryStats();
1615
+ const statsContext = `[Memory Status: ${memoryStats.totalMessages} messages across ${memoryStats.totalChats} chats, ${memoryStats.knowledgeChunks} knowledge chunks]`;
1616
+ const additionalContext = relevantContext ? `You are in a Telegram conversation with chat ID: ${chatId}. Maintain conversation continuity.
1617
+
1618
+ ${statsContext}
1619
+
1620
+ ${relevantContext}` : `You are in a Telegram conversation with chat ID: ${chatId}. Maintain conversation continuity.
1621
+
1622
+ ${statsContext}`;
1623
+ const compactionConfig = this.compactionManager.getConfig();
1624
+ const needsMemoryFlush = compactionConfig.enabled && compactionConfig.memoryFlushEnabled && context.messages.length > Math.floor((compactionConfig.maxMessages ?? 200) * 0.75);
1625
+ const systemPrompt = buildSystemPrompt({
1626
+ soul: this.soul,
1627
+ userName,
1628
+ senderUsername,
1629
+ senderId: toolContext?.senderId,
1630
+ ownerName: this.config.telegram.owner_name,
1631
+ ownerUsername: this.config.telegram.owner_username,
1632
+ context: additionalContext,
1633
+ includeMemory: !isGroup,
1634
+ includeStrategy: !isGroup,
1635
+ memoryFlushWarning: needsMemoryFlush
1636
+ });
1637
+ const userMsg = {
1638
+ role: "user",
1639
+ content: formattedMessage,
1640
+ timestamp: now
1641
+ };
1642
+ context.messages.push(userMsg);
1643
+ const preemptiveCompaction = await this.compactionManager.checkAndCompact(
1644
+ session.sessionId,
1645
+ context,
1646
+ getEffectiveApiKey(this.config.agent.provider, this.config.agent.api_key),
1647
+ chatId,
1648
+ this.config.agent.provider,
1649
+ this.config.agent.utility_model
1650
+ );
1651
+ if (preemptiveCompaction) {
1652
+ log6.info(`\u{1F5DC}\uFE0F Preemptive compaction triggered, reloading session...`);
1653
+ session = getSession(chatId);
1654
+ context = loadContextFromTranscript(session.sessionId);
1655
+ context.messages.push(userMsg);
1656
+ }
1657
+ appendToTranscript(session.sessionId, userMsg);
1658
+ const provider = this.config.agent.provider || "anthropic";
1659
+ const providerMeta = getProviderMetadata(provider);
1660
+ const isAdmin = toolContext?.config?.telegram.admin_ids.includes(toolContext.senderId) ?? false;
1661
+ let tools;
1662
+ {
1663
+ const toolIndex = this.toolRegistry?.getToolIndex();
1664
+ const useRAG = toolIndex?.isIndexed && this.config.tool_rag?.enabled !== false && !isTrivialMessage(userMessage) && !(providerMeta.toolLimit === null && this.config.tool_rag?.skip_unlimited_providers !== false);
1665
+ if (useRAG && this.toolRegistry && this.embedder) {
1666
+ const queryEmbedding = await this.embedder.embedQuery(userMessage);
1667
+ tools = await this.toolRegistry.getForContextWithRAG(
1668
+ userMessage,
1669
+ queryEmbedding,
1670
+ isGroup ?? false,
1671
+ providerMeta.toolLimit,
1672
+ chatId,
1673
+ isAdmin
1674
+ );
1675
+ log6.info(`\u{1F50D} Tool RAG: ${tools.length}/${this.toolRegistry.count} tools selected`);
1676
+ } else {
1677
+ tools = this.toolRegistry?.getForContext(
1678
+ isGroup ?? false,
1679
+ providerMeta.toolLimit,
1680
+ chatId,
1681
+ isAdmin
1682
+ );
1683
+ }
1684
+ }
1685
+ const maxIterations = this.config.agent.max_agentic_iterations || 5;
1686
+ let iteration = 0;
1687
+ let overflowResets = 0;
1688
+ let rateLimitRetries = 0;
1689
+ let serverErrorRetries = 0;
1690
+ let finalResponse = null;
1691
+ const totalToolCalls = [];
1692
+ const accumulatedTexts = [];
1693
+ const accumulatedUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalCost: 0 };
1694
+ while (iteration < maxIterations) {
1695
+ iteration++;
1696
+ log6.debug(`\u{1F504} Agentic iteration ${iteration}/${maxIterations}`);
1697
+ const maskedMessages = maskOldToolResults(
1698
+ context.messages,
1699
+ void 0,
1700
+ this.toolRegistry ?? void 0
1701
+ );
1702
+ const maskedContext = { ...context, messages: maskedMessages };
1703
+ const response2 = await chatWithContext(this.config.agent, {
1704
+ systemPrompt,
1705
+ context: maskedContext,
1706
+ sessionId: session.sessionId,
1707
+ persistTranscript: true,
1708
+ tools
1709
+ });
1710
+ const assistantMsg = response2.message;
1711
+ if (assistantMsg.stopReason === "error") {
1712
+ const errorMsg = assistantMsg.errorMessage || "";
1713
+ if (isContextOverflowError(errorMsg)) {
1714
+ overflowResets++;
1715
+ if (overflowResets > 1) {
1716
+ throw new Error(
1717
+ "Context overflow persists after session reset. Message may be too large for the model's context window."
1718
+ );
1719
+ }
1720
+ log6.error(`\u{1F6A8} Context overflow detected: ${errorMsg}`);
1721
+ log6.info(`\u{1F4BE} Saving session memory before reset...`);
1722
+ const summary = extractContextSummary(context, CONTEXT_OVERFLOW_SUMMARY_MESSAGES);
1723
+ appendToDailyLog(summary);
1724
+ log6.info(`\u2705 Memory saved to daily log`);
1725
+ const archived = archiveTranscript(session.sessionId);
1726
+ if (!archived) {
1727
+ log6.error(
1728
+ `\u26A0\uFE0F Failed to archive transcript ${session.sessionId}, proceeding with reset anyway`
1729
+ );
1730
+ }
1731
+ log6.info(`\u{1F504} Resetting session due to context overflow...`);
1732
+ session = resetSession(chatId);
1733
+ context = { messages: [userMsg] };
1734
+ appendToTranscript(session.sessionId, userMsg);
1735
+ log6.info(`\u{1F504} Retrying with fresh context...`);
1736
+ continue;
1737
+ } else if (errorMsg.toLowerCase().includes("rate") || errorMsg.includes("429")) {
1738
+ rateLimitRetries++;
1739
+ if (rateLimitRetries <= RATE_LIMIT_MAX_RETRIES) {
1740
+ const delay = 1e3 * Math.pow(2, rateLimitRetries - 1);
1741
+ log6.warn(
1742
+ `\u{1F6AB} Rate limited, retrying in ${delay}ms (attempt ${rateLimitRetries}/${RATE_LIMIT_MAX_RETRIES})...`
1743
+ );
1744
+ await new Promise((r) => setTimeout(r, delay));
1745
+ iteration--;
1746
+ continue;
1747
+ }
1748
+ log6.error(`\u{1F6AB} Rate limited after ${RATE_LIMIT_MAX_RETRIES} retries: ${errorMsg}`);
1749
+ throw new Error(
1750
+ `API rate limited after ${RATE_LIMIT_MAX_RETRIES} retries. Please try again later.`
1751
+ );
1752
+ } else if (errorMsg.includes("500") || errorMsg.includes("502") || errorMsg.includes("503") || errorMsg.includes("529")) {
1753
+ serverErrorRetries++;
1754
+ if (serverErrorRetries <= SERVER_ERROR_MAX_RETRIES) {
1755
+ const delay = 2e3 * Math.pow(2, serverErrorRetries - 1);
1756
+ log6.warn(
1757
+ `\u{1F504} Server error, retrying in ${delay}ms (attempt ${serverErrorRetries}/${SERVER_ERROR_MAX_RETRIES})...`
1758
+ );
1759
+ await new Promise((r) => setTimeout(r, delay));
1760
+ iteration--;
1761
+ continue;
1762
+ }
1763
+ log6.error(`\u{1F6A8} Server error after ${SERVER_ERROR_MAX_RETRIES} retries: ${errorMsg}`);
1764
+ throw new Error(
1765
+ `API server error after ${SERVER_ERROR_MAX_RETRIES} retries. The provider may be experiencing issues.`
1766
+ );
1767
+ } else {
1768
+ log6.error(`\u{1F6A8} API error: ${errorMsg}`);
1769
+ throw new Error(`API error: ${errorMsg || "Unknown error"}`);
1770
+ }
1771
+ }
1772
+ const iterUsage = response2.message.usage;
1773
+ if (iterUsage) {
1774
+ accumulatedUsage.input += iterUsage.input;
1775
+ accumulatedUsage.output += iterUsage.output;
1776
+ accumulatedUsage.cacheRead += iterUsage.cacheRead ?? 0;
1777
+ accumulatedUsage.cacheWrite += iterUsage.cacheWrite ?? 0;
1778
+ accumulatedUsage.totalCost += iterUsage.cost?.total ?? 0;
1779
+ }
1780
+ if (response2.text) {
1781
+ accumulatedTexts.push(response2.text);
1782
+ }
1783
+ const toolCalls = response2.message.content.filter((block) => block.type === "toolCall");
1784
+ if (toolCalls.length === 0) {
1785
+ log6.info(`\u{1F504} ${iteration}/${maxIterations} \u2192 done`);
1786
+ finalResponse = response2;
1787
+ break;
1788
+ }
1789
+ if (!this.toolRegistry || !toolContext) {
1790
+ log6.error("\u26A0\uFE0F Cannot execute tools: registry or context missing");
1791
+ break;
1792
+ }
1793
+ log6.debug(`\u{1F527} Executing ${toolCalls.length} tool call(s)`);
1794
+ context.messages.push(response2.message);
1795
+ const iterationToolNames = [];
1796
+ for (const block of toolCalls) {
1797
+ if (block.type !== "toolCall") continue;
1798
+ const fullContext = {
1799
+ ...toolContext,
1800
+ chatId,
1801
+ isGroup: isGroup ?? false
1802
+ };
1803
+ const result = await this.toolRegistry.execute(block, fullContext);
1804
+ log6.debug(`${block.name}: ${result.success ? "\u2713" : "\u2717"} ${result.error || ""}`);
1805
+ iterationToolNames.push(`${block.name} ${result.success ? "\u2713" : "\u2717"}`);
1806
+ totalToolCalls.push({
1807
+ name: block.name,
1808
+ input: block.arguments
1809
+ });
1810
+ let resultText = JSON.stringify(result, null, 2);
1811
+ if (resultText.length > MAX_TOOL_RESULT_SIZE) {
1812
+ log6.warn(`\u26A0\uFE0F Tool result too large (${resultText.length} chars), truncating...`);
1813
+ const data = result.data;
1814
+ if (data?.summary || data?.message) {
1815
+ resultText = JSON.stringify(
1816
+ {
1817
+ success: result.success,
1818
+ data: {
1819
+ summary: data.summary || data.message,
1820
+ _truncated: true,
1821
+ _originalSize: resultText.length,
1822
+ _message: "Full data truncated. Use limit parameter for smaller results."
1823
+ }
1824
+ },
1825
+ null,
1826
+ 2
1827
+ );
1828
+ } else {
1829
+ resultText = resultText.slice(0, MAX_TOOL_RESULT_SIZE) + "\n...[TRUNCATED]";
1830
+ }
1831
+ }
1832
+ if (provider === "cocoon") {
1833
+ const { wrapToolResult } = await import("./tool-adapter-Y3TCEQOC.js");
1834
+ const cocoonResultMsg = {
1835
+ role: "user",
1836
+ content: [
1837
+ {
1838
+ type: "text",
1839
+ text: wrapToolResult(resultText)
1840
+ }
1841
+ ],
1842
+ timestamp: Date.now()
1843
+ };
1844
+ context.messages.push(cocoonResultMsg);
1845
+ appendToTranscript(session.sessionId, cocoonResultMsg);
1846
+ } else {
1847
+ const toolResultMsg = {
1848
+ role: "toolResult",
1849
+ toolCallId: block.id,
1850
+ toolName: block.name,
1851
+ content: [
1852
+ {
1853
+ type: "text",
1854
+ text: resultText
1855
+ }
1856
+ ],
1857
+ isError: !result.success,
1858
+ timestamp: Date.now()
1859
+ };
1860
+ context.messages.push(toolResultMsg);
1861
+ appendToTranscript(session.sessionId, toolResultMsg);
1862
+ }
1863
+ }
1864
+ log6.info(`\u{1F504} ${iteration}/${maxIterations} \u2192 ${iterationToolNames.join(", ")}`);
1865
+ if (iteration === maxIterations) {
1866
+ log6.info(`\u26A0\uFE0F Max iterations reached (${maxIterations})`);
1867
+ finalResponse = response2;
1868
+ }
1869
+ }
1870
+ if (!finalResponse) {
1871
+ log6.error("\u26A0\uFE0F Agentic loop exited early without final response");
1872
+ return {
1873
+ content: "Internal error: Agent loop failed to produce a response.",
1874
+ toolCalls: []
1875
+ };
1876
+ }
1877
+ const response = finalResponse;
1878
+ const lastMsg = context.messages[context.messages.length - 1];
1879
+ if (lastMsg?.role !== "assistant") {
1880
+ context.messages.push(response.message);
1881
+ }
1882
+ const newSessionId = await this.compactionManager.checkAndCompact(
1883
+ session.sessionId,
1884
+ context,
1885
+ getEffectiveApiKey(this.config.agent.provider, this.config.agent.api_key),
1886
+ chatId,
1887
+ this.config.agent.provider,
1888
+ this.config.agent.utility_model
1889
+ );
1890
+ const sessionUpdate = {
1891
+ updatedAt: Date.now(),
1892
+ messageCount: session.messageCount + 1,
1893
+ model: this.config.agent.model,
1894
+ provider: this.config.agent.provider,
1895
+ inputTokens: (session.inputTokens ?? 0) + accumulatedUsage.input + accumulatedUsage.cacheRead + accumulatedUsage.cacheWrite,
1896
+ outputTokens: (session.outputTokens ?? 0) + accumulatedUsage.output
1897
+ };
1898
+ if (newSessionId) {
1899
+ sessionUpdate.sessionId = newSessionId;
1900
+ }
1901
+ updateSession(chatId, sessionUpdate);
1902
+ if (accumulatedUsage.input > 0 || accumulatedUsage.output > 0) {
1903
+ const u = accumulatedUsage;
1904
+ const totalInput = u.input + u.cacheRead + u.cacheWrite;
1905
+ const inK = (totalInput / 1e3).toFixed(1);
1906
+ const cacheParts = [];
1907
+ if (u.cacheRead) cacheParts.push(`${(u.cacheRead / 1e3).toFixed(1)}K cached`);
1908
+ if (u.cacheWrite) cacheParts.push(`${(u.cacheWrite / 1e3).toFixed(1)}K new`);
1909
+ const cacheInfo = cacheParts.length > 0 ? ` (${cacheParts.join(", ")})` : "";
1910
+ log6.info(`\u{1F4B0} ${inK}K in${cacheInfo}, ${u.output} out | $${u.totalCost.toFixed(3)}`);
1911
+ globalTokenUsage.totalTokens += u.input + u.output + u.cacheRead + u.cacheWrite;
1912
+ globalTokenUsage.totalCost += u.totalCost;
1913
+ }
1914
+ let content = accumulatedTexts.join("\n").trim() || response.text;
1915
+ const usedTelegramSendTool = totalToolCalls.some((tc) => TELEGRAM_SEND_TOOLS.has(tc.name));
1916
+ if (!content && totalToolCalls.length > 0 && !usedTelegramSendTool) {
1917
+ log6.warn("\u26A0\uFE0F Empty response after tool calls - generating fallback");
1918
+ content = "I executed the requested action but couldn't generate a response. Please try again.";
1919
+ } else if (!content && usedTelegramSendTool) {
1920
+ log6.info("\u2705 Response sent via Telegram tool - no additional text needed");
1921
+ content = "";
1922
+ } else if (!content && accumulatedUsage.input === 0 && accumulatedUsage.output === 0) {
1923
+ log6.warn("\u26A0\uFE0F Empty response with zero tokens - possible API issue");
1924
+ content = "I couldn't process your request. Please try again.";
1925
+ }
1926
+ return {
1927
+ content,
1928
+ toolCalls: totalToolCalls
1929
+ };
1930
+ } catch (error) {
1931
+ log6.error({ err: error }, "Agent error");
1932
+ throw error;
1933
+ }
1934
+ }
1935
+ clearHistory(chatId) {
1936
+ const db = getDatabase().getDb();
1937
+ db.prepare(
1938
+ `DELETE FROM tg_messages_vec WHERE id IN (
1939
+ SELECT id FROM tg_messages WHERE chat_id = ?
1940
+ )`
1941
+ ).run(chatId);
1942
+ db.prepare(`DELETE FROM tg_messages WHERE chat_id = ?`).run(chatId);
1943
+ resetSession(chatId);
1944
+ log6.info(`\u{1F5D1}\uFE0F Cleared history for chat ${chatId}`);
1945
+ }
1946
+ getConfig() {
1947
+ return this.config;
1948
+ }
1949
+ getActiveChatIds() {
1950
+ const db = getDatabase().getDb();
1951
+ const rows = db.prepare(
1952
+ `
1953
+ SELECT DISTINCT chat_id
1954
+ FROM tg_messages
1955
+ ORDER BY timestamp DESC
1956
+ `
1957
+ ).all();
1958
+ return rows.map((r) => r.chat_id);
1959
+ }
1960
+ setSoul(soul) {
1961
+ this.soul = soul;
1962
+ }
1963
+ configureCompaction(config) {
1964
+ this.compactionManager.updateConfig(config);
1965
+ log6.info({ config: this.compactionManager.getConfig() }, `\u{1F5DC}\uFE0F Compaction config updated`);
1966
+ }
1967
+ getCompactionConfig() {
1968
+ return this.compactionManager.getConfig();
1969
+ }
1970
+ _memoryStatsCache = null;
1971
+ getMemoryStats() {
1972
+ const now = Date.now();
1973
+ if (this._memoryStatsCache && now < this._memoryStatsCache.expiry) {
1974
+ return this._memoryStatsCache.data;
1975
+ }
1976
+ const db = getDatabase().getDb();
1977
+ const msgCount = db.prepare(`SELECT COUNT(*) as count FROM tg_messages`).get();
1978
+ const chatCount = db.prepare(`SELECT COUNT(DISTINCT chat_id) as count FROM tg_messages`).get();
1979
+ const knowledgeCount = db.prepare(`SELECT COUNT(*) as count FROM knowledge`).get();
1980
+ const data = {
1981
+ totalMessages: msgCount.count,
1982
+ totalChats: chatCount.count,
1983
+ knowledgeChunks: knowledgeCount.count
1984
+ };
1985
+ this._memoryStatsCache = { data, expiry: now + 5 * 60 * 1e3 };
1986
+ return data;
1987
+ }
1988
+ };
1989
+
576
1990
  // src/agent/tools/plugin-loader.ts
577
1991
  import { readdirSync as readdirSync2, readFileSync as readFileSync5, existsSync as existsSync6, statSync } from "fs";
578
- import { join as join4 } from "path";
1992
+ import { join as join5 } from "path";
579
1993
  import { pathToFileURL } from "url";
580
1994
  import { execFile } from "child_process";
581
1995
  import { promisify } from "util";
582
1996
 
583
1997
  // src/agent/tools/plugin-validator.ts
584
1998
  import { z } from "zod";
585
- var log3 = createLogger("PluginValidator");
1999
+ var log7 = createLogger("PluginValidator");
586
2000
  var ManifestSchema = z.object({
587
2001
  name: z.string().min(1).max(64).regex(
588
2002
  /^[a-z0-9][a-z0-9-]*$/,
@@ -610,20 +2024,20 @@ function validateToolDefs(defs, pluginName) {
610
2024
  const valid = [];
611
2025
  for (const def of defs) {
612
2026
  if (!def || typeof def !== "object") {
613
- log3.warn(`[${pluginName}] tool is not an object, skipping`);
2027
+ log7.warn(`[${pluginName}] tool is not an object, skipping`);
614
2028
  continue;
615
2029
  }
616
2030
  const t = def;
617
2031
  if (!t.name || typeof t.name !== "string") {
618
- log3.warn(`[${pluginName}] tool missing 'name', skipping`);
2032
+ log7.warn(`[${pluginName}] tool missing 'name', skipping`);
619
2033
  continue;
620
2034
  }
621
2035
  if (!t.description || typeof t.description !== "string") {
622
- log3.warn(`[${pluginName}] tool "${t.name}" missing 'description', skipping`);
2036
+ log7.warn(`[${pluginName}] tool "${t.name}" missing 'description', skipping`);
623
2037
  continue;
624
2038
  }
625
2039
  if (!t.execute || typeof t.execute !== "function") {
626
- log3.warn(`[${pluginName}] tool "${t.name}" missing 'execute' function, skipping`);
2040
+ log7.warn(`[${pluginName}] tool "${t.name}" missing 'execute' function, skipping`);
627
2041
  continue;
628
2042
  }
629
2043
  valid.push(t);
@@ -672,25 +2086,25 @@ function withTxLock(fn) {
672
2086
  }
673
2087
 
674
2088
  // src/ton/transfer.ts
675
- var log4 = createLogger("TON");
2089
+ var log8 = createLogger("TON");
676
2090
  async function sendTon(params) {
677
2091
  return withTxLock(async () => {
678
2092
  try {
679
2093
  const { toAddress, amount, comment = "", bounce = false } = params;
680
2094
  if (!Number.isFinite(amount) || amount <= 0) {
681
- log4.error({ amount }, "Invalid transfer amount");
2095
+ log8.error({ amount }, "Invalid transfer amount");
682
2096
  return null;
683
2097
  }
684
2098
  let recipientAddress;
685
2099
  try {
686
2100
  recipientAddress = Address.parse(toAddress);
687
2101
  } catch (e) {
688
- log4.error({ err: e }, `Invalid recipient address: ${toAddress}`);
2102
+ log8.error({ err: e }, `Invalid recipient address: ${toAddress}`);
689
2103
  return null;
690
2104
  }
691
2105
  const keyPair = await getKeyPair();
692
2106
  if (!keyPair) {
693
- log4.error("Wallet not initialized");
2107
+ log8.error("Wallet not initialized");
694
2108
  return null;
695
2109
  }
696
2110
  const wallet = WalletContractV5R1.create({
@@ -714,20 +2128,21 @@ async function sendTon(params) {
714
2128
  ]
715
2129
  });
716
2130
  const pseudoHash = `${seqno}_${Date.now()}_${amount.toFixed(2)}`;
717
- log4.info(`Sent ${amount} TON to ${toAddress.slice(0, 8)}... - seqno: ${seqno}`);
2131
+ log8.info(`Sent ${amount} TON to ${toAddress.slice(0, 8)}... - seqno: ${seqno}`);
718
2132
  return pseudoHash;
719
2133
  } catch (error) {
720
- if (error?.status >= 500 || error?.response?.status >= 500) {
2134
+ const status = error?.status || error?.response?.status;
2135
+ if (status === 429 || status >= 500) {
721
2136
  invalidateTonClientCache();
722
2137
  }
723
- log4.error({ err: error }, "Error sending TON");
2138
+ log8.error({ err: error }, "Error sending TON");
724
2139
  throw error;
725
2140
  }
726
2141
  });
727
2142
  }
728
2143
 
729
2144
  // src/utils/retry.ts
730
- var log5 = createLogger("Utils");
2145
+ var log9 = createLogger("Utils");
731
2146
  var DEFAULT_OPTIONS = {
732
2147
  maxAttempts: RETRY_DEFAULT_MAX_ATTEMPTS,
733
2148
  baseDelayMs: RETRY_DEFAULT_BASE_DELAY_MS,
@@ -751,7 +2166,7 @@ async function withRetry(fn, options = {}) {
751
2166
  return result;
752
2167
  } catch (error) {
753
2168
  lastError = error instanceof Error ? error : new Error(String(error));
754
- log5.warn(`Retry attempt ${attempt}/${opts.maxAttempts} failed: ${lastError.message}`);
2169
+ log9.warn(`Retry attempt ${attempt}/${opts.maxAttempts} failed: ${lastError.message}`);
755
2170
  if (attempt < opts.maxAttempts) {
756
2171
  const delay = Math.min(opts.baseDelayMs * Math.pow(2, attempt - 1), opts.maxDelayMs);
757
2172
  await sleep(delay);
@@ -950,25 +2365,25 @@ function findJettonBalance(balances, jettonAddress) {
950
2365
  }
951
2366
  });
952
2367
  }
953
- function cleanupOldTransactions(db, retentionDays, log7) {
2368
+ function cleanupOldTransactions(db, retentionDays, log11) {
954
2369
  if (Math.random() > CLEANUP_PROBABILITY) return;
955
2370
  try {
956
2371
  const cutoff = Math.floor(Date.now() / 1e3) - retentionDays * 24 * 60 * 60;
957
2372
  const result = db.prepare("DELETE FROM used_transactions WHERE used_at < ?").run(cutoff);
958
2373
  if (result.changes > 0) {
959
- log7.debug(`Cleaned up ${result.changes} old transaction records (>${retentionDays}d)`);
2374
+ log11.debug(`Cleaned up ${result.changes} old transaction records (>${retentionDays}d)`);
960
2375
  }
961
2376
  } catch (err) {
962
- log7.error("Transaction cleanup failed:", err);
2377
+ log11.error("Transaction cleanup failed:", err);
963
2378
  }
964
2379
  }
965
- function createTonSDK(log7, db) {
2380
+ function createTonSDK(log11, db) {
966
2381
  return {
967
2382
  getAddress() {
968
2383
  try {
969
2384
  return getWalletAddress();
970
2385
  } catch (err) {
971
- log7.error("ton.getAddress() failed:", err);
2386
+ log11.error("ton.getAddress() failed:", err);
972
2387
  return null;
973
2388
  }
974
2389
  },
@@ -978,7 +2393,7 @@ function createTonSDK(log7, db) {
978
2393
  if (!addr) return null;
979
2394
  return await getWalletBalance(addr);
980
2395
  } catch (err) {
981
- log7.error("ton.getBalance() failed:", err);
2396
+ log11.error("ton.getBalance() failed:", err);
982
2397
  return null;
983
2398
  }
984
2399
  },
@@ -986,7 +2401,7 @@ function createTonSDK(log7, db) {
986
2401
  try {
987
2402
  return await getTonPrice();
988
2403
  } catch (err) {
989
- log7.error("ton.getPrice() failed:", err);
2404
+ log11.error("ton.getPrice() failed:", err);
990
2405
  return null;
991
2406
  }
992
2407
  },
@@ -1037,7 +2452,7 @@ function createTonSDK(log7, db) {
1037
2452
  );
1038
2453
  return formatTransactions(transactions);
1039
2454
  } catch (err) {
1040
- log7.error("ton.getTransactions() failed:", err);
2455
+ log11.error("ton.getTransactions() failed:", err);
1041
2456
  return [];
1042
2457
  }
1043
2458
  },
@@ -1053,7 +2468,7 @@ function createTonSDK(log7, db) {
1053
2468
  throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
1054
2469
  }
1055
2470
  const maxAgeMinutes = params.maxAgeMinutes ?? DEFAULT_MAX_AGE_MINUTES;
1056
- cleanupOldTransactions(db, DEFAULT_TX_RETENTION_DAYS, log7);
2471
+ cleanupOldTransactions(db, DEFAULT_TX_RETENTION_DAYS, log11);
1057
2472
  try {
1058
2473
  const txs = await this.getTransactions(address, 20);
1059
2474
  for (const tx of txs) {
@@ -1087,7 +2502,7 @@ function createTonSDK(log7, db) {
1087
2502
  };
1088
2503
  } catch (err) {
1089
2504
  if (err instanceof PluginSDKError) throw err;
1090
- log7.error("ton.verifyPayment() failed:", err);
2505
+ log11.error("ton.verifyPayment() failed:", err);
1091
2506
  return {
1092
2507
  verified: false,
1093
2508
  error: `Verification failed: ${err instanceof Error ? err.message : String(err)}`
@@ -1101,7 +2516,7 @@ function createTonSDK(log7, db) {
1101
2516
  if (!addr) return [];
1102
2517
  const response = await tonapiFetch(`/accounts/${encodeURIComponent(addr)}/jettons`);
1103
2518
  if (!response.ok) {
1104
- log7.error(`ton.getJettonBalances() TonAPI error: ${response.status}`);
2519
+ log11.error(`ton.getJettonBalances() TonAPI error: ${response.status}`);
1105
2520
  return [];
1106
2521
  }
1107
2522
  const data = await response.json();
@@ -1129,7 +2544,7 @@ function createTonSDK(log7, db) {
1129
2544
  }
1130
2545
  return balances;
1131
2546
  } catch (err) {
1132
- log7.error("ton.getJettonBalances() failed:", err);
2547
+ log11.error("ton.getJettonBalances() failed:", err);
1133
2548
  return [];
1134
2549
  }
1135
2550
  },
@@ -1138,7 +2553,7 @@ function createTonSDK(log7, db) {
1138
2553
  const response = await tonapiFetch(`/jettons/${encodeURIComponent(jettonAddress)}`);
1139
2554
  if (response.status === 404) return null;
1140
2555
  if (!response.ok) {
1141
- log7.error(`ton.getJettonInfo() TonAPI error: ${response.status}`);
2556
+ log11.error(`ton.getJettonInfo() TonAPI error: ${response.status}`);
1142
2557
  return null;
1143
2558
  }
1144
2559
  const data = await response.json();
@@ -1156,7 +2571,7 @@ function createTonSDK(log7, db) {
1156
2571
  image: data.preview || metadata.image || void 0
1157
2572
  };
1158
2573
  } catch (err) {
1159
- log7.error("ton.getJettonInfo() failed:", err);
2574
+ log11.error("ton.getJettonInfo() failed:", err);
1160
2575
  return null;
1161
2576
  }
1162
2577
  },
@@ -1250,14 +2665,14 @@ function createTonSDK(log7, db) {
1250
2665
  try {
1251
2666
  const response = await tonapiFetch(`/accounts/${encodeURIComponent(ownerAddress)}/jettons`);
1252
2667
  if (!response.ok) {
1253
- log7.error(`ton.getJettonWalletAddress() TonAPI error: ${response.status}`);
2668
+ log11.error(`ton.getJettonWalletAddress() TonAPI error: ${response.status}`);
1254
2669
  return null;
1255
2670
  }
1256
2671
  const data = await response.json();
1257
2672
  const match = findJettonBalance(data.balances ?? [], jettonAddress);
1258
2673
  return match ? match.wallet_address.address : null;
1259
2674
  } catch (err) {
1260
- log7.error("ton.getJettonWalletAddress() failed:", err);
2675
+ log11.error("ton.getJettonWalletAddress() failed:", err);
1261
2676
  return null;
1262
2677
  }
1263
2678
  },
@@ -1270,14 +2685,14 @@ function createTonSDK(log7, db) {
1270
2685
  `/accounts/${encodeURIComponent(addr)}/nfts?limit=100&indirect_ownership=true`
1271
2686
  );
1272
2687
  if (!response.ok) {
1273
- log7.error(`ton.getNftItems() TonAPI error: ${response.status}`);
2688
+ log11.error(`ton.getNftItems() TonAPI error: ${response.status}`);
1274
2689
  return [];
1275
2690
  }
1276
2691
  const data = await response.json();
1277
2692
  if (!Array.isArray(data.nft_items)) return [];
1278
2693
  return data.nft_items.filter((item) => item.trust !== "blacklist").map((item) => mapNftItem(item));
1279
2694
  } catch (err) {
1280
- log7.error("ton.getNftItems() failed:", err);
2695
+ log11.error("ton.getNftItems() failed:", err);
1281
2696
  return [];
1282
2697
  }
1283
2698
  },
@@ -1286,13 +2701,13 @@ function createTonSDK(log7, db) {
1286
2701
  const response = await tonapiFetch(`/nfts/${encodeURIComponent(nftAddress)}`);
1287
2702
  if (response.status === 404) return null;
1288
2703
  if (!response.ok) {
1289
- log7.error(`ton.getNftInfo() TonAPI error: ${response.status}`);
2704
+ log11.error(`ton.getNftInfo() TonAPI error: ${response.status}`);
1290
2705
  return null;
1291
2706
  }
1292
2707
  const item = await response.json();
1293
2708
  return mapNftItem(item);
1294
2709
  } catch (err) {
1295
- log7.error("ton.getNftInfo() failed:", err);
2710
+ log11.error("ton.getNftInfo() failed:", err);
1296
2711
  return null;
1297
2712
  }
1298
2713
  },
@@ -1349,7 +2764,7 @@ function randomLong() {
1349
2764
  }
1350
2765
 
1351
2766
  // src/sdk/telegram-messages.ts
1352
- function createTelegramMessagesSDK(bridge, log7) {
2767
+ function createTelegramMessagesSDK(bridge, log11) {
1353
2768
  function requireBridge() {
1354
2769
  if (!bridge.isAvailable()) {
1355
2770
  throw new PluginSDKError(
@@ -1469,7 +2884,7 @@ function createTelegramMessagesSDK(bridge, log7) {
1469
2884
  return (resultData.messages ?? []).map(toSimpleMessage);
1470
2885
  } catch (err) {
1471
2886
  if (err instanceof PluginSDKError) throw err;
1472
- log7.error("telegram.searchMessages() failed:", err);
2887
+ log11.error("telegram.searchMessages() failed:", err);
1473
2888
  return [];
1474
2889
  }
1475
2890
  },
@@ -1694,7 +3109,7 @@ function createTelegramMessagesSDK(bridge, log7) {
1694
3109
  }
1695
3110
 
1696
3111
  // src/sdk/telegram-social.ts
1697
- function createTelegramSocialSDK(bridge, log7) {
3112
+ function createTelegramSocialSDK(bridge, log11) {
1698
3113
  function requireBridge() {
1699
3114
  if (!bridge.isAvailable()) {
1700
3115
  throw new PluginSDKError(
@@ -1774,7 +3189,7 @@ function createTelegramSocialSDK(bridge, log7) {
1774
3189
  return null;
1775
3190
  } catch (err) {
1776
3191
  if (err instanceof PluginSDKError) throw err;
1777
- log7.error("telegram.getChatInfo() failed:", err);
3192
+ log11.error("telegram.getChatInfo() failed:", err);
1778
3193
  return null;
1779
3194
  }
1780
3195
  },
@@ -1883,7 +3298,7 @@ function createTelegramSocialSDK(bridge, log7) {
1883
3298
  });
1884
3299
  } catch (err) {
1885
3300
  if (err instanceof PluginSDKError) throw err;
1886
- log7.error("telegram.getParticipants() failed:", err);
3301
+ log11.error("telegram.getParticipants() failed:", err);
1887
3302
  return [];
1888
3303
  }
1889
3304
  },
@@ -2097,7 +3512,7 @@ function createTelegramSocialSDK(bridge, log7) {
2097
3512
  try {
2098
3513
  const client = getClient();
2099
3514
  const { Api } = await import("telegram");
2100
- const user = await client.getEntity(userId.toString());
3515
+ const user = await client.getInputEntity(userId.toString());
2101
3516
  const invoiceData = {
2102
3517
  peer: user,
2103
3518
  giftId: BigInt(giftId),
@@ -2182,12 +3597,6 @@ function createTelegramSocialSDK(bridge, log7) {
2182
3597
  try {
2183
3598
  const client = getClient();
2184
3599
  const { Api } = await import("telegram");
2185
- if (!Api.payments.GetResaleStarGifts) {
2186
- throw new PluginSDKError(
2187
- "Resale gift marketplace is not supported in the current Telegram API layer.",
2188
- "OPERATION_FAILED"
2189
- );
2190
- }
2191
3600
  const result = await client.invoke(
2192
3601
  new Api.payments.GetResaleStarGifts({
2193
3602
  offset: "",
@@ -2211,25 +3620,16 @@ function createTelegramSocialSDK(bridge, log7) {
2211
3620
  try {
2212
3621
  const client = getClient();
2213
3622
  const { Api } = await import("telegram");
2214
- if (!Api.InputInvoiceStarGiftResale) {
2215
- throw new PluginSDKError(
2216
- "Resale gift purchasing is not supported in the current Telegram API layer.",
2217
- "OPERATION_FAILED"
2218
- );
2219
- }
2220
- const stargiftInput = new Api.InputSavedStarGiftUser({
2221
- odayId: BigInt(giftId)
3623
+ const toId = new Api.InputPeerSelf();
3624
+ const invoice = new Api.InputInvoiceStarGiftResale({
3625
+ slug: giftId,
3626
+ toId
2222
3627
  });
2223
- const invoiceData = { stargift: stargiftInput };
2224
- const form = await client.invoke(
2225
- new Api.payments.GetPaymentForm({
2226
- invoice: new Api.InputInvoiceStarGiftResale(invoiceData)
2227
- })
2228
- );
3628
+ const form = await client.invoke(new Api.payments.GetPaymentForm({ invoice }));
2229
3629
  await client.invoke(
2230
3630
  new Api.payments.SendStarsForm({
2231
3631
  formId: form.formId,
2232
- invoice: new Api.InputInvoiceStarGiftResale(invoiceData)
3632
+ invoice
2233
3633
  })
2234
3634
  );
2235
3635
  } catch (err) {
@@ -2322,7 +3722,7 @@ function createTelegramSocialSDK(bridge, log7) {
2322
3722
  }
2323
3723
 
2324
3724
  // src/sdk/telegram.ts
2325
- function createTelegramSDK(bridge, log7) {
3725
+ function createTelegramSDK(bridge, log11) {
2326
3726
  function requireBridge() {
2327
3727
  if (!bridge.isAvailable()) {
2328
3728
  throw new PluginSDKError(
@@ -2432,7 +3832,7 @@ function createTelegramSDK(bridge, log7) {
2432
3832
  timestamp: m.timestamp
2433
3833
  }));
2434
3834
  } catch (err) {
2435
- log7.error("telegram.getMessages() failed:", err);
3835
+ log11.error("telegram.getMessages() failed:", err);
2436
3836
  return [];
2437
3837
  }
2438
3838
  },
@@ -2454,7 +3854,7 @@ function createTelegramSDK(bridge, log7) {
2454
3854
  return bridge.isAvailable();
2455
3855
  },
2456
3856
  getRawClient() {
2457
- log7.warn("getRawClient() called \u2014 this bypasses SDK sandbox guarantees");
3857
+ log11.warn("getRawClient() called \u2014 this bypasses SDK sandbox guarantees");
2458
3858
  if (!bridge.isAvailable()) return null;
2459
3859
  try {
2460
3860
  return bridge.getClient().getClient();
@@ -2463,17 +3863,17 @@ function createTelegramSDK(bridge, log7) {
2463
3863
  }
2464
3864
  },
2465
3865
  // Spread extended methods from sub-modules
2466
- ...createTelegramMessagesSDK(bridge, log7),
2467
- ...createTelegramSocialSDK(bridge, log7)
3866
+ ...createTelegramMessagesSDK(bridge, log11),
3867
+ ...createTelegramSocialSDK(bridge, log11)
2468
3868
  };
2469
3869
  }
2470
3870
 
2471
3871
  // src/sdk/secrets.ts
2472
3872
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
2473
- import { join as join3 } from "path";
2474
- var SECRETS_DIR = join3(TELETON_ROOT, "plugins", "data");
3873
+ import { join as join4 } from "path";
3874
+ var SECRETS_DIR = join4(TELETON_ROOT, "plugins", "data");
2475
3875
  function getSecretsPath(pluginName) {
2476
- return join3(SECRETS_DIR, `${pluginName}.secrets.json`);
3876
+ return join4(SECRETS_DIR, `${pluginName}.secrets.json`);
2477
3877
  }
2478
3878
  function readSecretsFile(pluginName) {
2479
3879
  const filePath = getSecretsPath(pluginName);
@@ -2505,23 +3905,23 @@ function deletePluginSecret(pluginName, key) {
2505
3905
  function listPluginSecretKeys(pluginName) {
2506
3906
  return Object.keys(readSecretsFile(pluginName));
2507
3907
  }
2508
- function createSecretsSDK(pluginName, pluginConfig, log7) {
3908
+ function createSecretsSDK(pluginName, pluginConfig, log11) {
2509
3909
  const envPrefix = pluginName.replace(/-/g, "_").toUpperCase();
2510
3910
  function get(key) {
2511
3911
  const envKey = `${envPrefix}_${key.toUpperCase()}`;
2512
3912
  const envValue = process.env[envKey];
2513
3913
  if (envValue) {
2514
- log7.debug(`Secret "${key}" resolved from env var ${envKey}`);
3914
+ log11.debug(`Secret "${key}" resolved from env var ${envKey}`);
2515
3915
  return envValue;
2516
3916
  }
2517
3917
  const stored = readSecretsFile(pluginName);
2518
3918
  if (key in stored && stored[key]) {
2519
- log7.debug(`Secret "${key}" resolved from secrets store`);
3919
+ log11.debug(`Secret "${key}" resolved from secrets store`);
2520
3920
  return stored[key];
2521
3921
  }
2522
3922
  const configValue = pluginConfig[key];
2523
3923
  if (configValue !== void 0 && configValue !== null) {
2524
- log7.debug(`Secret "${key}" resolved from pluginConfig`);
3924
+ log11.debug(`Secret "${key}" resolved from pluginConfig`);
2525
3925
  return String(configValue);
2526
3926
  }
2527
3927
  return void 0;
@@ -2640,13 +4040,13 @@ function createSafeDb(db) {
2640
4040
  });
2641
4041
  }
2642
4042
  function createPluginSDK(deps, opts) {
2643
- const log7 = createLogger2(opts.pluginName);
4043
+ const log11 = createLogger2(opts.pluginName);
2644
4044
  const safeDb = opts.db ? createSafeDb(opts.db) : null;
2645
- const ton = Object.freeze(createTonSDK(log7, safeDb));
2646
- const telegram = Object.freeze(createTelegramSDK(deps.bridge, log7));
2647
- const secrets = Object.freeze(createSecretsSDK(opts.pluginName, opts.pluginConfig, log7));
4045
+ const ton = Object.freeze(createTonSDK(log11, safeDb));
4046
+ const telegram = Object.freeze(createTelegramSDK(deps.bridge, log11));
4047
+ const secrets = Object.freeze(createSecretsSDK(opts.pluginName, opts.pluginConfig, log11));
2648
4048
  const storage = safeDb ? Object.freeze(createStorageSDK(safeDb)) : null;
2649
- const frozenLog = Object.freeze(log7);
4049
+ const frozenLog = Object.freeze(log11);
2650
4050
  const frozenConfig = Object.freeze(opts.sanitizedConfig);
2651
4051
  const frozenPluginConfig = Object.freeze(JSON.parse(JSON.stringify(opts.pluginConfig ?? {})));
2652
4052
  return Object.freeze({
@@ -2719,21 +4119,21 @@ function semverSatisfies(current, range) {
2719
4119
 
2720
4120
  // src/agent/tools/plugin-loader.ts
2721
4121
  var execFileAsync = promisify(execFile);
2722
- var log6 = createLogger("PluginLoader");
2723
- var PLUGIN_DATA_DIR = join4(TELETON_ROOT, "plugins", "data");
4122
+ var log10 = createLogger("PluginLoader");
4123
+ var PLUGIN_DATA_DIR = join5(TELETON_ROOT, "plugins", "data");
2724
4124
  function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
2725
4125
  let manifest = null;
2726
4126
  if (raw.manifest) {
2727
4127
  try {
2728
4128
  manifest = validateManifest(raw.manifest);
2729
4129
  } catch (err) {
2730
- log6.warn(
4130
+ log10.warn(
2731
4131
  `[${entryName}] invalid manifest, ignoring: ${err instanceof Error ? err.message : err}`
2732
4132
  );
2733
4133
  }
2734
4134
  }
2735
4135
  if (!manifest) {
2736
- const manifestPath = join4(WORKSPACE_PATHS.PLUGINS_DIR, entryName, "manifest.json");
4136
+ const manifestPath = join5(WORKSPACE_PATHS.PLUGINS_DIR, entryName, "manifest.json");
2737
4137
  try {
2738
4138
  if (existsSync6(manifestPath)) {
2739
4139
  const diskManifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
@@ -2810,7 +4210,7 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
2810
4210
  },
2811
4211
  migrate() {
2812
4212
  try {
2813
- const dbPath = join4(PLUGIN_DATA_DIR, `${pluginName}.db`);
4213
+ const dbPath = join5(PLUGIN_DATA_DIR, `${pluginName}.db`);
2814
4214
  pluginDb = openModuleDb(dbPath);
2815
4215
  if (hasMigrate) {
2816
4216
  raw.migrate(pluginDb);
@@ -2911,30 +4311,30 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
2911
4311
  return module;
2912
4312
  }
2913
4313
  async function ensurePluginDeps(pluginDir, pluginEntry) {
2914
- const pkgJson = join4(pluginDir, "package.json");
2915
- const lockfile = join4(pluginDir, "package-lock.json");
2916
- const nodeModules = join4(pluginDir, "node_modules");
4314
+ const pkgJson = join5(pluginDir, "package.json");
4315
+ const lockfile = join5(pluginDir, "package-lock.json");
4316
+ const nodeModules = join5(pluginDir, "node_modules");
2917
4317
  if (!existsSync6(pkgJson)) return;
2918
4318
  if (!existsSync6(lockfile)) {
2919
- log6.warn(
4319
+ log10.warn(
2920
4320
  `[${pluginEntry}] package.json without package-lock.json \u2014 skipping (lockfile required)`
2921
4321
  );
2922
4322
  return;
2923
4323
  }
2924
4324
  if (existsSync6(nodeModules)) {
2925
- const marker = join4(nodeModules, ".package-lock.json");
4325
+ const marker = join5(nodeModules, ".package-lock.json");
2926
4326
  if (existsSync6(marker) && statSync(marker).mtimeMs >= statSync(lockfile).mtimeMs) return;
2927
4327
  }
2928
- log6.info(`[${pluginEntry}] Installing dependencies...`);
4328
+ log10.info(`[${pluginEntry}] Installing dependencies...`);
2929
4329
  try {
2930
4330
  await execFileAsync("npm", ["ci", "--ignore-scripts", "--no-audit", "--no-fund"], {
2931
4331
  cwd: pluginDir,
2932
4332
  timeout: 6e4,
2933
4333
  env: { ...process.env, NODE_ENV: "production" }
2934
4334
  });
2935
- log6.info(`[${pluginEntry}] Dependencies installed`);
4335
+ log10.info(`[${pluginEntry}] Dependencies installed`);
2936
4336
  } catch (err) {
2937
- log6.error(`[${pluginEntry}] Failed to install deps: ${String(err).slice(0, 300)}`);
4337
+ log10.error(`[${pluginEntry}] Failed to install deps: ${String(err).slice(0, 300)}`);
2938
4338
  }
2939
4339
  }
2940
4340
  async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
@@ -2948,14 +4348,14 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
2948
4348
  const pluginPaths = [];
2949
4349
  for (const entry of entries) {
2950
4350
  if (entry === "data") continue;
2951
- const entryPath = join4(pluginsDir, entry);
4351
+ const entryPath = join5(pluginsDir, entry);
2952
4352
  let modulePath = null;
2953
4353
  try {
2954
4354
  const stat = statSync(entryPath);
2955
4355
  if (stat.isFile() && entry.endsWith(".js")) {
2956
4356
  modulePath = entryPath;
2957
4357
  } else if (stat.isDirectory()) {
2958
- const indexPath = join4(entryPath, "index.js");
4358
+ const indexPath = join5(entryPath, "index.js");
2959
4359
  if (existsSync6(indexPath)) {
2960
4360
  modulePath = indexPath;
2961
4361
  }
@@ -2968,7 +4368,7 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
2968
4368
  }
2969
4369
  }
2970
4370
  await Promise.allSettled(
2971
- pluginPaths.filter(({ path }) => path.endsWith("index.js")).map(({ entry }) => ensurePluginDeps(join4(pluginsDir, entry), entry))
4371
+ pluginPaths.filter(({ path }) => path.endsWith("index.js")).map(({ entry }) => ensurePluginDeps(join5(pluginsDir, entry), entry))
2972
4372
  );
2973
4373
  const loadResults = await Promise.allSettled(
2974
4374
  pluginPaths.map(async ({ entry, path }) => {
@@ -2979,7 +4379,7 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
2979
4379
  );
2980
4380
  for (const result of loadResults) {
2981
4381
  if (result.status === "rejected") {
2982
- log6.error(
4382
+ log10.error(
2983
4383
  `Plugin failed to load: ${result.reason instanceof Error ? result.reason.message : result.reason}`
2984
4384
  );
2985
4385
  continue;
@@ -2987,18 +4387,18 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
2987
4387
  const { entry, mod } = result.value;
2988
4388
  try {
2989
4389
  if (!mod.tools || typeof mod.tools !== "function" && !Array.isArray(mod.tools)) {
2990
- log6.warn(`Plugin "${entry}": no 'tools' array or function exported, skipping`);
4390
+ log10.warn(`Plugin "${entry}": no 'tools' array or function exported, skipping`);
2991
4391
  continue;
2992
4392
  }
2993
4393
  const adapted = adaptPlugin(mod, entry, config, loadedModuleNames, sdkDeps);
2994
4394
  if (loadedNames.has(adapted.name)) {
2995
- log6.warn(`Plugin "${adapted.name}" already loaded, skipping duplicate from "${entry}"`);
4395
+ log10.warn(`Plugin "${adapted.name}" already loaded, skipping duplicate from "${entry}"`);
2996
4396
  continue;
2997
4397
  }
2998
4398
  loadedNames.add(adapted.name);
2999
4399
  modules.push(adapted);
3000
4400
  } catch (err) {
3001
- log6.error(`Plugin "${entry}" failed to adapt: ${err instanceof Error ? err.message : err}`);
4401
+ log10.error(`Plugin "${entry}" failed to adapt: ${err instanceof Error ? err.message : err}`);
3002
4402
  }
3003
4403
  }
3004
4404
  return modules;
@@ -3039,6 +4439,7 @@ var CONFIGURABLE_KEYS = {
3039
4439
  label: "LLM API Key",
3040
4440
  description: "LLM provider API key",
3041
4441
  sensitive: true,
4442
+ hotReload: "instant",
3042
4443
  validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
3043
4444
  mask: (v) => v.slice(0, 8) + "****",
3044
4445
  parse: identity
@@ -3049,6 +4450,7 @@ var CONFIGURABLE_KEYS = {
3049
4450
  label: "Tavily API Key",
3050
4451
  description: "Tavily API key for web search",
3051
4452
  sensitive: true,
4453
+ hotReload: "instant",
3052
4454
  validate: (v) => v.startsWith("tvly-") ? void 0 : "Must start with 'tvly-'",
3053
4455
  mask: (v) => v.slice(0, 9) + "****",
3054
4456
  parse: identity
@@ -3059,6 +4461,18 @@ var CONFIGURABLE_KEYS = {
3059
4461
  label: "TonAPI Key",
3060
4462
  description: "TonAPI key for higher rate limits",
3061
4463
  sensitive: true,
4464
+ hotReload: "instant",
4465
+ validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
4466
+ mask: (v) => v.slice(0, 10) + "****",
4467
+ parse: identity
4468
+ },
4469
+ toncenter_api_key: {
4470
+ type: "string",
4471
+ category: "API Keys",
4472
+ label: "TonCenter API Key",
4473
+ description: "TonCenter API key for dedicated RPC endpoint (free at toncenter.com)",
4474
+ sensitive: true,
4475
+ hotReload: "instant",
3062
4476
  validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
3063
4477
  mask: (v) => v.slice(0, 10) + "****",
3064
4478
  parse: identity
@@ -3069,6 +4483,7 @@ var CONFIGURABLE_KEYS = {
3069
4483
  label: "Bot Token",
3070
4484
  description: "Bot token from @BotFather",
3071
4485
  sensitive: true,
4486
+ hotReload: "instant",
3072
4487
  validate: (v) => v.includes(":") ? void 0 : "Must contain ':' (e.g., 123456:ABC...)",
3073
4488
  mask: (v) => v.split(":")[0] + ":****",
3074
4489
  parse: identity
@@ -3080,6 +4495,7 @@ var CONFIGURABLE_KEYS = {
3080
4495
  label: "Provider",
3081
4496
  description: "LLM provider",
3082
4497
  sensitive: false,
4498
+ hotReload: "instant",
3083
4499
  options: [
3084
4500
  "anthropic",
3085
4501
  "claude-code",
@@ -3115,6 +4531,7 @@ var CONFIGURABLE_KEYS = {
3115
4531
  label: "Model",
3116
4532
  description: "Main LLM model ID",
3117
4533
  sensitive: false,
4534
+ hotReload: "instant",
3118
4535
  validate: nonEmpty,
3119
4536
  mask: identity,
3120
4537
  parse: identity
@@ -3125,6 +4542,7 @@ var CONFIGURABLE_KEYS = {
3125
4542
  label: "Utility Model",
3126
4543
  description: "Cheap model for summarization (auto-detected if empty)",
3127
4544
  sensitive: false,
4545
+ hotReload: "instant",
3128
4546
  validate: noValidation,
3129
4547
  mask: identity,
3130
4548
  parse: identity
@@ -3135,6 +4553,7 @@ var CONFIGURABLE_KEYS = {
3135
4553
  label: "Temperature",
3136
4554
  description: "Response creativity (0.0 = deterministic, 2.0 = max)",
3137
4555
  sensitive: false,
4556
+ hotReload: "instant",
3138
4557
  validate: numberInRange(0, 2),
3139
4558
  mask: identity,
3140
4559
  parse: (v) => Number(v)
@@ -3145,6 +4564,7 @@ var CONFIGURABLE_KEYS = {
3145
4564
  label: "Max Tokens",
3146
4565
  description: "Maximum response length in tokens",
3147
4566
  sensitive: false,
4567
+ hotReload: "instant",
3148
4568
  validate: numberInRange(256, 128e3),
3149
4569
  mask: identity,
3150
4570
  parse: (v) => Number(v)
@@ -3155,6 +4575,7 @@ var CONFIGURABLE_KEYS = {
3155
4575
  label: "Max Iterations",
3156
4576
  description: "Max tool-call loop iterations per message",
3157
4577
  sensitive: false,
4578
+ hotReload: "instant",
3158
4579
  validate: numberInRange(1, 20),
3159
4580
  mask: identity,
3160
4581
  parse: (v) => Number(v)
@@ -3165,6 +4586,7 @@ var CONFIGURABLE_KEYS = {
3165
4586
  label: "API Base URL",
3166
4587
  description: "Base URL for local LLM server (requires restart)",
3167
4588
  sensitive: false,
4589
+ hotReload: "restart",
3168
4590
  validate: validateUrl,
3169
4591
  mask: identity,
3170
4592
  parse: identity
@@ -3175,6 +4597,7 @@ var CONFIGURABLE_KEYS = {
3175
4597
  label: "Cocoon Port",
3176
4598
  description: "Cocoon proxy port (requires restart)",
3177
4599
  sensitive: false,
4600
+ hotReload: "restart",
3178
4601
  validate: numberInRange(1, 65535),
3179
4602
  mask: identity,
3180
4603
  parse: (v) => Number(v)
@@ -3186,6 +4609,7 @@ var CONFIGURABLE_KEYS = {
3186
4609
  label: "Daily Reset",
3187
4610
  description: "Enable daily session reset at specified hour",
3188
4611
  sensitive: false,
4612
+ hotReload: "instant",
3189
4613
  validate: enumValidator(["true", "false"]),
3190
4614
  mask: identity,
3191
4615
  parse: (v) => v === "true"
@@ -3196,6 +4620,7 @@ var CONFIGURABLE_KEYS = {
3196
4620
  label: "Reset Hour",
3197
4621
  description: "Hour (0-23 UTC) for daily session reset",
3198
4622
  sensitive: false,
4623
+ hotReload: "instant",
3199
4624
  validate: numberInRange(0, 23),
3200
4625
  mask: identity,
3201
4626
  parse: (v) => Number(v)
@@ -3206,6 +4631,7 @@ var CONFIGURABLE_KEYS = {
3206
4631
  label: "Idle Expiry",
3207
4632
  description: "Enable automatic session expiry after idle period",
3208
4633
  sensitive: false,
4634
+ hotReload: "instant",
3209
4635
  validate: enumValidator(["true", "false"]),
3210
4636
  mask: identity,
3211
4637
  parse: (v) => v === "true"
@@ -3216,6 +4642,7 @@ var CONFIGURABLE_KEYS = {
3216
4642
  label: "Idle Minutes",
3217
4643
  description: "Idle minutes before session expires (minimum 1)",
3218
4644
  sensitive: false,
4645
+ hotReload: "instant",
3219
4646
  validate: numberInRange(1, Number.MAX_SAFE_INTEGER),
3220
4647
  mask: identity,
3221
4648
  parse: (v) => Number(v)
@@ -3227,6 +4654,7 @@ var CONFIGURABLE_KEYS = {
3227
4654
  label: "Bot Username",
3228
4655
  description: "Bot username without @",
3229
4656
  sensitive: false,
4657
+ hotReload: "instant",
3230
4658
  validate: (v) => v.length >= 3 ? void 0 : "Must be at least 3 characters",
3231
4659
  mask: identity,
3232
4660
  parse: identity
@@ -3237,9 +4665,15 @@ var CONFIGURABLE_KEYS = {
3237
4665
  label: "DM Policy",
3238
4666
  description: "Who can message the bot in private",
3239
4667
  sensitive: false,
3240
- options: ["open", "allowlist", "disabled"],
3241
- optionLabels: { open: "Open", allowlist: "Allow Users", disabled: "Admin Only" },
3242
- validate: enumValidator(["allowlist", "open", "disabled"]),
4668
+ hotReload: "instant",
4669
+ options: ["admin-only", "allowlist", "open", "disabled"],
4670
+ optionLabels: {
4671
+ "admin-only": "Admin Only",
4672
+ allowlist: "Allow Users",
4673
+ open: "Open",
4674
+ disabled: "Disabled"
4675
+ },
4676
+ validate: enumValidator(["open", "allowlist", "admin-only", "disabled"]),
3243
4677
  mask: identity,
3244
4678
  parse: identity
3245
4679
  },
@@ -3249,9 +4683,15 @@ var CONFIGURABLE_KEYS = {
3249
4683
  label: "Group Policy",
3250
4684
  description: "Which groups the bot can respond in",
3251
4685
  sensitive: false,
3252
- options: ["open", "allowlist", "disabled"],
3253
- optionLabels: { open: "Open", allowlist: "Allow Groups", disabled: "Disabled" },
3254
- validate: enumValidator(["open", "allowlist", "disabled"]),
4686
+ hotReload: "instant",
4687
+ options: ["open", "allowlist", "admin-only", "disabled"],
4688
+ optionLabels: {
4689
+ open: "Open",
4690
+ allowlist: "Allow Groups",
4691
+ "admin-only": "Admin Only",
4692
+ disabled: "Disabled"
4693
+ },
4694
+ validate: enumValidator(["open", "allowlist", "admin-only", "disabled"]),
3255
4695
  mask: identity,
3256
4696
  parse: identity
3257
4697
  },
@@ -3261,6 +4701,7 @@ var CONFIGURABLE_KEYS = {
3261
4701
  label: "Require Mention",
3262
4702
  description: "Require @mention in groups to respond",
3263
4703
  sensitive: false,
4704
+ hotReload: "instant",
3264
4705
  validate: enumValidator(["true", "false"]),
3265
4706
  mask: identity,
3266
4707
  parse: (v) => v === "true"
@@ -3271,6 +4712,7 @@ var CONFIGURABLE_KEYS = {
3271
4712
  label: "Owner Name",
3272
4713
  description: "Owner's first name (used in system prompt)",
3273
4714
  sensitive: false,
4715
+ hotReload: "instant",
3274
4716
  validate: noValidation,
3275
4717
  mask: identity,
3276
4718
  parse: identity
@@ -3281,6 +4723,7 @@ var CONFIGURABLE_KEYS = {
3281
4723
  label: "Owner Username",
3282
4724
  description: "Owner's Telegram username (without @)",
3283
4725
  sensitive: false,
4726
+ hotReload: "instant",
3284
4727
  validate: noValidation,
3285
4728
  mask: identity,
3286
4729
  parse: identity
@@ -3291,6 +4734,7 @@ var CONFIGURABLE_KEYS = {
3291
4734
  label: "Debounce (ms)",
3292
4735
  description: "Group message debounce delay in ms (0 = disabled)",
3293
4736
  sensitive: false,
4737
+ hotReload: "instant",
3294
4738
  validate: numberInRange(0, 1e4),
3295
4739
  mask: identity,
3296
4740
  parse: (v) => Number(v)
@@ -3301,6 +4745,7 @@ var CONFIGURABLE_KEYS = {
3301
4745
  label: "Agent Channel",
3302
4746
  description: "Channel username for auto-publishing",
3303
4747
  sensitive: false,
4748
+ hotReload: "instant",
3304
4749
  validate: noValidation,
3305
4750
  mask: identity,
3306
4751
  parse: identity
@@ -3311,6 +4756,7 @@ var CONFIGURABLE_KEYS = {
3311
4756
  label: "Typing Simulation",
3312
4757
  description: "Simulate typing indicator before sending replies",
3313
4758
  sensitive: false,
4759
+ hotReload: "instant",
3314
4760
  validate: enumValidator(["true", "false"]),
3315
4761
  mask: identity,
3316
4762
  parse: (v) => v === "true"
@@ -3321,6 +4767,7 @@ var CONFIGURABLE_KEYS = {
3321
4767
  label: "Admin ID",
3322
4768
  description: "Primary admin Telegram user ID (auto-added to Admin IDs)",
3323
4769
  sensitive: false,
4770
+ hotReload: "instant",
3324
4771
  validate: positiveInteger,
3325
4772
  mask: identity,
3326
4773
  parse: (v) => Number(v)
@@ -3331,6 +4778,7 @@ var CONFIGURABLE_KEYS = {
3331
4778
  label: "Max Message Length",
3332
4779
  description: "Maximum message length in characters",
3333
4780
  sensitive: false,
4781
+ hotReload: "instant",
3334
4782
  validate: numberInRange(1, 32768),
3335
4783
  mask: identity,
3336
4784
  parse: (v) => Number(v)
@@ -3341,6 +4789,7 @@ var CONFIGURABLE_KEYS = {
3341
4789
  label: "Rate Limit \u2014 Messages/sec",
3342
4790
  description: "Rate limit: messages per second (requires restart)",
3343
4791
  sensitive: false,
4792
+ hotReload: "restart",
3344
4793
  validate: numberInRange(0.1, 10),
3345
4794
  mask: identity,
3346
4795
  parse: (v) => Number(v)
@@ -3351,6 +4800,7 @@ var CONFIGURABLE_KEYS = {
3351
4800
  label: "Rate Limit \u2014 Groups/min",
3352
4801
  description: "Rate limit: groups per minute (requires restart)",
3353
4802
  sensitive: false,
4803
+ hotReload: "restart",
3354
4804
  validate: numberInRange(1, 60),
3355
4805
  mask: identity,
3356
4806
  parse: (v) => Number(v)
@@ -3362,6 +4812,7 @@ var CONFIGURABLE_KEYS = {
3362
4812
  label: "Admin IDs",
3363
4813
  description: "Admin user IDs with elevated access",
3364
4814
  sensitive: false,
4815
+ hotReload: "instant",
3365
4816
  validate: positiveInteger,
3366
4817
  mask: identity,
3367
4818
  parse: (v) => Number(v)
@@ -3373,6 +4824,7 @@ var CONFIGURABLE_KEYS = {
3373
4824
  label: "Allowed Users",
3374
4825
  description: "User IDs allowed for DM access",
3375
4826
  sensitive: false,
4827
+ hotReload: "instant",
3376
4828
  validate: positiveInteger,
3377
4829
  mask: identity,
3378
4830
  parse: (v) => Number(v)
@@ -3384,6 +4836,7 @@ var CONFIGURABLE_KEYS = {
3384
4836
  label: "Allowed Groups",
3385
4837
  description: "Group IDs allowed for group access",
3386
4838
  sensitive: false,
4839
+ hotReload: "instant",
3387
4840
  validate: positiveInteger,
3388
4841
  mask: identity,
3389
4842
  parse: (v) => Number(v)
@@ -3395,6 +4848,7 @@ var CONFIGURABLE_KEYS = {
3395
4848
  label: "Embedding Provider",
3396
4849
  description: "Embedding provider for RAG",
3397
4850
  sensitive: false,
4851
+ hotReload: "instant",
3398
4852
  options: ["local", "anthropic", "none"],
3399
4853
  validate: enumValidator(["local", "anthropic", "none"]),
3400
4854
  mask: identity,
@@ -3406,6 +4860,7 @@ var CONFIGURABLE_KEYS = {
3406
4860
  label: "Embedding Model",
3407
4861
  description: "Embedding model ID (requires restart)",
3408
4862
  sensitive: false,
4863
+ hotReload: "restart",
3409
4864
  validate: noValidation,
3410
4865
  mask: identity,
3411
4866
  parse: identity
@@ -3417,6 +4872,7 @@ var CONFIGURABLE_KEYS = {
3417
4872
  label: "WebUI Port",
3418
4873
  description: "HTTP server port (requires restart)",
3419
4874
  sensitive: false,
4875
+ hotReload: "restart",
3420
4876
  validate: numberInRange(1024, 65535),
3421
4877
  mask: identity,
3422
4878
  parse: (v) => Number(v)
@@ -3427,6 +4883,7 @@ var CONFIGURABLE_KEYS = {
3427
4883
  label: "Log HTTP Requests",
3428
4884
  description: "Log all HTTP requests to console",
3429
4885
  sensitive: false,
4886
+ hotReload: "instant",
3430
4887
  validate: enumValidator(["true", "false"]),
3431
4888
  mask: identity,
3432
4889
  parse: (v) => v === "true"
@@ -3438,6 +4895,7 @@ var CONFIGURABLE_KEYS = {
3438
4895
  label: "Deals Enabled",
3439
4896
  description: "Enable the deals/escrow module",
3440
4897
  sensitive: false,
4898
+ hotReload: "instant",
3441
4899
  validate: enumValidator(["true", "false"]),
3442
4900
  mask: identity,
3443
4901
  parse: (v) => v === "true"
@@ -3448,6 +4906,7 @@ var CONFIGURABLE_KEYS = {
3448
4906
  label: "Deal Expiry",
3449
4907
  description: "Deal expiry timeout in seconds",
3450
4908
  sensitive: false,
4909
+ hotReload: "instant",
3451
4910
  validate: numberInRange(10, 3600),
3452
4911
  mask: identity,
3453
4912
  parse: (v) => Number(v)
@@ -3458,6 +4917,7 @@ var CONFIGURABLE_KEYS = {
3458
4917
  label: "Buy Max Floor %",
3459
4918
  description: "Maximum floor % for buy deals",
3460
4919
  sensitive: false,
4920
+ hotReload: "instant",
3461
4921
  validate: numberInRange(1, 100),
3462
4922
  mask: identity,
3463
4923
  parse: (v) => Number(v)
@@ -3468,6 +4928,7 @@ var CONFIGURABLE_KEYS = {
3468
4928
  label: "Sell Min Floor %",
3469
4929
  description: "Minimum floor % for sell deals",
3470
4930
  sensitive: false,
4931
+ hotReload: "instant",
3471
4932
  validate: numberInRange(100, 500),
3472
4933
  mask: identity,
3473
4934
  parse: (v) => Number(v)
@@ -3479,6 +4940,7 @@ var CONFIGURABLE_KEYS = {
3479
4940
  label: "Hot Reload",
3480
4941
  description: "Watch ~/.teleton/plugins/ for live changes",
3481
4942
  sensitive: false,
4943
+ hotReload: "instant",
3482
4944
  validate: enumValidator(["true", "false"]),
3483
4945
  mask: identity,
3484
4946
  parse: (v) => v === "true"
@@ -3558,19 +5020,20 @@ export {
3558
5020
  validateReadPath,
3559
5021
  validateWritePath,
3560
5022
  validateDirectory,
3561
- appendToDailyLog,
3562
- writeSummaryToDailyLog,
3563
5023
  sanitizeForPrompt,
3564
5024
  sanitizeForContext,
3565
5025
  clearPromptCache,
3566
5026
  loadSoul,
3567
- buildSystemPrompt,
5027
+ TELEGRAM_SEND_TOOLS,
5028
+ getTokenUsage,
5029
+ AgentRuntime,
3568
5030
  writePluginSecret,
3569
5031
  deletePluginSecret,
3570
5032
  listPluginSecretKeys,
3571
5033
  toLong,
3572
5034
  randomLong,
3573
5035
  withBlockchainRetry,
5036
+ withTxLock,
3574
5037
  sendTon,
3575
5038
  formatTransactions,
3576
5039
  adaptPlugin,