morpheus-cli 0.2.7 → 0.2.8

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 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
@@ -209,6 +213,8 @@ The Morpheus Telegram bot supports several commands for interacting with the age
209
213
  - `/help` - Show available commands
210
214
  - `/zaion` - Show system configurations
211
215
  - `/sati <qnt>` - Show specific memories
216
+ - `/newsession` - Archive current session and start fresh
217
+ - `/sessions` - List all sessions and switch between them
212
218
  - `/restart` - Restart the Morpheus agent
213
219
  - `/mcp` or `/mcps` - List registered MCP servers
214
220
 
@@ -364,6 +370,33 @@ Get the current status of the Morpheus agent.
364
370
  }
365
371
  ```
366
372
 
373
+ ### Session Endpoints
374
+
375
+ #### POST `/api/session/reset`
376
+ Archive the current session and start a new one.
377
+
378
+ * **Authentication:** Requires `Authorization` header with the password set in `THE_ARCHITECT_PASS`.
379
+ * **Response:**
380
+ ```json
381
+ {
382
+ "success": true,
383
+ "message": "New session started"
384
+ }
385
+ ```
386
+
387
+ #### POST `/api/session/status`
388
+ Get the status of the current session.
389
+
390
+ * **Authentication:** Requires `Authorization` header with the password set in `THE_ARCHITECT_PASS`.
391
+ * **Response:**
392
+ ```json
393
+ {
394
+ "id": "uuid-...",
395
+ "messageCount": 42,
396
+ "embedding_status": "pending"
397
+ }
398
+ ```
399
+
367
400
  ### Configuration Endpoints
368
401
 
369
402
  #### GET `/api/config`
@@ -848,7 +881,7 @@ version: '3.8'
848
881
 
849
882
  services:
850
883
  morpheus:
851
- image: morpheus/morpheus-agent:latest
884
+ image: marcosnunesmbs/morpheus:latest
852
885
  container_name: morpheus-agent
853
886
  ports:
854
887
  - "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,45 @@ 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}`);
