morpheus-cli 0.9.9 → 0.9.10
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.
|
@@ -226,6 +226,54 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
226
226
|
console.warn(`[SQLite] model_pricing migration failed: ${error}`);
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Removes orphaned ToolMessages and incomplete tool-call groups that can
|
|
231
|
+
* appear when the LIMIT clause truncates the message window mid-sequence.
|
|
232
|
+
*
|
|
233
|
+
* Messages arrive in DESC order (newest first). An orphaned ToolMessage at
|
|
234
|
+
* the end of this array means its parent AIMessage (with tool_calls) was
|
|
235
|
+
* outside the window. We also strip AIMessages whose tool_calls have no
|
|
236
|
+
* corresponding ToolMessage responses in the window.
|
|
237
|
+
*/
|
|
238
|
+
sanitizeMessageWindow(messages) {
|
|
239
|
+
if (messages.length === 0)
|
|
240
|
+
return messages;
|
|
241
|
+
// Work in chronological order (reverse of DESC) for easier reasoning.
|
|
242
|
+
const chrono = [...messages].reverse();
|
|
243
|
+
// Drop leading ToolMessages that have no preceding AIMessage with matching tool_calls.
|
|
244
|
+
let startIdx = 0;
|
|
245
|
+
while (startIdx < chrono.length && chrono[startIdx] instanceof ToolMessage) {
|
|
246
|
+
startIdx++;
|
|
247
|
+
}
|
|
248
|
+
// Also drop a leading AIMessage that has tool_calls but whose ToolMessage
|
|
249
|
+
// responses were trimmed (they would have been before it in the DB).
|
|
250
|
+
if (startIdx < chrono.length && chrono[startIdx] instanceof AIMessage) {
|
|
251
|
+
const ai = chrono[startIdx];
|
|
252
|
+
if (ai.tool_calls && ai.tool_calls.length > 0) {
|
|
253
|
+
// Check if ALL tool_call responses exist after this AIMessage
|
|
254
|
+
const toolCallIds = ai.tool_calls.map((tc) => tc.id).filter(Boolean);
|
|
255
|
+
const remaining = chrono.slice(startIdx + 1);
|
|
256
|
+
let allFound = true;
|
|
257
|
+
for (let i = 0; i < toolCallIds.length; i++) {
|
|
258
|
+
const hasResponse = remaining.some((m) => m instanceof ToolMessage && m.tool_call_id === toolCallIds[i]);
|
|
259
|
+
if (!hasResponse) {
|
|
260
|
+
allFound = false;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (!allFound)
|
|
265
|
+
startIdx++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (startIdx === 0) {
|
|
269
|
+
// No sanitization needed — return original DESC order.
|
|
270
|
+
return messages;
|
|
271
|
+
}
|
|
272
|
+
// Return in the original DESC order (newest first), excluding trimmed messages.
|
|
273
|
+
const sanitized = chrono.slice(startIdx);
|
|
274
|
+
sanitized.reverse();
|
|
275
|
+
return sanitized;
|
|
276
|
+
}
|
|
229
277
|
/**
|
|
230
278
|
* Retrieves all messages for the current session from the database.
|
|
231
279
|
* @returns Promise resolving to an array of BaseMessage objects
|
|
@@ -239,7 +287,7 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
239
287
|
ORDER BY id DESC
|
|
240
288
|
LIMIT ?`);
|
|
241
289
|
const rows = stmt.all(this.sessionId, this.limit);
|
|
242
|
-
|
|
290
|
+
const mapped = rows.map((row) => {
|
|
243
291
|
let msg;
|
|
244
292
|
// Reconstruct usage metadata if present
|
|
245
293
|
const usage_metadata = row.total_tokens != null ? {
|
|
@@ -303,6 +351,13 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
303
351
|
}
|
|
304
352
|
return msg;
|
|
305
353
|
});
|
|
354
|
+
// Sanitize: the LIMIT clause may cut in the middle of a tool_calls/ToolMessage
|
|
355
|
+
// sequence, leaving orphaned ToolMessages without a preceding AIMessage that
|
|
356
|
+
// contains the corresponding tool_calls. LLM providers reject this.
|
|
357
|
+
// Messages are in DESC order (newest first) here — orphans appear at the tail.
|
|
358
|
+
// Remove trailing ToolMessages and AIMessages with tool_calls that have no
|
|
359
|
+
// matching ToolMessage response (i.e. incomplete tool-call groups at the boundary).
|
|
360
|
+
return this.sanitizeMessageWindow(mapped);
|
|
306
361
|
}
|
|
307
362
|
catch (error) {
|
|
308
363
|
// Check if it's a database lock error
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "morpheus-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.10",
|
|
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"
|