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.
@@ -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)
@@ -78,7 +79,7 @@ export const startCommand = new Command('start')
78
79
  // Initialize Web UI
79
80
  if (options.ui && config.ui.enabled) {
80
81
  try {
81
- httpServer = new HttpServer();
82
+ httpServer = new HttpServer(oracle);
82
83
  // Use CLI port if provided and valid, otherwise fallback to config or default
83
84
  const port = parseInt(options.port) || config.ui.port || 3333;
84
85
  httpServer.start(port);
@@ -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'))) {
@@ -54,18 +54,19 @@ export class ConfigManager {
54
54
  context_window: config.llm.context_window !== undefined ? resolveNumeric('MORPHEUS_LLM_CONTEXT_WINDOW', config.llm.context_window, DEFAULT_CONFIG.llm.context_window) : undefined
55
55
  };
56
56
  // Apply precedence to Sati config
57
- let santiConfig;
58
- if (config.santi) {
59
- const santiProvider = resolveProvider('MORPHEUS_SANTI_PROVIDER', config.santi.provider, llmConfig.provider);
60
- santiConfig = {
61
- provider: santiProvider,
62
- model: resolveModel(santiProvider, 'MORPHEUS_SANTI_MODEL', config.santi.model || llmConfig.model),
63
- temperature: resolveNumeric('MORPHEUS_SANTI_TEMPERATURE', config.santi.temperature, llmConfig.temperature),
64
- max_tokens: config.santi.max_tokens !== undefined ? resolveNumeric('MORPHEUS_SANTI_MAX_TOKENS', config.santi.max_tokens, config.santi.max_tokens) : llmConfig.max_tokens,
65
- api_key: resolveApiKey(santiProvider, 'MORPHEUS_SANTI_API_KEY', config.santi.api_key || llmConfig.api_key),
66
- base_url: config.santi.base_url || config.llm.base_url,
67
- context_window: config.santi.context_window !== undefined ? resolveNumeric('MORPHEUS_SANTI_CONTEXT_WINDOW', config.santi.context_window, config.santi.context_window) : llmConfig.context_window,
68
- memory_limit: config.santi.memory_limit !== undefined ? resolveNumeric('MORPHEUS_SANTI_MEMORY_LIMIT', config.santi.memory_limit, config.santi.memory_limit) : undefined
57
+ let satiConfig;
58
+ if (config.sati) {
59
+ const satiProvider = resolveProvider('MORPHEUS_SATI_PROVIDER', config.sati.provider, llmConfig.provider);
60
+ satiConfig = {
61
+ provider: satiProvider,
62
+ model: resolveModel(satiProvider, 'MORPHEUS_SATI_MODEL', config.sati.model || llmConfig.model),
63
+ temperature: resolveNumeric('MORPHEUS_SATI_TEMPERATURE', config.sati.temperature, llmConfig.temperature),
64
+ max_tokens: config.sati.max_tokens !== undefined ? resolveNumeric('MORPHEUS_SATI_MAX_TOKENS', config.sati.max_tokens, config.sati.max_tokens) : llmConfig.max_tokens,
65
+ api_key: resolveApiKey(satiProvider, 'MORPHEUS_SATI_API_KEY', config.sati.api_key || llmConfig.api_key),
66
+ base_url: config.sati.base_url || config.llm.base_url,
67
+ context_window: config.sati.context_window !== undefined ? resolveNumeric('MORPHEUS_SATI_CONTEXT_WINDOW', config.sati.context_window, config.sati.context_window) : llmConfig.context_window,
68
+ memory_limit: config.sati.memory_limit !== undefined ? resolveNumeric('MORPHEUS_SATI_MEMORY_LIMIT', config.sati.memory_limit, config.sati.memory_limit) : undefined,
69
+ enabled_archived_sessions: resolveBoolean('MORPHEUS_SATI_ENABLED_ARCHIVED_SESSIONS', config.sati.enabled_archived_sessions, true)
69
70
  };
70
71
  }
71
72
  // Apply precedence to audio config
@@ -107,7 +108,7 @@ export class ConfigManager {
107
108
  return {
108
109
  agent: agentConfig,
109
110
  llm: llmConfig,
110
- santi: santiConfig,
111
+ sati: satiConfig,
111
112
  audio: audioConfig,
112
113
  channels: channelsConfig,
113
114
  ui: uiConfig,
@@ -137,10 +138,10 @@ export class ConfigManager {
137
138
  return this.config.llm;
138
139
  }
139
140
  getSatiConfig() {
140
- if (this.config.santi) {
141
+ if (this.config.sati) {
141
142
  return {
142
143
  memory_limit: 10, // Default if undefined
143
- ...this.config.santi
144
+ ...this.config.sati
144
145
  };
145
146
  }
146
147
  // Fallback to main LLM config
@@ -19,6 +19,7 @@ export const LLMConfigSchema = z.object({
19
19
  });
20
20
  export const SatiConfigSchema = LLMConfigSchema.extend({
21
21
  memory_limit: z.number().int().positive().optional(),
22
+ enabled_archived_sessions: z.boolean().default(true),
22
23
  });
23
24
  // Zod Schema matching MorpheusConfig interface
24
25
  export const ConfigSchema = z.object({
@@ -27,7 +28,7 @@ export const ConfigSchema = z.object({
27
28
  personality: z.string().default(DEFAULT_CONFIG.agent.personality),
28
29
  }).default(DEFAULT_CONFIG.agent),
29
30
  llm: LLMConfigSchema.default(DEFAULT_CONFIG.llm),
30
- santi: SatiConfigSchema.optional(),
31
+ sati: SatiConfigSchema.optional(),
31
32
  audio: AudioConfigSchema.default(DEFAULT_CONFIG.audio),
32
33
  memory: z.object({
33
34
  limit: z.number().int().positive().optional(),
@@ -27,10 +27,15 @@ describe('Config API', () => {
27
27
  log: vi.fn(),
28
28
  };
29
29
  DisplayManager.getInstance.mockReturnValue(mockDisplayManager);
30
+ // Mock Oracle instance
31
+ const mockOracle = {
32
+ think: vi.fn(),
33
+ getMemory: vi.fn(),
34
+ };
30
35
  // Setup App
31
36
  app = express();
32
37
  app.use(bodyParser.json());
33
- app.use('/api', createApiRouter());
38
+ app.use('/api', createApiRouter(mockOracle));
34
39
  });
35
40
  afterEach(() => {
36
41
  vi.restoreAllMocks();
package/dist/http/api.js CHANGED
@@ -20,9 +20,166 @@ async function readLastLines(filePath, n) {
20
20
  return [];
21
21
  }
22
22
  }
23
- export function createApiRouter() {
23
+ export function createApiRouter(oracle) {
24
24
  const router = Router();
25
25
  const configManager = ConfigManager.getInstance();
26
+ const history = new SQLiteChatMessageHistory({ sessionId: 'api-reader' });
27
+ // --- Session Management ---
28
+ router.get('/sessions', async (req, res) => {
29
+ try {
30
+ const allSessions = await history.listSessions();
31
+ res.json(allSessions);
32
+ }
33
+ catch (err) {
34
+ res.status(500).json({ error: err.message });
35
+ }
36
+ });
37
+ router.post('/sessions', async (req, res) => {
38
+ try {
39
+ await history.createNewSession();
40
+ const newSessionId = await history.getCurrentSessionOrCreate(); // Should be the new one
41
+ res.json({ success: true, id: newSessionId, message: 'New session started' });
42
+ }
43
+ catch (err) {
44
+ res.status(500).json({ error: err.message });
45
+ }
46
+ });
47
+ router.delete('/sessions/:id', async (req, res) => {
48
+ try {
49
+ const { id } = req.params;
50
+ await history.deleteSession(id);
51
+ res.json({ success: true, message: 'Session deleted' });
52
+ }
53
+ catch (err) {
54
+ res.status(500).json({ error: err.message });
55
+ }
56
+ });
57
+ router.post('/sessions/:id/archive', async (req, res) => {
58
+ try {
59
+ const { id } = req.params;
60
+ await history.archiveSession(id);
61
+ res.json({ success: true, message: 'Session archived' });
62
+ }
63
+ catch (err) {
64
+ res.status(500).json({ error: err.message });
65
+ }
66
+ });
67
+ router.patch('/sessions/:id/title', async (req, res) => {
68
+ try {
69
+ const { id } = req.params;
70
+ const { title } = req.body;
71
+ if (!title) {
72
+ return res.status(400).json({ error: 'Title is required' });
73
+ }
74
+ await history.renameSession(id, title);
75
+ res.json({ success: true, message: 'Session renamed' });
76
+ }
77
+ catch (err) {
78
+ res.status(500).json({ error: err.message });
79
+ }
80
+ });
81
+ router.get('/sessions/:id/messages', async (req, res) => {
82
+ try {
83
+ const { id } = req.params;
84
+ const sessionHistory = new SQLiteChatMessageHistory({ sessionId: id, limit: 100 });
85
+ const messages = await sessionHistory.getMessages();
86
+ // Normalize messages for UI
87
+ const key = (msg) => msg._getType(); // Access internal type if available, or infer
88
+ const normalizedMessages = messages.map((msg) => {
89
+ const type = msg._getType ? msg._getType() : 'unknown';
90
+ return {
91
+ type,
92
+ content: msg.content,
93
+ tool_calls: msg.tool_calls,
94
+ usage_metadata: msg.usage_metadata
95
+ };
96
+ });
97
+ // Reverse to chronological order for UI
98
+ res.json(normalizedMessages.reverse());
99
+ }
100
+ catch (err) {
101
+ res.status(500).json({ error: err.message });
102
+ }
103
+ });
104
+ // --- Chat Interaction ---
105
+ router.post('/chat', async (req, res) => {
106
+ try {
107
+ const { message, sessionId } = req.body;
108
+ if (!message || !sessionId) {
109
+ return res.status(400).json({ error: 'Message and Session ID are required' });
110
+ }
111
+ // We need to ensure the Oracle uses the correct session history.
112
+ // The Oracle class uses its own internal history instance.
113
+ // We might need to refactor Oracle to accept a session ID per request or
114
+ // instantiate a temporary Oracle wrapper/context?
115
+ //
116
+ // ACTUALLY: The Oracle class uses `this.history`.
117
+ // `SQLiteChatMessageHistory` takes `sessionId` in constructor.
118
+ // To support multi-session chat via API, we should probably allow passing sessionId to `chat()`
119
+ // OR (cleaner for now) we can rely on the fact that `Oracle` might not support swapping sessions easily without
120
+ // re-initialization or we extend `oracle.chat` to support overriding session.
121
+ //
122
+ // Let's look at `Oracle.chat`: it uses `this.history`.
123
+ // And `SQLiteChatMessageHistory` is tied to a sessionId.
124
+ //
125
+ // Quick fix for this feature:
126
+ // We can use a trick: `Oracle` allows `overrides` in constructor but that's for db path.
127
+ // `Oracle` initializes `this.history` in `initialize`.
128
+ // Better approach:
129
+ // We can temporarily switch the session of the Oracle's history if it exposes it,
130
+ // OR we just instantiate a fresh history for the chat request and use the provider?
131
+ // No, `oracle.chat` encapsulates the provider invocation.
132
+ // Let's check `Oracle` class again. (I viewed it earlier).
133
+ // It has `private history`.
134
+ // SOLUTION:
135
+ // I will add a `setSessionId(id: string)` method to `Oracle` interface and class.
136
+ // OR pass `sessionId` to `chat`.
137
+ // For now, I will assume I can update `Oracle` to support dynamic sessions.
138
+ // I'll modify `Oracle.chat` signature in a separate step if needed.
139
+ // Wait, `Oracle` is a singleton-ish in `start.ts`.
140
+ // Let's modify `Oracle` to accept `sessionId` in `chat`?
141
+ // `chat(message: string, extraUsage?: UsageMetadata, isTelephonist?: boolean)`
142
+ //
143
+ // Adding `sessionId` to `chat` seems invasive if not threaded fast.
144
+ //
145
+ // Alternative:
146
+ // `router.post('/chat')` instantiates a *new* Oracle? No, expensive (provider factory).
147
+ //
148
+ // Ideally `Oracle` should be stateless regarding session, or easily switchable.
149
+ // `SQLiteChatMessageHistory` is cheap to instantiate.
150
+ //
151
+ // Let's update `Oracle` to allow switching session.
152
+ // `oracle.switchSession(sessionId)`
153
+ await oracle.setSessionId(sessionId); // Type cast for now, will implement next
154
+ const response = await oracle.chat(message);
155
+ res.json({ response });
156
+ }
157
+ catch (err) {
158
+ res.status(500).json({ error: err.message });
159
+ }
160
+ });
161
+ // Legacy /session/reset (keep for backward compat or redirect to POST /sessions)
162
+ router.post('/session/reset', async (req, res) => {
163
+ try {
164
+ await history.createNewSession();
165
+ res.json({ success: true, message: 'New session started' });
166
+ }
167
+ catch (err) {
168
+ res.status(500).json({ error: err.message });
169
+ }
170
+ });
171
+ router.post('/session/status', async (req, res) => {
172
+ try {
173
+ const sessionStatus = await history.getSessionStatus();
174
+ if (!sessionStatus) {
175
+ return res.status(404).json({ error: 'No session found' });
176
+ }
177
+ res.json(sessionStatus);
178
+ }
179
+ catch (err) {
180
+ res.status(500).json({ error: err.message });
181
+ }
182
+ });
26
183
  router.get('/status', async (req, res) => {
27
184
  let version = 'unknown';
28
185
  try {
@@ -154,7 +311,7 @@ export function createApiRouter() {
154
311
  router.post('/config/sati', async (req, res) => {
155
312
  try {
156
313
  const config = configManager.get();
157
- await configManager.save({ ...config, santi: req.body });
314
+ await configManager.save({ ...config, sati: req.body });
158
315
  const display = DisplayManager.getInstance();
159
316
  display.log('Sati configuration updated via UI', {
160
317
  source: 'Zaion',
@@ -174,7 +331,7 @@ export function createApiRouter() {
174
331
  router.delete('/config/sati', async (req, res) => {
175
332
  try {
176
333
  const config = configManager.get();
177
- const { santi, ...restConfig } = config;
334
+ const { sati: sati, ...restConfig } = config;
178
335
  await configManager.save(restConfig);
179
336
  const display = DisplayManager.getInstance();
180
337
  display.log('Sati configuration removed via UI (falling back to Oracle config)', {
@@ -12,8 +12,10 @@ const __dirname = path.dirname(__filename);
12
12
  export class HttpServer {
13
13
  app;
14
14
  server;
15
- constructor() {
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));
@@ -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
  }