191
+ }
192
+ });
142
193
  this.bot.launch().catch((err) => {
143
194
  if (this.isConnected) {
144
195
  this.display.log(`Telegram bot error: ${err}`, { source: 'Telegram', level: 'error' });
@@ -188,6 +239,11 @@ export class TelegramAdapter {
188
239
  this.bot = null;
189
240
  this.display.log(chalk.gray('Telegram disconnected.'), { source: 'Telegram' });
190
241
  }
242
+ /**
243
+ ** =========================
244
+ ** Commands Handlers
245
+ ** =========================
246
+ */
191
247
  async handleSystemCommand(ctx, text, user) {
192
248
  const command = text.split(' ')[0];
193
249
  const args = text.split(' ').slice(1);
@@ -220,24 +276,87 @@ export class TelegramAdapter {
220
276
  case '/mcps':
221
277
  await this.handleMcpListCommand(ctx, user);
222
278
  break;
279
+ case '/newsession':
280
+ case '/reset':
281
+ await this.handleNewSessionCommand(ctx, user);
282
+ break;
283
+ case '/sessionstatus':
284
+ case '/session':
285
+ case '/sessions':
286
+ await this.handleSessionStatusCommand(ctx, user);
287
+ break;
223
288
  default:
224
289
  await this.handleDefaultCommand(ctx, user, command);
225
290
  }
226
291
  }
292
+ async handleNewSessionCommand(ctx, user) {
293
+ try {
294
+ await ctx.reply("Are you ready to start a new session? Please confirm.", {
295
+ parse_mode: 'Markdown', reply_markup: {
296
+ inline_keyboard: [
297
+ [{ text: 'Yes, start new session', callback_data: 'confirm_new_session' }, { text: 'No, cancel', callback_data: 'cancel_new_session' }]
298
+ ]
299
+ }
300
+ });
301
+ }
302
+ catch (e) {
303
+ await ctx.reply(`Error starting new session: ${e.message}`);
304
+ }
305
+ }
306
+ async handleApproveNewSessionCommand(ctx, user) {
307
+ try {
308
+ const history = new SQLiteChatMessageHistory({ sessionId: "" });
309
+ await history.createNewSession();
310
+ }
311
+ catch (e) {
312
+ await ctx.reply(`Error creating new session: ${e.message}`);
313
+ }
314
+ }
315
+ async handleSessionStatusCommand(ctx, user) {
316
+ try {
317
+ // Obter todas as sessões ativas e pausadas usando a nova função
318
+ const history = new SQLiteChatMessageHistory({ sessionId: "" });
319
+ const sessions = await history.listSessions();
320
+ if (sessions.length === 0) {
321
+ await ctx.reply('No active or paused sessions found.', { parse_mode: 'Markdown' });
322
+ return;
323
+ }
324
+ let response = '*Sessions:*\n\n';
325
+ const keyboard = [];
326
+ for (const session of sessions) {
327
+ const title = session.title || 'Untitled Session';
328
+ const statusEmoji = session.status === 'active' ? '🟢' : '🟡';
329
+ response += `${statusEmoji} *${title}*\n`;
330
+ response += `- ID: ${session.id}\n`;
331
+ response += `- Status: ${session.status}\n`;
332
+ response += `- Started: ${new Date(session.started_at).toLocaleString()}\n\n`;
333
+ // Adicionar botão inline para alternar para esta sessão
334
+ if (session.status !== 'active') {
335
+ keyboard.push([{
336
+ text: `Switch to: ${title}`,
337
+ callback_data: `switch_session_${session.id}`
338
+ }]);
339
+ }
340
+ }
341
+ await ctx.reply(response, {
342
+ parse_mode: 'Markdown',
343
+ reply_markup: {
344
+ inline_keyboard: keyboard
345
+ }
346
+ });
347
+ history.close();
348
+ }
349
+ catch (e) {
350
+ await ctx.reply(`Error retrieving session status: ${e.message}`);
351
+ }
352
+ }
227
353
  async handleStartCommand(ctx, user) {
228
354
  const welcomeMessage = `
229
355
  Hello, @${user}! I am ${this.config.get().agent.name}, ${this.config.get().agent.personality}.
230
356
 
231
357
  I am your local AI operator/agent. Here are the commands you can use:
232
358
 
233
- /start - Show this welcome message and available commands
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
359
+ ${this.HELP_MESSAGE}
241
360
 
242
361
  How can I assist you today?`;
243
362
  await ctx.reply(welcomeMessage);
@@ -321,7 +440,7 @@ How can I assist you today?`;
321
440
  if (groupedStats.length > 0) {
322
441
  response += '*Breakdown by Provider and Model:*\n';
323
442
  for (const stat of groupedStats) {
324
- response += `- ${stat.provider}/${stat.model}: ${stat.totalTokens} tokens (${stat.messageCount} messages)\n`;
443
+ response += `- ${stat.provider}/${stat.model}:\n ${stat.totalTokens} tokens\n(${stat.messageCount} messages)\n\n`;
325
444
  }
326
445
  }
327
446
  else {
@@ -336,9 +455,10 @@ How can I assist you today?`;
336
455
  }
337
456
  }
338
457
  async handleDefaultCommand(ctx, user, command) {
339
- const prompt = `O usuário envio o comando: ${command},
458
+ const prompt = `O usuário enviou o comando: ${command},
340
459
  Não entendemos o comando
341
- temos os seguintes comandos disponíveis: /start, /status, /doctor, /stats, /help, /zaion, /sati <qnt>, /restart, /mcp, /mcps
460
+ temos os seguintes comandos disponíveis:
461
+ ${this.HELP_MESSAGE}
342
462
  Identifique se ele talvez tenha errado o comando e pergunte se ele não quis executar outro comando.
343
463
  Só faça isso agora.`;
344
464
  let response = await this.oracle.chat(prompt);
@@ -351,17 +471,9 @@ How can I assist you today?`;
351
471
  const helpMessage = `
352
472
  *Available Commands:*
353
473
 
354
- /start - Show welcome message and available commands
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
474
+ ${this.HELP_MESSAGE}
363
475
 
364
- How can I assist you today? `;
476
+ How can I assist you today?`;
365
477
  await ctx.reply(helpMessage, { parse_mode: 'Markdown' });
366
478
  }
367
479
  async handleZaionCommand(ctx, user) {
@@ -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, 'santi-memory.db');
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
  }
@@ -0,0 +1,79 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { ConfigManager } from '../../config/manager.js';
4
+ import { confirm } from '@inquirer/prompts';
5
+ const session = new Command('session')
6
+ .description('Manage chat sessions');
7
+ session.command('new')
8
+ .description('Archive current session and start a new one')
9
+ .action(async () => {
10
+ const confirmNew = await confirm({
11
+ message: 'Are you sure you want to start a new session?',
12
+ default: false,
13
+ });
14
+ if (confirmNew) {
15
+ const config = ConfigManager.getInstance().get();
16
+ const port = config.ui.port || 3333;
17
+ const authPass = process.env.THE_ARCHITECT_PASS || 'iamthearchitect';
18
+ try {
19
+ const response = await fetch(`http://localhost:${port}/api/session/reset`, {
20
+ method: 'POST',
21
+ headers: {
22
+ 'Content-Type': 'application/json',
23
+ 'x-architect-pass': authPass
24
+ }
25
+ });
26
+ if (response.ok) {
27
+ console.log(chalk.green('✓ New session started successfully on running Morpheus instance.'));
28
+ }
29
+ else {
30
+ const errorText = await response.text();
31
+ console.log(chalk.red(`Failed: ${response.status} ${response.statusText}`));
32
+ if (errorText)
33
+ console.log(chalk.gray(errorText));
34
+ }
35
+ }
36
+ catch (err) {
37
+ console.log(chalk.red('Could not connect to Morpheus daemon.'));
38
+ console.log(chalk.yellow(`Ensure Morpheus is running and listening on port ${port}.`));
39
+ }
40
+ }
41
+ else {
42
+ console.log(chalk.yellow('Session reset cancelled. Current session is intact.'));
43
+ }
44
+ });
45
+ session.command('status')
46
+ .description('Get current session status')
47
+ .action(async () => {
48
+ const config = ConfigManager.getInstance().get();
49
+ const port = config.ui.port || 3333;
50
+ const authPass = process.env.THE_ARCHITECT_PASS || 'iamthearchitect';
51
+ try {
52
+ const response = await fetch(`http://localhost:${port}/api/session/status`, {
53
+ method: 'GET',
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ 'x-architect-pass': authPass
57
+ }
58
+ });
59
+ if (response.ok) {
60
+ const data = await response.json();
61
+ console.log(chalk.bold('Current Session Status:'));
62
+ console.log(`- Session ID: ${data.id}`);
63
+ console.log(`- Messages in Session: ${data.messageCount}`);
64
+ console.log(`- Embedded: ${data.embedded}`);
65
+ console.log(`- Embedding Status: ${data.embedding_status}`);
66
+ }
67
+ else {
68
+ const errorText = await response.text();
69
+ console.log(chalk.red(`Failed: ${response.status} ${response.statusText}`));
70
+ if (errorText)
71
+ console.log(chalk.gray(errorText));
72
+ }
73
+ }
74
+ catch (err) {
75
+ console.log(chalk.red('Could not connect to Morpheus daemon.'));
76
+ console.log(chalk.yellow(`Ensure Morpheus is running and listening on port ${port}.`));
77
+ }
78
+ });
79
+ export const sessionCommand = session;
@@ -12,6 +12,7 @@ import { Oracle } from '../../runtime/oracle.js';
12
12
  import { ProviderError } from '../../runtime/errors.js';
