morpheus-cli 0.3.8 → 0.4.0
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.
|
@@ -23,6 +23,16 @@ export class TelegramAdapter {
|
|
|
23
23
|
telephonistProvider = null;
|
|
24
24
|
telephonistModel = null;
|
|
25
25
|
history = new SQLiteChatMessageHistory({ sessionId: '' });
|
|
26
|
+
RATE_LIMIT_MS = 3000; // minimum ms between requests per user
|
|
27
|
+
rateLimiter = new Map(); // userId -> last request timestamp
|
|
28
|
+
isRateLimited(userId) {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
const last = this.rateLimiter.get(userId);
|
|
31
|
+
if (last !== undefined && now - last < this.RATE_LIMIT_MS)
|
|
32
|
+
return true;
|
|
33
|
+
this.rateLimiter.set(userId, now);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
26
36
|
HELP_MESSAGE = `/start - Show this welcome message and available commands
|
|
27
37
|
/status - Check the status of the Morpheus agent
|
|
28
38
|
/doctor - Diagnose environment and configuration issues
|
|
@@ -61,11 +71,16 @@ export class TelegramAdapter {
|
|
|
61
71
|
return; // Silent fail for security
|
|
62
72
|
}
|
|
63
73
|
this.display.log(`@${user}: ${text}`, { source: 'Telegram' });
|
|
64
|
-
// Handle system commands
|
|
74
|
+
// Handle system commands (commands bypass rate limit)
|
|
65
75
|
if (text.startsWith('/')) {
|
|
66
76
|
await this.handleSystemCommand(ctx, text, user);
|
|
67
77
|
return;
|
|
68
78
|
}
|
|
79
|
+
// Rate limit check
|
|
80
|
+
if (this.isRateLimited(userId)) {
|
|
81
|
+
await ctx.reply('Please wait a moment before sending another message.');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
69
84
|
try {
|
|
70
85
|
// Send "typing" status
|
|
71
86
|
await ctx.sendChatAction('typing');
|
|
@@ -96,6 +111,11 @@ export class TelegramAdapter {
|
|
|
96
111
|
this.display.log(`Unauthorized audio attempt by @${user} (ID: ${userId})`, { source: 'Telegram', level: 'warning' });
|
|
97
112
|
return;
|
|
98
113
|
}
|
|
114
|
+
// Rate limit check
|
|
115
|
+
if (this.isRateLimited(userId)) {
|
|
116
|
+
await ctx.reply('Please wait a moment before sending another message.');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
99
119
|
if (!config.audio.enabled) {
|
|
100
120
|
await ctx.reply("Audio transcription is currently disabled.");
|
|
101
121
|
return;
|
package/dist/http/api.js
CHANGED
|
@@ -80,12 +80,11 @@ export function createApiRouter(oracle) {
|
|
|
80
80
|
}
|
|
81
81
|
});
|
|
82
82
|
router.get('/sessions/:id/messages', async (req, res) => {
|
|
83
|
+
const { id } = req.params;
|
|
84
|
+
const sessionHistory = new SQLiteChatMessageHistory({ sessionId: id, limit: 100 });
|
|
83
85
|
try {
|
|
84
|
-
const { id } = req.params;
|
|
85
|
-
const sessionHistory = new SQLiteChatMessageHistory({ sessionId: id, limit: 100 });
|
|
86
86
|
const messages = await sessionHistory.getMessages();
|
|
87
87
|
// Normalize messages for UI
|
|
88
|
-
const key = (msg) => msg._getType(); // Access internal type if available, or infer
|
|
89
88
|
const normalizedMessages = messages.map((msg) => {
|
|
90
89
|
const type = msg._getType ? msg._getType() : 'unknown';
|
|
91
90
|
return {
|
|
@@ -101,57 +100,23 @@ export function createApiRouter(oracle) {
|
|
|
101
100
|
catch (err) {
|
|
102
101
|
res.status(500).json({ error: err.message });
|
|
103
102
|
}
|
|
103
|
+
finally {
|
|
104
|
+
sessionHistory.close();
|
|
105
|
+
}
|
|
104
106
|
});
|
|
105
107
|
// --- Chat Interaction ---
|
|
108
|
+
const ChatSchema = z.object({
|
|
109
|
+
message: z.string().min(1).max(32_000),
|
|
110
|
+
sessionId: z.string().min(1)
|
|
111
|
+
});
|
|
106
112
|
router.post('/chat', async (req, res) => {
|
|
113
|
+
const parsed = ChatSchema.safeParse(req.body);
|
|
114
|
+
if (!parsed.success) {
|
|
115
|
+
return res.status(400).json({ error: 'Invalid input', details: parsed.error.message });
|
|
116
|
+
}
|
|
107
117
|
try {
|
|
108
|
-
const { message, sessionId } =
|
|
109
|
-
|
|
110
|
-
return res.status(400).json({ error: 'Message and Session ID are required' });
|
|
111
|
-
}
|
|
112
|
-
// We need to ensure the Oracle uses the correct session history.
|
|
113
|
-
// The Oracle class uses its own internal history instance.
|
|
114
|
-
// We might need to refactor Oracle to accept a session ID per request or
|
|
115
|
-
// instantiate a temporary Oracle wrapper/context?
|
|
116
|
-
//
|
|
117
|
-
// ACTUALLY: The Oracle class uses `this.history`.
|
|
118
|
-
// `SQLiteChatMessageHistory` takes `sessionId` in constructor.
|
|
119
|
-
// To support multi-session chat via API, we should probably allow passing sessionId to `chat()`
|
|
120
|
-
// OR (cleaner for now) we can rely on the fact that `Oracle` might not support swapping sessions easily without
|
|
121
|
-
// re-initialization or we extend `oracle.chat` to support overriding session.
|
|
122
|
-
//
|
|
123
|
-
// Let's look at `Oracle.chat`: it uses `this.history`.
|
|
124
|
-
// And `SQLiteChatMessageHistory` is tied to a sessionId.
|
|
125
|
-
//
|
|
126
|
-
// Quick fix for this feature:
|
|
127
|
-
// We can use a trick: `Oracle` allows `overrides` in constructor but that's for db path.
|
|
128
|
-
// `Oracle` initializes `this.history` in `initialize`.
|
|
129
|
-
// Better approach:
|
|
130
|
-
// We can temporarily switch the session of the Oracle's history if it exposes it,
|
|
131
|
-
// OR we just instantiate a fresh history for the chat request and use the provider?
|
|
132
|
-
// No, `oracle.chat` encapsulates the provider invocation.
|
|
133
|
-
// Let's check `Oracle` class again. (I viewed it earlier).
|
|
134
|
-
// It has `private history`.
|
|
135
|
-
// SOLUTION:
|
|
136
|
-
// I will add a `setSessionId(id: string)` method to `Oracle` interface and class.
|
|
137
|
-
// OR pass `sessionId` to `chat`.
|
|
138
|
-
// For now, I will assume I can update `Oracle` to support dynamic sessions.
|
|
139
|
-
// I'll modify `Oracle.chat` signature in a separate step if needed.
|
|
140
|
-
// Wait, `Oracle` is a singleton-ish in `start.ts`.
|
|
141
|
-
// Let's modify `Oracle` to accept `sessionId` in `chat`?
|
|
142
|
-
// `chat(message: string, extraUsage?: UsageMetadata, isTelephonist?: boolean)`
|
|
143
|
-
//
|
|
144
|
-
// Adding `sessionId` to `chat` seems invasive if not threaded fast.
|
|
145
|
-
//
|
|
146
|
-
// Alternative:
|
|
147
|
-
// `router.post('/chat')` instantiates a *new* Oracle? No, expensive (provider factory).
|
|
148
|
-
//
|
|
149
|
-
// Ideally `Oracle` should be stateless regarding session, or easily switchable.
|
|
150
|
-
// `SQLiteChatMessageHistory` is cheap to instantiate.
|
|
151
|
-
//
|
|
152
|
-
// Let's update `Oracle` to allow switching session.
|
|
153
|
-
// `oracle.switchSession(sessionId)`
|
|
154
|
-
await oracle.setSessionId(sessionId); // Type cast for now, will implement next
|
|
118
|
+
const { message, sessionId } = parsed.data;
|
|
119
|
+
await oracle.setSessionId(sessionId);
|
|
155
120
|
const response = await oracle.chat(message);
|
|
156
121
|
res.json({ response });
|
|
157
122
|
}
|
|
@@ -2,6 +2,8 @@ import { pipeline } from '@xenova/transformers';
|
|
|
2
2
|
export class EmbeddingService {
|
|
3
3
|
static instance;
|
|
4
4
|
extractor;
|
|
5
|
+
MAX_CACHE_SIZE = 256;
|
|
6
|
+
cache = new Map(); // text prefix -> embedding
|
|
5
7
|
constructor() { }
|
|
6
8
|
static async getInstance() {
|
|
7
9
|
if (!EmbeddingService.instance) {
|
|
@@ -12,10 +14,22 @@ export class EmbeddingService {
|
|
|
12
14
|
return EmbeddingService.instance;
|
|
13
15
|
}
|
|
14
16
|
async generate(text) {
|
|
17
|
+
const cacheKey = text.slice(0, 200);
|
|
18
|
+
const cached = this.cache.get(cacheKey);
|
|
19
|
+
if (cached)
|
|
20
|
+
return cached;
|
|
15
21
|
const output = await this.extractor(text, {
|
|
16
22
|
pooling: 'mean',
|
|
17
23
|
normalize: true,
|
|
18
24
|
});
|
|
19
|
-
|
|
25
|
+
const embedding = Array.from(output.data);
|
|
26
|
+
if (this.cache.size >= this.MAX_CACHE_SIZE) {
|
|
27
|
+
// Evict oldest entry (FIFO)
|
|
28
|
+
const firstKey = this.cache.keys().next().value;
|
|
29
|
+
if (firstKey !== undefined)
|
|
30
|
+
this.cache.delete(firstKey);
|
|
31
|
+
}
|
|
32
|
+
this.cache.set(cacheKey, embedding);
|
|
33
|
+
return embedding;
|
|
20
34
|
}
|
|
21
35
|
}
|
|
@@ -83,6 +83,9 @@ export class SatiRepository {
|
|
|
83
83
|
vec_rowid INTEGER NOT NULL
|
|
84
84
|
);
|
|
85
85
|
|
|
86
|
+
CREATE INDEX IF NOT EXISTS idx_embedding_map_vec_rowid
|
|
87
|
+
ON memory_embedding_map(vec_rowid);
|
|
88
|
+
|
|
86
89
|
-- ===============================
|
|
87
90
|
-- 4️⃣ TRIGGERS FTS
|
|
88
91
|
-- ===============================
|
|
@@ -129,6 +132,9 @@ export class SatiRepository {
|
|
|
129
132
|
vec_rowid INTEGER NOT NULL
|
|
130
133
|
);
|
|
131
134
|
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_session_embedding_map_vec_rowid
|
|
136
|
+
ON session_embedding_map(vec_rowid);
|
|
137
|
+
|
|
132
138
|
`);
|
|
133
139
|
}
|
|
134
140
|
// 🔥 NOVO — Salvar embedding
|
|
@@ -162,41 +168,47 @@ export class SatiRepository {
|
|
|
162
168
|
transaction();
|
|
163
169
|
}
|
|
164
170
|
// 🔥 NOVO — Busca vetorial
|
|
165
|
-
searchByVector(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
171
|
+
// private searchByVector(
|
|
172
|
+
// embedding: number[],
|
|
173
|
+
// limit: number
|
|
174
|
+
// ): IMemoryRecord[] {
|
|
175
|
+
// if (!this.db) return [];
|
|
176
|
+
// const SIMILARITY_THRESHOLD = 0.5; // ajuste fino depois
|
|
177
|
+
// const stmt = this.db.prepare(`
|
|
178
|
+
// SELECT
|
|
179
|
+
// m.*,
|
|
180
|
+
// vec_distance_cosine(v.embedding, ?) as distance
|
|
181
|
+
// FROM memory_vec v
|
|
182
|
+
// JOIN memory_embedding_map map ON map.vec_rowid = v.rowid
|
|
183
|
+
// JOIN long_term_memory m ON m.id = map.memory_id
|
|
184
|
+
// WHERE m.archived = 0
|
|
185
|
+
// ORDER BY distance ASC
|
|
186
|
+
// LIMIT ?
|
|
187
|
+
// `);
|
|
188
|
+
// const rows = stmt.all(
|
|
189
|
+
// new Float32Array(embedding),
|
|
190
|
+
// limit
|
|
191
|
+
// ) as any[];
|
|
192
|
+
// // 🔥 Filtrar por similaridade real
|
|
193
|
+
// const ranked = rows
|
|
194
|
+
// .map(r => ({
|
|
195
|
+
// ...r,
|
|
196
|
+
// similarity: 1 - r.distance
|
|
197
|
+
// }));
|
|
198
|
+
// const filtered = ranked
|
|
199
|
+
// .filter(r => r.distance >= SIMILARITY_THRESHOLD)
|
|
200
|
+
// .sort((a, b) => b.similarity - a.similarity);
|
|
201
|
+
// if (filtered.length > 0) {
|
|
202
|
+
// console.log(
|
|
203
|
+
// `[SatiRepository] Vector hit (${filtered.length})`
|
|
204
|
+
// );
|
|
205
|
+
// }
|
|
206
|
+
// return filtered.map(this.mapRowToRecord);
|
|
207
|
+
// }
|
|
196
208
|
searchUnifiedVector(embedding, limit) {
|
|
197
209
|
if (!this.db)
|
|
198
210
|
return [];
|
|
199
|
-
const SIMILARITY_THRESHOLD = 0.
|
|
211
|
+
const SIMILARITY_THRESHOLD = 0.8;
|
|
200
212
|
const stmt = this.db.prepare(`
|
|
201
213
|
SELECT *
|
|
202
214
|
FROM (
|
|
@@ -208,7 +220,7 @@ export class SatiRepository {
|
|
|
208
220
|
m.category as category,
|
|
209
221
|
m.importance as importance,
|
|
210
222
|
'long_term' as source_type,
|
|
211
|
-
(1 - vec_distance_cosine(v.embedding, ?)) *
|
|
223
|
+
(1 - vec_distance_cosine(v.embedding, ?)) * 1.7 as distance
|
|
212
224
|
FROM memory_vec v
|
|
213
225
|
JOIN memory_embedding_map map ON map.vec_rowid = v.rowid
|
|
214
226
|
JOIN long_term_memory m ON m.id = map.memory_id
|
|
@@ -224,7 +236,7 @@ export class SatiRepository {
|
|
|
224
236
|
'session' as category,
|
|
225
237
|
'medium' as importance,
|
|
226
238
|
'session_chunk' as source_type,
|
|
227
|
-
(1 - vec_distance_cosine(v.embedding, ?)) * 0.
|
|
239
|
+
(1 - vec_distance_cosine(v.embedding, ?)) * 0.5 as distance
|
|
228
240
|
FROM session_vec v
|
|
229
241
|
JOIN session_embedding_map map ON map.vec_rowid = v.rowid
|
|
230
242
|
JOIN session_chunks sc ON sc.id = map.session_chunk_id
|
|
@@ -240,6 +252,8 @@ export class SatiRepository {
|
|
|
240
252
|
// rows.forEach((row, index) => {
|
|
241
253
|
// console.log(`[SatiRepository] Row ${index + 1}:`, row);
|
|
242
254
|
// });
|
|
255
|
+
// Note: the SQL query already computes distance as (1 - cosine_distance) * weight,
|
|
256
|
+
// so higher values mean higher similarity. Use distance directly as similarity score.
|
|
243
257
|
const ranked = rows
|
|
244
258
|
.map(r => ({
|
|
245
259
|
...r,
|
|
@@ -13,6 +13,7 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
13
13
|
dbSati; // Optional separate DB for Sati memory, if needed in the future
|
|
14
14
|
sessionId;
|
|
15
15
|
limit;
|
|
16
|
+
titleSet = false; // cache: skip setSessionTitleIfNeeded after title is set
|
|
16
17
|
constructor(fields) {
|
|
17
18
|
super();
|
|
18
19
|
this.sessionId = fields.sessionId && fields.sessionId !== '' ? fields.sessionId : '';
|
|
@@ -110,9 +111,12 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
110
111
|
model TEXT
|
|
111
112
|
);
|
|
112
113
|
|
|
113
|
-
CREATE INDEX IF NOT EXISTS idx_messages_session_id
|
|
114
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session_id
|
|
114
115
|
ON messages(session_id);
|
|
115
116
|
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session_id_id
|
|
118
|
+
ON messages(session_id, id DESC);
|
|
119
|
+
|
|
116
120
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
117
121
|
id TEXT PRIMARY KEY,
|
|
118
122
|
title TEXT,
|
|
@@ -335,11 +339,67 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
335
339
|
throw new Error(`Failed to add message: ${error}`);
|
|
336
340
|
}
|
|
337
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Adds multiple messages in a single SQLite transaction for better performance.
|
|
344
|
+
* Replaces calling addMessage() in a loop when inserting agent-generated messages.
|
|
345
|
+
*/
|
|
346
|
+
async addMessages(messages) {
|
|
347
|
+
if (messages.length === 0)
|
|
348
|
+
return;
|
|
349
|
+
const stmt = this.db.prepare("INSERT INTO messages (session_id, type, content, created_at, input_tokens, output_tokens, total_tokens, cache_read_tokens, provider, model) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
350
|
+
const insertAll = this.db.transaction((msgs) => {
|
|
351
|
+
for (const message of msgs) {
|
|
352
|
+
let type;
|
|
353
|
+
if (message instanceof HumanMessage)
|
|
354
|
+
type = "human";
|
|
355
|
+
else if (message instanceof AIMessage)
|
|
356
|
+
type = "ai";
|
|
357
|
+
else if (message instanceof SystemMessage)
|
|
358
|
+
type = "system";
|
|
359
|
+
else if (message instanceof ToolMessage)
|
|
360
|
+
type = "tool";
|
|
361
|
+
else
|
|
362
|
+
throw new Error(`Unsupported message type: ${message.constructor.name}`);
|
|
363
|
+
const anyMsg = message;
|
|
364
|
+
const usage = anyMsg.usage_metadata || anyMsg.response_metadata?.usage || anyMsg.response_metadata?.tokenUsage || anyMsg.usage;
|
|
365
|
+
let finalContent;
|
|
366
|
+
if (type === 'ai' && (message.tool_calls?.length ?? 0) > 0) {
|
|
367
|
+
finalContent = JSON.stringify({ text: message.content, tool_calls: message.tool_calls });
|
|
368
|
+
}
|
|
369
|
+
else if (type === 'tool') {
|
|
370
|
+
const tm = message;
|
|
371
|
+
finalContent = JSON.stringify({ content: tm.content, tool_call_id: tm.tool_call_id, name: tm.name });
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
finalContent = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
|
|
375
|
+
}
|
|
376
|
+
stmt.run(this.sessionId, type, finalContent, Date.now(), usage?.input_tokens ?? null, usage?.output_tokens ?? null, usage?.total_tokens ?? null, usage?.input_token_details?.cache_read ?? usage?.cache_read_tokens ?? null, anyMsg.provider_metadata?.provider ?? null, anyMsg.provider_metadata?.model ?? null);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
try {
|
|
380
|
+
insertAll(messages);
|
|
381
|
+
await this.setSessionTitleIfNeeded();
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
if (error instanceof Error) {
|
|
385
|
+
if (error.message.includes('SQLITE_BUSY'))
|
|
386
|
+
throw new Error(`Database is locked. Please try again. Original error: ${error.message}`);
|
|
387
|
+
if (error.message.includes('SQLITE_READONLY'))
|
|
388
|
+
throw new Error(`Database is read-only. Check file permissions. Original error: ${error.message}`);
|
|
389
|
+
if (error.message.includes('SQLITE_FULL'))
|
|
390
|
+
throw new Error(`Database is full or disk space is exhausted. Original error: ${error.message}`);
|
|
391
|
+
}
|
|
392
|
+
throw new Error(`Failed to add messages in batch: ${error}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
338
395
|
/**
|
|
339
396
|
* Verifies if the session has a title, and if not, sets it automatically
|
|
340
397
|
* using the first 50 characters of the oldest human message.
|
|
341
398
|
*/
|
|
342
399
|
async setSessionTitleIfNeeded() {
|
|
400
|
+
// Fast path: skip DB query if we already set the title this session
|
|
401
|
+
if (this.titleSet)
|
|
402
|
+
return;
|
|
343
403
|
// Verificar se a sessão já tem título
|
|
344
404
|
const session = this.db.prepare(`
|
|
345
405
|
SELECT title FROM sessions
|
|
@@ -347,6 +407,7 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
347
407
|
`).get(this.sessionId);
|
|
348
408
|
if (session && session.title) {
|
|
349
409
|
// A sessão já tem título, não precisa fazer nada
|
|
410
|
+
this.titleSet = true;
|
|
350
411
|
return;
|
|
351
412
|
}
|
|
352
413
|
// Obter a mensagem mais antiga do tipo "human" da sessão
|
|
@@ -369,6 +430,7 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
369
430
|
}
|
|
370
431
|
// Chamar a função renameSession para definir o título automaticamente
|
|
371
432
|
await this.renameSession(this.sessionId, title);
|
|
433
|
+
this.titleSet = true;
|
|
372
434
|
}
|
|
373
435
|
}
|
|
374
436
|
/**
|
|
@@ -508,6 +570,7 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
508
570
|
`).run(newId, now);
|
|
509
571
|
// Atualizar o ID da sessão atual desta instância
|
|
510
572
|
this.sessionId = newId;
|
|
573
|
+
this.titleSet = false; // reset cache for new session
|
|
511
574
|
});
|
|
512
575
|
tx(); // Executar a transação
|
|
513
576
|
this.display.log('✅ Nova sessão iniciada e sessão anterior pausada', { source: 'Sati' });
|
package/dist/runtime/oracle.js
CHANGED
|
@@ -216,17 +216,15 @@ You maintain intent until resolution.
|
|
|
216
216
|
const startNewMessagesIndex = messages.length;
|
|
217
217
|
const newGeneratedMessages = response.messages.slice(startNewMessagesIndex);
|
|
218
218
|
// console.log('New generated messages', newGeneratedMessages);
|
|
219
|
-
//
|
|
220
|
-
await this.history.addMessage(userMessage);
|
|
221
|
-
// Persist all new intermediate tool calls and responses
|
|
219
|
+
// Inject provider/model metadata into all new messages
|
|
222
220
|
for (const msg of newGeneratedMessages) {
|
|
223
|
-
// Inject provider/model metadata search interactors
|
|
224
221
|
msg.provider_metadata = {
|
|
225
222
|
provider: this.config.llm.provider,
|
|
226
223
|
model: this.config.llm.model
|
|
227
224
|
};
|
|
228
|
-
await this.history.addMessage(msg);
|
|
229
225
|
}
|
|
226
|
+
// Persist user message + all generated messages in a single transaction
|
|
227
|
+
await this.history.addMessages([userMessage, ...newGeneratedMessages]);
|
|
230
228
|
this.display.log('Response generated.', { source: 'Oracle' });
|
|
231
229
|
const lastMessage = response.messages[response.messages.length - 1];
|
|
232
230
|
const responseContent = (typeof lastMessage.content === 'string') ? lastMessage.content : JSON.stringify(lastMessage.content);
|
|
@@ -302,6 +300,8 @@ You maintain intent until resolution.
|
|
|
302
300
|
//
|
|
303
301
|
// This is safe and clean.
|
|
304
302
|
await this.history.switchSession(sessionId);
|
|
303
|
+
// Close previous connection before re-instantiating to avoid file handle leaks
|
|
304
|
+
this.history.close();
|
|
305
305
|
// Re-instantiate to point to new session
|
|
306
306
|
this.history = new SQLiteChatMessageHistory({
|
|
307
307
|
sessionId: sessionId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "morpheus-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Morpheus is a local AI agent for developers, running as a CLI daemon that connects to LLMs, local tools, and MCPs, enabling interaction via Terminal, Telegram, and Discord. Inspired by the character Morpheus from *The Matrix*, the project acts as an intelligent orchestrator, bridging the gap between the developer and complex systems.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"morpheus": "./bin/morpheus.js"
|