morpheus-cli 0.4.11 → 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 +94 -71
- package/package.json +1 -1
|
@@ -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
|
|
50
|
-
/status
|
|
51
|
-
/doctor
|
|
52
|
-
/stats
|
|
53
|
-
/help
|
|
54
|
-
/zaion
|
|
55
|
-
/sati <qnt>
|
|
56
|
-
/newsession
|
|
57
|
-
/sessions
|
|
58
|
-
/restart
|
|
59
|
-
/mcpreload
|
|
60
|
-
/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`;
|
|
61
76
|
constructor(oracle) {
|
|
62
77
|
this.oracle = oracle;
|
|
63
78
|
}
|
|
@@ -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
|
-
//
|
|
406
|
-
const
|
|
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
|
-
|
|
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
|
|
415
|
-
|
|
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
|
}
|
|
@@ -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 +=
|
|
526
|
-
response +=
|
|
527
|
-
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`;
|
|
528
548
|
// Adicionar botão inline para alternar para esta sessão
|
|
529
549
|
const sessionButtons = [];
|
|
530
550
|
if (session.status !== 'active') {
|
|
@@ -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:
|
|
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:
|
|
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);
|
|
@@ -746,50 +766,50 @@ How can I assist you today?`;
|
|
|
746
766
|
const config = this.config.get();
|
|
747
767
|
let response = '*System Configuration*\n\n';
|
|
748
768
|
response += `*Agent:*\n`;
|
|
749
|
-
response +=
|
|
750
|
-
response +=
|
|
751
|
-
response += `*Oracle (LLM):*\n`;
|
|
752
|
-
response +=
|
|
753
|
-
response +=
|
|
754
|
-
response +=
|
|
755
|
-
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`;
|
|
756
776
|
// Sati config (falls back to llm if not set)
|
|
757
777
|
const sati = config.sati;
|
|
758
|
-
response += `*Sati (Memory):*\n`;
|
|
778
|
+
response += `*Sati \\(Memory\\):*\n`;
|
|
759
779
|
if (sati?.provider) {
|
|
760
|
-
response +=
|
|
761
|
-
response +=
|
|
762
|
-
response +=
|
|
763
|
-
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`;
|
|
764
784
|
}
|
|
765
785
|
else {
|
|
766
|
-
response +=
|
|
786
|
+
response += `\\- Inherits Oracle config\n`;
|
|
767
787
|
}
|
|
768
788
|
response += '\n';
|
|
769
789
|
// Apoc config (falls back to llm if not set)
|
|
770
790
|
const apoc = config.apoc;
|
|
771
|
-
response += `*Apoc (DevTools):*\n`;
|
|
791
|
+
response += `*Apoc \\(DevTools\\):*\n`;
|
|
772
792
|
if (apoc?.provider) {
|
|
773
|
-
response +=
|
|
774
|
-
response +=
|
|
775
|
-
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`;
|
|
776
796
|
if (apoc.working_dir)
|
|
777
|
-
response +=
|
|
778
|
-
response +=
|
|
797
|
+
response += `\\- Working Dir: ${escMd(apoc.working_dir)}\n`;
|
|
798
|
+
response += `\\- Timeout: ${escMd(apoc.timeout_ms ?? 30000)}ms\n`;
|
|
779
799
|
}
|
|
780
800
|
else {
|
|
781
|
-
response +=
|
|
801
|
+
response += `\\- Inherits Oracle config\n`;
|
|
782
802
|
}
|
|
783
803
|
response += '\n';
|
|
784
804
|
response += `*Channels:*\n`;
|
|
785
|
-
response +=
|
|
786
|
-
response +=
|
|
805
|
+
response += `\\- Telegram Enabled: ${escMd(config.channels.telegram.enabled)}\n`;
|
|
806
|
+
response += `\\- Discord Enabled: ${escMd(config.channels.discord.enabled)}\n\n`;
|
|
787
807
|
response += `*UI:*\n`;
|
|
788
|
-
response +=
|
|
789
|
-
response +=
|
|
808
|
+
response += `\\- Enabled: ${escMd(config.ui.enabled)}\n`;
|
|
809
|
+
response += `\\- Port: ${escMd(config.ui.port)}\n\n`;
|
|
790
810
|
response += `*Audio:*\n`;
|
|
791
|
-
response +=
|
|
792
|
-
response +=
|
|
811
|
+
response += `\\- Enabled: ${escMd(config.audio.enabled)}\n`;
|
|
812
|
+
response += `\\- Max Duration: ${escMd(config.audio.maxDurationSeconds)}s\n`;
|
|
793
813
|
await ctx.reply(response, { parse_mode: 'MarkdownV2' });
|
|
794
814
|
}
|
|
795
815
|
async handleSatiCommand(ctx, user, args) {
|
|
@@ -814,11 +834,14 @@ How can I assist you today?`;
|
|
|
814
834
|
if (limit !== null) {
|
|
815
835
|
selectedMemories = memories.slice(0, Math.min(limit, memories.length));
|
|
816
836
|
}
|
|
817
|
-
|
|
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`;
|
|
818
841
|
for (const memory of selectedMemories) {
|
|
819
842
|
// Limitar o tamanho do resumo para evitar mensagens muito longas
|
|
820
843
|
const truncatedSummary = memory.summary.length > 200 ? memory.summary.substring(0, 200) + '...' : memory.summary;
|
|
821
|
-
response += `*${memory.category} (${memory.importance}):* ${truncatedSummary}\n\n`;
|
|
844
|
+
response += `*${escMd(memory.category)} \\(${escMd(memory.importance)}\\):* ${escMd(truncatedSummary)}\n\n`;
|
|
822
845
|
}
|
|
823
846
|
await ctx.reply(response, { parse_mode: 'MarkdownV2' });
|
|
824
847
|
}
|
|
@@ -918,7 +941,7 @@ How can I assist you today?`;
|
|
|
918
941
|
return;
|
|
919
942
|
}
|
|
920
943
|
const probeMap = new Map(probeResults.map(r => [r.name, r]));
|
|
921
|
-
let response = `*MCP Servers (${servers.length})*\n\n`;
|
|
944
|
+
let response = `*MCP Servers \\(${escMd(servers.length)}\\)*\n\n`;
|
|
922
945
|
const keyboard = [];
|
|
923
946
|
servers.forEach((server, index) => {
|
|
924
947
|
const enabledStatus = server.enabled ? '✅ Enabled' : '❌ Disabled';
|
|
@@ -926,25 +949,25 @@ How can I assist you today?`;
|
|
|
926
949
|
const probe = probeMap.get(server.name);
|
|
927
950
|
const connectionStatus = probe
|
|
928
951
|
? probe.ok
|
|
929
|
-
? `🟢 Connected (${probe.toolCount} tools)`
|
|
952
|
+
? `🟢 Connected \\(${escMd(probe.toolCount)} tools\\)`
|
|
930
953
|
: `🔴 Failed`
|
|
931
954
|
: '⚪ Unknown';
|
|
932
|
-
response += `*${index + 1}
|
|
955
|
+
response += `*${escMd(index + 1)}\\. ${escMd(server.name)}*\n`;
|
|
933
956
|
response += `Status: ${enabledStatus}\n`;
|
|
934
957
|
response += `Connection: ${connectionStatus}\n`;
|
|
935
|
-
response += `Transport: ${transport}\n`;
|
|
958
|
+
response += `Transport: ${escMd(transport)}\n`;
|
|
936
959
|
if (server.config.transport === 'stdio') {
|
|
937
|
-
response += `Command: \`${server.config.command}\`\n`;
|
|
960
|
+
response += `Command: \`${escMd(server.config.command)}\`\n`;
|
|
938
961
|
if (server.config.args && server.config.args.length > 0) {
|
|
939
|
-
response += `Args: \`${server.config.args.join(' ')}\`\n`;
|
|
962
|
+
response += `Args: \`${escMd(server.config.args.join(' '))}\`\n`;
|
|
940
963
|
}
|
|
941
964
|
}
|
|
942
965
|
else if (server.config.transport === 'http') {
|
|
943
|
-
response += `URL: \`${server.config.url}\`\n`;
|
|
966
|
+
response += `URL: \`${escMd(server.config.url)}\`\n`;
|
|
944
967
|
}
|
|
945
968
|
if (probe && !probe.ok && probe.error) {
|
|
946
969
|
const shortError = probe.error.length > 80 ? probe.error.slice(0, 80) + '…' : probe.error;
|
|
947
|
-
response += `Error: \`${shortError}\`\n`;
|
|
970
|
+
response += `Error: \`${escMd(shortError)}\`\n`;
|
|
948
971
|
}
|
|
949
972
|
response += '\n';
|
|
950
973
|
if (server.enabled) {
|
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"
|