13
13
  import { HttpServer } from '../../http/server.js';
14
14
  import { getVersion } from '../utils/version.js';
15
+ import { startSessionEmbeddingScheduler } from '../../runtime/session-embedding-scheduler.js';
15
16
  export const startCommand = new Command('start')
16
17
  .description('Start the Morpheus agent')
17
18
  .option('--ui', 'Enable web UI', true)
@@ -103,6 +104,8 @@ export const startCommand = new Command('start')
103
104
  display.log(chalk.yellow('Telegram enabled but no token provided. Skipping.'));
104
105
  }
105
106
  }
107
+ // Start Background Services
108
+ startSessionEmbeddingScheduler();
106
109
  // Handle graceful shutdown
107
110
  const shutdown = async (signal) => {
108
111
  display.stopSpinner();
package/dist/cli/index.js CHANGED
@@ -6,6 +6,7 @@ import { configCommand } from './commands/config.js';
6
6
  import { doctorCommand } from './commands/doctor.js';
7
7
  import { initCommand } from './commands/init.js';
8
8
  import { restartCommand } from './commands/restart.js';
9
+ import { sessionCommand } from './commands/session.js';
9
10
  import { scaffold } from '../runtime/scaffold.js';
10
11
  import { getVersion } from './utils/version.js';
11
12
  export async function cli() {
@@ -24,7 +25,8 @@ export async function cli() {
24
25
  program.addCommand(statusCommand);
25
26
  program.addCommand(configCommand);
26
27
  program.addCommand(doctorCommand);
27
- program.parse(process.argv);
28
+ program.addCommand(sessionCommand);
29
+ await program.parseAsync(process.argv);
28
30
  }
29
31
  // Support direct execution via tsx
30
32
  if (import.meta.url.startsWith('file:') && (process.argv[1]?.endsWith('index.ts') || process.argv[1]?.endsWith('cli/index.js'))) {
package/dist/http/api.js CHANGED
@@ -23,6 +23,31 @@ async function readLastLines(filePath, n) {
23
23
  export function createApiRouter() {
24
24
  const router = Router();
25
25
  const configManager = ConfigManager.getInstance();
26
+ const history = new SQLiteChatMessageHistory({ sessionId: 'api-reader' });
27
+ router.post('/session/reset', async (req, res) => {
28
+ // if (!oracle) {
29
+ // return res.status(503).json({ error: 'Oracle unavailable' });
30
+ // }
31
+ try {
32
+ await history.createNewSession();
33
+ res.json({ success: true, message: 'New session started' });
34
+ }
35
+ catch (err) {
36
+ res.status(500).json({ error: err.message });
37
+ }
38
+ });
39
+ router.post('/session/status', async (req, res) => {
40
+ try {
41
+ const sessionStatus = await history.getSessionStatus();
42
+ if (!sessionStatus) {
43
+ return res.status(404).json({ error: 'No session found' });
44
+ }
45
+ res.json(sessionStatus);
46
+ }
47
+ catch (err) {
48
+ res.status(500).json({ error: err.message });
49
+ }
50
+ });
26
51
  router.get('/status', async (req, res) => {
27
52
  let version = 'unknown';
28
53
  try {
@@ -0,0 +1,54 @@
1
+ import Database from 'better-sqlite3';
2
+ import { EmbeddingService } from './embedding.service.js';
3
+ import path from 'path';
4
+ import { homedir } from 'os';
5
+ import loadVecExtension from './sqlite-vec.js';
6
+ const db = new Database(path.join(homedir(), '.morpheus', 'memory', 'sati-memory.db'));
7
+ db.pragma('journal_mode = WAL');
8
+ // 🔥 ISSO AQUI É O QUE ESTÁ FALTANDO
9
+ loadVecExtension(db);
10
+ const embeddingService = await EmbeddingService.getInstance();
11
+ const BATCH_SIZE = 50;
12
+ async function run() {
13
+ console.log('🔎 Buscando memórias sem embedding vetorial...');
14
+ while (true) {
15
+ const rows = db.prepare(`
16
+ SELECT m.rowid, m.summary, m.details
17
+ FROM long_term_memory m
18
+ LEFT JOIN memory_vec v ON m.rowid = v.rowid
19
+ WHERE v.rowid IS NULL
20
+ LIMIT ?
21
+ `).all(BATCH_SIZE);
22
+ if (rows.length === 0) {
23
+ console.log('✅ Todas as memórias já possuem embedding.');
24
+ break;
25
+ }
26
+ console.log(`⚙️ Processando batch de ${rows.length} memórias...`);
27
+ const vectors = [];
28
+ for (const row of rows) {
29
+ const text = `${row.summary} ${row.details || ''}`.trim();
30
+ const vector = await embeddingService.generate(text);
31
+ vectors.push({ rowid: row.rowid, vector });
32
+ }
33
+ const insertVec = db.prepare(`
34
+ INSERT INTO memory_vec (embedding)
35
+ VALUES (?)
36
+ `);
37
+ const insertMap = db.prepare(`
38
+ INSERT INTO memory_embedding_map (memory_id, vec_rowid)
39
+ VALUES (?, ?)
40
+ `);
41
+ const transaction = db.transaction((items) => {
42
+ for (const item of items) {
43
+ const result = insertVec.run(new Float32Array(item.vector));
44
+ const vecRowId = result.lastInsertRowid;
45
+ insertMap.run(item.memory_id, vecRowId);
46
+ }
47
+ });
48
+ transaction(vectors);
49
+ }
50
+ console.log('🎉 Backfill concluído.');
51
+ }
52
+ run().catch(err => {
53
+ console.error('❌ Erro no backfill:', err);
54
+ });
@@ -0,0 +1,21 @@
1
+ import { pipeline } from '@xenova/transformers';
2
+ export class EmbeddingService {
3
+ static instance;
4
+ extractor;
5
+ constructor() { }
6
+ static async getInstance() {
7
+ if (!EmbeddingService.instance) {
8
+ const service = new EmbeddingService();
9
+ service.extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
10
+ EmbeddingService.instance = service;
11
+ }
12
+ return EmbeddingService.instance;
13
+ }
14
+ async generate(text) {
15
+ const output = await this.extractor(text, {
16
+ pooling: 'mean',
17
+ normalize: true,
18
+ });
19
+ return Array.from(output.data);
20
+ }
21
+ }
@@ -18,16 +18,16 @@ export class SatiMemoryMiddleware {
18
18
  try {
19
19
  // Extract recent messages content strings for context
20
20
  const recentText = history.slice(-10).map(m => m.content.toString());
21
- display.log(`[Sati] Searching memories for: "${currentMessage.substring(0, 50)}${currentMessage.length > 50 ? '...' : ''}"`, { source: 'Sati' });
21
+ display.log(`Searching memories for: "${currentMessage.substring(0, 50)}${currentMessage.length > 50 ? '...' : ''}"`, { source: 'Sati' });
22
22
  const result = await this.service.recover(currentMessage, recentText);
23
23
  if (result.relevant_memories.length === 0) {
24
- display.log('[Sati] No relevant memories found', { source: 'Sati' });
24
+ display.log('No relevant memories found', { source: 'Sati' });
25
25
  return null;
26
26
  }
27
27
  const memoryContext = result.relevant_memories
28
28
  .map(m => `- [${m.category.toUpperCase()}] ${m.summary}`)
29
29
  .join('\n');
30
- display.log(`[Sati] Retrieved ${result.relevant_memories.length} memories.`, { source: 'Sati' });
30
+ display.log(`Retrieved ${result.relevant_memories.length} memories.`, { source: 'Sati' });
31
31
  return new AIMessage(`
32
32
  ### LONG-TERM MEMORY (SATI)
33
33
  The following information was retrieved from previous sessions. Use it if relevant:
@@ -36,7 +36,7 @@ export class SatiMemoryMiddleware {
36
36
  `);
37
37
  }
38
38
  catch (error) {
39
- display.log(`[SatiMiddleware] Error in beforeAgent: ${error}`, { source: 'Sati' });
39
+ display.log(`Error in beforeAgent: ${error}`, { source: 'Sati' });
40
40
  // Fail open: return null so execution continues without memory
41
41
  return null;
42
42
  }
@@ -52,7 +52,7 @@ export class SatiMemoryMiddleware {
52
52
  ]);
53
53
  }
54
54
  catch (error) {
55
- console.error('[SatiMiddleware] Error in afterAgent:', error);
55
+ display.log(`Error in afterAgent: ${error}`, { source: 'Sati' });
56
56
  }
57
57
  }
58
58
  }