morpheus-cli 0.4.11 → 0.4.13

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.
@@ -19,6 +19,7 @@ import { Construtor } from '../runtime/tools/factory.js';
19
19
  * Unsupported tags (e.g. tables) have their special chars escaped so they
20
20
  * render as plain text instead of breaking the parse.
21
21
  * Truncates to Telegram's 4096-char hard limit.
22
+ * Use for dynamic LLM/Oracle output.
22
23
  */
23
24
  function toMd(text) {
24
25
  const MAX = 4096;
@@ -26,6 +27,20 @@ function toMd(text) {
26
27
  const safe = converted.length > MAX ? converted.slice(0, MAX - 3) + '\\.\\.\\.' : converted;
27
28
  return { text: safe, parse_mode: 'MarkdownV2' };
28
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
+ }
29
44
  export class TelegramAdapter {
30
45
  bot = null;
31
46
  isConnected = false;
@@ -46,18 +61,18 @@ export class TelegramAdapter {
46
61
  this.rateLimiter.set(userId, now);
47
62
  return false;
48
63
  }
49
- HELP_MESSAGE = `/start - Show this welcome message and available commands
50
- /status - Check the status of the Morpheus agent
51
- /doctor - Diagnose environment and configuration issues
52
- /stats - Show token usage statistics
53
- /help - Show available commands
54
- /zaion - Show system configurations
55
- /sati <qnt> - Show specific memories
56
- /newsession - Archive current session and start fresh
57
- /sessions - List all sessions with titles and switch between them
58
- /restart - Restart the Morpheus agent
59
- /mcpreload - Reload MCP servers without restarting
60
- /mcp or /mcps - List registered MCP servers`;
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`;
61
76
  constructor(oracle) {
62
77
  this.oracle = oracle;
63
78
  }
@@ -249,7 +264,7 @@ export class TelegramAdapter {
249
264
  const data = ctx.callbackQuery.data;
250
265
  const sessionId = data.replace('ask_archive_session_', '');
251
266
  // Fetch session title for better UX (optional, but nice) - for now just use ID
252
- 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.`, {
267
+ await ctx.reply(`⚠️ *ARCHIVE SESSION?*\n\nAre you sure you want to archive session \`${escMd(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\\.`, {
253
268
  parse_mode: 'MarkdownV2',
254
269
  reply_markup: {
255
270
  inline_keyboard: [
@@ -272,7 +287,7 @@ export class TelegramAdapter {
272
287
  if (ctx.updateType === 'callback_query') {
273
288
  ctx.deleteMessage().catch(() => { });
274
289
  }
275
- await ctx.reply(`✅ Session \`${sessionId}\` has been archived and moved to long-term memory.`, { parse_mode: 'MarkdownV2' });
290
+ await ctx.reply(`✅ Session \`${escMd(sessionId)}\` has been archived and moved to long\\-term memory\\.`, { parse_mode: 'MarkdownV2' });
276
291
  }
277
292
  catch (error) {
278
293
  await ctx.answerCbQuery(`Error archiving: ${error.message}`, { show_alert: true });
@@ -282,7 +297,7 @@ export class TelegramAdapter {
282
297
  this.bot.action(/^ask_delete_session_/, async (ctx) => {
283
298
  const data = ctx.callbackQuery.data;
284
299
  const sessionId = data.replace('ask_delete_session_', '');
285
- 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.`, {
300
+ await ctx.reply(`🚫 *DELETE SESSION?*\n\nAre you sure you want to PERMANENTLY DELETE session \`${escMd(sessionId)}\`?\n\nThis action is *IRREVERSIBLE*\\. All data will be lost\\.`, {
286
301
  parse_mode: 'MarkdownV2',
287
302
  reply_markup: {
288
303
  inline_keyboard: [
@@ -305,7 +320,7 @@ export class TelegramAdapter {
305
320
  if (ctx.updateType === 'callback_query') {
306
321
  ctx.deleteMessage().catch(() => { });
307
322
  }
308
- await ctx.reply(`🗑️ Session \`${sessionId}\` has been permanently deleted.`, { parse_mode: 'MarkdownV2' });
323
+ await ctx.reply(`🗑️ Session \`${escMd(sessionId)}\` has been permanently deleted\\.`, { parse_mode: 'MarkdownV2' });
309
324
  }
310
325
  catch (error) {
311
326
  await ctx.answerCbQuery(`Error deleting: ${error.message}`, { show_alert: true });
@@ -402,17 +417,22 @@ export class TelegramAdapter {
402
417
  this.display.log('No allowed Telegram users configured — skipping notification.', { source: 'Telegram', level: 'warning' });
403
418
  return;
404
419
  }
405
- // Truncate to Telegram's 4096 char limit
406
- const MAX_LEN = 4096;
407
- 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);
408
422
  for (const userId of allowedUsers) {
409
423
  try {
410
- // Send as plain text — LLM output often has unbalanced markdown that
411
- // causes "Can't find end of entity" errors with parse_mode: 'MarkdownV2'.
412
- await this.bot.telegram.sendMessage(userId, safeText);
424
+ await this.bot.telegram.sendMessage(userId, mdText, { parse_mode });
413
425
  }
414
- catch (err) {
415
- this.display.log(`Failed to send message to Telegram user ${userId}: ${err.message}`, { source: 'Telegram', level: 'error' });
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
+ }
416
436
  }
417
437
  }
418
438
  }
@@ -486,7 +506,7 @@ export class TelegramAdapter {
486
506
  }
487
507
  async handleNewSessionCommand(ctx, user) {
488
508
  try {
489
- await ctx.reply("Are you ready to start a new session? Please confirm.", {
509
+ await ctx.reply("Are you ready to start a new session\\? Please confirm\\.", {
490
510
  parse_mode: 'MarkdownV2', reply_markup: {
491
511
  inline_keyboard: [
492
512
  [{ text: 'Yes, start new session', callback_data: 'confirm_new_session' }, { text: 'No, cancel', callback_data: 'cancel_new_session' }]
@@ -513,7 +533,7 @@ export class TelegramAdapter {
513
533
  const history = new SQLiteChatMessageHistory({ sessionId: "" });
514
534
  const sessions = await history.listSessions();
515
535
  if (sessions.length === 0) {
516
- await ctx.reply('No active or paused sessions found.', { parse_mode: 'MarkdownV2' });
536
+ await ctx.reply('No active or paused sessions found\\.', { parse_mode: 'MarkdownV2' });
517
537
  return;
518
538
  }
519
539
  let response = '*Sessions:*\n\n';
@@ -521,10 +541,10 @@ export class TelegramAdapter {
521
541
  for (const session of sessions) {
522
542
  const title = session.title || 'Untitled Session';
523
543
  const statusEmoji = session.status === 'active' ? '🟢' : '🟡';
524
- response += `${statusEmoji} *${title}*\n`;
525
- response += `- ID: ${session.id}\n`;
526
- response += `- Status: ${session.status}\n`;
527
- response += `- Started: ${new Date(session.started_at).toLocaleString()}\n\n`;
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`;
528
548
  // Adicionar botão inline para alternar para esta sessão
529
549
  const sessionButtons = [];
530
550
  if (session.status !== 'active') {
@@ -588,10 +608,10 @@ How can I assist you today?`;
588
608
  const nodeVersion = process.version;
589
609
  const majorVersion = parseInt(nodeVersion.replace('v', '').split('.')[0], 10);
590
610
  if (majorVersion >= 18) {
591
- response += '✅ Node.js: ' + nodeVersion + '\n';
611
+ response += `✅ Node\\.js: ${escMd(nodeVersion)}\n`;
592
612
  }
593
613
  else {
594
- response += '❌ Node.js: ' + nodeVersion + ' (Required: >=18)\n';
614
+ response += `❌ Node\\.js: ${escMd(nodeVersion)} \\(Required: >=18\\)\n`;
595
615
  }
596
616
  if (config) {
597
617
  response += '✅ Configuration: Valid\n';
@@ -613,10 +633,10 @@ How can I assist you today?`;
613
633
  const llmProvider = config.llm?.provider;
614
634
  if (llmProvider && llmProvider !== 'ollama') {
615
635
  if (hasApiKey(llmProvider, config.llm?.api_key)) {
616
- response += `✅ Oracle API key (${llmProvider})\n`;
636
+ response += `✅ Oracle API key \\(${escMd(llmProvider)}\\)\n`;
617
637
  }
618
638
  else {
619
- response += `❌ Oracle API key missing (${llmProvider})\n`;
639
+ response += `❌ Oracle API key missing \\(${escMd(llmProvider)}\\)\n`;
620
640
  }
621
641
  }
622
642
  // Sati
@@ -624,10 +644,10 @@ How can I assist you today?`;
624
644
  const satiProvider = sati?.provider || llmProvider;
625
645
  if (satiProvider && satiProvider !== 'ollama') {
626
646
  if (hasApiKey(satiProvider, sati?.api_key ?? config.llm?.api_key)) {
627
- response += `✅ Sati API key (${satiProvider})\n`;
647
+ response += `✅ Sati API key \\(${escMd(satiProvider)}\\)\n`;
628
648
  }
629
649
  else {
630
- response += `❌ Sati API key missing (${satiProvider})\n`;
650
+ response += `❌ Sati API key missing \\(${escMd(satiProvider)}\\)\n`;
631
651
  }
632
652
  }
633
653
  // Apoc
@@ -635,10 +655,10 @@ How can I assist you today?`;
635
655
  const apocProvider = apoc?.provider || llmProvider;
636
656
  if (apocProvider && apocProvider !== 'ollama') {
637
657
  if (hasApiKey(apocProvider, apoc?.api_key ?? config.llm?.api_key)) {
638
- response += `✅ Apoc API key (${apocProvider})\n`;
658
+ response += `✅ Apoc API key \\(${escMd(apocProvider)}\\)\n`;
639
659
  }
640
660
  else {
641
- response += `❌ Apoc API key missing (${apocProvider})\n`;
661
+ response += `❌ Apoc API key missing \\(${escMd(apocProvider)}\\)\n`;
642
662
  }
643
663
  }
644
664
  // Telegram token
@@ -682,31 +702,31 @@ How can I assist you today?`;
682
702
  const totalAudioSeconds = groupedStats.reduce((sum, s) => sum + (s.totalAudioSeconds || 0), 0);
683
703
  const totalCost = stats.totalEstimatedCostUsd;
684
704
  let response = '*Token Usage Statistics*\n\n';
685
- response += `Input Tokens: ${stats.totalInputTokens.toLocaleString()}\n`;
686
- response += `Output Tokens: ${stats.totalOutputTokens.toLocaleString()}\n`;
687
- 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`;
688
708
  if (totalAudioSeconds > 0) {
689
- response += `Audio Processed: ${totalAudioSeconds.toFixed(1)}s\n`;
709
+ response += `Audio Processed: ${escMd(totalAudioSeconds.toFixed(1))}s\n`;
690
710
  }
691
711
  if (totalCost != null) {
692
- response += `Estimated Cost: $${totalCost.toFixed(4)}\n`;
712
+ response += `Estimated Cost: \\$${escMd(totalCost.toFixed(4))}\n`;
693
713
  }
694
714
  response += '\n';
695
715
  if (groupedStats.length > 0) {
696
716
  response += '*By Provider/Model:*\n';
697
717
  for (const stat of groupedStats) {
698
- response += `\n*${stat.provider}/${stat.model}*\n`;
699
- 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`;
700
720
  if (stat.totalAudioSeconds > 0) {
701
- response += ` Audio: ${stat.totalAudioSeconds.toFixed(1)}s\n`;
721
+ response += ` Audio: ${escMd(stat.totalAudioSeconds.toFixed(1))}s\n`;
702
722
  }
703
723
  if (stat.estimatedCostUsd != null) {
704
- response += ` Cost: $${stat.estimatedCostUsd.toFixed(4)}\n`;
724
+ response += ` Cost: \\$${escMd(stat.estimatedCostUsd.toFixed(4))}\n`;
705
725
  }
706
726
  }
707
727
  }
708
728
  else {
709
- response += 'No detailed usage statistics available.';
729
+ response += 'No detailed usage statistics available\\.';
710
730
  }
711
731
  await ctx.reply(response, { parse_mode: 'MarkdownV2' });
712
732
  history.close();
@@ -725,7 +745,7 @@ How can I assist you today?`;
725
745
  let response = await this.oracle.chat(prompt);
726
746
  if (response) {
727
747
  try {
728
- await ctx.reply(response, { parse_mode: 'MarkdownV2' });
748
+ await ctx.reply(toMd(response).text, { parse_mode: 'MarkdownV2' });
729
749
  }
730
750
  catch {
731
751
  await ctx.reply(response);
@@ -734,62 +754,57 @@ How can I assist you today?`;
734
754
  // await ctx.reply(`Command not recognized. Type /help to see available commands.`);
735
755
  }
736
756
  async handleHelpCommand(ctx, user) {
737
- const helpMessage = `
738
- *Available Commands:*
739
-
740
- ${this.HELP_MESSAGE}
741
-
742
- How can I assist you today?`;
757
+ const helpMessage = `*Available Commands:*\n\n${this.HELP_MESSAGE}\n\nHow can I assist you today\\?`;
743
758
  await ctx.reply(helpMessage, { parse_mode: 'MarkdownV2' });
744
759
  }
745
760
  async handleZaionCommand(ctx, user) {
746
761
  const config = this.config.get();
747
762
  let response = '*System Configuration*\n\n';
748
763
  response += `*Agent:*\n`;
749
- response += `- Name: ${config.agent.name}\n`;
750
- response += `- Personality: ${config.agent.personality}\n\n`;
751
- response += `*Oracle (LLM):*\n`;
752
- response += `- Provider: ${config.llm.provider}\n`;
753
- response += `- Model: ${config.llm.model}\n`;
754
- response += `- Temperature: ${config.llm.temperature}\n`;
755
- response += `- Context Window: ${config.llm.context_window || 100}\n\n`;
764
+ response += `\\- Name: ${escMd(config.agent.name)}\n`;
765
+ response += `\\- Personality: ${escMd(config.agent.personality)}\n\n`;
766
+ response += `*Oracle \\(LLM\\):*\n`;
767
+ response += `\\- Provider: ${escMd(config.llm.provider)}\n`;
768
+ response += `\\- Model: ${escMd(config.llm.model)}\n`;
769
+ response += `\\- Temperature: ${escMd(config.llm.temperature)}\n`;
770
+ response += `\\- Context Window: ${escMd(config.llm.context_window || 100)}\n\n`;
756
771
  // Sati config (falls back to llm if not set)
757
772
  const sati = config.sati;
758
- response += `*Sati (Memory):*\n`;
773
+ response += `*Sati \\(Memory\\):*\n`;
759
774
  if (sati?.provider) {
760
- response += `- Provider: ${sati.provider}\n`;
761
- response += `- Model: ${sati.model || config.llm.model}\n`;
762
- response += `- Temperature: ${sati.temperature ?? config.llm.temperature}\n`;
763
- response += `- Memory Limit: ${sati.memory_limit ?? 1000}\n`;
775
+ response += `\\- Provider: ${escMd(sati.provider)}\n`;
776
+ response += `\\- Model: ${escMd(sati.model || config.llm.model)}\n`;
777
+ response += `\\- Temperature: ${escMd(sati.temperature ?? config.llm.temperature)}\n`;
778
+ response += `\\- Memory Limit: ${escMd(sati.memory_limit ?? 1000)}\n`;
764
779
  }
765
780
  else {
766
- response += `- Inherits Oracle config\n`;
781
+ response += `\\- Inherits Oracle config\n`;
767
782
  }
768
783
  response += '\n';
769
784
  // Apoc config (falls back to llm if not set)
770
785
  const apoc = config.apoc;
771
- response += `*Apoc (DevTools):*\n`;
786
+ response += `*Apoc \\(DevTools\\):*\n`;
772
787
  if (apoc?.provider) {
773
- response += `- Provider: ${apoc.provider}\n`;
774
- response += `- Model: ${apoc.model || config.llm.model}\n`;
775
- response += `- Temperature: ${apoc.temperature ?? 0.2}\n`;
788
+ response += `\\- Provider: ${escMd(apoc.provider)}\n`;
789
+ response += `\\- Model: ${escMd(apoc.model || config.llm.model)}\n`;
790
+ response += `\\- Temperature: ${escMd(apoc.temperature ?? 0.2)}\n`;
776
791
  if (apoc.working_dir)
777
- response += `- Working Dir: ${apoc.working_dir}\n`;
778
- response += `- Timeout: ${apoc.timeout_ms ?? 30000}ms\n`;
792
+ response += `\\- Working Dir: ${escMd(apoc.working_dir)}\n`;
793
+ response += `\\- Timeout: ${escMd(apoc.timeout_ms ?? 30000)}ms\n`;
779
794
  }
780
795
  else {
781
- response += `- Inherits Oracle config\n`;
796
+ response += `\\- Inherits Oracle config\n`;
782
797
  }
783
798
  response += '\n';
784
799
  response += `*Channels:*\n`;
785
- response += `- Telegram Enabled: ${config.channels.telegram.enabled}\n`;
786
- response += `- Discord Enabled: ${config.channels.discord.enabled}\n\n`;
800
+ response += `\\- Telegram Enabled: ${escMd(config.channels.telegram.enabled)}\n`;
801
+ response += `\\- Discord Enabled: ${escMd(config.channels.discord.enabled)}\n\n`;
787
802
  response += `*UI:*\n`;
788
- response += `- Enabled: ${config.ui.enabled}\n`;
789
- response += `- Port: ${config.ui.port}\n\n`;
803
+ response += `\\- Enabled: ${escMd(config.ui.enabled)}\n`;
804
+ response += `\\- Port: ${escMd(config.ui.port)}\n\n`;
790
805
  response += `*Audio:*\n`;
791
- response += `- Enabled: ${config.audio.enabled}\n`;
792
- response += `- Max Duration: ${config.audio.maxDurationSeconds}s\n`;
806
+ response += `\\- Enabled: ${escMd(config.audio.enabled)}\n`;
807
+ response += `\\- Max Duration: ${escMd(config.audio.maxDurationSeconds)}s\n`;
793
808
  await ctx.reply(response, { parse_mode: 'MarkdownV2' });
794
809
  }
795
810
  async handleSatiCommand(ctx, user, args) {
@@ -814,11 +829,14 @@ How can I assist you today?`;
814
829
  if (limit !== null) {
815
830
  selectedMemories = memories.slice(0, Math.min(limit, memories.length));
816
831
  }
817
- let response = `*${selectedMemories.length} SATI Memories${limit !== null ? ` (Showing first ${selectedMemories.length})` : ''}:*\n\n`;
832
+ const countLabel = limit !== null
833
+ ? `${escMd(selectedMemories.length)} SATI Memories \\(Showing first ${escMd(selectedMemories.length)}\\)`
834
+ : `${escMd(selectedMemories.length)} SATI Memories`;
835
+ let response = `*${countLabel}:*\n\n`;
818
836
  for (const memory of selectedMemories) {
819
837
  // Limitar o tamanho do resumo para evitar mensagens muito longas
820
838
  const truncatedSummary = memory.summary.length > 200 ? memory.summary.substring(0, 200) + '...' : memory.summary;
821
- response += `*${memory.category} (${memory.importance}):* ${truncatedSummary}\n\n`;
839
+ response += `*${escMd(memory.category)} \\(${escMd(memory.importance)}\\):* ${escMd(truncatedSummary)}\n\n`;
822
840
  }
823
841
  await ctx.reply(response, { parse_mode: 'MarkdownV2' });
824
842
  }
@@ -914,11 +932,11 @@ How can I assist you today?`;
914
932
  Construtor.probe(),
915
933
  ]);
916
934
  if (servers.length === 0) {
917
- await ctx.reply('*No MCP Servers Configured*\n\nThere are currently no MCP servers configured in the system.', { parse_mode: 'MarkdownV2' });
935
+ await ctx.reply('*No MCP Servers Configured*\n\nThere are currently no MCP servers configured in the system\\.', { parse_mode: 'MarkdownV2' });
918
936
  return;
919
937
  }
920
938
  const probeMap = new Map(probeResults.map(r => [r.name, r]));
921
- let response = `*MCP Servers (${servers.length})*\n\n`;
939
+ let response = `*MCP Servers \\(${escMd(servers.length)}\\)*\n\n`;
922
940
  const keyboard = [];
923
941
  servers.forEach((server, index) => {
924
942
  const enabledStatus = server.enabled ? '✅ Enabled' : '❌ Disabled';
@@ -926,25 +944,25 @@ How can I assist you today?`;
926
944
  const probe = probeMap.get(server.name);
927
945
  const connectionStatus = probe
928
946
  ? probe.ok
929
- ? `🟢 Connected (${probe.toolCount} tools)`
947
+ ? `🟢 Connected \\(${escMd(probe.toolCount)} tools\\)`
930
948
  : `🔴 Failed`
931
949
  : '⚪ Unknown';
932
- response += `*${index + 1}. ${server.name}*\n`;
950
+ response += `*${escMd(index + 1)}\\. ${escMd(server.name)}*\n`;
933
951
  response += `Status: ${enabledStatus}\n`;
934
952
  response += `Connection: ${connectionStatus}\n`;
935
- response += `Transport: ${transport}\n`;
953
+ response += `Transport: ${escMd(transport)}\n`;
936
954
  if (server.config.transport === 'stdio') {
937
- response += `Command: \`${server.config.command}\`\n`;
955
+ response += `Command: \`${escMd(server.config.command)}\`\n`;
938
956
  if (server.config.args && server.config.args.length > 0) {
939
- response += `Args: \`${server.config.args.join(' ')}\`\n`;
957
+ response += `Args: \`${escMd(server.config.args.join(' '))}\`\n`;
940
958
  }
941
959
  }
942
960
  else if (server.config.transport === 'http') {
943
- response += `URL: \`${server.config.url}\`\n`;
961
+ response += `URL: \`${escMd(server.config.url)}\`\n`;
944
962
  }
945
963
  if (probe && !probe.ok && probe.error) {
946
964
  const shortError = probe.error.length > 80 ? probe.error.slice(0, 80) + '…' : probe.error;
947
- response += `Error: \`${shortError}\`\n`;
965
+ response += `Error: \`${escMd(shortError)}\`\n`;
948
966
  }
949
967
  response += '\n';
950
968
  if (server.enabled) {
@@ -961,7 +979,7 @@ How can I assist you today?`;
961
979
  }
962
980
  catch (error) {
963
981
  this.display.log('Error listing MCP servers: ' + (error instanceof Error ? error.message : String(error)), { source: 'Telegram', level: 'error' });
964
- await ctx.reply('An error occurred while retrieving the list of MCP servers. Please check the logs for more details.', { parse_mode: 'MarkdownV2' });
982
+ await ctx.reply('An error occurred while retrieving the list of MCP servers\\. Please check the logs for more details\\.', { parse_mode: 'MarkdownV2' });
965
983
  }
966
984
  }
967
985
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morpheus-cli",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
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"