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
- return rows.map((row) => {
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.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"