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 +34 -1
- package/dist/channels/telegram.js +133 -21
- 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/package.json +10 -3
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:
|
|
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
|
-
|
|
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}
|
|
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
|
|
458
|
+
const prompt = `O usuário enviou o comando: ${command},
|
|
340
459
|
Não entendemos o comando
|
|
341
|
-
temos os seguintes comandos disponíveis:
|
|
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
|
-
|
|
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, '
|
|
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
|
}
|