morpheus-cli 0.2.7 → 0.3.1
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 +51 -8
- package/dist/channels/telegram.js +229 -22
- package/dist/cli/commands/doctor.js +11 -11
- package/dist/cli/commands/init.js +34 -34
- package/dist/cli/commands/restart.js +1 -1
- package/dist/cli/commands/session.js +79 -0
- package/dist/cli/commands/start.js +4 -1
- package/dist/cli/index.js +3 -1
- package/dist/config/manager.js +16 -15
- package/dist/config/schemas.js +2 -1
- package/dist/http/__tests__/config_api.test.js +6 -1
- package/dist/http/api.js +160 -3
- package/dist/http/server.js +4 -2
- package/dist/runtime/memory/backfill-embeddings.js +54 -0
- package/dist/runtime/memory/embedding.service.js +21 -0
- package/dist/runtime/memory/sati/index.js +5 -5
- package/dist/runtime/memory/sati/repository.js +323 -116
- package/dist/runtime/memory/sati/service.js +58 -33
- package/dist/runtime/memory/sati/system-prompts.js +19 -8
- package/dist/runtime/memory/session-embedding-worker.js +94 -0
- package/dist/runtime/memory/sqlite-vec.js +6 -0
- package/dist/runtime/memory/sqlite.js +432 -3
- package/dist/runtime/migration.js +40 -0
- package/dist/runtime/oracle.js +69 -1
- package/dist/runtime/session-embedding-scheduler.js +21 -0
- package/dist/types/config.js +8 -0
- package/dist/ui/assets/index-DqzvLXXS.js +109 -0
- package/dist/ui/assets/index-f1sqiqOo.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +11 -4
- package/dist/ui/assets/index-Dx1lwaMu.js +0 -96
- package/dist/ui/assets/index-QHZ08tDL.css +0 -1
package/README.md
CHANGED
|
@@ -52,6 +52,10 @@ morpheus restart
|
|
|
52
52
|
|
|
53
53
|
# Diagnose issues
|
|
54
54
|
morpheus doctor
|
|
55
|
+
|
|
56
|
+
# Manage sessions
|
|
57
|
+
morpheus session new # Archive current and start new
|
|
58
|
+
morpheus session status # Check current session info
|
|
55
59
|
```
|
|
56
60
|
|
|
57
61
|
## Troubleshooting
|
|
@@ -93,6 +97,14 @@ Morpheus is built with **Node.js** and **TypeScript**, using **LangChain** as th
|
|
|
93
97
|
### 🖥️ Web Dashboard
|
|
94
98
|
Local React-based UI to manage recordings, chat history, and system status across your agent instances.
|
|
95
99
|
|
|
100
|
+
**New: Interactive Web Chat**
|
|
101
|
+
- Full-featured chat interface accessible from the browser
|
|
102
|
+
- Session management: create, archive, delete, and rename sessions
|
|
103
|
+
- Cross-channel visibility: view and interact with sessions started on any channel (Telegram, Web, etc.)
|
|
104
|
+
- Real-time messaging with the Oracle agent
|
|
105
|
+
- Responsive design with collapsible sidebar
|
|
106
|
+
- Full support for Light and Dark (Matrix) themes
|
|
107
|
+
|
|
96
108
|
#### 🔒 UI Authentication
|
|
97
109
|
To protect your Web UI, use the `THE_ARCHITECT_PASS` environment variable. This ensures only authorized users can access the dashboard and API.
|
|
98
110
|
|
|
@@ -121,13 +133,15 @@ The system also supports generic environment variables that apply to all provide
|
|
|
121
133
|
| `MORPHEUS_LLM_MAX_TOKENS` | Maximum tokens for LLM | llm.max_tokens |
|
|
122
134
|
| `MORPHEUS_LLM_CONTEXT_WINDOW` | Context window size for LLM | llm.context_window |
|
|
123
135
|
| `MORPHEUS_LLM_API_KEY` | Generic API key for LLM (lower precedence than provider-specific keys) | llm.api_key |
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
136
|
+
| `MORPHEUS_SATI_PROVIDER` | Sati provider to use | santi.provider |
|
|
137
|
+
| `MORPHEUS_SATI_MODEL` | Model name for Sati | santi.model |
|
|
138
|
+
| `MORPHEUS_SATI_TEMPERATURE` | Temperature setting for Sati | santi.temperature |
|
|
139
|
+
| `MORPHEUS_SATI_MAX_TOKENS` | Maximum tokens for Sati | santi.max_tokens |
|
|
140
|
+
| `MORPHEUS_SATI_CONTEXT_WINDOW` | Context window size for Sati | santi.context_window |
|
|
141
|
+
| `MORPHEUS_SATI_API_KEY` | Generic API key for Sati (lower precedence than provider-specific keys) | santi.api_key |
|
|
142
|
+
| `MORPHEUS_SATI_MEMORY_LIMIT` | Memory retrieval limit for Sati | santi.memory_limit |
|
|
143
|
+
| `MORPHEUS_SATI_MEMORY_LIMIT` | Memory retrieval limit for Sati | santi.memory_limit |
|
|
144
|
+
| `MORPHEUS_SATI_ENABLED_ARCHIVED_SESSIONS`| Enable/disable retrieval of archived sessions in Sati | santi.enableArchivedSessions |
|
|
131
145
|
| `MORPHEUS_AUDIO_MODEL` | Model name for audio processing | audio.model |
|
|
132
146
|
| `MORPHEUS_AUDIO_ENABLED` | Enable/disable audio processing | audio.enabled |
|
|
133
147
|
| `MORPHEUS_AUDIO_API_KEY` | Generic API key for audio (lower precedence than provider-specific keys) | audio.apiKey |
|
|
@@ -209,6 +223,8 @@ The Morpheus Telegram bot supports several commands for interacting with the age
|
|
|
209
223
|
- `/help` - Show available commands
|
|
210
224
|
- `/zaion` - Show system configurations
|
|
211
225
|
- `/sati <qnt>` - Show specific memories
|
|
226
|
+
- `/newsession` - Archive current session and start fresh
|
|
227
|
+
- `/sessions` - List all sessions with options to switch, archive, or delete
|
|
212
228
|
- `/restart` - Restart the Morpheus agent
|
|
213
229
|
- `/mcp` or `/mcps` - List registered MCP servers
|
|
214
230
|
|
|
@@ -364,6 +380,33 @@ Get the current status of the Morpheus agent.
|
|
|
364
380
|
}
|
|
365
381
|
```
|
|
366
382
|
|
|
383
|
+
### Session Endpoints
|
|
384
|
+
|
|
385
|
+
#### POST `/api/session/reset`
|
|
386
|
+
Archive the current session and start a new one.
|
|
387
|
+
|
|
388
|
+
* **Authentication:** Requires `Authorization` header with the password set in `THE_ARCHITECT_PASS`.
|
|
389
|
+
* **Response:**
|
|
390
|
+
```json
|
|
391
|
+
{
|
|
392
|
+
"success": true,
|
|
393
|
+
"message": "New session started"
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### POST `/api/session/status`
|
|
398
|
+
Get the status of the current session.
|
|
399
|
+
|
|
400
|
+
* **Authentication:** Requires `Authorization` header with the password set in `THE_ARCHITECT_PASS`.
|
|
401
|
+
* **Response:**
|
|
402
|
+
```json
|
|
403
|
+
{
|
|
404
|
+
"id": "uuid-...",
|
|
405
|
+
"messageCount": 42,
|
|
406
|
+
"embedding_status": "pending"
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
367
410
|
### Configuration Endpoints
|
|
368
411
|
|
|
369
412
|
#### GET `/api/config`
|
|
@@ -848,7 +891,7 @@ version: '3.8'
|
|
|
848
891
|
|
|
849
892
|
services:
|
|
850
893
|
morpheus:
|
|
851
|
-
image:
|
|
894
|
+
image: marcosnunesmbs/morpheus:latest
|
|
852
895
|
container_name: morpheus-agent
|
|
853
896
|
ports:
|
|
854
897
|
- "3333:3333"
|
|
@@ -19,6 +19,18 @@ export class TelegramAdapter {
|
|
|
19
19
|
config = ConfigManager.getInstance();
|
|
20
20
|
oracle;
|
|
21
21
|
telephonist = new Telephonist();
|
|
22
|
+
history = new SQLiteChatMessageHistory({ sessionId: '' });
|
|
23
|
+
HELP_MESSAGE = `/start - Show this welcome message and available commands
|
|
24
|
+
/status - Check the status of the Morpheus agent
|
|
25
|
+
/doctor - Diagnose environment and configuration issues
|
|
26
|
+
/stats - Show token usage statistics
|
|
27
|
+
/help - Show available commands
|
|
28
|
+
/zaion - Show system configurations
|
|
29
|
+
/sati <qnt> - Show specific memories
|
|
30
|
+
/newsession - Archive current session and start fresh
|
|
31
|
+
/sessions - List all sessions with titles and switch between them
|
|
32
|
+
/restart - Restart the Morpheus agent
|
|
33
|
+
/mcp or /mcps - List registered MCP servers`;
|
|
22
34
|
constructor(oracle) {
|
|
23
35
|
this.oracle = oracle;
|
|
24
36
|
}
|
|
@@ -139,6 +151,120 @@ export class TelegramAdapter {
|
|
|
139
151
|
}
|
|
140
152
|
}
|
|
141
153
|
});
|
|
154
|
+
this.bot.action('confirm_new_session', async (ctx) => {
|
|
155
|
+
await this.handleApproveNewSessionCommand(ctx, ctx.from.username || ctx.from.first_name);
|
|
156
|
+
if (ctx.updateType === 'callback_query') {
|
|
157
|
+
ctx.answerCbQuery();
|
|
158
|
+
ctx.deleteMessage().catch(() => { });
|
|
159
|
+
}
|
|
160
|
+
ctx.reply("New session created.");
|
|
161
|
+
});
|
|
162
|
+
this.bot.action('cancel_new_session', async (ctx) => {
|
|
163
|
+
if (ctx.updateType === 'callback_query') {
|
|
164
|
+
ctx.answerCbQuery();
|
|
165
|
+
ctx.deleteMessage().catch(() => { });
|
|
166
|
+
}
|
|
167
|
+
ctx.reply("New session cancelled.");
|
|
168
|
+
});
|
|
169
|
+
this.bot.action(/^switch_session_/, async (ctx) => {
|
|
170
|
+
const callbackQuery = ctx.callbackQuery;
|
|
171
|
+
const data = callbackQuery && 'data' in callbackQuery ? callbackQuery.data : undefined;
|
|
172
|
+
const sessionId = typeof data === 'string' ? data.replace('switch_session_', '') : '';
|
|
173
|
+
if (!sessionId || sessionId === '') {
|
|
174
|
+
await ctx.answerCbQuery('Invalid session ID');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
// Obter a sessão atual antes de alternar
|
|
179
|
+
const history = new SQLiteChatMessageHistory({ sessionId: "" });
|
|
180
|
+
// Alternar para a nova sessão
|
|
181
|
+
await history.switchSession(sessionId);
|
|
182
|
+
await ctx.answerCbQuery();
|
|
183
|
+
// Remover a mensagem anterior e enviar confirmação
|
|
184
|
+
if (ctx.updateType === 'callback_query') {
|
|
185
|
+
ctx.deleteMessage().catch(() => { });
|
|
186
|
+
}
|
|
187
|
+
ctx.reply(`✅ Switched to session ID: ${sessionId}`);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
await ctx.answerCbQuery(`Error switching session: ${error.message}`, { show_alert: true });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
// --- Archive Flow ---
|
|
194
|
+
this.bot.action(/^ask_archive_session_/, async (ctx) => {
|
|
195
|
+
const data = ctx.callbackQuery.data;
|
|
196
|
+
const sessionId = data.replace('ask_archive_session_', '');
|
|
197
|
+
// Fetch session title for better UX (optional, but nice) - for now just use ID
|
|
198
|
+
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.`, {
|
|
199
|
+
parse_mode: 'Markdown',
|
|
200
|
+
reply_markup: {
|
|
201
|
+
inline_keyboard: [
|
|
202
|
+
[
|
|
203
|
+
{ text: '✅ Yes, Archive', callback_data: `confirm_archive_session_${sessionId}` },
|
|
204
|
+
{ text: '❌ Cancel', callback_data: 'cancel_session_action' }
|
|
205
|
+
]
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
await ctx.answerCbQuery();
|
|
210
|
+
});
|
|
211
|
+
this.bot.action(/^confirm_archive_session_/, async (ctx) => {
|
|
212
|
+
const data = ctx.callbackQuery.data;
|
|
213
|
+
const sessionId = data.replace('confirm_archive_session_', '');
|
|
214
|
+
try {
|
|
215
|
+
const history = new SQLiteChatMessageHistory({ sessionId: "" });
|
|
216
|
+
await history.archiveSession(sessionId);
|
|
217
|
+
await ctx.answerCbQuery('Session archived successfully');
|
|
218
|
+
if (ctx.updateType === 'callback_query') {
|
|
219
|
+
ctx.deleteMessage().catch(() => { });
|
|
220
|
+
}
|
|
221
|
+
await ctx.reply(`✅ Session \`${sessionId}\` has been archived and moved to long-term memory.`, { parse_mode: 'Markdown' });
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
await ctx.answerCbQuery(`Error archiving: ${error.message}`, { show_alert: true });
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
// --- Delete Flow ---
|
|
228
|
+
this.bot.action(/^ask_delete_session_/, async (ctx) => {
|
|
229
|
+
const data = ctx.callbackQuery.data;
|
|
230
|
+
const sessionId = data.replace('ask_delete_session_', '');
|
|
231
|
+
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.`, {
|
|
232
|
+
parse_mode: 'Markdown',
|
|
233
|
+
reply_markup: {
|
|
234
|
+
inline_keyboard: [
|
|
235
|
+
[
|
|
236
|
+
{ text: '🗑️ Yes, DELETE PERMANENTLY', callback_data: `confirm_delete_session_${sessionId}` },
|
|
237
|
+
{ text: '❌ Cancel', callback_data: 'cancel_session_action' }
|
|
238
|
+
]
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
await ctx.answerCbQuery();
|
|
243
|
+
});
|
|
244
|
+
this.bot.action(/^confirm_delete_session_/, async (ctx) => {
|
|
245
|
+
const data = ctx.callbackQuery.data;
|
|
246
|
+
const sessionId = data.replace('confirm_delete_session_', '');
|
|
247
|
+
try {
|
|
248
|
+
const history = new SQLiteChatMessageHistory({ sessionId: "" });
|
|
249
|
+
await history.deleteSession(sessionId);
|
|
250
|
+
await ctx.answerCbQuery('Session deleted successfully');
|
|
251
|
+
if (ctx.updateType === 'callback_query') {
|
|
252
|
+
ctx.deleteMessage().catch(() => { });
|
|
253
|
+
}
|
|
254
|
+
await ctx.reply(`🗑️ Session \`${sessionId}\` has been permanently deleted.`, { parse_mode: 'Markdown' });
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
await ctx.answerCbQuery(`Error deleting: ${error.message}`, { show_alert: true });
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
// --- Cancel Action ---
|
|
261
|
+
this.bot.action('cancel_session_action', async (ctx) => {
|
|
262
|
+
await ctx.answerCbQuery('Action cancelled');
|
|
263
|
+
if (ctx.updateType === 'callback_query') {
|
|
264
|
+
ctx.deleteMessage().catch(() => { });
|
|
265
|
+
}
|
|
266
|
+
await ctx.reply('Action cancelled.');
|
|
267
|
+
});
|
|
142
268
|
this.bot.launch().catch((err) => {
|
|
143
269
|
if (this.isConnected) {
|
|
144
270
|
this.display.log(`Telegram bot error: ${err}`, { source: 'Telegram', level: 'error' });
|
|
@@ -188,6 +314,11 @@ export class TelegramAdapter {
|
|
|
188
314
|
this.bot = null;
|
|
189
315
|
this.display.log(chalk.gray('Telegram disconnected.'), { source: 'Telegram' });
|
|
190
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
** =========================
|
|
319
|
+
** Commands Handlers
|
|
320
|
+
** =========================
|
|
321
|
+
*/
|
|
191
322
|
async handleSystemCommand(ctx, text, user) {
|
|
192
323
|
const command = text.split(' ')[0];
|
|
193
324
|
const args = text.split(' ').slice(1);
|
|
@@ -220,24 +351,97 @@ export class TelegramAdapter {
|
|
|
220
351
|
case '/mcps':
|
|
221
352
|
await this.handleMcpListCommand(ctx, user);
|
|
222
353
|
break;
|
|
354
|
+
case '/newsession':
|
|
355
|
+
case '/reset':
|
|
356
|
+
await this.handleNewSessionCommand(ctx, user);
|
|
357
|
+
break;
|
|
358
|
+
case '/sessionstatus':
|
|
359
|
+
case '/session':
|
|
360
|
+
case '/sessions':
|
|
361
|
+
await this.handleSessionStatusCommand(ctx, user);
|
|
362
|
+
break;
|
|
223
363
|
default:
|
|
224
364
|
await this.handleDefaultCommand(ctx, user, command);
|
|
225
365
|
}
|
|
226
366
|
}
|
|
367
|
+
async handleNewSessionCommand(ctx, user) {
|
|
368
|
+
try {
|
|
369
|
+
await ctx.reply("Are you ready to start a new session? Please confirm.", {
|
|
370
|
+
parse_mode: 'Markdown', reply_markup: {
|
|
371
|
+
inline_keyboard: [
|
|
372
|
+
[{ text: 'Yes, start new session', callback_data: 'confirm_new_session' }, { text: 'No, cancel', callback_data: 'cancel_new_session' }]
|
|
373
|
+
]
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
catch (e) {
|
|
378
|
+
await ctx.reply(`Error starting new session: ${e.message}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async handleApproveNewSessionCommand(ctx, user) {
|
|
382
|
+
try {
|
|
383
|
+
const history = new SQLiteChatMessageHistory({ sessionId: "" });
|
|
384
|
+
await history.createNewSession();
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
await ctx.reply(`Error creating new session: ${e.message}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
async handleSessionStatusCommand(ctx, user) {
|
|
391
|
+
try {
|
|
392
|
+
// Obter todas as sessões ativas e pausadas usando a nova função
|
|
393
|
+
const history = new SQLiteChatMessageHistory({ sessionId: "" });
|
|
394
|
+
const sessions = await history.listSessions();
|
|
395
|
+
if (sessions.length === 0) {
|
|
396
|
+
await ctx.reply('No active or paused sessions found.', { parse_mode: 'Markdown' });
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
let response = '*Sessions:*\n\n';
|
|
400
|
+
const keyboard = [];
|
|
401
|
+
for (const session of sessions) {
|
|
402
|
+
const title = session.title || 'Untitled Session';
|
|
403
|
+
const statusEmoji = session.status === 'active' ? '🟢' : '🟡';
|
|
404
|
+
response += `${statusEmoji} *${title}*\n`;
|
|
405
|
+
response += `- ID: ${session.id}\n`;
|
|
406
|
+
response += `- Status: ${session.status}\n`;
|
|
407
|
+
response += `- Started: ${new Date(session.started_at).toLocaleString()}\n\n`;
|
|
408
|
+
// Adicionar botão inline para alternar para esta sessão
|
|
409
|
+
const sessionButtons = [];
|
|
410
|
+
if (session.status !== 'active') {
|
|
411
|
+
sessionButtons.push({
|
|
412
|
+
text: `➡️ Switch`,
|
|
413
|
+
callback_data: `switch_session_${session.id}`
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
sessionButtons.push({
|
|
417
|
+
text: `📂 Archive`,
|
|
418
|
+
callback_data: `ask_archive_session_${session.id}`
|
|
419
|
+
});
|
|
420
|
+
sessionButtons.push({
|
|
421
|
+
text: `🗑️ Delete`,
|
|
422
|
+
callback_data: `ask_delete_session_${session.id}`
|
|
423
|
+
});
|
|
424
|
+
keyboard.push(sessionButtons);
|
|
425
|
+
}
|
|
426
|
+
await ctx.reply(response, {
|
|
427
|
+
parse_mode: 'Markdown',
|
|
428
|
+
reply_markup: {
|
|
429
|
+
inline_keyboard: keyboard
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
history.close();
|
|
433
|
+
}
|
|
434
|
+
catch (e) {
|
|
435
|
+
await ctx.reply(`Error retrieving session status: ${e.message}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
227
438
|
async handleStartCommand(ctx, user) {
|
|
228
439
|
const welcomeMessage = `
|
|
229
440
|
Hello, @${user}! I am ${this.config.get().agent.name}, ${this.config.get().agent.personality}.
|
|
230
441
|
|
|
231
442
|
I am your local AI operator/agent. Here are the commands you can use:
|
|
232
443
|
|
|
233
|
-
|
|
234
|
-
/status - Check the status of the Morpheus agent
|
|
235
|
-
/doctor - Diagnose environment and configuration issues
|
|
236
|
-
/stats - Show token usage statistics
|
|
237
|
-
/help - Show available commands
|
|
238
|
-
/zaion - Show system configurations
|
|
239
|
-
/sati <qnt> - Show specific memories
|
|
240
|
-
/restart - Restart the Morpheus agent
|
|
444
|
+
${this.HELP_MESSAGE}
|
|
241
445
|
|
|
242
446
|
How can I assist you today?`;
|
|
243
447
|
await ctx.reply(welcomeMessage);
|
|
@@ -321,7 +525,7 @@ How can I assist you today?`;
|
|
|
321
525
|
if (groupedStats.length > 0) {
|
|
322
526
|
response += '*Breakdown by Provider and Model:*\n';
|
|
323
527
|
for (const stat of groupedStats) {
|
|
324
|
-
response += `- ${stat.provider}/${stat.model}
|
|
528
|
+
response += `- ${stat.provider}/${stat.model}:\n ${stat.totalTokens} tokens\n(${stat.messageCount} messages)\n\n`;
|
|
325
529
|
}
|
|
326
530
|
}
|
|
327
531
|
else {
|
|
@@ -336,9 +540,10 @@ How can I assist you today?`;
|
|
|
336
540
|
}
|
|
337
541
|
}
|
|
338
542
|
async handleDefaultCommand(ctx, user, command) {
|
|
339
|
-
const prompt = `O usuário
|
|
543
|
+
const prompt = `O usuário enviou o comando: ${command},
|
|
340
544
|
Não entendemos o comando
|
|
341
|
-
temos os seguintes comandos disponíveis:
|
|
545
|
+
temos os seguintes comandos disponíveis:
|
|
546
|
+
${this.HELP_MESSAGE}
|
|
342
547
|
Identifique se ele talvez tenha errado o comando e pergunte se ele não quis executar outro comando.
|
|
343
548
|
Só faça isso agora.`;
|
|
344
549
|
let response = await this.oracle.chat(prompt);
|
|
@@ -351,17 +556,9 @@ How can I assist you today?`;
|
|
|
351
556
|
const helpMessage = `
|
|
352
557
|
*Available Commands:*
|
|
353
558
|
|
|
354
|
-
|
|
355
|
-
/status - Check the status of the Morpheus agent
|
|
356
|
-
/doctor - Diagnose environment and configuration issues
|
|
357
|
-
/stats - Show token usage statistics
|
|
358
|
-
/help - Show this help message
|
|
359
|
-
/zaion - Show system configurations
|
|
360
|
-
/sati <qnt> - Show specific memories
|
|
361
|
-
/restart - Restart the Morpheus agent
|
|
362
|
-
/mcp or /mcps - List registered MCP servers
|
|
559
|
+
${this.HELP_MESSAGE}
|
|
363
560
|
|
|
364
|
-
How can I assist you today
|
|
561
|
+
How can I assist you today?`;
|
|
365
562
|
await ctx.reply(helpMessage, { parse_mode: 'Markdown' });
|
|
366
563
|
}
|
|
367
564
|
async handleZaionCommand(ctx, user) {
|
|
@@ -423,6 +620,7 @@ How can I assist you today? `;
|
|
|
423
620
|
async handleRestartCommand(ctx, user) {
|
|
424
621
|
// Store the user ID who requested the restart
|
|
425
622
|
const userId = ctx.from.id;
|
|
623
|
+
const updateId = ctx.update.update_id;
|
|
426
624
|
// Save the user ID to a temporary file so the restarted process can notify them
|
|
427
625
|
const restartNotificationFile = path.join(os.tmpdir(), 'morpheus_restart_notification.json');
|
|
428
626
|
try {
|
|
@@ -433,6 +631,15 @@ How can I assist you today? `;
|
|
|
433
631
|
}
|
|
434
632
|
// Respond to the user first
|
|
435
633
|
await ctx.reply('🔄 Restart initiated. The Morpheus agent will restart shortly.');
|
|
634
|
+
// Acknowledge this update to Telegram by advancing the offset past it.
|
|
635
|
+
// Without this, Telegraf's in-memory offset is lost on process.exit() and the
|
|
636
|
+
// /restart message gets re-delivered on the next startup, causing an infinite loop.
|
|
637
|
+
try {
|
|
638
|
+
await ctx.telegram.callApi('getUpdates', { offset: updateId + 1, limit: 1, timeout: 0 });
|
|
639
|
+
}
|
|
640
|
+
catch (e) {
|
|
641
|
+
// Best-effort — proceed with restart regardless
|
|
642
|
+
}
|
|
436
643
|
// Schedule the restart after a short delay to ensure the response is sent
|
|
437
644
|
setTimeout(() => {
|
|
438
645
|
// Stop the bot to prevent processing more messages
|
|
@@ -452,7 +659,7 @@ How can I assist you today? `;
|
|
|
452
659
|
restartProcess.unref();
|
|
453
660
|
// Exit the current process
|
|
454
661
|
process.exit(0);
|
|
455
|
-
}, 500);
|
|
662
|
+
}, 500);
|
|
456
663
|
}
|
|
457
664
|
async checkAndSendRestartNotification() {
|
|
458
665
|
const restartNotificationFile = path.join(os.tmpdir(), 'morpheus_restart_notification.json');
|
|
@@ -49,7 +49,7 @@ export const doctorCommand = new Command('doctor')
|
|
|
49
49
|
}
|
|
50
50
|
// Check API keys availability for active providers
|
|
51
51
|
const llmProvider = config.llm?.provider;
|
|
52
|
-
const
|
|
52
|
+
const satiProvider = config.sati?.provider;
|
|
53
53
|
// Check LLM provider API key
|
|
54
54
|
if (llmProvider && llmProvider !== 'ollama') {
|
|
55
55
|
const hasLlmApiKey = config.llm?.api_key ||
|
|
@@ -65,18 +65,18 @@ export const doctorCommand = new Command('doctor')
|
|
|
65
65
|
allPassed = false;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
// Check
|
|
69
|
-
if (
|
|
70
|
-
const hasSantiApiKey = config.
|
|
71
|
-
(
|
|
72
|
-
(
|
|
73
|
-
(
|
|
74
|
-
(
|
|
68
|
+
// Check Sati provider API key
|
|
69
|
+
if (satiProvider && satiProvider !== 'ollama') {
|
|
70
|
+
const hasSantiApiKey = config.sati?.api_key ||
|
|
71
|
+
(satiProvider === 'openai' && process.env.OPENAI_API_KEY) ||
|
|
72
|
+
(satiProvider === 'anthropic' && process.env.ANTHROPIC_API_KEY) ||
|
|
73
|
+
(satiProvider === 'gemini' && process.env.GOOGLE_API_KEY) ||
|
|
74
|
+
(satiProvider === 'openrouter' && process.env.OPENROUTER_API_KEY);
|
|
75
75
|
if (hasSantiApiKey) {
|
|
76
|
-
console.log(chalk.green('✓') + `
|
|
76
|
+
console.log(chalk.green('✓') + ` Sati API key available for ${satiProvider}`);
|
|
77
77
|
}
|
|
78
78
|
else {
|
|
79
|
-
console.log(chalk.red('✗') + `
|
|
79
|
+
console.log(chalk.red('✗') + ` Sati API key missing for ${satiProvider}. Either set in config or define environment variable.`);
|
|
80
80
|
allPassed = false;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
@@ -144,7 +144,7 @@ export const doctorCommand = new Command('doctor')
|
|
|
144
144
|
}
|
|
145
145
|
// 5. Check Sati Memory DB
|
|
146
146
|
try {
|
|
147
|
-
const satiDbPath = path.join(PATHS.memory, '
|
|
147
|
+
const satiDbPath = path.join(PATHS.memory, 'sati-memory.db');
|
|
148
148
|
if (await fs.pathExists(satiDbPath)) {
|
|
149
149
|
console.log(chalk.green('✓') + ' Sati Memory: Database exists');
|
|
150
150
|
}
|
|
@@ -117,15 +117,15 @@ export const initCommand = new Command('init')
|
|
|
117
117
|
],
|
|
118
118
|
default: 'no',
|
|
119
119
|
});
|
|
120
|
-
let
|
|
121
|
-
let
|
|
122
|
-
let
|
|
120
|
+
let satiProvider = provider;
|
|
121
|
+
let satiModel = model;
|
|
122
|
+
let satiApiKey = apiKey;
|
|
123
123
|
// If using main settings and no new key provided, use existing if available
|
|
124
|
-
if (configureSati === 'no' && !
|
|
125
|
-
|
|
124
|
+
if (configureSati === 'no' && !satiApiKey && hasExistingKey) {
|
|
125
|
+
satiApiKey = currentConfig.llm.api_key;
|
|
126
126
|
}
|
|
127
127
|
if (configureSati === 'yes') {
|
|
128
|
-
|
|
128
|
+
satiProvider = await select({
|
|
129
129
|
message: 'Select Sati LLM Provider:',
|
|
130
130
|
choices: [
|
|
131
131
|
{ name: 'OpenAI', value: 'openai' },
|
|
@@ -134,10 +134,10 @@ export const initCommand = new Command('init')
|
|
|
134
134
|
{ name: 'Ollama', value: 'ollama' },
|
|
135
135
|
{ name: 'Google Gemini', value: 'gemini' },
|
|
136
136
|
],
|
|
137
|
-
default: currentConfig.
|
|
137
|
+
default: currentConfig.sati?.provider || provider,
|
|
138
138
|
});
|
|
139
139
|
let defaultSatiModel = 'gpt-3.5-turbo';
|
|
140
|
-
switch (
|
|
140
|
+
switch (satiProvider) {
|
|
141
141
|
case 'openai':
|
|
142
142
|
defaultSatiModel = 'gpt-4o';
|
|
143
143
|
break;
|
|
@@ -154,59 +154,59 @@ export const initCommand = new Command('init')
|
|
|
154
154
|
defaultSatiModel = 'gemini-pro';
|
|
155
155
|
break;
|
|
156
156
|
}
|
|
157
|
-
if (
|
|
158
|
-
defaultSatiModel = currentConfig.
|
|
157
|
+
if (satiProvider === currentConfig.sati?.provider) {
|
|
158
|
+
defaultSatiModel = currentConfig.sati?.model || defaultSatiModel;
|
|
159
159
|
}
|
|
160
|
-
|
|
160
|
+
satiModel = await input({
|
|
161
161
|
message: 'Enter Sati Model Name:',
|
|
162
162
|
default: defaultSatiModel,
|
|
163
163
|
});
|
|
164
|
-
const hasExistingSatiKey = !!currentConfig.
|
|
165
|
-
let
|
|
164
|
+
const hasExistingSatiKey = !!currentConfig.sati?.api_key;
|
|
165
|
+
let satiKeyMsg = hasExistingSatiKey
|
|
166
166
|
? 'Enter Sati API Key (leave empty to preserve existing, or if using env vars):'
|
|
167
167
|
: 'Enter Sati API Key (leave empty if using env vars):';
|
|
168
168
|
// Add info about environment variables to the message
|
|
169
|
-
if (
|
|
170
|
-
|
|
169
|
+
if (satiProvider === 'openai') {
|
|
170
|
+
satiKeyMsg = `${satiKeyMsg} (Env var: OPENAI_API_KEY)`;
|
|
171
171
|
}
|
|
172
|
-
else if (
|
|
173
|
-
|
|
172
|
+
else if (satiProvider === 'anthropic') {
|
|
173
|
+
satiKeyMsg = `${satiKeyMsg} (Env var: ANTHROPIC_API_KEY)`;
|
|
174
174
|
}
|
|
175
|
-
else if (
|
|
176
|
-
|
|
175
|
+
else if (satiProvider === 'gemini') {
|
|
176
|
+
satiKeyMsg = `${satiKeyMsg} (Env var: GOOGLE_API_KEY)`;
|
|
177
177
|
}
|
|
178
|
-
else if (
|
|
179
|
-
|
|
178
|
+
else if (satiProvider === 'openrouter') {
|
|
179
|
+
satiKeyMsg = `${satiKeyMsg} (Env var: OPENROUTER_API_KEY)`;
|
|
180
180
|
}
|
|
181
|
-
const keyInput = await password({ message:
|
|
181
|
+
const keyInput = await password({ message: satiKeyMsg });
|
|
182
182
|
if (keyInput) {
|
|
183
|
-
|
|
183
|
+
satiApiKey = keyInput;
|
|
184
184
|
}
|
|
185
185
|
else if (hasExistingSatiKey) {
|
|
186
|
-
|
|
186
|
+
satiApiKey = currentConfig.sati?.api_key;
|
|
187
187
|
}
|
|
188
188
|
else {
|
|
189
|
-
|
|
189
|
+
satiApiKey = undefined; // Ensure we don't accidentally carry over invalid state
|
|
190
190
|
}
|
|
191
191
|
// Base URL Configuration for Sati OpenRouter
|
|
192
|
-
if (
|
|
192
|
+
if (satiProvider === 'openrouter') {
|
|
193
193
|
const satiBaseUrl = await input({
|
|
194
194
|
message: 'Enter Sati OpenRouter Base URL:',
|
|
195
|
-
default: currentConfig.
|
|
195
|
+
default: currentConfig.sati?.base_url || 'https://openrouter.ai/api/v1',
|
|
196
196
|
});
|
|
197
|
-
await configManager.set('
|
|
197
|
+
await configManager.set('sati.base_url', satiBaseUrl);
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
const memoryLimit = await input({
|
|
201
201
|
message: 'Sati Memory Retrieval Limit (messages):',
|
|
202
|
-
default: currentConfig.
|
|
202
|
+
default: currentConfig.sati?.memory_limit?.toString() || '1000',
|
|
203
203
|
validate: (val) => !isNaN(Number(val)) && Number(val) > 0 || 'Must be a positive number'
|
|
204
204
|
});
|
|
205
|
-
await configManager.set('
|
|
206
|
-
await configManager.set('
|
|
207
|
-
await configManager.set('
|
|
208
|
-
if (
|
|
209
|
-
await configManager.set('
|
|
205
|
+
await configManager.set('sati.provider', satiProvider);
|
|
206
|
+
await configManager.set('sati.model', satiModel);
|
|
207
|
+
await configManager.set('sati.memory_limit', Number(memoryLimit));
|
|
208
|
+
if (satiApiKey) {
|
|
209
|
+
await configManager.set('sati.api_key', satiApiKey);
|
|
210
210
|
}
|
|
211
211
|
// Audio Configuration
|
|
212
212
|
const audioEnabled = await confirm({
|
|
@@ -99,7 +99,7 @@ export const restartCommand = new Command('restart')
|
|
|
99
99
|
// Initialize Web UI
|
|
100
100
|
if (options.ui && config.ui.enabled) {
|
|
101
101
|
try {
|
|
102
|
-
httpServer = new HttpServer();
|
|
102
|
+
httpServer = new HttpServer(oracle);
|
|
103
103
|
// Use CLI port if provided and valid, otherwise fallback to config or default
|
|
104
104
|
const port = parseInt(options.port) || config.ui.port || 3333;
|
|
105
105
|
httpServer.start(port);
|