obol-ai 0.2.5 → 0.2.7
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/.claude/settings.local.json +2 -1
- package/package.json +2 -2
- package/src/background.js +10 -3
- package/src/claude.js +55 -77
- package/src/messages.js +1 -1
- package/src/telegram.js +29 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Self-evolving AI assistant that learns, remembers, and acts on its own. Persistent vector memory, self-rewriting personality, proactive heartbeats.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"author": "Jo Vinkenroye <jestersimpps@gmail.com>",
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@anthropic-ai/sdk": "^0.
|
|
25
|
+
"@anthropic-ai/sdk": "^0.78.0",
|
|
26
26
|
"@supabase/supabase-js": "^2.49.1",
|
|
27
27
|
"@xenova/transformers": "^2.17.2",
|
|
28
28
|
"commander": "^13.1.0",
|
package/src/background.js
CHANGED
|
@@ -20,8 +20,9 @@ class BackgroundRunner {
|
|
|
20
20
|
* @param {string} task - The task description
|
|
21
21
|
* @param {object} ctx - Telegram context (for sending updates)
|
|
22
22
|
* @param {object} memory - Memory instance
|
|
23
|
+
* @param {object} parentContext - Parent context for verbose forwarding
|
|
23
24
|
*/
|
|
24
|
-
spawn(claude, task, ctx, memory) {
|
|
25
|
+
spawn(claude, task, ctx, memory, parentContext) {
|
|
25
26
|
let running = 0;
|
|
26
27
|
for (const t of this.tasks.values()) {
|
|
27
28
|
if (t.status === 'running') running++;
|
|
@@ -42,6 +43,9 @@ class BackgroundRunner {
|
|
|
42
43
|
|
|
43
44
|
this.tasks.set(taskId, taskState);
|
|
44
45
|
|
|
46
|
+
const verbose = parentContext?.verbose || false;
|
|
47
|
+
const verboseNotify = parentContext?._verboseNotify;
|
|
48
|
+
|
|
45
49
|
// Start check-in timer before running task to avoid leak if task throws immediately
|
|
46
50
|
taskState.checkInTimer = setInterval(async () => {
|
|
47
51
|
if (taskState.status !== 'running') {
|
|
@@ -55,13 +59,13 @@ class BackgroundRunner {
|
|
|
55
59
|
}, CHECK_IN_INTERVAL);
|
|
56
60
|
|
|
57
61
|
// Run the task
|
|
58
|
-
const promise = this._runTask(claude, task, taskState, ctx, memory);
|
|
62
|
+
const promise = this._runTask(claude, task, taskState, ctx, memory, verbose, verboseNotify);
|
|
59
63
|
taskState.promise = promise;
|
|
60
64
|
|
|
61
65
|
return taskId;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
async _runTask(claude, task, taskState, ctx, memory) {
|
|
68
|
+
async _runTask(claude, task, taskState, ctx, memory, verbose, verboseNotify) {
|
|
65
69
|
try {
|
|
66
70
|
// Give the background task a system instruction to report progress
|
|
67
71
|
const bgPrompt = `You are working on a background task. Do the work thoroughly.
|
|
@@ -73,9 +77,12 @@ This helps track what you're doing. Complete the full task, then give the final
|
|
|
73
77
|
|
|
74
78
|
TASK: ${task}`;
|
|
75
79
|
|
|
80
|
+
const bgNotify = verboseNotify ? (msg) => verboseNotify(`[bg#${taskState.id}] ${msg}`) : undefined;
|
|
76
81
|
const result = await claude.chat(bgPrompt, {
|
|
77
82
|
chatId: `bg-${taskState.id}`,
|
|
78
83
|
userName: 'BackgroundTask',
|
|
84
|
+
verbose,
|
|
85
|
+
_verboseNotify: bgNotify,
|
|
79
86
|
});
|
|
80
87
|
|
|
81
88
|
taskState.status = 'done';
|
package/src/claude.js
CHANGED
|
@@ -15,18 +15,8 @@ const BLOCKED_EXEC_PATTERNS = [
|
|
|
15
15
|
/\bmkfs\b/, /\bdd\s+if=/, /\b:()\{\s*:|:&\s*\};:/,
|
|
16
16
|
/\bchmod\s+(-R\s+)?[0-7]*\s+\/[^t]/,
|
|
17
17
|
/>\s*\/etc\//, />\s*\/boot\//,
|
|
18
|
-
/\beval\s+/, /\bsource\s+/,
|
|
19
|
-
/\bbash\s+-c\b/, /\bsh\s+-c\b/, /\bzsh\s+-c\b/,
|
|
20
|
-
/`[^`]*`/,
|
|
21
|
-
/\$\([^)]*\)/,
|
|
22
|
-
/\bpython[23]?\s+-c\b/, /\bperl\s+-e\b/, /\bruby\s+-e\b/, /\bnode\s+-e\b/,
|
|
23
18
|
/\bcurl\b.*\|\s*(ba)?sh/, /\bwget\b.*\|\s*(ba)?sh/,
|
|
24
|
-
/\benv\b.*\b(sh|bash|zsh)\b/,
|
|
25
|
-
/\bfind\b.*-exec\b/,
|
|
26
|
-
/\bprintf\b.*\|\s*(ba)?sh/,
|
|
27
|
-
/\\x[0-9a-fA-F]{2}/, /\\[0-7]{3}/,
|
|
28
19
|
/\bnc\s+-e\b/, /\bncat\b.*-e\b/,
|
|
29
|
-
/\bmkfifo\b/,
|
|
30
20
|
/>\s*\/dev\/sd/,
|
|
31
21
|
];
|
|
32
22
|
|
|
@@ -237,7 +227,11 @@ function createClaude(anthropicConfig, { personality, memory, userDir = OBOL_DIR
|
|
|
237
227
|
|
|
238
228
|
const verbose = context.verbose || false;
|
|
239
229
|
if (verbose) context.verboseLog = [];
|
|
240
|
-
const vlog = (msg) => {
|
|
230
|
+
const vlog = (msg) => {
|
|
231
|
+
if (!verbose) return;
|
|
232
|
+
context.verboseLog.push(msg);
|
|
233
|
+
context._verboseNotify?.(msg);
|
|
234
|
+
};
|
|
241
235
|
|
|
242
236
|
let memoryContext = '';
|
|
243
237
|
if (memory) {
|
|
@@ -255,7 +249,7 @@ Reply with ONLY a JSON object:
|
|
|
255
249
|
|
|
256
250
|
Memory: casual messages (greetings, jokes, simple questions) → false. References to past, people, projects, preferences → true with optimized search query.
|
|
257
251
|
|
|
258
|
-
Model: Use "haiku" for:
|
|
252
|
+
Model: Default to "sonnet". Use "haiku" for: greetings, brief acknowledgments (thanks/ok/bye), casual chitchat, simple factual questions with short answers, quick yes/no questions, and short single-turn exchanges that don't need deep reasoning. Use "sonnet" for: code generation, data analysis, content creation, explanations, creative writing, agentic tool use, general questions, opinions, advice, and most conversational exchanges with substance. Use "opus" for: professional software engineering tasks, advanced multi-step agent work, complex reasoning, scientific or mathematical problems, tasks requiring nuanced understanding, advanced coding challenges, in-depth research, and architecture or design decisions.`,
|
|
259
253
|
messages: [{ role: 'user', content: userMessage }],
|
|
260
254
|
});
|
|
261
255
|
|
|
@@ -342,81 +336,47 @@ Model: Use "haiku" for: casual chat, greetings, simple factual questions, short
|
|
|
342
336
|
const model = context._model || 'claude-sonnet-4-6';
|
|
343
337
|
vlog(`[model] ${model} | history=${history.length} msgs`);
|
|
344
338
|
const systemPrompt = baseSystemPrompt + `\nCurrent time: ${new Date().toISOString()}`;
|
|
345
|
-
|
|
339
|
+
const runnableTools = buildRunnableTools(tools, memory, context, vlog);
|
|
340
|
+
|
|
341
|
+
const runner = client.beta.messages.toolRunner({
|
|
346
342
|
model,
|
|
347
343
|
max_tokens: 4096,
|
|
348
344
|
system: systemPrompt,
|
|
349
|
-
messages: history,
|
|
350
|
-
tools:
|
|
345
|
+
messages: [...history],
|
|
346
|
+
tools: runnableTools.length > 0 ? runnableTools : undefined,
|
|
347
|
+
max_iterations: MAX_TOOL_ITERATIONS,
|
|
351
348
|
});
|
|
352
349
|
|
|
353
|
-
let
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (
|
|
357
|
-
|
|
358
|
-
history.push({ role: 'assistant', content: bailoutContent });
|
|
359
|
-
const bailoutResults = bailoutContent
|
|
360
|
-
.filter(b => b.type === 'tool_use')
|
|
361
|
-
.map(b => ({ type: 'tool_result', tool_use_id: b.id, content: '[max tool iterations reached]' }));
|
|
362
|
-
history.push({ role: 'user', content: [
|
|
363
|
-
...bailoutResults,
|
|
364
|
-
{ type: 'text', text: 'You have used too many tool calls. Please provide a final response now based on what you have so far.' },
|
|
365
|
-
] });
|
|
366
|
-
response = await client.messages.create({
|
|
367
|
-
model,
|
|
368
|
-
max_tokens: 4096,
|
|
369
|
-
system: systemPrompt,
|
|
370
|
-
messages: history,
|
|
371
|
-
});
|
|
372
|
-
break;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const assistantContent = response.content;
|
|
376
|
-
history.push({ role: 'assistant', content: assistantContent });
|
|
377
|
-
|
|
378
|
-
const toolResults = [];
|
|
379
|
-
for (const block of assistantContent) {
|
|
380
|
-
if (block.type === 'tool_use') {
|
|
381
|
-
const inputSummary = block.name === 'exec' ? block.input.command :
|
|
382
|
-
block.name === 'write_file' ? block.input.path :
|
|
383
|
-
block.name === 'read_file' ? block.input.path :
|
|
384
|
-
block.name === 'memory_search' ? block.input.query :
|
|
385
|
-
block.name === 'memory_add' ? `[${block.input.category || 'fact'}]` :
|
|
386
|
-
block.name === 'web_fetch' ? block.input.url :
|
|
387
|
-
block.name === 'background_task' ? block.input.task?.substring(0, 60) :
|
|
388
|
-
JSON.stringify(block.input).substring(0, 80);
|
|
389
|
-
vlog(`[tool] ${block.name}: ${inputSummary}`);
|
|
390
|
-
const result = await executeToolCall(block, memory, context);
|
|
391
|
-
toolResults.push({
|
|
392
|
-
type: 'tool_result',
|
|
393
|
-
tool_use_id: block.id,
|
|
394
|
-
content: result,
|
|
395
|
-
});
|
|
396
|
-
}
|
|
350
|
+
let finalMessage;
|
|
351
|
+
for await (const message of runner) {
|
|
352
|
+
finalMessage = message;
|
|
353
|
+
if (message.usage) {
|
|
354
|
+
vlog(`[tokens] in=${message.usage.input_tokens} out=${message.usage.output_tokens}`);
|
|
397
355
|
}
|
|
398
|
-
|
|
399
|
-
history.push({ role: 'user', content: toolResults });
|
|
400
|
-
|
|
401
|
-
response = await client.messages.create({
|
|
402
|
-
model,
|
|
403
|
-
max_tokens: 4096,
|
|
404
|
-
system: systemPrompt,
|
|
405
|
-
messages: history,
|
|
406
|
-
tools,
|
|
407
|
-
});
|
|
408
356
|
}
|
|
409
357
|
|
|
410
|
-
const
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
vlog(`[tokens] in=${response.usage.input_tokens} out=${response.usage.output_tokens}`);
|
|
358
|
+
const runnerMessages = runner.params.messages;
|
|
359
|
+
const newMessages = runnerMessages.slice(history.length);
|
|
360
|
+
for (const msg of newMessages) {
|
|
361
|
+
history.push(msg);
|
|
415
362
|
}
|
|
416
363
|
|
|
417
|
-
|
|
364
|
+
if (finalMessage.stop_reason === 'tool_use') {
|
|
365
|
+
const bailoutResults = finalMessage.content
|
|
366
|
+
.filter(b => b.type === 'tool_use')
|
|
367
|
+
.map(b => ({ type: 'tool_result', tool_use_id: b.id, content: '[max tool iterations reached]' }));
|
|
368
|
+
history.push({ role: 'user', content: [
|
|
369
|
+
...bailoutResults,
|
|
370
|
+
{ type: 'text', text: 'You have used too many tool calls. Please provide a final response now based on what you have so far.' },
|
|
371
|
+
] });
|
|
372
|
+
const bailoutResponse = await client.messages.create({
|
|
373
|
+
model, max_tokens: 4096, system: systemPrompt, messages: history,
|
|
374
|
+
});
|
|
375
|
+
history.push({ role: 'assistant', content: bailoutResponse.content });
|
|
376
|
+
return bailoutResponse.content.filter(b => b.type === 'text').map(b => b.text).join('\n');
|
|
377
|
+
}
|
|
418
378
|
|
|
419
|
-
return
|
|
379
|
+
return finalMessage.content.filter(b => b.type === 'text').map(b => b.text).join('\n');
|
|
420
380
|
|
|
421
381
|
} catch (e) {
|
|
422
382
|
if (e.status === 400 && e.message?.includes('tool_use')) {
|
|
@@ -908,6 +868,24 @@ function buildTools(memory, opts = {}) {
|
|
|
908
868
|
return tools;
|
|
909
869
|
}
|
|
910
870
|
|
|
871
|
+
function buildRunnableTools(tools, memory, context, vlog) {
|
|
872
|
+
return tools.map(tool => ({
|
|
873
|
+
...tool,
|
|
874
|
+
run: async (input) => {
|
|
875
|
+
const inputSummary = tool.name === 'exec' ? input.command :
|
|
876
|
+
tool.name === 'write_file' ? input.path :
|
|
877
|
+
tool.name === 'read_file' ? input.path :
|
|
878
|
+
tool.name === 'memory_search' ? input.query :
|
|
879
|
+
tool.name === 'memory_add' ? `[${input.category || 'fact'}]` :
|
|
880
|
+
tool.name === 'web_fetch' ? input.url :
|
|
881
|
+
tool.name === 'background_task' ? input.task?.substring(0, 60) :
|
|
882
|
+
JSON.stringify(input).substring(0, 80);
|
|
883
|
+
vlog(`[tool] ${tool.name}: ${inputSummary}`);
|
|
884
|
+
return await executeToolCall({ name: tool.name, input }, memory, context);
|
|
885
|
+
},
|
|
886
|
+
}));
|
|
887
|
+
}
|
|
888
|
+
|
|
911
889
|
function resolveUserPath(inputPath, userDir) {
|
|
912
890
|
if (!userDir) throw new Error('userDir is required for path resolution');
|
|
913
891
|
const resolved = path.isAbsolute(inputPath)
|
|
@@ -1017,7 +995,7 @@ async function executeToolCall(toolUse, memory, context = {}) {
|
|
|
1017
995
|
const { bg, ctx: telegramCtx, claude: claudeInstance } = context;
|
|
1018
996
|
if (!bg || !telegramCtx) return 'Background tasks not available in this context.';
|
|
1019
997
|
if (!claudeInstance) return 'Background tasks not available.';
|
|
1020
|
-
const taskId = bg.spawn(claudeInstance, input.task, telegramCtx, memory);
|
|
998
|
+
const taskId = bg.spawn(claudeInstance, input.task, telegramCtx, memory, context);
|
|
1021
999
|
if (taskId === null) return 'Too many background tasks running. Wait for one to finish.';
|
|
1022
1000
|
return `Background task #${taskId} spawned. It will send progress updates and the final result to the chat.`;
|
|
1023
1001
|
}
|
package/src/messages.js
CHANGED
|
@@ -70,7 +70,7 @@ class MessageLog {
|
|
|
70
70
|
|
|
71
71
|
const { tickExchange } = require('./evolve');
|
|
72
72
|
tickExchange(this.userDir).then(result => {
|
|
73
|
-
if (result?.ready) this._evolutionReady = true;
|
|
73
|
+
if (result?.ready && !this._evolutionReady && !this._evolutionPending) this._evolutionReady = true;
|
|
74
74
|
}).catch(() => {});
|
|
75
75
|
}
|
|
76
76
|
}
|
package/src/telegram.js
CHANGED
|
@@ -13,6 +13,9 @@ const pkg = require('../package.json');
|
|
|
13
13
|
const RATE_LIMIT_MS = 3000;
|
|
14
14
|
const SPAM_THRESHOLD = 5;
|
|
15
15
|
const SPAM_COOLDOWN_MS = 30000;
|
|
16
|
+
const EVOLUTION_IDLE_MS = 15 * 60 * 1000;
|
|
17
|
+
|
|
18
|
+
const _evolutionTimers = new Map();
|
|
16
19
|
|
|
17
20
|
function startTyping(ctx) {
|
|
18
21
|
ctx.replyWithChatAction('typing').catch(() => {});
|
|
@@ -482,6 +485,12 @@ Your message is deleted immediately when using /secret set to keep credentials o
|
|
|
482
485
|
|
|
483
486
|
const tenant = await getTenant(userId, config);
|
|
484
487
|
|
|
488
|
+
if (_evolutionTimers.has(userId)) {
|
|
489
|
+
clearTimeout(_evolutionTimers.get(userId));
|
|
490
|
+
_evolutionTimers.delete(userId);
|
|
491
|
+
if (tenant.messageLog) tenant.messageLog._evolutionPending = false;
|
|
492
|
+
}
|
|
493
|
+
|
|
485
494
|
const stopTyping = startTyping(ctx);
|
|
486
495
|
|
|
487
496
|
try {
|
|
@@ -496,6 +505,10 @@ Your message is deleted immediately when using /secret set to keep credentials o
|
|
|
496
505
|
claude: tenant.claude,
|
|
497
506
|
config,
|
|
498
507
|
verbose: tenant.verbose,
|
|
508
|
+
_verboseNotify: tenant.verbose ? (msg) => {
|
|
509
|
+
const safe = msg.replace(/`/g, "'");
|
|
510
|
+
ctx.reply(`\`${safe}\``, { parse_mode: 'Markdown' }).catch(() => {});
|
|
511
|
+
} : undefined,
|
|
499
512
|
telegramAsk: (message, options, timeout) => createAsk(ctx, message, options, timeout),
|
|
500
513
|
_notifyFn: (targetUserId, message) => {
|
|
501
514
|
if (!allowedUsers.has(targetUserId)) throw new Error('Cannot notify user outside allowed list');
|
|
@@ -506,16 +519,12 @@ Your message is deleted immediately when using /secret set to keep credentials o
|
|
|
506
519
|
|
|
507
520
|
tenant.messageLog?.log(ctx.chat.id, 'assistant', response);
|
|
508
521
|
|
|
509
|
-
if (tenant.verbose && chatContext.verboseLog?.length) {
|
|
510
|
-
const verboseText = '```\n' + chatContext.verboseLog.join('\n') + '\n```';
|
|
511
|
-
await ctx.reply(verboseText, { parse_mode: 'Markdown' }).catch(() =>
|
|
512
|
-
ctx.reply(verboseText).catch(() => {})
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
522
|
|
|
516
|
-
if (tenant.messageLog?._evolutionReady) {
|
|
523
|
+
if (tenant.messageLog?._evolutionReady && !_evolutionTimers.has(userId)) {
|
|
517
524
|
tenant.messageLog._evolutionReady = false;
|
|
518
|
-
|
|
525
|
+
tenant.messageLog._evolutionPending = true;
|
|
526
|
+
const timer = setTimeout(async () => {
|
|
527
|
+
_evolutionTimers.delete(userId);
|
|
519
528
|
try {
|
|
520
529
|
const result = await evolve(tenant.claude.client, tenant.messageLog, tenant.memory, tenant.userDir);
|
|
521
530
|
tenant.claude.reloadPersonality?.();
|
|
@@ -555,8 +564,11 @@ Your message is deleted immediately when using /secret set to keep credentials o
|
|
|
555
564
|
);
|
|
556
565
|
} catch (e) {
|
|
557
566
|
console.error('Evolution failed:', e.message);
|
|
567
|
+
} finally {
|
|
568
|
+
tenant.messageLog._evolutionPending = false;
|
|
558
569
|
}
|
|
559
|
-
});
|
|
570
|
+
}, EVOLUTION_IDLE_MS);
|
|
571
|
+
_evolutionTimers.set(userId, timer);
|
|
560
572
|
}
|
|
561
573
|
|
|
562
574
|
stopTyping();
|
|
@@ -639,6 +651,10 @@ Your message is deleted immediately when using /secret set to keep credentials o
|
|
|
639
651
|
claude: tenant.claude,
|
|
640
652
|
config,
|
|
641
653
|
verbose: tenant.verbose,
|
|
654
|
+
_verboseNotify: tenant.verbose ? (msg) => {
|
|
655
|
+
const safe = msg.replace(/`/g, "'");
|
|
656
|
+
ctx.reply(`\`${safe}\``, { parse_mode: 'Markdown' }).catch(() => {});
|
|
657
|
+
} : undefined,
|
|
642
658
|
images: [imageBlock],
|
|
643
659
|
_notifyFn: (targetUserId, message) => {
|
|
644
660
|
if (!allowedUsers.has(targetUserId)) throw new Error('Cannot notify user outside allowed list');
|
|
@@ -650,11 +666,6 @@ Your message is deleted immediately when using /secret set to keep credentials o
|
|
|
650
666
|
tenant.messageLog?.log(ctx.chat.id, 'user', `[${fileInfo.mediaType}] ${caption || filename}`);
|
|
651
667
|
tenant.messageLog?.log(ctx.chat.id, 'assistant', response);
|
|
652
668
|
|
|
653
|
-
if (tenant.verbose && mediaChatCtx.verboseLog?.length) {
|
|
654
|
-
const verboseText = '```\n' + mediaChatCtx.verboseLog.join('\n') + '\n```';
|
|
655
|
-
await ctx.reply(verboseText, { parse_mode: 'Markdown' }).catch(() => ctx.reply(verboseText).catch(() => {}));
|
|
656
|
-
}
|
|
657
|
-
|
|
658
669
|
stopTyping();
|
|
659
670
|
if (response.length > 4096) {
|
|
660
671
|
for (const chunk of splitMessage(response, 4096)) {
|
|
@@ -674,6 +685,10 @@ Your message is deleted immediately when using /secret set to keep credentials o
|
|
|
674
685
|
claude: tenant.claude,
|
|
675
686
|
config,
|
|
676
687
|
verbose: tenant.verbose,
|
|
688
|
+
_verboseNotify: tenant.verbose ? (msg) => {
|
|
689
|
+
const safe = msg.replace(/`/g, "'");
|
|
690
|
+
ctx.reply(`\`${safe}\``, { parse_mode: 'Markdown' }).catch(() => {});
|
|
691
|
+
} : undefined,
|
|
677
692
|
_notifyFn: (targetUserId, message) => {
|
|
678
693
|
if (!allowedUsers.has(targetUserId)) throw new Error('Cannot notify user outside allowed list');
|
|
679
694
|
return bot.api.sendMessage(targetUserId, message);
|
|
@@ -684,11 +699,6 @@ Your message is deleted immediately when using /secret set to keep credentials o
|
|
|
684
699
|
tenant.messageLog?.log(ctx.chat.id, 'user', contextMsg);
|
|
685
700
|
tenant.messageLog?.log(ctx.chat.id, 'assistant', response);
|
|
686
701
|
|
|
687
|
-
if (tenant.verbose && mediaCaptionCtx.verboseLog?.length) {
|
|
688
|
-
const verboseText = '```\n' + mediaCaptionCtx.verboseLog.join('\n') + '\n```';
|
|
689
|
-
await ctx.reply(verboseText, { parse_mode: 'Markdown' }).catch(() => ctx.reply(verboseText).catch(() => {}));
|
|
690
|
-
}
|
|
691
|
-
|
|
692
702
|
stopTyping();
|
|
693
703
|
if (response.length > 4096) {
|
|
694
704
|
for (const chunk of splitMessage(response, 4096)) {
|