teleton 0.8.0 → 0.8.2
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 +28 -11
- package/dist/{chunk-H36RFKRI.js → chunk-2IZU3REP.js} +572 -174
- package/dist/chunk-3UFPFWYP.js +12 -0
- package/dist/{chunk-NUGDTPE4.js → chunk-4L66JHQE.js} +2 -1
- package/dist/{chunk-TVRZJIZX.js → chunk-55SKE6YH.js} +4 -4
- package/dist/{setup-server-QXED3D2L.js → chunk-57URFK6M.js} +161 -210
- package/dist/chunk-5SEMA47R.js +75 -0
- package/dist/{chunk-JHYZYFZJ.js → chunk-7YKSXOQQ.js} +17 -2
- package/dist/{chunk-IJBWWQE4.js → chunk-C4NKJT2Z.js} +12 -0
- package/dist/{chunk-RQBAMUCV.js → chunk-GGXJLMOH.js} +1451 -743
- package/dist/{chunk-WIKM24GZ.js → chunk-H7MFXJZK.js} +7 -2
- package/dist/{chunk-U56QTM46.js → chunk-HEDJCLA6.js} +85 -44
- package/dist/{chunk-QVBSUYVX.js → chunk-J73TA3UM.js} +17 -9
- package/dist/{chunk-P36I6OIV.js → chunk-LC4TV3KL.js} +13 -2
- package/dist/{chunk-RCMD3U65.js → chunk-NQ6FZKCE.js} +13 -0
- package/dist/{chunk-SD4NLLYG.js → chunk-VYKW7FMV.js} +224 -93
- package/dist/chunk-W25Z7CM6.js +487 -0
- package/dist/{chunk-OJCLKU5Z.js → chunk-WFTC3JJW.js} +16 -0
- package/dist/{server-H3QA252W.js → chunk-XBSCYMKM.js} +369 -374
- package/dist/{chunk-PHSAHTK4.js → chunk-YOSUPUAJ.js} +75 -7
- package/dist/cli/index.js +67 -22
- package/dist/{client-LNZTDQSA.js → client-YOOHI776.js} +4 -4
- package/dist/{get-my-gifts-OMGKOEPM.js → get-my-gifts-Y7EN7RK4.js} +3 -3
- package/dist/index.js +15 -14
- package/dist/{memory-AS7WKGTW.js → memory-Q6EWGK2S.js} +7 -5
- package/dist/memory-hook-WUXJNVT5.js +18 -0
- package/dist/{migrate-POHWYEIW.js → migrate-WFU6COBN.js} +5 -5
- package/dist/server-GYZXKIKU.js +787 -0
- package/dist/server-YODFBZKG.js +392 -0
- package/dist/setup-server-IZBUOJRU.js +215 -0
- package/dist/{store-GAFULOOX.js → store-7M4XV6M5.js} +6 -6
- package/dist/{task-dependency-resolver-3FIKQ7Z6.js → task-dependency-resolver-L6UUMTHK.js} +3 -3
- package/dist/{task-executor-RUTFG6VG.js → task-executor-XBNJLUCS.js} +3 -3
- package/dist/{tasks-BEZ4QRI2.js → tasks-WQIKXDX5.js} +1 -1
- package/dist/{tool-adapter-IH5VGBOO.js → tool-adapter-IVX2XQJE.js} +1 -1
- package/dist/{tool-index-H3SHOJC3.js → tool-index-NYH57UWP.js} +9 -6
- package/dist/{transcript-IMNE6KU3.js → transcript-IM7G25OS.js} +2 -2
- package/dist/web/assets/index-BfYCdwLI.js +80 -0
- package/dist/web/assets/{index-BrVqauzj.css → index-DmlyQVhR.css} +1 -1
- package/dist/web/assets/{index.es-DkU1GvWU.js → index.es-DitvF-9H.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +14 -5
- package/dist/chunk-XBE4JB7C.js +0 -8
- package/dist/web/assets/index-DYeEkvJ6.js +0 -72
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
getWalletBalance,
|
|
8
8
|
invalidateTonClientCache,
|
|
9
9
|
loadWallet
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-7YKSXOQQ.js";
|
|
11
11
|
import {
|
|
12
12
|
getOrCreateSession,
|
|
13
13
|
getSession,
|
|
@@ -15,7 +15,15 @@ import {
|
|
|
15
15
|
resetSessionWithPolicy,
|
|
16
16
|
shouldResetSession,
|
|
17
17
|
updateSession
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-55SKE6YH.js";
|
|
19
|
+
import {
|
|
20
|
+
saveSessionMemory,
|
|
21
|
+
summarizeWithFallback
|
|
22
|
+
} from "./chunk-W25Z7CM6.js";
|
|
23
|
+
import {
|
|
24
|
+
getErrorMessage,
|
|
25
|
+
isHttpError
|
|
26
|
+
} from "./chunk-3UFPFWYP.js";
|
|
19
27
|
import {
|
|
20
28
|
Mi,
|
|
21
29
|
Mn,
|
|
@@ -26,41 +34,33 @@ import {
|
|
|
26
34
|
ut,
|
|
27
35
|
ye
|
|
28
36
|
} from "./chunk-7TECSLJ4.js";
|
|
29
|
-
import {
|
|
30
|
-
getErrorMessage
|
|
31
|
-
} from "./chunk-XBE4JB7C.js";
|
|
32
37
|
import {
|
|
33
38
|
chatWithContext,
|
|
34
39
|
getEffectiveApiKey,
|
|
35
40
|
getProviderModel,
|
|
36
|
-
getUtilityModel,
|
|
37
41
|
loadContextFromTranscript
|
|
38
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-J73TA3UM.js";
|
|
39
43
|
import {
|
|
40
44
|
getProviderMetadata,
|
|
41
45
|
getSupportedProviders
|
|
42
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-YOSUPUAJ.js";
|
|
43
47
|
import {
|
|
44
48
|
appendToTranscript,
|
|
45
49
|
archiveTranscript,
|
|
46
50
|
transcriptExists
|
|
47
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-LC4TV3KL.js";
|
|
48
52
|
import {
|
|
49
53
|
ContextBuilder,
|
|
50
54
|
createDbWrapper,
|
|
51
55
|
getDatabase,
|
|
52
56
|
migrateFromMainDb,
|
|
53
57
|
openModuleDb
|
|
54
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-VYKW7FMV.js";
|
|
55
59
|
import {
|
|
56
60
|
GECKOTERMINAL_API_URL,
|
|
57
61
|
tonapiFetch
|
|
58
62
|
} from "./chunk-VFA7QMCZ.js";
|
|
59
63
|
import {
|
|
60
|
-
ADAPTIVE_CHUNK_RATIO_BASE,
|
|
61
|
-
ADAPTIVE_CHUNK_RATIO_MIN,
|
|
62
|
-
ADAPTIVE_CHUNK_RATIO_TRIGGER,
|
|
63
|
-
CHARS_PER_TOKEN_ESTIMATE,
|
|
64
64
|
COMPACTION_KEEP_RECENT,
|
|
65
65
|
COMPACTION_MAX_MESSAGES,
|
|
66
66
|
COMPACTION_MAX_TOKENS_RATIO,
|
|
@@ -72,19 +72,18 @@ import {
|
|
|
72
72
|
DEFAULT_MAX_SUMMARY_TOKENS,
|
|
73
73
|
DEFAULT_MAX_TOKENS,
|
|
74
74
|
DEFAULT_SOFT_THRESHOLD_TOKENS,
|
|
75
|
-
|
|
75
|
+
EMBEDDING_QUERY_MAX_CHARS,
|
|
76
76
|
FALLBACK_SOFT_THRESHOLD_TOKENS,
|
|
77
77
|
MASKING_KEEP_RECENT_COUNT,
|
|
78
78
|
MAX_TOOL_RESULT_SIZE,
|
|
79
79
|
MEMORY_FLUSH_RECENT_MESSAGES,
|
|
80
|
-
OVERSIZED_MESSAGE_RATIO,
|
|
81
80
|
PAYMENT_TOLERANCE_RATIO,
|
|
82
81
|
RATE_LIMIT_MAX_RETRIES,
|
|
82
|
+
RESULT_TRUNCATION_KEEP_CHARS,
|
|
83
|
+
RESULT_TRUNCATION_THRESHOLD,
|
|
83
84
|
SERVER_ERROR_MAX_RETRIES,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
TOKEN_ESTIMATE_SAFETY_MARGIN
|
|
87
|
-
} from "./chunk-IJBWWQE4.js";
|
|
85
|
+
TOOL_CONCURRENCY_LIMIT
|
|
86
|
+
} from "./chunk-C4NKJT2Z.js";
|
|
88
87
|
import {
|
|
89
88
|
fetchWithTimeout
|
|
90
89
|
} from "./chunk-XQUHC3JZ.js";
|
|
@@ -105,7 +104,7 @@ import {
|
|
|
105
104
|
} from "./chunk-EYWNOHMJ.js";
|
|
106
105
|
import {
|
|
107
106
|
createLogger
|
|
108
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-NQ6FZKCE.js";
|
|
109
108
|
|
|
110
109
|
// src/config/loader.ts
|
|
111
110
|
import { readFileSync, existsSync, writeFileSync, mkdirSync } from "fs";
|
|
@@ -191,6 +190,17 @@ Run 'teleton setup' to create one.`);
|
|
|
191
190
|
);
|
|
192
191
|
}
|
|
193
192
|
}
|
|
193
|
+
if (process.env.TELETON_API_ENABLED) {
|
|
194
|
+
if (!config.api) config.api = { enabled: false, port: 7778, key_hash: "", allowed_ips: [] };
|
|
195
|
+
config.api.enabled = process.env.TELETON_API_ENABLED === "true";
|
|
196
|
+
}
|
|
197
|
+
if (process.env.TELETON_API_PORT) {
|
|
198
|
+
const port = parseInt(process.env.TELETON_API_PORT, 10);
|
|
199
|
+
if (!isNaN(port) && port >= 1024 && port <= 65535) {
|
|
200
|
+
if (!config.api) config.api = { enabled: false, port: 7778, key_hash: "", allowed_ips: [] };
|
|
201
|
+
config.api.port = port;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
194
204
|
if (process.env.TELETON_BASE_URL) {
|
|
195
205
|
try {
|
|
196
206
|
new URL(process.env.TELETON_BASE_URL);
|
|
@@ -364,7 +374,7 @@ function appendToDailyLog(content, date = /* @__PURE__ */ new Date()) {
|
|
|
364
374
|
const header = `# Daily Log - ${formatDate(date)}
|
|
365
375
|
|
|
366
376
|
`;
|
|
367
|
-
appendFileSync(logPath, header, "utf-8");
|
|
377
|
+
appendFileSync(logPath, header, { encoding: "utf-8", mode: 384 });
|
|
368
378
|
}
|
|
369
379
|
const entry = `## ${timestamp}
|
|
370
380
|
|
|
@@ -739,398 +749,6 @@ ${body}`;
|
|
|
739
749
|
|
|
740
750
|
// src/memory/compaction.ts
|
|
741
751
|
import { randomUUID } from "crypto";
|
|
742
|
-
|
|
743
|
-
// src/memory/ai-summarization.ts
|
|
744
|
-
import {
|
|
745
|
-
complete
|
|
746
|
-
} from "@mariozechner/pi-ai";
|
|
747
|
-
var log3 = createLogger("Memory");
|
|
748
|
-
function estimateMessageTokens(content) {
|
|
749
|
-
return Math.ceil(content.length / CHARS_PER_TOKEN_ESTIMATE * TOKEN_ESTIMATE_SAFETY_MARGIN);
|
|
750
|
-
}
|
|
751
|
-
function splitMessagesByTokens(messages, maxChunkTokens) {
|
|
752
|
-
if (messages.length === 0) {
|
|
753
|
-
return [];
|
|
754
|
-
}
|
|
755
|
-
const chunks = [];
|
|
756
|
-
let currentChunk = [];
|
|
757
|
-
let currentTokens = 0;
|
|
758
|
-
for (const message of messages) {
|
|
759
|
-
const content = extractMessageContent(message);
|
|
760
|
-
const messageTokens = estimateMessageTokens(content);
|
|
761
|
-
if (currentChunk.length > 0 && currentTokens + messageTokens > maxChunkTokens) {
|
|
762
|
-
chunks.push(currentChunk);
|
|
763
|
-
currentChunk = [];
|
|
764
|
-
currentTokens = 0;
|
|
765
|
-
}
|
|
766
|
-
currentChunk.push(message);
|
|
767
|
-
currentTokens += messageTokens;
|
|
768
|
-
if (messageTokens > maxChunkTokens && currentChunk.length === 1) {
|
|
769
|
-
chunks.push(currentChunk);
|
|
770
|
-
currentChunk = [];
|
|
771
|
-
currentTokens = 0;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
if (currentChunk.length > 0) {
|
|
775
|
-
chunks.push(currentChunk);
|
|
776
|
-
}
|
|
777
|
-
return chunks;
|
|
778
|
-
}
|
|
779
|
-
function extractMessageContent(message) {
|
|
780
|
-
if (message.role === "user") {
|
|
781
|
-
return typeof message.content === "string" ? message.content : "[complex content]";
|
|
782
|
-
} else if (message.role === "assistant") {
|
|
783
|
-
return message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
784
|
-
}
|
|
785
|
-
return "";
|
|
786
|
-
}
|
|
787
|
-
function formatMessagesForSummary(messages) {
|
|
788
|
-
const formatted = [];
|
|
789
|
-
for (const msg of messages) {
|
|
790
|
-
if (msg.role === "user") {
|
|
791
|
-
const content = typeof msg.content === "string" ? msg.content : "[complex]";
|
|
792
|
-
const bodyMatch = content.match(/\] (.+)/s);
|
|
793
|
-
const body = bodyMatch ? bodyMatch[1] : content;
|
|
794
|
-
formatted.push(`User: ${body}`);
|
|
795
|
-
} else if (msg.role === "assistant") {
|
|
796
|
-
const textBlocks = msg.content.filter((b) => b.type === "text");
|
|
797
|
-
if (textBlocks.length > 0) {
|
|
798
|
-
const text = textBlocks.map((b) => b.text).join("\n");
|
|
799
|
-
formatted.push(`Assistant: ${text}`);
|
|
800
|
-
}
|
|
801
|
-
const toolCalls = msg.content.filter((b) => b.type === "toolCall");
|
|
802
|
-
if (toolCalls.length > 0) {
|
|
803
|
-
const toolNames = toolCalls.map((b) => b.name).join(", ");
|
|
804
|
-
formatted.push(`[Used tools: ${toolNames}]`);
|
|
805
|
-
}
|
|
806
|
-
} else if (msg.role === "toolResult") {
|
|
807
|
-
formatted.push(`[Tool result: ${msg.toolName}]`);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
return formatted.join("\n\n");
|
|
811
|
-
}
|
|
812
|
-
function isOversizedForSummary(message, contextWindow) {
|
|
813
|
-
const content = extractMessageContent(message);
|
|
814
|
-
const tokens = estimateMessageTokens(content);
|
|
815
|
-
return tokens > contextWindow * OVERSIZED_MESSAGE_RATIO;
|
|
816
|
-
}
|
|
817
|
-
function computeAdaptiveChunkRatio(messages, contextWindow) {
|
|
818
|
-
const BASE_CHUNK_RATIO = ADAPTIVE_CHUNK_RATIO_BASE;
|
|
819
|
-
const MIN_CHUNK_RATIO = ADAPTIVE_CHUNK_RATIO_MIN;
|
|
820
|
-
if (messages.length === 0) {
|
|
821
|
-
return BASE_CHUNK_RATIO;
|
|
822
|
-
}
|
|
823
|
-
let totalTokens = 0;
|
|
824
|
-
for (const msg of messages) {
|
|
825
|
-
const content = extractMessageContent(msg);
|
|
826
|
-
totalTokens += estimateMessageTokens(content);
|
|
827
|
-
}
|
|
828
|
-
const avgTokens = totalTokens / messages.length;
|
|
829
|
-
const avgRatio = avgTokens / contextWindow;
|
|
830
|
-
if (avgRatio > ADAPTIVE_CHUNK_RATIO_TRIGGER) {
|
|
831
|
-
const reduction = Math.min(avgRatio * 2, BASE_CHUNK_RATIO - MIN_CHUNK_RATIO);
|
|
832
|
-
return Math.max(MIN_CHUNK_RATIO, BASE_CHUNK_RATIO - reduction);
|
|
833
|
-
}
|
|
834
|
-
return BASE_CHUNK_RATIO;
|
|
835
|
-
}
|
|
836
|
-
async function summarizeViaClaude(params) {
|
|
837
|
-
const provider = params.provider || "anthropic";
|
|
838
|
-
const model = getUtilityModel(provider, params.utilityModel);
|
|
839
|
-
const maxTokens = params.maxSummaryTokens ?? DEFAULT_SUMMARY_FALLBACK_TOKENS;
|
|
840
|
-
const formatted = formatMessagesForSummary(params.messages);
|
|
841
|
-
if (!formatted.trim()) {
|
|
842
|
-
return "No conversation content to summarize.";
|
|
843
|
-
}
|
|
844
|
-
const defaultInstructions = `Summarize this conversation concisely. Focus on:
|
|
845
|
-
- Key decisions made
|
|
846
|
-
- Action items and TODOs
|
|
847
|
-
- Open questions
|
|
848
|
-
- Important context and constraints
|
|
849
|
-
- Technical details that matter
|
|
850
|
-
|
|
851
|
-
Be specific but concise. Preserve critical information.`;
|
|
852
|
-
const instructions = params.customInstructions ? `${defaultInstructions}
|
|
853
|
-
|
|
854
|
-
Additional focus:
|
|
855
|
-
${params.customInstructions}` : defaultInstructions;
|
|
856
|
-
try {
|
|
857
|
-
const context = {
|
|
858
|
-
messages: [
|
|
859
|
-
{
|
|
860
|
-
role: "user",
|
|
861
|
-
content: `${instructions}
|
|
862
|
-
|
|
863
|
-
Conversation:
|
|
864
|
-
${formatted}`,
|
|
865
|
-
timestamp: Date.now()
|
|
866
|
-
}
|
|
867
|
-
]
|
|
868
|
-
};
|
|
869
|
-
const response = await complete(model, context, {
|
|
870
|
-
apiKey: params.apiKey,
|
|
871
|
-
maxTokens
|
|
872
|
-
});
|
|
873
|
-
const textContent = response.content.find((block) => block.type === "text");
|
|
874
|
-
const summary = textContent?.type === "text" ? textContent.text : "";
|
|
875
|
-
return summary.trim() || "Unable to generate summary.";
|
|
876
|
-
} catch (error) {
|
|
877
|
-
log3.error({ err: error }, "Summarization error");
|
|
878
|
-
throw new Error(`Summarization failed: ${getErrorMessage(error)}`);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
async function summarizeInChunks(params) {
|
|
882
|
-
if (params.messages.length === 0) {
|
|
883
|
-
return {
|
|
884
|
-
summary: "No messages to summarize.",
|
|
885
|
-
tokensUsed: 0,
|
|
886
|
-
chunksProcessed: 0
|
|
887
|
-
};
|
|
888
|
-
}
|
|
889
|
-
const chunks = splitMessagesByTokens(params.messages, params.maxChunkTokens);
|
|
890
|
-
log3.info(`Splitting into ${chunks.length} chunks for summarization`);
|
|
891
|
-
if (chunks.length === 1) {
|
|
892
|
-
const summary = await summarizeViaClaude({
|
|
893
|
-
messages: chunks[0],
|
|
894
|
-
apiKey: params.apiKey,
|
|
895
|
-
maxSummaryTokens: params.maxSummaryTokens,
|
|
896
|
-
customInstructions: params.customInstructions,
|
|
897
|
-
provider: params.provider,
|
|
898
|
-
utilityModel: params.utilityModel
|
|
899
|
-
});
|
|
900
|
-
return {
|
|
901
|
-
summary,
|
|
902
|
-
tokensUsed: estimateMessageTokens(summary),
|
|
903
|
-
chunksProcessed: 1
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
const partialSummaries = [];
|
|
907
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
908
|
-
log3.info(`Summarizing chunk ${i + 1}/${chunks.length} (${chunks[i].length} messages)`);
|
|
909
|
-
const partial = await summarizeViaClaude({
|
|
910
|
-
messages: chunks[i],
|
|
911
|
-
apiKey: params.apiKey,
|
|
912
|
-
maxSummaryTokens: Math.floor(
|
|
913
|
-
(params.maxSummaryTokens ?? DEFAULT_SUMMARY_FALLBACK_TOKENS) / 2
|
|
914
|
-
),
|
|
915
|
-
customInstructions: params.customInstructions,
|
|
916
|
-
provider: params.provider,
|
|
917
|
-
utilityModel: params.utilityModel
|
|
918
|
-
});
|
|
919
|
-
partialSummaries.push(partial);
|
|
920
|
-
}
|
|
921
|
-
log3.info(`Merging ${partialSummaries.length} partial summaries`);
|
|
922
|
-
const provider = params.provider || "anthropic";
|
|
923
|
-
const model = getUtilityModel(provider, params.utilityModel);
|
|
924
|
-
const mergeContext = {
|
|
925
|
-
messages: [
|
|
926
|
-
{
|
|
927
|
-
role: "user",
|
|
928
|
-
content: `Merge these partial conversation summaries into one cohesive summary.
|
|
929
|
-
Preserve all key decisions, action items, open questions, and important context.
|
|
930
|
-
Do not add new information - only synthesize what's provided.
|
|
931
|
-
|
|
932
|
-
Partial summaries:
|
|
933
|
-
|
|
934
|
-
${partialSummaries.map((s, i) => `Part ${i + 1}:
|
|
935
|
-
${s}`).join("\n\n---\n\n")}`,
|
|
936
|
-
timestamp: Date.now()
|
|
937
|
-
}
|
|
938
|
-
]
|
|
939
|
-
};
|
|
940
|
-
const mergeResponse = await complete(model, mergeContext, {
|
|
941
|
-
apiKey: params.apiKey,
|
|
942
|
-
maxTokens: params.maxSummaryTokens ?? DEFAULT_SUMMARY_FALLBACK_TOKENS
|
|
943
|
-
});
|
|
944
|
-
const textContent = mergeResponse.content.find((block) => block.type === "text");
|
|
945
|
-
const merged = textContent?.type === "text" ? textContent.text : "";
|
|
946
|
-
return {
|
|
947
|
-
summary: merged.trim() || "Unable to merge summaries.",
|
|
948
|
-
tokensUsed: estimateMessageTokens(merged),
|
|
949
|
-
chunksProcessed: chunks.length
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
async function summarizeWithFallback(params) {
|
|
953
|
-
if (params.messages.length === 0) {
|
|
954
|
-
return {
|
|
955
|
-
summary: "No messages to summarize.",
|
|
956
|
-
tokensUsed: 0,
|
|
957
|
-
chunksProcessed: 0
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
const chunkRatio = computeAdaptiveChunkRatio(params.messages, params.contextWindow);
|
|
961
|
-
const maxChunkTokens = Math.floor(params.contextWindow * chunkRatio);
|
|
962
|
-
log3.info(
|
|
963
|
-
`AI Summarization: ${params.messages.length} messages, chunk ratio: ${(chunkRatio * 100).toFixed(0)}%`
|
|
964
|
-
);
|
|
965
|
-
try {
|
|
966
|
-
return await summarizeInChunks({
|
|
967
|
-
messages: params.messages,
|
|
968
|
-
apiKey: params.apiKey,
|
|
969
|
-
maxChunkTokens,
|
|
970
|
-
maxSummaryTokens: params.maxSummaryTokens,
|
|
971
|
-
customInstructions: params.customInstructions,
|
|
972
|
-
provider: params.provider,
|
|
973
|
-
utilityModel: params.utilityModel
|
|
974
|
-
});
|
|
975
|
-
} catch (fullError) {
|
|
976
|
-
log3.warn(
|
|
977
|
-
`Full summarization failed: ${fullError instanceof Error ? fullError.message : String(fullError)}`
|
|
978
|
-
);
|
|
979
|
-
}
|
|
980
|
-
const smallMessages = [];
|
|
981
|
-
const oversizedNotes = [];
|
|
982
|
-
for (const msg of params.messages) {
|
|
983
|
-
if (isOversizedForSummary(msg, params.contextWindow)) {
|
|
984
|
-
const content = extractMessageContent(msg);
|
|
985
|
-
const tokens = estimateMessageTokens(content);
|
|
986
|
-
oversizedNotes.push(
|
|
987
|
-
`[Large ${msg.role} message (~${Math.round(tokens / 1e3)}K tokens) omitted from summary]`
|
|
988
|
-
);
|
|
989
|
-
} else {
|
|
990
|
-
smallMessages.push(msg);
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
log3.info(
|
|
994
|
-
`Fallback: Processing ${smallMessages.length} messages, skipping ${oversizedNotes.length} oversized`
|
|
995
|
-
);
|
|
996
|
-
if (smallMessages.length > 0) {
|
|
997
|
-
try {
|
|
998
|
-
const result = await summarizeInChunks({
|
|
999
|
-
messages: smallMessages,
|
|
1000
|
-
apiKey: params.apiKey,
|
|
1001
|
-
maxChunkTokens,
|
|
1002
|
-
maxSummaryTokens: params.maxSummaryTokens,
|
|
1003
|
-
customInstructions: params.customInstructions,
|
|
1004
|
-
provider: params.provider,
|
|
1005
|
-
utilityModel: params.utilityModel
|
|
1006
|
-
});
|
|
1007
|
-
const notes = oversizedNotes.length > 0 ? `
|
|
1008
|
-
|
|
1009
|
-
${oversizedNotes.join("\n")}` : "";
|
|
1010
|
-
return {
|
|
1011
|
-
summary: result.summary + notes,
|
|
1012
|
-
tokensUsed: result.tokensUsed,
|
|
1013
|
-
chunksProcessed: result.chunksProcessed
|
|
1014
|
-
};
|
|
1015
|
-
} catch (partialError) {
|
|
1016
|
-
log3.warn(
|
|
1017
|
-
`Partial summarization also failed: ${partialError instanceof Error ? partialError.message : String(partialError)}`
|
|
1018
|
-
);
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
const note = `Context contained ${params.messages.length} messages (${oversizedNotes.length} were oversized). AI summarization unavailable due to size constraints. Recent conversation history was preserved.`;
|
|
1022
|
-
return {
|
|
1023
|
-
summary: note,
|
|
1024
|
-
tokensUsed: estimateMessageTokens(note),
|
|
1025
|
-
chunksProcessed: 0
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
// src/session/memory-hook.ts
|
|
1030
|
-
import { writeFile, mkdir } from "fs/promises";
|
|
1031
|
-
import { join as join3 } from "path";
|
|
1032
|
-
import { complete as complete2 } from "@mariozechner/pi-ai";
|
|
1033
|
-
var log4 = createLogger("Session");
|
|
1034
|
-
async function generateSlugViaClaude(params) {
|
|
1035
|
-
const provider = params.provider || "anthropic";
|
|
1036
|
-
const model = getUtilityModel(provider, params.utilityModel);
|
|
1037
|
-
const formatted = formatMessagesForSummary(params.messages.slice(-SESSION_SLUG_RECENT_MESSAGES));
|
|
1038
|
-
if (!formatted.trim()) {
|
|
1039
|
-
return "empty-session";
|
|
1040
|
-
}
|
|
1041
|
-
try {
|
|
1042
|
-
const context = {
|
|
1043
|
-
messages: [
|
|
1044
|
-
{
|
|
1045
|
-
role: "user",
|
|
1046
|
-
content: `Generate a short, descriptive slug (2-4 words, kebab-case) for this conversation.
|
|
1047
|
-
Examples: "gift-transfer-fix", "context-overflow-debug", "telegram-integration"
|
|
1048
|
-
|
|
1049
|
-
Conversation:
|
|
1050
|
-
${formatted}
|
|
1051
|
-
|
|
1052
|
-
Slug:`,
|
|
1053
|
-
timestamp: Date.now()
|
|
1054
|
-
}
|
|
1055
|
-
]
|
|
1056
|
-
};
|
|
1057
|
-
const response = await complete2(model, context, {
|
|
1058
|
-
apiKey: params.apiKey,
|
|
1059
|
-
maxTokens: SESSION_SLUG_MAX_TOKENS
|
|
1060
|
-
});
|
|
1061
|
-
const textContent = response.content.find((block) => block.type === "text");
|
|
1062
|
-
const slug = textContent?.type === "text" ? textContent.text.trim() : "";
|
|
1063
|
-
return slug.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 50) || "session";
|
|
1064
|
-
} catch (error) {
|
|
1065
|
-
log4.warn({ err: error }, "Slug generation failed, using fallback");
|
|
1066
|
-
const now = /* @__PURE__ */ new Date();
|
|
1067
|
-
return `session-${now.getHours().toString().padStart(2, "0")}${now.getMinutes().toString().padStart(2, "0")}`;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
async function saveSessionMemory(params) {
|
|
1071
|
-
try {
|
|
1072
|
-
const { TELETON_ROOT: TELETON_ROOT2 } = await import("./paths-XA2RJH4S.js");
|
|
1073
|
-
const memoryDir = join3(TELETON_ROOT2, "memory");
|
|
1074
|
-
await mkdir(memoryDir, { recursive: true });
|
|
1075
|
-
const now = /* @__PURE__ */ new Date();
|
|
1076
|
-
const dateStr = now.toISOString().split("T")[0];
|
|
1077
|
-
log4.info("Generating semantic slug for session memory...");
|
|
1078
|
-
const slug = await generateSlugViaClaude({
|
|
1079
|
-
messages: params.context.messages,
|
|
1080
|
-
apiKey: params.apiKey,
|
|
1081
|
-
provider: params.provider,
|
|
1082
|
-
utilityModel: params.utilityModel
|
|
1083
|
-
});
|
|
1084
|
-
const filename = `${dateStr}-${slug}.md`;
|
|
1085
|
-
const filepath = join3(memoryDir, filename);
|
|
1086
|
-
const timeStr = now.toISOString().split("T")[1].split(".")[0];
|
|
1087
|
-
log4.info("Generating session summary...");
|
|
1088
|
-
let summary;
|
|
1089
|
-
try {
|
|
1090
|
-
summary = await summarizeViaClaude({
|
|
1091
|
-
messages: params.context.messages,
|
|
1092
|
-
apiKey: params.apiKey,
|
|
1093
|
-
maxSummaryTokens: DEFAULT_MAX_SUMMARY_TOKENS,
|
|
1094
|
-
customInstructions: "Summarize this session comprehensively. Include key topics, decisions made, problems solved, and important context.",
|
|
1095
|
-
provider: params.provider,
|
|
1096
|
-
utilityModel: params.utilityModel
|
|
1097
|
-
});
|
|
1098
|
-
} catch (error) {
|
|
1099
|
-
log4.warn({ err: error }, "Session summary generation failed");
|
|
1100
|
-
summary = `Session contained ${params.context.messages.length} messages. Summary generation failed.`;
|
|
1101
|
-
}
|
|
1102
|
-
const content = `# Session Memory: ${dateStr} ${timeStr} UTC
|
|
1103
|
-
|
|
1104
|
-
## Metadata
|
|
1105
|
-
|
|
1106
|
-
- **Old Session ID**: \`${params.oldSessionId}\`
|
|
1107
|
-
- **New Session ID**: \`${params.newSessionId}\`
|
|
1108
|
-
- **Chat ID**: \`${params.chatId}\`
|
|
1109
|
-
- **Timestamp**: ${now.toISOString()}
|
|
1110
|
-
- **Message Count**: ${params.context.messages.length}
|
|
1111
|
-
|
|
1112
|
-
## Session Summary
|
|
1113
|
-
|
|
1114
|
-
${summary}
|
|
1115
|
-
|
|
1116
|
-
## Context
|
|
1117
|
-
|
|
1118
|
-
This session was compacted and migrated to a new session ID. The summary above preserves key information for continuity.
|
|
1119
|
-
|
|
1120
|
-
---
|
|
1121
|
-
|
|
1122
|
-
*Generated automatically by Teleton-AI session memory hook*
|
|
1123
|
-
`;
|
|
1124
|
-
await writeFile(filepath, content, "utf-8");
|
|
1125
|
-
const relPath = filepath.replace(TELETON_ROOT2, "~/.teleton");
|
|
1126
|
-
log4.info(`Session memory saved: ${relPath}`);
|
|
1127
|
-
} catch (error) {
|
|
1128
|
-
log4.error({ err: error }, "Failed to save session memory");
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
// src/memory/compaction.ts
|
|
1133
|
-
import { encodingForModel } from "js-tiktoken";
|
|
1134
752
|
var DEFAULT_COMPACTION_CONFIG = {
|
|
1135
753
|
enabled: true,
|
|
1136
754
|
maxMessages: COMPACTION_MAX_MESSAGES,
|
|
@@ -1139,7 +757,7 @@ var DEFAULT_COMPACTION_CONFIG = {
|
|
|
1139
757
|
memoryFlushEnabled: true,
|
|
1140
758
|
softThresholdTokens: DEFAULT_SOFT_THRESHOLD_TOKENS
|
|
1141
759
|
};
|
|
1142
|
-
var
|
|
760
|
+
var log3 = createLogger("Memory");
|
|
1143
761
|
function estimateContextTokens(context) {
|
|
1144
762
|
let charCount = 0;
|
|
1145
763
|
if (context.systemPrompt) {
|
|
@@ -1171,7 +789,7 @@ function shouldFlushMemory(context, config, tokenCount) {
|
|
|
1171
789
|
const tokens = tokenCount ?? estimateContextTokens(context);
|
|
1172
790
|
const softThreshold = config.softThresholdTokens ?? FALLBACK_SOFT_THRESHOLD_TOKENS;
|
|
1173
791
|
if (tokens >= softThreshold) {
|
|
1174
|
-
|
|
792
|
+
log3.info(`Memory flush needed: ~${tokens} tokens (soft threshold: ${softThreshold})`);
|
|
1175
793
|
return true;
|
|
1176
794
|
}
|
|
1177
795
|
return false;
|
|
@@ -1193,7 +811,7 @@ function flushMemoryToDailyLog(context) {
|
|
|
1193
811
|
}
|
|
1194
812
|
}
|
|
1195
813
|
writeSummaryToDailyLog(summary.join("\n"));
|
|
1196
|
-
|
|
814
|
+
log3.info(`Memory flushed to daily log`);
|
|
1197
815
|
}
|
|
1198
816
|
function shouldCompact(context, config, tokenCount) {
|
|
1199
817
|
if (!config.enabled) {
|
|
@@ -1201,13 +819,13 @@ function shouldCompact(context, config, tokenCount) {
|
|
|
1201
819
|
}
|
|
1202
820
|
const messageCount = context.messages.length;
|
|
1203
821
|
if (config.maxMessages && messageCount >= config.maxMessages) {
|
|
1204
|
-
|
|
822
|
+
log3.info(`Compaction needed: ${messageCount} messages (max: ${config.maxMessages})`);
|
|
1205
823
|
return true;
|
|
1206
824
|
}
|
|
1207
825
|
if (config.maxTokens) {
|
|
1208
826
|
const tokens = tokenCount ?? estimateContextTokens(context);
|
|
1209
827
|
if (tokens >= config.maxTokens) {
|
|
1210
|
-
|
|
828
|
+
log3.info(`Compaction needed: ~${tokens} tokens (max: ${config.maxTokens})`);
|
|
1211
829
|
return true;
|
|
1212
830
|
}
|
|
1213
831
|
}
|
|
@@ -1253,12 +871,12 @@ async function compactContext(context, config, apiKey, provider, utilityModel) {
|
|
|
1253
871
|
iterations++;
|
|
1254
872
|
}
|
|
1255
873
|
if (hasOrphanedToolResults(context.messages.slice(cutIndex))) {
|
|
1256
|
-
|
|
874
|
+
log3.warn(`Compaction: couldn't find clean cut point, keeping all messages`);
|
|
1257
875
|
return context;
|
|
1258
876
|
}
|
|
1259
877
|
const recentMessages = context.messages.slice(cutIndex);
|
|
1260
878
|
const oldMessages = context.messages.slice(0, cutIndex);
|
|
1261
|
-
|
|
879
|
+
log3.info(
|
|
1262
880
|
`Compacting ${oldMessages.length} old messages, keeping ${recentMessages.length} recent (cut at clean boundary)`
|
|
1263
881
|
);
|
|
1264
882
|
try {
|
|
@@ -1288,7 +906,7 @@ Keep each section concise. Omit a section if empty. Preserve specific names, num
|
|
|
1288
906
|
provider,
|
|
1289
907
|
utilityModel
|
|
1290
908
|
});
|
|
1291
|
-
|
|
909
|
+
log3.info(`AI Summary: ${result.tokensUsed} tokens, ${result.chunksProcessed} chunks processed`);
|
|
1292
910
|
const summaryText = `[Auto-compacted ${oldMessages.length} messages]
|
|
1293
911
|
|
|
1294
912
|
${result.summary}`;
|
|
@@ -1302,7 +920,7 @@ ${result.summary}`;
|
|
|
1302
920
|
messages: [summaryMessage, ...recentMessages]
|
|
1303
921
|
};
|
|
1304
922
|
} catch (error) {
|
|
1305
|
-
|
|
923
|
+
log3.error({ err: error }, "AI summarization failed, using fallback");
|
|
1306
924
|
const summaryText = `[Auto-compacted: ${oldMessages.length} earlier messages from this conversation]`;
|
|
1307
925
|
const summaryMessage = {
|
|
1308
926
|
role: "user",
|
|
@@ -1317,7 +935,7 @@ ${result.summary}`;
|
|
|
1317
935
|
}
|
|
1318
936
|
async function compactAndSaveTranscript(sessionId, context, config, apiKey, chatId, provider, utilityModel) {
|
|
1319
937
|
const newSessionId = randomUUID();
|
|
1320
|
-
|
|
938
|
+
log3.info(`Creating compacted transcript: ${sessionId} \u2192 ${newSessionId}`);
|
|
1321
939
|
if (chatId) {
|
|
1322
940
|
await saveSessionMemory({
|
|
1323
941
|
oldSessionId: sessionId,
|
|
@@ -1351,7 +969,7 @@ var CompactionManager = class {
|
|
|
1351
969
|
if (this.config.memoryFlushEnabled) {
|
|
1352
970
|
flushMemoryToDailyLog(context);
|
|
1353
971
|
}
|
|
1354
|
-
|
|
972
|
+
log3.info(`Auto-compacting session ${sessionId}`);
|
|
1355
973
|
const newSessionId = await compactAndSaveTranscript(
|
|
1356
974
|
sessionId,
|
|
1357
975
|
context,
|
|
@@ -1361,7 +979,7 @@ var CompactionManager = class {
|
|
|
1361
979
|
provider,
|
|
1362
980
|
utilityModel
|
|
1363
981
|
);
|
|
1364
|
-
|
|
982
|
+
log3.info(`Compaction complete: ${newSessionId}`);
|
|
1365
983
|
return newSessionId;
|
|
1366
984
|
}
|
|
1367
985
|
updateConfig(config) {
|
|
@@ -1375,62 +993,125 @@ var CompactionManager = class {
|
|
|
1375
993
|
// src/memory/observation-masking.ts
|
|
1376
994
|
var DEFAULT_MASKING_CONFIG = {
|
|
1377
995
|
keepRecentCount: MASKING_KEEP_RECENT_COUNT,
|
|
1378
|
-
keepErrorResults: true
|
|
996
|
+
keepErrorResults: true,
|
|
997
|
+
truncationThreshold: RESULT_TRUNCATION_THRESHOLD,
|
|
998
|
+
truncationKeepChars: RESULT_TRUNCATION_KEEP_CHARS
|
|
1379
999
|
};
|
|
1380
1000
|
var isCocoonToolResult = (msg) => msg.role === "user" && Array.isArray(msg.content) && msg.content.some((c2) => c2.type === "text" && c2.text.includes("<tool_response>"));
|
|
1381
|
-
function
|
|
1001
|
+
function isExempt(toolMsg, config, toolRegistry) {
|
|
1002
|
+
if (config.keepErrorResults && toolMsg.isError) return true;
|
|
1003
|
+
if (toolRegistry && toolRegistry.getToolCategory(toolMsg.toolName) === "data-bearing")
|
|
1004
|
+
return true;
|
|
1005
|
+
return false;
|
|
1006
|
+
}
|
|
1007
|
+
function truncateToolResult(text, keepChars) {
|
|
1008
|
+
try {
|
|
1009
|
+
const parsed = JSON.parse(text);
|
|
1010
|
+
if (parsed.data?.summary) {
|
|
1011
|
+
return JSON.stringify({
|
|
1012
|
+
success: parsed.success,
|
|
1013
|
+
data: { summary: parsed.data.summary, _truncated: true }
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
if (parsed.data?.message) {
|
|
1017
|
+
return JSON.stringify({
|
|
1018
|
+
success: parsed.success,
|
|
1019
|
+
data: { summary: parsed.data.message, _truncated: true }
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
} catch {
|
|
1023
|
+
}
|
|
1024
|
+
return text.slice(0, keepChars) + `
|
|
1025
|
+
...[truncated, original: ${text.length} chars]`;
|
|
1026
|
+
}
|
|
1027
|
+
function maskOldToolResults(messages, options) {
|
|
1028
|
+
const config = options?.config ?? DEFAULT_MASKING_CONFIG;
|
|
1029
|
+
const toolRegistry = options?.toolRegistry;
|
|
1030
|
+
const iterStart = options?.currentIterationStartIndex;
|
|
1382
1031
|
const toolResults = messages.map((msg, index) => ({ msg, index })).filter(({ msg }) => msg.role === "toolResult" || isCocoonToolResult(msg));
|
|
1383
|
-
|
|
1032
|
+
const needsMasking = toolResults.length > config.keepRecentCount;
|
|
1033
|
+
const needsTruncation = iterStart !== void 0 && config.truncationThreshold > 0;
|
|
1034
|
+
if (!needsMasking && !needsTruncation) {
|
|
1384
1035
|
return messages;
|
|
1385
1036
|
}
|
|
1386
|
-
const toMask = toolResults.slice(0, -config.keepRecentCount);
|
|
1387
1037
|
const result = [...messages];
|
|
1388
|
-
|
|
1389
|
-
|
|
1038
|
+
if (needsMasking) {
|
|
1039
|
+
const toMask = toolResults.slice(0, -config.keepRecentCount);
|
|
1040
|
+
for (const { msg, index } of toMask) {
|
|
1041
|
+
if (isCocoonToolResult(msg)) {
|
|
1042
|
+
result[index] = {
|
|
1043
|
+
...msg,
|
|
1044
|
+
content: [{ type: "text", text: "[Tool response masked]" }]
|
|
1045
|
+
};
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
const toolMsg = msg;
|
|
1049
|
+
if (isExempt(toolMsg, config, toolRegistry)) continue;
|
|
1050
|
+
let summaryText = "";
|
|
1051
|
+
try {
|
|
1052
|
+
const textBlock = toolMsg.content.find((c2) => c2.type === "text");
|
|
1053
|
+
if (textBlock) {
|
|
1054
|
+
const parsed = JSON.parse(textBlock.text);
|
|
1055
|
+
if (parsed.data?.summary) {
|
|
1056
|
+
summaryText = ` - ${parsed.data.summary}`;
|
|
1057
|
+
} else if (parsed.data?.message) {
|
|
1058
|
+
summaryText = ` - ${parsed.data.message}`;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
} catch {
|
|
1062
|
+
}
|
|
1390
1063
|
result[index] = {
|
|
1391
|
-
...
|
|
1392
|
-
content: [
|
|
1064
|
+
...toolMsg,
|
|
1065
|
+
content: [
|
|
1066
|
+
{
|
|
1067
|
+
type: "text",
|
|
1068
|
+
text: `[Tool: ${toolMsg.toolName} - ${toolMsg.isError ? "ERROR" : "OK"}${summaryText}]`
|
|
1069
|
+
}
|
|
1070
|
+
]
|
|
1393
1071
|
};
|
|
1394
|
-
continue;
|
|
1395
|
-
}
|
|
1396
|
-
const toolMsg = msg;
|
|
1397
|
-
if (config.keepErrorResults && toolMsg.isError) {
|
|
1398
|
-
continue;
|
|
1399
1072
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1073
|
+
}
|
|
1074
|
+
if (needsTruncation) {
|
|
1075
|
+
const recentResults = needsMasking ? toolResults.slice(-config.keepRecentCount) : toolResults;
|
|
1076
|
+
for (const { msg, index } of recentResults) {
|
|
1077
|
+
if (index >= iterStart) continue;
|
|
1078
|
+
if (isCocoonToolResult(msg)) {
|
|
1079
|
+
const userMsg = msg;
|
|
1080
|
+
if (!Array.isArray(userMsg.content)) continue;
|
|
1081
|
+
const textBlock2 = userMsg.content.find((c2) => c2.type === "text");
|
|
1082
|
+
if (textBlock2 && textBlock2.text.length > config.truncationThreshold) {
|
|
1083
|
+
result[index] = {
|
|
1084
|
+
...userMsg,
|
|
1085
|
+
content: [
|
|
1086
|
+
{
|
|
1087
|
+
type: "text",
|
|
1088
|
+
text: truncateToolResult(textBlock2.text, config.truncationKeepChars)
|
|
1089
|
+
}
|
|
1090
|
+
]
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1403
1093
|
continue;
|
|
1404
1094
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
try {
|
|
1095
|
+
const toolMsg = msg;
|
|
1096
|
+
if (isExempt(toolMsg, config, toolRegistry)) continue;
|
|
1408
1097
|
const textBlock = toolMsg.content.find((c2) => c2.type === "text");
|
|
1409
|
-
if (textBlock)
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1098
|
+
if (!textBlock || textBlock.text.length <= config.truncationThreshold) continue;
|
|
1099
|
+
result[index] = {
|
|
1100
|
+
...toolMsg,
|
|
1101
|
+
content: [
|
|
1102
|
+
{
|
|
1103
|
+
type: "text",
|
|
1104
|
+
text: truncateToolResult(textBlock.text, config.truncationKeepChars)
|
|
1105
|
+
}
|
|
1106
|
+
]
|
|
1107
|
+
};
|
|
1418
1108
|
}
|
|
1419
|
-
result[index] = {
|
|
1420
|
-
...toolMsg,
|
|
1421
|
-
content: [
|
|
1422
|
-
{
|
|
1423
|
-
type: "text",
|
|
1424
|
-
text: `[Tool: ${toolMsg.toolName} - ${toolMsg.isError ? "ERROR" : "OK"}${summaryText}]`
|
|
1425
|
-
}
|
|
1426
|
-
]
|
|
1427
|
-
};
|
|
1428
1109
|
}
|
|
1429
1110
|
return result;
|
|
1430
1111
|
}
|
|
1431
1112
|
|
|
1432
1113
|
// src/agent/runtime.ts
|
|
1433
|
-
var
|
|
1114
|
+
var log4 = createLogger("Agent");
|
|
1434
1115
|
var globalTokenUsage = { totalTokens: 0, totalCost: 0 };
|
|
1435
1116
|
function getTokenUsage() {
|
|
1436
1117
|
return { ...globalTokenUsage };
|
|
@@ -1484,6 +1165,8 @@ var AgentRuntime = class {
|
|
|
1484
1165
|
contextBuilder = null;
|
|
1485
1166
|
toolRegistry = null;
|
|
1486
1167
|
embedder = null;
|
|
1168
|
+
hookRunner;
|
|
1169
|
+
userHookEvaluator;
|
|
1487
1170
|
constructor(config, soul, toolRegistry) {
|
|
1488
1171
|
this.config = config;
|
|
1489
1172
|
this.soul = soul ?? "";
|
|
@@ -1504,6 +1187,12 @@ var AgentRuntime = class {
|
|
|
1504
1187
|
this.compactionManager = new CompactionManager(DEFAULT_COMPACTION_CONFIG);
|
|
1505
1188
|
}
|
|
1506
1189
|
}
|
|
1190
|
+
setHookRunner(runner) {
|
|
1191
|
+
this.hookRunner = runner;
|
|
1192
|
+
}
|
|
1193
|
+
setUserHookEvaluator(evaluator) {
|
|
1194
|
+
this.userHookEvaluator = evaluator;
|
|
1195
|
+
}
|
|
1507
1196
|
initializeContextBuilder(embedder, vectorEnabled) {
|
|
1508
1197
|
this.embedder = embedder;
|
|
1509
1198
|
const db = getDatabase().getDb();
|
|
@@ -1528,15 +1217,62 @@ var AgentRuntime = class {
|
|
|
1528
1217
|
messageId,
|
|
1529
1218
|
replyContext
|
|
1530
1219
|
} = opts;
|
|
1220
|
+
const effectiveIsGroup = isGroup ?? false;
|
|
1221
|
+
const processStartTime = Date.now();
|
|
1531
1222
|
try {
|
|
1223
|
+
let userHookContext = "";
|
|
1224
|
+
if (this.userHookEvaluator) {
|
|
1225
|
+
const hookResult = this.userHookEvaluator.evaluate(userMessage);
|
|
1226
|
+
if (hookResult.blocked) {
|
|
1227
|
+
log4.info("Message blocked by keyword filter");
|
|
1228
|
+
return { content: hookResult.blockMessage ?? "", toolCalls: [] };
|
|
1229
|
+
}
|
|
1230
|
+
if (hookResult.additionalContext) {
|
|
1231
|
+
userHookContext = sanitizeForContext(hookResult.additionalContext);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
let effectiveMessage = userMessage;
|
|
1235
|
+
let hookMessageContext = "";
|
|
1236
|
+
if (this.hookRunner) {
|
|
1237
|
+
const msgEvent = {
|
|
1238
|
+
chatId,
|
|
1239
|
+
senderId: toolContext?.senderId ? String(toolContext.senderId) : chatId,
|
|
1240
|
+
senderName: userName ?? "",
|
|
1241
|
+
isGroup: effectiveIsGroup,
|
|
1242
|
+
isReply: !!replyContext,
|
|
1243
|
+
replyToMessageId: replyContext ? messageId : void 0,
|
|
1244
|
+
messageId: messageId ?? 0,
|
|
1245
|
+
timestamp: timestamp ?? Date.now(),
|
|
1246
|
+
text: userMessage,
|
|
1247
|
+
block: false,
|
|
1248
|
+
blockReason: "",
|
|
1249
|
+
additionalContext: ""
|
|
1250
|
+
};
|
|
1251
|
+
await this.hookRunner.runModifyingHook("message:receive", msgEvent);
|
|
1252
|
+
if (msgEvent.block) {
|
|
1253
|
+
log4.info(`\u{1F6AB} Message blocked by hook: ${msgEvent.blockReason || "no reason"}`);
|
|
1254
|
+
return { content: "", toolCalls: [] };
|
|
1255
|
+
}
|
|
1256
|
+
effectiveMessage = sanitizeForContext(msgEvent.text);
|
|
1257
|
+
if (msgEvent.additionalContext) {
|
|
1258
|
+
hookMessageContext = sanitizeForContext(msgEvent.additionalContext);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1532
1261
|
let session = getOrCreateSession(chatId);
|
|
1533
1262
|
const now = timestamp ?? Date.now();
|
|
1534
1263
|
const resetPolicy = this.config.agent.session_reset_policy;
|
|
1535
1264
|
if (shouldResetSession(session, resetPolicy)) {
|
|
1536
|
-
|
|
1265
|
+
log4.info(`\u{1F504} Auto-resetting session based on policy`);
|
|
1266
|
+
if (this.hookRunner) {
|
|
1267
|
+
await this.hookRunner.runObservingHook("session:end", {
|
|
1268
|
+
sessionId: session.sessionId,
|
|
1269
|
+
chatId,
|
|
1270
|
+
messageCount: session.messageCount
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1537
1273
|
if (transcriptExists(session.sessionId)) {
|
|
1538
1274
|
try {
|
|
1539
|
-
|
|
1275
|
+
log4.info(`\u{1F4BE} Saving memory before daily reset...`);
|
|
1540
1276
|
const oldContext = loadContextFromTranscript(session.sessionId);
|
|
1541
1277
|
await saveSessionMemory({
|
|
1542
1278
|
oldSessionId: session.sessionId,
|
|
@@ -1547,18 +1283,26 @@ var AgentRuntime = class {
|
|
|
1547
1283
|
provider: this.config.agent.provider,
|
|
1548
1284
|
utilityModel: this.config.agent.utility_model
|
|
1549
1285
|
});
|
|
1550
|
-
|
|
1286
|
+
log4.info(`\u2705 Memory saved before reset`);
|
|
1551
1287
|
} catch (error) {
|
|
1552
|
-
|
|
1288
|
+
log4.warn({ err: error }, `\u26A0\uFE0F Failed to save memory before reset`);
|
|
1553
1289
|
}
|
|
1554
1290
|
}
|
|
1555
1291
|
session = resetSessionWithPolicy(chatId, resetPolicy);
|
|
1556
1292
|
}
|
|
1557
1293
|
let context = loadContextFromTranscript(session.sessionId);
|
|
1558
|
-
|
|
1559
|
-
|
|
1294
|
+
const isNewSession = context.messages.length === 0;
|
|
1295
|
+
if (!isNewSession) {
|
|
1296
|
+
log4.info(`\u{1F4D6} Loading existing session: ${session.sessionId}`);
|
|
1560
1297
|
} else {
|
|
1561
|
-
|
|
1298
|
+
log4.info(`\u{1F195} Starting new session: ${session.sessionId}`);
|
|
1299
|
+
}
|
|
1300
|
+
if (this.hookRunner) {
|
|
1301
|
+
await this.hookRunner.runObservingHook("session:start", {
|
|
1302
|
+
sessionId: session.sessionId,
|
|
1303
|
+
chatId,
|
|
1304
|
+
isResume: !isNewSession
|
|
1305
|
+
});
|
|
1562
1306
|
}
|
|
1563
1307
|
const previousTimestamp = session.updatedAt;
|
|
1564
1308
|
let formattedMessage = formatMessageEnvelope({
|
|
@@ -1569,8 +1313,8 @@ var AgentRuntime = class {
|
|
|
1569
1313
|
senderRank,
|
|
1570
1314
|
timestamp: now,
|
|
1571
1315
|
previousTimestamp,
|
|
1572
|
-
body:
|
|
1573
|
-
isGroup:
|
|
1316
|
+
body: effectiveMessage,
|
|
1317
|
+
isGroup: effectiveIsGroup,
|
|
1574
1318
|
hasMedia,
|
|
1575
1319
|
mediaType,
|
|
1576
1320
|
messageId,
|
|
@@ -1580,27 +1324,38 @@ var AgentRuntime = class {
|
|
|
1580
1324
|
formattedMessage = `${pendingContext}
|
|
1581
1325
|
|
|
1582
1326
|
${formattedMessage}`;
|
|
1583
|
-
|
|
1327
|
+
log4.debug(`\u{1F4CB} Including ${pendingContext.split("\n").length - 1} pending messages`);
|
|
1584
1328
|
}
|
|
1585
|
-
|
|
1329
|
+
log4.debug(`\u{1F4E8} Formatted message: ${formattedMessage.substring(0, 100)}...`);
|
|
1586
1330
|
const preview = formattedMessage.slice(0, 50).replace(/\n/g, " ");
|
|
1587
1331
|
const who = senderUsername ? `@${senderUsername}` : userName;
|
|
1588
1332
|
const msgType = isGroup ? `Group ${chatId} ${who}` : `DM ${who}`;
|
|
1589
|
-
|
|
1333
|
+
log4.info(`\u{1F4E8} ${msgType}: "${preview}${formattedMessage.length > 50 ? "..." : ""}"`);
|
|
1590
1334
|
let relevantContext = "";
|
|
1591
1335
|
let queryEmbedding;
|
|
1592
|
-
const isNonTrivial = !isTrivialMessage(
|
|
1336
|
+
const isNonTrivial = !isTrivialMessage(effectiveMessage);
|
|
1593
1337
|
if (this.embedder && isNonTrivial) {
|
|
1594
1338
|
try {
|
|
1595
|
-
|
|
1339
|
+
let searchQuery = effectiveMessage;
|
|
1340
|
+
const recentUserMsgs = context.messages.filter((m) => m.role === "user" && typeof m.content === "string").slice(-3).map((m) => {
|
|
1341
|
+
const text = m.content;
|
|
1342
|
+
const bodyMatch = text.match(/\] (.+)/s);
|
|
1343
|
+
return (bodyMatch ? bodyMatch[1] : text).trim();
|
|
1344
|
+
}).filter((t) => t.length > 0);
|
|
1345
|
+
if (recentUserMsgs.length > 0) {
|
|
1346
|
+
searchQuery = recentUserMsgs.join(" ") + " " + effectiveMessage;
|
|
1347
|
+
}
|
|
1348
|
+
queryEmbedding = await this.embedder.embedQuery(
|
|
1349
|
+
searchQuery.slice(0, EMBEDDING_QUERY_MAX_CHARS)
|
|
1350
|
+
);
|
|
1596
1351
|
} catch (error) {
|
|
1597
|
-
|
|
1352
|
+
log4.warn({ err: error }, "Embedding computation failed");
|
|
1598
1353
|
}
|
|
1599
1354
|
}
|
|
1600
1355
|
if (this.contextBuilder && isNonTrivial) {
|
|
1601
1356
|
try {
|
|
1602
1357
|
const dbContext = await this.contextBuilder.buildContext({
|
|
1603
|
-
query:
|
|
1358
|
+
query: effectiveMessage,
|
|
1604
1359
|
chatId,
|
|
1605
1360
|
includeAgentMemory: true,
|
|
1606
1361
|
includeFeedHistory: true,
|
|
@@ -1628,12 +1383,12 @@ ${sanitizedFeed.join("\n")}`
|
|
|
1628
1383
|
}
|
|
1629
1384
|
if (contextParts.length > 0) {
|
|
1630
1385
|
relevantContext = contextParts.join("\n\n");
|
|
1631
|
-
|
|
1386
|
+
log4.debug(
|
|
1632
1387
|
`\u{1F50D} Found ${dbContext.relevantKnowledge.length} knowledge chunks, ${dbContext.relevantFeed.length} feed messages`
|
|
1633
1388
|
);
|
|
1634
1389
|
}
|
|
1635
1390
|
} catch (error) {
|
|
1636
|
-
|
|
1391
|
+
log4.warn({ err: error }, "Context building failed");
|
|
1637
1392
|
}
|
|
1638
1393
|
}
|
|
1639
1394
|
const memoryStats = this.getMemoryStats();
|
|
@@ -1645,8 +1400,23 @@ ${statsContext}
|
|
|
1645
1400
|
${relevantContext}` : `You are in a Telegram conversation with chat ID: ${chatId}. Maintain conversation continuity.
|
|
1646
1401
|
|
|
1647
1402
|
${statsContext}`;
|
|
1403
|
+
let hookAdditionalContext = "";
|
|
1404
|
+
if (this.hookRunner) {
|
|
1405
|
+
const promptEvent = {
|
|
1406
|
+
chatId,
|
|
1407
|
+
sessionId: session.sessionId,
|
|
1408
|
+
isGroup: effectiveIsGroup,
|
|
1409
|
+
additionalContext: ""
|
|
1410
|
+
};
|
|
1411
|
+
await this.hookRunner.runModifyingHook("prompt:before", promptEvent);
|
|
1412
|
+
hookAdditionalContext = sanitizeForContext(promptEvent.additionalContext);
|
|
1413
|
+
}
|
|
1648
1414
|
const compactionConfig = this.compactionManager.getConfig();
|
|
1649
1415
|
const needsMemoryFlush = compactionConfig.enabled && compactionConfig.memoryFlushEnabled && context.messages.length > Math.floor((compactionConfig.maxMessages ?? 200) * 0.75);
|
|
1416
|
+
const allHookContext = [userHookContext, hookAdditionalContext, hookMessageContext].filter(Boolean).join("\n\n");
|
|
1417
|
+
const finalContext = additionalContext + (allHookContext ? `
|
|
1418
|
+
|
|
1419
|
+
${allHookContext}` : "");
|
|
1650
1420
|
const systemPrompt = buildSystemPrompt({
|
|
1651
1421
|
soul: this.soul,
|
|
1652
1422
|
userName,
|
|
@@ -1654,11 +1424,23 @@ ${statsContext}`;
|
|
|
1654
1424
|
senderId: toolContext?.senderId,
|
|
1655
1425
|
ownerName: this.config.telegram.owner_name,
|
|
1656
1426
|
ownerUsername: this.config.telegram.owner_username,
|
|
1657
|
-
context:
|
|
1658
|
-
includeMemory: !
|
|
1659
|
-
includeStrategy: !
|
|
1427
|
+
context: finalContext,
|
|
1428
|
+
includeMemory: !effectiveIsGroup,
|
|
1429
|
+
includeStrategy: !effectiveIsGroup,
|
|
1660
1430
|
memoryFlushWarning: needsMemoryFlush
|
|
1661
1431
|
});
|
|
1432
|
+
if (this.hookRunner) {
|
|
1433
|
+
const promptAfterEvent = {
|
|
1434
|
+
chatId,
|
|
1435
|
+
sessionId: session.sessionId,
|
|
1436
|
+
isGroup: effectiveIsGroup,
|
|
1437
|
+
promptLength: systemPrompt.length,
|
|
1438
|
+
sectionCount: (systemPrompt.match(/^#{1,3} /gm) || []).length,
|
|
1439
|
+
ragContextLength: relevantContext.length,
|
|
1440
|
+
hookContextLength: allHookContext.length
|
|
1441
|
+
};
|
|
1442
|
+
await this.hookRunner.runObservingHook("prompt:after", promptAfterEvent);
|
|
1443
|
+
}
|
|
1662
1444
|
const userMsg = {
|
|
1663
1445
|
role: "user",
|
|
1664
1446
|
content: formattedMessage,
|
|
@@ -1674,7 +1456,7 @@ ${statsContext}`;
|
|
|
1674
1456
|
this.config.agent.utility_model
|
|
1675
1457
|
);
|
|
1676
1458
|
if (preemptiveCompaction) {
|
|
1677
|
-
|
|
1459
|
+
log4.info(`\u{1F5DC}\uFE0F Preemptive compaction triggered, reloading session...`);
|
|
1678
1460
|
session = getSession(chatId);
|
|
1679
1461
|
context = loadContextFromTranscript(session.sessionId);
|
|
1680
1462
|
context.messages.push(userMsg);
|
|
@@ -1686,20 +1468,20 @@ ${statsContext}`;
|
|
|
1686
1468
|
let tools;
|
|
1687
1469
|
{
|
|
1688
1470
|
const toolIndex = this.toolRegistry?.getToolIndex();
|
|
1689
|
-
const useRAG = toolIndex?.isIndexed && this.config.tool_rag?.enabled !== false && !isTrivialMessage(
|
|
1471
|
+
const useRAG = toolIndex?.isIndexed && this.config.tool_rag?.enabled !== false && !isTrivialMessage(effectiveMessage) && !(providerMeta.toolLimit === null && this.config.tool_rag?.skip_unlimited_providers !== false);
|
|
1690
1472
|
if (useRAG && this.toolRegistry && queryEmbedding) {
|
|
1691
1473
|
tools = await this.toolRegistry.getForContextWithRAG(
|
|
1692
|
-
|
|
1474
|
+
effectiveMessage,
|
|
1693
1475
|
queryEmbedding,
|
|
1694
|
-
|
|
1476
|
+
effectiveIsGroup,
|
|
1695
1477
|
providerMeta.toolLimit,
|
|
1696
1478
|
chatId,
|
|
1697
1479
|
isAdmin
|
|
1698
1480
|
);
|
|
1699
|
-
|
|
1481
|
+
log4.info(`\u{1F50D} Tool RAG: ${tools.length}/${this.toolRegistry.count} tools selected`);
|
|
1700
1482
|
} else {
|
|
1701
1483
|
tools = this.toolRegistry?.getForContext(
|
|
1702
|
-
|
|
1484
|
+
effectiveIsGroup,
|
|
1703
1485
|
providerMeta.toolLimit,
|
|
1704
1486
|
chatId,
|
|
1705
1487
|
isAdmin
|
|
@@ -1715,14 +1497,15 @@ ${statsContext}`;
|
|
|
1715
1497
|
const totalToolCalls = [];
|
|
1716
1498
|
const accumulatedTexts = [];
|
|
1717
1499
|
const accumulatedUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalCost: 0 };
|
|
1500
|
+
const seenToolSignatures = /* @__PURE__ */ new Set();
|
|
1718
1501
|
while (iteration < maxIterations) {
|
|
1719
1502
|
iteration++;
|
|
1720
|
-
|
|
1721
|
-
const
|
|
1722
|
-
|
|
1723
|
-
void 0,
|
|
1724
|
-
|
|
1725
|
-
);
|
|
1503
|
+
log4.debug(`\u{1F504} Agentic iteration ${iteration}/${maxIterations}`);
|
|
1504
|
+
const iterationStartIndex = context.messages.length;
|
|
1505
|
+
const maskedMessages = maskOldToolResults(context.messages, {
|
|
1506
|
+
toolRegistry: this.toolRegistry ?? void 0,
|
|
1507
|
+
currentIterationStartIndex: iterationStartIndex
|
|
1508
|
+
});
|
|
1726
1509
|
const maskedContext = { ...context, messages: maskedMessages };
|
|
1727
1510
|
const response2 = await chatWithContext(this.config.agent, {
|
|
1728
1511
|
systemPrompt,
|
|
@@ -1734,6 +1517,21 @@ ${statsContext}`;
|
|
|
1734
1517
|
const assistantMsg = response2.message;
|
|
1735
1518
|
if (assistantMsg.stopReason === "error") {
|
|
1736
1519
|
const errorMsg = assistantMsg.errorMessage || "";
|
|
1520
|
+
if (this.hookRunner) {
|
|
1521
|
+
const errorCode = errorMsg.includes("429") || errorMsg.toLowerCase().includes("rate") ? "RATE_LIMIT" : isContextOverflowError(errorMsg) ? "CONTEXT_OVERFLOW" : errorMsg.includes("500") || errorMsg.includes("502") || errorMsg.includes("503") ? "PROVIDER_ERROR" : "UNKNOWN";
|
|
1522
|
+
const responseErrorEvent = {
|
|
1523
|
+
chatId,
|
|
1524
|
+
sessionId: session.sessionId,
|
|
1525
|
+
isGroup: effectiveIsGroup,
|
|
1526
|
+
error: errorMsg,
|
|
1527
|
+
errorCode,
|
|
1528
|
+
provider,
|
|
1529
|
+
model: this.config.agent.model,
|
|
1530
|
+
retryCount: rateLimitRetries + serverErrorRetries,
|
|
1531
|
+
durationMs: Date.now() - processStartTime
|
|
1532
|
+
};
|
|
1533
|
+
await this.hookRunner.runObservingHook("response:error", responseErrorEvent);
|
|
1534
|
+
}
|
|
1737
1535
|
if (isContextOverflowError(errorMsg)) {
|
|
1738
1536
|
overflowResets++;
|
|
1739
1537
|
if (overflowResets > 1) {
|
|
@@ -1741,35 +1539,35 @@ ${statsContext}`;
|
|
|
1741
1539
|
"Context overflow persists after session reset. Message may be too large for the model's context window."
|
|
1742
1540
|
);
|
|
1743
1541
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1542
|
+
log4.error(`\u{1F6A8} Context overflow detected: ${errorMsg}`);
|
|
1543
|
+
log4.info(`\u{1F4BE} Saving session memory before reset...`);
|
|
1746
1544
|
const summary = extractContextSummary(context, CONTEXT_OVERFLOW_SUMMARY_MESSAGES);
|
|
1747
1545
|
appendToDailyLog(summary);
|
|
1748
|
-
|
|
1546
|
+
log4.info(`\u2705 Memory saved to daily log`);
|
|
1749
1547
|
const archived = archiveTranscript(session.sessionId);
|
|
1750
1548
|
if (!archived) {
|
|
1751
|
-
|
|
1549
|
+
log4.error(
|
|
1752
1550
|
`\u26A0\uFE0F Failed to archive transcript ${session.sessionId}, proceeding with reset anyway`
|
|
1753
1551
|
);
|
|
1754
1552
|
}
|
|
1755
|
-
|
|
1553
|
+
log4.info(`\u{1F504} Resetting session due to context overflow...`);
|
|
1756
1554
|
session = resetSession(chatId);
|
|
1757
1555
|
context = { messages: [userMsg] };
|
|
1758
1556
|
appendToTranscript(session.sessionId, userMsg);
|
|
1759
|
-
|
|
1557
|
+
log4.info(`\u{1F504} Retrying with fresh context...`);
|
|
1760
1558
|
continue;
|
|
1761
1559
|
} else if (errorMsg.toLowerCase().includes("rate") || errorMsg.includes("429")) {
|
|
1762
1560
|
rateLimitRetries++;
|
|
1763
1561
|
if (rateLimitRetries <= RATE_LIMIT_MAX_RETRIES) {
|
|
1764
1562
|
const delay = 1e3 * Math.pow(2, rateLimitRetries - 1);
|
|
1765
|
-
|
|
1563
|
+
log4.warn(
|
|
1766
1564
|
`\u{1F6AB} Rate limited, retrying in ${delay}ms (attempt ${rateLimitRetries}/${RATE_LIMIT_MAX_RETRIES})...`
|
|
1767
1565
|
);
|
|
1768
1566
|
await new Promise((r3) => setTimeout(r3, delay));
|
|
1769
1567
|
iteration--;
|
|
1770
1568
|
continue;
|
|
1771
1569
|
}
|
|
1772
|
-
|
|
1570
|
+
log4.error(`\u{1F6AB} Rate limited after ${RATE_LIMIT_MAX_RETRIES} retries: ${errorMsg}`);
|
|
1773
1571
|
throw new Error(
|
|
1774
1572
|
`API rate limited after ${RATE_LIMIT_MAX_RETRIES} retries. Please try again later.`
|
|
1775
1573
|
);
|
|
@@ -1777,19 +1575,19 @@ ${statsContext}`;
|
|
|
1777
1575
|
serverErrorRetries++;
|
|
1778
1576
|
if (serverErrorRetries <= SERVER_ERROR_MAX_RETRIES) {
|
|
1779
1577
|
const delay = 2e3 * Math.pow(2, serverErrorRetries - 1);
|
|
1780
|
-
|
|
1578
|
+
log4.warn(
|
|
1781
1579
|
`\u{1F504} Server error, retrying in ${delay}ms (attempt ${serverErrorRetries}/${SERVER_ERROR_MAX_RETRIES})...`
|
|
1782
1580
|
);
|
|
1783
1581
|
await new Promise((r3) => setTimeout(r3, delay));
|
|
1784
1582
|
iteration--;
|
|
1785
1583
|
continue;
|
|
1786
1584
|
}
|
|
1787
|
-
|
|
1585
|
+
log4.error(`\u{1F6A8} Server error after ${SERVER_ERROR_MAX_RETRIES} retries: ${errorMsg}`);
|
|
1788
1586
|
throw new Error(
|
|
1789
1587
|
`API server error after ${SERVER_ERROR_MAX_RETRIES} retries. The provider may be experiencing issues.`
|
|
1790
1588
|
);
|
|
1791
1589
|
} else {
|
|
1792
|
-
|
|
1590
|
+
log4.error(`\u{1F6A8} API error: ${errorMsg}`);
|
|
1793
1591
|
throw new Error(`API error: ${errorMsg || "Unknown error"}`);
|
|
1794
1592
|
}
|
|
1795
1593
|
}
|
|
@@ -1806,55 +1604,156 @@ ${statsContext}`;
|
|
|
1806
1604
|
}
|
|
1807
1605
|
const toolCalls = response2.message.content.filter((block) => block.type === "toolCall");
|
|
1808
1606
|
if (toolCalls.length === 0) {
|
|
1809
|
-
|
|
1607
|
+
log4.info(`\u{1F504} ${iteration}/${maxIterations} \u2192 done`);
|
|
1810
1608
|
finalResponse = response2;
|
|
1811
1609
|
break;
|
|
1812
1610
|
}
|
|
1813
1611
|
if (!this.toolRegistry || !toolContext) {
|
|
1814
|
-
|
|
1612
|
+
log4.error("\u26A0\uFE0F Cannot execute tools: registry or context missing");
|
|
1815
1613
|
break;
|
|
1816
1614
|
}
|
|
1817
|
-
|
|
1615
|
+
log4.debug(`\u{1F527} Executing ${toolCalls.length} tool call(s)`);
|
|
1818
1616
|
context.messages.push(response2.message);
|
|
1819
1617
|
const iterationToolNames = [];
|
|
1618
|
+
const fullContext = {
|
|
1619
|
+
...toolContext,
|
|
1620
|
+
chatId,
|
|
1621
|
+
isGroup: effectiveIsGroup
|
|
1622
|
+
};
|
|
1623
|
+
const toolPlans = [];
|
|
1820
1624
|
for (const block of toolCalls) {
|
|
1821
1625
|
if (block.type !== "toolCall") continue;
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1626
|
+
let toolParams = block.arguments ?? {};
|
|
1627
|
+
let blocked = false;
|
|
1628
|
+
let blockReason = "";
|
|
1629
|
+
if (this.hookRunner) {
|
|
1630
|
+
const beforeEvent = {
|
|
1631
|
+
toolName: block.name,
|
|
1632
|
+
params: structuredClone(toolParams),
|
|
1633
|
+
chatId,
|
|
1634
|
+
isGroup: effectiveIsGroup,
|
|
1635
|
+
block: false,
|
|
1636
|
+
blockReason: ""
|
|
1637
|
+
};
|
|
1638
|
+
await this.hookRunner.runModifyingHook("tool:before", beforeEvent);
|
|
1639
|
+
if (beforeEvent.block) {
|
|
1640
|
+
blocked = true;
|
|
1641
|
+
blockReason = beforeEvent.blockReason || "Blocked by plugin hook";
|
|
1642
|
+
} else {
|
|
1643
|
+
toolParams = structuredClone(beforeEvent.params);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
toolPlans.push({ block, blocked, blockReason, params: toolParams });
|
|
1647
|
+
}
|
|
1648
|
+
const execResults = new Array(toolPlans.length);
|
|
1649
|
+
{
|
|
1650
|
+
let cursor = 0;
|
|
1651
|
+
const runWorker = async () => {
|
|
1652
|
+
while (cursor < toolPlans.length) {
|
|
1653
|
+
const idx = cursor++;
|
|
1654
|
+
const plan = toolPlans[idx];
|
|
1655
|
+
if (plan.blocked) {
|
|
1656
|
+
execResults[idx] = {
|
|
1657
|
+
result: { success: false, error: plan.blockReason },
|
|
1658
|
+
durationMs: 0
|
|
1659
|
+
};
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
const startTime = Date.now();
|
|
1663
|
+
try {
|
|
1664
|
+
const result = await this.toolRegistry.execute(
|
|
1665
|
+
{ ...plan.block, arguments: plan.params },
|
|
1666
|
+
fullContext
|
|
1667
|
+
);
|
|
1668
|
+
execResults[idx] = { result, durationMs: Date.now() - startTime };
|
|
1669
|
+
} catch (execErr) {
|
|
1670
|
+
const errMsg = execErr instanceof Error ? execErr.message : String(execErr);
|
|
1671
|
+
const errStack = execErr instanceof Error ? execErr.stack : void 0;
|
|
1672
|
+
execResults[idx] = {
|
|
1673
|
+
result: { success: false, error: errMsg },
|
|
1674
|
+
durationMs: Date.now() - startTime,
|
|
1675
|
+
execError: { message: errMsg, stack: errStack }
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1826
1679
|
};
|
|
1827
|
-
const
|
|
1828
|
-
|
|
1829
|
-
|
|
1680
|
+
const workers = Math.min(TOOL_CONCURRENCY_LIMIT, toolPlans.length);
|
|
1681
|
+
await Promise.all(Array.from({ length: workers }, () => runWorker()));
|
|
1682
|
+
}
|
|
1683
|
+
for (let i = 0; i < toolPlans.length; i++) {
|
|
1684
|
+
const plan = toolPlans[i];
|
|
1685
|
+
const { block } = plan;
|
|
1686
|
+
const exec = execResults[i];
|
|
1687
|
+
if (exec.execError && this.hookRunner) {
|
|
1688
|
+
const errorEvent = {
|
|
1689
|
+
toolName: block.name,
|
|
1690
|
+
params: structuredClone(plan.params),
|
|
1691
|
+
error: exec.execError.message,
|
|
1692
|
+
stack: exec.execError.stack,
|
|
1693
|
+
chatId,
|
|
1694
|
+
isGroup: effectiveIsGroup,
|
|
1695
|
+
durationMs: exec.durationMs
|
|
1696
|
+
};
|
|
1697
|
+
await this.hookRunner.runObservingHook("tool:error", errorEvent);
|
|
1698
|
+
}
|
|
1699
|
+
if (this.hookRunner) {
|
|
1700
|
+
const afterEvent = {
|
|
1701
|
+
toolName: block.name,
|
|
1702
|
+
params: structuredClone(plan.params),
|
|
1703
|
+
result: {
|
|
1704
|
+
success: exec.result.success,
|
|
1705
|
+
data: exec.result.data,
|
|
1706
|
+
error: exec.result.error
|
|
1707
|
+
},
|
|
1708
|
+
durationMs: exec.durationMs,
|
|
1709
|
+
chatId,
|
|
1710
|
+
isGroup: effectiveIsGroup,
|
|
1711
|
+
...plan.blocked ? { blocked: true, blockReason: plan.blockReason } : {}
|
|
1712
|
+
};
|
|
1713
|
+
await this.hookRunner.runObservingHook("tool:after", afterEvent);
|
|
1714
|
+
}
|
|
1715
|
+
log4.debug(`${block.name}: ${exec.result.success ? "\u2713" : "\u2717"} ${exec.result.error || ""}`);
|
|
1716
|
+
iterationToolNames.push(`${block.name} ${exec.result.success ? "\u2713" : "\u2717"}`);
|
|
1830
1717
|
totalToolCalls.push({
|
|
1831
1718
|
name: block.name,
|
|
1832
1719
|
input: block.arguments
|
|
1833
1720
|
});
|
|
1834
|
-
let resultText = JSON.stringify(result
|
|
1721
|
+
let resultText = JSON.stringify(exec.result);
|
|
1835
1722
|
if (resultText.length > MAX_TOOL_RESULT_SIZE) {
|
|
1836
|
-
|
|
1837
|
-
const data = result.data;
|
|
1723
|
+
log4.warn(`\u26A0\uFE0F Tool result too large (${resultText.length} chars), truncating...`);
|
|
1724
|
+
const data = exec.result.data;
|
|
1838
1725
|
if (data?.summary || data?.message) {
|
|
1839
|
-
resultText = JSON.stringify(
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
},
|
|
1849
|
-
null,
|
|
1850
|
-
2
|
|
1851
|
-
);
|
|
1726
|
+
resultText = JSON.stringify({
|
|
1727
|
+
success: exec.result.success,
|
|
1728
|
+
data: {
|
|
1729
|
+
summary: data.summary || data.message,
|
|
1730
|
+
_truncated: true,
|
|
1731
|
+
_originalSize: resultText.length,
|
|
1732
|
+
_message: "Full data truncated. Use limit parameter for smaller results."
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1852
1735
|
} else {
|
|
1853
|
-
|
|
1736
|
+
const summarized = {
|
|
1737
|
+
_truncated: true,
|
|
1738
|
+
_originalSize: resultText.length,
|
|
1739
|
+
_message: "Full data truncated. Use limit parameter for smaller results."
|
|
1740
|
+
};
|
|
1741
|
+
if (data && typeof data === "object") {
|
|
1742
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1743
|
+
if (Array.isArray(value)) {
|
|
1744
|
+
summarized[key] = `[${value.length} items]`;
|
|
1745
|
+
} else if (typeof value === "string" && value.length > 500) {
|
|
1746
|
+
summarized[key] = value.slice(0, 500) + "...[truncated]";
|
|
1747
|
+
} else {
|
|
1748
|
+
summarized[key] = value;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
resultText = JSON.stringify({ success: exec.result.success, data: summarized });
|
|
1854
1753
|
}
|
|
1855
1754
|
}
|
|
1856
1755
|
if (provider === "cocoon") {
|
|
1857
|
-
const { wrapToolResult } = await import("./tool-adapter-
|
|
1756
|
+
const { wrapToolResult } = await import("./tool-adapter-IVX2XQJE.js");
|
|
1858
1757
|
const cocoonResultMsg = {
|
|
1859
1758
|
role: "user",
|
|
1860
1759
|
content: [
|
|
@@ -1878,21 +1777,33 @@ ${statsContext}`;
|
|
|
1878
1777
|
text: resultText
|
|
1879
1778
|
}
|
|
1880
1779
|
],
|
|
1881
|
-
isError: !result.success,
|
|
1780
|
+
isError: !exec.result.success,
|
|
1882
1781
|
timestamp: Date.now()
|
|
1883
1782
|
};
|
|
1884
1783
|
context.messages.push(toolResultMsg);
|
|
1885
1784
|
appendToTranscript(session.sessionId, toolResultMsg);
|
|
1886
1785
|
}
|
|
1887
1786
|
}
|
|
1888
|
-
|
|
1787
|
+
log4.info(`\u{1F504} ${iteration}/${maxIterations} \u2192 ${iterationToolNames.join(", ")}`);
|
|
1788
|
+
const iterSignatures = toolPlans.map(
|
|
1789
|
+
(p2) => `${p2.block.name}:${JSON.stringify(p2.params, Object.keys(p2.params).sort())}`
|
|
1790
|
+
);
|
|
1791
|
+
const allDuplicates = iterSignatures.length > 0 && iterSignatures.every((sig) => seenToolSignatures.has(sig));
|
|
1792
|
+
for (const sig of iterSignatures) seenToolSignatures.add(sig);
|
|
1793
|
+
if (allDuplicates) {
|
|
1794
|
+
log4.warn(
|
|
1795
|
+
`\u{1F501} Loop stall detected: all ${iterSignatures.length} tool call(s) are repeats \u2014 breaking early`
|
|
1796
|
+
);
|
|
1797
|
+
finalResponse = response2;
|
|
1798
|
+
break;
|
|
1799
|
+
}
|
|
1889
1800
|
if (iteration === maxIterations) {
|
|
1890
|
-
|
|
1801
|
+
log4.info(`\u26A0\uFE0F Max iterations reached (${maxIterations})`);
|
|
1891
1802
|
finalResponse = response2;
|
|
1892
1803
|
}
|
|
1893
1804
|
}
|
|
1894
1805
|
if (!finalResponse) {
|
|
1895
|
-
|
|
1806
|
+
log4.error("\u26A0\uFE0F Agentic loop exited early without final response");
|
|
1896
1807
|
return {
|
|
1897
1808
|
content: "Internal error: Agent loop failed to produce a response.",
|
|
1898
1809
|
toolCalls: []
|
|
@@ -1903,14 +1814,6 @@ ${statsContext}`;
|
|
|
1903
1814
|
if (lastMsg?.role !== "assistant") {
|
|
1904
1815
|
context.messages.push(response.message);
|
|
1905
1816
|
}
|
|
1906
|
-
const newSessionId = await this.compactionManager.checkAndCompact(
|
|
1907
|
-
session.sessionId,
|
|
1908
|
-
context,
|
|
1909
|
-
getEffectiveApiKey(this.config.agent.provider, this.config.agent.api_key),
|
|
1910
|
-
chatId,
|
|
1911
|
-
this.config.agent.provider,
|
|
1912
|
-
this.config.agent.utility_model
|
|
1913
|
-
);
|
|
1914
1817
|
const sessionUpdate = {
|
|
1915
1818
|
updatedAt: Date.now(),
|
|
1916
1819
|
messageCount: session.messageCount + 1,
|
|
@@ -1919,9 +1822,6 @@ ${statsContext}`;
|
|
|
1919
1822
|
inputTokens: (session.inputTokens ?? 0) + accumulatedUsage.input + accumulatedUsage.cacheRead + accumulatedUsage.cacheWrite,
|
|
1920
1823
|
outputTokens: (session.outputTokens ?? 0) + accumulatedUsage.output
|
|
1921
1824
|
};
|
|
1922
|
-
if (newSessionId) {
|
|
1923
|
-
sessionUpdate.sessionId = newSessionId;
|
|
1924
|
-
}
|
|
1925
1825
|
updateSession(chatId, sessionUpdate);
|
|
1926
1826
|
if (accumulatedUsage.input > 0 || accumulatedUsage.output > 0) {
|
|
1927
1827
|
const u = accumulatedUsage;
|
|
@@ -1931,28 +1831,64 @@ ${statsContext}`;
|
|
|
1931
1831
|
if (u.cacheRead) cacheParts.push(`${(u.cacheRead / 1e3).toFixed(1)}K cached`);
|
|
1932
1832
|
if (u.cacheWrite) cacheParts.push(`${(u.cacheWrite / 1e3).toFixed(1)}K new`);
|
|
1933
1833
|
const cacheInfo = cacheParts.length > 0 ? ` (${cacheParts.join(", ")})` : "";
|
|
1934
|
-
|
|
1834
|
+
log4.info(`\u{1F4B0} ${inK}K in${cacheInfo}, ${u.output} out | $${u.totalCost.toFixed(3)}`);
|
|
1935
1835
|
globalTokenUsage.totalTokens += u.input + u.output + u.cacheRead + u.cacheWrite;
|
|
1936
1836
|
globalTokenUsage.totalCost += u.totalCost;
|
|
1937
1837
|
}
|
|
1938
1838
|
let content = accumulatedTexts.join("\n").trim() || response.text;
|
|
1939
1839
|
const usedTelegramSendTool = totalToolCalls.some((tc) => TELEGRAM_SEND_TOOLS.has(tc.name));
|
|
1940
1840
|
if (!content && totalToolCalls.length > 0 && !usedTelegramSendTool) {
|
|
1941
|
-
|
|
1841
|
+
log4.warn("\u26A0\uFE0F Empty response after tool calls - generating fallback");
|
|
1942
1842
|
content = "I executed the requested action but couldn't generate a response. Please try again.";
|
|
1943
1843
|
} else if (!content && usedTelegramSendTool) {
|
|
1944
|
-
|
|
1844
|
+
log4.info("\u2705 Response sent via Telegram tool - no additional text needed");
|
|
1945
1845
|
content = "";
|
|
1946
1846
|
} else if (!content && accumulatedUsage.input === 0 && accumulatedUsage.output === 0) {
|
|
1947
|
-
|
|
1847
|
+
log4.warn("\u26A0\uFE0F Empty response with zero tokens - possible API issue");
|
|
1948
1848
|
content = "I couldn't process your request. Please try again.";
|
|
1949
1849
|
}
|
|
1850
|
+
let responseMetadata = {};
|
|
1851
|
+
if (this.hookRunner) {
|
|
1852
|
+
const responseBeforeEvent = {
|
|
1853
|
+
chatId,
|
|
1854
|
+
sessionId: session.sessionId,
|
|
1855
|
+
isGroup: effectiveIsGroup,
|
|
1856
|
+
originalText: content,
|
|
1857
|
+
text: content,
|
|
1858
|
+
block: false,
|
|
1859
|
+
blockReason: "",
|
|
1860
|
+
metadata: {}
|
|
1861
|
+
};
|
|
1862
|
+
await this.hookRunner.runModifyingHook("response:before", responseBeforeEvent);
|
|
1863
|
+
if (responseBeforeEvent.block) {
|
|
1864
|
+
log4.info(
|
|
1865
|
+
`\u{1F6AB} Response blocked by hook: ${responseBeforeEvent.blockReason || "no reason"}`
|
|
1866
|
+
);
|
|
1867
|
+
content = "";
|
|
1868
|
+
} else {
|
|
1869
|
+
content = responseBeforeEvent.text;
|
|
1870
|
+
}
|
|
1871
|
+
responseMetadata = responseBeforeEvent.metadata;
|
|
1872
|
+
}
|
|
1873
|
+
if (this.hookRunner) {
|
|
1874
|
+
const responseAfterEvent = {
|
|
1875
|
+
chatId,
|
|
1876
|
+
sessionId: session.sessionId,
|
|
1877
|
+
isGroup: effectiveIsGroup,
|
|
1878
|
+
text: content,
|
|
1879
|
+
durationMs: Date.now() - processStartTime,
|
|
1880
|
+
toolsUsed: totalToolCalls.map((tc) => tc.name),
|
|
1881
|
+
tokenUsage: accumulatedUsage.input > 0 || accumulatedUsage.output > 0 ? { input: accumulatedUsage.input, output: accumulatedUsage.output } : void 0,
|
|
1882
|
+
metadata: responseMetadata
|
|
1883
|
+
};
|
|
1884
|
+
await this.hookRunner.runObservingHook("response:after", responseAfterEvent);
|
|
1885
|
+
}
|
|
1950
1886
|
return {
|
|
1951
1887
|
content,
|
|
1952
1888
|
toolCalls: totalToolCalls
|
|
1953
1889
|
};
|
|
1954
1890
|
} catch (error) {
|
|
1955
|
-
|
|
1891
|
+
log4.error({ err: error }, "Agent error");
|
|
1956
1892
|
throw error;
|
|
1957
1893
|
}
|
|
1958
1894
|
}
|
|
@@ -1965,7 +1901,7 @@ ${statsContext}`;
|
|
|
1965
1901
|
).run(chatId);
|
|
1966
1902
|
db.prepare(`DELETE FROM tg_messages WHERE chat_id = ?`).run(chatId);
|
|
1967
1903
|
resetSession(chatId);
|
|
1968
|
-
|
|
1904
|
+
log4.info(`\u{1F5D1}\uFE0F Cleared history for chat ${chatId}`);
|
|
1969
1905
|
}
|
|
1970
1906
|
getConfig() {
|
|
1971
1907
|
return this.config;
|
|
@@ -1986,7 +1922,7 @@ ${statsContext}`;
|
|
|
1986
1922
|
}
|
|
1987
1923
|
configureCompaction(config) {
|
|
1988
1924
|
this.compactionManager.updateConfig(config);
|
|
1989
|
-
|
|
1925
|
+
log4.info({ config: this.compactionManager.getConfig() }, `\u{1F5DC}\uFE0F Compaction config updated`);
|
|
1990
1926
|
}
|
|
1991
1927
|
getCompactionConfig() {
|
|
1992
1928
|
return this.compactionManager.getConfig();
|
|
@@ -2074,6 +2010,17 @@ function prefixButtons(rows, pluginName) {
|
|
|
2074
2010
|
|
|
2075
2011
|
// src/bot/services/html-parser.ts
|
|
2076
2012
|
import { Api as Api2 } from "telegram";
|
|
2013
|
+
|
|
2014
|
+
// src/utils/gramjs-bigint.ts
|
|
2015
|
+
import { randomBytes } from "crypto";
|
|
2016
|
+
function toLong(value) {
|
|
2017
|
+
return typeof value === "bigint" ? value : BigInt(value);
|
|
2018
|
+
}
|
|
2019
|
+
function randomLong() {
|
|
2020
|
+
return randomBytes(8).readBigUInt64BE();
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
// src/bot/services/html-parser.ts
|
|
2077
2024
|
function parseHtml(html) {
|
|
2078
2025
|
const entities = [];
|
|
2079
2026
|
let text = "";
|
|
@@ -2124,7 +2071,7 @@ function parseHtml(html) {
|
|
|
2124
2071
|
new Api2.MessageEntityCustomEmoji({
|
|
2125
2072
|
offset: open.offset,
|
|
2126
2073
|
length,
|
|
2127
|
-
documentId:
|
|
2074
|
+
documentId: toLong(open.emojiId)
|
|
2128
2075
|
})
|
|
2129
2076
|
);
|
|
2130
2077
|
}
|
|
@@ -2185,7 +2132,7 @@ function unescapeHtml(text) {
|
|
|
2185
2132
|
}
|
|
2186
2133
|
|
|
2187
2134
|
// src/bot/inline-router.ts
|
|
2188
|
-
var
|
|
2135
|
+
var log5 = createLogger("InlineRouter");
|
|
2189
2136
|
var INLINE_TIMEOUT_MS = 5e3;
|
|
2190
2137
|
var CALLBACK_TIMEOUT_MS = 15e3;
|
|
2191
2138
|
function compileGlob(pattern) {
|
|
@@ -2206,11 +2153,11 @@ var InlineRouter = class {
|
|
|
2206
2153
|
}
|
|
2207
2154
|
registerPlugin(name, handlers) {
|
|
2208
2155
|
this.plugins.set(name, handlers);
|
|
2209
|
-
|
|
2156
|
+
log5.info(`Registered plugin "${name}" for inline routing`);
|
|
2210
2157
|
}
|
|
2211
2158
|
unregisterPlugin(name) {
|
|
2212
2159
|
this.plugins.delete(name);
|
|
2213
|
-
|
|
2160
|
+
log5.info(`Unregistered plugin "${name}" from inline routing`);
|
|
2214
2161
|
}
|
|
2215
2162
|
hasPlugin(name) {
|
|
2216
2163
|
return this.plugins.has(name);
|
|
@@ -2262,11 +2209,14 @@ var InlineRouter = class {
|
|
|
2262
2209
|
}
|
|
2263
2210
|
async handleInlineQuery(ctx, pluginName, query, plugin) {
|
|
2264
2211
|
try {
|
|
2212
|
+
const inlineQuery = ctx.inlineQuery;
|
|
2213
|
+
const from = ctx.from;
|
|
2214
|
+
if (!inlineQuery || !from || !plugin.onInlineQuery) return;
|
|
2265
2215
|
const iqCtx = {
|
|
2266
2216
|
query,
|
|
2267
|
-
queryId:
|
|
2268
|
-
userId:
|
|
2269
|
-
offset:
|
|
2217
|
+
queryId: inlineQuery.id,
|
|
2218
|
+
userId: from.id,
|
|
2219
|
+
offset: inlineQuery.offset
|
|
2270
2220
|
};
|
|
2271
2221
|
const results = await withTimeout(
|
|
2272
2222
|
plugin.onInlineQuery(iqCtx),
|
|
@@ -2279,7 +2229,7 @@ var InlineRouter = class {
|
|
|
2279
2229
|
is_personal: true
|
|
2280
2230
|
});
|
|
2281
2231
|
} catch (error) {
|
|
2282
|
-
|
|
2232
|
+
log5.error({ err: error }, `Plugin "${pluginName}" inline query handler failed`);
|
|
2283
2233
|
try {
|
|
2284
2234
|
await ctx.answerInlineQuery([], { cache_time: 0, is_personal: true });
|
|
2285
2235
|
} catch {
|
|
@@ -2291,7 +2241,7 @@ var InlineRouter = class {
|
|
|
2291
2241
|
try {
|
|
2292
2242
|
let matchedHandler;
|
|
2293
2243
|
let matchGroups = [];
|
|
2294
|
-
for (const entry of plugin.onCallback) {
|
|
2244
|
+
for (const entry of plugin.onCallback ?? []) {
|
|
2295
2245
|
const groups = globMatch(entry.regex, strippedData);
|
|
2296
2246
|
if (groups !== null) {
|
|
2297
2247
|
matchedHandler = entry.handler;
|
|
@@ -2304,14 +2254,17 @@ var InlineRouter = class {
|
|
|
2304
2254
|
return;
|
|
2305
2255
|
}
|
|
2306
2256
|
const gramjsBotRef = this.gramjsBot;
|
|
2257
|
+
const callbackQuery = ctx.callbackQuery;
|
|
2258
|
+
const from = ctx.from;
|
|
2259
|
+
if (!from || !callbackQuery) return;
|
|
2307
2260
|
const cbCtx = {
|
|
2308
2261
|
data: strippedData,
|
|
2309
2262
|
match: matchGroups,
|
|
2310
|
-
userId:
|
|
2311
|
-
username:
|
|
2312
|
-
inlineMessageId:
|
|
2263
|
+
userId: from.id,
|
|
2264
|
+
username: from.username,
|
|
2265
|
+
inlineMessageId: callbackQuery.inline_message_id,
|
|
2313
2266
|
chatId: ctx.chat?.id?.toString(),
|
|
2314
|
-
messageId:
|
|
2267
|
+
messageId: callbackQuery.message?.message_id,
|
|
2315
2268
|
async answer(text, alert) {
|
|
2316
2269
|
if (!answered) {
|
|
2317
2270
|
answered = true;
|
|
@@ -2320,7 +2273,7 @@ var InlineRouter = class {
|
|
|
2320
2273
|
},
|
|
2321
2274
|
async editMessage(text, opts) {
|
|
2322
2275
|
const styledButtons = opts?.keyboard ? prefixButtons(opts.keyboard, pluginName) : void 0;
|
|
2323
|
-
const inlineMsgId = ctx.callbackQuery
|
|
2276
|
+
const inlineMsgId = ctx.callbackQuery?.inline_message_id;
|
|
2324
2277
|
if (inlineMsgId && gramjsBotRef?.isConnected() && styledButtons) {
|
|
2325
2278
|
try {
|
|
2326
2279
|
const strippedHtml = stripCustomEmoji(text);
|
|
@@ -2334,10 +2287,9 @@ var InlineRouter = class {
|
|
|
2334
2287
|
});
|
|
2335
2288
|
return;
|
|
2336
2289
|
} catch (error) {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
);
|
|
2290
|
+
const errMsg = error?.errorMessage;
|
|
2291
|
+
if (errMsg === "MESSAGE_NOT_MODIFIED") return;
|
|
2292
|
+
log5.debug(`GramJS edit failed, falling back to Grammy: ${errMsg || error}`);
|
|
2341
2293
|
}
|
|
2342
2294
|
}
|
|
2343
2295
|
const replyMarkup = styledButtons ? toGrammyKeyboard(styledButtons) : void 0;
|
|
@@ -2357,7 +2309,7 @@ var InlineRouter = class {
|
|
|
2357
2309
|
await ctx.answerCallbackQuery();
|
|
2358
2310
|
}
|
|
2359
2311
|
} catch (error) {
|
|
2360
|
-
|
|
2312
|
+
log5.error({ err: error }, `Plugin "${pluginName}" callback handler failed`);
|
|
2361
2313
|
if (!answered) {
|
|
2362
2314
|
try {
|
|
2363
2315
|
await ctx.answerCallbackQuery({ text: "Error processing action" });
|
|
@@ -2368,17 +2320,19 @@ var InlineRouter = class {
|
|
|
2368
2320
|
}
|
|
2369
2321
|
async handleChosenResult(ctx, pluginName, plugin) {
|
|
2370
2322
|
try {
|
|
2371
|
-
const
|
|
2323
|
+
const chosenResult = ctx.chosenInlineResult;
|
|
2324
|
+
if (!chosenResult || !plugin.onChosenResult) return;
|
|
2325
|
+
const resultId = chosenResult.result_id;
|
|
2372
2326
|
const colonIdx = resultId.indexOf(":");
|
|
2373
2327
|
const strippedResultId = colonIdx > 0 ? resultId.slice(colonIdx + 1) : resultId;
|
|
2374
2328
|
const crCtx = {
|
|
2375
2329
|
resultId: strippedResultId,
|
|
2376
|
-
inlineMessageId:
|
|
2377
|
-
query:
|
|
2330
|
+
inlineMessageId: chosenResult.inline_message_id,
|
|
2331
|
+
query: chosenResult.query
|
|
2378
2332
|
};
|
|
2379
2333
|
await plugin.onChosenResult(crCtx);
|
|
2380
2334
|
} catch (error) {
|
|
2381
|
-
|
|
2335
|
+
log5.error({ err: error }, `Plugin "${pluginName}" chosen result handler failed`);
|
|
2382
2336
|
}
|
|
2383
2337
|
}
|
|
2384
2338
|
/**
|
|
@@ -2457,14 +2411,38 @@ function withTimeout(promise, ms, message) {
|
|
|
2457
2411
|
|
|
2458
2412
|
// src/agent/tools/plugin-loader.ts
|
|
2459
2413
|
import { readdirSync as readdirSync2, readFileSync as readFileSync5, existsSync as existsSync6, statSync } from "fs";
|
|
2460
|
-
import { join as
|
|
2414
|
+
import { join as join4 } from "path";
|
|
2461
2415
|
import { pathToFileURL } from "url";
|
|
2462
2416
|
import { execFile } from "child_process";
|
|
2417
|
+
|
|
2418
|
+
// src/agent/tools/plugin-config-store.ts
|
|
2419
|
+
function getPluginPriorities(db) {
|
|
2420
|
+
const rows = db.prepare("SELECT plugin_name, priority FROM plugin_config").all();
|
|
2421
|
+
const map = /* @__PURE__ */ new Map();
|
|
2422
|
+
for (const row of rows) {
|
|
2423
|
+
map.set(row.plugin_name, row.priority);
|
|
2424
|
+
}
|
|
2425
|
+
return map;
|
|
2426
|
+
}
|
|
2427
|
+
function setPluginPriority(db, pluginName, priority) {
|
|
2428
|
+
db.prepare(
|
|
2429
|
+
`INSERT INTO plugin_config (plugin_name, priority, updated_at)
|
|
2430
|
+
VALUES (?, ?, datetime('now'))
|
|
2431
|
+
ON CONFLICT(plugin_name) DO UPDATE SET
|
|
2432
|
+
priority = excluded.priority,
|
|
2433
|
+
updated_at = excluded.updated_at`
|
|
2434
|
+
).run(pluginName, priority);
|
|
2435
|
+
}
|
|
2436
|
+
function resetPluginPriority(db, pluginName) {
|
|
2437
|
+
db.prepare("DELETE FROM plugin_config WHERE plugin_name = ?").run(pluginName);
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// src/agent/tools/plugin-loader.ts
|
|
2463
2441
|
import { promisify } from "util";
|
|
2464
2442
|
|
|
2465
2443
|
// src/agent/tools/plugin-validator.ts
|
|
2466
2444
|
import { z } from "zod";
|
|
2467
|
-
var
|
|
2445
|
+
var log6 = createLogger("PluginValidator");
|
|
2468
2446
|
var ManifestSchema = z.object({
|
|
2469
2447
|
name: z.string().min(1).max(64).regex(
|
|
2470
2448
|
/^[a-z0-9][a-z0-9-]*$/,
|
|
@@ -2491,7 +2469,14 @@ var ManifestSchema = z.object({
|
|
|
2491
2469
|
inlinePerMinute: z.number().positive().optional(),
|
|
2492
2470
|
callbackPerMinute: z.number().positive().optional()
|
|
2493
2471
|
}).optional()
|
|
2494
|
-
}).optional()
|
|
2472
|
+
}).optional(),
|
|
2473
|
+
hooks: z.array(
|
|
2474
|
+
z.object({
|
|
2475
|
+
name: z.string().min(1).max(64),
|
|
2476
|
+
priority: z.number().optional(),
|
|
2477
|
+
description: z.string().max(256).optional()
|
|
2478
|
+
})
|
|
2479
|
+
).optional()
|
|
2495
2480
|
});
|
|
2496
2481
|
function validateManifest(raw) {
|
|
2497
2482
|
return ManifestSchema.parse(raw);
|
|
@@ -2500,20 +2485,20 @@ function validateToolDefs(defs, pluginName) {
|
|
|
2500
2485
|
const valid = [];
|
|
2501
2486
|
for (const def of defs) {
|
|
2502
2487
|
if (!def || typeof def !== "object") {
|
|
2503
|
-
|
|
2488
|
+
log6.warn(`[${pluginName}] tool is not an object, skipping`);
|
|
2504
2489
|
continue;
|
|
2505
2490
|
}
|
|
2506
2491
|
const t = def;
|
|
2507
2492
|
if (!t.name || typeof t.name !== "string") {
|
|
2508
|
-
|
|
2493
|
+
log6.warn(`[${pluginName}] tool missing 'name', skipping`);
|
|
2509
2494
|
continue;
|
|
2510
2495
|
}
|
|
2511
2496
|
if (!t.description || typeof t.description !== "string") {
|
|
2512
|
-
|
|
2497
|
+
log6.warn(`[${pluginName}] tool "${t.name}" missing 'description', skipping`);
|
|
2513
2498
|
continue;
|
|
2514
2499
|
}
|
|
2515
2500
|
if (!t.execute || typeof t.execute !== "function") {
|
|
2516
|
-
|
|
2501
|
+
log6.warn(`[${pluginName}] tool "${t.name}" missing 'execute' function, skipping`);
|
|
2517
2502
|
continue;
|
|
2518
2503
|
}
|
|
2519
2504
|
valid.push(t);
|
|
@@ -2550,8 +2535,19 @@ import { Address, SendMode } from "@ton/core";
|
|
|
2550
2535
|
|
|
2551
2536
|
// src/ton/tx-lock.ts
|
|
2552
2537
|
var pending = Promise.resolve();
|
|
2538
|
+
var TX_LOCK_TIMEOUT_MS = 6e4;
|
|
2553
2539
|
function withTxLock(fn) {
|
|
2554
|
-
const
|
|
2540
|
+
const guarded = () => {
|
|
2541
|
+
let timerId;
|
|
2542
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2543
|
+
timerId = setTimeout(
|
|
2544
|
+
() => reject(new Error("TON tx-lock timeout (60s)")),
|
|
2545
|
+
TX_LOCK_TIMEOUT_MS
|
|
2546
|
+
);
|
|
2547
|
+
});
|
|
2548
|
+
return Promise.race([fn(), timeoutPromise]).finally(() => clearTimeout(timerId));
|
|
2549
|
+
};
|
|
2550
|
+
const execute = pending.then(guarded, guarded);
|
|
2555
2551
|
pending = execute.then(
|
|
2556
2552
|
() => {
|
|
2557
2553
|
},
|
|
@@ -2562,25 +2558,25 @@ function withTxLock(fn) {
|
|
|
2562
2558
|
}
|
|
2563
2559
|
|
|
2564
2560
|
// src/ton/transfer.ts
|
|
2565
|
-
var
|
|
2561
|
+
var log7 = createLogger("TON");
|
|
2566
2562
|
async function sendTon(params) {
|
|
2567
2563
|
return withTxLock(async () => {
|
|
2568
2564
|
try {
|
|
2569
2565
|
const { toAddress: toAddress2, amount, comment = "", bounce = false } = params;
|
|
2570
2566
|
if (!Number.isFinite(amount) || amount <= 0) {
|
|
2571
|
-
|
|
2567
|
+
log7.error({ amount }, "Invalid transfer amount");
|
|
2572
2568
|
return null;
|
|
2573
2569
|
}
|
|
2574
2570
|
let recipientAddress;
|
|
2575
2571
|
try {
|
|
2576
2572
|
recipientAddress = Address.parse(toAddress2);
|
|
2577
2573
|
} catch (e) {
|
|
2578
|
-
|
|
2574
|
+
log7.error({ err: e }, `Invalid recipient address: ${toAddress2}`);
|
|
2579
2575
|
return null;
|
|
2580
2576
|
}
|
|
2581
2577
|
const keyPair = await getKeyPair();
|
|
2582
2578
|
if (!keyPair) {
|
|
2583
|
-
|
|
2579
|
+
log7.error("Wallet not initialized");
|
|
2584
2580
|
return null;
|
|
2585
2581
|
}
|
|
2586
2582
|
const wallet = WalletContractV5R1.create({
|
|
@@ -2604,21 +2600,22 @@ async function sendTon(params) {
|
|
|
2604
2600
|
]
|
|
2605
2601
|
});
|
|
2606
2602
|
const pseudoHash = `${seqno}_${Date.now()}_${amount.toFixed(2)}`;
|
|
2607
|
-
|
|
2603
|
+
log7.info(`Sent ${amount} TON to ${toAddress2.slice(0, 8)}... - seqno: ${seqno}`);
|
|
2608
2604
|
return pseudoHash;
|
|
2609
2605
|
} catch (error) {
|
|
2610
|
-
const
|
|
2611
|
-
|
|
2606
|
+
const err = error;
|
|
2607
|
+
const status = err?.status || err?.response?.status;
|
|
2608
|
+
if (status === 429 || status !== void 0 && status >= 500) {
|
|
2612
2609
|
invalidateTonClientCache();
|
|
2613
2610
|
}
|
|
2614
|
-
|
|
2611
|
+
log7.error({ err: error }, "Error sending TON");
|
|
2615
2612
|
throw error;
|
|
2616
2613
|
}
|
|
2617
2614
|
});
|
|
2618
2615
|
}
|
|
2619
2616
|
|
|
2620
2617
|
// src/utils/retry.ts
|
|
2621
|
-
var
|
|
2618
|
+
var log8 = createLogger("Utils");
|
|
2622
2619
|
var DEFAULT_OPTIONS = {
|
|
2623
2620
|
maxAttempts: RETRY_DEFAULT_MAX_ATTEMPTS,
|
|
2624
2621
|
baseDelayMs: RETRY_DEFAULT_BASE_DELAY_MS,
|
|
@@ -2642,7 +2639,7 @@ async function withRetry(fn, options = {}) {
|
|
|
2642
2639
|
return result;
|
|
2643
2640
|
} catch (error) {
|
|
2644
2641
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2645
|
-
|
|
2642
|
+
log8.warn(`Retry attempt ${attempt}/${opts.maxAttempts} failed: ${lastError.message}`);
|
|
2646
2643
|
if (attempt < opts.maxAttempts) {
|
|
2647
2644
|
const delay = Math.min(opts.baseDelayMs * Math.pow(2, attempt - 1), opts.maxDelayMs);
|
|
2648
2645
|
await sleep(delay);
|
|
@@ -3142,9 +3139,9 @@ var retryStatusCodes = /* @__PURE__ */ new Set([
|
|
|
3142
3139
|
var nullBodyResponses = /* @__PURE__ */ new Set([101, 204, 205, 304]);
|
|
3143
3140
|
function createFetch(globalOptions = {}) {
|
|
3144
3141
|
const {
|
|
3145
|
-
fetch:
|
|
3142
|
+
fetch: fetch3 = globalThis.fetch,
|
|
3146
3143
|
Headers: Headers2 = globalThis.Headers,
|
|
3147
|
-
AbortController:
|
|
3144
|
+
AbortController: AbortController3 = globalThis.AbortController
|
|
3148
3145
|
} = globalOptions;
|
|
3149
3146
|
async function onError(context) {
|
|
3150
3147
|
const isAbort = context.error && context.error.name === "AbortError" && !context.options.timeout || false;
|
|
@@ -3228,7 +3225,7 @@ function createFetch(globalOptions = {}) {
|
|
|
3228
3225
|
}
|
|
3229
3226
|
let abortTimeout;
|
|
3230
3227
|
if (!context.options.signal && context.options.timeout) {
|
|
3231
|
-
const controller = new
|
|
3228
|
+
const controller = new AbortController3();
|
|
3232
3229
|
abortTimeout = setTimeout(() => {
|
|
3233
3230
|
const error = new Error(
|
|
3234
3231
|
"[TimeoutError]: The operation was aborted due to timeout"
|
|
@@ -3240,7 +3237,7 @@ function createFetch(globalOptions = {}) {
|
|
|
3240
3237
|
context.options.signal = controller.signal;
|
|
3241
3238
|
}
|
|
3242
3239
|
try {
|
|
3243
|
-
context.response = await
|
|
3240
|
+
context.response = await fetch3(
|
|
3244
3241
|
context.request,
|
|
3245
3242
|
context.options
|
|
3246
3243
|
);
|
|
@@ -3302,7 +3299,7 @@ function createFetch(globalOptions = {}) {
|
|
|
3302
3299
|
return r3._data;
|
|
3303
3300
|
};
|
|
3304
3301
|
$fetch.raw = $fetchRaw;
|
|
3305
|
-
$fetch.native = (...args) =>
|
|
3302
|
+
$fetch.native = (...args) => fetch3(...args);
|
|
3306
3303
|
$fetch.create = (defaultOptions = {}, customGlobalOptions = {}) => createFetch({
|
|
3307
3304
|
...globalOptions,
|
|
3308
3305
|
...customGlobalOptions,
|
|
@@ -3333,10 +3330,10 @@ function createNodeFetch() {
|
|
|
3333
3330
|
return r(input, { ...nodeFetchOptions, ...init });
|
|
3334
3331
|
};
|
|
3335
3332
|
}
|
|
3336
|
-
var
|
|
3333
|
+
var fetch2 = globalThis.fetch ? (...args) => globalThis.fetch(...args) : createNodeFetch();
|
|
3337
3334
|
var Headers = globalThis.Headers || n;
|
|
3338
|
-
var
|
|
3339
|
-
var ofetch = createFetch({ fetch, Headers, AbortController });
|
|
3335
|
+
var AbortController2 = globalThis.AbortController || T;
|
|
3336
|
+
var ofetch = createFetch({ fetch: fetch2, Headers, AbortController: AbortController2 });
|
|
3340
3337
|
|
|
3341
3338
|
// node_modules/@ston-fi/api/dist/esm/index.js
|
|
3342
3339
|
var __create = Object.create;
|
|
@@ -6120,7 +6117,7 @@ var DEDUST_GAS = {
|
|
|
6120
6117
|
var NATIVE_TON_ADDRESS = "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c";
|
|
6121
6118
|
|
|
6122
6119
|
// src/agent/tools/dedust/asset-cache.ts
|
|
6123
|
-
var
|
|
6120
|
+
var log9 = createLogger("Tools");
|
|
6124
6121
|
var ASSET_LIST_URL = "https://assets.dedust.io/list.json";
|
|
6125
6122
|
var CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
6126
6123
|
var cachedAssets = [];
|
|
@@ -6139,7 +6136,7 @@ async function getAssetList() {
|
|
|
6139
6136
|
return cachedAssets;
|
|
6140
6137
|
} catch (error) {
|
|
6141
6138
|
if (cachedAssets.length > 0) {
|
|
6142
|
-
|
|
6139
|
+
log9.warn({ err: error }, "Asset list fetch failed, using stale cache");
|
|
6143
6140
|
return cachedAssets;
|
|
6144
6141
|
}
|
|
6145
6142
|
throw error;
|
|
@@ -6265,7 +6262,7 @@ async function getDedustQuote(fromAsset, toAsset, amount, slippage, log13) {
|
|
|
6265
6262
|
return null;
|
|
6266
6263
|
}
|
|
6267
6264
|
}
|
|
6268
|
-
async function executeSTONfiSwap(params,
|
|
6265
|
+
async function executeSTONfiSwap(params, _log) {
|
|
6269
6266
|
const { fromAsset, toAsset, amount, slippage = 0.01 } = params;
|
|
6270
6267
|
const walletData = loadWallet();
|
|
6271
6268
|
if (!walletData) {
|
|
@@ -6352,7 +6349,7 @@ async function executeSTONfiSwap(params, log13) {
|
|
|
6352
6349
|
};
|
|
6353
6350
|
});
|
|
6354
6351
|
}
|
|
6355
|
-
async function executeDedustSwap(params,
|
|
6352
|
+
async function executeDedustSwap(params, _log) {
|
|
6356
6353
|
const { fromAsset, toAsset, amount, slippage = 0.01 } = params;
|
|
6357
6354
|
const walletData = loadWallet();
|
|
6358
6355
|
if (!walletData) {
|
|
@@ -6372,7 +6369,7 @@ async function executeDedustSwap(params, log13) {
|
|
|
6372
6369
|
const fromDecimals = await getDecimals(isTonInput ? "ton" : fromAsset);
|
|
6373
6370
|
const toDecimals = await getDecimals(isTonOutput ? "ton" : toAsset);
|
|
6374
6371
|
const amountIn = toUnits(amount, fromDecimals);
|
|
6375
|
-
const { amountOut
|
|
6372
|
+
const { amountOut } = await pool.getEstimatedSwapOut({
|
|
6376
6373
|
assetIn: fromAssetObj,
|
|
6377
6374
|
amountIn
|
|
6378
6375
|
});
|
|
@@ -6648,7 +6645,10 @@ function createDnsSDK(log13) {
|
|
|
6648
6645
|
throw new PluginSDKError(`No active auction found for ${normalized}`, "OPERATION_FAILED");
|
|
6649
6646
|
}
|
|
6650
6647
|
try {
|
|
6651
|
-
await sendWalletMessage(
|
|
6648
|
+
await sendWalletMessage(
|
|
6649
|
+
Address6.parse(checkResult.nftAddress),
|
|
6650
|
+
toNano15(amount.toString())
|
|
6651
|
+
);
|
|
6652
6652
|
return { domain: normalized, bidAmount: amount.toString(), success: true };
|
|
6653
6653
|
} catch (err) {
|
|
6654
6654
|
if (err instanceof PluginSDKError) throw err;
|
|
@@ -6745,7 +6745,7 @@ import {
|
|
|
6745
6745
|
WalletContractV5R1 as WalletContractV5R14,
|
|
6746
6746
|
internal as internal4
|
|
6747
6747
|
} from "@ton/ton";
|
|
6748
|
-
import { Address as TonAddress, beginCell as beginCell13, SendMode as SendMode4 } from "@ton/core";
|
|
6748
|
+
import { Address as TonAddress, beginCell as beginCell13, SendMode as SendMode4, storeMessage } from "@ton/core";
|
|
6749
6749
|
|
|
6750
6750
|
// src/ton/format-transactions.ts
|
|
6751
6751
|
import { fromNano } from "@ton/ton";
|
|
@@ -7114,26 +7114,222 @@ function createTonSDK(log13, db) {
|
|
|
7114
7114
|
log13.error(`ton.getJettonInfo() TonAPI error: ${response.status}`);
|
|
7115
7115
|
return null;
|
|
7116
7116
|
}
|
|
7117
|
-
const data = await response.json();
|
|
7118
|
-
const metadata = data.metadata || {};
|
|
7119
|
-
const decimals = parseInt(metadata.decimals || "9");
|
|
7117
|
+
const data = await response.json();
|
|
7118
|
+
const metadata = data.metadata || {};
|
|
7119
|
+
const decimals = parseInt(metadata.decimals || "9");
|
|
7120
|
+
return {
|
|
7121
|
+
address: metadata.address || jettonAddress,
|
|
7122
|
+
name: metadata.name || "Unknown",
|
|
7123
|
+
symbol: metadata.symbol || "UNKNOWN",
|
|
7124
|
+
decimals,
|
|
7125
|
+
totalSupply: data.total_supply || "0",
|
|
7126
|
+
holdersCount: data.holders_count || 0,
|
|
7127
|
+
verified: data.verification === "whitelist",
|
|
7128
|
+
description: metadata.description || void 0,
|
|
7129
|
+
image: data.preview || metadata.image || void 0
|
|
7130
|
+
};
|
|
7131
|
+
} catch (err) {
|
|
7132
|
+
log13.error("ton.getJettonInfo() failed:", err);
|
|
7133
|
+
return null;
|
|
7134
|
+
}
|
|
7135
|
+
},
|
|
7136
|
+
async sendJetton(jettonAddress, to, amount, opts) {
|
|
7137
|
+
const walletData = loadWallet();
|
|
7138
|
+
if (!walletData) {
|
|
7139
|
+
throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
|
|
7140
|
+
}
|
|
7141
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
7142
|
+
throw new PluginSDKError("Amount must be a positive number", "OPERATION_FAILED");
|
|
7143
|
+
}
|
|
7144
|
+
try {
|
|
7145
|
+
TonAddress.parse(to);
|
|
7146
|
+
} catch {
|
|
7147
|
+
throw new PluginSDKError("Invalid recipient address", "INVALID_ADDRESS");
|
|
7148
|
+
}
|
|
7149
|
+
try {
|
|
7150
|
+
const jettonsResponse = await tonapiFetch(
|
|
7151
|
+
`/accounts/${encodeURIComponent(walletData.address)}/jettons`
|
|
7152
|
+
);
|
|
7153
|
+
if (!jettonsResponse.ok) {
|
|
7154
|
+
throw new PluginSDKError(
|
|
7155
|
+
`Failed to fetch jetton balances: ${jettonsResponse.status}`,
|
|
7156
|
+
"OPERATION_FAILED"
|
|
7157
|
+
);
|
|
7158
|
+
}
|
|
7159
|
+
const jettonsData = await jettonsResponse.json();
|
|
7160
|
+
const jettonBalance = findJettonBalance(jettonsData.balances ?? [], jettonAddress);
|
|
7161
|
+
if (!jettonBalance) {
|
|
7162
|
+
throw new PluginSDKError(
|
|
7163
|
+
`You don't own any of this jetton: ${jettonAddress}`,
|
|
7164
|
+
"OPERATION_FAILED"
|
|
7165
|
+
);
|
|
7166
|
+
}
|
|
7167
|
+
const senderJettonWallet = jettonBalance.wallet_address.address;
|
|
7168
|
+
const decimals = jettonBalance.jetton.decimals ?? 9;
|
|
7169
|
+
const currentBalance = BigInt(jettonBalance.balance);
|
|
7170
|
+
const amountStr = amount.toFixed(decimals);
|
|
7171
|
+
const [whole, frac = ""] = amountStr.split(".");
|
|
7172
|
+
const amountInUnits = BigInt(whole + (frac + "0".repeat(decimals)).slice(0, decimals));
|
|
7173
|
+
if (amountInUnits > currentBalance) {
|
|
7174
|
+
const balStr = formatTokenBalance(currentBalance, decimals);
|
|
7175
|
+
throw new PluginSDKError(
|
|
7176
|
+
`Insufficient balance. Have ${balStr}, need ${amount}`,
|
|
7177
|
+
"OPERATION_FAILED"
|
|
7178
|
+
);
|
|
7179
|
+
}
|
|
7180
|
+
const comment = opts?.comment;
|
|
7181
|
+
let forwardPayload = beginCell13().endCell();
|
|
7182
|
+
if (comment) {
|
|
7183
|
+
forwardPayload = beginCell13().storeUint(0, 32).storeStringTail(comment).endCell();
|
|
7184
|
+
}
|
|
7185
|
+
const JETTON_TRANSFER_OP = 260734629;
|
|
7186
|
+
const messageBody = beginCell13().storeUint(JETTON_TRANSFER_OP, 32).storeUint(0, 64).storeCoins(amountInUnits).storeAddress(TonAddress.parse(to)).storeAddress(TonAddress.parse(walletData.address)).storeBit(false).storeCoins(comment ? tonToNano("0.01") : BigInt(1)).storeBit(comment ? 1 : 0).storeRef(comment ? forwardPayload : beginCell13().endCell()).endCell();
|
|
7187
|
+
const keyPair = await getKeyPair();
|
|
7188
|
+
if (!keyPair) {
|
|
7189
|
+
throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
|
|
7190
|
+
}
|
|
7191
|
+
const seqno = await withTxLock(async () => {
|
|
7192
|
+
const MAX_SEND_ATTEMPTS = 3;
|
|
7193
|
+
let lastErr;
|
|
7194
|
+
for (let attempt = 1; attempt <= MAX_SEND_ATTEMPTS; attempt++) {
|
|
7195
|
+
try {
|
|
7196
|
+
const wallet = WalletContractV5R14.create({
|
|
7197
|
+
workchain: 0,
|
|
7198
|
+
publicKey: keyPair.publicKey
|
|
7199
|
+
});
|
|
7200
|
+
const client = await getCachedTonClient();
|
|
7201
|
+
const walletContract = client.open(wallet);
|
|
7202
|
+
const seq = await walletContract.getSeqno();
|
|
7203
|
+
await walletContract.sendTransfer({
|
|
7204
|
+
seqno: seq,
|
|
7205
|
+
secretKey: keyPair.secretKey,
|
|
7206
|
+
sendMode: SendMode4.PAY_GAS_SEPARATELY,
|
|
7207
|
+
messages: [
|
|
7208
|
+
internal4({
|
|
7209
|
+
to: TonAddress.parse(senderJettonWallet),
|
|
7210
|
+
value: tonToNano("0.05"),
|
|
7211
|
+
body: messageBody,
|
|
7212
|
+
bounce: true
|
|
7213
|
+
})
|
|
7214
|
+
]
|
|
7215
|
+
});
|
|
7216
|
+
return seq;
|
|
7217
|
+
} catch (err) {
|
|
7218
|
+
lastErr = err;
|
|
7219
|
+
const httpErr = isHttpError(err) ? err : void 0;
|
|
7220
|
+
const status = httpErr?.status || httpErr?.response?.status;
|
|
7221
|
+
const respData = httpErr?.response?.data;
|
|
7222
|
+
if (status === 429 || status && status >= 500) {
|
|
7223
|
+
invalidateTonClientCache();
|
|
7224
|
+
if (attempt < MAX_SEND_ATTEMPTS) {
|
|
7225
|
+
log13.warn(
|
|
7226
|
+
`sendJetton attempt ${attempt} failed (${status}): ${JSON.stringify(respData ?? err.message)}, retrying...`
|
|
7227
|
+
);
|
|
7228
|
+
await new Promise((r3) => setTimeout(r3, 1e3 * attempt));
|
|
7229
|
+
continue;
|
|
7230
|
+
}
|
|
7231
|
+
}
|
|
7232
|
+
throw err;
|
|
7233
|
+
}
|
|
7234
|
+
}
|
|
7235
|
+
throw lastErr;
|
|
7236
|
+
});
|
|
7237
|
+
return { success: true, seqno };
|
|
7238
|
+
} catch (err) {
|
|
7239
|
+
const outerHttpErr = isHttpError(err) ? err : void 0;
|
|
7240
|
+
const status = outerHttpErr?.status || outerHttpErr?.response?.status;
|
|
7241
|
+
if (status === 429 || status && status >= 500) {
|
|
7242
|
+
invalidateTonClientCache();
|
|
7243
|
+
}
|
|
7244
|
+
if (err instanceof PluginSDKError) throw err;
|
|
7245
|
+
throw new PluginSDKError(
|
|
7246
|
+
`Failed to send jetton: ${err instanceof Error ? err.message : String(err)}`,
|
|
7247
|
+
"OPERATION_FAILED"
|
|
7248
|
+
);
|
|
7249
|
+
}
|
|
7250
|
+
},
|
|
7251
|
+
async getJettonWalletAddress(ownerAddress, jettonAddress) {
|
|
7252
|
+
try {
|
|
7253
|
+
const response = await tonapiFetch(`/accounts/${encodeURIComponent(ownerAddress)}/jettons`);
|
|
7254
|
+
if (!response.ok) {
|
|
7255
|
+
log13.error(`ton.getJettonWalletAddress() TonAPI error: ${response.status}`);
|
|
7256
|
+
return null;
|
|
7257
|
+
}
|
|
7258
|
+
const data = await response.json();
|
|
7259
|
+
const match = findJettonBalance(data.balances ?? [], jettonAddress);
|
|
7260
|
+
return match ? match.wallet_address.address : null;
|
|
7261
|
+
} catch (err) {
|
|
7262
|
+
log13.error("ton.getJettonWalletAddress() failed:", err);
|
|
7263
|
+
return null;
|
|
7264
|
+
}
|
|
7265
|
+
},
|
|
7266
|
+
// ─── Signed Transfers (no broadcast) ──────────────────────────
|
|
7267
|
+
async createTransfer(to, amount, comment) {
|
|
7268
|
+
const walletData = loadWallet();
|
|
7269
|
+
if (!walletData) {
|
|
7270
|
+
throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
|
|
7271
|
+
}
|
|
7272
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
7273
|
+
throw new PluginSDKError("Amount must be a positive number", "OPERATION_FAILED");
|
|
7274
|
+
}
|
|
7275
|
+
try {
|
|
7276
|
+
TonAddress.parse(to);
|
|
7277
|
+
} catch {
|
|
7278
|
+
throw new PluginSDKError("Invalid TON address format", "INVALID_ADDRESS");
|
|
7279
|
+
}
|
|
7280
|
+
try {
|
|
7281
|
+
const keyPair = await getKeyPair();
|
|
7282
|
+
if (!keyPair) {
|
|
7283
|
+
throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
|
|
7284
|
+
}
|
|
7285
|
+
const boc = await withTxLock(async () => {
|
|
7286
|
+
const wallet = WalletContractV5R14.create({
|
|
7287
|
+
workchain: 0,
|
|
7288
|
+
publicKey: keyPair.publicKey
|
|
7289
|
+
});
|
|
7290
|
+
const client = await getCachedTonClient();
|
|
7291
|
+
const contract = client.open(wallet);
|
|
7292
|
+
const seqno = await contract.getSeqno();
|
|
7293
|
+
const transferCell = wallet.createTransfer({
|
|
7294
|
+
seqno,
|
|
7295
|
+
secretKey: keyPair.secretKey,
|
|
7296
|
+
sendMode: SendMode4.PAY_GAS_SEPARATELY,
|
|
7297
|
+
messages: [
|
|
7298
|
+
internal4({
|
|
7299
|
+
to: TonAddress.parse(to),
|
|
7300
|
+
value: tonToNano(amount),
|
|
7301
|
+
body: comment || "",
|
|
7302
|
+
bounce: false
|
|
7303
|
+
})
|
|
7304
|
+
]
|
|
7305
|
+
});
|
|
7306
|
+
const extMsg = beginCell13().store(
|
|
7307
|
+
storeMessage({
|
|
7308
|
+
info: {
|
|
7309
|
+
type: "external-in",
|
|
7310
|
+
dest: wallet.address,
|
|
7311
|
+
importFee: 0n
|
|
7312
|
+
},
|
|
7313
|
+
init: seqno === 0 ? wallet.init : void 0,
|
|
7314
|
+
body: transferCell
|
|
7315
|
+
})
|
|
7316
|
+
).endCell();
|
|
7317
|
+
return extMsg.toBoc().toString("base64");
|
|
7318
|
+
});
|
|
7120
7319
|
return {
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
decimals,
|
|
7125
|
-
totalSupply: data.total_supply || "0",
|
|
7126
|
-
holdersCount: data.holders_count || 0,
|
|
7127
|
-
verified: data.verification === "whitelist",
|
|
7128
|
-
description: metadata.description || void 0,
|
|
7129
|
-
image: data.preview || metadata.image || void 0
|
|
7320
|
+
boc,
|
|
7321
|
+
publicKey: walletData.publicKey,
|
|
7322
|
+
walletVersion: "v5r1"
|
|
7130
7323
|
};
|
|
7131
7324
|
} catch (err) {
|
|
7132
|
-
|
|
7133
|
-
|
|
7325
|
+
if (err instanceof PluginSDKError) throw err;
|
|
7326
|
+
throw new PluginSDKError(
|
|
7327
|
+
`Failed to create transfer: ${err instanceof Error ? err.message : String(err)}`,
|
|
7328
|
+
"OPERATION_FAILED"
|
|
7329
|
+
);
|
|
7134
7330
|
}
|
|
7135
7331
|
},
|
|
7136
|
-
async
|
|
7332
|
+
async createJettonTransfer(jettonAddress, to, amount, opts) {
|
|
7137
7333
|
const walletData = loadWallet();
|
|
7138
7334
|
if (!walletData) {
|
|
7139
7335
|
throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
|
|
@@ -7188,79 +7384,65 @@ function createTonSDK(log13, db) {
|
|
|
7188
7384
|
if (!keyPair) {
|
|
7189
7385
|
throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
|
|
7190
7386
|
}
|
|
7191
|
-
const
|
|
7192
|
-
const
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
log13.warn(
|
|
7225
|
-
`sendJetton attempt ${attempt} failed (${status}): ${JSON.stringify(respData ?? err.message)}, retrying...`
|
|
7226
|
-
);
|
|
7227
|
-
await new Promise((r3) => setTimeout(r3, 1e3 * attempt));
|
|
7228
|
-
continue;
|
|
7229
|
-
}
|
|
7230
|
-
}
|
|
7231
|
-
throw err;
|
|
7232
|
-
}
|
|
7233
|
-
}
|
|
7234
|
-
throw lastErr;
|
|
7387
|
+
const boc = await withTxLock(async () => {
|
|
7388
|
+
const wallet = WalletContractV5R14.create({
|
|
7389
|
+
workchain: 0,
|
|
7390
|
+
publicKey: keyPair.publicKey
|
|
7391
|
+
});
|
|
7392
|
+
const client = await getCachedTonClient();
|
|
7393
|
+
const walletContract = client.open(wallet);
|
|
7394
|
+
const seqno = await walletContract.getSeqno();
|
|
7395
|
+
const transferCell = wallet.createTransfer({
|
|
7396
|
+
seqno,
|
|
7397
|
+
secretKey: keyPair.secretKey,
|
|
7398
|
+
sendMode: SendMode4.PAY_GAS_SEPARATELY,
|
|
7399
|
+
messages: [
|
|
7400
|
+
internal4({
|
|
7401
|
+
to: TonAddress.parse(senderJettonWallet),
|
|
7402
|
+
value: tonToNano("0.05"),
|
|
7403
|
+
body: messageBody,
|
|
7404
|
+
bounce: true
|
|
7405
|
+
})
|
|
7406
|
+
]
|
|
7407
|
+
});
|
|
7408
|
+
const extMsg = beginCell13().store(
|
|
7409
|
+
storeMessage({
|
|
7410
|
+
info: {
|
|
7411
|
+
type: "external-in",
|
|
7412
|
+
dest: wallet.address,
|
|
7413
|
+
importFee: 0n
|
|
7414
|
+
},
|
|
7415
|
+
init: seqno === 0 ? wallet.init : void 0,
|
|
7416
|
+
body: transferCell
|
|
7417
|
+
})
|
|
7418
|
+
).endCell();
|
|
7419
|
+
return extMsg.toBoc().toString("base64");
|
|
7235
7420
|
});
|
|
7236
|
-
return {
|
|
7421
|
+
return {
|
|
7422
|
+
boc,
|
|
7423
|
+
publicKey: walletData.publicKey,
|
|
7424
|
+
walletVersion: "v5r1"
|
|
7425
|
+
};
|
|
7237
7426
|
} catch (err) {
|
|
7238
|
-
const status = err?.status || err?.response?.status;
|
|
7239
|
-
if (status === 429 || status && status >= 500) {
|
|
7240
|
-
invalidateTonClientCache();
|
|
7241
|
-
}
|
|
7242
7427
|
if (err instanceof PluginSDKError) throw err;
|
|
7243
7428
|
throw new PluginSDKError(
|
|
7244
|
-
`Failed to
|
|
7429
|
+
`Failed to create jetton transfer: ${err instanceof Error ? err.message : String(err)}`,
|
|
7245
7430
|
"OPERATION_FAILED"
|
|
7246
7431
|
);
|
|
7247
7432
|
}
|
|
7248
7433
|
},
|
|
7249
|
-
|
|
7434
|
+
getPublicKey() {
|
|
7250
7435
|
try {
|
|
7251
|
-
const
|
|
7252
|
-
|
|
7253
|
-
log13.error(`ton.getJettonWalletAddress() TonAPI error: ${response.status}`);
|
|
7254
|
-
return null;
|
|
7255
|
-
}
|
|
7256
|
-
const data = await response.json();
|
|
7257
|
-
const match = findJettonBalance(data.balances ?? [], jettonAddress);
|
|
7258
|
-
return match ? match.wallet_address.address : null;
|
|
7436
|
+
const wallet = loadWallet();
|
|
7437
|
+
return wallet?.publicKey ?? null;
|
|
7259
7438
|
} catch (err) {
|
|
7260
|
-
log13.error("ton.
|
|
7439
|
+
log13.error("ton.getPublicKey() failed:", err);
|
|
7261
7440
|
return null;
|
|
7262
7441
|
}
|
|
7263
7442
|
},
|
|
7443
|
+
getWalletVersion() {
|
|
7444
|
+
return "v5r1";
|
|
7445
|
+
},
|
|
7264
7446
|
// ─── NFT ─────────────────────────────────────────────────────
|
|
7265
7447
|
async getNftItems(ownerAddress) {
|
|
7266
7448
|
try {
|
|
@@ -7475,15 +7657,6 @@ function mapNftItem(item) {
|
|
|
7475
7657
|
// src/sdk/telegram.ts
|
|
7476
7658
|
import { Api as Api3 } from "telegram";
|
|
7477
7659
|
|
|
7478
|
-
// src/utils/gramjs-bigint.ts
|
|
7479
|
-
import { randomBytes } from "crypto";
|
|
7480
|
-
function toLong(value) {
|
|
7481
|
-
return typeof value === "bigint" ? value : BigInt(value);
|
|
7482
|
-
}
|
|
7483
|
-
function randomLong() {
|
|
7484
|
-
return randomBytes(8).readBigUInt64BE();
|
|
7485
|
-
}
|
|
7486
|
-
|
|
7487
7660
|
// src/sdk/telegram-utils.ts
|
|
7488
7661
|
function requireBridge(bridge) {
|
|
7489
7662
|
if (!bridge.isAvailable()) {
|
|
@@ -7657,7 +7830,7 @@ function createTelegramMessagesSDK(bridge, log13) {
|
|
|
7657
7830
|
limit,
|
|
7658
7831
|
maxId: 0,
|
|
7659
7832
|
minId: 0,
|
|
7660
|
-
hash: 0n
|
|
7833
|
+
hash: toLong(0n)
|
|
7661
7834
|
})
|
|
7662
7835
|
);
|
|
7663
7836
|
const messages = [];
|
|
@@ -7820,10 +7993,12 @@ function createTelegramMessagesSDK(bridge, log13) {
|
|
|
7820
7993
|
if (!messages || messages.length === 0 || !messages[0].media) {
|
|
7821
7994
|
return null;
|
|
7822
7995
|
}
|
|
7823
|
-
const
|
|
7824
|
-
|
|
7996
|
+
const media = messages[0].media;
|
|
7997
|
+
const doc = media && "document" in media ? media.document : void 0;
|
|
7998
|
+
const docSize = doc && "size" in doc ? doc.size : void 0;
|
|
7999
|
+
if (docSize && Number(docSize) > MAX_DOWNLOAD_SIZE) {
|
|
7825
8000
|
throw new PluginSDKError(
|
|
7826
|
-
`File too large (${Math.round(Number(
|
|
8001
|
+
`File too large (${Math.round(Number(docSize) / 1024 / 1024)}MB). Max: 50MB`,
|
|
7827
8002
|
"OPERATION_FAILED"
|
|
7828
8003
|
);
|
|
7829
8004
|
}
|
|
@@ -7847,7 +8022,7 @@ function createTelegramMessagesSDK(bridge, log13) {
|
|
|
7847
8022
|
const result = await gramJsClient.invoke(
|
|
7848
8023
|
new Api4.messages.GetScheduledHistory({
|
|
7849
8024
|
peer,
|
|
7850
|
-
hash: 0n
|
|
8025
|
+
hash: toLong(0n)
|
|
7851
8026
|
})
|
|
7852
8027
|
);
|
|
7853
8028
|
const messages = [];
|
|
@@ -8005,7 +8180,6 @@ function createTelegramSocialSDK(bridge, log13) {
|
|
|
8005
8180
|
requireBridge2();
|
|
8006
8181
|
try {
|
|
8007
8182
|
const client = getClient2();
|
|
8008
|
-
const Api4 = await getApi();
|
|
8009
8183
|
let entity;
|
|
8010
8184
|
try {
|
|
8011
8185
|
const id = typeof userId === "string" ? userId.replace("@", "") : userId.toString();
|
|
@@ -8329,7 +8503,7 @@ function createTelegramSocialSDK(bridge, log13) {
|
|
|
8329
8503
|
const user = await client.getInputEntity(userId.toString());
|
|
8330
8504
|
const invoiceData = {
|
|
8331
8505
|
peer: user,
|
|
8332
|
-
giftId:
|
|
8506
|
+
giftId: toLong(giftId),
|
|
8333
8507
|
hideName: opts?.anonymous ?? false,
|
|
8334
8508
|
message: opts?.message ? new Api4.TextWithEntities({ text: opts.message, entities: [] }) : void 0
|
|
8335
8509
|
};
|
|
@@ -8413,7 +8587,7 @@ function createTelegramSocialSDK(bridge, log13) {
|
|
|
8413
8587
|
const Api4 = await getApi();
|
|
8414
8588
|
const result = await client.invoke(
|
|
8415
8589
|
new Api4.payments.GetResaleStarGifts({
|
|
8416
|
-
giftId:
|
|
8590
|
+
giftId: toLong(giftId),
|
|
8417
8591
|
offset: "",
|
|
8418
8592
|
limit: limit ?? 50
|
|
8419
8593
|
})
|
|
@@ -8572,7 +8746,7 @@ function createTelegramSocialSDK(bridge, log13) {
|
|
|
8572
8746
|
new Api4.payments.UpdateStarGiftPrice({
|
|
8573
8747
|
stargift: new Api4.InputSavedStarGiftUser({ msgId }),
|
|
8574
8748
|
resellAmount: new Api4.StarsAmount({
|
|
8575
|
-
amount:
|
|
8749
|
+
amount: toLong(price),
|
|
8576
8750
|
nanos: 0
|
|
8577
8751
|
})
|
|
8578
8752
|
})
|
|
@@ -8703,7 +8877,7 @@ function createTelegramSocialSDK(bridge, log13) {
|
|
|
8703
8877
|
new Api4.payments.SendStarGiftOffer({
|
|
8704
8878
|
peer,
|
|
8705
8879
|
slug: giftSlug,
|
|
8706
|
-
price: new Api4.StarsAmount({ amount:
|
|
8880
|
+
price: new Api4.StarsAmount({ amount: toLong(price), nanos: 0 }),
|
|
8707
8881
|
duration,
|
|
8708
8882
|
randomId: randomLong()
|
|
8709
8883
|
})
|
|
@@ -8723,7 +8897,7 @@ function createTelegramSocialSDK(bridge, log13) {
|
|
|
8723
8897
|
const client = getClient2();
|
|
8724
8898
|
const { Api: Api4, helpers } = await import("telegram");
|
|
8725
8899
|
const { CustomFile } = await import("telegram/client/uploads.js");
|
|
8726
|
-
const { readFileSync:
|
|
8900
|
+
const { readFileSync: readFileSync8, statSync: statSync2 } = await import("fs");
|
|
8727
8901
|
const { basename: basename2 } = await import("path");
|
|
8728
8902
|
const { resolve: resolve2, normalize: normalize2 } = await import("path");
|
|
8729
8903
|
const { homedir: homedir3 } = await import("os");
|
|
@@ -8748,7 +8922,7 @@ function createTelegramSocialSDK(bridge, log13) {
|
|
|
8748
8922
|
}
|
|
8749
8923
|
const fileName = basename2(filePath);
|
|
8750
8924
|
const fileSize = statSync2(filePath).size;
|
|
8751
|
-
const fileBuffer =
|
|
8925
|
+
const fileBuffer = readFileSync8(filePath);
|
|
8752
8926
|
const isVideo = filePath.toLowerCase().match(/\.(mp4|mov|avi|webm|mkv|m4v)$/);
|
|
8753
8927
|
const customFile = new CustomFile(fileName, fileSize, filePath, fileBuffer);
|
|
8754
8928
|
const uploadedFile = await client.uploadFile({
|
|
@@ -8941,10 +9115,10 @@ function createTelegramSDK(bridge, log13) {
|
|
|
8941
9115
|
|
|
8942
9116
|
// src/sdk/secrets.ts
|
|
8943
9117
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
|
|
8944
|
-
import { join as
|
|
8945
|
-
var SECRETS_DIR =
|
|
9118
|
+
import { join as join3 } from "path";
|
|
9119
|
+
var SECRETS_DIR = join3(TELETON_ROOT, "plugins", "data");
|
|
8946
9120
|
function getSecretsPath(pluginName) {
|
|
8947
|
-
return
|
|
9121
|
+
return join3(SECRETS_DIR, `${pluginName}.secrets.json`);
|
|
8948
9122
|
}
|
|
8949
9123
|
function readSecretsFile(pluginName) {
|
|
8950
9124
|
const filePath = getSecretsPath(pluginName);
|
|
@@ -9102,7 +9276,7 @@ function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLi
|
|
|
9102
9276
|
const callbackLimit = manifest.rateLimits?.callbackPerMinute ?? 60;
|
|
9103
9277
|
const handlers = {};
|
|
9104
9278
|
function syncToRouter() {
|
|
9105
|
-
router
|
|
9279
|
+
router?.registerPlugin(pluginName, { ...handlers });
|
|
9106
9280
|
}
|
|
9107
9281
|
const sdk = {
|
|
9108
9282
|
get isAvailable() {
|
|
@@ -9269,6 +9443,36 @@ function createPluginSDK(deps, opts) {
|
|
|
9269
9443
|
);
|
|
9270
9444
|
if (result) cachedBot = result;
|
|
9271
9445
|
return result;
|
|
9446
|
+
},
|
|
9447
|
+
on(hookName, handler, onOpts) {
|
|
9448
|
+
if (!opts.hookRegistry) {
|
|
9449
|
+
log13.warn(`Hook registration unavailable \u2014 sdk.on() ignored`);
|
|
9450
|
+
return;
|
|
9451
|
+
}
|
|
9452
|
+
if (opts.declaredHooks) {
|
|
9453
|
+
const declared = opts.declaredHooks.some((h2) => h2.name === hookName);
|
|
9454
|
+
if (!declared) {
|
|
9455
|
+
log13.warn(`Hook "${hookName}" not declared in manifest \u2014 registration rejected`);
|
|
9456
|
+
return;
|
|
9457
|
+
}
|
|
9458
|
+
}
|
|
9459
|
+
const rawPriority = Number(onOpts?.priority) || 0;
|
|
9460
|
+
const clampedPriority = Math.max(-1e3, Math.min(1e3, rawPriority));
|
|
9461
|
+
if (rawPriority !== clampedPriority) {
|
|
9462
|
+
log13.debug(`Hook "${hookName}" priority ${rawPriority} clamped to ${clampedPriority}`);
|
|
9463
|
+
}
|
|
9464
|
+
const registered = opts.hookRegistry.register({
|
|
9465
|
+
pluginId: opts.pluginName,
|
|
9466
|
+
hookName,
|
|
9467
|
+
handler,
|
|
9468
|
+
priority: clampedPriority,
|
|
9469
|
+
globalPriority: opts.globalPriority ?? 0
|
|
9470
|
+
});
|
|
9471
|
+
if (!registered) {
|
|
9472
|
+
log13.warn(
|
|
9473
|
+
`Hook registration limit reached for plugin "${opts.pluginName}" \u2014 "${hookName}" rejected`
|
|
9474
|
+
);
|
|
9475
|
+
}
|
|
9272
9476
|
}
|
|
9273
9477
|
};
|
|
9274
9478
|
return Object.freeze(sdk);
|
|
@@ -9329,23 +9533,76 @@ function semverSatisfies(current, range) {
|
|
|
9329
9533
|
return cur.major === req.major && cur.minor === req.minor && cur.patch === req.patch;
|
|
9330
9534
|
}
|
|
9331
9535
|
|
|
9536
|
+
// src/sdk/hooks/registry.ts
|
|
9537
|
+
var MAX_HOOKS_PER_PLUGIN = 100;
|
|
9538
|
+
var HookRegistry = class {
|
|
9539
|
+
hooks = [];
|
|
9540
|
+
hookMap = /* @__PURE__ */ new Map();
|
|
9541
|
+
rebuildMap() {
|
|
9542
|
+
this.hookMap.clear();
|
|
9543
|
+
for (const h2 of this.hooks) {
|
|
9544
|
+
let arr = this.hookMap.get(h2.hookName);
|
|
9545
|
+
if (!arr) {
|
|
9546
|
+
arr = [];
|
|
9547
|
+
this.hookMap.set(h2.hookName, arr);
|
|
9548
|
+
}
|
|
9549
|
+
arr.push(h2);
|
|
9550
|
+
}
|
|
9551
|
+
for (const arr of this.hookMap.values()) {
|
|
9552
|
+
arr.sort((a, b) => {
|
|
9553
|
+
const aPrio = a.globalPriority + a.priority;
|
|
9554
|
+
const bPrio = b.globalPriority + b.priority;
|
|
9555
|
+
return aPrio - bPrio;
|
|
9556
|
+
});
|
|
9557
|
+
}
|
|
9558
|
+
}
|
|
9559
|
+
register(reg) {
|
|
9560
|
+
const pluginHookCount = this.hooks.filter((h2) => h2.pluginId === reg.pluginId).length;
|
|
9561
|
+
if (pluginHookCount >= MAX_HOOKS_PER_PLUGIN) {
|
|
9562
|
+
return false;
|
|
9563
|
+
}
|
|
9564
|
+
this.hooks.push({ ...reg, globalPriority: reg.globalPriority ?? 0 });
|
|
9565
|
+
this.rebuildMap();
|
|
9566
|
+
return true;
|
|
9567
|
+
}
|
|
9568
|
+
getHooks(name) {
|
|
9569
|
+
return this.hookMap.get(name) ?? [];
|
|
9570
|
+
}
|
|
9571
|
+
hasHooks(name) {
|
|
9572
|
+
return (this.hookMap.get(name)?.length ?? 0) > 0;
|
|
9573
|
+
}
|
|
9574
|
+
hasAnyHooks() {
|
|
9575
|
+
return this.hooks.length > 0;
|
|
9576
|
+
}
|
|
9577
|
+
unregister(pluginId) {
|
|
9578
|
+
const before = this.hooks.length;
|
|
9579
|
+
this.hooks = this.hooks.filter((h2) => h2.pluginId !== pluginId);
|
|
9580
|
+
this.rebuildMap();
|
|
9581
|
+
return before - this.hooks.length;
|
|
9582
|
+
}
|
|
9583
|
+
clear() {
|
|
9584
|
+
this.hooks = [];
|
|
9585
|
+
this.hookMap.clear();
|
|
9586
|
+
}
|
|
9587
|
+
};
|
|
9588
|
+
|
|
9332
9589
|
// src/agent/tools/plugin-loader.ts
|
|
9333
9590
|
var execFileAsync = promisify(execFile);
|
|
9334
|
-
var
|
|
9335
|
-
var PLUGIN_DATA_DIR =
|
|
9336
|
-
function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
9591
|
+
var log10 = createLogger("PluginLoader");
|
|
9592
|
+
var PLUGIN_DATA_DIR = join4(TELETON_ROOT, "plugins", "data");
|
|
9593
|
+
function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps, hookRegistry, pluginPriorities) {
|
|
9337
9594
|
let manifest = null;
|
|
9338
9595
|
if (raw.manifest) {
|
|
9339
9596
|
try {
|
|
9340
9597
|
manifest = validateManifest(raw.manifest);
|
|
9341
9598
|
} catch (err) {
|
|
9342
|
-
|
|
9599
|
+
log10.warn(
|
|
9343
9600
|
`[${entryName}] invalid manifest, ignoring: ${err instanceof Error ? err.message : err}`
|
|
9344
9601
|
);
|
|
9345
9602
|
}
|
|
9346
9603
|
}
|
|
9347
9604
|
if (!manifest) {
|
|
9348
|
-
const manifestPath =
|
|
9605
|
+
const manifestPath = join4(WORKSPACE_PATHS.PLUGINS_DIR, entryName, "manifest.json");
|
|
9349
9606
|
try {
|
|
9350
9607
|
if (existsSync6(manifestPath)) {
|
|
9351
9608
|
const diskManifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
|
|
@@ -9363,6 +9620,7 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
|
9363
9620
|
}
|
|
9364
9621
|
const pluginName = manifest?.name ?? entryName.replace(/\.js$/, "");
|
|
9365
9622
|
const pluginVersion = manifest?.version ?? "0.0.0";
|
|
9623
|
+
const globalPriority = pluginPriorities?.get(pluginName) ?? 0;
|
|
9366
9624
|
if (manifest?.dependencies) {
|
|
9367
9625
|
for (const dep of manifest.dependencies) {
|
|
9368
9626
|
if (!loadedModuleNames.includes(dep)) {
|
|
@@ -9422,10 +9680,10 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
|
9422
9680
|
},
|
|
9423
9681
|
migrate() {
|
|
9424
9682
|
try {
|
|
9425
|
-
const dbPath =
|
|
9683
|
+
const dbPath = join4(PLUGIN_DATA_DIR, `${pluginName}.db`);
|
|
9426
9684
|
pluginDb = openModuleDb(dbPath);
|
|
9427
9685
|
if (hasMigrate) {
|
|
9428
|
-
raw.migrate(pluginDb);
|
|
9686
|
+
raw.migrate?.(pluginDb);
|
|
9429
9687
|
const pluginTables = pluginDb.prepare(
|
|
9430
9688
|
`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`
|
|
9431
9689
|
).all().map((t) => t.name).filter((n2) => n2 !== "_kv");
|
|
@@ -9453,7 +9711,10 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
|
9453
9711
|
db: pluginDb,
|
|
9454
9712
|
sanitizedConfig,
|
|
9455
9713
|
pluginConfig,
|
|
9456
|
-
botManifest: manifest?.bot
|
|
9714
|
+
botManifest: manifest?.bot,
|
|
9715
|
+
hookRegistry,
|
|
9716
|
+
declaredHooks: manifest?.hooks,
|
|
9717
|
+
globalPriority
|
|
9457
9718
|
});
|
|
9458
9719
|
toolDefs = raw.tools(sdk);
|
|
9459
9720
|
} else if (Array.isArray(raw.tools)) {
|
|
@@ -9524,51 +9785,59 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
|
9524
9785
|
return module;
|
|
9525
9786
|
}
|
|
9526
9787
|
async function ensurePluginDeps(pluginDir, pluginEntry) {
|
|
9527
|
-
const pkgJson =
|
|
9528
|
-
const lockfile =
|
|
9529
|
-
const nodeModules =
|
|
9788
|
+
const pkgJson = join4(pluginDir, "package.json");
|
|
9789
|
+
const lockfile = join4(pluginDir, "package-lock.json");
|
|
9790
|
+
const nodeModules = join4(pluginDir, "node_modules");
|
|
9530
9791
|
if (!existsSync6(pkgJson)) return;
|
|
9531
9792
|
if (!existsSync6(lockfile)) {
|
|
9532
|
-
|
|
9793
|
+
log10.warn(
|
|
9533
9794
|
`[${pluginEntry}] package.json without package-lock.json \u2014 skipping (lockfile required)`
|
|
9534
9795
|
);
|
|
9535
9796
|
return;
|
|
9536
9797
|
}
|
|
9537
9798
|
if (existsSync6(nodeModules)) {
|
|
9538
|
-
const marker =
|
|
9799
|
+
const marker = join4(nodeModules, ".package-lock.json");
|
|
9539
9800
|
if (existsSync6(marker) && statSync(marker).mtimeMs >= statSync(lockfile).mtimeMs) return;
|
|
9540
9801
|
}
|
|
9541
|
-
|
|
9802
|
+
log10.info(`[${pluginEntry}] Installing dependencies...`);
|
|
9542
9803
|
try {
|
|
9543
9804
|
await execFileAsync("npm", ["ci", "--ignore-scripts", "--no-audit", "--no-fund"], {
|
|
9544
9805
|
cwd: pluginDir,
|
|
9545
9806
|
timeout: 6e4,
|
|
9546
9807
|
env: { ...process.env, NODE_ENV: "production" }
|
|
9547
9808
|
});
|
|
9548
|
-
|
|
9809
|
+
log10.info(`[${pluginEntry}] Dependencies installed`);
|
|
9549
9810
|
} catch (err) {
|
|
9550
|
-
|
|
9811
|
+
log10.error(`[${pluginEntry}] Failed to install deps: ${String(err).slice(0, 300)}`);
|
|
9551
9812
|
}
|
|
9552
9813
|
}
|
|
9553
|
-
async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
|
|
9814
|
+
async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps, db) {
|
|
9815
|
+
const hookRegistry = new HookRegistry();
|
|
9554
9816
|
const pluginsDir = WORKSPACE_PATHS.PLUGINS_DIR;
|
|
9555
9817
|
if (!existsSync6(pluginsDir)) {
|
|
9556
|
-
return [];
|
|
9818
|
+
return { modules: [], hookRegistry };
|
|
9819
|
+
}
|
|
9820
|
+
let pluginPriorities = /* @__PURE__ */ new Map();
|
|
9821
|
+
if (db) {
|
|
9822
|
+
try {
|
|
9823
|
+
pluginPriorities = getPluginPriorities(db);
|
|
9824
|
+
} catch {
|
|
9825
|
+
}
|
|
9557
9826
|
}
|
|
9558
|
-
const entries = readdirSync2(pluginsDir);
|
|
9827
|
+
const entries = readdirSync2(pluginsDir).sort();
|
|
9559
9828
|
const modules = [];
|
|
9560
9829
|
const loadedNames = /* @__PURE__ */ new Set();
|
|
9561
9830
|
const pluginPaths = [];
|
|
9562
9831
|
for (const entry of entries) {
|
|
9563
9832
|
if (entry === "data") continue;
|
|
9564
|
-
const entryPath =
|
|
9833
|
+
const entryPath = join4(pluginsDir, entry);
|
|
9565
9834
|
let modulePath = null;
|
|
9566
9835
|
try {
|
|
9567
9836
|
const stat = statSync(entryPath);
|
|
9568
9837
|
if (stat.isFile() && entry.endsWith(".js")) {
|
|
9569
9838
|
modulePath = entryPath;
|
|
9570
9839
|
} else if (stat.isDirectory()) {
|
|
9571
|
-
const indexPath =
|
|
9840
|
+
const indexPath = join4(entryPath, "index.js");
|
|
9572
9841
|
if (existsSync6(indexPath)) {
|
|
9573
9842
|
modulePath = indexPath;
|
|
9574
9843
|
}
|
|
@@ -9581,7 +9850,7 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
|
|
|
9581
9850
|
}
|
|
9582
9851
|
}
|
|
9583
9852
|
await Promise.allSettled(
|
|
9584
|
-
pluginPaths.filter(({ path }) => path.endsWith("index.js")).map(({ entry }) => ensurePluginDeps(
|
|
9853
|
+
pluginPaths.filter(({ path }) => path.endsWith("index.js")).map(({ entry }) => ensurePluginDeps(join4(pluginsDir, entry), entry))
|
|
9585
9854
|
);
|
|
9586
9855
|
const loadResults = await Promise.allSettled(
|
|
9587
9856
|
pluginPaths.map(async ({ entry, path }) => {
|
|
@@ -9592,7 +9861,7 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
|
|
|
9592
9861
|
);
|
|
9593
9862
|
for (const result of loadResults) {
|
|
9594
9863
|
if (result.status === "rejected") {
|
|
9595
|
-
|
|
9864
|
+
log10.error(
|
|
9596
9865
|
`Plugin failed to load: ${result.reason instanceof Error ? result.reason.message : result.reason}`
|
|
9597
9866
|
);
|
|
9598
9867
|
continue;
|
|
@@ -9600,21 +9869,29 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
|
|
|
9600
9869
|
const { entry, mod } = result.value;
|
|
9601
9870
|
try {
|
|
9602
9871
|
if (!mod.tools || typeof mod.tools !== "function" && !Array.isArray(mod.tools)) {
|
|
9603
|
-
|
|
9872
|
+
log10.warn(`Plugin "${entry}": no 'tools' array or function exported, skipping`);
|
|
9604
9873
|
continue;
|
|
9605
9874
|
}
|
|
9606
|
-
const adapted = adaptPlugin(
|
|
9875
|
+
const adapted = adaptPlugin(
|
|
9876
|
+
mod,
|
|
9877
|
+
entry,
|
|
9878
|
+
config,
|
|
9879
|
+
loadedModuleNames,
|
|
9880
|
+
sdkDeps,
|
|
9881
|
+
hookRegistry,
|
|
9882
|
+
pluginPriorities
|
|
9883
|
+
);
|
|
9607
9884
|
if (loadedNames.has(adapted.name)) {
|
|
9608
|
-
|
|
9885
|
+
log10.warn(`Plugin "${adapted.name}" already loaded, skipping duplicate from "${entry}"`);
|
|
9609
9886
|
continue;
|
|
9610
9887
|
}
|
|
9611
9888
|
loadedNames.add(adapted.name);
|
|
9612
9889
|
modules.push(adapted);
|
|
9613
9890
|
} catch (err) {
|
|
9614
|
-
|
|
9891
|
+
log10.error(`Plugin "${entry}" failed to adapt: ${err instanceof Error ? err.message : err}`);
|
|
9615
9892
|
}
|
|
9616
9893
|
}
|
|
9617
|
-
return modules;
|
|
9894
|
+
return { modules, hookRegistry };
|
|
9618
9895
|
}
|
|
9619
9896
|
|
|
9620
9897
|
// src/config/configurable-keys.ts
|
|
@@ -10122,6 +10399,40 @@ var CONFIGURABLE_KEYS = {
|
|
|
10122
10399
|
mask: identity,
|
|
10123
10400
|
parse: (v) => Number(v)
|
|
10124
10401
|
},
|
|
10402
|
+
// ─── TON Proxy ────────────────────────────────────────────────────
|
|
10403
|
+
"ton_proxy.enabled": {
|
|
10404
|
+
type: "boolean",
|
|
10405
|
+
category: "TON Proxy",
|
|
10406
|
+
label: "TON Proxy Enabled",
|
|
10407
|
+
description: "Enable Tonutils-Proxy for .ton site access (auto-downloads binary on first run)",
|
|
10408
|
+
sensitive: false,
|
|
10409
|
+
hotReload: "instant",
|
|
10410
|
+
validate: enumValidator(["true", "false"]),
|
|
10411
|
+
mask: identity,
|
|
10412
|
+
parse: (v) => v === "true"
|
|
10413
|
+
},
|
|
10414
|
+
"ton_proxy.port": {
|
|
10415
|
+
type: "number",
|
|
10416
|
+
category: "TON Proxy",
|
|
10417
|
+
label: "Proxy Port",
|
|
10418
|
+
description: "HTTP proxy port for .ton sites (default: 8080)",
|
|
10419
|
+
sensitive: false,
|
|
10420
|
+
hotReload: "restart",
|
|
10421
|
+
validate: numberInRange(1, 65535),
|
|
10422
|
+
mask: identity,
|
|
10423
|
+
parse: (v) => Number(v)
|
|
10424
|
+
},
|
|
10425
|
+
"ton_proxy.binary_path": {
|
|
10426
|
+
type: "string",
|
|
10427
|
+
category: "TON Proxy",
|
|
10428
|
+
label: "Binary Path",
|
|
10429
|
+
description: "Custom path to tonutils-proxy-cli (leave empty for auto-download)",
|
|
10430
|
+
sensitive: false,
|
|
10431
|
+
hotReload: "restart",
|
|
10432
|
+
validate: noValidation,
|
|
10433
|
+
mask: identity,
|
|
10434
|
+
parse: identity
|
|
10435
|
+
},
|
|
10125
10436
|
// ─── Capabilities ──────────────────────────────────────────────────
|
|
10126
10437
|
"capabilities.exec.mode": {
|
|
10127
10438
|
type: "enum",
|
|
@@ -10227,6 +10538,392 @@ function writeRawConfig(raw, configPath) {
|
|
|
10227
10538
|
writeFileSync3(fullPath, stringify2(raw), { encoding: "utf-8", mode: 384 });
|
|
10228
10539
|
}
|
|
10229
10540
|
|
|
10541
|
+
// src/ton-proxy/manager.ts
|
|
10542
|
+
import { spawn, execSync } from "child_process";
|
|
10543
|
+
import {
|
|
10544
|
+
existsSync as existsSync8,
|
|
10545
|
+
chmodSync,
|
|
10546
|
+
createWriteStream,
|
|
10547
|
+
readFileSync as readFileSync7,
|
|
10548
|
+
writeFileSync as writeFileSync4,
|
|
10549
|
+
unlinkSync
|
|
10550
|
+
} from "fs";
|
|
10551
|
+
import { mkdir } from "fs/promises";
|
|
10552
|
+
import { join as join5 } from "path";
|
|
10553
|
+
import { pipeline } from "stream/promises";
|
|
10554
|
+
var log11 = createLogger("TonProxy");
|
|
10555
|
+
var GITHUB_REPO = "xssnick/Tonutils-Proxy";
|
|
10556
|
+
var BINARY_DIR = join5(TELETON_ROOT, "bin");
|
|
10557
|
+
var PID_FILE = join5(TELETON_ROOT, "ton-proxy.pid");
|
|
10558
|
+
var HEALTH_CHECK_INTERVAL_MS = 3e4;
|
|
10559
|
+
var HEALTH_CHECK_TIMEOUT_MS = 5e3;
|
|
10560
|
+
var KILL_GRACE_MS = 5e3;
|
|
10561
|
+
var TonProxyManager = class {
|
|
10562
|
+
process = null;
|
|
10563
|
+
healthInterval = null;
|
|
10564
|
+
config;
|
|
10565
|
+
restartCount = 0;
|
|
10566
|
+
maxRestarts = 3;
|
|
10567
|
+
constructor(config) {
|
|
10568
|
+
this.config = config;
|
|
10569
|
+
}
|
|
10570
|
+
/** Resolve the binary path — user-specified or auto-detected */
|
|
10571
|
+
getBinaryPath() {
|
|
10572
|
+
if (this.config.binary_path) return this.config.binary_path;
|
|
10573
|
+
return join5(BINARY_DIR, getBinaryName());
|
|
10574
|
+
}
|
|
10575
|
+
/** Check if the binary exists on disk */
|
|
10576
|
+
isInstalled() {
|
|
10577
|
+
return existsSync8(this.getBinaryPath());
|
|
10578
|
+
}
|
|
10579
|
+
/** Whether the proxy process is currently running */
|
|
10580
|
+
isRunning() {
|
|
10581
|
+
return this.process !== null && this.process.exitCode === null;
|
|
10582
|
+
}
|
|
10583
|
+
/**
|
|
10584
|
+
* Download the latest CLI binary from GitHub releases.
|
|
10585
|
+
* Fetches the latest release tag, then downloads the platform-appropriate binary.
|
|
10586
|
+
*/
|
|
10587
|
+
async install() {
|
|
10588
|
+
const binaryName = getBinaryName();
|
|
10589
|
+
log11.info(`Downloading TON Proxy binary (${binaryName})...`);
|
|
10590
|
+
await mkdir(BINARY_DIR, { recursive: true });
|
|
10591
|
+
const releaseUrl = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
|
|
10592
|
+
const releaseRes = await fetch(releaseUrl, {
|
|
10593
|
+
headers: { Accept: "application/vnd.github.v3+json" }
|
|
10594
|
+
});
|
|
10595
|
+
if (!releaseRes.ok) {
|
|
10596
|
+
throw new Error(`Failed to fetch latest release: ${releaseRes.status}`);
|
|
10597
|
+
}
|
|
10598
|
+
const release = await releaseRes.json();
|
|
10599
|
+
const tag = release.tag_name;
|
|
10600
|
+
const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/${tag}/${binaryName}`;
|
|
10601
|
+
log11.info(`Downloading ${downloadUrl}`);
|
|
10602
|
+
const res = await fetch(downloadUrl);
|
|
10603
|
+
if (!res.ok || !res.body) {
|
|
10604
|
+
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
10605
|
+
}
|
|
10606
|
+
const dest = this.getBinaryPath();
|
|
10607
|
+
const fileStream = createWriteStream(dest);
|
|
10608
|
+
await pipeline(res.body, fileStream);
|
|
10609
|
+
chmodSync(dest, 493);
|
|
10610
|
+
log11.info(`TON Proxy installed: ${dest} (${tag})`);
|
|
10611
|
+
}
|
|
10612
|
+
/** Kill any orphan proxy process from a previous session */
|
|
10613
|
+
killOrphan() {
|
|
10614
|
+
if (existsSync8(PID_FILE)) {
|
|
10615
|
+
try {
|
|
10616
|
+
const pid = parseInt(readFileSync7(PID_FILE, "utf-8").trim(), 10);
|
|
10617
|
+
if (pid && !isNaN(pid)) {
|
|
10618
|
+
try {
|
|
10619
|
+
process.kill(pid, 0);
|
|
10620
|
+
log11.warn(`Killing orphan TON Proxy (PID ${pid}) from previous session`);
|
|
10621
|
+
process.kill(pid, "SIGTERM");
|
|
10622
|
+
} catch {
|
|
10623
|
+
}
|
|
10624
|
+
}
|
|
10625
|
+
unlinkSync(PID_FILE);
|
|
10626
|
+
} catch {
|
|
10627
|
+
}
|
|
10628
|
+
}
|
|
10629
|
+
try {
|
|
10630
|
+
const out = execSync(`ss -tlnp 2>/dev/null | grep ':${this.config.port} ' || true`, {
|
|
10631
|
+
encoding: "utf-8",
|
|
10632
|
+
timeout: 3e3
|
|
10633
|
+
});
|
|
10634
|
+
const pidMatch = out.match(/pid=(\d+)/);
|
|
10635
|
+
if (pidMatch) {
|
|
10636
|
+
const pid = parseInt(pidMatch[1], 10);
|
|
10637
|
+
log11.warn(`Port ${this.config.port} occupied by PID ${pid}, killing it`);
|
|
10638
|
+
try {
|
|
10639
|
+
process.kill(pid, "SIGTERM");
|
|
10640
|
+
} catch {
|
|
10641
|
+
}
|
|
10642
|
+
execSync("sleep 0.5");
|
|
10643
|
+
}
|
|
10644
|
+
} catch {
|
|
10645
|
+
}
|
|
10646
|
+
}
|
|
10647
|
+
/** Write PID to file for orphan detection */
|
|
10648
|
+
writePidFile(pid) {
|
|
10649
|
+
try {
|
|
10650
|
+
writeFileSync4(PID_FILE, String(pid), { mode: 384 });
|
|
10651
|
+
} catch {
|
|
10652
|
+
log11.warn("Failed to write TON Proxy PID file");
|
|
10653
|
+
}
|
|
10654
|
+
}
|
|
10655
|
+
/** Remove PID file */
|
|
10656
|
+
removePidFile() {
|
|
10657
|
+
try {
|
|
10658
|
+
if (existsSync8(PID_FILE)) unlinkSync(PID_FILE);
|
|
10659
|
+
} catch {
|
|
10660
|
+
}
|
|
10661
|
+
}
|
|
10662
|
+
/** Start the proxy process */
|
|
10663
|
+
async start() {
|
|
10664
|
+
if (this.isRunning()) {
|
|
10665
|
+
log11.warn("TON Proxy is already running");
|
|
10666
|
+
return;
|
|
10667
|
+
}
|
|
10668
|
+
this.restartCount = 0;
|
|
10669
|
+
this.maxRestarts = 3;
|
|
10670
|
+
this.killOrphan();
|
|
10671
|
+
if (!this.isInstalled()) {
|
|
10672
|
+
await this.install();
|
|
10673
|
+
}
|
|
10674
|
+
const binaryPath = this.getBinaryPath();
|
|
10675
|
+
const port = String(this.config.port);
|
|
10676
|
+
log11.info(`Starting TON Proxy on 127.0.0.1:${port}`);
|
|
10677
|
+
this.process = spawn(binaryPath, ["-addr", `127.0.0.1:${port}`], {
|
|
10678
|
+
cwd: BINARY_DIR,
|
|
10679
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
10680
|
+
detached: false
|
|
10681
|
+
});
|
|
10682
|
+
this.process.stdout?.on("data", (chunk) => {
|
|
10683
|
+
const line = chunk.toString().trim();
|
|
10684
|
+
if (line) log11.debug(`[proxy] ${line}`);
|
|
10685
|
+
});
|
|
10686
|
+
this.process.stderr?.on("data", (chunk) => {
|
|
10687
|
+
const line = chunk.toString().trim();
|
|
10688
|
+
if (line) log11.warn(`[proxy:err] ${line}`);
|
|
10689
|
+
});
|
|
10690
|
+
this.process.on("exit", (code, signal) => {
|
|
10691
|
+
log11.info(`TON Proxy exited (code=${code}, signal=${signal})`);
|
|
10692
|
+
this.process = null;
|
|
10693
|
+
this.removePidFile();
|
|
10694
|
+
if (code !== 0 && code !== null && this.restartCount < this.maxRestarts) {
|
|
10695
|
+
this.restartCount++;
|
|
10696
|
+
log11.warn(`Auto-restarting TON Proxy (attempt ${this.restartCount}/${this.maxRestarts})`);
|
|
10697
|
+
this.start().catch((err) => log11.error({ err }, "Failed to auto-restart TON Proxy"));
|
|
10698
|
+
}
|
|
10699
|
+
});
|
|
10700
|
+
this.process.on("error", (err) => {
|
|
10701
|
+
log11.error({ err }, "TON Proxy process error");
|
|
10702
|
+
this.process = null;
|
|
10703
|
+
});
|
|
10704
|
+
this.startHealthCheck();
|
|
10705
|
+
await new Promise((resolve2, reject) => {
|
|
10706
|
+
const timer = setTimeout(() => {
|
|
10707
|
+
if (this.isRunning()) {
|
|
10708
|
+
resolve2();
|
|
10709
|
+
} else {
|
|
10710
|
+
reject(new Error("TON Proxy process exited immediately"));
|
|
10711
|
+
}
|
|
10712
|
+
}, 1e3);
|
|
10713
|
+
this.process?.on("exit", () => {
|
|
10714
|
+
clearTimeout(timer);
|
|
10715
|
+
reject(new Error("TON Proxy process exited during startup"));
|
|
10716
|
+
});
|
|
10717
|
+
});
|
|
10718
|
+
if (this.process?.pid) this.writePidFile(this.process.pid);
|
|
10719
|
+
log11.info(`TON Proxy running on 127.0.0.1:${port} (PID ${this.process?.pid})`);
|
|
10720
|
+
}
|
|
10721
|
+
/** Stop the proxy process gracefully */
|
|
10722
|
+
async stop() {
|
|
10723
|
+
this.stopHealthCheck();
|
|
10724
|
+
if (!this.process) return;
|
|
10725
|
+
this.maxRestarts = 0;
|
|
10726
|
+
log11.info("Stopping TON Proxy...");
|
|
10727
|
+
return new Promise((resolve2) => {
|
|
10728
|
+
if (!this.process) {
|
|
10729
|
+
resolve2();
|
|
10730
|
+
return;
|
|
10731
|
+
}
|
|
10732
|
+
const forceKill = setTimeout(() => {
|
|
10733
|
+
if (this.process) {
|
|
10734
|
+
log11.warn("TON Proxy did not exit gracefully, sending SIGKILL");
|
|
10735
|
+
this.process.kill("SIGKILL");
|
|
10736
|
+
}
|
|
10737
|
+
}, KILL_GRACE_MS);
|
|
10738
|
+
this.process.on("exit", () => {
|
|
10739
|
+
clearTimeout(forceKill);
|
|
10740
|
+
this.process = null;
|
|
10741
|
+
this.removePidFile();
|
|
10742
|
+
resolve2();
|
|
10743
|
+
});
|
|
10744
|
+
this.process.kill("SIGTERM");
|
|
10745
|
+
});
|
|
10746
|
+
}
|
|
10747
|
+
/** Remove the downloaded binary from disk */
|
|
10748
|
+
async uninstall() {
|
|
10749
|
+
if (this.isRunning()) {
|
|
10750
|
+
await this.stop();
|
|
10751
|
+
}
|
|
10752
|
+
const binaryPath = this.getBinaryPath();
|
|
10753
|
+
if (existsSync8(binaryPath)) {
|
|
10754
|
+
const { unlink } = await import("fs/promises");
|
|
10755
|
+
await unlink(binaryPath);
|
|
10756
|
+
log11.info(`TON Proxy binary removed: ${binaryPath}`);
|
|
10757
|
+
}
|
|
10758
|
+
}
|
|
10759
|
+
/** Get proxy status for WebUI / tools */
|
|
10760
|
+
getStatus() {
|
|
10761
|
+
return {
|
|
10762
|
+
running: this.isRunning(),
|
|
10763
|
+
port: this.config.port,
|
|
10764
|
+
installed: this.isInstalled(),
|
|
10765
|
+
pid: this.process?.pid
|
|
10766
|
+
};
|
|
10767
|
+
}
|
|
10768
|
+
startHealthCheck() {
|
|
10769
|
+
this.stopHealthCheck();
|
|
10770
|
+
this.healthInterval = setInterval(() => {
|
|
10771
|
+
void this.checkHealth();
|
|
10772
|
+
}, HEALTH_CHECK_INTERVAL_MS);
|
|
10773
|
+
}
|
|
10774
|
+
stopHealthCheck() {
|
|
10775
|
+
if (this.healthInterval) {
|
|
10776
|
+
clearInterval(this.healthInterval);
|
|
10777
|
+
this.healthInterval = null;
|
|
10778
|
+
}
|
|
10779
|
+
}
|
|
10780
|
+
async checkHealth() {
|
|
10781
|
+
if (!this.isRunning()) return;
|
|
10782
|
+
try {
|
|
10783
|
+
const controller = new AbortController();
|
|
10784
|
+
const timeout = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);
|
|
10785
|
+
const res = await fetch(`http://127.0.0.1:${this.config.port}/`, {
|
|
10786
|
+
signal: controller.signal
|
|
10787
|
+
}).catch(() => null);
|
|
10788
|
+
clearTimeout(timeout);
|
|
10789
|
+
if (!res) {
|
|
10790
|
+
log11.warn("TON Proxy health check failed (no response)");
|
|
10791
|
+
}
|
|
10792
|
+
} catch {
|
|
10793
|
+
}
|
|
10794
|
+
}
|
|
10795
|
+
};
|
|
10796
|
+
function getBinaryName() {
|
|
10797
|
+
const platform = process.platform;
|
|
10798
|
+
const arch = process.arch;
|
|
10799
|
+
let os;
|
|
10800
|
+
switch (platform) {
|
|
10801
|
+
case "linux":
|
|
10802
|
+
os = "linux";
|
|
10803
|
+
break;
|
|
10804
|
+
case "darwin":
|
|
10805
|
+
os = "darwin";
|
|
10806
|
+
break;
|
|
10807
|
+
case "win32":
|
|
10808
|
+
os = "windows";
|
|
10809
|
+
break;
|
|
10810
|
+
default:
|
|
10811
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
10812
|
+
}
|
|
10813
|
+
let cpuArch;
|
|
10814
|
+
switch (arch) {
|
|
10815
|
+
case "x64":
|
|
10816
|
+
cpuArch = "amd64";
|
|
10817
|
+
break;
|
|
10818
|
+
case "arm64":
|
|
10819
|
+
cpuArch = "arm64";
|
|
10820
|
+
break;
|
|
10821
|
+
default:
|
|
10822
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
10823
|
+
}
|
|
10824
|
+
const ext = platform === "win32" ? ".exe" : "";
|
|
10825
|
+
return `tonutils-proxy-cli-${os}-${cpuArch}${ext}`;
|
|
10826
|
+
}
|
|
10827
|
+
|
|
10828
|
+
// src/ton-proxy/tools.ts
|
|
10829
|
+
import { Type } from "@sinclair/typebox";
|
|
10830
|
+
var proxyManager = null;
|
|
10831
|
+
function setProxyManager(mgr) {
|
|
10832
|
+
proxyManager = mgr;
|
|
10833
|
+
}
|
|
10834
|
+
var tonProxyStatusTool = {
|
|
10835
|
+
name: "ton_proxy_status",
|
|
10836
|
+
description: "Check the status of the TON Proxy (Tonutils-Proxy). Returns whether the proxy is running, installed, the port, and PID.",
|
|
10837
|
+
parameters: Type.Object({})
|
|
10838
|
+
};
|
|
10839
|
+
var tonProxyStatusExecutor = async () => {
|
|
10840
|
+
if (!proxyManager) {
|
|
10841
|
+
return { success: true, data: { enabled: false, message: "TON Proxy is not configured" } };
|
|
10842
|
+
}
|
|
10843
|
+
return { success: true, data: proxyManager.getStatus() };
|
|
10844
|
+
};
|
|
10845
|
+
|
|
10846
|
+
// src/ton-proxy/module.ts
|
|
10847
|
+
var log12 = createLogger("TonProxyModule");
|
|
10848
|
+
var manager = null;
|
|
10849
|
+
function getTonProxyManager() {
|
|
10850
|
+
return manager;
|
|
10851
|
+
}
|
|
10852
|
+
function setTonProxyManager(mgr) {
|
|
10853
|
+
manager = mgr;
|
|
10854
|
+
setProxyManager(mgr);
|
|
10855
|
+
}
|
|
10856
|
+
var tonProxyModule = {
|
|
10857
|
+
name: "ton-proxy",
|
|
10858
|
+
version: "1.0.0",
|
|
10859
|
+
tools(config) {
|
|
10860
|
+
if (!config.ton_proxy?.enabled) return [];
|
|
10861
|
+
return [{ tool: tonProxyStatusTool, executor: tonProxyStatusExecutor }];
|
|
10862
|
+
},
|
|
10863
|
+
async start(context) {
|
|
10864
|
+
if (!context.config.ton_proxy?.enabled) return;
|
|
10865
|
+
const proxyConfig = context.config.ton_proxy;
|
|
10866
|
+
manager = new TonProxyManager({
|
|
10867
|
+
enabled: proxyConfig.enabled,
|
|
10868
|
+
port: proxyConfig.port,
|
|
10869
|
+
binary_path: proxyConfig.binary_path
|
|
10870
|
+
});
|
|
10871
|
+
setProxyManager(manager);
|
|
10872
|
+
try {
|
|
10873
|
+
await manager.start();
|
|
10874
|
+
log12.info(`TON Proxy started on port ${proxyConfig.port}`);
|
|
10875
|
+
} catch (err) {
|
|
10876
|
+
log12.error({ err }, "Failed to start TON Proxy");
|
|
10877
|
+
manager = null;
|
|
10878
|
+
}
|
|
10879
|
+
},
|
|
10880
|
+
async stop() {
|
|
10881
|
+
if (manager) {
|
|
10882
|
+
await manager.stop();
|
|
10883
|
+
manager = null;
|
|
10884
|
+
setProxyManager(null);
|
|
10885
|
+
}
|
|
10886
|
+
}
|
|
10887
|
+
};
|
|
10888
|
+
var module_default = tonProxyModule;
|
|
10889
|
+
|
|
10890
|
+
// src/agent/hooks/user-hook-store.ts
|
|
10891
|
+
function getUserHookConfig(db, key) {
|
|
10892
|
+
const row = db.prepare("SELECT value FROM user_hook_config WHERE key = ?").get(key);
|
|
10893
|
+
return row?.value ?? null;
|
|
10894
|
+
}
|
|
10895
|
+
function setUserHookConfig(db, key, value) {
|
|
10896
|
+
db.prepare(
|
|
10897
|
+
`INSERT INTO user_hook_config (key, value, updated_at)
|
|
10898
|
+
VALUES (?, ?, datetime('now'))
|
|
10899
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
10900
|
+
value = excluded.value,
|
|
10901
|
+
updated_at = excluded.updated_at`
|
|
10902
|
+
).run(key, value);
|
|
10903
|
+
}
|
|
10904
|
+
function getBlocklistConfig(db) {
|
|
10905
|
+
const enabled = getUserHookConfig(db, "blocklist.enabled");
|
|
10906
|
+
const keywords = getUserHookConfig(db, "blocklist.keywords");
|
|
10907
|
+
const message = getUserHookConfig(db, "blocklist.message");
|
|
10908
|
+
return {
|
|
10909
|
+
enabled: enabled === "true",
|
|
10910
|
+
keywords: keywords ? JSON.parse(keywords) : [],
|
|
10911
|
+
message: message ?? ""
|
|
10912
|
+
};
|
|
10913
|
+
}
|
|
10914
|
+
function setBlocklistConfig(db, config) {
|
|
10915
|
+
setUserHookConfig(db, "blocklist.enabled", String(config.enabled));
|
|
10916
|
+
setUserHookConfig(db, "blocklist.keywords", JSON.stringify(config.keywords));
|
|
10917
|
+
setUserHookConfig(db, "blocklist.message", config.message);
|
|
10918
|
+
}
|
|
10919
|
+
function getTriggersConfig(db) {
|
|
10920
|
+
const raw = getUserHookConfig(db, "triggers");
|
|
10921
|
+
return raw ? JSON.parse(raw) : [];
|
|
10922
|
+
}
|
|
10923
|
+
function setTriggersConfig(db, triggers) {
|
|
10924
|
+
setUserHookConfig(db, "triggers", JSON.stringify(triggers));
|
|
10925
|
+
}
|
|
10926
|
+
|
|
10230
10927
|
export {
|
|
10231
10928
|
loadConfig,
|
|
10232
10929
|
configExists,
|
|
@@ -10268,6 +10965,9 @@ export {
|
|
|
10268
10965
|
toUnits,
|
|
10269
10966
|
fromUnits,
|
|
10270
10967
|
dexFactory,
|
|
10968
|
+
getPluginPriorities,
|
|
10969
|
+
setPluginPriority,
|
|
10970
|
+
resetPluginPriority,
|
|
10271
10971
|
InlineRouter,
|
|
10272
10972
|
adaptPlugin,
|
|
10273
10973
|
ensurePluginDeps,
|
|
@@ -10277,5 +10977,13 @@ export {
|
|
|
10277
10977
|
setNestedValue,
|
|
10278
10978
|
deleteNestedValue,
|
|
10279
10979
|
readRawConfig,
|
|
10280
|
-
writeRawConfig
|
|
10980
|
+
writeRawConfig,
|
|
10981
|
+
TonProxyManager,
|
|
10982
|
+
getTonProxyManager,
|
|
10983
|
+
setTonProxyManager,
|
|
10984
|
+
module_default,
|
|
10985
|
+
getBlocklistConfig,
|
|
10986
|
+
setBlocklistConfig,
|
|
10987
|
+
getTriggersConfig,
|
|
10988
|
+
setTriggersConfig
|
|
10281
10989
|
};
|