morpheus-cli 0.4.10 → 0.4.12
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/dist/channels/telegram.js +135 -89
- package/package.json +4 -3
|
@@ -5,6 +5,7 @@ import fs from 'fs-extra';
|
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import { spawn } from 'child_process';
|
|
8
|
+
import { convert } from 'telegram-markdown-v2';
|
|
8
9
|
import { ConfigManager } from '../config/manager.js';
|
|
9
10
|
import { DisplayManager } from '../runtime/display.js';
|
|
10
11
|
import { createTelephonist } from '../runtime/telephonist.js';
|
|
@@ -13,6 +14,33 @@ import { SQLiteChatMessageHistory } from '../runtime/memory/sqlite.js';
|
|
|
13
14
|
import { SatiRepository } from '../runtime/memory/sati/repository.js';
|
|
14
15
|
import { MCPManager } from '../config/mcp-manager.js';
|
|
15
16
|
import { Construtor } from '../runtime/tools/factory.js';
|
|
17
|
+
/**
|
|
18
|
+
* Converts standard Markdown (as produced by LLMs) to Telegram MarkdownV2.
|
|
19
|
+
* Unsupported tags (e.g. tables) have their special chars escaped so they
|
|
20
|
+
* render as plain text instead of breaking the parse.
|
|
21
|
+
* Truncates to Telegram's 4096-char hard limit.
|
|
22
|
+
* Use for dynamic LLM/Oracle output.
|
|
23
|
+
*/
|
|
24
|
+
function toMd(text) {
|
|
25
|
+
const MAX = 4096;
|
|
26
|
+
const converted = convert(text, 'escape');
|
|
27
|
+
const safe = converted.length > MAX ? converted.slice(0, MAX - 3) + '\\.\\.\\.' : converted;
|
|
28
|
+
return { text: safe, parse_mode: 'MarkdownV2' };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Escapes special characters in a plain string segment so it's safe to embed
|
|
32
|
+
* inside a manually-built MarkdownV2 message. Does NOT touch * _ ` [ ] chars
|
|
33
|
+
* (those are intentional MarkdownV2 formatting from our own code).
|
|
34
|
+
* Use for dynamic values (usernames, numbers, paths) interpolated into fixed templates.
|
|
35
|
+
*/
|
|
36
|
+
function escMd(value) {
|
|
37
|
+
// Escape all MarkdownV2 special characters that are NOT used as intentional
|
|
38
|
+
// formatters in our static templates (*bold*, _italic_, `code`, [link]).
|
|
39
|
+
// Per Telegram docs: _ * [ ] ( ) ~ ` # + - = | { } . ! must be escaped.
|
|
40
|
+
// We skip * _ ` [ ] here because those are our intentional formatters.
|
|
41
|
+
// The - must be at end of character class to avoid being treated as a range.
|
|
42
|
+
return String(value).replace(/([.!?(){}#+~|=>$@\\-])/g, '\\$1');
|
|
43
|
+
}
|
|
16
44
|
export class TelegramAdapter {
|
|
17
45
|
bot = null;
|
|
18
46
|
isConnected = false;
|
|
@@ -33,18 +61,18 @@ export class TelegramAdapter {
|
|
|
33
61
|
this.rateLimiter.set(userId, now);
|
|
34
62
|
return false;
|
|
35
63
|
}
|
|
36
|
-
HELP_MESSAGE = `/start
|
|
37
|
-
/status
|
|
38
|
-
/doctor
|
|
39
|
-
/stats
|
|
40
|
-
/help
|
|
41
|
-
/zaion
|
|
42
|
-
/sati <qnt>
|
|
43
|
-
/newsession
|
|
44
|
-
/sessions
|
|
45
|
-
/restart
|
|
46
|
-
/mcpreload
|
|
47
|
-
/mcp or /mcps
|
|
64
|
+
HELP_MESSAGE = `/start \\- Show this welcome message and available commands
|
|
65
|
+
/status \\- Check the status of the Morpheus agent
|
|
66
|
+
/doctor \\- Diagnose environment and configuration issues
|
|
67
|
+
/stats \\- Show token usage statistics
|
|
68
|
+
/help \\- Show available commands
|
|
69
|
+
/zaion \\- Show system configurations
|
|
70
|
+
/sati <qnt> \\- Show specific memories
|
|
71
|
+
/newsession \\- Archive current session and start fresh
|
|
72
|
+
/sessions \\- List all sessions with titles and switch between them
|
|
73
|
+
/restart \\- Restart the Morpheus agent
|
|
74
|
+
/mcpreload \\- Reload MCP servers without restarting
|
|
75
|
+
/mcp or /mcps \\- List registered MCP servers`;
|
|
48
76
|
constructor(oracle) {
|
|
49
77
|
this.oracle = oracle;
|
|
50
78
|
}
|
|
@@ -87,7 +115,12 @@ export class TelegramAdapter {
|
|
|
87
115
|
// Process with Agent
|
|
88
116
|
const response = await this.oracle.chat(text);
|
|
89
117
|
if (response) {
|
|
90
|
-
|
|
118
|
+
try {
|
|
119
|
+
await ctx.reply(toMd(response).text, { parse_mode: 'MarkdownV2' });
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
await ctx.reply(response);
|
|
123
|
+
}
|
|
91
124
|
this.display.log(`Responded to @${user}: ${response}`, { source: 'Telegram' });
|
|
92
125
|
}
|
|
93
126
|
}
|
|
@@ -154,7 +187,7 @@ export class TelegramAdapter {
|
|
|
154
187
|
// The prompt says "reply with the answer".
|
|
155
188
|
// "Transcribe them... and process the resulting text as a standard user prompt."
|
|
156
189
|
// So I should treat 'text' as if it was a text message.
|
|
157
|
-
await ctx.reply(`🎤
|
|
190
|
+
await ctx.reply(`🎤 Transcription: "${text}"`);
|
|
158
191
|
await ctx.sendChatAction('typing');
|
|
159
192
|
// Process with Agent
|
|
160
193
|
const response = await this.oracle.chat(text, usage, true);
|
|
@@ -166,7 +199,12 @@ export class TelegramAdapter {
|
|
|
166
199
|
// }
|
|
167
200
|
// }
|
|
168
201
|
if (response) {
|
|
169
|
-
|
|
202
|
+
try {
|
|
203
|
+
await ctx.reply(toMd(response).text, { parse_mode: 'MarkdownV2' });
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
await ctx.reply(response);
|
|
207
|
+
}
|
|
170
208
|
this.display.log(`Responded to @${user} (via audio)`, { source: 'Telegram' });
|
|
171
209
|
}
|
|
172
210
|
}
|
|
@@ -227,7 +265,7 @@ export class TelegramAdapter {
|
|
|
227
265
|
const sessionId = data.replace('ask_archive_session_', '');
|
|
228
266
|
// Fetch session title for better UX (optional, but nice) - for now just use ID
|
|
229
267
|
await ctx.reply(`⚠️ **ARCHIVE SESSION?**\n\nAre you sure you want to archive session \`${sessionId}\`?\n\nIt will be moved to long-term memory (SATI) and removed from the active list. This action cannot be easily undone via Telegram.`, {
|
|
230
|
-
parse_mode: '
|
|
268
|
+
parse_mode: 'MarkdownV2',
|
|
231
269
|
reply_markup: {
|
|
232
270
|
inline_keyboard: [
|
|
233
271
|
[
|
|
@@ -249,7 +287,7 @@ export class TelegramAdapter {
|
|
|
249
287
|
if (ctx.updateType === 'callback_query') {
|
|
250
288
|
ctx.deleteMessage().catch(() => { });
|
|
251
289
|
}
|
|
252
|
-
await ctx.reply(`✅ Session \`${sessionId}\` has been archived and moved to long-term memory.`, { parse_mode: '
|
|
290
|
+
await ctx.reply(`✅ Session \`${sessionId}\` has been archived and moved to long-term memory.`, { parse_mode: 'MarkdownV2' });
|
|
253
291
|
}
|
|
254
292
|
catch (error) {
|
|
255
293
|
await ctx.answerCbQuery(`Error archiving: ${error.message}`, { show_alert: true });
|
|
@@ -260,7 +298,7 @@ export class TelegramAdapter {
|
|
|
260
298
|
const data = ctx.callbackQuery.data;
|
|
261
299
|
const sessionId = data.replace('ask_delete_session_', '');
|
|
262
300
|
await ctx.reply(`🚫 **DELETE SESSION?**\n\nAre you sure you want to PERMANENTLY DELETE session \`${sessionId}\`?\n\nThis action is **IRREVERSIBLE**. All data will be lost.`, {
|
|
263
|
-
parse_mode: '
|
|
301
|
+
parse_mode: 'MarkdownV2',
|
|
264
302
|
reply_markup: {
|
|
265
303
|
inline_keyboard: [
|
|
266
304
|
[
|
|
@@ -282,7 +320,7 @@ export class TelegramAdapter {
|
|
|
282
320
|
if (ctx.updateType === 'callback_query') {
|
|
283
321
|
ctx.deleteMessage().catch(() => { });
|
|
284
322
|
}
|
|
285
|
-
await ctx.reply(`🗑️ Session \`${sessionId}\` has been permanently deleted.`, { parse_mode: '
|
|
323
|
+
await ctx.reply(`🗑️ Session \`${sessionId}\` has been permanently deleted.`, { parse_mode: 'MarkdownV2' });
|
|
286
324
|
}
|
|
287
325
|
catch (error) {
|
|
288
326
|
await ctx.answerCbQuery(`Error deleting: ${error.message}`, { show_alert: true });
|
|
@@ -379,17 +417,22 @@ export class TelegramAdapter {
|
|
|
379
417
|
this.display.log('No allowed Telegram users configured — skipping notification.', { source: 'Telegram', level: 'warning' });
|
|
380
418
|
return;
|
|
381
419
|
}
|
|
382
|
-
//
|
|
383
|
-
const
|
|
384
|
-
const safeText = text.length > MAX_LEN ? text.slice(0, MAX_LEN - 3) + '...' : text;
|
|
420
|
+
// toMd() already truncates to 4096 chars (Telegram's hard limit)
|
|
421
|
+
const { text: mdText, parse_mode } = toMd(text);
|
|
385
422
|
for (const userId of allowedUsers) {
|
|
386
423
|
try {
|
|
387
|
-
|
|
388
|
-
// causes "Can't find end of entity" errors with parse_mode: 'Markdown'.
|
|
389
|
-
await this.bot.telegram.sendMessage(userId, safeText);
|
|
424
|
+
await this.bot.telegram.sendMessage(userId, mdText, { parse_mode });
|
|
390
425
|
}
|
|
391
|
-
catch
|
|
392
|
-
|
|
426
|
+
catch {
|
|
427
|
+
// Fallback to plain text if MarkdownV2 conversion still fails
|
|
428
|
+
try {
|
|
429
|
+
const MAX_LEN = 4096;
|
|
430
|
+
const plain = text.length > MAX_LEN ? text.slice(0, MAX_LEN - 3) + '...' : text;
|
|
431
|
+
await this.bot.telegram.sendMessage(userId, plain);
|
|
432
|
+
}
|
|
433
|
+
catch (err) {
|
|
434
|
+
this.display.log(`Failed to send message to Telegram user ${userId}: ${err.message}`, { source: 'Telegram', level: 'error' });
|
|
435
|
+
}
|
|
393
436
|
}
|
|
394
437
|
}
|
|
395
438
|
}
|
|
@@ -464,7 +507,7 @@ export class TelegramAdapter {
|
|
|
464
507
|
async handleNewSessionCommand(ctx, user) {
|
|
465
508
|
try {
|
|
466
509
|
await ctx.reply("Are you ready to start a new session? Please confirm.", {
|
|
467
|
-
parse_mode: '
|
|
510
|
+
parse_mode: 'MarkdownV2', reply_markup: {
|
|
468
511
|
inline_keyboard: [
|
|
469
512
|
[{ text: 'Yes, start new session', callback_data: 'confirm_new_session' }, { text: 'No, cancel', callback_data: 'cancel_new_session' }]
|
|
470
513
|
]
|
|
@@ -490,7 +533,7 @@ export class TelegramAdapter {
|
|
|
490
533
|
const history = new SQLiteChatMessageHistory({ sessionId: "" });
|
|
491
534
|
const sessions = await history.listSessions();
|
|
492
535
|
if (sessions.length === 0) {
|
|
493
|
-
await ctx.reply('No active or paused sessions found.', { parse_mode: '
|
|
536
|
+
await ctx.reply('No active or paused sessions found.', { parse_mode: 'MarkdownV2' });
|
|
494
537
|
return;
|
|
495
538
|
}
|
|
496
539
|
let response = '*Sessions:*\n\n';
|
|
@@ -498,10 +541,10 @@ export class TelegramAdapter {
|
|
|
498
541
|
for (const session of sessions) {
|
|
499
542
|
const title = session.title || 'Untitled Session';
|
|
500
543
|
const statusEmoji = session.status === 'active' ? '🟢' : '🟡';
|
|
501
|
-
response += `${statusEmoji} *${title}*\n`;
|
|
502
|
-
response +=
|
|
503
|
-
response +=
|
|
504
|
-
response +=
|
|
544
|
+
response += `${statusEmoji} *${escMd(title)}*\n`;
|
|
545
|
+
response += `\\- ID: ${escMd(session.id)}\n`;
|
|
546
|
+
response += `\\- Status: ${escMd(session.status)}\n`;
|
|
547
|
+
response += `\\- Started: ${escMd(new Date(session.started_at).toLocaleString())}\n\n`;
|
|
505
548
|
// Adicionar botão inline para alternar para esta sessão
|
|
506
549
|
const sessionButtons = [];
|
|
507
550
|
if (session.status !== 'active') {
|
|
@@ -521,7 +564,7 @@ export class TelegramAdapter {
|
|
|
521
564
|
keyboard.push(sessionButtons);
|
|
522
565
|
}
|
|
523
566
|
await ctx.reply(response, {
|
|
524
|
-
parse_mode: '
|
|
567
|
+
parse_mode: 'MarkdownV2',
|
|
525
568
|
reply_markup: {
|
|
526
569
|
inline_keyboard: keyboard
|
|
527
570
|
}
|
|
@@ -642,7 +685,7 @@ How can I assist you today?`;
|
|
|
642
685
|
else {
|
|
643
686
|
response += '⚠️ Configuration: Missing\n';
|
|
644
687
|
}
|
|
645
|
-
await ctx.reply(response, { parse_mode: '
|
|
688
|
+
await ctx.reply(response, { parse_mode: 'MarkdownV2' });
|
|
646
689
|
}
|
|
647
690
|
async handleStatsCommand(ctx, user) {
|
|
648
691
|
try {
|
|
@@ -659,33 +702,33 @@ How can I assist you today?`;
|
|
|
659
702
|
const totalAudioSeconds = groupedStats.reduce((sum, s) => sum + (s.totalAudioSeconds || 0), 0);
|
|
660
703
|
const totalCost = stats.totalEstimatedCostUsd;
|
|
661
704
|
let response = '*Token Usage Statistics*\n\n';
|
|
662
|
-
response += `Input Tokens: ${stats.totalInputTokens.toLocaleString()}\n`;
|
|
663
|
-
response += `Output Tokens: ${stats.totalOutputTokens.toLocaleString()}\n`;
|
|
664
|
-
response += `Total Tokens: ${totalTokens.toLocaleString()}\n`;
|
|
705
|
+
response += `Input Tokens: ${escMd(stats.totalInputTokens.toLocaleString())}\n`;
|
|
706
|
+
response += `Output Tokens: ${escMd(stats.totalOutputTokens.toLocaleString())}\n`;
|
|
707
|
+
response += `Total Tokens: ${escMd(totalTokens.toLocaleString())}\n`;
|
|
665
708
|
if (totalAudioSeconds > 0) {
|
|
666
|
-
response += `Audio Processed: ${totalAudioSeconds.toFixed(1)}s\n`;
|
|
709
|
+
response += `Audio Processed: ${escMd(totalAudioSeconds.toFixed(1))}s\n`;
|
|
667
710
|
}
|
|
668
711
|
if (totalCost != null) {
|
|
669
|
-
response += `Estimated Cost:
|
|
712
|
+
response += `Estimated Cost: \\$${escMd(totalCost.toFixed(4))}\n`;
|
|
670
713
|
}
|
|
671
714
|
response += '\n';
|
|
672
715
|
if (groupedStats.length > 0) {
|
|
673
716
|
response += '*By Provider/Model:*\n';
|
|
674
717
|
for (const stat of groupedStats) {
|
|
675
|
-
response += `\n*${stat.provider}/${stat.model}*\n`;
|
|
676
|
-
response += ` Tokens: ${stat.totalTokens.toLocaleString()} (${stat.messageCount} msgs)\n`;
|
|
718
|
+
response += `\n*${escMd(stat.provider)}/${escMd(stat.model)}*\n`;
|
|
719
|
+
response += ` Tokens: ${escMd(stat.totalTokens.toLocaleString())} \\(${escMd(stat.messageCount)} msgs\\)\n`;
|
|
677
720
|
if (stat.totalAudioSeconds > 0) {
|
|
678
|
-
response += ` Audio: ${stat.totalAudioSeconds.toFixed(1)}s\n`;
|
|
721
|
+
response += ` Audio: ${escMd(stat.totalAudioSeconds.toFixed(1))}s\n`;
|
|
679
722
|
}
|
|
680
723
|
if (stat.estimatedCostUsd != null) {
|
|
681
|
-
response += ` Cost:
|
|
724
|
+
response += ` Cost: \\$${escMd(stat.estimatedCostUsd.toFixed(4))}\n`;
|
|
682
725
|
}
|
|
683
726
|
}
|
|
684
727
|
}
|
|
685
728
|
else {
|
|
686
|
-
response += 'No detailed usage statistics available
|
|
729
|
+
response += 'No detailed usage statistics available\\.';
|
|
687
730
|
}
|
|
688
|
-
await ctx.reply(response, { parse_mode: '
|
|
731
|
+
await ctx.reply(response, { parse_mode: 'MarkdownV2' });
|
|
689
732
|
history.close();
|
|
690
733
|
}
|
|
691
734
|
catch (error) {
|
|
@@ -702,7 +745,7 @@ How can I assist you today?`;
|
|
|
702
745
|
let response = await this.oracle.chat(prompt);
|
|
703
746
|
if (response) {
|
|
704
747
|
try {
|
|
705
|
-
await ctx.reply(response, { parse_mode: '
|
|
748
|
+
await ctx.reply(toMd(response).text, { parse_mode: 'MarkdownV2' });
|
|
706
749
|
}
|
|
707
750
|
catch {
|
|
708
751
|
await ctx.reply(response);
|
|
@@ -717,57 +760,57 @@ How can I assist you today?`;
|
|
|
717
760
|
${this.HELP_MESSAGE}
|
|
718
761
|
|
|
719
762
|
How can I assist you today?`;
|
|
720
|
-
await ctx.reply(helpMessage, { parse_mode: '
|
|
763
|
+
await ctx.reply(helpMessage, { parse_mode: 'MarkdownV2' });
|
|
721
764
|
}
|
|
722
765
|
async handleZaionCommand(ctx, user) {
|
|
723
766
|
const config = this.config.get();
|
|
724
767
|
let response = '*System Configuration*\n\n';
|
|
725
768
|
response += `*Agent:*\n`;
|
|
726
|
-
response +=
|
|
727
|
-
response +=
|
|
728
|
-
response += `*Oracle (LLM):*\n`;
|
|
729
|
-
response +=
|
|
730
|
-
response +=
|
|
731
|
-
response +=
|
|
732
|
-
response +=
|
|
769
|
+
response += `\\- Name: ${escMd(config.agent.name)}\n`;
|
|
770
|
+
response += `\\- Personality: ${escMd(config.agent.personality)}\n\n`;
|
|
771
|
+
response += `*Oracle \\(LLM\\):*\n`;
|
|
772
|
+
response += `\\- Provider: ${escMd(config.llm.provider)}\n`;
|
|
773
|
+
response += `\\- Model: ${escMd(config.llm.model)}\n`;
|
|
774
|
+
response += `\\- Temperature: ${escMd(config.llm.temperature)}\n`;
|
|
775
|
+
response += `\\- Context Window: ${escMd(config.llm.context_window || 100)}\n\n`;
|
|
733
776
|
// Sati config (falls back to llm if not set)
|
|
734
777
|
const sati = config.sati;
|
|
735
|
-
response += `*Sati (Memory):*\n`;
|
|
778
|
+
response += `*Sati \\(Memory\\):*\n`;
|
|
736
779
|
if (sati?.provider) {
|
|
737
|
-
response +=
|
|
738
|
-
response +=
|
|
739
|
-
response +=
|
|
740
|
-
response +=
|
|
780
|
+
response += `\\- Provider: ${escMd(sati.provider)}\n`;
|
|
781
|
+
response += `\\- Model: ${escMd(sati.model || config.llm.model)}\n`;
|
|
782
|
+
response += `\\- Temperature: ${escMd(sati.temperature ?? config.llm.temperature)}\n`;
|
|
783
|
+
response += `\\- Memory Limit: ${escMd(sati.memory_limit ?? 1000)}\n`;
|
|
741
784
|
}
|
|
742
785
|
else {
|
|
743
|
-
response +=
|
|
786
|
+
response += `\\- Inherits Oracle config\n`;
|
|
744
787
|
}
|
|
745
788
|
response += '\n';
|
|
746
789
|
// Apoc config (falls back to llm if not set)
|
|
747
790
|
const apoc = config.apoc;
|
|
748
|
-
response += `*Apoc (DevTools):*\n`;
|
|
791
|
+
response += `*Apoc \\(DevTools\\):*\n`;
|
|
749
792
|
if (apoc?.provider) {
|
|
750
|
-
response +=
|
|
751
|
-
response +=
|
|
752
|
-
response +=
|
|
793
|
+
response += `\\- Provider: ${escMd(apoc.provider)}\n`;
|
|
794
|
+
response += `\\- Model: ${escMd(apoc.model || config.llm.model)}\n`;
|
|
795
|
+
response += `\\- Temperature: ${escMd(apoc.temperature ?? 0.2)}\n`;
|
|
753
796
|
if (apoc.working_dir)
|
|
754
|
-
response +=
|
|
755
|
-
response +=
|
|
797
|
+
response += `\\- Working Dir: ${escMd(apoc.working_dir)}\n`;
|
|
798
|
+
response += `\\- Timeout: ${escMd(apoc.timeout_ms ?? 30000)}ms\n`;
|
|
756
799
|
}
|
|
757
800
|
else {
|
|
758
|
-
response +=
|
|
801
|
+
response += `\\- Inherits Oracle config\n`;
|
|
759
802
|
}
|
|
760
803
|
response += '\n';
|
|
761
804
|
response += `*Channels:*\n`;
|
|
762
|
-
response +=
|
|
763
|
-
response +=
|
|
805
|
+
response += `\\- Telegram Enabled: ${escMd(config.channels.telegram.enabled)}\n`;
|
|
806
|
+
response += `\\- Discord Enabled: ${escMd(config.channels.discord.enabled)}\n\n`;
|
|
764
807
|
response += `*UI:*\n`;
|
|
765
|
-
response +=
|
|
766
|
-
response +=
|
|
808
|
+
response += `\\- Enabled: ${escMd(config.ui.enabled)}\n`;
|
|
809
|
+
response += `\\- Port: ${escMd(config.ui.port)}\n\n`;
|
|
767
810
|
response += `*Audio:*\n`;
|
|
768
|
-
response +=
|
|
769
|
-
response +=
|
|
770
|
-
await ctx.reply(response, { parse_mode: '
|
|
811
|
+
response += `\\- Enabled: ${escMd(config.audio.enabled)}\n`;
|
|
812
|
+
response += `\\- Max Duration: ${escMd(config.audio.maxDurationSeconds)}s\n`;
|
|
813
|
+
await ctx.reply(response, { parse_mode: 'MarkdownV2' });
|
|
771
814
|
}
|
|
772
815
|
async handleSatiCommand(ctx, user, args) {
|
|
773
816
|
let limit = null;
|
|
@@ -791,13 +834,16 @@ How can I assist you today?`;
|
|
|
791
834
|
if (limit !== null) {
|
|
792
835
|
selectedMemories = memories.slice(0, Math.min(limit, memories.length));
|
|
793
836
|
}
|
|
794
|
-
|
|
837
|
+
const countLabel = limit !== null
|
|
838
|
+
? `${escMd(selectedMemories.length)} SATI Memories \\(Showing first ${escMd(selectedMemories.length)}\\)`
|
|
839
|
+
: `${escMd(selectedMemories.length)} SATI Memories`;
|
|
840
|
+
let response = `*${countLabel}:*\n\n`;
|
|
795
841
|
for (const memory of selectedMemories) {
|
|
796
842
|
// Limitar o tamanho do resumo para evitar mensagens muito longas
|
|
797
843
|
const truncatedSummary = memory.summary.length > 200 ? memory.summary.substring(0, 200) + '...' : memory.summary;
|
|
798
|
-
response += `*${memory.category} (${memory.importance}):* ${truncatedSummary}\n\n`;
|
|
844
|
+
response += `*${escMd(memory.category)} \\(${escMd(memory.importance)}\\):* ${escMd(truncatedSummary)}\n\n`;
|
|
799
845
|
}
|
|
800
|
-
await ctx.reply(response, { parse_mode: '
|
|
846
|
+
await ctx.reply(response, { parse_mode: 'MarkdownV2' });
|
|
801
847
|
}
|
|
802
848
|
catch (error) {
|
|
803
849
|
await ctx.reply(`Failed to retrieve memories: ${error.message}`);
|
|
@@ -891,11 +937,11 @@ How can I assist you today?`;
|
|
|
891
937
|
Construtor.probe(),
|
|
892
938
|
]);
|
|
893
939
|
if (servers.length === 0) {
|
|
894
|
-
await ctx.reply('*No MCP Servers Configured*\n\nThere are currently no MCP servers configured in the system.', { parse_mode: '
|
|
940
|
+
await ctx.reply('*No MCP Servers Configured*\n\nThere are currently no MCP servers configured in the system.', { parse_mode: 'MarkdownV2' });
|
|
895
941
|
return;
|
|
896
942
|
}
|
|
897
943
|
const probeMap = new Map(probeResults.map(r => [r.name, r]));
|
|
898
|
-
let response = `*MCP Servers (${servers.length})*\n\n`;
|
|
944
|
+
let response = `*MCP Servers \\(${escMd(servers.length)}\\)*\n\n`;
|
|
899
945
|
const keyboard = [];
|
|
900
946
|
servers.forEach((server, index) => {
|
|
901
947
|
const enabledStatus = server.enabled ? '✅ Enabled' : '❌ Disabled';
|
|
@@ -903,25 +949,25 @@ How can I assist you today?`;
|
|
|
903
949
|
const probe = probeMap.get(server.name);
|
|
904
950
|
const connectionStatus = probe
|
|
905
951
|
? probe.ok
|
|
906
|
-
? `🟢 Connected (${probe.toolCount} tools)`
|
|
952
|
+
? `🟢 Connected \\(${escMd(probe.toolCount)} tools\\)`
|
|
907
953
|
: `🔴 Failed`
|
|
908
954
|
: '⚪ Unknown';
|
|
909
|
-
response += `*${index + 1}
|
|
955
|
+
response += `*${escMd(index + 1)}\\. ${escMd(server.name)}*\n`;
|
|
910
956
|
response += `Status: ${enabledStatus}\n`;
|
|
911
957
|
response += `Connection: ${connectionStatus}\n`;
|
|
912
|
-
response += `Transport: ${transport}\n`;
|
|
958
|
+
response += `Transport: ${escMd(transport)}\n`;
|
|
913
959
|
if (server.config.transport === 'stdio') {
|
|
914
|
-
response += `Command: \`${server.config.command}\`\n`;
|
|
960
|
+
response += `Command: \`${escMd(server.config.command)}\`\n`;
|
|
915
961
|
if (server.config.args && server.config.args.length > 0) {
|
|
916
|
-
response += `Args: \`${server.config.args.join(' ')}\`\n`;
|
|
962
|
+
response += `Args: \`${escMd(server.config.args.join(' '))}\`\n`;
|
|
917
963
|
}
|
|
918
964
|
}
|
|
919
965
|
else if (server.config.transport === 'http') {
|
|
920
|
-
response += `URL: \`${server.config.url}\`\n`;
|
|
966
|
+
response += `URL: \`${escMd(server.config.url)}\`\n`;
|
|
921
967
|
}
|
|
922
968
|
if (probe && !probe.ok && probe.error) {
|
|
923
969
|
const shortError = probe.error.length > 80 ? probe.error.slice(0, 80) + '…' : probe.error;
|
|
924
|
-
response += `Error: \`${shortError}\`\n`;
|
|
970
|
+
response += `Error: \`${escMd(shortError)}\`\n`;
|
|
925
971
|
}
|
|
926
972
|
response += '\n';
|
|
927
973
|
if (server.enabled) {
|
|
@@ -932,13 +978,13 @@ How can I assist you today?`;
|
|
|
932
978
|
}
|
|
933
979
|
});
|
|
934
980
|
await ctx.reply(response, {
|
|
935
|
-
parse_mode: '
|
|
981
|
+
parse_mode: 'MarkdownV2',
|
|
936
982
|
reply_markup: { inline_keyboard: keyboard },
|
|
937
983
|
});
|
|
938
984
|
}
|
|
939
985
|
catch (error) {
|
|
940
986
|
this.display.log('Error listing MCP servers: ' + (error instanceof Error ? error.message : String(error)), { source: 'Telegram', level: 'error' });
|
|
941
|
-
await ctx.reply('An error occurred while retrieving the list of MCP servers. Please check the logs for more details.', { parse_mode: '
|
|
987
|
+
await ctx.reply('An error occurred while retrieving the list of MCP servers. Please check the logs for more details.', { parse_mode: 'MarkdownV2' });
|
|
942
988
|
}
|
|
943
989
|
}
|
|
944
990
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "morpheus-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.12",
|
|
4
4
|
"description": "Morpheus is a local AI agent for developers, running as a CLI daemon that connects to LLMs, local tools, and MCPs, enabling interaction via Terminal, Telegram, and Discord. Inspired by the character Morpheus from *The Matrix*, the project acts as an intelligent orchestrator, bridging the gap between the developer and complex systems.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"morpheus": "./bin/morpheus.js"
|
|
@@ -47,14 +47,15 @@
|
|
|
47
47
|
"fs-extra": "^11.3.3",
|
|
48
48
|
"js-yaml": "^4.1.1",
|
|
49
49
|
"langchain": "^1.2.16",
|
|
50
|
+
"mcp-remote": "^0.1.38",
|
|
50
51
|
"open": "^11.0.0",
|
|
51
52
|
"ora": "^9.1.0",
|
|
52
53
|
"sqlite-vec": "^0.1.7-alpha.2",
|
|
53
54
|
"telegraf": "^4.16.3",
|
|
55
|
+
"telegram-markdown-v2": "^0.0.4",
|
|
54
56
|
"winston": "^3.19.0",
|
|
55
57
|
"winston-daily-rotate-file": "^5.0.0",
|
|
56
|
-
"zod": "^4.3.6"
|
|
57
|
-
"mcp-remote": "^0.1.38"
|
|
58
|
+
"zod": "^4.3.6"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
60
61
|
"@types/body-parser": "^1.19.6",
|