teleton 0.7.3 → 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 (36) hide show
  1. package/README.md +64 -35
  2. package/dist/{chunk-RBU6JXD3.js → chunk-2GLHOJ5C.js} +268 -59
  3. package/dist/chunk-5UVXJMOX.js +292 -0
  4. package/dist/{chunk-DAMCNMYL.js → chunk-AVDWXYQ7.js} +73 -28
  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-A4GCOHCE.js → chunk-G2LLMJXJ.js} +1751 -116
  8. package/dist/{chunk-FNV5FF35.js → chunk-LCCVZ4D2.js} +32 -16
  9. package/dist/{chunk-BU453WX4.js → chunk-OGMVWDVU.js} +4172 -3792
  10. package/dist/chunk-QOQWUUA4.js +158 -0
  11. package/dist/{chunk-4DU3C27M.js → chunk-R4YSJ4EY.js} +5 -1
  12. package/dist/{chunk-XBKSS6DM.js → chunk-VFA7QMCZ.js} +5 -3
  13. package/dist/{chunk-VAUJSSD3.js → chunk-XQUHC3JZ.js} +1 -1
  14. package/dist/{chunk-RO62LO6Z.js → chunk-YP25WTQK.js} +2 -0
  15. package/dist/cli/index.js +234 -289
  16. package/dist/{client-RTNALK7W.js → client-O37XDCJB.js} +4 -5
  17. package/dist/index.js +12 -13
  18. package/dist/{memory-5SS3Q5EA.js → memory-KQALFUV3.js} +6 -7
  19. package/dist/{migrate-M7SJMDOL.js → migrate-UV3WEL5D.js} +6 -7
  20. package/dist/{server-FOC5P7U6.js → server-BHHJGUDF.js} +324 -16
  21. package/dist/{setup-server-BVVD2PR6.js → setup-server-G7UG2DI3.js} +26 -118
  22. package/dist/store-H4XPNGC2.js +34 -0
  23. package/dist/{task-dependency-resolver-WKZWJLLM.js → task-dependency-resolver-VMEVJRPO.js} +2 -2
  24. package/dist/{task-executor-PD3H4MLO.js → task-executor-WWSPBJ4V.js} +1 -1
  25. package/dist/{tool-index-MIVK3D7H.js → tool-index-2KH3OB6X.js} +5 -5
  26. package/dist/web/assets/index-BrVqauzj.css +1 -0
  27. package/dist/web/assets/index-Bx8JW3gV.js +72 -0
  28. package/dist/web/assets/{index.es-7MTSV5SL.js → index.es-Pet5-M13.js} +1 -1
  29. package/dist/web/index.html +2 -2
  30. package/package.json +3 -3
  31. package/dist/chunk-JQDLW7IE.js +0 -107
  32. package/dist/chunk-UCN6TI25.js +0 -143
  33. package/dist/web/assets/index-By_fs4Jl.js +0 -72
  34. package/dist/web/assets/index-CRDIf07k.css +0 -1
  35. package/scripts/patch-gramjs.sh +0 -46
  36. package/scripts/postinstall.mjs +0 -16
@@ -7,27 +7,75 @@ import {
7
7
  getWalletBalance,
8
8
  invalidateTonClientCache,
9
9
  loadWallet
10
- } from "./chunk-DAMCNMYL.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";
20
35
  import {
36
+ appendToTranscript,
37
+ archiveTranscript,
38
+ transcriptExists
39
+ } from "./chunk-OCLG5GKI.js";
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,
@@ -97,7 +145,6 @@ Run 'teleton setup' to create one.`);
97
145
  }
98
146
  config.telegram.session_path = expandPath(config.telegram.session_path);
99
147
  config.storage.sessions_file = expandPath(config.storage.sessions_file);
100
- config.storage.pairing_file = expandPath(config.storage.pairing_file);
101
148
  config.storage.memory_file = expandPath(config.storage.memory_file);
102
149
  if (process.env.TELETON_API_KEY) {
103
150
  config.agent.api_key = process.env.TELETON_API_KEY;
@@ -151,6 +198,9 @@ Run 'teleton setup' to create one.`);
151
198
  if (process.env.TELETON_TONAPI_KEY) {
152
199
  config.tonapi_key = process.env.TELETON_TONAPI_KEY;
153
200
  }
201
+ if (process.env.TELETON_TONCENTER_API_KEY) {
202
+ config.toncenter_api_key = process.env.TELETON_TONCENTER_API_KEY;
203
+ }
154
204
  return config;
155
205
  }
156
206
  function configExists(configPath = DEFAULT_CONFIG_PATH) {
@@ -574,16 +624,1379 @@ Your conversation context is approaching the limit and may be compacted soon.
574
624
  return parts.join("\n");
575
625
  }
576
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
+
577
1990
  // src/agent/tools/plugin-loader.ts
578
1991
  import { readdirSync as readdirSync2, readFileSync as readFileSync5, existsSync as existsSync6, statSync } from "fs";
579
- import { join as join4 } from "path";
1992
+ import { join as join5 } from "path";
580
1993
  import { pathToFileURL } from "url";
581
1994
  import { execFile } from "child_process";
582
1995
  import { promisify } from "util";
583
1996
 
584
1997
  // src/agent/tools/plugin-validator.ts
585
1998
  import { z } from "zod";
