morpheus-cli 0.2.6 → 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 +428 -2
- package/dist/channels/telegram.js +169 -19
- package/dist/cli/commands/doctor.js +1 -1
- package/dist/cli/commands/session.js +79 -0
- package/dist/cli/commands/start.js +3 -0
- package/dist/cli/index.js +3 -1
- package/dist/http/api.js +25 -0
- 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 +320 -116
- package/dist/runtime/memory/sati/service.js +48 -25
- package/dist/runtime/memory/sati/system-prompts.js +19 -8
- package/dist/runtime/memory/session-embedding-worker.js +99 -0
- package/dist/runtime/memory/sqlite-vec.js +6 -0
- package/dist/runtime/memory/sqlite.js +415 -3
- package/dist/runtime/oracle.js +13 -1
- package/dist/runtime/session-embedding-scheduler.js +21 -0
- package/dist/ui/assets/{index-BrbyUtJ5.js → index-Dx1lwaMu.js} +2 -2
- package/dist/ui/assets/index-QHZ08tDL.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +10 -3
- package/dist/ui/assets/index-BiXkm8Yr.css +0 -1
|
@@ -11,6 +11,7 @@ import { Telephonist } from '../runtime/telephonist.js';
|
|
|
11
11
|
import { readPid, isProcessRunning, checkStalePid } from '../runtime/lifecycle.js';
|
|
12
12
|
import { SQLiteChatMessageHistory } from '../runtime/memory/sqlite.js';
|
|
13
13
|
import { SatiRepository } from '../runtime/memory/sati/repository.js';
|
|
14
|
+
import { MCPManager } from '../config/mcp-manager.js';
|
|
14
15
|
export class TelegramAdapter {
|
|
15
16
|
bot = null;
|
|
16
17
|
isConnected = false;
|
|
@@ -18,6 +19,18 @@ export class TelegramAdapter {
|
|
|
18
19
|
config = ConfigManager.getInstance();
|
|
19
20
|
oracle;
|
|
20
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`;
|
|
21
34
|
constructor(oracle) {
|
|
22
35
|
this.oracle = oracle;
|
|
23
36
|
}
|
|
@@ -138,6 +151,45 @@ export class TelegramAdapter {
|
|
|
138
151
|
}
|
|
139
152
|
}
|
|
140
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
|
+
});
|
|
141
193
|
this.bot.launch().catch((err) => {
|
|
142
194
|
if (this.isConnected) {
|
|
143
195
|
this.display.log(`Telegram bot error: ${err}`, { source: 'Telegram', level: 'error' });
|
|
@@ -187,6 +239,11 @@ export class TelegramAdapter {
|
|
|
187
239
|
this.bot = null;
|
|
188
240
|
this.display.log(chalk.gray('Telegram disconnected.'), { source: 'Telegram' });
|
|
189
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
** =========================
|
|
244
|
+
** Commands Handlers
|
|
245
|
+
** =========================
|
|
246
|
+
*/
|
|
190
247
|
async handleSystemCommand(ctx, text, user) {
|
|
191
248
|
const command = text.split(' ')[0];
|
|
192
249
|
const args = text.split(' ').slice(1);
|
|
@@ -215,24 +272,91 @@ export class TelegramAdapter {
|
|
|
215
272
|
case '/restart':
|
|
216
273
|
await this.handleRestartCommand(ctx, user);
|
|
217
274
|
break;
|
|
275
|
+
case '/mcp':
|
|
276
|
+
case '/mcps':
|
|
277
|
+
await this.handleMcpListCommand(ctx, user);
|
|
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;
|
|
218
288
|
default:
|
|
219
289
|
await this.handleDefaultCommand(ctx, user, command);
|
|
220
290
|
}
|
|
221
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
|
+
}
|
|
222
353
|
async handleStartCommand(ctx, user) {
|
|
223
354
|
const welcomeMessage = `
|
|
224
355
|
Hello, @${user}! I am ${this.config.get().agent.name}, ${this.config.get().agent.personality}.
|
|
225
356
|
|
|
226
357
|
I am your local AI operator/agent. Here are the commands you can use:
|
|
227
358
|
|
|
228
|
-
|
|
229
|
-
/status - Check the status of the Morpheus agent
|
|
230
|
-
/doctor - Diagnose environment and configuration issues
|
|
231
|
-
/stats - Show token usage statistics
|
|
232
|
-
/help - Show available commands
|
|
233
|
-
/zaion - Show system configurations
|
|
234
|
-
/sati <qnt> - Show specific memories
|
|
235
|
-
/restart - Restart the Morpheus agent
|
|
359
|
+
${this.HELP_MESSAGE}
|
|
236
360
|
|
|
237
361
|
How can I assist you today?`;
|
|
238
362
|
await ctx.reply(welcomeMessage);
|
|
@@ -316,7 +440,7 @@ How can I assist you today?`;
|
|
|
316
440
|
if (groupedStats.length > 0) {
|
|
317
441
|
response += '*Breakdown by Provider and Model:*\n';
|
|
318
442
|
for (const stat of groupedStats) {
|
|
319
|
-
response += `- ${stat.provider}/${stat.model}
|
|
443
|
+
response += `- ${stat.provider}/${stat.model}:\n ${stat.totalTokens} tokens\n(${stat.messageCount} messages)\n\n`;
|
|
320
444
|
}
|
|
321
445
|
}
|
|
322
446
|
else {
|
|
@@ -331,9 +455,10 @@ How can I assist you today?`;
|
|
|
331
455
|
}
|
|
332
456
|
}
|
|
333
457
|
async handleDefaultCommand(ctx, user, command) {
|
|
334
|
-
const prompt = `O usuário
|
|
458
|
+
const prompt = `O usuário enviou o comando: ${command},
|
|
335
459
|
Não entendemos o comando
|
|
336
|
-
temos os seguintes comandos disponíveis:
|
|
460
|
+
temos os seguintes comandos disponíveis:
|
|
461
|
+
${this.HELP_MESSAGE}
|
|
337
462
|
Identifique se ele talvez tenha errado o comando e pergunte se ele não quis executar outro comando.
|
|
338
463
|
Só faça isso agora.`;
|
|
339
464
|
let response = await this.oracle.chat(prompt);
|
|
@@ -346,14 +471,7 @@ How can I assist you today?`;
|
|
|
346
471
|
const helpMessage = `
|
|
347
472
|
*Available Commands:*
|
|
348
473
|
|
|
349
|
-
|
|
350
|
-
/status - Check the status of the Morpheus agent
|
|
351
|
-
/doctor - Diagnose environment and configuration issues
|
|
352
|
-
/stats - Show token usage statistics
|
|
353
|
-
/help - Show this help message
|
|
354
|
-
/zaion - Show system configurations
|
|
355
|
-
/sati <qnt> - Show specific memories
|
|
356
|
-
/restart - Restart the Morpheus agent
|
|
474
|
+
${this.HELP_MESSAGE}
|
|
357
475
|
|
|
358
476
|
How can I assist you today?`;
|
|
359
477
|
await ctx.reply(helpMessage, { parse_mode: 'Markdown' });
|
|
@@ -473,4 +591,36 @@ How can I assist you today?`;
|
|
|
473
591
|
this.display.log(`Error checking restart notification: ${error.message}`, { source: 'Telegram', level: 'error' });
|
|
474
592
|
}
|
|
475
593
|
}
|
|
594
|
+
async handleMcpListCommand(ctx, user) {
|
|
595
|
+
try {
|
|
596
|
+
const servers = await MCPManager.listServers();
|
|
597
|
+
if (servers.length === 0) {
|
|
598
|
+
await ctx.reply('*No MCP Servers Configured*\n\nThere are currently no MCP servers configured in the system.', { parse_mode: 'Markdown' });
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
let response = `*MCP Servers (${servers.length})*\n\n`;
|
|
602
|
+
servers.forEach((server, index) => {
|
|
603
|
+
const status = server.enabled ? '✅ Enabled' : '❌ Disabled';
|
|
604
|
+
const transport = server.config.transport.toUpperCase();
|
|
605
|
+
response += `*${index + 1}. ${server.name}*\n`;
|
|
606
|
+
response += `Status: ${status}\n`;
|
|
607
|
+
response += `Transport: ${transport}\n`;
|
|
608
|
+
if (server.config.transport === 'stdio') {
|
|
609
|
+
response += `Command: \`${server.config.command}\`\n`;
|
|
610
|
+
if (server.config.args && server.config.args.length > 0) {
|
|
611
|
+
response += `Args: \`${server.config.args.join(' ')}\`\n`;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
else if (server.config.transport === 'http') {
|
|
615
|
+
response += `URL: \`${server.config.url}\`\n`;
|
|
616
|
+
}
|
|
617
|
+
response += '\n';
|
|
618
|
+
});
|
|
619
|
+
await ctx.reply(response, { parse_mode: 'Markdown' });
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
this.display.log('Error listing MCP servers: ' + (error instanceof Error ? error.message : String(error)), { source: 'Telegram', level: 'error' });
|
|
623
|
+
await ctx.reply('An error occurred while retrieving the list of MCP servers. Please check the logs for more details.', { parse_mode: 'Markdown' });
|
|
624
|
+
}
|
|
625
|
+
}
|
|
476
626
|
}
|
|
@@ -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
|
}
|
|
@@ -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.
|
|
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(`
|
|
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('
|
|
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(`
|
|
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(`
|
|
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
|
-
|
|
55
|
+
display.log(`Error in afterAgent: ${error}`, { source: 'Sati' });
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
}
|