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