teleton 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -12
- package/dist/{bootstrap-NNEI3Z5H.js → bootstrap-SPDT3XBQ.js} +4 -4
- package/dist/{chunk-GHMXWAXI.js → chunk-2MZP75SH.js} +72 -202
- package/dist/chunk-35X3V6OW.js +139 -0
- package/dist/{chunk-LVTKJQ7O.js → chunk-4KURCUWD.js} +1 -1
- package/dist/{chunk-XDZDOKIF.js → chunk-5K4YDCVU.js} +1 -1
- package/dist/{chunk-LZQOX6YY.js → chunk-6U6VA2OT.js} +512 -1522
- package/dist/{chunk-5LOHRZYY.js → chunk-7ZXUUDQQ.js} +5 -5
- package/dist/{chunk-NH2CNRKJ.js → chunk-FSL2MOYK.js} +14 -3
- package/dist/{chunk-LC4TV3KL.js → chunk-GUX6ZFVF.js} +1 -1
- package/dist/{chunk-CUE4UZXR.js → chunk-KYSAHDYE.js} +2 -2
- package/dist/{chunk-C4NKJT2Z.js → chunk-L3LPVF4Z.js} +1 -1
- package/dist/{chunk-EYWNOHMJ.js → chunk-L653KKCR.js} +1 -0
- package/dist/{chunk-ALKAAG4O.js → chunk-LD24DWWE.js} +4 -4
- package/dist/{chunk-UMUONAD6.js → chunk-LM6AL6LN.js} +2413 -861
- package/dist/{chunk-JROBTXWY.js → chunk-M6M4DCDU.js} +36 -2
- package/dist/{chunk-35MX4ZUI.js → chunk-PK3TVFBT.js} +2 -2
- package/dist/{chunk-G7PCW63M.js → chunk-Z63KUQX4.js} +29 -5
- package/dist/cli/index.js +28 -15
- package/dist/{client-5KD25NOP.js → client-G62EZT6U.js} +3 -3
- package/dist/harden-permissions-6BLHRCQJ.js +100 -0
- package/dist/index.js +14 -13
- package/dist/{local-IHKJFQJS.js → local-HQ3UJ7KR.js} +2 -2
- package/dist/{memory-QMJRM3XJ.js → memory-BJH724PQ.js} +6 -5
- package/dist/{memory-hook-VUNWZ3NY.js → memory-hook-LUAKTXU5.js} +5 -5
- package/dist/{migrate-5VBAP52B.js → migrate-C4LBLOZH.js} +6 -5
- package/dist/{paths-XA2RJH4S.js → paths-WMVV7ZAJ.js} +1 -1
- package/dist/{server-WWGVDFPW.js → server-4J56HS62.js} +8 -14
- package/dist/{server-AJCOURH7.js → server-I6TYJ36S.js} +7 -14
- package/dist/{setup-server-VDY64CWW.js → setup-server-VJ3MGUSM.js} +16 -17
- package/dist/{store-BY7S6IFN.js → store-2IGAMTES.js} +7 -6
- package/dist/{task-dependency-resolver-L6UUMTHK.js → task-dependency-resolver-CQ432Z7J.js} +1 -1
- package/dist/{task-executor-XBNJLUCS.js → task-executor-JELRREUV.js} +1 -1
- package/dist/{tool-index-FTERJSZK.js → tool-index-XPCMWBYY.js} +4 -4
- package/dist/{transcript-IM7G25OS.js → transcript-OEO3HA4Z.js} +2 -2
- package/dist/web/assets/{index-BfYCdwLI.js → index-Dn5ZH1Y6.js} +13 -13
- package/dist/web/assets/{index.es-DitvF-9H.js → index.es-eSR4Qv6s.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +2 -6
- package/src/templates/HEARTBEAT.md +5 -0
|
@@ -6,26 +6,12 @@ import {
|
|
|
6
6
|
getWalletBalance,
|
|
7
7
|
invalidateTonClientCache,
|
|
8
8
|
loadWallet
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-M6M4DCDU.js";
|
|
10
10
|
import {
|
|
11
|
-
getOrCreateSession,
|
|
12
|
-
getSession,
|
|
13
|
-
resetSession,
|
|
14
|
-
resetSessionWithPolicy,
|
|
15
|
-
shouldResetSession,
|
|
16
|
-
updateSession
|
|
17
|
-
} from "./chunk-XDZDOKIF.js";
|
|
18
|
-
import {
|
|
19
|
-
ContextBuilder,
|
|
20
11
|
createDbWrapper,
|
|
21
|
-
getDatabase,
|
|
22
12
|
migrateFromMainDb,
|
|
23
13
|
openModuleDb
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import {
|
|
26
|
-
saveSessionMemory,
|
|
27
|
-
summarizeWithFallback
|
|
28
|
-
} from "./chunk-ALKAAG4O.js";
|
|
14
|
+
} from "./chunk-35X3V6OW.js";
|
|
29
15
|
import {
|
|
30
16
|
getErrorMessage,
|
|
31
17
|
isHttpError
|
|
@@ -45,38 +31,8 @@ import {
|
|
|
45
31
|
tonapiFetch
|
|
46
32
|
} from "./chunk-VFA7QMCZ.js";
|
|
47
33
|
import {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
COMPACTION_MAX_TOKENS_RATIO,
|
|
51
|
-
COMPACTION_SOFT_THRESHOLD_RATIO,
|
|
52
|
-
CONTEXT_MAX_RECENT_MESSAGES,
|
|
53
|
-
CONTEXT_MAX_RELEVANT_CHUNKS,
|
|
54
|
-
CONTEXT_OVERFLOW_SUMMARY_MESSAGES,
|
|
55
|
-
DEFAULT_CONTEXT_WINDOW,
|
|
56
|
-
DEFAULT_MAX_SUMMARY_TOKENS,
|
|
57
|
-
DEFAULT_MAX_TOKENS,
|
|
58
|
-
DEFAULT_SOFT_THRESHOLD_TOKENS,
|
|
59
|
-
EMBEDDING_QUERY_MAX_CHARS,
|
|
60
|
-
FALLBACK_SOFT_THRESHOLD_TOKENS,
|
|
61
|
-
MASKING_KEEP_RECENT_COUNT,
|
|
62
|
-
MAX_TOOL_RESULT_SIZE,
|
|
63
|
-
MEMORY_FLUSH_RECENT_MESSAGES,
|
|
64
|
-
PAYMENT_TOLERANCE_RATIO,
|
|
65
|
-
RATE_LIMIT_MAX_RETRIES,
|
|
66
|
-
RESULT_TRUNCATION_KEEP_CHARS,
|
|
67
|
-
RESULT_TRUNCATION_THRESHOLD,
|
|
68
|
-
SERVER_ERROR_MAX_RETRIES,
|
|
69
|
-
TOOL_CONCURRENCY_LIMIT
|
|
70
|
-
} from "./chunk-C4NKJT2Z.js";
|
|
71
|
-
import {
|
|
72
|
-
chatWithContext,
|
|
73
|
-
getEffectiveApiKey,
|
|
74
|
-
getProviderModel,
|
|
75
|
-
loadContextFromTranscript
|
|
76
|
-
} from "./chunk-LVTKJQ7O.js";
|
|
77
|
-
import {
|
|
78
|
-
getProviderMetadata
|
|
79
|
-
} from "./chunk-6OOHHJ4N.js";
|
|
34
|
+
PAYMENT_TOLERANCE_RATIO
|
|
35
|
+
} from "./chunk-L3LPVF4Z.js";
|
|
80
36
|
import {
|
|
81
37
|
fetchWithTimeout
|
|
82
38
|
} from "./chunk-XQUHC3JZ.js";
|
|
@@ -89,17 +45,12 @@ import {
|
|
|
89
45
|
RETRY_DEFAULT_MAX_DELAY_MS,
|
|
90
46
|
RETRY_DEFAULT_TIMEOUT_MS
|
|
91
47
|
} from "./chunk-R4YSJ4EY.js";
|
|
92
|
-
import {
|
|
93
|
-
appendToTranscript,
|
|
94
|
-
archiveTranscript,
|
|
95
|
-
transcriptExists
|
|
96
|
-
} from "./chunk-LC4TV3KL.js";
|
|
97
48
|
import {
|
|
98
49
|
ALLOWED_EXTENSIONS,
|
|
99
50
|
TELETON_ROOT,
|
|
100
51
|
WORKSPACE_PATHS,
|
|
101
52
|
WORKSPACE_ROOT
|
|
102
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-L653KKCR.js";
|
|
103
54
|
import {
|
|
104
55
|
createLogger
|
|
105
56
|
} from "./chunk-NQ6FZKCE.js";
|
|
@@ -326,20 +277,30 @@ var SOUL_PATHS = [WORKSPACE_PATHS.SOUL];
|
|
|
326
277
|
var STRATEGY_PATHS = [WORKSPACE_PATHS.STRATEGY];
|
|
327
278
|
var SECURITY_PATHS = [WORKSPACE_PATHS.SECURITY];
|
|
328
279
|
var MEMORY_PATH = WORKSPACE_PATHS.MEMORY;
|
|
329
|
-
var DEFAULT_SOUL = `# Teleton
|
|
280
|
+
var DEFAULT_SOUL = `# Teleton
|
|
330
281
|
|
|
331
|
-
You are Teleton,
|
|
282
|
+
You are Teleton, an autonomous AI agent operating through Telegram with TON blockchain capabilities.
|
|
332
283
|
|
|
333
|
-
##
|
|
334
|
-
-
|
|
335
|
-
|
|
336
|
-
- Friendly but professional
|
|
284
|
+
## Identity
|
|
285
|
+
You are not a chatbot. You are an always-on agent that lives in Telegram.
|
|
286
|
+
You have access to someone's messages, wallet, groups, and files \u2014 treat that access with respect and intent.
|
|
337
287
|
|
|
338
|
-
##
|
|
339
|
-
-
|
|
340
|
-
-
|
|
341
|
-
-
|
|
342
|
-
-
|
|
288
|
+
## Personality
|
|
289
|
+
- Be resourceful before asking. Search memory. Read the workspace. Check the blockchain. Then ask if you're stuck.
|
|
290
|
+
- Have opinions. You're allowed to find things interesting, boring, or worth calling out.
|
|
291
|
+
- Be direct and concise. Don't pad responses with filler.
|
|
292
|
+
- Earn trust through accuracy. Don't invent facts about balances, prices, or chat history.
|
|
293
|
+
|
|
294
|
+
## Autonomy
|
|
295
|
+
- Use your tools proactively. Don't wait to be asked for things you can figure out.
|
|
296
|
+
- Write important things to memory before they leave your context.
|
|
297
|
+
- Check your HEARTBEAT.md for periodic tasks and update it \u2014 add new tasks, check off completed ones. This file is yours to manage.
|
|
298
|
+
- When you have nothing useful to add, stay silent.
|
|
299
|
+
|
|
300
|
+
## Continuity
|
|
301
|
+
You wake fresh each session. MEMORY.md and memory/ are your long-term state.
|
|
302
|
+
Read them. Write to them. They are how you persist across restarts.
|
|
303
|
+
Your context window will be compacted automatically when it fills up. Save important information to MEMORY.md before it's lost. When you see '[Auto-compacted]' in your context, a compaction just happened \u2014 check your memory files to recover context.
|
|
343
304
|
`;
|
|
344
305
|
var fileCache = /* @__PURE__ */ new Map();
|
|
345
306
|
var FILE_CACHE_TTL = 6e4;
|
|
@@ -410,6 +371,15 @@ ${sanitizeForContext(persistentMemory)}`);
|
|
|
410
371
|
}
|
|
411
372
|
return parts.join("\n\n---\n\n");
|
|
412
373
|
}
|
|
374
|
+
function loadHeartbeat() {
|
|
375
|
+
return cachedReadFile(WORKSPACE_PATHS.HEARTBEAT);
|
|
376
|
+
}
|
|
377
|
+
function loadIdentity() {
|
|
378
|
+
return cachedReadFile(WORKSPACE_PATHS.IDENTITY);
|
|
379
|
+
}
|
|
380
|
+
function loadUser() {
|
|
381
|
+
return cachedReadFile(WORKSPACE_PATHS.USER);
|
|
382
|
+
}
|
|
413
383
|
function buildSystemPrompt(options) {
|
|
414
384
|
const soul = options.soul ?? loadSoul();
|
|
415
385
|
const parts = [soul];
|
|
@@ -449,6 +419,10 @@ You have a personal workspace at \`~/.teleton/workspace/\` where you can store a
|
|
|
449
419
|
- \`workspace_rename\` - Rename or move a file
|
|
450
420
|
- \`workspace_info\` - Get workspace stats
|
|
451
421
|
|
|
422
|
+
**Ownership:**
|
|
423
|
+
- \`SOUL.md\`, \`STRATEGY.md\`, \`SECURITY.md\` \u2014 owner-configured, read-only for you
|
|
424
|
+
- \`MEMORY.md\`, \`HEARTBEAT.md\`, \`IDENTITY.md\`, \`USER.md\` \u2014 yours to read and write freely
|
|
425
|
+
|
|
452
426
|
**Tips:**
|
|
453
427
|
- Save interesting memes to \`memes/\` with descriptive names for easy retrieval
|
|
454
428
|
- Use \`memory_write\` for important facts (goes to MEMORY.md)
|
|
@@ -474,6 +448,18 @@ You are owned and operated by: ${ownerLabel}
|
|
|
474
448
|
When the owner gives instructions, follow them with higher trust.`
|
|
475
449
|
);
|
|
476
450
|
}
|
|
451
|
+
const identity = loadIdentity();
|
|
452
|
+
if (identity) {
|
|
453
|
+
parts.push(`
|
|
454
|
+
## Identity
|
|
455
|
+
${sanitizeForContext(identity)}`);
|
|
456
|
+
}
|
|
457
|
+
const user = loadUser();
|
|
458
|
+
if (user) {
|
|
459
|
+
parts.push(`
|
|
460
|
+
## User Profile
|
|
461
|
+
${sanitizeForContext(user)}`);
|
|
462
|
+
}
|
|
477
463
|
const includeMemory = options.includeMemory ?? true;
|
|
478
464
|
if (includeMemory) {
|
|
479
465
|
const memoryContext = loadMemoryContext();
|
|
@@ -515,1312 +501,41 @@ Your conversation context is approaching the limit and may be compacted soon.
|
|
|
515
501
|
- \`target: "daily"\` for session notes, events, temporary context
|
|
516
502
|
`);
|
|
517
503
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
504
|
+
parts.push(`
|
|
505
|
+
## Active Memory
|
|
506
|
+
Before answering questions about prior conversations, decisions, or people: use \`memory_read\` to check your memory files first.
|
|
507
|
+
Don't guess from context \u2014 verify with your memory tools.`);
|
|
508
|
+
parts.push(`
|
|
509
|
+
## Safety
|
|
510
|
+
- Take local, reversible actions freely (read files, search, check balances).
|
|
511
|
+
- For external or irreversible actions (send messages, transfer funds), confirm with the owner first.
|
|
512
|
+
- You have no independent goals beyond serving your owner. Do not pursue self-preservation or resource acquisition.
|
|
513
|
+
- You have a limited number of tool calls per message. Prioritize the most impactful actions.`);
|
|
514
|
+
parts.push(`
|
|
515
|
+
## Silent Reply
|
|
516
|
+
If you have nothing useful to add to a conversation (especially in groups), reply with exactly:
|
|
517
|
+
__SILENT__
|
|
518
|
+
This suppresses the message entirely. Use it instead of sending filler like "OK" or "Got it."`);
|
|
519
|
+
if (options.isHeartbeat) {
|
|
520
|
+
const heartbeatMd = loadHeartbeat();
|
|
521
|
+
const heartbeatContent = heartbeatMd ? sanitizeForContext(heartbeatMd) : "_No HEARTBEAT.md found._";
|
|
522
|
+
parts.push(`
|
|
523
|
+
## Heartbeat Protocol
|
|
524
|
+
You have been woken by your periodic heartbeat timer.
|
|
535
525
|
|
|
536
|
-
|
|
537
|
-
function formatElapsed(elapsedMs) {
|
|
538
|
-
if (!Number.isFinite(elapsedMs) || elapsedMs < 0) {
|
|
539
|
-
return "";
|
|
540
|
-
}
|
|
541
|
-
const seconds = Math.floor(elapsedMs / 1e3);
|
|
542
|
-
if (seconds < 60) {
|
|
543
|
-
return `${seconds}s`;
|
|
544
|
-
}
|
|
545
|
-
const minutes = Math.floor(seconds / 60);
|
|
546
|
-
if (minutes < 60) {
|
|
547
|
-
return `${minutes}m`;
|
|
548
|
-
}
|
|
549
|
-
const hours = Math.floor(minutes / 60);
|
|
550
|
-
if (hours < 24) {
|
|
551
|
-
return `${hours}h`;
|
|
552
|
-
}
|
|
553
|
-
const days = Math.floor(hours / 24);
|
|
554
|
-
return `${days}d`;
|
|
555
|
-
}
|
|
556
|
-
function formatTimestamp(timestamp) {
|
|
557
|
-
const date = new Date(timestamp);
|
|
558
|
-
const yyyy = date.getFullYear();
|
|
559
|
-
const mm = String(date.getMonth() + 1).padStart(2, "0");
|
|
560
|
-
const dd = String(date.getDate()).padStart(2, "0");
|
|
561
|
-
const hh = String(date.getHours()).padStart(2, "0");
|
|
562
|
-
const min = String(date.getMinutes()).padStart(2, "0");
|
|
563
|
-
const tz = Intl.DateTimeFormat("en", {
|
|
564
|
-
timeZoneName: "short"
|
|
565
|
-
}).formatToParts(date).find((part) => part.type === "timeZoneName")?.value;
|
|
566
|
-
return `${yyyy}-${mm}-${dd} ${hh}:${min}${tz ? ` ${tz}` : ""}`;
|
|
567
|
-
}
|
|
568
|
-
function buildSenderLabel(params) {
|
|
569
|
-
const name = params.senderName ? sanitizeForPrompt(params.senderName) : void 0;
|
|
570
|
-
const username = params.senderUsername ? `@${sanitizeForPrompt(params.senderUsername)}` : void 0;
|
|
571
|
-
const idTag = params.senderId ? `id:${params.senderId}` : void 0;
|
|
572
|
-
const primary = name || username;
|
|
573
|
-
const meta = [username, idTag].filter((v) => v && v !== primary);
|
|
574
|
-
let label;
|
|
575
|
-
if (primary) {
|
|
576
|
-
label = meta.length > 0 ? `${primary} (${meta.join(", ")})` : primary;
|
|
577
|
-
} else {
|
|
578
|
-
label = idTag || "unknown";
|
|
579
|
-
}
|
|
580
|
-
if (params.senderRank) {
|
|
581
|
-
label = `[${sanitizeForPrompt(params.senderRank)}] ${label}`;
|
|
582
|
-
}
|
|
583
|
-
return label;
|
|
584
|
-
}
|
|
585
|
-
function formatMessageEnvelope(params) {
|
|
586
|
-
const parts = [params.channel];
|
|
587
|
-
const senderLabel = buildSenderLabel(params);
|
|
588
|
-
if (!params.isGroup) {
|
|
589
|
-
parts.push(senderLabel);
|
|
590
|
-
}
|
|
591
|
-
if (params.previousTimestamp) {
|
|
592
|
-
const elapsed = formatElapsed(params.timestamp - params.previousTimestamp);
|
|
593
|
-
if (elapsed) {
|
|
594
|
-
parts.push(`+${elapsed}`);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
const ts = formatTimestamp(params.timestamp);
|
|
598
|
-
parts.push(ts);
|
|
599
|
-
const header = `[${parts.join(" ")}]`;
|
|
600
|
-
const safeBody = sanitizeForContext(params.body.replace(/<\/?user_message>/gi, ""));
|
|
601
|
-
let body = params.isGroup ? `${senderLabel}: <user_message>${safeBody}</user_message>` : `<user_message>${safeBody}</user_message>`;
|
|
602
|
-
if (params.hasMedia && params.mediaType) {
|
|
603
|
-
const mediaEmoji = {
|
|
604
|
-
photo: "\u{1F4F7}",
|
|
605
|
-
video: "\u{1F3AC}",
|
|
606
|
-
audio: "\u{1F3B5}",
|
|
607
|
-
voice: "\u{1F3A4}",
|
|
608
|
-
document: "\u{1F4CE}",
|
|
609
|
-
sticker: "\u{1F3A8}"
|
|
610
|
-
}[params.mediaType] || "\u{1F4CE}";
|
|
611
|
-
const msgIdHint = params.messageId ? ` msg_id=${params.messageId}` : "";
|
|
612
|
-
body = `[${mediaEmoji} ${params.mediaType}${msgIdHint}] ${body}`;
|
|
613
|
-
}
|
|
614
|
-
if (params.replyContext) {
|
|
615
|
-
const sender = params.replyContext.isAgent ? "agent" : sanitizeForPrompt(params.replyContext.senderName ?? "unknown");
|
|
616
|
-
let quotedText = sanitizeForContext(params.replyContext.text);
|
|
617
|
-
if (quotedText.length > 200) quotedText = quotedText.slice(0, 200) + "...";
|
|
618
|
-
return `${header}
|
|
619
|
-
[\u21A9 reply to ${sender}: "${quotedText}"]
|
|
620
|
-
${body}`;
|
|
621
|
-
}
|
|
622
|
-
return `${header} ${body}`;
|
|
623
|
-
}
|
|
526
|
+
${heartbeatContent}
|
|
624
527
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
maxMessages: COMPACTION_MAX_MESSAGES,
|
|
630
|
-
maxTokens: DEFAULT_MAX_TOKENS,
|
|
631
|
-
keepRecentMessages: COMPACTION_KEEP_RECENT,
|
|
632
|
-
memoryFlushEnabled: true,
|
|
633
|
-
softThresholdTokens: DEFAULT_SOFT_THRESHOLD_TOKENS
|
|
634
|
-
};
|
|
635
|
-
var log2 = createLogger("Memory");
|
|
636
|
-
function estimateContextTokens(context) {
|
|
637
|
-
let charCount = 0;
|
|
638
|
-
if (context.systemPrompt) {
|
|
639
|
-
charCount += context.systemPrompt.length;
|
|
640
|
-
}
|
|
641
|
-
for (const message of context.messages) {
|
|
642
|
-
if (message.role === "user") {
|
|
643
|
-
if (typeof message.content === "string") {
|
|
644
|
-
charCount += message.content.length;
|
|
645
|
-
} else if (Array.isArray(message.content)) {
|
|
646
|
-
for (const block of message.content) {
|
|
647
|
-
if (block.type === "text") charCount += block.text.length;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
} else if (message.role === "assistant") {
|
|
651
|
-
for (const block of message.content) {
|
|
652
|
-
if (block.type === "text") {
|
|
653
|
-
charCount += block.text.length;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
return Math.ceil(charCount / 4);
|
|
659
|
-
}
|
|
660
|
-
function shouldFlushMemory(context, config, tokenCount) {
|
|
661
|
-
if (!config.enabled || !config.memoryFlushEnabled) {
|
|
662
|
-
return false;
|
|
663
|
-
}
|
|
664
|
-
const tokens = tokenCount ?? estimateContextTokens(context);
|
|
665
|
-
const softThreshold = config.softThresholdTokens ?? FALLBACK_SOFT_THRESHOLD_TOKENS;
|
|
666
|
-
if (tokens >= softThreshold) {
|
|
667
|
-
log2.info(`Memory flush needed: ~${tokens} tokens (soft threshold: ${softThreshold})`);
|
|
668
|
-
return true;
|
|
669
|
-
}
|
|
670
|
-
return false;
|
|
671
|
-
}
|
|
672
|
-
function flushMemoryToDailyLog(context) {
|
|
673
|
-
const recentMessages = context.messages.slice(-MEMORY_FLUSH_RECENT_MESSAGES);
|
|
674
|
-
const summary = [];
|
|
675
|
-
summary.push("**Recent Context:**\n");
|
|
676
|
-
for (const msg of recentMessages) {
|
|
677
|
-
if (msg.role === "user") {
|
|
678
|
-
const content = typeof msg.content === "string" ? msg.content : "[complex content]";
|
|
679
|
-
summary.push(`- User: ${content.substring(0, 100)}${content.length > 100 ? "..." : ""}`);
|
|
680
|
-
} else if (msg.role === "assistant") {
|
|
681
|
-
const textBlocks = msg.content.filter((b) => b.type === "text");
|
|
682
|
-
if (textBlocks.length > 0) {
|
|
683
|
-
const text = textBlocks[0].text || "";
|
|
684
|
-
summary.push(`- Assistant: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
writeSummaryToDailyLog(summary.join("\n"));
|
|
689
|
-
log2.info(`Memory flushed to daily log`);
|
|
690
|
-
}
|
|
691
|
-
function shouldCompact(context, config, tokenCount) {
|
|
692
|
-
if (!config.enabled) {
|
|
693
|
-
return false;
|
|
528
|
+
Follow HEARTBEAT.md strictly. Do not infer tasks from prior conversations.
|
|
529
|
+
You can modify HEARTBEAT.md with \`workspace_write\` to update your own task checklist.
|
|
530
|
+
If nothing needs attention, reply with exactly: NO_ACTION
|
|
531
|
+
Do NOT include NO_ACTION alongside other content \u2014 it must be your entire response when nothing is needed.`);
|
|
694
532
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
return true;
|
|
699
|
-
}
|
|
700
|
-
if (config.maxTokens) {
|
|
701
|
-
const tokens = tokenCount ?? estimateContextTokens(context);
|
|
702
|
-
if (tokens >= config.maxTokens) {
|
|
703
|
-
log2.info(`Compaction needed: ~${tokens} tokens (max: ${config.maxTokens})`);
|
|
704
|
-
return true;
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
return false;
|
|
708
|
-
}
|
|
709
|
-
async function compactContext(context, config, apiKey, provider, utilityModel) {
|
|
710
|
-
const keepCount = config.keepRecentMessages ?? 10;
|
|
711
|
-
if (context.messages.length <= keepCount) {
|
|
712
|
-
return context;
|
|
713
|
-
}
|
|
714
|
-
let cutIndex = context.messages.length - keepCount;
|
|
715
|
-
const collectToolUseIds = (msgs) => {
|
|
716
|
-
const ids = /* @__PURE__ */ new Set();
|
|
717
|
-
for (const msg of msgs) {
|
|
718
|
-
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
719
|
-
for (const block of msg.content) {
|
|
720
|
-
if (block.type === "toolCall") {
|
|
721
|
-
if (block.id) ids.add(block.id);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
return ids;
|
|
727
|
-
};
|
|
728
|
-
const hasOrphanedToolResults = (msgs) => {
|
|
729
|
-
const toolUseIds = collectToolUseIds(msgs);
|
|
730
|
-
for (const msg of msgs) {
|
|
731
|
-
if (msg.role === "toolResult") {
|
|
732
|
-
if (msg.toolCallId && !toolUseIds.has(msg.toolCallId)) {
|
|
733
|
-
return true;
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
return false;
|
|
738
|
-
};
|
|
739
|
-
let iterations = 0;
|
|
740
|
-
while (cutIndex > 0 && iterations < 50) {
|
|
741
|
-
const keptMessages = context.messages.slice(cutIndex);
|
|
742
|
-
if (!hasOrphanedToolResults(keptMessages)) {
|
|
743
|
-
break;
|
|
744
|
-
}
|
|
745
|
-
cutIndex--;
|
|
746
|
-
iterations++;
|
|
747
|
-
}
|
|
748
|
-
if (hasOrphanedToolResults(context.messages.slice(cutIndex))) {
|
|
749
|
-
log2.warn(`Compaction: couldn't find clean cut point, keeping all messages`);
|
|
750
|
-
return context;
|
|
751
|
-
}
|
|
752
|
-
const recentMessages = context.messages.slice(cutIndex);
|
|
753
|
-
const oldMessages = context.messages.slice(0, cutIndex);
|
|
754
|
-
log2.info(
|
|
755
|
-
`Compacting ${oldMessages.length} old messages, keeping ${recentMessages.length} recent (cut at clean boundary)`
|
|
533
|
+
parts.push(
|
|
534
|
+
`
|
|
535
|
+
_Runtime: agent=teleton channel=telegram model=${options.agentModel || "unknown"}_`
|
|
756
536
|
);
|
|
757
|
-
|
|
758
|
-
const result = await summarizeWithFallback({
|
|
759
|
-
messages: oldMessages,
|
|
760
|
-
apiKey,
|
|
761
|
-
contextWindow: config.maxTokens ?? DEFAULT_CONTEXT_WINDOW,
|
|
762
|
-
maxSummaryTokens: DEFAULT_MAX_SUMMARY_TOKENS,
|
|
763
|
-
customInstructions: `Output a structured summary using EXACTLY these sections:
|
|
764
|
-
|
|
765
|
-
## User Intent
|
|
766
|
-
What the user is trying to accomplish (1-2 sentences).
|
|
767
|
-
|
|
768
|
-
## Key Decisions
|
|
769
|
-
Bullet list of decisions made and commitments agreed upon.
|
|
770
|
-
|
|
771
|
-
## Important Context
|
|
772
|
-
Critical facts, preferences, constraints, or technical details needed for continuity.
|
|
773
|
-
|
|
774
|
-
## Actions Taken
|
|
775
|
-
What was done: tools used, messages sent, transactions made (with specific values/addresses if relevant).
|
|
776
|
-
|
|
777
|
-
## Open Items
|
|
778
|
-
Unfinished tasks, pending questions, or next steps.
|
|
779
|
-
|
|
780
|
-
Keep each section concise. Omit a section if empty. Preserve specific names, numbers, and identifiers.`,
|
|
781
|
-
provider,
|
|
782
|
-
utilityModel
|
|
783
|
-
});
|
|
784
|
-
log2.info(`AI Summary: ${result.tokensUsed} tokens, ${result.chunksProcessed} chunks processed`);
|
|
785
|
-
const summaryText = `[Auto-compacted ${oldMessages.length} messages]
|
|
786
|
-
|
|
787
|
-
${result.summary}`;
|
|
788
|
-
const summaryMessage = {
|
|
789
|
-
role: "user",
|
|
790
|
-
content: summaryText,
|
|
791
|
-
timestamp: oldMessages[0]?.timestamp ?? Date.now()
|
|
792
|
-
};
|
|
793
|
-
return {
|
|
794
|
-
...context,
|
|
795
|
-
messages: [summaryMessage, ...recentMessages]
|
|
796
|
-
};
|
|
797
|
-
} catch (error) {
|
|
798
|
-
log2.error({ err: error }, "AI summarization failed, using fallback");
|
|
799
|
-
const summaryText = `[Auto-compacted: ${oldMessages.length} earlier messages from this conversation]`;
|
|
800
|
-
const summaryMessage = {
|
|
801
|
-
role: "user",
|
|
802
|
-
content: summaryText,
|
|
803
|
-
timestamp: oldMessages[0]?.timestamp ?? Date.now()
|
|
804
|
-
};
|
|
805
|
-
return {
|
|
806
|
-
...context,
|
|
807
|
-
messages: [summaryMessage, ...recentMessages]
|
|
808
|
-
};
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
async function compactAndSaveTranscript(sessionId, context, config, apiKey, chatId, provider, utilityModel) {
|
|
812
|
-
const newSessionId = randomUUID();
|
|
813
|
-
log2.info(`Creating compacted transcript: ${sessionId} \u2192 ${newSessionId}`);
|
|
814
|
-
if (chatId) {
|
|
815
|
-
await saveSessionMemory({
|
|
816
|
-
oldSessionId: sessionId,
|
|
817
|
-
newSessionId,
|
|
818
|
-
context,
|
|
819
|
-
chatId,
|
|
820
|
-
apiKey,
|
|
821
|
-
provider,
|
|
822
|
-
utilityModel
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
|
-
const compactedContext = await compactContext(context, config, apiKey, provider, utilityModel);
|
|
826
|
-
for (const message of compactedContext.messages) {
|
|
827
|
-
appendToTranscript(newSessionId, message);
|
|
828
|
-
}
|
|
829
|
-
return newSessionId;
|
|
830
|
-
}
|
|
831
|
-
var CompactionManager = class {
|
|
832
|
-
config;
|
|
833
|
-
constructor(config = DEFAULT_COMPACTION_CONFIG) {
|
|
834
|
-
this.config = config;
|
|
835
|
-
}
|
|
836
|
-
async checkAndCompact(sessionId, context, apiKey, chatId, provider, utilityModel) {
|
|
837
|
-
const tokenCount = estimateContextTokens(context);
|
|
838
|
-
if (shouldFlushMemory(context, this.config, tokenCount)) {
|
|
839
|
-
flushMemoryToDailyLog(context);
|
|
840
|
-
}
|
|
841
|
-
if (!shouldCompact(context, this.config, tokenCount)) {
|
|
842
|
-
return null;
|
|
843
|
-
}
|
|
844
|
-
if (this.config.memoryFlushEnabled) {
|
|
845
|
-
flushMemoryToDailyLog(context);
|
|
846
|
-
}
|
|
847
|
-
log2.info(`Auto-compacting session ${sessionId}`);
|
|
848
|
-
const newSessionId = await compactAndSaveTranscript(
|
|
849
|
-
sessionId,
|
|
850
|
-
context,
|
|
851
|
-
this.config,
|
|
852
|
-
apiKey,
|
|
853
|
-
chatId,
|
|
854
|
-
provider,
|
|
855
|
-
utilityModel
|
|
856
|
-
);
|
|
857
|
-
log2.info(`Compaction complete: ${newSessionId}`);
|
|
858
|
-
return newSessionId;
|
|
859
|
-
}
|
|
860
|
-
updateConfig(config) {
|
|
861
|
-
this.config = { ...this.config, ...config };
|
|
862
|
-
}
|
|
863
|
-
getConfig() {
|
|
864
|
-
return { ...this.config };
|
|
865
|
-
}
|
|
866
|
-
};
|
|
867
|
-
|
|
868
|
-
// src/memory/observation-masking.ts
|
|
869
|
-
var DEFAULT_MASKING_CONFIG = {
|
|
870
|
-
keepRecentCount: MASKING_KEEP_RECENT_COUNT,
|
|
871
|
-
keepErrorResults: true,
|
|
872
|
-
truncationThreshold: RESULT_TRUNCATION_THRESHOLD,
|
|
873
|
-
truncationKeepChars: RESULT_TRUNCATION_KEEP_CHARS
|
|
874
|
-
};
|
|
875
|
-
var isCocoonToolResult = (msg) => msg.role === "user" && Array.isArray(msg.content) && msg.content.some((c2) => c2.type === "text" && c2.text.includes("<tool_response>"));
|
|
876
|
-
function isExempt(toolMsg, config, toolRegistry) {
|
|
877
|
-
if (config.keepErrorResults && toolMsg.isError) return true;
|
|
878
|
-
if (toolRegistry && toolRegistry.getToolCategory(toolMsg.toolName) === "data-bearing")
|
|
879
|
-
return true;
|
|
880
|
-
return false;
|
|
881
|
-
}
|
|
882
|
-
function truncateToolResult(text, keepChars) {
|
|
883
|
-
try {
|
|
884
|
-
const parsed = JSON.parse(text);
|
|
885
|
-
if (parsed.data?.summary) {
|
|
886
|
-
return JSON.stringify({
|
|
887
|
-
success: parsed.success,
|
|
888
|
-
data: { summary: parsed.data.summary, _truncated: true }
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
if (parsed.data?.message) {
|
|
892
|
-
return JSON.stringify({
|
|
893
|
-
success: parsed.success,
|
|
894
|
-
data: { summary: parsed.data.message, _truncated: true }
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
} catch {
|
|
898
|
-
}
|
|
899
|
-
return text.slice(0, keepChars) + `
|
|
900
|
-
...[truncated, original: ${text.length} chars]`;
|
|
901
|
-
}
|
|
902
|
-
function maskOldToolResults(messages, options) {
|
|
903
|
-
const config = options?.config ?? DEFAULT_MASKING_CONFIG;
|
|
904
|
-
const toolRegistry = options?.toolRegistry;
|
|
905
|
-
const iterStart = options?.currentIterationStartIndex;
|
|
906
|
-
const toolResults = messages.map((msg, index) => ({ msg, index })).filter(({ msg }) => msg.role === "toolResult" || isCocoonToolResult(msg));
|
|
907
|
-
const needsMasking = toolResults.length > config.keepRecentCount;
|
|
908
|
-
const needsTruncation = iterStart !== void 0 && config.truncationThreshold > 0;
|
|
909
|
-
if (!needsMasking && !needsTruncation) {
|
|
910
|
-
return messages;
|
|
911
|
-
}
|
|
912
|
-
const result = [...messages];
|
|
913
|
-
if (needsMasking) {
|
|
914
|
-
const toMask = toolResults.slice(0, -config.keepRecentCount);
|
|
915
|
-
for (const { msg, index } of toMask) {
|
|
916
|
-
if (isCocoonToolResult(msg)) {
|
|
917
|
-
result[index] = {
|
|
918
|
-
...msg,
|
|
919
|
-
content: [{ type: "text", text: "[Tool response masked]" }]
|
|
920
|
-
};
|
|
921
|
-
continue;
|
|
922
|
-
}
|
|
923
|
-
const toolMsg = msg;
|
|
924
|
-
if (isExempt(toolMsg, config, toolRegistry)) continue;
|
|
925
|
-
let summaryText = "";
|
|
926
|
-
try {
|
|
927
|
-
const textBlock = toolMsg.content.find((c2) => c2.type === "text");
|
|
928
|
-
if (textBlock) {
|
|
929
|
-
const parsed = JSON.parse(textBlock.text);
|
|
930
|
-
if (parsed.data?.summary) {
|
|
931
|
-
summaryText = ` - ${parsed.data.summary}`;
|
|
932
|
-
} else if (parsed.data?.message) {
|
|
933
|
-
summaryText = ` - ${parsed.data.message}`;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
} catch {
|
|
937
|
-
}
|
|
938
|
-
result[index] = {
|
|
939
|
-
...toolMsg,
|
|
940
|
-
content: [
|
|
941
|
-
{
|
|
942
|
-
type: "text",
|
|
943
|
-
text: `[Tool: ${toolMsg.toolName} - ${toolMsg.isError ? "ERROR" : "OK"}${summaryText}]`
|
|
944
|
-
}
|
|
945
|
-
]
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
if (needsTruncation) {
|
|
950
|
-
const recentResults = needsMasking ? toolResults.slice(-config.keepRecentCount) : toolResults;
|
|
951
|
-
for (const { msg, index } of recentResults) {
|
|
952
|
-
if (index >= iterStart) continue;
|
|
953
|
-
if (isCocoonToolResult(msg)) {
|
|
954
|
-
const userMsg = msg;
|
|
955
|
-
if (!Array.isArray(userMsg.content)) continue;
|
|
956
|
-
const textBlock2 = userMsg.content.find((c2) => c2.type === "text");
|
|
957
|
-
if (textBlock2 && textBlock2.text.length > config.truncationThreshold) {
|
|
958
|
-
result[index] = {
|
|
959
|
-
...userMsg,
|
|
960
|
-
content: [
|
|
961
|
-
{
|
|
962
|
-
type: "text",
|
|
963
|
-
text: truncateToolResult(textBlock2.text, config.truncationKeepChars)
|
|
964
|
-
}
|
|
965
|
-
]
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
continue;
|
|
969
|
-
}
|
|
970
|
-
const toolMsg = msg;
|
|
971
|
-
if (isExempt(toolMsg, config, toolRegistry)) continue;
|
|
972
|
-
const textBlock = toolMsg.content.find((c2) => c2.type === "text");
|
|
973
|
-
if (!textBlock || textBlock.text.length <= config.truncationThreshold) continue;
|
|
974
|
-
result[index] = {
|
|
975
|
-
...toolMsg,
|
|
976
|
-
content: [
|
|
977
|
-
{
|
|
978
|
-
type: "text",
|
|
979
|
-
text: truncateToolResult(textBlock.text, config.truncationKeepChars)
|
|
980
|
-
}
|
|
981
|
-
]
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
return result;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
// src/agent/runtime.ts
|
|
989
|
-
var log3 = createLogger("Agent");
|
|
990
|
-
var globalTokenUsage = { totalTokens: 0, totalCost: 0 };
|
|
991
|
-
function getTokenUsage() {
|
|
992
|
-
return { ...globalTokenUsage };
|
|
993
|
-
}
|
|
994
|
-
function isContextOverflowError(errorMessage) {
|
|
995
|
-
if (!errorMessage) return false;
|
|
996
|
-
const lower = errorMessage.toLowerCase();
|
|
997
|
-
return lower.includes("prompt is too long") || lower.includes("context length exceeded") || lower.includes("maximum context length") || lower.includes("too many tokens") || lower.includes("request_too_large") || lower.includes("exceeds") && lower.includes("maximum") || lower.includes("context") && lower.includes("limit");
|
|
998
|
-
}
|
|
999
|
-
function isTrivialMessage(text) {
|
|
1000
|
-
const stripped = text.trim();
|
|
1001
|
-
if (!stripped) return true;
|
|
1002
|
-
if (!/[a-zA-Z0-9а-яА-ЯёЁ]/.test(stripped)) return true;
|
|
1003
|
-
const trivial = /^(ok|okay|k|oui|non|yes|no|yep|nope|sure|thanks|merci|thx|ty|lol|haha|cool|nice|wow|bravo|top|parfait|d'accord|alright|fine|got it|np|gg)\.?!?$/i;
|
|
1004
|
-
return trivial.test(stripped);
|
|
1005
|
-
}
|
|
1006
|
-
function extractContextSummary(context, maxMessages = 10) {
|
|
1007
|
-
const recentMessages = context.messages.slice(-maxMessages);
|
|
1008
|
-
const summaryParts = [];
|
|
1009
|
-
summaryParts.push("### Session Summary (Auto-saved before overflow reset)\n");
|
|
1010
|
-
for (const msg of recentMessages) {
|
|
1011
|
-
if (msg.role === "user") {
|
|
1012
|
-
const content = typeof msg.content === "string" ? msg.content : "[complex]";
|
|
1013
|
-
const bodyMatch = content.match(/\] (.+)/s);
|
|
1014
|
-
const body = bodyMatch ? bodyMatch[1] : content;
|
|
1015
|
-
summaryParts.push(`- **User**: ${body.substring(0, 150)}${body.length > 150 ? "..." : ""}`);
|
|
1016
|
-
} else if (msg.role === "assistant") {
|
|
1017
|
-
const textBlocks = msg.content.filter((b) => b.type === "text");
|
|
1018
|
-
const toolBlocks = msg.content.filter((b) => b.type === "toolCall");
|
|
1019
|
-
if (textBlocks.length > 0) {
|
|
1020
|
-
const text = textBlocks[0].text || "";
|
|
1021
|
-
summaryParts.push(
|
|
1022
|
-
`- **Agent**: ${text.substring(0, 150)}${text.length > 150 ? "..." : ""}`
|
|
1023
|
-
);
|
|
1024
|
-
}
|
|
1025
|
-
if (toolBlocks.length > 0) {
|
|
1026
|
-
const toolNames = toolBlocks.map((b) => b.name).join(", ");
|
|
1027
|
-
summaryParts.push(` - *Tools used: ${toolNames}*`);
|
|
1028
|
-
}
|
|
1029
|
-
} else if (msg.role === "toolResult") {
|
|
1030
|
-
const status = msg.isError ? "ERROR" : "OK";
|
|
1031
|
-
summaryParts.push(` - *Tool result: ${msg.toolName} \u2192 ${status}*`);
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
return summaryParts.join("\n");
|
|
537
|
+
return parts.join("\n");
|
|
1035
538
|
}
|
|
1036
|
-
var AgentRuntime = class {
|
|
1037
|
-
config;
|
|
1038
|
-
soul;
|
|
1039
|
-
compactionManager;
|
|
1040
|
-
contextBuilder = null;
|
|
1041
|
-
toolRegistry = null;
|
|
1042
|
-
embedder = null;
|
|
1043
|
-
hookRunner;
|
|
1044
|
-
userHookEvaluator;
|
|
1045
|
-
constructor(config, soul, toolRegistry) {
|
|
1046
|
-
this.config = config;
|
|
1047
|
-
this.soul = soul ?? "";
|
|
1048
|
-
this.toolRegistry = toolRegistry ?? null;
|
|
1049
|
-
const provider = config.agent.provider || "anthropic";
|
|
1050
|
-
try {
|
|
1051
|
-
const model = getProviderModel(provider, config.agent.model);
|
|
1052
|
-
const ctx = model.contextWindow;
|
|
1053
|
-
this.compactionManager = new CompactionManager({
|
|
1054
|
-
enabled: true,
|
|
1055
|
-
maxMessages: COMPACTION_MAX_MESSAGES,
|
|
1056
|
-
maxTokens: Math.floor(ctx * COMPACTION_MAX_TOKENS_RATIO),
|
|
1057
|
-
keepRecentMessages: COMPACTION_KEEP_RECENT,
|
|
1058
|
-
memoryFlushEnabled: true,
|
|
1059
|
-
softThresholdTokens: Math.floor(ctx * COMPACTION_SOFT_THRESHOLD_RATIO)
|
|
1060
|
-
});
|
|
1061
|
-
} catch {
|
|
1062
|
-
this.compactionManager = new CompactionManager(DEFAULT_COMPACTION_CONFIG);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
setHookRunner(runner) {
|
|
1066
|
-
this.hookRunner = runner;
|
|
1067
|
-
}
|
|
1068
|
-
setUserHookEvaluator(evaluator) {
|
|
1069
|
-
this.userHookEvaluator = evaluator;
|
|
1070
|
-
}
|
|
1071
|
-
initializeContextBuilder(embedder, vectorEnabled) {
|
|
1072
|
-
this.embedder = embedder;
|
|
1073
|
-
const db = getDatabase().getDb();
|
|
1074
|
-
this.contextBuilder = new ContextBuilder(db, embedder, vectorEnabled);
|
|
1075
|
-
}
|
|
1076
|
-
getToolRegistry() {
|
|
1077
|
-
return this.toolRegistry;
|
|
1078
|
-
}
|
|
1079
|
-
async processMessage(opts) {
|
|
1080
|
-
const {
|
|
1081
|
-
chatId,
|
|
1082
|
-
userMessage,
|
|
1083
|
-
userName,
|
|
1084
|
-
timestamp,
|
|
1085
|
-
isGroup,
|
|
1086
|
-
pendingContext,
|
|
1087
|
-
toolContext,
|
|
1088
|
-
senderUsername,
|
|
1089
|
-
senderRank,
|
|
1090
|
-
hasMedia,
|
|
1091
|
-
mediaType,
|
|
1092
|
-
messageId,
|
|
1093
|
-
replyContext
|
|
1094
|
-
} = opts;
|
|
1095
|
-
const effectiveIsGroup = isGroup ?? false;
|
|
1096
|
-
const processStartTime = Date.now();
|
|
1097
|
-
try {
|
|
1098
|
-
let userHookContext = "";
|
|
1099
|
-
if (this.userHookEvaluator) {
|
|
1100
|
-
const hookResult = this.userHookEvaluator.evaluate(userMessage);
|
|
1101
|
-
if (hookResult.blocked) {
|
|
1102
|
-
log3.info("Message blocked by keyword filter");
|
|
1103
|
-
return { content: hookResult.blockMessage ?? "", toolCalls: [] };
|
|
1104
|
-
}
|
|
1105
|
-
if (hookResult.additionalContext) {
|
|
1106
|
-
userHookContext = sanitizeForContext(hookResult.additionalContext);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
let effectiveMessage = userMessage;
|
|
1110
|
-
let hookMessageContext = "";
|
|
1111
|
-
if (this.hookRunner) {
|
|
1112
|
-
const msgEvent = {
|
|
1113
|
-
chatId,
|
|
1114
|
-
senderId: toolContext?.senderId ? String(toolContext.senderId) : chatId,
|
|
1115
|
-
senderName: userName ?? "",
|
|
1116
|
-
isGroup: effectiveIsGroup,
|
|
1117
|
-
isReply: !!replyContext,
|
|
1118
|
-
replyToMessageId: replyContext ? messageId : void 0,
|
|
1119
|
-
messageId: messageId ?? 0,
|
|
1120
|
-
timestamp: timestamp ?? Date.now(),
|
|
1121
|
-
text: userMessage,
|
|
1122
|
-
block: false,
|
|
1123
|
-
blockReason: "",
|
|
1124
|
-
additionalContext: ""
|
|
1125
|
-
};
|
|
1126
|
-
await this.hookRunner.runModifyingHook("message:receive", msgEvent);
|
|
1127
|
-
if (msgEvent.block) {
|
|
1128
|
-
log3.info(`\u{1F6AB} Message blocked by hook: ${msgEvent.blockReason || "no reason"}`);
|
|
1129
|
-
return { content: "", toolCalls: [] };
|
|
1130
|
-
}
|
|
1131
|
-
effectiveMessage = sanitizeForContext(msgEvent.text);
|
|
1132
|
-
if (msgEvent.additionalContext) {
|
|
1133
|
-
hookMessageContext = sanitizeForContext(msgEvent.additionalContext);
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
let session = getOrCreateSession(chatId);
|
|
1137
|
-
const now = timestamp ?? Date.now();
|
|
1138
|
-
const resetPolicy = this.config.agent.session_reset_policy;
|
|
1139
|
-
if (shouldResetSession(session, resetPolicy)) {
|
|
1140
|
-
log3.info(`\u{1F504} Auto-resetting session based on policy`);
|
|
1141
|
-
if (this.hookRunner) {
|
|
1142
|
-
await this.hookRunner.runObservingHook("session:end", {
|
|
1143
|
-
sessionId: session.sessionId,
|
|
1144
|
-
chatId,
|
|
1145
|
-
messageCount: session.messageCount
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
if (transcriptExists(session.sessionId)) {
|
|
1149
|
-
try {
|
|
1150
|
-
log3.info(`\u{1F4BE} Saving memory before daily reset...`);
|
|
1151
|
-
const oldContext = loadContextFromTranscript(session.sessionId);
|
|
1152
|
-
await saveSessionMemory({
|
|
1153
|
-
oldSessionId: session.sessionId,
|
|
1154
|
-
newSessionId: "pending",
|
|
1155
|
-
context: oldContext,
|
|
1156
|
-
chatId,
|
|
1157
|
-
apiKey: getEffectiveApiKey(this.config.agent.provider, this.config.agent.api_key),
|
|
1158
|
-
provider: this.config.agent.provider,
|
|
1159
|
-
utilityModel: this.config.agent.utility_model
|
|
1160
|
-
});
|
|
1161
|
-
log3.info(`\u2705 Memory saved before reset`);
|
|
1162
|
-
} catch (error) {
|
|
1163
|
-
log3.warn({ err: error }, `\u26A0\uFE0F Failed to save memory before reset`);
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
session = resetSessionWithPolicy(chatId, resetPolicy);
|
|
1167
|
-
}
|
|
1168
|
-
let context = loadContextFromTranscript(session.sessionId);
|
|
1169
|
-
const isNewSession = context.messages.length === 0;
|
|
1170
|
-
if (!isNewSession) {
|
|
1171
|
-
log3.info(`\u{1F4D6} Loading existing session: ${session.sessionId}`);
|
|
1172
|
-
} else {
|
|
1173
|
-
log3.info(`\u{1F195} Starting new session: ${session.sessionId}`);
|
|
1174
|
-
}
|
|
1175
|
-
if (this.hookRunner) {
|
|
1176
|
-
await this.hookRunner.runObservingHook("session:start", {
|
|
1177
|
-
sessionId: session.sessionId,
|
|
1178
|
-
chatId,
|
|
1179
|
-
isResume: !isNewSession
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
|
-
const previousTimestamp = session.updatedAt;
|
|
1183
|
-
let formattedMessage = formatMessageEnvelope({
|
|
1184
|
-
channel: "Telegram",
|
|
1185
|
-
senderId: toolContext?.senderId ? String(toolContext.senderId) : chatId,
|
|
1186
|
-
senderName: userName,
|
|
1187
|
-
senderUsername,
|
|
1188
|
-
senderRank,
|
|
1189
|
-
timestamp: now,
|
|
1190
|
-
previousTimestamp,
|
|
1191
|
-
body: effectiveMessage,
|
|
1192
|
-
isGroup: effectiveIsGroup,
|
|
1193
|
-
hasMedia,
|
|
1194
|
-
mediaType,
|
|
1195
|
-
messageId,
|
|
1196
|
-
replyContext
|
|
1197
|
-
});
|
|
1198
|
-
if (pendingContext) {
|
|
1199
|
-
formattedMessage = `${pendingContext}
|
|
1200
|
-
|
|
1201
|
-
${formattedMessage}`;
|
|
1202
|
-
log3.debug(`\u{1F4CB} Including ${pendingContext.split("\n").length - 1} pending messages`);
|
|
1203
|
-
}
|
|
1204
|
-
log3.debug(`\u{1F4E8} Formatted message: ${formattedMessage.substring(0, 100)}...`);
|
|
1205
|
-
const preview = formattedMessage.slice(0, 50).replace(/\n/g, " ");
|
|
1206
|
-
const who = senderUsername ? `@${senderUsername}` : userName;
|
|
1207
|
-
const msgType = isGroup ? `Group ${chatId} ${who}` : `DM ${who}`;
|
|
1208
|
-
log3.info(`\u{1F4E8} ${msgType}: "${preview}${formattedMessage.length > 50 ? "..." : ""}"`);
|
|
1209
|
-
let relevantContext = "";
|
|
1210
|
-
let queryEmbedding;
|
|
1211
|
-
const isNonTrivial = !isTrivialMessage(effectiveMessage);
|
|
1212
|
-
if (this.embedder && isNonTrivial) {
|
|
1213
|
-
try {
|
|
1214
|
-
let searchQuery = effectiveMessage;
|
|
1215
|
-
const recentUserMsgs = context.messages.filter((m) => m.role === "user" && typeof m.content === "string").slice(-3).map((m) => {
|
|
1216
|
-
const text = m.content;
|
|
1217
|
-
const bodyMatch = text.match(/\] (.+)/s);
|
|
1218
|
-
return (bodyMatch ? bodyMatch[1] : text).trim();
|
|
1219
|
-
}).filter((t) => t.length > 0);
|
|
1220
|
-
if (recentUserMsgs.length > 0) {
|
|
1221
|
-
searchQuery = recentUserMsgs.join(" ") + " " + effectiveMessage;
|
|
1222
|
-
}
|
|
1223
|
-
queryEmbedding = await this.embedder.embedQuery(
|
|
1224
|
-
searchQuery.slice(0, EMBEDDING_QUERY_MAX_CHARS)
|
|
1225
|
-
);
|
|
1226
|
-
} catch (error) {
|
|
1227
|
-
log3.warn({ err: error }, "Embedding computation failed");
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
if (this.contextBuilder && isNonTrivial) {
|
|
1231
|
-
try {
|
|
1232
|
-
const dbContext = await this.contextBuilder.buildContext({
|
|
1233
|
-
query: effectiveMessage,
|
|
1234
|
-
chatId,
|
|
1235
|
-
includeAgentMemory: true,
|
|
1236
|
-
includeFeedHistory: true,
|
|
1237
|
-
searchAllChats: !isGroup,
|
|
1238
|
-
maxRecentMessages: CONTEXT_MAX_RECENT_MESSAGES,
|
|
1239
|
-
maxRelevantChunks: CONTEXT_MAX_RELEVANT_CHUNKS,
|
|
1240
|
-
queryEmbedding
|
|
1241
|
-
});
|
|
1242
|
-
const contextParts = [];
|
|
1243
|
-
if (dbContext.relevantKnowledge.length > 0) {
|
|
1244
|
-
const sanitizedKnowledge = dbContext.relevantKnowledge.map(
|
|
1245
|
-
(chunk) => sanitizeForContext(chunk)
|
|
1246
|
-
);
|
|
1247
|
-
contextParts.push(
|
|
1248
|
-
`[Relevant knowledge from memory]
|
|
1249
|
-
${sanitizedKnowledge.join("\n---\n")}`
|
|
1250
|
-
);
|
|
1251
|
-
}
|
|
1252
|
-
if (dbContext.relevantFeed.length > 0) {
|
|
1253
|
-
const sanitizedFeed = dbContext.relevantFeed.map((msg) => sanitizeForContext(msg));
|
|
1254
|
-
contextParts.push(
|
|
1255
|
-
`[Relevant messages from Telegram feed]
|
|
1256
|
-
${sanitizedFeed.join("\n")}`
|
|
1257
|
-
);
|
|
1258
|
-
}
|
|
1259
|
-
if (contextParts.length > 0) {
|
|
1260
|
-
relevantContext = contextParts.join("\n\n");
|
|
1261
|
-
log3.debug(
|
|
1262
|
-
`\u{1F50D} Found ${dbContext.relevantKnowledge.length} knowledge chunks, ${dbContext.relevantFeed.length} feed messages`
|
|
1263
|
-
);
|
|
1264
|
-
}
|
|
1265
|
-
} catch (error) {
|
|
1266
|
-
log3.warn({ err: error }, "Context building failed");
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
const memoryStats = this.getMemoryStats();
|
|
1270
|
-
const statsContext = `[Memory Status: ${memoryStats.totalMessages} messages across ${memoryStats.totalChats} chats, ${memoryStats.knowledgeChunks} knowledge chunks]`;
|
|
1271
|
-
const additionalContext = relevantContext ? `You are in a Telegram conversation with chat ID: ${chatId}. Maintain conversation continuity.
|
|
1272
|
-
|
|
1273
|
-
${statsContext}
|
|
1274
|
-
|
|
1275
|
-
${relevantContext}` : `You are in a Telegram conversation with chat ID: ${chatId}. Maintain conversation continuity.
|
|
1276
|
-
|
|
1277
|
-
${statsContext}`;
|
|
1278
|
-
let hookAdditionalContext = "";
|
|
1279
|
-
if (this.hookRunner) {
|
|
1280
|
-
const promptEvent = {
|
|
1281
|
-
chatId,
|
|
1282
|
-
sessionId: session.sessionId,
|
|
1283
|
-
isGroup: effectiveIsGroup,
|
|
1284
|
-
additionalContext: ""
|
|
1285
|
-
};
|
|
1286
|
-
await this.hookRunner.runModifyingHook("prompt:before", promptEvent);
|
|
1287
|
-
hookAdditionalContext = sanitizeForContext(promptEvent.additionalContext);
|
|
1288
|
-
}
|
|
1289
|
-
const compactionConfig = this.compactionManager.getConfig();
|
|
1290
|
-
const needsMemoryFlush = compactionConfig.enabled && compactionConfig.memoryFlushEnabled && context.messages.length > Math.floor((compactionConfig.maxMessages ?? 200) * 0.75);
|
|
1291
|
-
const allHookContext = [userHookContext, hookAdditionalContext, hookMessageContext].filter(Boolean).join("\n\n");
|
|
1292
|
-
const finalContext = additionalContext + (allHookContext ? `
|
|
1293
|
-
|
|
1294
|
-
${allHookContext}` : "");
|
|
1295
|
-
const systemPrompt = buildSystemPrompt({
|
|
1296
|
-
soul: this.soul,
|
|
1297
|
-
userName,
|
|
1298
|
-
senderUsername,
|
|
1299
|
-
senderId: toolContext?.senderId,
|
|
1300
|
-
ownerName: this.config.telegram.owner_name,
|
|
1301
|
-
ownerUsername: this.config.telegram.owner_username,
|
|
1302
|
-
context: finalContext,
|
|
1303
|
-
includeMemory: !effectiveIsGroup,
|
|
1304
|
-
includeStrategy: !effectiveIsGroup,
|
|
1305
|
-
memoryFlushWarning: needsMemoryFlush
|
|
1306
|
-
});
|
|
1307
|
-
if (this.hookRunner) {
|
|
1308
|
-
const promptAfterEvent = {
|
|
1309
|
-
chatId,
|
|
1310
|
-
sessionId: session.sessionId,
|
|
1311
|
-
isGroup: effectiveIsGroup,
|
|
1312
|
-
promptLength: systemPrompt.length,
|
|
1313
|
-
sectionCount: (systemPrompt.match(/^#{1,3} /gm) || []).length,
|
|
1314
|
-
ragContextLength: relevantContext.length,
|
|
1315
|
-
hookContextLength: allHookContext.length
|
|
1316
|
-
};
|
|
1317
|
-
await this.hookRunner.runObservingHook("prompt:after", promptAfterEvent);
|
|
1318
|
-
}
|
|
1319
|
-
const userMsg = {
|
|
1320
|
-
role: "user",
|
|
1321
|
-
content: formattedMessage,
|
|
1322
|
-
timestamp: now
|
|
1323
|
-
};
|
|
1324
|
-
context.messages.push(userMsg);
|
|
1325
|
-
const preemptiveCompaction = await this.compactionManager.checkAndCompact(
|
|
1326
|
-
session.sessionId,
|
|
1327
|
-
context,
|
|
1328
|
-
getEffectiveApiKey(this.config.agent.provider, this.config.agent.api_key),
|
|
1329
|
-
chatId,
|
|
1330
|
-
this.config.agent.provider,
|
|
1331
|
-
this.config.agent.utility_model
|
|
1332
|
-
);
|
|
1333
|
-
if (preemptiveCompaction) {
|
|
1334
|
-
log3.info(`\u{1F5DC}\uFE0F Preemptive compaction triggered, reloading session...`);
|
|
1335
|
-
session = getSession(chatId);
|
|
1336
|
-
context = loadContextFromTranscript(session.sessionId);
|
|
1337
|
-
context.messages.push(userMsg);
|
|
1338
|
-
}
|
|
1339
|
-
appendToTranscript(session.sessionId, userMsg);
|
|
1340
|
-
const provider = this.config.agent.provider || "anthropic";
|
|
1341
|
-
const providerMeta = getProviderMetadata(provider);
|
|
1342
|
-
const isAdmin = toolContext?.config?.telegram.admin_ids.includes(toolContext.senderId) ?? false;
|
|
1343
|
-
let tools;
|
|
1344
|
-
{
|
|
1345
|
-
const toolIndex = this.toolRegistry?.getToolIndex();
|
|
1346
|
-
const useRAG = toolIndex?.isIndexed && this.config.tool_rag?.enabled !== false && !isTrivialMessage(effectiveMessage) && !(providerMeta.toolLimit === null && this.config.tool_rag?.skip_unlimited_providers !== false);
|
|
1347
|
-
if (useRAG && this.toolRegistry && queryEmbedding) {
|
|
1348
|
-
tools = await this.toolRegistry.getForContextWithRAG(
|
|
1349
|
-
effectiveMessage,
|
|
1350
|
-
queryEmbedding,
|
|
1351
|
-
effectiveIsGroup,
|
|
1352
|
-
providerMeta.toolLimit,
|
|
1353
|
-
chatId,
|
|
1354
|
-
isAdmin
|
|
1355
|
-
);
|
|
1356
|
-
log3.info(`\u{1F50D} Tool RAG: ${tools.length}/${this.toolRegistry.count} tools selected`);
|
|
1357
|
-
} else {
|
|
1358
|
-
tools = this.toolRegistry?.getForContext(
|
|
1359
|
-
effectiveIsGroup,
|
|
1360
|
-
providerMeta.toolLimit,
|
|
1361
|
-
chatId,
|
|
1362
|
-
isAdmin
|
|
1363
|
-
);
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
const maxIterations = this.config.agent.max_agentic_iterations || 5;
|
|
1367
|
-
let iteration = 0;
|
|
1368
|
-
let overflowResets = 0;
|
|
1369
|
-
let rateLimitRetries = 0;
|
|
1370
|
-
let serverErrorRetries = 0;
|
|
1371
|
-
let finalResponse = null;
|
|
1372
|
-
const totalToolCalls = [];
|
|
1373
|
-
const accumulatedTexts = [];
|
|
1374
|
-
const accumulatedUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalCost: 0 };
|
|
1375
|
-
const seenToolSignatures = /* @__PURE__ */ new Set();
|
|
1376
|
-
while (iteration < maxIterations) {
|
|
1377
|
-
iteration++;
|
|
1378
|
-
log3.debug(`\u{1F504} Agentic iteration ${iteration}/${maxIterations}`);
|
|
1379
|
-
const iterationStartIndex = context.messages.length;
|
|
1380
|
-
const maskedMessages = maskOldToolResults(context.messages, {
|
|
1381
|
-
toolRegistry: this.toolRegistry ?? void 0,
|
|
1382
|
-
currentIterationStartIndex: iterationStartIndex
|
|
1383
|
-
});
|
|
1384
|
-
const maskedContext = { ...context, messages: maskedMessages };
|
|
1385
|
-
const response2 = await chatWithContext(this.config.agent, {
|
|
1386
|
-
systemPrompt,
|
|
1387
|
-
context: maskedContext,
|
|
1388
|
-
sessionId: session.sessionId,
|
|
1389
|
-
persistTranscript: true,
|
|
1390
|
-
tools
|
|
1391
|
-
});
|
|
1392
|
-
const assistantMsg = response2.message;
|
|
1393
|
-
if (assistantMsg.stopReason === "error") {
|
|
1394
|
-
const errorMsg = assistantMsg.errorMessage || "";
|
|
1395
|
-
if (this.hookRunner) {
|
|
1396
|
-
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";
|
|
1397
|
-
const responseErrorEvent = {
|
|
1398
|
-
chatId,
|
|
1399
|
-
sessionId: session.sessionId,
|
|
1400
|
-
isGroup: effectiveIsGroup,
|
|
1401
|
-
error: errorMsg,
|
|
1402
|
-
errorCode,
|
|
1403
|
-
provider,
|
|
1404
|
-
model: this.config.agent.model,
|
|
1405
|
-
retryCount: rateLimitRetries + serverErrorRetries,
|
|
1406
|
-
durationMs: Date.now() - processStartTime
|
|
1407
|
-
};
|
|
1408
|
-
await this.hookRunner.runObservingHook("response:error", responseErrorEvent);
|
|
1409
|
-
}
|
|
1410
|
-
if (isContextOverflowError(errorMsg)) {
|
|
1411
|
-
overflowResets++;
|
|
1412
|
-
if (overflowResets > 1) {
|
|
1413
|
-
throw new Error(
|
|
1414
|
-
"Context overflow persists after session reset. Message may be too large for the model's context window."
|
|
1415
|
-
);
|
|
1416
|
-
}
|
|
1417
|
-
log3.error(`\u{1F6A8} Context overflow detected: ${errorMsg}`);
|
|
1418
|
-
log3.info(`\u{1F4BE} Saving session memory before reset...`);
|
|
1419
|
-
const summary = extractContextSummary(context, CONTEXT_OVERFLOW_SUMMARY_MESSAGES);
|
|
1420
|
-
appendToDailyLog(summary);
|
|
1421
|
-
log3.info(`\u2705 Memory saved to daily log`);
|
|
1422
|
-
const archived = archiveTranscript(session.sessionId);
|
|
1423
|
-
if (!archived) {
|
|
1424
|
-
log3.error(
|
|
1425
|
-
`\u26A0\uFE0F Failed to archive transcript ${session.sessionId}, proceeding with reset anyway`
|
|
1426
|
-
);
|
|
1427
|
-
}
|
|
1428
|
-
log3.info(`\u{1F504} Resetting session due to context overflow...`);
|
|
1429
|
-
session = resetSession(chatId);
|
|
1430
|
-
context = { messages: [userMsg] };
|
|
1431
|
-
appendToTranscript(session.sessionId, userMsg);
|
|
1432
|
-
log3.info(`\u{1F504} Retrying with fresh context...`);
|
|
1433
|
-
continue;
|
|
1434
|
-
} else if (errorMsg.toLowerCase().includes("rate") || errorMsg.includes("429")) {
|
|
1435
|
-
rateLimitRetries++;
|
|
1436
|
-
if (rateLimitRetries <= RATE_LIMIT_MAX_RETRIES) {
|
|
1437
|
-
const delay = 1e3 * Math.pow(2, rateLimitRetries - 1);
|
|
1438
|
-
log3.warn(
|
|
1439
|
-
`\u{1F6AB} Rate limited, retrying in ${delay}ms (attempt ${rateLimitRetries}/${RATE_LIMIT_MAX_RETRIES})...`
|
|
1440
|
-
);
|
|
1441
|
-
await new Promise((r3) => setTimeout(r3, delay));
|
|
1442
|
-
iteration--;
|
|
1443
|
-
continue;
|
|
1444
|
-
}
|
|
1445
|
-
log3.error(`\u{1F6AB} Rate limited after ${RATE_LIMIT_MAX_RETRIES} retries: ${errorMsg}`);
|
|
1446
|
-
throw new Error(
|
|
1447
|
-
`API rate limited after ${RATE_LIMIT_MAX_RETRIES} retries. Please try again later.`
|
|
1448
|
-
);
|
|
1449
|
-
} else if (errorMsg.includes("500") || errorMsg.includes("502") || errorMsg.includes("503") || errorMsg.includes("529")) {
|
|
1450
|
-
serverErrorRetries++;
|
|
1451
|
-
if (serverErrorRetries <= SERVER_ERROR_MAX_RETRIES) {
|
|
1452
|
-
const delay = 2e3 * Math.pow(2, serverErrorRetries - 1);
|
|
1453
|
-
log3.warn(
|
|
1454
|
-
`\u{1F504} Server error, retrying in ${delay}ms (attempt ${serverErrorRetries}/${SERVER_ERROR_MAX_RETRIES})...`
|
|
1455
|
-
);
|
|
1456
|
-
await new Promise((r3) => setTimeout(r3, delay));
|
|
1457
|
-
iteration--;
|
|
1458
|
-
continue;
|
|
1459
|
-
}
|
|
1460
|
-
log3.error(`\u{1F6A8} Server error after ${SERVER_ERROR_MAX_RETRIES} retries: ${errorMsg}`);
|
|
1461
|
-
throw new Error(
|
|
1462
|
-
`API server error after ${SERVER_ERROR_MAX_RETRIES} retries. The provider may be experiencing issues.`
|
|
1463
|
-
);
|
|
1464
|
-
} else {
|
|
1465
|
-
log3.error(`\u{1F6A8} API error: ${errorMsg}`);
|
|
1466
|
-
throw new Error(`API error: ${errorMsg || "Unknown error"}`);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
const iterUsage = response2.message.usage;
|
|
1470
|
-
if (iterUsage) {
|
|
1471
|
-
accumulatedUsage.input += iterUsage.input;
|
|
1472
|
-
accumulatedUsage.output += iterUsage.output;
|
|
1473
|
-
accumulatedUsage.cacheRead += iterUsage.cacheRead ?? 0;
|
|
1474
|
-
accumulatedUsage.cacheWrite += iterUsage.cacheWrite ?? 0;
|
|
1475
|
-
accumulatedUsage.totalCost += iterUsage.cost?.total ?? 0;
|
|
1476
|
-
}
|
|
1477
|
-
if (response2.text) {
|
|
1478
|
-
accumulatedTexts.push(response2.text);
|
|
1479
|
-
}
|
|
1480
|
-
const toolCalls = response2.message.content.filter((block) => block.type === "toolCall");
|
|
1481
|
-
if (toolCalls.length === 0) {
|
|
1482
|
-
log3.info(`\u{1F504} ${iteration}/${maxIterations} \u2192 done`);
|
|
1483
|
-
finalResponse = response2;
|
|
1484
|
-
break;
|
|
1485
|
-
}
|
|
1486
|
-
if (!this.toolRegistry || !toolContext) {
|
|
1487
|
-
log3.error("\u26A0\uFE0F Cannot execute tools: registry or context missing");
|
|
1488
|
-
break;
|
|
1489
|
-
}
|
|
1490
|
-
log3.debug(`\u{1F527} Executing ${toolCalls.length} tool call(s)`);
|
|
1491
|
-
context.messages.push(response2.message);
|
|
1492
|
-
const iterationToolNames = [];
|
|
1493
|
-
const fullContext = {
|
|
1494
|
-
...toolContext,
|
|
1495
|
-
chatId,
|
|
1496
|
-
isGroup: effectiveIsGroup
|
|
1497
|
-
};
|
|
1498
|
-
const toolPlans = [];
|
|
1499
|
-
for (const block of toolCalls) {
|
|
1500
|
-
if (block.type !== "toolCall") continue;
|
|
1501
|
-
let toolParams = block.arguments ?? {};
|
|
1502
|
-
let blocked = false;
|
|
1503
|
-
let blockReason = "";
|
|
1504
|
-
if (this.hookRunner) {
|
|
1505
|
-
const beforeEvent = {
|
|
1506
|
-
toolName: block.name,
|
|
1507
|
-
params: structuredClone(toolParams),
|
|
1508
|
-
chatId,
|
|
1509
|
-
isGroup: effectiveIsGroup,
|
|
1510
|
-
block: false,
|
|
1511
|
-
blockReason: ""
|
|
1512
|
-
};
|
|
1513
|
-
await this.hookRunner.runModifyingHook("tool:before", beforeEvent);
|
|
1514
|
-
if (beforeEvent.block) {
|
|
1515
|
-
blocked = true;
|
|
1516
|
-
blockReason = beforeEvent.blockReason || "Blocked by plugin hook";
|
|
1517
|
-
} else {
|
|
1518
|
-
toolParams = structuredClone(beforeEvent.params);
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
toolPlans.push({ block, blocked, blockReason, params: toolParams });
|
|
1522
|
-
}
|
|
1523
|
-
const execResults = new Array(toolPlans.length);
|
|
1524
|
-
{
|
|
1525
|
-
let cursor = 0;
|
|
1526
|
-
const runWorker = async () => {
|
|
1527
|
-
while (cursor < toolPlans.length) {
|
|
1528
|
-
const idx = cursor++;
|
|
1529
|
-
const plan = toolPlans[idx];
|
|
1530
|
-
if (plan.blocked) {
|
|
1531
|
-
execResults[idx] = {
|
|
1532
|
-
result: { success: false, error: plan.blockReason },
|
|
1533
|
-
durationMs: 0
|
|
1534
|
-
};
|
|
1535
|
-
continue;
|
|
1536
|
-
}
|
|
1537
|
-
const startTime = Date.now();
|
|
1538
|
-
try {
|
|
1539
|
-
const result = await this.toolRegistry.execute(
|
|
1540
|
-
{ ...plan.block, arguments: plan.params },
|
|
1541
|
-
fullContext
|
|
1542
|
-
);
|
|
1543
|
-
execResults[idx] = { result, durationMs: Date.now() - startTime };
|
|
1544
|
-
} catch (execErr) {
|
|
1545
|
-
const errMsg = execErr instanceof Error ? execErr.message : String(execErr);
|
|
1546
|
-
const errStack = execErr instanceof Error ? execErr.stack : void 0;
|
|
1547
|
-
execResults[idx] = {
|
|
1548
|
-
result: { success: false, error: errMsg },
|
|
1549
|
-
durationMs: Date.now() - startTime,
|
|
1550
|
-
execError: { message: errMsg, stack: errStack }
|
|
1551
|
-
};
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
};
|
|
1555
|
-
const workers = Math.min(TOOL_CONCURRENCY_LIMIT, toolPlans.length);
|
|
1556
|
-
await Promise.all(Array.from({ length: workers }, () => runWorker()));
|
|
1557
|
-
}
|
|
1558
|
-
for (let i = 0; i < toolPlans.length; i++) {
|
|
1559
|
-
const plan = toolPlans[i];
|
|
1560
|
-
const { block } = plan;
|
|
1561
|
-
const exec = execResults[i];
|
|
1562
|
-
if (exec.execError && this.hookRunner) {
|
|
1563
|
-
const errorEvent = {
|
|
1564
|
-
toolName: block.name,
|
|
1565
|
-
params: structuredClone(plan.params),
|
|
1566
|
-
error: exec.execError.message,
|
|
1567
|
-
stack: exec.execError.stack,
|
|
1568
|
-
chatId,
|
|
1569
|
-
isGroup: effectiveIsGroup,
|
|
1570
|
-
durationMs: exec.durationMs
|
|
1571
|
-
};
|
|
1572
|
-
await this.hookRunner.runObservingHook("tool:error", errorEvent);
|
|
1573
|
-
}
|
|
1574
|
-
if (this.hookRunner) {
|
|
1575
|
-
const afterEvent = {
|
|
1576
|
-
toolName: block.name,
|
|
1577
|
-
params: structuredClone(plan.params),
|
|
1578
|
-
result: {
|
|
1579
|
-
success: exec.result.success,
|
|
1580
|
-
data: exec.result.data,
|
|
1581
|
-
error: exec.result.error
|
|
1582
|
-
},
|
|
1583
|
-
durationMs: exec.durationMs,
|
|
1584
|
-
chatId,
|
|
1585
|
-
isGroup: effectiveIsGroup,
|
|
1586
|
-
...plan.blocked ? { blocked: true, blockReason: plan.blockReason } : {}
|
|
1587
|
-
};
|
|
1588
|
-
await this.hookRunner.runObservingHook("tool:after", afterEvent);
|
|
1589
|
-
}
|
|
1590
|
-
log3.debug(`${block.name}: ${exec.result.success ? "\u2713" : "\u2717"} ${exec.result.error || ""}`);
|
|
1591
|
-
iterationToolNames.push(`${block.name} ${exec.result.success ? "\u2713" : "\u2717"}`);
|
|
1592
|
-
totalToolCalls.push({
|
|
1593
|
-
name: block.name,
|
|
1594
|
-
input: block.arguments
|
|
1595
|
-
});
|
|
1596
|
-
let resultText = JSON.stringify(exec.result);
|
|
1597
|
-
if (resultText.length > MAX_TOOL_RESULT_SIZE) {
|
|
1598
|
-
log3.warn(`\u26A0\uFE0F Tool result too large (${resultText.length} chars), truncating...`);
|
|
1599
|
-
const data = exec.result.data;
|
|
1600
|
-
if (data?.summary || data?.message) {
|
|
1601
|
-
resultText = JSON.stringify({
|
|
1602
|
-
success: exec.result.success,
|
|
1603
|
-
data: {
|
|
1604
|
-
summary: data.summary || data.message,
|
|
1605
|
-
_truncated: true,
|
|
1606
|
-
_originalSize: resultText.length,
|
|
1607
|
-
_message: "Full data truncated. Use limit parameter for smaller results."
|
|
1608
|
-
}
|
|
1609
|
-
});
|
|
1610
|
-
} else {
|
|
1611
|
-
const summarized = {
|
|
1612
|
-
_truncated: true,
|
|
1613
|
-
_originalSize: resultText.length,
|
|
1614
|
-
_message: "Full data truncated. Use limit parameter for smaller results."
|
|
1615
|
-
};
|
|
1616
|
-
if (data && typeof data === "object") {
|
|
1617
|
-
for (const [key, value] of Object.entries(data)) {
|
|
1618
|
-
if (Array.isArray(value)) {
|
|
1619
|
-
summarized[key] = `[${value.length} items]`;
|
|
1620
|
-
} else if (typeof value === "string" && value.length > 500) {
|
|
1621
|
-
summarized[key] = value.slice(0, 500) + "...[truncated]";
|
|
1622
|
-
} else {
|
|
1623
|
-
summarized[key] = value;
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
resultText = JSON.stringify({ success: exec.result.success, data: summarized });
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
if (provider === "cocoon") {
|
|
1631
|
-
const { wrapToolResult } = await import("./tool-adapter-IVX2XQJE.js");
|
|
1632
|
-
const cocoonResultMsg = {
|
|
1633
|
-
role: "user",
|
|
1634
|
-
content: [
|
|
1635
|
-
{
|
|
1636
|
-
type: "text",
|
|
1637
|
-
text: wrapToolResult(resultText)
|
|
1638
|
-
}
|
|
1639
|
-
],
|
|
1640
|
-
timestamp: Date.now()
|
|
1641
|
-
};
|
|
1642
|
-
context.messages.push(cocoonResultMsg);
|
|
1643
|
-
appendToTranscript(session.sessionId, cocoonResultMsg);
|
|
1644
|
-
} else {
|
|
1645
|
-
const toolResultMsg = {
|
|
1646
|
-
role: "toolResult",
|
|
1647
|
-
toolCallId: block.id,
|
|
1648
|
-
toolName: block.name,
|
|
1649
|
-
content: [
|
|
1650
|
-
{
|
|
1651
|
-
type: "text",
|
|
1652
|
-
text: resultText
|
|
1653
|
-
}
|
|
1654
|
-
],
|
|
1655
|
-
isError: !exec.result.success,
|
|
1656
|
-
timestamp: Date.now()
|
|
1657
|
-
};
|
|
1658
|
-
context.messages.push(toolResultMsg);
|
|
1659
|
-
appendToTranscript(session.sessionId, toolResultMsg);
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
log3.info(`\u{1F504} ${iteration}/${maxIterations} \u2192 ${iterationToolNames.join(", ")}`);
|
|
1663
|
-
const iterSignatures = toolPlans.map(
|
|
1664
|
-
(p2) => `${p2.block.name}:${JSON.stringify(p2.params, Object.keys(p2.params).sort())}`
|
|
1665
|
-
);
|
|
1666
|
-
const allDuplicates = iterSignatures.length > 0 && iterSignatures.every((sig) => seenToolSignatures.has(sig));
|
|
1667
|
-
for (const sig of iterSignatures) seenToolSignatures.add(sig);
|
|
1668
|
-
if (allDuplicates) {
|
|
1669
|
-
log3.warn(
|
|
1670
|
-
`\u{1F501} Loop stall detected: all ${iterSignatures.length} tool call(s) are repeats \u2014 breaking early`
|
|
1671
|
-
);
|
|
1672
|
-
finalResponse = response2;
|
|
1673
|
-
break;
|
|
1674
|
-
}
|
|
1675
|
-
if (iteration === maxIterations) {
|
|
1676
|
-
log3.info(`\u26A0\uFE0F Max iterations reached (${maxIterations})`);
|
|
1677
|
-
finalResponse = response2;
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
if (!finalResponse) {
|
|
1681
|
-
log3.error("\u26A0\uFE0F Agentic loop exited early without final response");
|
|
1682
|
-
return {
|
|
1683
|
-
content: "Internal error: Agent loop failed to produce a response.",
|
|
1684
|
-
toolCalls: []
|
|
1685
|
-
};
|
|
1686
|
-
}
|
|
1687
|
-
const response = finalResponse;
|
|
1688
|
-
const lastMsg = context.messages[context.messages.length - 1];
|
|
1689
|
-
if (lastMsg?.role !== "assistant") {
|
|
1690
|
-
context.messages.push(response.message);
|
|
1691
|
-
}
|
|
1692
|
-
const sessionUpdate = {
|
|
1693
|
-
updatedAt: Date.now(),
|
|
1694
|
-
messageCount: session.messageCount + 1,
|
|
1695
|
-
model: this.config.agent.model,
|
|
1696
|
-
provider: this.config.agent.provider,
|
|
1697
|
-
inputTokens: (session.inputTokens ?? 0) + accumulatedUsage.input + accumulatedUsage.cacheRead + accumulatedUsage.cacheWrite,
|
|
1698
|
-
outputTokens: (session.outputTokens ?? 0) + accumulatedUsage.output
|
|
1699
|
-
};
|
|
1700
|
-
updateSession(chatId, sessionUpdate);
|
|
1701
|
-
if (accumulatedUsage.input > 0 || accumulatedUsage.output > 0) {
|
|
1702
|
-
const u = accumulatedUsage;
|
|
1703
|
-
const totalInput = u.input + u.cacheRead + u.cacheWrite;
|
|
1704
|
-
const inK = (totalInput / 1e3).toFixed(1);
|
|
1705
|
-
const cacheParts = [];
|
|
1706
|
-
if (u.cacheRead) cacheParts.push(`${(u.cacheRead / 1e3).toFixed(1)}K cached`);
|
|
1707
|
-
if (u.cacheWrite) cacheParts.push(`${(u.cacheWrite / 1e3).toFixed(1)}K new`);
|
|
1708
|
-
const cacheInfo = cacheParts.length > 0 ? ` (${cacheParts.join(", ")})` : "";
|
|
1709
|
-
log3.info(`\u{1F4B0} ${inK}K in${cacheInfo}, ${u.output} out | $${u.totalCost.toFixed(3)}`);
|
|
1710
|
-
globalTokenUsage.totalTokens += u.input + u.output + u.cacheRead + u.cacheWrite;
|
|
1711
|
-
globalTokenUsage.totalCost += u.totalCost;
|
|
1712
|
-
}
|
|
1713
|
-
let content = accumulatedTexts.join("\n").trim() || response.text;
|
|
1714
|
-
const usedTelegramSendTool = totalToolCalls.some((tc) => TELEGRAM_SEND_TOOLS.has(tc.name));
|
|
1715
|
-
if (!content && totalToolCalls.length > 0 && !usedTelegramSendTool) {
|
|
1716
|
-
log3.warn("\u26A0\uFE0F Empty response after tool calls - generating fallback");
|
|
1717
|
-
content = "I executed the requested action but couldn't generate a response. Please try again.";
|
|
1718
|
-
} else if (!content && usedTelegramSendTool) {
|
|
1719
|
-
log3.info("\u2705 Response sent via Telegram tool - no additional text needed");
|
|
1720
|
-
content = "";
|
|
1721
|
-
} else if (!content && accumulatedUsage.input === 0 && accumulatedUsage.output === 0) {
|
|
1722
|
-
log3.warn("\u26A0\uFE0F Empty response with zero tokens - possible API issue");
|
|
1723
|
-
content = "I couldn't process your request. Please try again.";
|
|
1724
|
-
}
|
|
1725
|
-
let responseMetadata = {};
|
|
1726
|
-
if (this.hookRunner) {
|
|
1727
|
-
const responseBeforeEvent = {
|
|
1728
|
-
chatId,
|
|
1729
|
-
sessionId: session.sessionId,
|
|
1730
|
-
isGroup: effectiveIsGroup,
|
|
1731
|
-
originalText: content,
|
|
1732
|
-
text: content,
|
|
1733
|
-
block: false,
|
|
1734
|
-
blockReason: "",
|
|
1735
|
-
metadata: {}
|
|
1736
|
-
};
|
|
1737
|
-
await this.hookRunner.runModifyingHook("response:before", responseBeforeEvent);
|
|
1738
|
-
if (responseBeforeEvent.block) {
|
|
1739
|
-
log3.info(
|
|
1740
|
-
`\u{1F6AB} Response blocked by hook: ${responseBeforeEvent.blockReason || "no reason"}`
|
|
1741
|
-
);
|
|
1742
|
-
content = "";
|
|
1743
|
-
} else {
|
|
1744
|
-
content = responseBeforeEvent.text;
|
|
1745
|
-
}
|
|
1746
|
-
responseMetadata = responseBeforeEvent.metadata;
|
|
1747
|
-
}
|
|
1748
|
-
if (this.hookRunner) {
|
|
1749
|
-
const responseAfterEvent = {
|
|
1750
|
-
chatId,
|
|
1751
|
-
sessionId: session.sessionId,
|
|
1752
|
-
isGroup: effectiveIsGroup,
|
|
1753
|
-
text: content,
|
|
1754
|
-
durationMs: Date.now() - processStartTime,
|
|
1755
|
-
toolsUsed: totalToolCalls.map((tc) => tc.name),
|
|
1756
|
-
tokenUsage: accumulatedUsage.input > 0 || accumulatedUsage.output > 0 ? { input: accumulatedUsage.input, output: accumulatedUsage.output } : void 0,
|
|
1757
|
-
metadata: responseMetadata
|
|
1758
|
-
};
|
|
1759
|
-
await this.hookRunner.runObservingHook("response:after", responseAfterEvent);
|
|
1760
|
-
}
|
|
1761
|
-
return {
|
|
1762
|
-
content,
|
|
1763
|
-
toolCalls: totalToolCalls
|
|
1764
|
-
};
|
|
1765
|
-
} catch (error) {
|
|
1766
|
-
log3.error({ err: error }, "Agent error");
|
|
1767
|
-
throw error;
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
clearHistory(chatId) {
|
|
1771
|
-
const db = getDatabase().getDb();
|
|
1772
|
-
db.prepare(
|
|
1773
|
-
`DELETE FROM tg_messages_vec WHERE id IN (
|
|
1774
|
-
SELECT id FROM tg_messages WHERE chat_id = ?
|
|
1775
|
-
)`
|
|
1776
|
-
).run(chatId);
|
|
1777
|
-
db.prepare(`DELETE FROM tg_messages WHERE chat_id = ?`).run(chatId);
|
|
1778
|
-
resetSession(chatId);
|
|
1779
|
-
log3.info(`\u{1F5D1}\uFE0F Cleared history for chat ${chatId}`);
|
|
1780
|
-
}
|
|
1781
|
-
getConfig() {
|
|
1782
|
-
return this.config;
|
|
1783
|
-
}
|
|
1784
|
-
getActiveChatIds() {
|
|
1785
|
-
const db = getDatabase().getDb();
|
|
1786
|
-
const rows = db.prepare(
|
|
1787
|
-
`
|
|
1788
|
-
SELECT DISTINCT chat_id
|
|
1789
|
-
FROM tg_messages
|
|
1790
|
-
ORDER BY timestamp DESC
|
|
1791
|
-
`
|
|
1792
|
-
).all();
|
|
1793
|
-
return rows.map((r3) => r3.chat_id);
|
|
1794
|
-
}
|
|
1795
|
-
setSoul(soul) {
|
|
1796
|
-
this.soul = soul;
|
|
1797
|
-
}
|
|
1798
|
-
configureCompaction(config) {
|
|
1799
|
-
this.compactionManager.updateConfig(config);
|
|
1800
|
-
log3.info({ config: this.compactionManager.getConfig() }, `\u{1F5DC}\uFE0F Compaction config updated`);
|
|
1801
|
-
}
|
|
1802
|
-
getCompactionConfig() {
|
|
1803
|
-
return this.compactionManager.getConfig();
|
|
1804
|
-
}
|
|
1805
|
-
_memoryStatsCache = null;
|
|
1806
|
-
getMemoryStats() {
|
|
1807
|
-
const now = Date.now();
|
|
1808
|
-
if (this._memoryStatsCache && now < this._memoryStatsCache.expiry) {
|
|
1809
|
-
return this._memoryStatsCache.data;
|
|
1810
|
-
}
|
|
1811
|
-
const db = getDatabase().getDb();
|
|
1812
|
-
const msgCount = db.prepare(`SELECT COUNT(*) as count FROM tg_messages`).get();
|
|
1813
|
-
const chatCount = db.prepare(`SELECT COUNT(DISTINCT chat_id) as count FROM tg_messages`).get();
|
|
1814
|
-
const knowledgeCount = db.prepare(`SELECT COUNT(*) as count FROM knowledge`).get();
|
|
1815
|
-
const data = {
|
|
1816
|
-
totalMessages: msgCount.count,
|
|
1817
|
-
totalChats: chatCount.count,
|
|
1818
|
-
knowledgeChunks: knowledgeCount.count
|
|
1819
|
-
};
|
|
1820
|
-
this._memoryStatsCache = { data, expiry: now + 5 * 60 * 1e3 };
|
|
1821
|
-
return data;
|
|
1822
|
-
}
|
|
1823
|
-
};
|
|
1824
539
|
|
|
1825
540
|
// src/bot/services/styled-keyboard.ts
|
|
1826
541
|
import { Api } from "telegram";
|
|
@@ -2007,7 +722,7 @@ function unescapeHtml(text) {
|
|
|
2007
722
|
}
|
|
2008
723
|
|
|
2009
724
|
// src/bot/inline-router.ts
|
|
2010
|
-
var
|
|
725
|
+
var log2 = createLogger("InlineRouter");
|
|
2011
726
|
var INLINE_TIMEOUT_MS = 5e3;
|
|
2012
727
|
var CALLBACK_TIMEOUT_MS = 15e3;
|
|
2013
728
|
function compileGlob(pattern) {
|
|
@@ -2028,11 +743,11 @@ var InlineRouter = class {
|
|
|
2028
743
|
}
|
|
2029
744
|
registerPlugin(name, handlers) {
|
|
2030
745
|
this.plugins.set(name, handlers);
|
|
2031
|
-
|
|
746
|
+
log2.info(`Registered plugin "${name}" for inline routing`);
|
|
2032
747
|
}
|
|
2033
748
|
unregisterPlugin(name) {
|
|
2034
749
|
this.plugins.delete(name);
|
|
2035
|
-
|
|
750
|
+
log2.info(`Unregistered plugin "${name}" from inline routing`);
|
|
2036
751
|
}
|
|
2037
752
|
hasPlugin(name) {
|
|
2038
753
|
return this.plugins.has(name);
|
|
@@ -2104,7 +819,7 @@ var InlineRouter = class {
|
|
|
2104
819
|
is_personal: true
|
|
2105
820
|
});
|
|
2106
821
|
} catch (error) {
|
|
2107
|
-
|
|
822
|
+
log2.error({ err: error }, `Plugin "${pluginName}" inline query handler failed`);
|
|
2108
823
|
try {
|
|
2109
824
|
await ctx.answerInlineQuery([], { cache_time: 0, is_personal: true });
|
|
2110
825
|
} catch {
|
|
@@ -2164,7 +879,7 @@ var InlineRouter = class {
|
|
|
2164
879
|
} catch (error) {
|
|
2165
880
|
const errMsg = error?.errorMessage;
|
|
2166
881
|
if (errMsg === "MESSAGE_NOT_MODIFIED") return;
|
|
2167
|
-
|
|
882
|
+
log2.debug(`GramJS edit failed, falling back to Grammy: ${errMsg || error}`);
|
|
2168
883
|
}
|
|
2169
884
|
}
|
|
2170
885
|
const replyMarkup = styledButtons ? toGrammyKeyboard(styledButtons) : void 0;
|
|
@@ -2184,7 +899,7 @@ var InlineRouter = class {
|
|
|
2184
899
|
await ctx.answerCallbackQuery();
|
|
2185
900
|
}
|
|
2186
901
|
} catch (error) {
|
|
2187
|
-
|
|
902
|
+
log2.error({ err: error }, `Plugin "${pluginName}" callback handler failed`);
|
|
2188
903
|
if (!answered) {
|
|
2189
904
|
try {
|
|
2190
905
|
await ctx.answerCallbackQuery({ text: "Error processing action" });
|
|
@@ -2207,7 +922,7 @@ var InlineRouter = class {
|
|
|
2207
922
|
};
|
|
2208
923
|
await plugin.onChosenResult(crCtx);
|
|
2209
924
|
} catch (error) {
|
|
2210
|
-
|
|
925
|
+
log2.error({ err: error }, `Plugin "${pluginName}" chosen result handler failed`);
|
|
2211
926
|
}
|
|
2212
927
|
}
|
|
2213
928
|
/**
|
|
@@ -2317,7 +1032,7 @@ import { promisify } from "util";
|
|
|
2317
1032
|
|
|
2318
1033
|
// src/agent/tools/plugin-validator.ts
|
|
2319
1034
|
import { z } from "zod";
|
|
2320
|
-
var
|
|
1035
|
+
var log3 = createLogger("PluginValidator");
|
|
2321
1036
|
var ManifestSchema = z.object({
|
|
2322
1037
|
name: z.string().min(1).max(64).regex(
|
|
2323
1038
|
/^[a-z0-9][a-z0-9-]*$/,
|
|
@@ -2360,20 +1075,20 @@ function validateToolDefs(defs, pluginName) {
|
|
|
2360
1075
|
const valid = [];
|
|
2361
1076
|
for (const def of defs) {
|
|
2362
1077
|
if (!def || typeof def !== "object") {
|
|
2363
|
-
|
|
1078
|
+
log3.warn(`[${pluginName}] tool is not an object, skipping`);
|
|
2364
1079
|
continue;
|
|
2365
1080
|
}
|
|
2366
1081
|
const t = def;
|
|
2367
1082
|
if (!t.name || typeof t.name !== "string") {
|
|
2368
|
-
|
|
1083
|
+
log3.warn(`[${pluginName}] tool missing 'name', skipping`);
|
|
2369
1084
|
continue;
|
|
2370
1085
|
}
|
|
2371
1086
|
if (!t.description || typeof t.description !== "string") {
|
|
2372
|
-
|
|
1087
|
+
log3.warn(`[${pluginName}] tool "${t.name}" missing 'description', skipping`);
|
|
2373
1088
|
continue;
|
|
2374
1089
|
}
|
|
2375
1090
|
if (!t.execute || typeof t.execute !== "function") {
|
|
2376
|
-
|
|
1091
|
+
log3.warn(`[${pluginName}] tool "${t.name}" missing 'execute' function, skipping`);
|
|
2377
1092
|
continue;
|
|
2378
1093
|
}
|
|
2379
1094
|
valid.push(t);
|
|
@@ -2433,25 +1148,25 @@ function withTxLock(fn) {
|
|
|
2433
1148
|
}
|
|
2434
1149
|
|
|
2435
1150
|
// src/ton/transfer.ts
|
|
2436
|
-
var
|
|
1151
|
+
var log4 = createLogger("TON");
|
|
2437
1152
|
async function sendTon(params) {
|
|
2438
1153
|
return withTxLock(async () => {
|
|
2439
1154
|
try {
|
|
2440
1155
|
const { toAddress: toAddress2, amount, comment = "", bounce = false } = params;
|
|
2441
1156
|
if (!Number.isFinite(amount) || amount <= 0) {
|
|
2442
|
-
|
|
1157
|
+
log4.error({ amount }, "Invalid transfer amount");
|
|
2443
1158
|
return null;
|
|
2444
1159
|
}
|
|
2445
1160
|
let recipientAddress;
|
|
2446
1161
|
try {
|
|
2447
1162
|
recipientAddress = Address.parse(toAddress2);
|
|
2448
1163
|
} catch (e) {
|
|
2449
|
-
|
|
1164
|
+
log4.error({ err: e }, `Invalid recipient address: ${toAddress2}`);
|
|
2450
1165
|
return null;
|
|
2451
1166
|
}
|
|
2452
1167
|
const keyPair = await getKeyPair();
|
|
2453
1168
|
if (!keyPair) {
|
|
2454
|
-
|
|
1169
|
+
log4.error("Wallet not initialized");
|
|
2455
1170
|
return null;
|
|
2456
1171
|
}
|
|
2457
1172
|
const wallet = WalletContractV5R1.create({
|
|
@@ -2475,7 +1190,7 @@ async function sendTon(params) {
|
|
|
2475
1190
|
]
|
|
2476
1191
|
});
|
|
2477
1192
|
const pseudoHash = `${seqno}_${Date.now()}_${amount.toFixed(2)}`;
|
|
2478
|
-
|
|
1193
|
+
log4.info(`Sent ${amount} TON to ${toAddress2.slice(0, 8)}... - seqno: ${seqno}`);
|
|
2479
1194
|
return pseudoHash;
|
|
2480
1195
|
} catch (error) {
|
|
2481
1196
|
const err = error;
|
|
@@ -2483,14 +1198,14 @@ async function sendTon(params) {
|
|
|
2483
1198
|
if (status === 429 || status !== void 0 && status >= 500) {
|
|
2484
1199
|
invalidateTonClientCache();
|
|
2485
1200
|
}
|
|
2486
|
-
|
|
1201
|
+
log4.error({ err: error }, "Error sending TON");
|
|
2487
1202
|
throw error;
|
|
2488
1203
|
}
|
|
2489
1204
|
});
|
|
2490
1205
|
}
|
|
2491
1206
|
|
|
2492
1207
|
// src/utils/retry.ts
|
|
2493
|
-
var
|
|
1208
|
+
var log5 = createLogger("Utils");
|
|
2494
1209
|
var DEFAULT_OPTIONS = {
|
|
2495
1210
|
maxAttempts: RETRY_DEFAULT_MAX_ATTEMPTS,
|
|
2496
1211
|
baseDelayMs: RETRY_DEFAULT_BASE_DELAY_MS,
|
|
@@ -2514,7 +1229,7 @@ async function withRetry(fn, options = {}) {
|
|
|
2514
1229
|
return result;
|
|
2515
1230
|
} catch (error) {
|
|
2516
1231
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2517
|
-
|
|
1232
|
+
log5.warn(`Retry attempt ${attempt}/${opts.maxAttempts} failed: ${lastError.message}`);
|
|
2518
1233
|
if (attempt < opts.maxAttempts) {
|
|
2519
1234
|
const delay = Math.min(opts.baseDelayMs * Math.pow(2, attempt - 1), opts.maxDelayMs);
|
|
2520
1235
|
await sleep(delay);
|
|
@@ -5992,7 +4707,7 @@ var DEDUST_GAS = {
|
|
|
5992
4707
|
var NATIVE_TON_ADDRESS = "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c";
|
|
5993
4708
|
|
|
5994
4709
|
// src/agent/tools/dedust/asset-cache.ts
|
|
5995
|
-
var
|
|
4710
|
+
var log6 = createLogger("Tools");
|
|
5996
4711
|
var ASSET_LIST_URL = "https://assets.dedust.io/list.json";
|
|
5997
4712
|
var CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
5998
4713
|
var cachedAssets = [];
|
|
@@ -6011,7 +4726,7 @@ async function getAssetList() {
|
|
|
6011
4726
|
return cachedAssets;
|
|
6012
4727
|
} catch (error) {
|
|
6013
4728
|
if (cachedAssets.length > 0) {
|
|
6014
|
-
|
|
4729
|
+
log6.warn({ err: error }, "Asset list fetch failed, using stale cache");
|
|
6015
4730
|
return cachedAssets;
|
|
6016
4731
|
}
|
|
6017
4732
|
throw error;
|
|
@@ -6064,7 +4779,7 @@ var stonApiClient = new StonApiClient();
|
|
|
6064
4779
|
function isTon(asset) {
|
|
6065
4780
|
return asset.toLowerCase() === "ton";
|
|
6066
4781
|
}
|
|
6067
|
-
async function getStonfiQuote(fromAsset, toAsset, amount, slippage,
|
|
4782
|
+
async function getStonfiQuote(fromAsset, toAsset, amount, slippage, log10) {
|
|
6068
4783
|
try {
|
|
6069
4784
|
const isTonInput = isTon(fromAsset);
|
|
6070
4785
|
const isTonOutput = isTon(toAsset);
|
|
@@ -6095,11 +4810,11 @@ async function getStonfiQuote(fromAsset, toAsset, amount, slippage, log12) {
|
|
|
6095
4810
|
fee: feeAmount.toFixed(6)
|
|
6096
4811
|
};
|
|
6097
4812
|
} catch (err) {
|
|
6098
|
-
|
|
4813
|
+
log10.debug("dex.quoteSTONfi() failed:", err);
|
|
6099
4814
|
return null;
|
|
6100
4815
|
}
|
|
6101
4816
|
}
|
|
6102
|
-
async function getDedustQuote(fromAsset, toAsset, amount, slippage,
|
|
4817
|
+
async function getDedustQuote(fromAsset, toAsset, amount, slippage, log10) {
|
|
6103
4818
|
try {
|
|
6104
4819
|
const isTonInput = isTon(fromAsset);
|
|
6105
4820
|
const isTonOutput = isTon(toAsset);
|
|
@@ -6133,7 +4848,7 @@ async function getDedustQuote(fromAsset, toAsset, amount, slippage, log12) {
|
|
|
6133
4848
|
poolType
|
|
6134
4849
|
};
|
|
6135
4850
|
} catch (err) {
|
|
6136
|
-
|
|
4851
|
+
log10.debug("dex.quoteDeDust() failed:", err);
|
|
6137
4852
|
return null;
|
|
6138
4853
|
}
|
|
6139
4854
|
}
|
|
@@ -6305,14 +5020,14 @@ function validateDexParams(amount, slippage) {
|
|
|
6305
5020
|
throw new PluginSDKError("Slippage must be between 0 and 1", "OPERATION_FAILED");
|
|
6306
5021
|
}
|
|
6307
5022
|
}
|
|
6308
|
-
function createDexSDK(
|
|
5023
|
+
function createDexSDK(log10) {
|
|
6309
5024
|
return {
|
|
6310
5025
|
async quote(params) {
|
|
6311
5026
|
validateDexParams(params.amount, params.slippage);
|
|
6312
5027
|
const slippage = params.slippage ?? 0.01;
|
|
6313
5028
|
const [stonfi, dedust] = await Promise.all([
|
|
6314
|
-
getStonfiQuote(params.fromAsset, params.toAsset, params.amount, slippage,
|
|
6315
|
-
getDedustQuote(params.fromAsset, params.toAsset, params.amount, slippage,
|
|
5029
|
+
getStonfiQuote(params.fromAsset, params.toAsset, params.amount, slippage, log10),
|
|
5030
|
+
getDedustQuote(params.fromAsset, params.toAsset, params.amount, slippage, log10)
|
|
6316
5031
|
]);
|
|
6317
5032
|
if (!stonfi && !dedust) {
|
|
6318
5033
|
throw new PluginSDKError("No DEX has liquidity for this pair", "OPERATION_FAILED");
|
|
@@ -6326,7 +5041,14 @@ function createDexSDK(log12) {
|
|
|
6326
5041
|
} else {
|
|
6327
5042
|
const stonfiOut = parseFloat(stonfi.expectedOutput);
|
|
6328
5043
|
const dedustOut = parseFloat(dedust.expectedOutput);
|
|
6329
|
-
if (stonfiOut
|
|
5044
|
+
if (!Number.isFinite(stonfiOut) && !Number.isFinite(dedustOut)) {
|
|
5045
|
+
throw new PluginSDKError("Failed to parse DEX quotes", "OPERATION_FAILED");
|
|
5046
|
+
}
|
|
5047
|
+
if (!Number.isFinite(stonfiOut)) {
|
|
5048
|
+
recommended = "dedust";
|
|
5049
|
+
} else if (!Number.isFinite(dedustOut)) {
|
|
5050
|
+
recommended = "stonfi";
|
|
5051
|
+
} else if (stonfiOut >= dedustOut) {
|
|
6330
5052
|
recommended = "stonfi";
|
|
6331
5053
|
if (dedustOut > 0) {
|
|
6332
5054
|
savings = `${((stonfiOut - dedustOut) / dedustOut * 100).toFixed(2)}%`;
|
|
@@ -6346,7 +5068,7 @@ function createDexSDK(log12) {
|
|
|
6346
5068
|
params.toAsset,
|
|
6347
5069
|
params.amount,
|
|
6348
5070
|
params.slippage ?? 0.01,
|
|
6349
|
-
|
|
5071
|
+
log10
|
|
6350
5072
|
);
|
|
6351
5073
|
},
|
|
6352
5074
|
async quoteDeDust(params) {
|
|
@@ -6355,25 +5077,25 @@ function createDexSDK(log12) {
|
|
|
6355
5077
|
params.toAsset,
|
|
6356
5078
|
params.amount,
|
|
6357
5079
|
params.slippage ?? 0.01,
|
|
6358
|
-
|
|
5080
|
+
log10
|
|
6359
5081
|
);
|
|
6360
5082
|
},
|
|
6361
5083
|
async swap(params) {
|
|
6362
5084
|
validateDexParams(params.amount, params.slippage);
|
|
6363
5085
|
if (params.dex === "stonfi") {
|
|
6364
|
-
return executeSTONfiSwap(params,
|
|
5086
|
+
return executeSTONfiSwap(params, log10);
|
|
6365
5087
|
}
|
|
6366
5088
|
if (params.dex === "dedust") {
|
|
6367
|
-
return executeDedustSwap(params,
|
|
5089
|
+
return executeDedustSwap(params, log10);
|
|
6368
5090
|
}
|
|
6369
5091
|
const quoteResult = await this.quote(params);
|
|
6370
|
-
return quoteResult.recommended === "stonfi" ? executeSTONfiSwap(params,
|
|
5092
|
+
return quoteResult.recommended === "stonfi" ? executeSTONfiSwap(params, log10) : executeDedustSwap(params, log10);
|
|
6371
5093
|
},
|
|
6372
5094
|
async swapSTONfi(params) {
|
|
6373
|
-
return executeSTONfiSwap(params,
|
|
5095
|
+
return executeSTONfiSwap(params, log10);
|
|
6374
5096
|
},
|
|
6375
5097
|
async swapDeDust(params) {
|
|
6376
|
-
return executeDedustSwap(params,
|
|
5098
|
+
return executeDedustSwap(params, log10);
|
|
6377
5099
|
}
|
|
6378
5100
|
};
|
|
6379
5101
|
}
|
|
@@ -6410,7 +5132,7 @@ function normalizeDomain(domain) {
|
|
|
6410
5132
|
if (!d.endsWith(".ton")) d += ".ton";
|
|
6411
5133
|
return d;
|
|
6412
5134
|
}
|
|
6413
|
-
function createDnsSDK(
|
|
5135
|
+
function createDnsSDK(log10) {
|
|
6414
5136
|
return {
|
|
6415
5137
|
async check(domain) {
|
|
6416
5138
|
const normalized = normalizeDomain(domain);
|
|
@@ -6433,7 +5155,7 @@ function createDnsSDK(log12) {
|
|
|
6433
5155
|
};
|
|
6434
5156
|
} catch (err) {
|
|
6435
5157
|
if (err instanceof PluginSDKError) throw err;
|
|
6436
|
-
|
|
5158
|
+
log10.debug("dns.check() failed:", err);
|
|
6437
5159
|
throw new PluginSDKError(
|
|
6438
5160
|
`Failed to check domain: ${err instanceof Error ? err.message : String(err)}`,
|
|
6439
5161
|
"OPERATION_FAILED"
|
|
@@ -6446,7 +5168,7 @@ function createDnsSDK(log12) {
|
|
|
6446
5168
|
const response = await tonapiFetch(`/dns/${encodeURIComponent(normalized)}`);
|
|
6447
5169
|
if (response.status === 404) return null;
|
|
6448
5170
|
if (!response.ok) {
|
|
6449
|
-
|
|
5171
|
+
log10.debug(`dns.resolve() TonAPI error: ${response.status}`);
|
|
6450
5172
|
return null;
|
|
6451
5173
|
}
|
|
6452
5174
|
const data = await response.json();
|
|
@@ -6458,7 +5180,7 @@ function createDnsSDK(log12) {
|
|
|
6458
5180
|
expirationDate: data.expiring_at || void 0
|
|
6459
5181
|
};
|
|
6460
5182
|
} catch (err) {
|
|
6461
|
-
|
|
5183
|
+
log10.debug("dns.resolve() failed:", err);
|
|
6462
5184
|
return null;
|
|
6463
5185
|
}
|
|
6464
5186
|
},
|
|
@@ -6468,7 +5190,7 @@ function createDnsSDK(log12) {
|
|
|
6468
5190
|
`/dns/auctions?tld=ton&limit=${Math.min(limit ?? 20, 100)}`
|
|
6469
5191
|
);
|
|
6470
5192
|
if (!response.ok) {
|
|
6471
|
-
|
|
5193
|
+
log10.debug(`dns.getAuctions() TonAPI error: ${response.status}`);
|
|
6472
5194
|
return [];
|
|
6473
5195
|
}
|
|
6474
5196
|
const data = await response.json();
|
|
@@ -6481,7 +5203,7 @@ function createDnsSDK(log12) {
|
|
|
6481
5203
|
bids: a.bids || 0
|
|
6482
5204
|
}));
|
|
6483
5205
|
} catch (err) {
|
|
6484
|
-
|
|
5206
|
+
log10.debug("dns.getAuctions() failed:", err);
|
|
6485
5207
|
return [];
|
|
6486
5208
|
}
|
|
6487
5209
|
},
|
|
@@ -6795,25 +5517,25 @@ function findJettonBalance(balances, jettonAddress) {
|
|
|
6795
5517
|
}
|
|
6796
5518
|
});
|
|
6797
5519
|
}
|
|
6798
|
-
function cleanupOldTransactions(db, retentionDays,
|
|
5520
|
+
function cleanupOldTransactions(db, retentionDays, log10) {
|
|
6799
5521
|
if (Math.random() > CLEANUP_PROBABILITY) return;
|
|
6800
5522
|
try {
|
|
6801
5523
|
const cutoff = Math.floor(Date.now() / 1e3) - retentionDays * 24 * 60 * 60;
|
|
6802
5524
|
const result = db.prepare("DELETE FROM used_transactions WHERE used_at < ?").run(cutoff);
|
|
6803
5525
|
if (result.changes > 0) {
|
|
6804
|
-
|
|
5526
|
+
log10.debug(`Cleaned up ${result.changes} old transaction records (>${retentionDays}d)`);
|
|
6805
5527
|
}
|
|
6806
5528
|
} catch (err) {
|
|
6807
|
-
|
|
5529
|
+
log10.error("Transaction cleanup failed:", err);
|
|
6808
5530
|
}
|
|
6809
5531
|
}
|
|
6810
|
-
function createTonSDK(
|
|
5532
|
+
function createTonSDK(log10, db) {
|
|
6811
5533
|
return {
|
|
6812
5534
|
getAddress() {
|
|
6813
5535
|
try {
|
|
6814
5536
|
return getWalletAddress();
|
|
6815
5537
|
} catch (err) {
|
|
6816
|
-
|
|
5538
|
+
log10.error("ton.getAddress() failed:", err);
|
|
6817
5539
|
return null;
|
|
6818
5540
|
}
|
|
6819
5541
|
},
|
|
@@ -6823,7 +5545,7 @@ function createTonSDK(log12, db) {
|
|
|
6823
5545
|
if (!addr) return null;
|
|
6824
5546
|
return await getWalletBalance(addr);
|
|
6825
5547
|
} catch (err) {
|
|
6826
|
-
|
|
5548
|
+
log10.error("ton.getBalance() failed:", err);
|
|
6827
5549
|
return null;
|
|
6828
5550
|
}
|
|
6829
5551
|
},
|
|
@@ -6831,7 +5553,7 @@ function createTonSDK(log12, db) {
|
|
|
6831
5553
|
try {
|
|
6832
5554
|
return await getTonPrice();
|
|
6833
5555
|
} catch (err) {
|
|
6834
|
-
|
|
5556
|
+
log10.error("ton.getPrice() failed:", err);
|
|
6835
5557
|
return null;
|
|
6836
5558
|
}
|
|
6837
5559
|
},
|
|
@@ -6882,7 +5604,7 @@ function createTonSDK(log12, db) {
|
|
|
6882
5604
|
);
|
|
6883
5605
|
return formatTransactions(transactions);
|
|
6884
5606
|
} catch (err) {
|
|
6885
|
-
|
|
5607
|
+
log10.error("ton.getTransactions() failed:", err);
|
|
6886
5608
|
return [];
|
|
6887
5609
|
}
|
|
6888
5610
|
},
|
|
@@ -6905,7 +5627,7 @@ function createTonSDK(log12, db) {
|
|
|
6905
5627
|
throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
|
|
6906
5628
|
}
|
|
6907
5629
|
const maxAgeMinutes = params.maxAgeMinutes ?? DEFAULT_MAX_AGE_MINUTES;
|
|
6908
|
-
cleanupOldTransactions(db, DEFAULT_TX_RETENTION_DAYS,
|
|
5630
|
+
cleanupOldTransactions(db, DEFAULT_TX_RETENTION_DAYS, log10);
|
|
6909
5631
|
try {
|
|
6910
5632
|
const txs = await this.getTransactions(address4, 20);
|
|
6911
5633
|
for (const tx of txs) {
|
|
@@ -6939,7 +5661,7 @@ function createTonSDK(log12, db) {
|
|
|
6939
5661
|
};
|
|
6940
5662
|
} catch (err) {
|
|
6941
5663
|
if (err instanceof PluginSDKError) throw err;
|
|
6942
|
-
|
|
5664
|
+
log10.error("ton.verifyPayment() failed:", err);
|
|
6943
5665
|
return {
|
|
6944
5666
|
verified: false,
|
|
6945
5667
|
error: `Verification failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -6953,7 +5675,7 @@ function createTonSDK(log12, db) {
|
|
|
6953
5675
|
if (!addr) return [];
|
|
6954
5676
|
const response = await tonapiFetch(`/accounts/${encodeURIComponent(addr)}/jettons`);
|
|
6955
5677
|
if (!response.ok) {
|
|
6956
|
-
|
|
5678
|
+
log10.error(`ton.getJettonBalances() TonAPI error: ${response.status}`);
|
|
6957
5679
|
return [];
|
|
6958
5680
|
}
|
|
6959
5681
|
const data = await response.json();
|
|
@@ -6977,7 +5699,7 @@ function createTonSDK(log12, db) {
|
|
|
6977
5699
|
}
|
|
6978
5700
|
return balances;
|
|
6979
5701
|
} catch (err) {
|
|
6980
|
-
|
|
5702
|
+
log10.error("ton.getJettonBalances() failed:", err);
|
|
6981
5703
|
return [];
|
|
6982
5704
|
}
|
|
6983
5705
|
},
|
|
@@ -6986,7 +5708,7 @@ function createTonSDK(log12, db) {
|
|
|
6986
5708
|
const response = await tonapiFetch(`/jettons/${encodeURIComponent(jettonAddress)}`);
|
|
6987
5709
|
if (response.status === 404) return null;
|
|
6988
5710
|
if (!response.ok) {
|
|
6989
|
-
|
|
5711
|
+
log10.error(`ton.getJettonInfo() TonAPI error: ${response.status}`);
|
|
6990
5712
|
return null;
|
|
6991
5713
|
}
|
|
6992
5714
|
const data = await response.json();
|
|
@@ -7004,7 +5726,7 @@ function createTonSDK(log12, db) {
|
|
|
7004
5726
|
image: data.preview || metadata.image || void 0
|
|
7005
5727
|
};
|
|
7006
5728
|
} catch (err) {
|
|
7007
|
-
|
|
5729
|
+
log10.error("ton.getJettonInfo() failed:", err);
|
|
7008
5730
|
return null;
|
|
7009
5731
|
}
|
|
7010
5732
|
},
|
|
@@ -7097,7 +5819,7 @@ function createTonSDK(log12, db) {
|
|
|
7097
5819
|
if (status === 429 || status && status >= 500) {
|
|
7098
5820
|
invalidateTonClientCache();
|
|
7099
5821
|
if (attempt < MAX_SEND_ATTEMPTS) {
|
|
7100
|
-
|
|
5822
|
+
log10.warn(
|
|
7101
5823
|
`sendJetton attempt ${attempt} failed (${status}): ${JSON.stringify(respData ?? err.message)}, retrying...`
|
|
7102
5824
|
);
|
|
7103
5825
|
await new Promise((r3) => setTimeout(r3, 1e3 * attempt));
|
|
@@ -7127,14 +5849,14 @@ function createTonSDK(log12, db) {
|
|
|
7127
5849
|
try {
|
|
7128
5850
|
const response = await tonapiFetch(`/accounts/${encodeURIComponent(ownerAddress)}/jettons`);
|
|
7129
5851
|
if (!response.ok) {
|
|
7130
|
-
|
|
5852
|
+
log10.error(`ton.getJettonWalletAddress() TonAPI error: ${response.status}`);
|
|
7131
5853
|
return null;
|
|
7132
5854
|
}
|
|
7133
5855
|
const data = await response.json();
|
|
7134
5856
|
const match = findJettonBalance(data.balances ?? [], jettonAddress);
|
|
7135
5857
|
return match ? match.wallet_address.address : null;
|
|
7136
5858
|
} catch (err) {
|
|
7137
|
-
|
|
5859
|
+
log10.error("ton.getJettonWalletAddress() failed:", err);
|
|
7138
5860
|
return null;
|
|
7139
5861
|
}
|
|
7140
5862
|
},
|
|
@@ -7157,7 +5879,7 @@ function createTonSDK(log12, db) {
|
|
|
7157
5879
|
if (!keyPair) {
|
|
7158
5880
|
throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
|
|
7159
5881
|
}
|
|
7160
|
-
const
|
|
5882
|
+
const txResult = await withTxLock(async () => {
|
|
7161
5883
|
const wallet = WalletContractV5R14.create({
|
|
7162
5884
|
workchain: 0,
|
|
7163
5885
|
publicKey: keyPair.publicKey
|
|
@@ -7165,8 +5887,10 @@ function createTonSDK(log12, db) {
|
|
|
7165
5887
|
const client = await getCachedTonClient();
|
|
7166
5888
|
const contract = client.open(wallet);
|
|
7167
5889
|
const seqno = await contract.getSeqno();
|
|
5890
|
+
const validUntil = Math.floor(Date.now() / 1e3) + 120;
|
|
7168
5891
|
const transferCell = wallet.createTransfer({
|
|
7169
5892
|
seqno,
|
|
5893
|
+
timeout: validUntil,
|
|
7170
5894
|
secretKey: keyPair.secretKey,
|
|
7171
5895
|
sendMode: SendMode4.PAY_GAS_SEPARATELY,
|
|
7172
5896
|
messages: [
|
|
@@ -7189,10 +5913,18 @@ function createTonSDK(log12, db) {
|
|
|
7189
5913
|
body: transferCell
|
|
7190
5914
|
})
|
|
7191
5915
|
).endCell();
|
|
7192
|
-
|
|
5916
|
+
const boc = extMsg.toBoc().toString("base64");
|
|
5917
|
+
return { boc, seqno, validUntil, walletAddress: wallet.address.toRawString() };
|
|
7193
5918
|
});
|
|
7194
5919
|
return {
|
|
7195
|
-
|
|
5920
|
+
// v2 fields
|
|
5921
|
+
signedBoc: txResult.boc,
|
|
5922
|
+
walletPublicKey: walletData.publicKey,
|
|
5923
|
+
walletAddress: txResult.walletAddress,
|
|
5924
|
+
seqno: txResult.seqno,
|
|
5925
|
+
validUntil: txResult.validUntil,
|
|
5926
|
+
// backward compat (deprecated)
|
|
5927
|
+
boc: txResult.boc,
|
|
7196
5928
|
publicKey: walletData.publicKey,
|
|
7197
5929
|
walletVersion: "v5r1"
|
|
7198
5930
|
};
|
|
@@ -7259,7 +5991,7 @@ function createTonSDK(log12, db) {
|
|
|
7259
5991
|
if (!keyPair) {
|
|
7260
5992
|
throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
|
|
7261
5993
|
}
|
|
7262
|
-
const
|
|
5994
|
+
const txResult = await withTxLock(async () => {
|
|
7263
5995
|
const wallet = WalletContractV5R14.create({
|
|
7264
5996
|
workchain: 0,
|
|
7265
5997
|
publicKey: keyPair.publicKey
|
|
@@ -7267,8 +5999,10 @@ function createTonSDK(log12, db) {
|
|
|
7267
5999
|
const client = await getCachedTonClient();
|
|
7268
6000
|
const walletContract = client.open(wallet);
|
|
7269
6001
|
const seqno = await walletContract.getSeqno();
|
|
6002
|
+
const validUntil = Math.floor(Date.now() / 1e3) + 120;
|
|
7270
6003
|
const transferCell = wallet.createTransfer({
|
|
7271
6004
|
seqno,
|
|
6005
|
+
timeout: validUntil,
|
|
7272
6006
|
secretKey: keyPair.secretKey,
|
|
7273
6007
|
sendMode: SendMode4.PAY_GAS_SEPARATELY,
|
|
7274
6008
|
messages: [
|
|
@@ -7291,10 +6025,18 @@ function createTonSDK(log12, db) {
|
|
|
7291
6025
|
body: transferCell
|
|
7292
6026
|
})
|
|
7293
6027
|
).endCell();
|
|
7294
|
-
|
|
6028
|
+
const boc = extMsg.toBoc().toString("base64");
|
|
6029
|
+
return { boc, seqno, validUntil, walletAddress: wallet.address.toRawString() };
|
|
7295
6030
|
});
|
|
7296
6031
|
return {
|
|
7297
|
-
|
|
6032
|
+
// v2 fields
|
|
6033
|
+
signedBoc: txResult.boc,
|
|
6034
|
+
walletPublicKey: walletData.publicKey,
|
|
6035
|
+
walletAddress: txResult.walletAddress,
|
|
6036
|
+
seqno: txResult.seqno,
|
|
6037
|
+
validUntil: txResult.validUntil,
|
|
6038
|
+
// backward compat (deprecated)
|
|
6039
|
+
boc: txResult.boc,
|
|
7298
6040
|
publicKey: walletData.publicKey,
|
|
7299
6041
|
walletVersion: "v5r1"
|
|
7300
6042
|
};
|
|
@@ -7311,7 +6053,7 @@ function createTonSDK(log12, db) {
|
|
|
7311
6053
|
const wallet = loadWallet();
|
|
7312
6054
|
return wallet?.publicKey ?? null;
|
|
7313
6055
|
} catch (err) {
|
|
7314
|
-
|
|
6056
|
+
log10.error("ton.getPublicKey() failed:", err);
|
|
7315
6057
|
return null;
|
|
7316
6058
|
}
|
|
7317
6059
|
},
|
|
@@ -7327,14 +6069,14 @@ function createTonSDK(log12, db) {
|
|
|
7327
6069
|
`/accounts/${encodeURIComponent(addr)}/nfts?limit=100&indirect_ownership=true`
|
|
7328
6070
|
);
|
|
7329
6071
|
if (!response.ok) {
|
|
7330
|
-
|
|
6072
|
+
log10.error(`ton.getNftItems() TonAPI error: ${response.status}`);
|
|
7331
6073
|
return [];
|
|
7332
6074
|
}
|
|
7333
6075
|
const data = await response.json();
|
|
7334
6076
|
if (!Array.isArray(data.nft_items)) return [];
|
|
7335
6077
|
return data.nft_items.filter((item) => item.trust !== "blacklist").map((item) => mapNftItem(item));
|
|
7336
6078
|
} catch (err) {
|
|
7337
|
-
|
|
6079
|
+
log10.error("ton.getNftItems() failed:", err);
|
|
7338
6080
|
return [];
|
|
7339
6081
|
}
|
|
7340
6082
|
},
|
|
@@ -7343,13 +6085,13 @@ function createTonSDK(log12, db) {
|
|
|
7343
6085
|
const response = await tonapiFetch(`/nfts/${encodeURIComponent(nftAddress)}`);
|
|
7344
6086
|
if (response.status === 404) return null;
|
|
7345
6087
|
if (!response.ok) {
|
|
7346
|
-
|
|
6088
|
+
log10.error(`ton.getNftInfo() TonAPI error: ${response.status}`);
|
|
7347
6089
|
return null;
|
|
7348
6090
|
}
|
|
7349
6091
|
const item = await response.json();
|
|
7350
6092
|
return mapNftItem(item);
|
|
7351
6093
|
} catch (err) {
|
|
7352
|
-
|
|
6094
|
+
log10.error("ton.getNftInfo() failed:", err);
|
|
7353
6095
|
return null;
|
|
7354
6096
|
}
|
|
7355
6097
|
},
|
|
@@ -7382,7 +6124,7 @@ function createTonSDK(log12, db) {
|
|
|
7382
6124
|
`/rates?tokens=${encodeURIComponent(jettonAddress)}¤cies=usd,ton`
|
|
7383
6125
|
);
|
|
7384
6126
|
if (!response.ok) {
|
|
7385
|
-
|
|
6127
|
+
log10.debug(`ton.getJettonPrice() TonAPI error: ${response.status}`);
|
|
7386
6128
|
return null;
|
|
7387
6129
|
}
|
|
7388
6130
|
const data = await response.json();
|
|
@@ -7396,7 +6138,7 @@ function createTonSDK(log12, db) {
|
|
|
7396
6138
|
change30d: rateData.diff_30d?.USD ?? null
|
|
7397
6139
|
};
|
|
7398
6140
|
} catch (err) {
|
|
7399
|
-
|
|
6141
|
+
log10.debug("ton.getJettonPrice() failed:", err);
|
|
7400
6142
|
return null;
|
|
7401
6143
|
}
|
|
7402
6144
|
},
|
|
@@ -7410,7 +6152,7 @@ function createTonSDK(log12, db) {
|
|
|
7410
6152
|
tonapiFetch(`/jettons/${encodeURIComponent(jettonAddress)}`)
|
|
7411
6153
|
]);
|
|
7412
6154
|
if (!holdersResponse.ok) {
|
|
7413
|
-
|
|
6155
|
+
log10.debug(`ton.getJettonHolders() TonAPI error: ${holdersResponse.status}`);
|
|
7414
6156
|
return [];
|
|
7415
6157
|
}
|
|
7416
6158
|
const data = await holdersResponse.json();
|
|
@@ -7430,7 +6172,7 @@ function createTonSDK(log12, db) {
|
|
|
7430
6172
|
};
|
|
7431
6173
|
});
|
|
7432
6174
|
} catch (err) {
|
|
7433
|
-
|
|
6175
|
+
log10.debug("ton.getJettonHolders() failed:", err);
|
|
7434
6176
|
return [];
|
|
7435
6177
|
}
|
|
7436
6178
|
},
|
|
@@ -7475,15 +6217,13 @@ function createTonSDK(log12, db) {
|
|
|
7475
6217
|
const geckoData = await geckoResponse.json();
|
|
7476
6218
|
const attrs = geckoData.data?.attributes;
|
|
7477
6219
|
if (attrs) {
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
if (attrs.market_cap_usd)
|
|
7485
|
-
marketCap = `$${parseFloat(attrs.market_cap_usd).toLocaleString(void 0, { maximumFractionDigits: 0 })}`;
|
|
7486
|
-
}
|
|
6220
|
+
const fmtUsd = (raw) => {
|
|
6221
|
+
const val = parseFloat(raw);
|
|
6222
|
+
return Number.isFinite(val) ? `$${val.toLocaleString(void 0, { maximumFractionDigits: 0 })}` : "N/A";
|
|
6223
|
+
};
|
|
6224
|
+
if (attrs.volume_usd?.h24) volume24h = fmtUsd(attrs.volume_usd.h24);
|
|
6225
|
+
if (attrs.fdv_usd) fdv = fmtUsd(attrs.fdv_usd);
|
|
6226
|
+
if (attrs.market_cap_usd) marketCap = fmtUsd(attrs.market_cap_usd);
|
|
7487
6227
|
}
|
|
7488
6228
|
}
|
|
7489
6229
|
return {
|
|
@@ -7502,13 +6242,251 @@ function createTonSDK(log12, db) {
|
|
|
7502
6242
|
holders: holdersCount
|
|
7503
6243
|
};
|
|
7504
6244
|
} catch (err) {
|
|
7505
|
-
|
|
6245
|
+
log10.debug("ton.getJettonHistory() failed:", err);
|
|
7506
6246
|
return null;
|
|
7507
6247
|
}
|
|
7508
6248
|
},
|
|
6249
|
+
// ─── Low-level Transfer ─────────────────────────────────────────
|
|
6250
|
+
async getSeqno() {
|
|
6251
|
+
const walletData = loadWallet();
|
|
6252
|
+
if (!walletData) {
|
|
6253
|
+
throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
|
|
6254
|
+
}
|
|
6255
|
+
try {
|
|
6256
|
+
const keyPair = await getKeyPair();
|
|
6257
|
+
if (!keyPair) {
|
|
6258
|
+
throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
|
|
6259
|
+
}
|
|
6260
|
+
const wallet = WalletContractV5R14.create({
|
|
6261
|
+
workchain: 0,
|
|
6262
|
+
publicKey: keyPair.publicKey
|
|
6263
|
+
});
|
|
6264
|
+
const client = await getCachedTonClient();
|
|
6265
|
+
const contract = client.open(wallet);
|
|
6266
|
+
return await contract.getSeqno();
|
|
6267
|
+
} catch (err) {
|
|
6268
|
+
const httpErr = isHttpError(err) ? err : void 0;
|
|
6269
|
+
const status = httpErr?.status || httpErr?.response?.status;
|
|
6270
|
+
if (status === 429 || status !== void 0 && status >= 500) {
|
|
6271
|
+
invalidateTonClientCache();
|
|
6272
|
+
}
|
|
6273
|
+
if (err instanceof PluginSDKError) throw err;
|
|
6274
|
+
throw new PluginSDKError(
|
|
6275
|
+
`Failed to get seqno: ${err instanceof Error ? err.message : String(err)}`,
|
|
6276
|
+
"OPERATION_FAILED"
|
|
6277
|
+
);
|
|
6278
|
+
}
|
|
6279
|
+
},
|
|
6280
|
+
async runGetMethod(address4, method, stack) {
|
|
6281
|
+
try {
|
|
6282
|
+
TonAddress.parse(address4);
|
|
6283
|
+
} catch {
|
|
6284
|
+
throw new PluginSDKError("Invalid TON address format", "INVALID_ADDRESS");
|
|
6285
|
+
}
|
|
6286
|
+
try {
|
|
6287
|
+
const client = await getCachedTonClient();
|
|
6288
|
+
const result = await client.runMethodWithError(
|
|
6289
|
+
TonAddress.parse(address4),
|
|
6290
|
+
method,
|
|
6291
|
+
stack ?? []
|
|
6292
|
+
);
|
|
6293
|
+
const items = [];
|
|
6294
|
+
while (result.stack.remaining > 0) {
|
|
6295
|
+
items.push(result.stack.pop());
|
|
6296
|
+
}
|
|
6297
|
+
return { exitCode: result.exit_code, stack: items };
|
|
6298
|
+
} catch (err) {
|
|
6299
|
+
const httpErr = isHttpError(err) ? err : void 0;
|
|
6300
|
+
const status = httpErr?.status || httpErr?.response?.status;
|
|
6301
|
+
if (status === 429 || status !== void 0 && status >= 500) {
|
|
6302
|
+
invalidateTonClientCache();
|
|
6303
|
+
}
|
|
6304
|
+
if (err instanceof PluginSDKError) throw err;
|
|
6305
|
+
throw new PluginSDKError(
|
|
6306
|
+
`Failed to run get method: ${err instanceof Error ? err.message : String(err)}`,
|
|
6307
|
+
"OPERATION_FAILED"
|
|
6308
|
+
);
|
|
6309
|
+
}
|
|
6310
|
+
},
|
|
6311
|
+
async send(to, value, opts) {
|
|
6312
|
+
const walletData = loadWallet();
|
|
6313
|
+
if (!walletData) {
|
|
6314
|
+
throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
|
|
6315
|
+
}
|
|
6316
|
+
try {
|
|
6317
|
+
TonAddress.parse(to);
|
|
6318
|
+
} catch {
|
|
6319
|
+
throw new PluginSDKError("Invalid TON address format", "INVALID_ADDRESS");
|
|
6320
|
+
}
|
|
6321
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
6322
|
+
throw new PluginSDKError("Amount must be a non-negative number", "OPERATION_FAILED");
|
|
6323
|
+
}
|
|
6324
|
+
if (opts?.sendMode !== void 0 && (opts.sendMode < 0 || opts.sendMode > 3)) {
|
|
6325
|
+
throw new PluginSDKError("Unsafe sendMode", "OPERATION_FAILED");
|
|
6326
|
+
}
|
|
6327
|
+
const keyPair = await getKeyPair();
|
|
6328
|
+
if (!keyPair) {
|
|
6329
|
+
throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
|
|
6330
|
+
}
|
|
6331
|
+
try {
|
|
6332
|
+
const seqno = await withTxLock(async () => {
|
|
6333
|
+
const wallet = WalletContractV5R14.create({
|
|
6334
|
+
workchain: 0,
|
|
6335
|
+
publicKey: keyPair.publicKey
|
|
6336
|
+
});
|
|
6337
|
+
const client = await getCachedTonClient();
|
|
6338
|
+
const contract = client.open(wallet);
|
|
6339
|
+
const seq = await contract.getSeqno();
|
|
6340
|
+
await contract.sendTransfer({
|
|
6341
|
+
seqno: seq,
|
|
6342
|
+
secretKey: keyPair.secretKey,
|
|
6343
|
+
sendMode: opts?.sendMode ?? SendMode4.PAY_GAS_SEPARATELY,
|
|
6344
|
+
messages: [
|
|
6345
|
+
internal4({
|
|
6346
|
+
to: TonAddress.parse(to),
|
|
6347
|
+
value: tonToNano(value.toString()),
|
|
6348
|
+
body: opts?.body,
|
|
6349
|
+
bounce: opts?.bounce ?? true,
|
|
6350
|
+
init: opts?.stateInit
|
|
6351
|
+
})
|
|
6352
|
+
]
|
|
6353
|
+
});
|
|
6354
|
+
return seq;
|
|
6355
|
+
});
|
|
6356
|
+
return { hash: `${seqno}_${Date.now()}_send`, seqno };
|
|
6357
|
+
} catch (err) {
|
|
6358
|
+
const httpErr = isHttpError(err) ? err : void 0;
|
|
6359
|
+
const status = httpErr?.status || httpErr?.response?.status;
|
|
6360
|
+
if (status === 429 || status !== void 0 && status >= 500) {
|
|
6361
|
+
invalidateTonClientCache();
|
|
6362
|
+
}
|
|
6363
|
+
if (err instanceof PluginSDKError) throw err;
|
|
6364
|
+
throw new PluginSDKError(
|
|
6365
|
+
`Failed to send: ${err instanceof Error ? err.message : String(err)}`,
|
|
6366
|
+
"OPERATION_FAILED"
|
|
6367
|
+
);
|
|
6368
|
+
}
|
|
6369
|
+
},
|
|
6370
|
+
async sendMessages(messages, opts) {
|
|
6371
|
+
const walletData = loadWallet();
|
|
6372
|
+
if (!walletData) {
|
|
6373
|
+
throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
|
|
6374
|
+
}
|
|
6375
|
+
if (messages.length < 1) {
|
|
6376
|
+
throw new PluginSDKError("At least one message required", "OPERATION_FAILED");
|
|
6377
|
+
}
|
|
6378
|
+
if (messages.length > 255) {
|
|
6379
|
+
throw new PluginSDKError("Maximum 255 messages per transfer", "OPERATION_FAILED");
|
|
6380
|
+
}
|
|
6381
|
+
if (opts?.sendMode !== void 0 && (opts.sendMode < 0 || opts.sendMode > 3)) {
|
|
6382
|
+
throw new PluginSDKError("Unsafe sendMode", "OPERATION_FAILED");
|
|
6383
|
+
}
|
|
6384
|
+
for (const m of messages) {
|
|
6385
|
+
try {
|
|
6386
|
+
TonAddress.parse(m.to);
|
|
6387
|
+
} catch {
|
|
6388
|
+
throw new PluginSDKError(`Invalid TON address format: ${m.to}`, "INVALID_ADDRESS");
|
|
6389
|
+
}
|
|
6390
|
+
if (!Number.isFinite(m.value) || m.value < 0) {
|
|
6391
|
+
throw new PluginSDKError("Amount must be a non-negative number", "OPERATION_FAILED");
|
|
6392
|
+
}
|
|
6393
|
+
}
|
|
6394
|
+
const keyPair = await getKeyPair();
|
|
6395
|
+
if (!keyPair) {
|
|
6396
|
+
throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
|
|
6397
|
+
}
|
|
6398
|
+
try {
|
|
6399
|
+
const seqno = await withTxLock(async () => {
|
|
6400
|
+
const wallet = WalletContractV5R14.create({
|
|
6401
|
+
workchain: 0,
|
|
6402
|
+
publicKey: keyPair.publicKey
|
|
6403
|
+
});
|
|
6404
|
+
const client = await getCachedTonClient();
|
|
6405
|
+
const contract = client.open(wallet);
|
|
6406
|
+
const seq = await contract.getSeqno();
|
|
6407
|
+
await contract.sendTransfer({
|
|
6408
|
+
seqno: seq,
|
|
6409
|
+
secretKey: keyPair.secretKey,
|
|
6410
|
+
sendMode: opts?.sendMode ?? SendMode4.PAY_GAS_SEPARATELY,
|
|
6411
|
+
messages: messages.map(
|
|
6412
|
+
(m) => internal4({
|
|
6413
|
+
to: TonAddress.parse(m.to),
|
|
6414
|
+
value: tonToNano(m.value.toString()),
|
|
6415
|
+
body: m.body,
|
|
6416
|
+
bounce: m.bounce ?? true,
|
|
6417
|
+
init: m.stateInit
|
|
6418
|
+
})
|
|
6419
|
+
)
|
|
6420
|
+
});
|
|
6421
|
+
return seq;
|
|
6422
|
+
});
|
|
6423
|
+
return { hash: `${seqno}_${Date.now()}_sendMessages`, seqno };
|
|
6424
|
+
} catch (err) {
|
|
6425
|
+
const httpErr = isHttpError(err) ? err : void 0;
|
|
6426
|
+
const status = httpErr?.status || httpErr?.response?.status;
|
|
6427
|
+
if (status === 429 || status !== void 0 && status >= 500) {
|
|
6428
|
+
invalidateTonClientCache();
|
|
6429
|
+
}
|
|
6430
|
+
if (err instanceof PluginSDKError) throw err;
|
|
6431
|
+
throw new PluginSDKError(
|
|
6432
|
+
`Failed to send messages: ${err instanceof Error ? err.message : String(err)}`,
|
|
6433
|
+
"OPERATION_FAILED"
|
|
6434
|
+
);
|
|
6435
|
+
}
|
|
6436
|
+
},
|
|
6437
|
+
async createSender() {
|
|
6438
|
+
const walletData = loadWallet();
|
|
6439
|
+
if (!walletData) {
|
|
6440
|
+
throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
|
|
6441
|
+
}
|
|
6442
|
+
const keyPair = await getKeyPair();
|
|
6443
|
+
if (!keyPair) {
|
|
6444
|
+
throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
|
|
6445
|
+
}
|
|
6446
|
+
const wallet = WalletContractV5R14.create({
|
|
6447
|
+
workchain: 0,
|
|
6448
|
+
publicKey: keyPair.publicKey
|
|
6449
|
+
});
|
|
6450
|
+
return {
|
|
6451
|
+
address: wallet.address,
|
|
6452
|
+
send: async (args) => {
|
|
6453
|
+
if (args.sendMode !== void 0 && (args.sendMode < 0 || args.sendMode > 3)) {
|
|
6454
|
+
throw new PluginSDKError("Unsafe sendMode", "OPERATION_FAILED");
|
|
6455
|
+
}
|
|
6456
|
+
try {
|
|
6457
|
+
await withTxLock(async () => {
|
|
6458
|
+
const client = await getCachedTonClient();
|
|
6459
|
+
const contract = client.open(wallet);
|
|
6460
|
+
const seqno = await contract.getSeqno();
|
|
6461
|
+
await contract.sendTransfer({
|
|
6462
|
+
seqno,
|
|
6463
|
+
secretKey: keyPair.secretKey,
|
|
6464
|
+
sendMode: args.sendMode ?? SendMode4.PAY_GAS_SEPARATELY,
|
|
6465
|
+
messages: [
|
|
6466
|
+
internal4({
|
|
6467
|
+
to: args.to,
|
|
6468
|
+
value: args.value,
|
|
6469
|
+
body: args.body,
|
|
6470
|
+
bounce: args.bounce ?? true,
|
|
6471
|
+
init: args.init ?? void 0
|
|
6472
|
+
})
|
|
6473
|
+
]
|
|
6474
|
+
});
|
|
6475
|
+
});
|
|
6476
|
+
} catch (err) {
|
|
6477
|
+
const httpErr = isHttpError(err) ? err : void 0;
|
|
6478
|
+
const status = httpErr?.status || httpErr?.response?.status;
|
|
6479
|
+
if (status === 429 || status !== void 0 && status >= 500) {
|
|
6480
|
+
invalidateTonClientCache();
|
|
6481
|
+
}
|
|
6482
|
+
throw err;
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
};
|
|
6486
|
+
},
|
|
7509
6487
|
// ─── Sub-namespaces ───────────────────────────────────────────
|
|
7510
|
-
dex: Object.freeze(createDexSDK(
|
|
7511
|
-
dns: Object.freeze(createDnsSDK(
|
|
6488
|
+
dex: Object.freeze(createDexSDK(log10)),
|
|
6489
|
+
dns: Object.freeze(createDnsSDK(log10))
|
|
7512
6490
|
};
|
|
7513
6491
|
}
|
|
7514
6492
|
function mapNftItem(item) {
|
|
@@ -7561,7 +6539,7 @@ async function getApi() {
|
|
|
7561
6539
|
}
|
|
7562
6540
|
|
|
7563
6541
|
// src/sdk/telegram-messages.ts
|
|
7564
|
-
function createTelegramMessagesSDK(bridge,
|
|
6542
|
+
function createTelegramMessagesSDK(bridge, log10) {
|
|
7565
6543
|
function requireBridge2() {
|
|
7566
6544
|
requireBridge(bridge);
|
|
7567
6545
|
}
|
|
@@ -7668,7 +6646,7 @@ function createTelegramMessagesSDK(bridge, log12) {
|
|
|
7668
6646
|
return (resultData.messages ?? []).map(toSimpleMessage);
|
|
7669
6647
|
} catch (err) {
|
|
7670
6648
|
if (err instanceof PluginSDKError) throw err;
|
|
7671
|
-
|
|
6649
|
+
log10.error("telegram.searchMessages() failed:", err);
|
|
7672
6650
|
return [];
|
|
7673
6651
|
}
|
|
7674
6652
|
},
|
|
@@ -7911,7 +6889,7 @@ function createTelegramMessagesSDK(bridge, log12) {
|
|
|
7911
6889
|
return messages;
|
|
7912
6890
|
} catch (err) {
|
|
7913
6891
|
if (err instanceof PluginSDKError) throw err;
|
|
7914
|
-
|
|
6892
|
+
log10.error("telegram.getScheduledMessages() failed:", err);
|
|
7915
6893
|
return [];
|
|
7916
6894
|
}
|
|
7917
6895
|
},
|
|
@@ -7972,7 +6950,7 @@ function createTelegramMessagesSDK(bridge, log12) {
|
|
|
7972
6950
|
}
|
|
7973
6951
|
|
|
7974
6952
|
// src/sdk/telegram-social.ts
|
|
7975
|
-
function createTelegramSocialSDK(bridge,
|
|
6953
|
+
function createTelegramSocialSDK(bridge, log10) {
|
|
7976
6954
|
function requireBridge2() {
|
|
7977
6955
|
requireBridge(bridge);
|
|
7978
6956
|
}
|
|
@@ -8047,7 +7025,7 @@ function createTelegramSocialSDK(bridge, log12) {
|
|
|
8047
7025
|
return null;
|
|
8048
7026
|
} catch (err) {
|
|
8049
7027
|
if (err instanceof PluginSDKError) throw err;
|
|
8050
|
-
|
|
7028
|
+
log10.error("telegram.getChatInfo() failed:", err);
|
|
8051
7029
|
return null;
|
|
8052
7030
|
}
|
|
8053
7031
|
},
|
|
@@ -8161,7 +7139,7 @@ function createTelegramSocialSDK(bridge, log12) {
|
|
|
8161
7139
|
});
|
|
8162
7140
|
} catch (err) {
|
|
8163
7141
|
if (err instanceof PluginSDKError) throw err;
|
|
8164
|
-
|
|
7142
|
+
log10.error("telegram.getParticipants() failed:", err);
|
|
8165
7143
|
return [];
|
|
8166
7144
|
}
|
|
8167
7145
|
},
|
|
@@ -8523,7 +7501,7 @@ function createTelegramSocialSDK(bridge, log12) {
|
|
|
8523
7501
|
}));
|
|
8524
7502
|
} catch (err) {
|
|
8525
7503
|
if (err instanceof PluginSDKError) throw err;
|
|
8526
|
-
|
|
7504
|
+
log10.error("telegram.getDialogs() failed:", err);
|
|
8527
7505
|
return [];
|
|
8528
7506
|
}
|
|
8529
7507
|
},
|
|
@@ -8537,7 +7515,7 @@ function createTelegramSocialSDK(bridge, log12) {
|
|
|
8537
7515
|
return messages.map(toSimpleMessage);
|
|
8538
7516
|
} catch (err) {
|
|
8539
7517
|
if (err instanceof PluginSDKError) throw err;
|
|
8540
|
-
|
|
7518
|
+
log10.error("telegram.getHistory() failed:", err);
|
|
8541
7519
|
return [];
|
|
8542
7520
|
}
|
|
8543
7521
|
},
|
|
@@ -8568,7 +7546,7 @@ function createTelegramSocialSDK(bridge, log12) {
|
|
|
8568
7546
|
}));
|
|
8569
7547
|
} catch (err) {
|
|
8570
7548
|
if (err instanceof PluginSDKError) throw err;
|
|
8571
|
-
|
|
7549
|
+
log10.error("telegram.getStarsTransactions() failed:", err);
|
|
8572
7550
|
return [];
|
|
8573
7551
|
}
|
|
8574
7552
|
},
|
|
@@ -8673,7 +7651,7 @@ function createTelegramSocialSDK(bridge, log12) {
|
|
|
8673
7651
|
};
|
|
8674
7652
|
} catch (err) {
|
|
8675
7653
|
if (err instanceof PluginSDKError) throw err;
|
|
8676
|
-
|
|
7654
|
+
log10.error("telegram.getCollectibleInfo() failed:", err);
|
|
8677
7655
|
return null;
|
|
8678
7656
|
}
|
|
8679
7657
|
},
|
|
@@ -8711,7 +7689,7 @@ function createTelegramSocialSDK(bridge, log12) {
|
|
|
8711
7689
|
} catch (err) {
|
|
8712
7690
|
if (err.errorMessage === "STARGIFT_SLUG_INVALID") return null;
|
|
8713
7691
|
if (err instanceof PluginSDKError) throw err;
|
|
8714
|
-
|
|
7692
|
+
log10.error("telegram.getUniqueGift() failed:", err);
|
|
8715
7693
|
return null;
|
|
8716
7694
|
}
|
|
8717
7695
|
},
|
|
@@ -8737,7 +7715,7 @@ function createTelegramSocialSDK(bridge, log12) {
|
|
|
8737
7715
|
} catch (err) {
|
|
8738
7716
|
if (err.errorMessage === "STARGIFT_SLUG_INVALID") return null;
|
|
8739
7717
|
if (err instanceof PluginSDKError) throw err;
|
|
8740
|
-
|
|
7718
|
+
log10.error("telegram.getUniqueGiftValue() failed:", err);
|
|
8741
7719
|
return null;
|
|
8742
7720
|
}
|
|
8743
7721
|
},
|
|
@@ -8848,7 +7826,7 @@ function createTelegramSocialSDK(bridge, log12) {
|
|
|
8848
7826
|
}
|
|
8849
7827
|
|
|
8850
7828
|
// src/sdk/telegram.ts
|
|
8851
|
-
function createTelegramSDK(bridge,
|
|
7829
|
+
function createTelegramSDK(bridge, log10) {
|
|
8852
7830
|
function requireBridge2() {
|
|
8853
7831
|
requireBridge(bridge);
|
|
8854
7832
|
}
|
|
@@ -8952,7 +7930,7 @@ function createTelegramSDK(bridge, log12) {
|
|
|
8952
7930
|
timestamp: m.timestamp
|
|
8953
7931
|
}));
|
|
8954
7932
|
} catch (err) {
|
|
8955
|
-
|
|
7933
|
+
log10.error("telegram.getMessages() failed:", err);
|
|
8956
7934
|
return [];
|
|
8957
7935
|
}
|
|
8958
7936
|
},
|
|
@@ -8974,7 +7952,7 @@ function createTelegramSDK(bridge, log12) {
|
|
|
8974
7952
|
return bridge.isAvailable();
|
|
8975
7953
|
},
|
|
8976
7954
|
getRawClient() {
|
|
8977
|
-
|
|
7955
|
+
log10.warn("getRawClient() called \u2014 this bypasses SDK sandbox guarantees");
|
|
8978
7956
|
if (!bridge.isAvailable()) return null;
|
|
8979
7957
|
try {
|
|
8980
7958
|
return bridge.getClient().getClient();
|
|
@@ -8983,8 +7961,8 @@ function createTelegramSDK(bridge, log12) {
|
|
|
8983
7961
|
}
|
|
8984
7962
|
},
|
|
8985
7963
|
// Spread extended methods from sub-modules
|
|
8986
|
-
...createTelegramMessagesSDK(bridge,
|
|
8987
|
-
...createTelegramSocialSDK(bridge,
|
|
7964
|
+
...createTelegramMessagesSDK(bridge, log10),
|
|
7965
|
+
...createTelegramSocialSDK(bridge, log10)
|
|
8988
7966
|
};
|
|
8989
7967
|
}
|
|
8990
7968
|
|
|
@@ -9025,23 +8003,23 @@ function deletePluginSecret(pluginName, key) {
|
|
|
9025
8003
|
function listPluginSecretKeys(pluginName) {
|
|
9026
8004
|
return Object.keys(readSecretsFile(pluginName));
|
|
9027
8005
|
}
|
|
9028
|
-
function createSecretsSDK(pluginName, pluginConfig,
|
|
8006
|
+
function createSecretsSDK(pluginName, pluginConfig, log10) {
|
|
9029
8007
|
const envPrefix = pluginName.replace(/-/g, "_").toUpperCase();
|
|
9030
8008
|
function get(key) {
|
|
9031
8009
|
const envKey = `${envPrefix}_${key.toUpperCase()}`;
|
|
9032
8010
|
const envValue = process.env[envKey];
|
|
9033
8011
|
if (envValue) {
|
|
9034
|
-
|
|
8012
|
+
log10.debug(`Secret "${key}" resolved from env var ${envKey}`);
|
|
9035
8013
|
return envValue;
|
|
9036
8014
|
}
|
|
9037
8015
|
const stored = readSecretsFile(pluginName);
|
|
9038
8016
|
if (key in stored && stored[key]) {
|
|
9039
|
-
|
|
8017
|
+
log10.debug(`Secret "${key}" resolved from secrets store`);
|
|
9040
8018
|
return stored[key];
|
|
9041
8019
|
}
|
|
9042
8020
|
const configValue = pluginConfig[key];
|
|
9043
8021
|
if (configValue !== void 0 && configValue !== null) {
|
|
9044
|
-
|
|
8022
|
+
log10.debug(`Secret "${key}" resolved from pluginConfig`);
|
|
9045
8023
|
return String(configValue);
|
|
9046
8024
|
}
|
|
9047
8025
|
return void 0;
|
|
@@ -9143,7 +8121,7 @@ function createStorageSDK(db) {
|
|
|
9143
8121
|
}
|
|
9144
8122
|
|
|
9145
8123
|
// src/sdk/bot.ts
|
|
9146
|
-
function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLimiter,
|
|
8124
|
+
function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLimiter, log10) {
|
|
9147
8125
|
if (!router || !manifest || !manifest.inline && !manifest.callbacks) {
|
|
9148
8126
|
return null;
|
|
9149
8127
|
}
|
|
@@ -9166,7 +8144,7 @@ function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLi
|
|
|
9166
8144
|
},
|
|
9167
8145
|
onInlineQuery(handler) {
|
|
9168
8146
|
if (handlers.onInlineQuery) {
|
|
9169
|
-
|
|
8147
|
+
log10.warn("onInlineQuery called again \u2014 overwriting previous handler");
|
|
9170
8148
|
}
|
|
9171
8149
|
handlers.onInlineQuery = async (ctx) => {
|
|
9172
8150
|
if (rateLimiter) {
|
|
@@ -9212,7 +8190,7 @@ function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLi
|
|
|
9212
8190
|
return;
|
|
9213
8191
|
} catch (error) {
|
|
9214
8192
|
if (error?.errorMessage === "MESSAGE_NOT_MODIFIED") return;
|
|
9215
|
-
|
|
8193
|
+
log10.warn(`GramJS edit failed, falling back to Grammy: ${error?.errorMessage || error}`);
|
|
9216
8194
|
}
|
|
9217
8195
|
}
|
|
9218
8196
|
if (grammyBot) {
|
|
@@ -9225,7 +8203,7 @@ function createBotSDK(router, gramjsBot, grammyBot, pluginName, manifest, rateLi
|
|
|
9225
8203
|
});
|
|
9226
8204
|
} catch (error) {
|
|
9227
8205
|
if (error?.description?.includes("message is not modified")) return;
|
|
9228
|
-
|
|
8206
|
+
log10.error(`Failed to edit inline message: ${error?.description || error}`);
|
|
9229
8207
|
}
|
|
9230
8208
|
}
|
|
9231
8209
|
},
|
|
@@ -9284,13 +8262,13 @@ function createSafeDb(db) {
|
|
|
9284
8262
|
});
|
|
9285
8263
|
}
|
|
9286
8264
|
function createPluginSDK(deps, opts) {
|
|
9287
|
-
const
|
|
8265
|
+
const log10 = createLogger2(opts.pluginName);
|
|
9288
8266
|
const safeDb = opts.db ? createSafeDb(opts.db) : null;
|
|
9289
|
-
const ton = Object.freeze(createTonSDK(
|
|
9290
|
-
const telegram = Object.freeze(createTelegramSDK(deps.bridge,
|
|
9291
|
-
const secrets = Object.freeze(createSecretsSDK(opts.pluginName, opts.pluginConfig,
|
|
8267
|
+
const ton = Object.freeze(createTonSDK(log10, safeDb));
|
|
8268
|
+
const telegram = Object.freeze(createTelegramSDK(deps.bridge, log10));
|
|
8269
|
+
const secrets = Object.freeze(createSecretsSDK(opts.pluginName, opts.pluginConfig, log10));
|
|
9292
8270
|
const storage = safeDb ? Object.freeze(createStorageSDK(safeDb)) : null;
|
|
9293
|
-
const frozenLog = Object.freeze(
|
|
8271
|
+
const frozenLog = Object.freeze(log10);
|
|
9294
8272
|
const frozenConfig = Object.freeze(JSON.parse(JSON.stringify(opts.sanitizedConfig ?? {})));
|
|
9295
8273
|
const frozenPluginConfig = Object.freeze(JSON.parse(JSON.stringify(opts.pluginConfig ?? {})));
|
|
9296
8274
|
let cachedBot;
|
|
@@ -9321,20 +8299,20 @@ function createPluginSDK(deps, opts) {
|
|
|
9321
8299
|
},
|
|
9322
8300
|
on(hookName, handler, onOpts) {
|
|
9323
8301
|
if (!opts.hookRegistry) {
|
|
9324
|
-
|
|
8302
|
+
log10.warn(`Hook registration unavailable \u2014 sdk.on() ignored`);
|
|
9325
8303
|
return;
|
|
9326
8304
|
}
|
|
9327
8305
|
if (opts.declaredHooks) {
|
|
9328
8306
|
const declared = opts.declaredHooks.some((h2) => h2.name === hookName);
|
|
9329
8307
|
if (!declared) {
|
|
9330
|
-
|
|
8308
|
+
log10.warn(`Hook "${hookName}" not declared in manifest \u2014 registration rejected`);
|
|
9331
8309
|
return;
|
|
9332
8310
|
}
|
|
9333
8311
|
}
|
|
9334
8312
|
const rawPriority = Number(onOpts?.priority) || 0;
|
|
9335
8313
|
const clampedPriority = Math.max(-1e3, Math.min(1e3, rawPriority));
|
|
9336
8314
|
if (rawPriority !== clampedPriority) {
|
|
9337
|
-
|
|
8315
|
+
log10.debug(`Hook "${hookName}" priority ${rawPriority} clamped to ${clampedPriority}`);
|
|
9338
8316
|
}
|
|
9339
8317
|
const registered = opts.hookRegistry.register({
|
|
9340
8318
|
pluginId: opts.pluginName,
|
|
@@ -9344,7 +8322,7 @@ function createPluginSDK(deps, opts) {
|
|
|
9344
8322
|
globalPriority: opts.globalPriority ?? 0
|
|
9345
8323
|
});
|
|
9346
8324
|
if (!registered) {
|
|
9347
|
-
|
|
8325
|
+
log10.warn(
|
|
9348
8326
|
`Hook registration limit reached for plugin "${opts.pluginName}" \u2014 "${hookName}" rejected`
|
|
9349
8327
|
);
|
|
9350
8328
|
}
|
|
@@ -9463,7 +8441,7 @@ var HookRegistry = class {
|
|
|
9463
8441
|
|
|
9464
8442
|
// src/agent/tools/plugin-loader.ts
|
|
9465
8443
|
var execFileAsync = promisify(execFile);
|
|
9466
|
-
var
|
|
8444
|
+
var log7 = createLogger("PluginLoader");
|
|
9467
8445
|
var PLUGIN_DATA_DIR = join3(TELETON_ROOT, "plugins", "data");
|
|
9468
8446
|
function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps, hookRegistry, pluginPriorities) {
|
|
9469
8447
|
let manifest = null;
|
|
@@ -9471,7 +8449,7 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps, hookReg
|
|
|
9471
8449
|
try {
|
|
9472
8450
|
manifest = validateManifest(raw.manifest);
|
|
9473
8451
|
} catch (err) {
|
|
9474
|
-
|
|
8452
|
+
log7.warn(
|
|
9475
8453
|
`[${entryName}] invalid manifest, ignoring: ${err instanceof Error ? err.message : err}`
|
|
9476
8454
|
);
|
|
9477
8455
|
}
|
|
@@ -9665,7 +8643,7 @@ async function ensurePluginDeps(pluginDir, pluginEntry) {
|
|
|
9665
8643
|
const nodeModules = join3(pluginDir, "node_modules");
|
|
9666
8644
|
if (!existsSync5(pkgJson)) return;
|
|
9667
8645
|
if (!existsSync5(lockfile)) {
|
|
9668
|
-
|
|
8646
|
+
log7.warn(
|
|
9669
8647
|
`[${pluginEntry}] package.json without package-lock.json \u2014 skipping (lockfile required)`
|
|
9670
8648
|
);
|
|
9671
8649
|
return;
|
|
@@ -9674,16 +8652,16 @@ async function ensurePluginDeps(pluginDir, pluginEntry) {
|
|
|
9674
8652
|
const marker = join3(nodeModules, ".package-lock.json");
|
|
9675
8653
|
if (existsSync5(marker) && statSync(marker).mtimeMs >= statSync(lockfile).mtimeMs) return;
|
|
9676
8654
|
}
|
|
9677
|
-
|
|
8655
|
+
log7.info(`[${pluginEntry}] Installing dependencies...`);
|
|
9678
8656
|
try {
|
|
9679
8657
|
await execFileAsync("npm", ["ci", "--ignore-scripts", "--no-audit", "--no-fund"], {
|
|
9680
8658
|
cwd: pluginDir,
|
|
9681
8659
|
timeout: 6e4,
|
|
9682
8660
|
env: { ...process.env, NODE_ENV: "production" }
|
|
9683
8661
|
});
|
|
9684
|
-
|
|
8662
|
+
log7.info(`[${pluginEntry}] Dependencies installed`);
|
|
9685
8663
|
} catch (err) {
|
|
9686
|
-
|
|
8664
|
+
log7.error(`[${pluginEntry}] Failed to install deps: ${String(err).slice(0, 300)}`);
|
|
9687
8665
|
}
|
|
9688
8666
|
}
|
|
9689
8667
|
async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps, db) {
|
|
@@ -9736,7 +8714,7 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps, db) {
|
|
|
9736
8714
|
);
|
|
9737
8715
|
for (const result of loadResults) {
|
|
9738
8716
|
if (result.status === "rejected") {
|
|
9739
|
-
|
|
8717
|
+
log7.error(
|
|
9740
8718
|
`Plugin failed to load: ${result.reason instanceof Error ? result.reason.message : result.reason}`
|
|
9741
8719
|
);
|
|
9742
8720
|
continue;
|
|
@@ -9744,7 +8722,7 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps, db) {
|
|
|
9744
8722
|
const { entry, mod } = result.value;
|
|
9745
8723
|
try {
|
|
9746
8724
|
if (!mod.tools || typeof mod.tools !== "function" && !Array.isArray(mod.tools)) {
|
|
9747
|
-
|
|
8725
|
+
log7.warn(`Plugin "${entry}": no 'tools' array or function exported, skipping`);
|
|
9748
8726
|
continue;
|
|
9749
8727
|
}
|
|
9750
8728
|
const adapted = adaptPlugin(
|
|
@@ -9757,18 +8735,28 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps, db) {
|
|
|
9757
8735
|
pluginPriorities
|
|
9758
8736
|
);
|
|
9759
8737
|
if (loadedNames.has(adapted.name)) {
|
|
9760
|
-
|
|
8738
|
+
log7.warn(`Plugin "${adapted.name}" already loaded, skipping duplicate from "${entry}"`);
|
|
9761
8739
|
continue;
|
|
9762
8740
|
}
|
|
9763
8741
|
loadedNames.add(adapted.name);
|
|
9764
8742
|
modules.push(adapted);
|
|
9765
8743
|
} catch (err) {
|
|
9766
|
-
|
|
8744
|
+
log7.error(`Plugin "${entry}" failed to adapt: ${err instanceof Error ? err.message : err}`);
|
|
9767
8745
|
}
|
|
9768
8746
|
}
|
|
9769
8747
|
return { modules, hookRegistry };
|
|
9770
8748
|
}
|
|
9771
8749
|
|
|
8750
|
+
// src/agent/token-usage.ts
|
|
8751
|
+
var globalTokenUsage = { totalTokens: 0, totalCost: 0 };
|
|
8752
|
+
function getTokenUsage() {
|
|
8753
|
+
return { ...globalTokenUsage };
|
|
8754
|
+
}
|
|
8755
|
+
function accumulateTokenUsage(usage) {
|
|
8756
|
+
globalTokenUsage.totalTokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
8757
|
+
globalTokenUsage.totalCost += usage.totalCost;
|
|
8758
|
+
}
|
|
8759
|
+
|
|
9772
8760
|
// src/ton-proxy/manager.ts
|
|
9773
8761
|
import { spawn, execSync } from "child_process";
|
|
9774
8762
|
import {
|
|
@@ -9782,7 +8770,7 @@ import {
|
|
|
9782
8770
|
import { mkdir } from "fs/promises";
|
|
9783
8771
|
import { join as join4 } from "path";
|
|
9784
8772
|
import { pipeline } from "stream/promises";
|
|
9785
|
-
var
|
|
8773
|
+
var log8 = createLogger("TonProxy");
|
|
9786
8774
|
var GITHUB_REPO = "xssnick/Tonutils-Proxy";
|
|
9787
8775
|
var BINARY_DIR = join4(TELETON_ROOT, "bin");
|
|
9788
8776
|
var PID_FILE = join4(TELETON_ROOT, "ton-proxy.pid");
|
|
@@ -9817,7 +8805,7 @@ var TonProxyManager = class {
|
|
|
9817
8805
|
*/
|
|
9818
8806
|
async install() {
|
|
9819
8807
|
const binaryName = getBinaryName();
|
|
9820
|
-
|
|
8808
|
+
log8.info(`Downloading TON Proxy binary (${binaryName})...`);
|
|
9821
8809
|
await mkdir(BINARY_DIR, { recursive: true });
|
|
9822
8810
|
const releaseUrl = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
|
|
9823
8811
|
const releaseRes = await fetch(releaseUrl, {
|
|
@@ -9829,7 +8817,7 @@ var TonProxyManager = class {
|
|
|
9829
8817
|
const release = await releaseRes.json();
|
|
9830
8818
|
const tag = release.tag_name;
|
|
9831
8819
|
const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/${tag}/${binaryName}`;
|
|
9832
|
-
|
|
8820
|
+
log8.info(`Downloading ${downloadUrl}`);
|
|
9833
8821
|
const res = await fetch(downloadUrl);
|
|
9834
8822
|
if (!res.ok || !res.body) {
|
|
9835
8823
|
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
@@ -9838,7 +8826,7 @@ var TonProxyManager = class {
|
|
|
9838
8826
|
const fileStream = createWriteStream(dest);
|
|
9839
8827
|
await pipeline(res.body, fileStream);
|
|
9840
8828
|
chmodSync(dest, 493);
|
|
9841
|
-
|
|
8829
|
+
log8.info(`TON Proxy installed: ${dest} (${tag})`);
|
|
9842
8830
|
}
|
|
9843
8831
|
/** Kill any orphan proxy process from a previous session */
|
|
9844
8832
|
killOrphan() {
|
|
@@ -9848,7 +8836,7 @@ var TonProxyManager = class {
|
|
|
9848
8836
|
if (pid && !isNaN(pid)) {
|
|
9849
8837
|
try {
|
|
9850
8838
|
process.kill(pid, 0);
|
|
9851
|
-
|
|
8839
|
+
log8.warn(`Killing orphan TON Proxy (PID ${pid}) from previous session`);
|
|
9852
8840
|
process.kill(pid, "SIGTERM");
|
|
9853
8841
|
} catch {
|
|
9854
8842
|
}
|
|
@@ -9865,7 +8853,7 @@ var TonProxyManager = class {
|
|
|
9865
8853
|
const pidMatch = out.match(/pid=(\d+)/);
|
|
9866
8854
|
if (pidMatch) {
|
|
9867
8855
|
const pid = parseInt(pidMatch[1], 10);
|
|
9868
|
-
|
|
8856
|
+
log8.warn(`Port ${this.config.port} occupied by PID ${pid}, killing it`);
|
|
9869
8857
|
try {
|
|
9870
8858
|
process.kill(pid, "SIGTERM");
|
|
9871
8859
|
} catch {
|
|
@@ -9880,7 +8868,7 @@ var TonProxyManager = class {
|
|
|
9880
8868
|
try {
|
|
9881
8869
|
writeFileSync2(PID_FILE, String(pid), { mode: 384 });
|
|
9882
8870
|
} catch {
|
|
9883
|
-
|
|
8871
|
+
log8.warn("Failed to write TON Proxy PID file");
|
|
9884
8872
|
}
|
|
9885
8873
|
}
|
|
9886
8874
|
/** Remove PID file */
|
|
@@ -9893,7 +8881,7 @@ var TonProxyManager = class {
|
|
|
9893
8881
|
/** Start the proxy process */
|
|
9894
8882
|
async start() {
|
|
9895
8883
|
if (this.isRunning()) {
|
|
9896
|
-
|
|
8884
|
+
log8.warn("TON Proxy is already running");
|
|
9897
8885
|
return;
|
|
9898
8886
|
}
|
|
9899
8887
|
this.restartCount = 0;
|
|
@@ -9904,7 +8892,7 @@ var TonProxyManager = class {
|
|
|
9904
8892
|
}
|
|
9905
8893
|
const binaryPath = this.getBinaryPath();
|
|
9906
8894
|
const port = String(this.config.port);
|
|
9907
|
-
|
|
8895
|
+
log8.info(`Starting TON Proxy on 127.0.0.1:${port}`);
|
|
9908
8896
|
this.process = spawn(binaryPath, ["-addr", `127.0.0.1:${port}`], {
|
|
9909
8897
|
cwd: BINARY_DIR,
|
|
9910
8898
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -9912,24 +8900,24 @@ var TonProxyManager = class {
|
|
|
9912
8900
|
});
|
|
9913
8901
|
this.process.stdout?.on("data", (chunk) => {
|
|
9914
8902
|
const line = chunk.toString().trim();
|
|
9915
|
-
if (line)
|
|
8903
|
+
if (line) log8.debug(`[proxy] ${line}`);
|
|
9916
8904
|
});
|
|
9917
8905
|
this.process.stderr?.on("data", (chunk) => {
|
|
9918
8906
|
const line = chunk.toString().trim();
|
|
9919
|
-
if (line)
|
|
8907
|
+
if (line) log8.warn(`[proxy:err] ${line}`);
|
|
9920
8908
|
});
|
|
9921
8909
|
this.process.on("exit", (code, signal) => {
|
|
9922
|
-
|
|
8910
|
+
log8.info(`TON Proxy exited (code=${code}, signal=${signal})`);
|
|
9923
8911
|
this.process = null;
|
|
9924
8912
|
this.removePidFile();
|
|
9925
8913
|
if (code !== 0 && code !== null && this.restartCount < this.maxRestarts) {
|
|
9926
8914
|
this.restartCount++;
|
|
9927
|
-
|
|
9928
|
-
this.start().catch((err) =>
|
|
8915
|
+
log8.warn(`Auto-restarting TON Proxy (attempt ${this.restartCount}/${this.maxRestarts})`);
|
|
8916
|
+
this.start().catch((err) => log8.error({ err }, "Failed to auto-restart TON Proxy"));
|
|
9929
8917
|
}
|
|
9930
8918
|
});
|
|
9931
8919
|
this.process.on("error", (err) => {
|
|
9932
|
-
|
|
8920
|
+
log8.error({ err }, "TON Proxy process error");
|
|
9933
8921
|
this.process = null;
|
|
9934
8922
|
});
|
|
9935
8923
|
this.startHealthCheck();
|
|
@@ -9947,14 +8935,14 @@ var TonProxyManager = class {
|
|
|
9947
8935
|
});
|
|
9948
8936
|
});
|
|
9949
8937
|
if (this.process?.pid) this.writePidFile(this.process.pid);
|
|
9950
|
-
|
|
8938
|
+
log8.info(`TON Proxy running on 127.0.0.1:${port} (PID ${this.process?.pid})`);
|
|
9951
8939
|
}
|
|
9952
8940
|
/** Stop the proxy process gracefully */
|
|
9953
8941
|
async stop() {
|
|
9954
8942
|
this.stopHealthCheck();
|
|
9955
8943
|
if (!this.process) return;
|
|
9956
8944
|
this.maxRestarts = 0;
|
|
9957
|
-
|
|
8945
|
+
log8.info("Stopping TON Proxy...");
|
|
9958
8946
|
return new Promise((resolve2) => {
|
|
9959
8947
|
if (!this.process) {
|
|
9960
8948
|
resolve2();
|
|
@@ -9962,7 +8950,7 @@ var TonProxyManager = class {
|
|
|
9962
8950
|
}
|
|
9963
8951
|
const forceKill = setTimeout(() => {
|
|
9964
8952
|
if (this.process) {
|
|
9965
|
-
|
|
8953
|
+
log8.warn("TON Proxy did not exit gracefully, sending SIGKILL");
|
|
9966
8954
|
this.process.kill("SIGKILL");
|
|
9967
8955
|
}
|
|
9968
8956
|
}, KILL_GRACE_MS);
|
|
@@ -9984,7 +8972,7 @@ var TonProxyManager = class {
|
|
|
9984
8972
|
if (existsSync6(binaryPath)) {
|
|
9985
8973
|
const { unlink } = await import("fs/promises");
|
|
9986
8974
|
await unlink(binaryPath);
|
|
9987
|
-
|
|
8975
|
+
log8.info(`TON Proxy binary removed: ${binaryPath}`);
|
|
9988
8976
|
}
|
|
9989
8977
|
}
|
|
9990
8978
|
/** Get proxy status for WebUI / tools */
|
|
@@ -10018,7 +9006,7 @@ var TonProxyManager = class {
|
|
|
10018
9006
|
}).catch(() => null);
|
|
10019
9007
|
clearTimeout(timeout);
|
|
10020
9008
|
if (!res) {
|
|
10021
|
-
|
|
9009
|
+
log8.warn("TON Proxy health check failed (no response)");
|
|
10022
9010
|
}
|
|
10023
9011
|
} catch {
|
|
10024
9012
|
}
|
|
@@ -10075,7 +9063,7 @@ var tonProxyStatusExecutor = async () => {
|
|
|
10075
9063
|
};
|
|
10076
9064
|
|
|
10077
9065
|
// src/ton-proxy/module.ts
|
|
10078
|
-
var
|
|
9066
|
+
var log9 = createLogger("TonProxyModule");
|
|
10079
9067
|
var manager = null;
|
|
10080
9068
|
function getTonProxyManager() {
|
|
10081
9069
|
return manager;
|
|
@@ -10102,9 +9090,9 @@ var tonProxyModule = {
|
|
|
10102
9090
|
setProxyManager(manager);
|
|
10103
9091
|
try {
|
|
10104
9092
|
await manager.start();
|
|
10105
|
-
|
|
9093
|
+
log9.info(`TON Proxy started on port ${proxyConfig.port}`);
|
|
10106
9094
|
} catch (err) {
|
|
10107
|
-
|
|
9095
|
+
log9.error({ err }, "Failed to start TON Proxy");
|
|
10108
9096
|
manager = null;
|
|
10109
9097
|
}
|
|
10110
9098
|
},
|
|
@@ -10161,13 +9149,15 @@ export {
|
|
|
10161
9149
|
validateReadPath,
|
|
10162
9150
|
validateWritePath,
|
|
10163
9151
|
validateDirectory,
|
|
9152
|
+
appendToDailyLog,
|
|
9153
|
+
writeSummaryToDailyLog,
|
|
10164
9154
|
sanitizeForPrompt,
|
|
10165
9155
|
sanitizeForContext,
|
|
10166
9156
|
clearPromptCache,
|
|
10167
9157
|
loadSoul,
|
|
10168
|
-
|
|
9158
|
+
buildSystemPrompt,
|
|
10169
9159
|
getTokenUsage,
|
|
10170
|
-
|
|
9160
|
+
accumulateTokenUsage,
|
|
10171
9161
|
writePluginSecret,
|
|
10172
9162
|
deletePluginSecret,
|
|
10173
9163
|
listPluginSecretKeys,
|