@ouro.bot/cli 0.1.0-alpha.505 → 0.1.0-alpha.506

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/changelog.json CHANGED
@@ -1,6 +1,14 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.506",
6
+ "changes": [
7
+ "Detect duplicate tool_call_id across assistant messages in `validateSessionMessages`. MiniMax-M2.7 emits canonical tool_call ids of the form `call_function_<hash>_<n>` and reuses the same id across turns when the same function gets called — which causes provider rejections on replay because tool_call_id is supposed to be unique per request. The session sanitize pass already had position-aware orphan detection (#613) and inline-reasoning strip (#612); this adds the third member of the family — collision detection.",
8
+ "New exported `detectDuplicateToolCallIds(messages)` returns `{ id, indices }[]` for each tool_call_id that appears in multiple assistant messages. Same-message duplicates (one assistant calling the same id twice) are not flagged — those are a legitimate parallel-call shape. `validateSessionMessages` now folds collisions into its violations list with a message that calls out MiniMax specifically so operators reading nerves know what they're looking at.",
9
+ "Detection only — no rewriting yet, since rewriting tool_call_ids and the matching tool_results requires careful pairing logic that risks regression. The collision is visible to operators via the `mind.session_invariant_violation` nerves event the sanitize pass already emits when violations are present, and the existing `nerves-review` CLI from #622 makes it filterable. 3 new tests cover collision detection, single-message parallel-call shape (no false positive), and the all-distinct happy path."
10
+ ]
11
+ },
4
12
  {
5
13
  "version": "0.1.0-alpha.505",
6
14
  "changes": [
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.normalizeContinuityState = normalizeContinuityState;
37
37
  exports.validateSessionMessages = validateSessionMessages;
38
+ exports.detectDuplicateToolCallIds = detectDuplicateToolCallIds;
38
39
  exports.repairSessionMessages = repairSessionMessages;
39
40
  exports.migrateToolNames = migrateToolNames;
40
41
  exports.sanitizeProviderMessages = sanitizeProviderMessages;
@@ -305,8 +306,49 @@ function validateSessionMessages(messages) {
305
306
  sawToolResultSincePrevAssistant = false;
306
307
  prevNonToolRole = msg.role;
307
308
  }
309
+ for (const collision of detectDuplicateToolCallIds(messages)) {
310
+ violations.push(`duplicate tool_call_id '${collision.id}' across assistant messages at indices ${collision.indices.join(", ")} — provider may reject (MiniMax canonicalizes call_function_<hash>_<n> across turns)`);
311
+ }
308
312
  return violations;
309
313
  }
314
+ /**
315
+ * Detect tool_call_ids that appear in more than one assistant message
316
+ * within the conversation. MiniMax-M2.7 in particular emits canonical
317
+ * ids of the form `call_function_<hash>_<n>` and reuses the same id
318
+ * across turns when the same function is called — which causes provider
319
+ * rejections on replay because tool_call_id is supposed to be unique
320
+ * per request. We don't (yet) rewrite these here; this function exists
321
+ * so the sanitize pipeline can surface the collision through nerves
322
+ * (`mind.session_invariant_violation`) and operators can decide.
323
+ *
324
+ * Same-message duplicates (one assistant calling the same id twice)
325
+ * are not collisions — they're a legitimate parallel call shape and
326
+ * would be handled by the assistant's own emit logic. We only flag
327
+ * cross-message reuse.
328
+ */
329
+ function detectDuplicateToolCallIds(messages) {
330
+ const idsByFirstIndex = new Map();
331
+ for (let i = 0; i < messages.length; i++) {
332
+ const msg = normalizeMessage(messages[i]);
333
+ if (msg.role !== "assistant")
334
+ continue;
335
+ const seenInThisMessage = new Set();
336
+ for (const call of msg.toolCalls) {
337
+ if (!call.id || seenInThisMessage.has(call.id))
338
+ continue;
339
+ seenInThisMessage.add(call.id);
340
+ const indices = idsByFirstIndex.get(call.id) ?? [];
341
+ indices.push(i);
342
+ idsByFirstIndex.set(call.id, indices);
343
+ }
344
+ }
345
+ const collisions = [];
346
+ for (const [id, indices] of idsByFirstIndex) {
347
+ if (indices.length > 1)
348
+ collisions.push({ id, indices });
349
+ }
350
+ return collisions;
351
+ }
310
352
  function repairSessionMessages(messages) {
311
353
  const normalized = messages.map(normalizeMessage);
312
354
  const violations = validateSessionMessages(messages);
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.validateSessionMessages = exports.repairSessionMessages = exports.migrateToolNames = void 0;
36
+ exports.validateSessionMessages = exports.repairSessionMessages = exports.migrateToolNames = exports.detectDuplicateToolCallIds = void 0;
37
37
  exports.trimMessages = trimMessages;
38
38
  exports.saveSession = saveSession;
39
39
  exports.appendSyntheticAssistantMessage = appendSyntheticAssistantMessage;
@@ -50,6 +50,7 @@ const fs = __importStar(require("fs"));
50
50
  const path = __importStar(require("path"));
51
51
  const token_estimate_1 = require("./token-estimate");
52
52
  var session_events_2 = require("../heart/session-events");
53
+ Object.defineProperty(exports, "detectDuplicateToolCallIds", { enumerable: true, get: function () { return session_events_2.detectDuplicateToolCallIds; } });
53
54
  Object.defineProperty(exports, "migrateToolNames", { enumerable: true, get: function () { return session_events_2.migrateToolNames; } });
54
55
  Object.defineProperty(exports, "repairSessionMessages", { enumerable: true, get: function () { return session_events_2.repairSessionMessages; } });
55
56
  Object.defineProperty(exports, "validateSessionMessages", { enumerable: true, get: function () { return session_events_2.validateSessionMessages; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.505",
3
+ "version": "0.1.0-alpha.506",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",
@@ -37,8 +37,7 @@
37
37
  "lint": "eslint src/",
38
38
  "release:preflight": "node scripts/release-preflight.cjs",
39
39
  "release:smoke": "node scripts/release-smoke.cjs",
40
- "audit:nerves": "npm run build && node dist/nerves/coverage/cli-main.js",
41
- "session:stats": "npm run build && node dist/heart/session-stats-cli-main.js"
40
+ "audit:nerves": "npm run build && node dist/nerves/coverage/cli-main.js"
42
41
  },
43
42
  "dependencies": {
44
43
  "@anthropic-ai/sdk": "^0.78.0",