morpheus-cli 0.2.8 → 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 +18 -8
- package/dist/channels/telegram.js +101 -6
- package/dist/cli/commands/doctor.js +10 -10
- package/dist/cli/commands/init.js +34 -34
- package/dist/cli/commands/restart.js +1 -1
- package/dist/cli/commands/start.js +1 -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 +138 -6
- package/dist/http/server.js +4 -2
- package/dist/runtime/memory/sati/repository.js +14 -11
- package/dist/runtime/memory/sati/service.js +14 -12
- package/dist/runtime/memory/session-embedding-worker.js +4 -9
- package/dist/runtime/memory/sqlite.js +19 -2
- package/dist/runtime/migration.js +40 -0
- package/dist/runtime/oracle.js +56 -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 +2 -2
- package/dist/ui/assets/index-Dx1lwaMu.js +0 -96
- package/dist/ui/assets/index-QHZ08tDL.css +0 -1
package/dist/http/server.js
CHANGED
|
@@ -12,8 +12,10 @@ const __dirname = path.dirname(__filename);
|
|
|
12
12
|
export class HttpServer {
|
|
13
13
|
app;
|
|
14
14
|
server;
|
|
15
|
-
|
|
15
|
+
oracle;
|
|
16
|
+
constructor(oracle) {
|
|
16
17
|
this.app = express();
|
|
18
|
+
this.oracle = oracle;
|
|
17
19
|
this.setupMiddleware();
|
|
18
20
|
this.setupRoutes();
|
|
19
21
|
}
|
|
@@ -43,7 +45,7 @@ export class HttpServer {
|
|
|
43
45
|
uptime: process.uptime()
|
|
44
46
|
});
|
|
45
47
|
});
|
|
46
|
-
this.app.use('/api', authMiddleware, createApiRouter());
|
|
48
|
+
this.app.use('/api', authMiddleware, createApiRouter(this.oracle));
|
|
47
49
|
// Serve static frontend from compiled output
|
|
48
50
|
const uiPath = path.resolve(__dirname, '../ui');
|
|
49
51
|
this.app.use(express.static(uiPath));
|
|
@@ -318,14 +318,17 @@ export class SatiRepository {
|
|
|
318
318
|
try {
|
|
319
319
|
this.display.log(`🔍 Iniciando busca de memória | Query: "${query}"`, { source: 'Sati', level: 'debug' });
|
|
320
320
|
// 1️⃣ Vetorial
|
|
321
|
-
if (embedding) {
|
|
322
|
-
this.display.log('🧠
|
|
321
|
+
if (embedding && embedding.length > 0) {
|
|
322
|
+
this.display.log('🧠 Tentando busca vetorial...', { source: 'Sati', level: 'debug' });
|
|
323
323
|
const vectorResults = this.searchUnifiedVector(embedding, limit);
|
|
324
324
|
if (vectorResults.length > 0) {
|
|
325
|
-
this.display.log(`✅
|
|
325
|
+
this.display.log(`✅ Vetorial retornou ${vectorResults.length} resultado(s)`, { source: 'Sati', level: 'success' });
|
|
326
326
|
return vectorResults.slice(0, limit);
|
|
327
327
|
}
|
|
328
|
-
this.display.log('⚠️
|
|
328
|
+
this.display.log('⚠️ Vetorial não encontrou resultados relevantes', { source: 'Sati', level: 'debug' });
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
this.display.log('🛡️ Disabled Archived Sessions in Memory Retrieval', { source: 'Sati', level: 'info' });
|
|
329
332
|
}
|
|
330
333
|
// 2️⃣ BM25 (FTS)
|
|
331
334
|
// Sanitize query: remove characters that could break FTS5 syntax (like ?, *, OR, etc)
|
|
@@ -335,7 +338,7 @@ export class SatiRepository {
|
|
|
335
338
|
.replace(/\s+/g, ' ')
|
|
336
339
|
.trim();
|
|
337
340
|
if (safeQuery) {
|
|
338
|
-
this.display.log('📚
|
|
341
|
+
this.display.log('📚 Tentando busca BM25 (FTS5)...', { source: 'Sati', level: 'debug' });
|
|
339
342
|
const stmt = this.db.prepare(`
|
|
340
343
|
SELECT m.*, bm25(memory_fts) as rank
|
|
341
344
|
FROM long_term_memory m
|
|
@@ -347,13 +350,13 @@ export class SatiRepository {
|
|
|
347
350
|
`);
|
|
348
351
|
const rows = stmt.all(safeQuery, limit);
|
|
349
352
|
if (rows.length > 0) {
|
|
350
|
-
this.display.log(`✅
|
|
353
|
+
this.display.log(`✅ BM25 retornou ${rows.length} resultado(s)`, { source: 'Sati', level: 'success' });
|
|
351
354
|
return rows.map(this.mapRowToRecord);
|
|
352
355
|
}
|
|
353
|
-
this.display.log('⚠️
|
|
356
|
+
this.display.log('⚠️ BM25 não encontrou resultados', { source: 'Sati', level: 'debug' });
|
|
354
357
|
}
|
|
355
358
|
// 3️⃣ LIKE fallback
|
|
356
|
-
this.display.log('🧵
|
|
359
|
+
this.display.log('🧵 Tentando fallback LIKE...', { source: 'Sati', level: 'debug' });
|
|
357
360
|
const likeStmt = this.db.prepare(`
|
|
358
361
|
SELECT * FROM long_term_memory
|
|
359
362
|
WHERE (summary LIKE ? OR details LIKE ?)
|
|
@@ -364,15 +367,15 @@ export class SatiRepository {
|
|
|
364
367
|
const pattern = `%${query}%`;
|
|
365
368
|
const likeRows = likeStmt.all(pattern, pattern, limit);
|
|
366
369
|
if (likeRows.length > 0) {
|
|
367
|
-
this.display.log(`✅
|
|
370
|
+
this.display.log(`✅ LIKE retornou ${likeRows.length} resultado(s)`, { source: 'Sati', level: 'success' });
|
|
368
371
|
return likeRows.map(this.mapRowToRecord);
|
|
369
372
|
}
|
|
370
373
|
// 4️⃣ Final fallback
|
|
371
|
-
this.display.log('🛟
|
|
374
|
+
this.display.log('🛟 Nenhum mecanismo encontrou resultados. Usando fallback estratégico.', { source: 'Sati', level: 'warning' });
|
|
372
375
|
return this.getFallbackMemories(limit);
|
|
373
376
|
}
|
|
374
377
|
catch (e) {
|
|
375
|
-
this.display.log(`❌
|
|
378
|
+
this.display.log(`❌ Erro durante busca: ${e}`, { source: 'Sati', level: 'error' });
|
|
376
379
|
return this.getFallbackMemories(limit);
|
|
377
380
|
}
|
|
378
381
|
}
|
|
@@ -24,8 +24,9 @@ export class SatiService {
|
|
|
24
24
|
this.repository.initialize();
|
|
25
25
|
}
|
|
26
26
|
async recover(currentMessage, recentMessages) {
|
|
27
|
-
const
|
|
28
|
-
const memoryLimit =
|
|
27
|
+
const satiConfig = ConfigManager.getInstance().getSatiConfig();
|
|
28
|
+
const memoryLimit = satiConfig.memory_limit || 10;
|
|
29
|
+
const enabled_vector_search = satiConfig.enabled_archived_sessions ?? true;
|
|
29
30
|
let queryEmbedding;
|
|
30
31
|
try {
|
|
31
32
|
const embeddingService = await EmbeddingService.getInstance();
|
|
@@ -33,13 +34,14 @@ export class SatiService {
|
|
|
33
34
|
...recentMessages.slice(-3),
|
|
34
35
|
currentMessage
|
|
35
36
|
].join(' ');
|
|
36
|
-
|
|
37
|
+
if (enabled_vector_search) {
|
|
38
|
+
queryEmbedding = await embeddingService.generate(queryText);
|
|
39
|
+
}
|
|
37
40
|
}
|
|
38
41
|
catch (err) {
|
|
39
42
|
console.warn('[Sati] Failed to generate embedding:', err);
|
|
40
43
|
}
|
|
41
|
-
const memories = this.repository.search(currentMessage, memoryLimit, queryEmbedding
|
|
42
|
-
);
|
|
44
|
+
const memories = this.repository.search(currentMessage, memoryLimit, queryEmbedding);
|
|
43
45
|
return {
|
|
44
46
|
relevant_memories: memories.map(m => ({
|
|
45
47
|
summary: m.summary,
|
|
@@ -50,12 +52,12 @@ export class SatiService {
|
|
|
50
52
|
}
|
|
51
53
|
async evaluateAndPersist(conversation) {
|
|
52
54
|
try {
|
|
53
|
-
const
|
|
54
|
-
if (!
|
|
55
|
+
const satiConfig = ConfigManager.getInstance().getSatiConfig();
|
|
56
|
+
if (!satiConfig)
|
|
55
57
|
return;
|
|
56
58
|
// Use the main provider factory to get an agent (Reusing Zion configuration)
|
|
57
59
|
// We pass empty tools as Sati is a pure reasoning agent here
|
|
58
|
-
const agent = await ProviderFactory.create(
|
|
60
|
+
const agent = await ProviderFactory.create(satiConfig, []);
|
|
59
61
|
// Get existing memories for context (Simulated "Working Memory" or full list if small)
|
|
60
62
|
const allMemories = this.repository.getAllMemories();
|
|
61
63
|
const existingSummaries = allMemories.slice(0, 50).map(m => m.summary);
|
|
@@ -80,8 +82,8 @@ export class SatiService {
|
|
|
80
82
|
name: 'sati_evaluation_input'
|
|
81
83
|
});
|
|
82
84
|
inputMsg.provider_metadata = {
|
|
83
|
-
provider:
|
|
84
|
-
model:
|
|
85
|
+
provider: satiConfig.provider,
|
|
86
|
+
model: satiConfig.model
|
|
85
87
|
};
|
|
86
88
|
await history.addMessage(inputMsg);
|
|
87
89
|
}
|
|
@@ -101,8 +103,8 @@ export class SatiService {
|
|
|
101
103
|
outputToolMsg.usage_metadata = lastMessage.usage_metadata;
|
|
102
104
|
}
|
|
103
105
|
outputToolMsg.provider_metadata = {
|
|
104
|
-
provider:
|
|
105
|
-
model:
|
|
106
|
+
provider: satiConfig.provider,
|
|
107
|
+
model: satiConfig.model
|
|
106
108
|
};
|
|
107
109
|
await history.addMessage(outputToolMsg);
|
|
108
110
|
}
|
|
@@ -24,7 +24,6 @@ export async function runSessionEmbeddingWorker() {
|
|
|
24
24
|
FROM sessions
|
|
25
25
|
WHERE ended_at IS NOT NULL
|
|
26
26
|
AND embedding_status = 'pending'
|
|
27
|
-
OR embedding_status = 'failed'
|
|
28
27
|
LIMIT ?
|
|
29
28
|
`).all(BATCH_LIMIT);
|
|
30
29
|
if (sessions.length === 0) {
|
|
@@ -35,12 +34,8 @@ export async function runSessionEmbeddingWorker() {
|
|
|
35
34
|
const sessionId = session.id;
|
|
36
35
|
display.log(`🧠 Processando sessão ${sessionId}...`, { source: 'SessionEmbeddingWorker' });
|
|
37
36
|
try {
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
UPDATE sessions
|
|
41
|
-
SET embedding_status = 'processing'
|
|
42
|
-
WHERE id = ?
|
|
43
|
-
`).run(sessionId);
|
|
37
|
+
// Skip setting 'processing' as it violates CHECK constraint
|
|
38
|
+
// active_processing.add(sessionId); // If we needed concurrency control
|
|
44
39
|
const chunks = satiDb.prepare(`
|
|
45
40
|
SELECT id, content
|
|
46
41
|
FROM session_chunks
|
|
@@ -51,7 +46,7 @@ export async function runSessionEmbeddingWorker() {
|
|
|
51
46
|
display.log(`⚠️ Sessão ${sessionId} não possui chunks.`, { source: 'SessionEmbeddingWorker' });
|
|
52
47
|
shortDb.prepare(`
|
|
53
48
|
UPDATE sessions
|
|
54
|
-
SET embedding_status = '
|
|
49
|
+
SET embedding_status = 'embedded',
|
|
55
50
|
embedded = 1
|
|
56
51
|
WHERE id = ?
|
|
57
52
|
`).run(sessionId);
|
|
@@ -79,7 +74,7 @@ export async function runSessionEmbeddingWorker() {
|
|
|
79
74
|
// ✅ finalizar sessão
|
|
80
75
|
shortDb.prepare(`
|
|
81
76
|
UPDATE sessions
|
|
82
|
-
SET embedding_status = '
|
|
77
|
+
SET embedding_status = 'embedded',
|
|
83
78
|
embedded = 1
|
|
84
79
|
WHERE id = ?
|
|
85
80
|
`).run(sessionId);
|
|
@@ -615,9 +615,26 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
615
615
|
`).run(now, sessionId);
|
|
616
616
|
});
|
|
617
617
|
tx(); // Executar a transação
|
|
618
|
-
// Se a sessão era active,
|
|
618
|
+
// Se a sessão era active, verificar se há outra para ativar
|
|
619
619
|
if (session.status === 'active') {
|
|
620
|
-
this.
|
|
620
|
+
const nextSession = this.db.prepare(`
|
|
621
|
+
SELECT id FROM sessions
|
|
622
|
+
WHERE status = 'paused'
|
|
623
|
+
ORDER BY started_at DESC
|
|
624
|
+
LIMIT 1
|
|
625
|
+
`).get();
|
|
626
|
+
if (nextSession) {
|
|
627
|
+
// Promover a próxima sessão a ativa
|
|
628
|
+
this.db.prepare(`
|
|
629
|
+
UPDATE sessions
|
|
630
|
+
SET status = 'active'
|
|
631
|
+
WHERE id = ?
|
|
632
|
+
`).run(nextSession.id);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
// Nenhuma outra sessão, criar nova
|
|
636
|
+
this.createFreshSession();
|
|
637
|
+
}
|
|
621
638
|
}
|
|
622
639
|
}
|
|
623
640
|
/**
|
|
@@ -27,6 +27,8 @@ export async function migrateConfigFile() {
|
|
|
27
27
|
}
|
|
28
28
|
// Migrate memory.limit to llm.context_window
|
|
29
29
|
await migrateContextWindow();
|
|
30
|
+
// Migrate santi -> sati
|
|
31
|
+
await migrateSantiToSati();
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* Migrates memory.limit to llm.context_window
|
|
@@ -78,3 +80,41 @@ async function migrateContextWindow() {
|
|
|
78
80
|
});
|
|
79
81
|
}
|
|
80
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Migrates santi config section to sati
|
|
85
|
+
* Fixes typo in previous versions
|
|
86
|
+
*/
|
|
87
|
+
async function migrateSantiToSati() {
|
|
88
|
+
const display = DisplayManager.getInstance();
|
|
89
|
+
const configPath = PATHS.config;
|
|
90
|
+
try {
|
|
91
|
+
// Check if config file exists
|
|
92
|
+
if (!await fs.pathExists(configPath)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Read current config
|
|
96
|
+
const configContent = await fs.readFile(configPath, 'utf8');
|
|
97
|
+
const config = yaml.load(configContent);
|
|
98
|
+
// Check if migration is needed
|
|
99
|
+
if (config?.santi !== undefined && config?.sati === undefined) {
|
|
100
|
+
// Create backup before migration
|
|
101
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
102
|
+
const backupPath = `${configPath}.backup-${timestamp}`;
|
|
103
|
+
await fs.copy(configPath, backupPath);
|
|
104
|
+
display.log(`Created config backup: ${backupPath}`, { source: 'Migration', level: 'info' });
|
|
105
|
+
// Perform migration
|
|
106
|
+
config.sati = config.santi;
|
|
107
|
+
delete config.santi;
|
|
108
|
+
// Write migrated config
|
|
109
|
+
const migratedYaml = yaml.dump(config);
|
|
110
|
+
await fs.writeFile(configPath, migratedYaml, 'utf8');
|
|
111
|
+
display.log('Migrated santi → sati in configuration', { source: 'Migration', level: 'info' });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
display.log(`Config migration (santi->sati) failed: ${error.message}`, {
|
|
116
|
+
source: 'Migration',
|
|
117
|
+
level: 'warning'
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
package/dist/runtime/oracle.js
CHANGED
|
@@ -257,6 +257,62 @@ You maintain intent until resolution.
|
|
|
257
257
|
throw new Error("Current history provider does not support session rollover.");
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
+
async setSessionId(sessionId) {
|
|
261
|
+
if (!this.history) {
|
|
262
|
+
throw new Error("Message history not initialized. Call initialize() first.");
|
|
263
|
+
}
|
|
264
|
+
// Check if the history provider supports switching sessions
|
|
265
|
+
// SQLiteChatMessageHistory does support it via constructor (new instance) or maybe we can add a method there too?
|
|
266
|
+
// Actually SQLiteChatMessageHistory has `switchSession(targetSessionId)` but that one logic is "pause current, activate target".
|
|
267
|
+
// For API usage, we might just want to *target* a session without necessarily changing the global "active" state regarding the Daemon?
|
|
268
|
+
//
|
|
269
|
+
// However, the user request implies this is "the" chat.
|
|
270
|
+
// If we use `switchSession` it pauses others. That seems correct for a single-user agent model.
|
|
271
|
+
//
|
|
272
|
+
// But `SQLiteChatMessageHistory` properties are `sessionId`.
|
|
273
|
+
// It seems `switchSession` in `sqlite.ts` updates the DB state.
|
|
274
|
+
// We also need to update the `sessionId` property of the `SQLiteChatMessageHistory` instance held by Oracle.
|
|
275
|
+
//
|
|
276
|
+
// Let's check `SQLiteChatMessageHistory` again.
|
|
277
|
+
// It has `sessionId` property.
|
|
278
|
+
// It does NOT have a method to just update `sessionId` property without DB side effects?
|
|
279
|
+
//
|
|
280
|
+
// Use `switchSession` from `sqlite.ts` is good for "Active/Paused" state management.
|
|
281
|
+
// But we also need the `history` instance to know it is now pointing to `sessionId`.
|
|
282
|
+
if (this.history instanceof SQLiteChatMessageHistory) {
|
|
283
|
+
// Logic:
|
|
284
|
+
// 1. If currently active session is different, switch.
|
|
285
|
+
// 2. Update internal sessionId.
|
|
286
|
+
// Actually `switchSession` in `sqlite.ts` takes `targetSessionId`.
|
|
287
|
+
// It updates the DB status.
|
|
288
|
+
// It DOES NOT seem to update `this.sessionId` of the instance?
|
|
289
|
+
// Wait, let me check `sqlite.ts` content from memory or view it again alongside.
|
|
290
|
+
//
|
|
291
|
+
// In `sqlite.ts`:
|
|
292
|
+
// public async switchSession(targetSessionId: string): Promise<void> { ... }
|
|
293
|
+
// It updates DB.
|
|
294
|
+
// It DOES NOT update `this.sessionId`.
|
|
295
|
+
//
|
|
296
|
+
// So we need to ensure `this.history` points to the new session.
|
|
297
|
+
// Since `SQLiteChatMessageHistory` might not allow changing `sessionId` publicly if it's protected/private...
|
|
298
|
+
// It is `private sessionId: string;`.
|
|
299
|
+
//
|
|
300
|
+
// So simple fix: Re-instantiate `this.history`?
|
|
301
|
+
// `this.history = new SQLiteChatMessageHistory({ sessionId: sessionId, ... })`
|
|
302
|
+
//
|
|
303
|
+
// This is safe and clean.
|
|
304
|
+
await this.history.switchSession(sessionId);
|
|
305
|
+
// Re-instantiate to point to new session
|
|
306
|
+
this.history = new SQLiteChatMessageHistory({
|
|
307
|
+
sessionId: sessionId,
|
|
308
|
+
databasePath: this.databasePath,
|
|
309
|
+
limit: this.config.llm?.context_window ?? 100
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
throw new Error("Current history provider does not support session switching.");
|
|
314
|
+
}
|
|
315
|
+
}
|
|
260
316
|
async clearMemory() {
|
|
261
317
|
if (!this.history) {
|
|
262
318
|
throw new Error("Message history not initialized. Call initialize() first.");
|
package/dist/types/config.js
CHANGED