586
- var log3 = createLogger("PluginValidator");
1999
+ var log7 = createLogger("PluginValidator");
587
2000
  var ManifestSchema = z.object({
588
2001
  name: z.string().min(1).max(64).regex(
589
2002
  /^[a-z0-9][a-z0-9-]*$/,
@@ -611,20 +2024,20 @@ function validateToolDefs(defs, pluginName) {
611
2024
  const valid = [];
612
2025
  for (const def of defs) {
613
2026
  if (!def || typeof def !== "object") {
614
- log3.warn(`[${pluginName}] tool is not an object, skipping`);
2027
+ log7.warn(`[${pluginName}] tool is not an object, skipping`);
615
2028
  continue;
616
2029
  }
617
2030
  const t = def;
618
2031
  if (!t.name || typeof t.name !== "string") {
619
- log3.warn(`[${pluginName}] tool missing 'name', skipping`);
2032
+ log7.warn(`[${pluginName}] tool missing 'name', skipping`);
620
2033
  continue;
621
2034
  }
622
2035
  if (!t.description || typeof t.description !== "string") {
623
- log3.warn(`[${pluginName}] tool "${t.name}" missing 'description', skipping`);
2036
+ log7.warn(`[${pluginName}] tool "${t.name}" missing 'description', skipping`);
624
2037
  continue;
625
2038
  }
626
2039
  if (!t.execute || typeof t.execute !== "function") {
627
- log3.warn(`[${pluginName}] tool "${t.name}" missing 'execute' function, skipping`);
2040
+ log7.warn(`[${pluginName}] tool "${t.name}" missing 'execute' function, skipping`);
628
2041
  continue;
629
2042
  }
630
2043
  valid.push(t);
@@ -673,25 +2086,25 @@ function withTxLock(fn) {
673
2086
  }
674
2087
 
675
2088
  // src/ton/transfer.ts
676
- var log4 = createLogger("TON");
2089
+ var log8 = createLogger("TON");
677
2090
  async function sendTon(params) {
678
2091
  return withTxLock(async () => {
679
2092
  try {
680
2093
  const { toAddress, amount, comment = "", bounce = false } = params;
681
2094
  if (!Number.isFinite(amount) || amount <= 0) {
682
- log4.error({ amount }, "Invalid transfer amount");
2095
+ log8.error({ amount }, "Invalid transfer amount");
683
2096
  return null;
684
2097
  }
685
2098
  let recipientAddress;
686
2099
  try {
687
2100
  recipientAddress = Address.parse(toAddress);
688
2101
  } catch (e) {
689
- log4.error({ err: e }, `Invalid recipient address: ${toAddress}`);
2102
+ log8.error({ err: e }, `Invalid recipient address: ${toAddress}`);
690
2103
  return null;
691
2104
  }
692
2105
  const keyPair = await getKeyPair();
693
2106
  if (!keyPair) {
694
- log4.error("Wallet not initialized");
2107
+ log8.error("Wallet not initialized");
695
2108
  return null;
696
2109
  }
697
2110
  const wallet = WalletContractV5R1.create({
@@ -715,20 +2128,21 @@ async function sendTon(params) {
715
2128
  ]
716
2129
  });
717
2130
  const pseudoHash = `${seqno}_${Date.now()}_${amount.toFixed(2)}`;
718
- 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}`);
719
2132
  return pseudoHash;
720
2133
  } catch (error) {
721
- if (error?.status >= 500 || error?.response?.status >= 500) {
2134
+ const status = error?.status || error?.response?.status;
2135
+ if (status === 429 || status >= 500) {
722
2136
  invalidateTonClientCache();
723
2137
  }
724
- log4.error({ err: error }, "Error sending TON");
2138
+ log8.error({ err: error }, "Error sending TON");
725
2139
  throw error;
726
2140
  }
727
2141
  });
728
2142
  }
729
2143
 
730
2144
  // src/utils/retry.ts
731
- var log5 = createLogger("Utils");
2145
+ var log9 = createLogger("Utils");
732
2146
  var DEFAULT_OPTIONS = {
733
2147
  maxAttempts: RETRY_DEFAULT_MAX_ATTEMPTS,
734
2148
  baseDelayMs: RETRY_DEFAULT_BASE_DELAY_MS,
@@ -752,7 +2166,7 @@ async function withRetry(fn, options = {}) {
752
2166
  return result;
753
2167
  } catch (error) {
754
2168
  lastError = error instanceof Error ? error : new Error(String(error));
755
- log5.warn(`Retry attempt ${attempt}/${opts.maxAttempts} failed: ${lastError.message}`);
2169
+ log9.warn(`Retry attempt ${attempt}/${opts.maxAttempts} failed: ${lastError.message}`);
756
2170
  if (attempt < opts.maxAttempts) {
757
2171
  const delay = Math.min(opts.baseDelayMs * Math.pow(2, attempt - 1), opts.maxDelayMs);
758
2172
  await sleep(delay);
@@ -951,25 +2365,25 @@ function findJettonBalance(balances, jettonAddress) {
951
2365
  }
952
2366
  });
953
2367
  }
954
- function cleanupOldTransactions(db, retentionDays, log7) {
2368
+ function cleanupOldTransactions(db, retentionDays, log11) {
955
2369
  if (Math.random() > CLEANUP_PROBABILITY) return;
956
2370
  try {
957
2371
  const cutoff = Math.floor(Date.now() / 1e3) - retentionDays * 24 * 60 * 60;
958
2372
  const result = db.prepare("DELETE FROM used_transactions WHERE used_at < ?").run(cutoff);
959
2373
  if (result.changes > 0) {
960
- log7.debug(`Cleaned up ${result.changes} old transaction records (>${retentionDays}d)`);
2374
+ log11.debug(`Cleaned up ${result.changes} old transaction records (>${retentionDays}d)`);
961
2375
  }
962
2376
  } catch (err) {
963
- log7.error("Transaction cleanup failed:", err);
2377
+ log11.error("Transaction cleanup failed:", err);
964
2378
  }
965
2379
  }
966
- function createTonSDK(log7, db) {
2380
+ function createTonSDK(log11, db) {
967
2381
  return {
968
2382
  getAddress() {
969
2383
  try {
970
2384
  return getWalletAddress();
971
2385
  } catch (err) {
972
- log7.error("ton.getAddress() failed:", err);
2386
+ log11.error("ton.getAddress() failed:", err);
973
2387
  return null;
974
2388
  }
975
2389
  },
@@ -979,7 +2393,7 @@ function createTonSDK(log7, db) {
979
2393
  if (!addr) return null;
980
2394
  return await getWalletBalance(addr);
981
2395
  } catch (err) {
982
- log7.error("ton.getBalance() failed:", err);
2396
+ log11.error("ton.getBalance() failed:", err);
983
2397
  return null;
984
2398
  }
985
2399
  },
@@ -987,7 +2401,7 @@ function createTonSDK(log7, db) {
987
2401
  try {
988
2402
  return await getTonPrice();
989
2403
  } catch (err) {
990
- log7.error("ton.getPrice() failed:", err);
2404
+ log11.error("ton.getPrice() failed:", err);
991
2405
  return null;
992
2406
  }
993
2407
  },
@@ -1038,7 +2452,7 @@ function createTonSDK(log7, db) {
1038
2452
  );
1039
2453
  return formatTransactions(transactions);
1040
2454
  } catch (err) {
1041
- log7.error("ton.getTransactions() failed:", err);
2455
+ log11.error("ton.getTransactions() failed:", err);
1042
2456
  return [];
1043
2457
  }
1044
2458
  },
@@ -1054,7 +2468,7 @@ function createTonSDK(log7, db) {
1054
2468
  throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
1055
2469
  }
1056
2470
  const maxAgeMinutes = params.maxAgeMinutes ?? DEFAULT_MAX_AGE_MINUTES;
1057
- cleanupOldTransactions(db, DEFAULT_TX_RETENTION_DAYS, log7);
2471
+ cleanupOldTransactions(db, DEFAULT_TX_RETENTION_DAYS, log11);
1058
2472
  try {
1059
2473
  const txs = await this.getTransactions(address, 20);
1060
2474
  for (const tx of txs) {
@@ -1088,7 +2502,7 @@ function createTonSDK(log7, db) {
1088
2502
  };
1089
2503
  } catch (err) {
1090
2504
  if (err instanceof PluginSDKError) throw err;
1091
- log7.error("ton.verifyPayment() failed:", err);
2505
+ log11.error("ton.verifyPayment() failed:", err);
1092
2506
  return {
1093
2507
  verified: false,
1094
2508
  error: `Verification failed: ${err instanceof Error ? err.message : String(err)}`
@@ -1102,7 +2516,7 @@ function createTonSDK(log7, db) {
1102
2516
  if (!addr) return [];
1103
2517
  const response = await tonapiFetch(`/accounts/${encodeURIComponent(addr)}/jettons`);
1104
2518
  if (!response.ok) {
1105
- log7.error(`ton.getJettonBalances() TonAPI error: ${response.status}`);
2519
+ log11.error(`ton.getJettonBalances() TonAPI error: ${response.status}`);
1106
2520
  return [];
1107
2521
  }
1108
2522
  const data = await response.json();
@@ -1130,7 +2544,7 @@ function createTonSDK(log7, db) {
1130
2544
  }
1131
2545
  return balances;
1132
2546
  } catch (err) {
1133
- log7.error("ton.getJettonBalances() failed:", err);
2547
+ log11.error("ton.getJettonBalances() failed:", err);
1134
2548
  return [];
1135
2549
  }
1136
2550
  },
@@ -1139,7 +2553,7 @@ function createTonSDK(log7, db) {
1139
2553
  const response = await tonapiFetch(`/jettons/${encodeURIComponent(jettonAddress)}`);
1140
2554
  if (response.status === 404) return null;
1141
2555
  if (!response.ok) {
1142
- log7.error(`ton.getJettonInfo() TonAPI error: ${response.status}`);
2556
+ log11.error(`ton.getJettonInfo() TonAPI error: ${response.status}`);
1143
2557
  return null;
1144
2558
  }
1145
2559
  const data = await response.json();
@@ -1157,7 +2571,7 @@ function createTonSDK(log7, db) {
1157
2571
  image: data.preview || metadata.image || void 0
1158
2572
  };
1159
2573
  } catch (err) {
1160
- log7.error("ton.getJettonInfo() failed:", err);
2574
+ log11.error("ton.getJettonInfo() failed:", err);
1161
2575
  return null;
1162
2576
  }
1163
2577
  },
@@ -1251,14 +2665,14 @@ function createTonSDK(log7, db) {
1251
2665
  try {
1252
2666
  const response = await tonapiFetch(`/accounts/${encodeURIComponent(ownerAddress)}/jettons`);
1253
2667
  if (!response.ok) {
1254
- log7.error(`ton.getJettonWalletAddress() TonAPI error: ${response.status}`);
2668
+ log11.error(`ton.getJettonWalletAddress() TonAPI error: ${response.status}`);
1255
2669
  return null;
1256
2670
  }
1257
2671
  const data = await response.json();
1258
2672
  const match = findJettonBalance(data.balances ?? [], jettonAddress);
1259
2673
  return match ? match.wallet_address.address : null;
1260
2674
  } catch (err) {
1261
- log7.error("ton.getJettonWalletAddress() failed:", err);
2675
+ log11.error("ton.getJettonWalletAddress() failed:", err);
1262
2676
  return null;
1263
2677
  }
1264
2678
  },
@@ -1271,14 +2685,14 @@ function createTonSDK(log7, db) {
1271
2685
  `/accounts/${encodeURIComponent(addr)}/nfts?limit=100&indirect_ownership=true`
1272
2686
  );
1273
2687
  if (!response.ok) {
1274
- log7.error(`ton.getNftItems() TonAPI error: ${response.status}`);
2688
+ log11.error(`ton.getNftItems() TonAPI error: ${response.status}`);
1275
2689
  return [];
1276
2690
  }
1277
2691
  const data = await response.json();
1278
2692
  if (!Array.isArray(data.nft_items)) return [];
1279
2693
  return data.nft_items.filter((item) => item.trust !== "blacklist").map((item) => mapNftItem(item));
1280
2694
  } catch (err) {
1281
- log7.error("ton.getNftItems() failed:", err);
2695
+ log11.error("ton.getNftItems() failed:", err);
1282
2696
  return [];
1283
2697
  }
1284
2698
  },
@@ -1287,13 +2701,13 @@ function createTonSDK(log7, db) {
1287
2701
  const response = await tonapiFetch(`/nfts/${encodeURIComponent(nftAddress)}`);
1288
2702
  if (response.status === 404) return null;
1289
2703
  if (!response.ok) {
1290
- log7.error(`ton.getNftInfo() TonAPI error: ${response.status}`);
2704
+ log11.error(`ton.getNftInfo() TonAPI error: ${response.status}`);
1291
2705
  return null;
1292
2706
  }
1293
2707
  const item = await response.json();
1294
2708
  return mapNftItem(item);
1295
2709
  } catch (err) {
1296
- log7.error("ton.getNftInfo() failed:", err);
2710
+ log11.error("ton.getNftInfo() failed:", err);
1297
2711
  return null;
1298
2712
  }
1299
2713
  },
@@ -1350,7 +2764,7 @@ function randomLong() {
1350
2764
  }
1351
2765
 
1352
2766
  // src/sdk/telegram-messages.ts
1353
- function createTelegramMessagesSDK(bridge, log7) {
2767
+ function createTelegramMessagesSDK(bridge, log11) {
1354
2768
  function requireBridge() {
1355
2769
  if (!bridge.isAvailable()) {
1356
2770
  throw new PluginSDKError(
@@ -1470,7 +2884,7 @@ function createTelegramMessagesSDK(bridge, log7) {
1470
2884
  return (resultData.messages ?? []).map(toSimpleMessage);
1471
2885
  } catch (err) {
1472
2886
  if (err instanceof PluginSDKError) throw err;
1473
- log7.error("telegram.searchMessages() failed:", err);
2887
+ log11.error("telegram.searchMessages() failed:", err);
1474
2888
  return [];
1475
2889
  }
1476
2890
  },
@@ -1695,7 +3109,7 @@ function createTelegramMessagesSDK(bridge, log7) {
1695
3109
  }
1696
3110
 
1697
3111
  // src/sdk/telegram-social.ts
1698
- function createTelegramSocialSDK(bridge, log7) {
3112
+ function createTelegramSocialSDK(bridge, log11) {
1699
3113
  function requireBridge() {
1700
3114
  if (!bridge.isAvailable()) {
1701
3115
  throw new PluginSDKError(
@@ -1775,7 +3189,7 @@ function createTelegramSocialSDK(bridge, log7) {
1775
3189
  return null;
1776
3190
  } catch (err) {
1777
3191
  if (err instanceof PluginSDKError) throw err;
1778
- log7.error("telegram.getChatInfo() failed:", err);
3192
+ log11.error("telegram.getChatInfo() failed:", err);
1779
3193
  return null;
1780
3194
  }
1781
3195
  },
@@ -1884,7 +3298,7 @@ function createTelegramSocialSDK(bridge, log7) {
1884
3298
  });
1885
3299
  } catch (err) {
1886
3300
  if (err instanceof PluginSDKError) throw err;
1887
- log7.error("telegram.getParticipants() failed:", err);
3301
+ log11.error("telegram.getParticipants() failed:", err);
1888
3302
  return [];
1889
3303
  }
1890
3304
  },
@@ -2098,7 +3512,7 @@ function createTelegramSocialSDK(bridge, log7) {
2098
3512
  try {
2099
3513
  const client = getClient();
2100
3514
  const { Api } = await import("telegram");
2101
- const user = await client.getEntity(userId.toString());
3515
+ const user = await client.getInputEntity(userId.toString());
2102
3516
  const invoiceData = {
2103
3517
  peer: user,
2104
3518
  giftId: BigInt(giftId),
@@ -2183,12 +3597,6 @@ function createTelegramSocialSDK(bridge, log7) {
2183
3597
  try {
2184
3598
  const client = getClient();
2185
3599
  const { Api } = await import("telegram");
2186
- if (!Api.payments.GetResaleStarGifts) {
2187
- throw new PluginSDKError(
2188
- "Resale gift marketplace is not supported in the current Telegram API layer.",
2189
- "OPERATION_FAILED"
2190
- );
2191
- }
2192
3600
  const result = await client.invoke(
2193
3601
  new Api.payments.GetResaleStarGifts({
2194
3602
  offset: "",
@@ -2212,25 +3620,16 @@ function createTelegramSocialSDK(bridge, log7) {
2212
3620
  try {
2213
3621
  const client = getClient();
2214
3622
  const { Api } = await import("telegram");
2215
- if (!Api.InputInvoiceStarGiftResale) {
2216
- throw new PluginSDKError(
2217
- "Resale gift purchasing is not supported in the current Telegram API layer.",
2218
- "OPERATION_FAILED"
2219
- );
2220
- }
2221
- const stargiftInput = new Api.InputSavedStarGiftUser({
2222
- odayId: BigInt(giftId)
3623
+ const toId = new Api.InputPeerSelf();
3624
+ const invoice = new Api.InputInvoiceStarGiftResale({
3625
+ slug: giftId,
3626
+ toId
2223
3627
  });
2224
- const invoiceData = { stargift: stargiftInput };
2225
- const form = await client.invoke(
2226
- new Api.payments.GetPaymentForm({
2227
- invoice: new Api.InputInvoiceStarGiftResale(invoiceData)
2228
- })
2229
- );
3628
+ const form = await client.invoke(new Api.payments.GetPaymentForm({ invoice }));
2230
3629
  await client.invoke(
2231
3630
  new Api.payments.SendStarsForm({
2232
3631
  formId: form.formId,
2233
- invoice: new Api.InputInvoiceStarGiftResale(invoiceData)
3632
+ invoice
2234
3633
  })
2235
3634
  );
2236
3635
  } catch (err) {
@@ -2323,7 +3722,7 @@ function createTelegramSocialSDK(bridge, log7) {
2323
3722
  }
2324
3723
 
2325
3724
  // src/sdk/telegram.ts
2326
- function createTelegramSDK(bridge, log7) {
3725
+ function createTelegramSDK(bridge, log11) {
2327
3726
  function requireBridge() {
2328
3727
  if (!bridge.isAvailable()) {
2329
3728
  throw new PluginSDKError(
@@ -2433,7 +3832,7 @@ function createTelegramSDK(bridge, log7) {
2433
3832
  timestamp: m.timestamp
2434
3833
  }));
2435
3834
  } catch (err) {
2436
- log7.error("telegram.getMessages() failed:", err);
3835
+ log11.error("telegram.getMessages() failed:", err);
2437
3836
  return [];
2438
3837
  }
2439
3838
  },
@@ -2455,7 +3854,7 @@ function createTelegramSDK(bridge, log7) {
2455
3854
  return bridge.isAvailable();
2456
3855
  },
2457
3856
  getRawClient() {
2458
- log7.warn("getRawClient() called \u2014 this bypasses SDK sandbox guarantees");
3857
+ log11.warn("getRawClient() called \u2014 this bypasses SDK sandbox guarantees");
2459
3858
  if (!bridge.isAvailable()) return null;
2460
3859
  try {
2461
3860
  return bridge.getClient().getClient();
@@ -2464,17 +3863,17 @@ function createTelegramSDK(bridge, log7) {
2464
3863
  }
2465
3864
  },
2466
3865
  // Spread extended methods from sub-modules
2467
- ...createTelegramMessagesSDK(bridge, log7),
2468
- ...createTelegramSocialSDK(bridge, log7)
3866
+ ...createTelegramMessagesSDK(bridge, log11),
3867
+ ...createTelegramSocialSDK(bridge, log11)
2469
3868
  };
2470
3869
  }
2471
3870
 
2472
3871
  // src/sdk/secrets.ts
2473
3872
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
2474
- import { join as join3 } from "path";
2475
- var SECRETS_DIR = join3(TELETON_ROOT, "plugins", "data");
3873
+ import { join as join4 } from "path";
3874
+ var SECRETS_DIR = join4(TELETON_ROOT, "plugins", "data");
2476
3875
  function getSecretsPath(pluginName) {
2477
- return join3(SECRETS_DIR, `${pluginName}.secrets.json`);
3876
+ return join4(SECRETS_DIR, `${pluginName}.secrets.json`);
2478
3877
  }
2479
3878
  function readSecretsFile(pluginName) {
2480
3879
  const filePath = getSecretsPath(pluginName);
@@ -2506,23 +3905,23 @@ function deletePluginSecret(pluginName, key) {
2506
3905
  function listPluginSecretKeys(pluginName) {
2507
3906
  return Object.keys(readSecretsFile(pluginName));
2508
3907
  }
2509
- function createSecretsSDK(pluginName, pluginConfig, log7) {
3908
+ function createSecretsSDK(pluginName, pluginConfig, log11) {
2510
3909
  const envPrefix = pluginName.replace(/-/g, "_").toUpperCase();
2511
3910
  function get(key) {
2512
3911
  const envKey = `${envPrefix}_${key.toUpperCase()}`;
2513
3912
  const envValue = process.env[envKey];
2514
3913
  if (envValue) {
2515
- log7.debug(`Secret "${key}" resolved from env var ${envKey}`);
3914
+ log11.debug(`Secret "${key}" resolved from env var ${envKey}`);
2516
3915
  return envValue;
2517
3916
  }
2518
3917
  const stored = readSecretsFile(pluginName);
2519
3918
  if (key in stored && stored[key]) {
2520
- log7.debug(`Secret "${key}" resolved from secrets store`);
3919
+ log11.debug(`Secret "${key}" resolved from secrets store`);
2521
3920
  return stored[key];
2522
3921
  }
2523
3922
  const configValue = pluginConfig[key];
2524
3923
  if (configValue !== void 0 && configValue !== null) {
2525
- log7.debug(`Secret "${key}" resolved from pluginConfig`);
3924
+ log11.debug(`Secret "${key}" resolved from pluginConfig`);
2526
3925
  return String(configValue);
2527
3926
  }
2528
3927
  return void 0;
@@ -2641,13 +4040,13 @@ function createSafeDb(db) {
2641
4040
  });
2642
4041
  }
2643
4042
  function createPluginSDK(deps, opts) {
2644
- const log7 = createLogger2(opts.pluginName);
4043
+ const log11 = createLogger2(opts.pluginName);
2645
4044
  const safeDb = opts.db ? createSafeDb(opts.db) : null;
2646
- const ton = Object.freeze(createTonSDK(log7, safeDb));
2647
- const telegram = Object.freeze(createTelegramSDK(deps.bridge, log7));
2648
- 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));
2649
4048
  const storage = safeDb ? Object.freeze(createStorageSDK(safeDb)) : null;
2650
- const frozenLog = Object.freeze(log7);
4049
+ const frozenLog = Object.freeze(log11);
2651
4050
  const frozenConfig = Object.freeze(opts.sanitizedConfig);
2652
4051
  const frozenPluginConfig = Object.freeze(JSON.parse(JSON.stringify(opts.pluginConfig ?? {})));
2653
4052
  return Object.freeze({
@@ -2720,21 +4119,21 @@ function semverSatisfies(current, range) {
2720
4119
 
2721
4120
  // src/agent/tools/plugin-loader.ts
2722
4121
  var execFileAsync = promisify(execFile);
2723
- var log6 = createLogger("PluginLoader");
2724
- var PLUGIN_DATA_DIR = join4(TELETON_ROOT, "plugins", "data");
4122
+ var log10 = createLogger("PluginLoader");
4123
+ var PLUGIN_DATA_DIR = join5(TELETON_ROOT, "plugins", "data");
2725
4124
  function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
2726
4125
  let manifest = null;
2727
4126
  if (raw.manifest) {
2728
4127
  try {
2729
4128
  manifest = validateManifest(raw.manifest);
2730
4129
  } catch (err) {
2731
- log6.warn(
4130
+ log10.warn(
2732
4131
  `[${entryName}] invalid manifest, ignoring: ${err instanceof Error ? err.message : err}`
2733
4132
  );
2734
4133
  }
2735
4134
  }
2736
4135
  if (!manifest) {
2737
- const manifestPath = join4(WORKSPACE_PATHS.PLUGINS_DIR, entryName, "manifest.json");
4136
+ const manifestPath = join5(WORKSPACE_PATHS.PLUGINS_DIR, entryName, "manifest.json");
2738
4137
  try {
2739
4138
  if (existsSync6(manifestPath)) {
2740
4139
  const diskManifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
@@ -2811,7 +4210,7 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
2811
4210
  },
2812
4211
  migrate() {
2813
4212
  try {
2814
- const dbPath = join4(PLUGIN_DATA_DIR, `${pluginName}.db`);
4213
+ const dbPath = join5(PLUGIN_DATA_DIR, `${pluginName}.db`);
2815
4214
  pluginDb = openModuleDb(dbPath);
2816
4215
  if (hasMigrate) {
2817
4216
  raw.migrate(pluginDb);
@@ -2912,30 +4311,30 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
2912
4311
  return module;
2913
4312
  }
2914
4313
  async function ensurePluginDeps(pluginDir, pluginEntry) {
2915
- const pkgJson = join4(pluginDir, "package.json");
2916
- const lockfile = join4(pluginDir, "package-lock.json");
2917
- 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");
2918
4317
  if (!existsSync6(pkgJson)) return;
2919
4318
  if (!existsSync6(lockfile)) {
2920
- log6.warn(
4319
+ log10.warn(
2921
4320
  `[${pluginEntry}] package.json without package-lock.json \u2014 skipping (lockfile required)`
2922
4321
  );
2923
4322
  return;
2924
4323
  }
2925
4324
  if (existsSync6(nodeModules)) {
2926
- const marker = join4(nodeModules, ".package-lock.json");
4325
+ const marker = join5(nodeModules, ".package-lock.json");
2927
4326
  if (existsSync6(marker) && statSync(marker).mtimeMs >= statSync(lockfile).mtimeMs) return;
2928
4327
  }
2929
- log6.info(`[${pluginEntry}] Installing dependencies...`);
4328
+ log10.info(`[${pluginEntry}] Installing dependencies...`);
2930
4329
  try {
2931
4330
  await execFileAsync("npm", ["ci", "--ignore-scripts", "--no-audit", "--no-fund"], {
2932
4331
  cwd: pluginDir,
2933
4332
  timeout: 6e4,
2934
4333
  env: { ...process.env, NODE_ENV: "production" }
2935
4334
  });
2936
- log6.info(`[${pluginEntry}] Dependencies installed`);
4335
+ log10.info(`[${pluginEntry}] Dependencies installed`);
2937
4336
  } catch (err) {
2938
- 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)}`);
2939
4338
  }
2940
4339
  }
2941
4340
  async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
@@ -2949,14 +4348,14 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
2949
4348
  const pluginPaths = [];
2950
4349
  for (const entry of entries) {
2951
4350
  if (entry === "data") continue;
2952
- const entryPath = join4(pluginsDir, entry);
4351
+ const entryPath = join5(pluginsDir, entry);
2953
4352
  let modulePath = null;
2954
4353
  try {
2955
4354
  const stat = statSync(entryPath);
2956
4355
  if (stat.isFile() && entry.endsWith(".js")) {
2957
4356
  modulePath = entryPath;
2958
4357
  } else if (stat.isDirectory()) {
2959
- const indexPath = join4(entryPath, "index.js");
4358
+ const indexPath = join5(entryPath, "index.js");
2960
4359
  if (existsSync6(indexPath)) {
2961
4360
  modulePath = indexPath;
2962
4361
  }
@@ -2969,7 +4368,7 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
2969
4368
  }
2970
4369
  }
2971
4370
  await Promise.allSettled(
2972
- 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))
2973
4372
  );
2974
4373
  const loadResults = await Promise.allSettled(
2975
4374
  pluginPaths.map(async ({ entry, path }) => {
@@ -2980,7 +4379,7 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
2980
4379
  );
2981
4380
  for (const result of loadResults) {
2982
4381
  if (result.status === "rejected") {
2983
- log6.error(
4382
+ log10.error(
2984
4383
  `Plugin failed to load: ${result.reason instanceof Error ? result.reason.message : result.reason}`
2985
4384
  );
2986
4385
  continue;
@@ -2988,18 +4387,18 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
2988
4387
  const { entry, mod } = result.value;
2989
4388
  try {
2990
4389
  if (!mod.tools || typeof mod.tools !== "function" && !Array.isArray(mod.tools)) {
2991
- log6.warn(`Plugin "${entry}": no 'tools' array or function exported, skipping`);
4390
+ log10.warn(`Plugin "${entry}": no 'tools' array or function exported, skipping`);
2992
4391
  continue;
2993
4392
  }
2994
4393
  const adapted = adaptPlugin(mod, entry, config, loadedModuleNames, sdkDeps);
2995
4394
  if (loadedNames.has(adapted.name)) {
2996
- log6.warn(`Plugin "${adapted.name}" already loaded, skipping duplicate from "${entry}"`);
4395
+ log10.warn(`Plugin "${adapted.name}" already loaded, skipping duplicate from "${entry}"`);
2997
4396
  continue;
2998
4397
  }
2999
4398
  loadedNames.add(adapted.name);
3000
4399
  modules.push(adapted);
3001
4400
  } catch (err) {
3002
- 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}`);
3003
4402
  }
3004
4403
  }
3005
4404
  return modules;
@@ -3022,13 +4421,25 @@ function numberInRange(min, max) {
3022
4421
  function enumValidator(options) {
3023
4422
  return (v) => options.includes(v) ? void 0 : `Must be one of: ${options.join(", ")}`;
3024
4423
  }
4424
+ function positiveInteger(v) {
4425
+ const n = Number(v);
4426
+ if (!Number.isInteger(n) || n <= 0) return "Must be a positive integer";
4427
+ return void 0;
4428
+ }
4429
+ function validateUrl(v) {
4430
+ if (v === "") return void 0;
4431
+ if (v.startsWith("http://") || v.startsWith("https://")) return void 0;
4432
+ return "Must be empty or start with http:// or https://";
4433
+ }
3025
4434
  var CONFIGURABLE_KEYS = {
3026
4435
  // ─── API Keys ──────────────────────────────────────────────────────
3027
4436
  "agent.api_key": {
3028
4437
  type: "string",
3029
4438
  category: "API Keys",
4439
+ label: "LLM API Key",
3030
4440
  description: "LLM provider API key",
3031
4441
  sensitive: true,
4442
+ hotReload: "instant",
3032
4443
  validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
3033
4444
  mask: (v) => v.slice(0, 8) + "****",
3034
4445
  parse: identity
@@ -3036,8 +4447,10 @@ var CONFIGURABLE_KEYS = {
3036
4447
  tavily_api_key: {
3037
4448
  type: "string",
3038
4449
  category: "API Keys",
4450
+ label: "Tavily API Key",
3039
4451
  description: "Tavily API key for web search",
3040
4452
  sensitive: true,
4453
+ hotReload: "instant",
3041
4454
  validate: (v) => v.startsWith("tvly-") ? void 0 : "Must start with 'tvly-'",
3042
4455
  mask: (v) => v.slice(0, 9) + "****",
3043
4456
  parse: identity
@@ -3045,8 +4458,21 @@ var CONFIGURABLE_KEYS = {
3045
4458
  tonapi_key: {
3046
4459
  type: "string",
3047
4460
  category: "API Keys",
4461
+ label: "TonAPI Key",
3048
4462
  description: "TonAPI key for higher rate limits",
3049
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",
3050
4476
  validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
3051
4477
  mask: (v) => v.slice(0, 10) + "****",
3052
4478
  parse: identity
@@ -3054,8 +4480,10 @@ var CONFIGURABLE_KEYS = {
3054
4480
  "telegram.bot_token": {
3055
4481
  type: "string",
3056
4482
  category: "API Keys",
4483
+ label: "Bot Token",
3057
4484
  description: "Bot token from @BotFather",
3058
4485
  sensitive: true,
4486
+ hotReload: "instant",
3059
4487
  validate: (v) => v.includes(":") ? void 0 : "Must contain ':' (e.g., 123456:ABC...)",
3060
4488
  mask: (v) => v.split(":")[0] + ":****",
3061
4489
  parse: identity
@@ -3064,8 +4492,10 @@ var CONFIGURABLE_KEYS = {
3064
4492
  "agent.provider": {
3065
4493
  type: "enum",
3066
4494
  category: "Agent",
4495
+ label: "Provider",
3067
4496
  description: "LLM provider",
3068
4497
  sensitive: false,
4498
+ hotReload: "instant",
3069
4499
  options: [
3070
4500
  "anthropic",
3071
4501
  "claude-code",
@@ -3098,8 +4528,10 @@ var CONFIGURABLE_KEYS = {
3098
4528
  "agent.model": {
3099
4529
  type: "string",
3100
4530
  category: "Agent",
4531
+ label: "Model",
3101
4532
  description: "Main LLM model ID",
3102
4533
  sensitive: false,
4534
+ hotReload: "instant",
3103
4535
  validate: nonEmpty,
3104
4536
  mask: identity,
3105
4537
  parse: identity
@@ -3107,8 +4539,10 @@ var CONFIGURABLE_KEYS = {
3107
4539
  "agent.utility_model": {
3108
4540
  type: "string",
3109
4541
  category: "Agent",
4542
+ label: "Utility Model",
3110
4543
  description: "Cheap model for summarization (auto-detected if empty)",
3111
4544
  sensitive: false,
4545
+ hotReload: "instant",
3112
4546
  validate: noValidation,
3113
4547
  mask: identity,
3114
4548
  parse: identity
@@ -3116,8 +4550,10 @@ var CONFIGURABLE_KEYS = {
3116
4550
  "agent.temperature": {
3117
4551
  type: "number",
3118
4552
  category: "Agent",
4553
+ label: "Temperature",
3119
4554
  description: "Response creativity (0.0 = deterministic, 2.0 = max)",
3120
4555
  sensitive: false,
4556
+ hotReload: "instant",
3121
4557
  validate: numberInRange(0, 2),
3122
4558
  mask: identity,
3123
4559
  parse: (v) => Number(v)
@@ -3125,8 +4561,10 @@ var CONFIGURABLE_KEYS = {
3125
4561
  "agent.max_tokens": {
3126
4562
  type: "number",
3127
4563
  category: "Agent",
4564
+ label: "Max Tokens",
3128
4565
  description: "Maximum response length in tokens",
3129
4566
  sensitive: false,
4567
+ hotReload: "instant",
3130
4568
  validate: numberInRange(256, 128e3),
3131
4569
  mask: identity,
3132
4570
  parse: (v) => Number(v)
@@ -3134,18 +4572,44 @@ var CONFIGURABLE_KEYS = {
3134
4572
  "agent.max_agentic_iterations": {
3135
4573
  type: "number",
3136
4574
  category: "Agent",
4575
+ label: "Max Iterations",
3137
4576
  description: "Max tool-call loop iterations per message",
3138
4577
  sensitive: false,
4578
+ hotReload: "instant",
3139
4579
  validate: numberInRange(1, 20),
3140
4580
  mask: identity,
3141
4581
  parse: (v) => Number(v)
3142
4582
  },
4583
+ "agent.base_url": {
4584
+ type: "string",
4585
+ category: "Agent",
4586
+ label: "API Base URL",
4587
+ description: "Base URL for local LLM server (requires restart)",
4588
+ sensitive: false,
4589
+ hotReload: "restart",
4590
+ validate: validateUrl,
4591
+ mask: identity,
4592
+ parse: identity
4593
+ },
4594
+ "cocoon.port": {
4595
+ type: "number",
4596
+ category: "Agent",
4597
+ label: "Cocoon Port",
4598
+ description: "Cocoon proxy port (requires restart)",
4599
+ sensitive: false,
4600
+ hotReload: "restart",
4601
+ validate: numberInRange(1, 65535),
4602
+ mask: identity,
4603
+ parse: (v) => Number(v)
4604
+ },
3143
4605
  // ─── Session ───────────────────────────────────────────────────
3144
4606
  "agent.session_reset_policy.daily_reset_enabled": {
3145
4607
  type: "boolean",
3146
4608
  category: "Session",
4609
+ label: "Daily Reset",
3147
4610
  description: "Enable daily session reset at specified hour",
3148
4611
  sensitive: false,
4612
+ hotReload: "instant",
3149
4613
  validate: enumValidator(["true", "false"]),
3150
4614
  mask: identity,
3151
4615
  parse: (v) => v === "true"
@@ -3153,8 +4617,10 @@ var CONFIGURABLE_KEYS = {
3153
4617
  "agent.session_reset_policy.daily_reset_hour": {
3154
4618
  type: "number",
3155
4619
  category: "Session",
4620
+ label: "Reset Hour",
3156
4621
  description: "Hour (0-23 UTC) for daily session reset",
3157
4622
  sensitive: false,
4623
+ hotReload: "instant",
3158
4624
  validate: numberInRange(0, 23),
3159
4625
  mask: identity,
3160
4626
  parse: (v) => Number(v)
@@ -3162,8 +4628,10 @@ var CONFIGURABLE_KEYS = {
3162
4628
  "agent.session_reset_policy.idle_expiry_enabled": {
3163
4629
  type: "boolean",
3164
4630
  category: "Session",
4631
+ label: "Idle Expiry",
3165
4632
  description: "Enable automatic session expiry after idle period",
3166
4633
  sensitive: false,
4634
+ hotReload: "instant",
3167
4635
  validate: enumValidator(["true", "false"]),
3168
4636
  mask: identity,
3169
4637
  parse: (v) => v === "true"
@@ -3171,8 +4639,10 @@ var CONFIGURABLE_KEYS = {
3171
4639
  "agent.session_reset_policy.idle_expiry_minutes": {
3172
4640
  type: "number",
3173
4641
  category: "Session",
4642
+ label: "Idle Minutes",
3174
4643
  description: "Idle minutes before session expires (minimum 1)",
3175
4644
  sensitive: false,
4645
+ hotReload: "instant",
3176
4646
  validate: numberInRange(1, Number.MAX_SAFE_INTEGER),
3177
4647
  mask: identity,
3178
4648
  parse: (v) => Number(v)
@@ -3181,8 +4651,10 @@ var CONFIGURABLE_KEYS = {
3181
4651
  "telegram.bot_username": {
3182
4652
  type: "string",
3183
4653
  category: "Telegram",
4654
+ label: "Bot Username",
3184
4655
  description: "Bot username without @",
3185
4656
  sensitive: false,
4657
+ hotReload: "instant",
3186
4658
  validate: (v) => v.length >= 3 ? void 0 : "Must be at least 3 characters",
3187
4659
  mask: identity,
3188
4660
  parse: identity
@@ -3190,28 +4662,46 @@ var CONFIGURABLE_KEYS = {
3190
4662
  "telegram.dm_policy": {
3191
4663
  type: "enum",
3192
4664
  category: "Telegram",
3193
- description: "DM access policy",
4665
+ label: "DM Policy",
4666
+ description: "Who can message the bot in private",
3194
4667
  sensitive: false,
3195
- options: ["pairing", "allowlist", "open", "disabled"],
3196
- validate: enumValidator(["pairing", "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"]),
3197
4677
  mask: identity,
3198
4678
  parse: identity
3199
4679
  },
3200
4680
  "telegram.group_policy": {
3201
4681
  type: "enum",
3202
4682
  category: "Telegram",
3203
- description: "Group access policy",
4683
+ label: "Group Policy",
4684
+ description: "Which groups the bot can respond in",
3204
4685
  sensitive: false,
3205
- options: ["open", "allowlist", "disabled"],
3206
- 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"]),
3207
4695
  mask: identity,
3208
4696
  parse: identity
3209
4697
  },
3210
4698
  "telegram.require_mention": {
3211
4699
  type: "boolean",
3212
4700
  category: "Telegram",
4701
+ label: "Require Mention",
3213
4702
  description: "Require @mention in groups to respond",
3214
4703
  sensitive: false,
4704
+ hotReload: "instant",
3215
4705
  validate: enumValidator(["true", "false"]),
3216
4706
  mask: identity,
3217
4707
  parse: (v) => v === "true"
@@ -3219,8 +4709,10 @@ var CONFIGURABLE_KEYS = {
3219
4709
  "telegram.owner_name": {
3220
4710
  type: "string",
3221
4711
  category: "Telegram",
4712
+ label: "Owner Name",
3222
4713
  description: "Owner's first name (used in system prompt)",
3223
4714
  sensitive: false,
4715
+ hotReload: "instant",
3224
4716
  validate: noValidation,
3225
4717
  mask: identity,
3226
4718
  parse: identity
@@ -3228,8 +4720,10 @@ var CONFIGURABLE_KEYS = {
3228
4720
  "telegram.owner_username": {
3229
4721
  type: "string",
3230
4722
  category: "Telegram",
4723
+ label: "Owner Username",
3231
4724
  description: "Owner's Telegram username (without @)",
3232
4725
  sensitive: false,
4726
+ hotReload: "instant",
3233
4727
  validate: noValidation,
3234
4728
  mask: identity,
3235
4729
  parse: identity
@@ -3237,8 +4731,10 @@ var CONFIGURABLE_KEYS = {
3237
4731
  "telegram.debounce_ms": {
3238
4732
  type: "number",
3239
4733
  category: "Telegram",
4734
+ label: "Debounce (ms)",
3240
4735
  description: "Group message debounce delay in ms (0 = disabled)",
3241
4736
  sensitive: false,
4737
+ hotReload: "instant",
3242
4738
  validate: numberInRange(0, 1e4),
3243
4739
  mask: identity,
3244
4740
  parse: (v) => Number(v)
@@ -3246,8 +4742,10 @@ var CONFIGURABLE_KEYS = {
3246
4742
  "telegram.agent_channel": {
3247
4743
  type: "string",
3248
4744
  category: "Telegram",
4745
+ label: "Agent Channel",
3249
4746
  description: "Channel username for auto-publishing",
3250
4747
  sensitive: false,
4748
+ hotReload: "instant",
3251
4749
  validate: noValidation,
3252
4750
  mask: identity,
3253
4751
  parse: identity
@@ -3255,29 +4753,126 @@ var CONFIGURABLE_KEYS = {
3255
4753
  "telegram.typing_simulation": {
3256
4754
  type: "boolean",
3257
4755
  category: "Telegram",
4756
+ label: "Typing Simulation",
3258
4757
  description: "Simulate typing indicator before sending replies",
3259
4758
  sensitive: false,
4759
+ hotReload: "instant",
3260
4760
  validate: enumValidator(["true", "false"]),
3261
4761
  mask: identity,
3262
4762
  parse: (v) => v === "true"
3263
4763
  },
4764
+ "telegram.owner_id": {
4765
+ type: "number",
4766
+ category: "Telegram",
4767
+ label: "Admin ID",
4768
+ description: "Primary admin Telegram user ID (auto-added to Admin IDs)",
4769
+ sensitive: false,
4770
+ hotReload: "instant",
4771
+ validate: positiveInteger,
4772
+ mask: identity,
4773
+ parse: (v) => Number(v)
4774
+ },
4775
+ "telegram.max_message_length": {
4776
+ type: "number",
4777
+ category: "Telegram",
4778
+ label: "Max Message Length",
4779
+ description: "Maximum message length in characters",
4780
+ sensitive: false,
4781
+ hotReload: "instant",
4782
+ validate: numberInRange(1, 32768),
4783
+ mask: identity,
4784
+ parse: (v) => Number(v)
4785
+ },
4786
+ "telegram.rate_limit_messages_per_second": {
4787
+ type: "number",
4788
+ category: "Telegram",
4789
+ label: "Rate Limit \u2014 Messages/sec",
4790
+ description: "Rate limit: messages per second (requires restart)",
4791
+ sensitive: false,
4792
+ hotReload: "restart",
4793
+ validate: numberInRange(0.1, 10),
4794
+ mask: identity,
4795
+ parse: (v) => Number(v)
4796
+ },
4797
+ "telegram.rate_limit_groups_per_minute": {
4798
+ type: "number",
4799
+ category: "Telegram",
4800
+ label: "Rate Limit \u2014 Groups/min",
4801
+ description: "Rate limit: groups per minute (requires restart)",
4802
+ sensitive: false,
4803
+ hotReload: "restart",
4804
+ validate: numberInRange(1, 60),
4805
+ mask: identity,
4806
+ parse: (v) => Number(v)
4807
+ },
4808
+ "telegram.admin_ids": {
4809
+ type: "array",
4810
+ itemType: "number",
4811
+ category: "Telegram",
4812
+ label: "Admin IDs",
4813
+ description: "Admin user IDs with elevated access",
4814
+ sensitive: false,
4815
+ hotReload: "instant",
4816
+ validate: positiveInteger,
4817
+ mask: identity,
4818
+ parse: (v) => Number(v)
4819
+ },
4820
+ "telegram.allow_from": {
4821
+ type: "array",
4822
+ itemType: "number",
4823
+ category: "Telegram",
4824
+ label: "Allowed Users",
4825
+ description: "User IDs allowed for DM access",
4826
+ sensitive: false,
4827
+ hotReload: "instant",
4828
+ validate: positiveInteger,
4829
+ mask: identity,
4830
+ parse: (v) => Number(v)
4831
+ },
4832
+ "telegram.group_allow_from": {
4833
+ type: "array",
4834
+ itemType: "number",
4835
+ category: "Telegram",
4836
+ label: "Allowed Groups",
4837
+ description: "Group IDs allowed for group access",
4838
+ sensitive: false,
4839
+ hotReload: "instant",
4840
+ validate: positiveInteger,
4841
+ mask: identity,
4842
+ parse: (v) => Number(v)
4843
+ },
3264
4844
  // ─── Embedding ─────────────────────────────────────────────────────
3265
4845
  "embedding.provider": {
3266
4846
  type: "enum",
3267
4847
  category: "Embedding",
4848
+ label: "Embedding Provider",
3268
4849
  description: "Embedding provider for RAG",
3269
4850
  sensitive: false,
4851
+ hotReload: "instant",
3270
4852
  options: ["local", "anthropic", "none"],
3271
4853
  validate: enumValidator(["local", "anthropic", "none"]),
3272
4854
  mask: identity,
3273
4855
  parse: identity
3274
4856
  },
4857
+ "embedding.model": {
4858
+ type: "string",
4859
+ category: "Embedding",
4860
+ label: "Embedding Model",
4861
+ description: "Embedding model ID (requires restart)",
4862
+ sensitive: false,
4863
+ hotReload: "restart",
4864
+ validate: noValidation,
4865
+ mask: identity,
4866
+ parse: identity
4867
+ },
3275
4868
  // ─── WebUI ─────────────────────────────────────────────────────────
3276
4869
  "webui.port": {
3277
4870
  type: "number",
3278
4871
  category: "WebUI",
4872
+ label: "WebUI Port",
3279
4873
  description: "HTTP server port (requires restart)",
3280
4874
  sensitive: false,
4875
+ hotReload: "restart",
3281
4876
  validate: numberInRange(1024, 65535),
3282
4877
  mask: identity,
3283
4878
  parse: (v) => Number(v)
@@ -3285,8 +4880,10 @@ var CONFIGURABLE_KEYS = {
3285
4880
  "webui.log_requests": {
3286
4881
  type: "boolean",
3287
4882
  category: "WebUI",
4883
+ label: "Log HTTP Requests",
3288
4884
  description: "Log all HTTP requests to console",
3289
4885
  sensitive: false,
4886
+ hotReload: "instant",
3290
4887
  validate: enumValidator(["true", "false"]),
3291
4888
  mask: identity,
3292
4889
  parse: (v) => v === "true"
@@ -3295,18 +4892,55 @@ var CONFIGURABLE_KEYS = {
3295
4892
  "deals.enabled": {
3296
4893
  type: "boolean",
3297
4894
  category: "Deals",
4895
+ label: "Deals Enabled",
3298
4896
  description: "Enable the deals/escrow module",
3299
4897
  sensitive: false,
4898
+ hotReload: "instant",
3300
4899
  validate: enumValidator(["true", "false"]),
3301
4900
  mask: identity,
3302
4901
  parse: (v) => v === "true"
3303
4902
  },
4903
+ "deals.expiry_seconds": {
4904
+ type: "number",
4905
+ category: "Deals",
4906
+ label: "Deal Expiry",
4907
+ description: "Deal expiry timeout in seconds",
4908
+ sensitive: false,
4909
+ hotReload: "instant",
4910
+ validate: numberInRange(10, 3600),
4911
+ mask: identity,
4912
+ parse: (v) => Number(v)
4913
+ },
4914
+ "deals.buy_max_floor_percent": {
4915
+ type: "number",
4916
+ category: "Deals",
4917
+ label: "Buy Max Floor %",
4918
+ description: "Maximum floor % for buy deals",
4919
+ sensitive: false,
4920
+ hotReload: "instant",
4921
+ validate: numberInRange(1, 100),
4922
+ mask: identity,
4923
+ parse: (v) => Number(v)
4924
+ },
4925
+ "deals.sell_min_floor_percent": {
4926
+ type: "number",
4927
+ category: "Deals",
4928
+ label: "Sell Min Floor %",
4929
+ description: "Minimum floor % for sell deals",
4930
+ sensitive: false,
4931
+ hotReload: "instant",
4932
+ validate: numberInRange(100, 500),
4933
+ mask: identity,
4934
+ parse: (v) => Number(v)
4935
+ },
3304
4936
  // ─── Developer ─────────────────────────────────────────────────────
3305
4937
  "dev.hot_reload": {
3306
4938
  type: "boolean",
3307
4939
  category: "Developer",
4940
+ label: "Hot Reload",
3308
4941
  description: "Watch ~/.teleton/plugins/ for live changes",
3309
4942
  sensitive: false,
4943
+ hotReload: "instant",
3310
4944
  validate: enumValidator(["true", "false"]),
3311
4945
  mask: identity,
3312
4946
  parse: (v) => v === "true"
@@ -3386,19 +5020,20 @@ export {
3386
5020
  validateReadPath,
3387
5021
  validateWritePath,
3388
5022
  validateDirectory,
3389
- appendToDailyLog,
3390
- writeSummaryToDailyLog,
3391
5023
  sanitizeForPrompt,
3392
5024
  sanitizeForContext,
3393
5025
  clearPromptCache,
3394
5026
  loadSoul,
3395
- buildSystemPrompt,
5027
+ TELEGRAM_SEND_TOOLS,
5028
+ getTokenUsage,
5029
+ AgentRuntime,
3396
5030
  writePluginSecret,
3397
5031
  deletePluginSecret,
3398
5032
  listPluginSecretKeys,
3399
5033
  toLong,
3400
5034
  randomLong,
3401
5035
  withBlockchainRetry,
5036
+ withTxLock,
3402
5037
  sendTon,
3403
5038
  formatTransactions,
3404
5039
  adaptPlugin,