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