zidane 5.3.0 → 5.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.
Files changed (64) hide show
  1. package/dist/{agent-CYpPKn5Z.d.ts → agent-bKs7MRT2.d.ts} +430 -5
  2. package/dist/agent-bKs7MRT2.d.ts.map +1 -0
  3. package/dist/chat.d.ts +310 -6
  4. package/dist/chat.d.ts.map +1 -1
  5. package/dist/chat.js +2 -2
  6. package/dist/{errors-COmsomd5.js → errors-Byb0F8B9.js} +44 -2
  7. package/dist/errors-Byb0F8B9.js.map +1 -0
  8. package/dist/{index-D-cTScN3.d.ts → index-BlMvPh9X.d.ts} +57 -10
  9. package/dist/index-BlMvPh9X.d.ts.map +1 -0
  10. package/dist/{index-Cc-q1hLT.d.ts → index-CTmNaIDb.d.ts} +2 -2
  11. package/dist/{index-Cc-q1hLT.d.ts.map → index-CTmNaIDb.d.ts.map} +1 -1
  12. package/dist/index.d.ts +4 -4
  13. package/dist/index.js +10 -10
  14. package/dist/{interpolate-BhmHKD6x.js → interpolate-ERgZUxgg.js} +2 -2
  15. package/dist/{interpolate-BhmHKD6x.js.map → interpolate-ERgZUxgg.js.map} +1 -1
  16. package/dist/{login-BXVt5wuA.js → login-CNS9_8Ue.js} +3 -3
  17. package/dist/{login-BXVt5wuA.js.map → login-CNS9_8Ue.js.map} +1 -1
  18. package/dist/{mcp-B1psg7jf.js → mcp-ZsSFo4Dp.js} +2 -2
  19. package/dist/{mcp-B1psg7jf.js.map → mcp-ZsSFo4Dp.js.map} +1 -1
  20. package/dist/mcp.d.ts +1 -1
  21. package/dist/mcp.js +1 -1
  22. package/dist/{messages-DsbMYNmt.js → messages-D0xT979U.js} +631 -68
  23. package/dist/messages-D0xT979U.js.map +1 -0
  24. package/dist/{presets-tvD28pCu.js → presets-h5i3kpOP.js} +29 -10
  25. package/dist/presets-h5i3kpOP.js.map +1 -0
  26. package/dist/presets.d.ts +2 -2
  27. package/dist/presets.js +1 -1
  28. package/dist/{providers-v1Rn2rqG.js → providers-x3LZByR5.js} +38 -6
  29. package/dist/providers-x3LZByR5.js.map +1 -0
  30. package/dist/providers.d.ts +2 -2
  31. package/dist/providers.js +3 -3
  32. package/dist/session/sqlite.d.ts +1 -1
  33. package/dist/session/sqlite.js +1 -1
  34. package/dist/{session-DOJgRXvF.js → session-BHZwxmfr.js} +2 -2
  35. package/dist/{session-DOJgRXvF.js.map → session-BHZwxmfr.js.map} +1 -1
  36. package/dist/session.d.ts +1 -1
  37. package/dist/session.js +2 -2
  38. package/dist/skills.d.ts +2 -2
  39. package/dist/skills.js +1 -1
  40. package/dist/{tools-CMVruxF0.js → tools-CWEDS2ZT.js} +380 -48
  41. package/dist/tools-CWEDS2ZT.js.map +1 -0
  42. package/dist/tools.d.ts +2 -2
  43. package/dist/tools.js +1 -1
  44. package/dist/{transcript-anchors-eyhlGeBI.d.ts → transcript-anchors-DOUqyvXR.d.ts} +28 -4
  45. package/dist/transcript-anchors-DOUqyvXR.d.ts.map +1 -0
  46. package/dist/tui.d.ts +29 -3
  47. package/dist/tui.d.ts.map +1 -1
  48. package/dist/tui.js +365 -80
  49. package/dist/tui.js.map +1 -1
  50. package/dist/{turn-operations-Y7e15gJf.js → turn-operations-D9HvatsR.js} +678 -33
  51. package/dist/turn-operations-D9HvatsR.js.map +1 -0
  52. package/dist/types-IcokUOyC.js.map +1 -1
  53. package/dist/types.d.ts +2 -2
  54. package/dist/types.js +1 -1
  55. package/package.json +1 -1
  56. package/dist/agent-CYpPKn5Z.d.ts.map +0 -1
  57. package/dist/errors-COmsomd5.js.map +0 -1
  58. package/dist/index-D-cTScN3.d.ts.map +0 -1
  59. package/dist/messages-DsbMYNmt.js.map +0 -1
  60. package/dist/presets-tvD28pCu.js.map +0 -1
  61. package/dist/providers-v1Rn2rqG.js.map +0 -1
  62. package/dist/tools-CMVruxF0.js.map +0 -1
  63. package/dist/transcript-anchors-eyhlGeBI.d.ts.map +0 -1
  64. package/dist/turn-operations-Y7e15gJf.js.map +0 -1
@@ -1,9 +1,9 @@
1
1
  import { n as createProcessContext } from "./contexts-BwiHIr2w.js";
2
- import { c as toTypedError, o as errorMessage, r as AgentProviderError, t as AgentAbortedError } from "./errors-COmsomd5.js";
2
+ import { a as AgentToolPairingError, l as toTypedError, r as AgentProviderError, s as errorMessage, t as AgentAbortedError } from "./errors-Byb0F8B9.js";
3
3
  import { t as toolOutputByteLength } from "./types-IcokUOyC.js";
4
- import { i as sanitizeOrphanedToolCalls } from "./messages-DsbMYNmt.js";
5
- import { t as connectMcpServers } from "./mcp-B1psg7jf.js";
6
- import { _ as validateResourcePath, b as createSkillActivationState, d as escapeXml, n as resolveSkills, p as installAllowedToolsGate, t as interpolateShellCommands, u as buildCatalog } from "./interpolate-BhmHKD6x.js";
4
+ import { a as detectTurnInterruption, n as SYNTHETIC_TOOL_RESULT_PLACEHOLDER, o as ensureToolResultPairing, s as filterUnresolvedToolUses } from "./messages-D0xT979U.js";
5
+ import { t as connectMcpServers } from "./mcp-ZsSFo4Dp.js";
6
+ import { _ as validateResourcePath, b as createSkillActivationState, d as escapeXml, n as resolveSkills, p as installAllowedToolsGate, t as interpolateShellCommands, u as buildCatalog } from "./interpolate-ERgZUxgg.js";
7
7
  import { n as formatTokenUsage, t as flattenTurns } from "./stats-DgOvY7wd.js";
8
8
  import { createHooks } from "hookable";
9
9
  import { mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
@@ -480,6 +480,7 @@ function validateToolArgs(input, schema) {
480
480
  };
481
481
  let coerced;
482
482
  const coercions = [];
483
+ let droppedItems;
483
484
  for (const [key, value] of Object.entries(input)) {
484
485
  const propSchema = properties[key];
485
486
  if (!propSchema?.type) continue;
@@ -494,11 +495,138 @@ function validateToolArgs(input, schema) {
494
495
  coerced[key] = outcome.value;
495
496
  coercions.push(key);
496
497
  }
498
+ const arrayValue = outcome.changed ? outcome.value : value;
499
+ if (propSchema.type === "array" && propSchema.items && Array.isArray(arrayValue)) {
500
+ const itemOutcome = validateArrayItems(arrayValue, propSchema);
501
+ if (itemOutcome.error) return {
502
+ valid: false,
503
+ error: `Field "${key}": ${itemOutcome.error}`
504
+ };
505
+ if (itemOutcome.changed || itemOutcome.dropped.length > 0 || itemOutcome.truncated) {
506
+ if (!coerced) coerced = { ...input };
507
+ coerced[key] = itemOutcome.items;
508
+ if (!coercions.includes(key)) coercions.push(key);
509
+ }
510
+ if (itemOutcome.dropped.length > 0) {
511
+ if (!droppedItems) droppedItems = {};
512
+ droppedItems[key] = itemOutcome.dropped;
513
+ }
514
+ }
497
515
  }
498
516
  return {
499
517
  valid: true,
500
518
  coercedInput: coerced ?? input,
501
- coercions
519
+ coercions,
520
+ ...droppedItems ? { droppedItems } : {}
521
+ };
522
+ }
523
+ function validateArrayItems(items, schema) {
524
+ if (schema.minItems !== void 0 && items.length < schema.minItems) return {
525
+ items,
526
+ changed: false,
527
+ truncated: false,
528
+ dropped: [],
529
+ error: `expected at least ${schema.minItems} item${schema.minItems === 1 ? "" : "s"}, got ${items.length}`
530
+ };
531
+ const itemSchema = schema.items;
532
+ if (!itemSchema) return {
533
+ items,
534
+ changed: false,
535
+ truncated: false,
536
+ dropped: []
537
+ };
538
+ const out = [];
539
+ const outOriginalIdx = [];
540
+ const dropped = [];
541
+ let changed = false;
542
+ for (let i = 0; i < items.length; i++) {
543
+ const item = items[i];
544
+ const v = validateOneItem(item, itemSchema);
545
+ if (v.dropped) {
546
+ dropped.push(i);
547
+ changed = true;
548
+ continue;
549
+ }
550
+ if (v.changed) changed = true;
551
+ out.push(v.value);
552
+ outOriginalIdx.push(i);
553
+ }
554
+ let truncated = false;
555
+ if (schema.maxItems !== void 0 && out.length > schema.maxItems) {
556
+ for (let i = schema.maxItems; i < out.length; i++) dropped.push(outOriginalIdx[i]);
557
+ out.length = schema.maxItems;
558
+ truncated = true;
559
+ changed = true;
560
+ }
561
+ dropped.sort((a, b) => a - b);
562
+ return {
563
+ items: out,
564
+ changed,
565
+ truncated,
566
+ dropped
567
+ };
568
+ }
569
+ function validateOneItem(item, schema) {
570
+ if (schema.type === "object") {
571
+ if (!item || typeof item !== "object" || Array.isArray(item)) return {
572
+ value: item,
573
+ changed: false,
574
+ dropped: true
575
+ };
576
+ const obj = item;
577
+ const required = schema.required ?? [];
578
+ for (const field of required) {
579
+ const v = obj[field];
580
+ if (v === void 0 || v === null) return {
581
+ value: item,
582
+ changed: false,
583
+ dropped: true
584
+ };
585
+ }
586
+ const properties = schema.properties ?? {};
587
+ let coercedItem;
588
+ for (const [key, value] of Object.entries(obj)) {
589
+ const subSchema = properties[key];
590
+ if (!subSchema?.type) continue;
591
+ if (value === void 0 || value === null) continue;
592
+ const outcome = coerceValue(value, subSchema);
593
+ if (outcome.error) return {
594
+ value: item,
595
+ changed: false,
596
+ dropped: true
597
+ };
598
+ if (outcome.changed) {
599
+ if (!coercedItem) coercedItem = { ...obj };
600
+ coercedItem[key] = outcome.value;
601
+ }
602
+ }
603
+ return coercedItem ? {
604
+ value: coercedItem,
605
+ changed: true,
606
+ dropped: false
607
+ } : {
608
+ value: item,
609
+ changed: false,
610
+ dropped: false
611
+ };
612
+ }
613
+ if (schema.type) {
614
+ const outcome = coerceValue(item, schema);
615
+ if (outcome.error) return {
616
+ value: item,
617
+ changed: false,
618
+ dropped: true
619
+ };
620
+ return {
621
+ value: outcome.value,
622
+ changed: outcome.changed,
623
+ dropped: false
624
+ };
625
+ }
626
+ return {
627
+ value: item,
628
+ changed: false,
629
+ dropped: false
502
630
  };
503
631
  }
504
632
  function coerceValue(value, schema) {
@@ -628,6 +756,30 @@ function formatValue(value) {
628
756
  //#region src/loop.ts
629
757
  const IMAGE_OMITTED_MARKER = "[image omitted — model does not support vision]";
630
758
  /**
759
+ * Canonical tool_result text emitted when a tool call is interrupted by the
760
+ * user mid-flight (Esc / Ctrl-C / external `AbortSignal`). Mirrors Claude
761
+ * Code's `INTERRUPT_MESSAGE_FOR_TOOL_USE` so downstream consumers can pattern
762
+ * match a single string across both harnesses. Always paired with
763
+ * `isError: true` on the wire — the model treats it as a failed call rather
764
+ * than a successful tool response.
765
+ */
766
+ const INTERRUPT_MESSAGE_FOR_TOOL_USE = "[Request interrupted by user for tool use]";
767
+ /**
768
+ * Canonical tool_result text emitted when a tool call is skipped because a
769
+ * sibling sequential call errored or a steering message arrived between
770
+ * iterations of {@link executeToolsSequential}. Distinguished from
771
+ * {@link INTERRUPT_MESSAGE_FOR_TOOL_USE} so consumers can distinguish "user
772
+ * cancelled" from "framework superseded".
773
+ */
774
+ const TOOL_USE_SKIPPED_MESSAGE = "[Tool use skipped — superseded by user message]";
775
+ /**
776
+ * Canonical tool_result text emitted when the loop catches a sequential
777
+ * sibling that threw and synthesizes follow-up results for the remaining
778
+ * queued calls. Distinct from {@link TOOL_USE_SKIPPED_MESSAGE} so telemetry
779
+ * can split "skipped by user steering" from "skipped after error".
780
+ */
781
+ const TOOL_USE_AFTER_ERROR_MESSAGE = "[Tool use skipped — previous tool call in batch threw]";
782
+ /**
631
783
  * Compute the effective thinking budget for a given run-relative turn, given
632
784
  * the configured decay schedule. Pure helper — exported for tests and so
633
785
  * downstream tooling can preview decay curves without spinning up the loop.
@@ -929,6 +1081,32 @@ function invalidateReadStateForElidedPaths(session, elidedPaths) {
929
1081
  const suffixes = elidedPaths.map((p) => `::${p}`);
930
1082
  for (const key of [...readState.keys()]) if (suffixes.some((s) => key.endsWith(s))) readState.delete(key);
931
1083
  }
1084
+ /**
1085
+ * Run {@link ensureToolResultPairing} with the loop's hook + strict-mode
1086
+ * context plugged in. Centralized so the pre-send path and the schema-
1087
+ * enforcement path share identical telemetry + throw semantics.
1088
+ *
1089
+ * The captured `repairs` array is what `AgentToolPairingError` carries on
1090
+ * strict-mode throws, and it's what `pairing:repair` fires from. Hook
1091
+ * notifications run AFTER the pass completes so they don't interfere with
1092
+ * the synchronous walk — and we drop them on the floor in strict mode (the
1093
+ * throw is more informative than a fire-and-forget log).
1094
+ */
1095
+ function applyPairingRepair(ctx, messages, turnId) {
1096
+ const repairs = [];
1097
+ const repaired = ensureToolResultPairing(messages, { onRepair: (repair) => repairs.push(repair) });
1098
+ if (repairs.length === 0) return repaired;
1099
+ if (ctx.strictToolPairing) throw new AgentToolPairingError({
1100
+ message: `Tool pairing corruption detected (${repairs.length} repair${repairs.length === 1 ? "" : "s"}); strict mode is on so the request was not sent.`,
1101
+ ...ctx.providerName ? { provider: ctx.providerName } : {},
1102
+ repairs
1103
+ });
1104
+ for (const repair of repairs) ctx.hooks.callHook("pairing:repair", {
1105
+ ...repair,
1106
+ turnId
1107
+ });
1108
+ return repaired;
1109
+ }
932
1110
  function sanitizeStoredToolResults(provider, messages) {
933
1111
  if (provider.meta.capabilities?.vision !== false) return messages;
934
1112
  return messages.map((msg) => {
@@ -1060,6 +1238,27 @@ function wrapProviderError(err, ctx) {
1060
1238
  cause: err
1061
1239
  });
1062
1240
  }
1241
+ /** Max bytes of provider error text inlined into the assistant turn placeholder. */
1242
+ const ERROR_PLACEHOLDER_MAX = 280;
1243
+ /**
1244
+ * Build the assistant-turn placeholder text when the provider throws before
1245
+ * streaming any output. Inlines the underlying error message (truncated and
1246
+ * stripped of stack-like newlines) so the persisted turn carries a useful
1247
+ * diagnostic — the human reading the transcript sees the failure mode
1248
+ * without having to attach a debugger, and `tool_search` schema rejections
1249
+ * become self-explanatory.
1250
+ *
1251
+ * The bracketed `[✗ Streaming failed: ...]` shape preserves the prior
1252
+ * format that hosts may pattern-match on while adding the new payload
1253
+ * suffix. Falls back to the original generic placeholder when no message
1254
+ * can be extracted.
1255
+ */
1256
+ function buildStreamErrorPlaceholder(err) {
1257
+ const raw = errorMessage(err).trim();
1258
+ if (raw.length === 0) return "[✗ Streaming failed before any output.]";
1259
+ const oneLine = raw.replace(/\s+/g, " ");
1260
+ return `[✗ Streaming failed before any output: ${oneLine.length > ERROR_PLACEHOLDER_MAX ? `${oneLine.slice(0, ERROR_PLACEHOLDER_MAX - 1).trimEnd()}…` : oneLine}]`;
1261
+ }
1063
1262
  async function executeTurn(ctx, turn) {
1064
1263
  const turnId = await ctx.generateTurnId();
1065
1264
  let canonicalMessages = turnsToMessages(applyCompactSummaryCutoff(ctx.turns));
@@ -1091,7 +1290,7 @@ async function executeTurn(ctx, turn) {
1091
1290
  const transformCtx = { messages: streamOptions.messages };
1092
1291
  await ctx.hooks.callHook("context:transform", transformCtx);
1093
1292
  streamOptions.messages = transformCtx.messages;
1094
- streamOptions.messages = sanitizeOrphanedToolCalls(streamOptions.messages);
1293
+ streamOptions.messages = applyPairingRepair(ctx, streamOptions.messages, turnId);
1095
1294
  const systemCtx = {
1096
1295
  system: streamOptions.system,
1097
1296
  messages: streamOptions.messages,
@@ -1137,12 +1336,13 @@ async function executeTurn(ctx, turn) {
1137
1336
  input: 0,
1138
1337
  output: 0
1139
1338
  };
1339
+ const placeholderText = wasAborted ? "[⏹ Streaming was aborted.]" : buildStreamErrorPlaceholder(err);
1140
1340
  const errorContent = currentText ? [{
1141
1341
  type: "text",
1142
1342
  text: currentText
1143
1343
  }] : [{
1144
1344
  type: "text",
1145
- text: wasAborted ? "[⏹ Streaming was aborted.]" : "[✗ Streaming failed before any output.]"
1345
+ text: placeholderText
1146
1346
  }];
1147
1347
  const errorTurn = {
1148
1348
  id: turnId,
@@ -1153,6 +1353,10 @@ async function executeTurn(ctx, turn) {
1153
1353
  createdAt: Date.now()
1154
1354
  };
1155
1355
  ctx.turns.push(errorTurn);
1356
+ if (!wasAborted) await ctx.hooks.callHook("stream:error", {
1357
+ err,
1358
+ turnId
1359
+ });
1156
1360
  await ctx.hooks.callHook("turn:after", {
1157
1361
  turn,
1158
1362
  turnId,
@@ -1205,7 +1409,7 @@ async function executeTurn(ctx, turn) {
1205
1409
  description: "Return the final structured output matching the required schema.",
1206
1410
  inputSchema: ctx.schema
1207
1411
  };
1208
- const schemaMessages = sanitizeOrphanedToolCalls(rewriteMessagesToWire(turnsToMessages(applyCompactSummaryCutoff(ctx.turns)), ctx.aliasMaps));
1412
+ const schemaMessages = applyPairingRepair(ctx, rewriteMessagesToWire(turnsToMessages(applyCompactSummaryCutoff(ctx.turns)), ctx.aliasMaps), turnId);
1209
1413
  let schemaResult;
1210
1414
  try {
1211
1415
  schemaResult = await ctx.provider.stream({
@@ -1351,14 +1555,35 @@ async function executeSingleTool(ctx, call, turnId) {
1351
1555
  runToolCounts
1352
1556
  };
1353
1557
  await ctx.hooks.callHook("tool:gate", gateCtx);
1354
- if (gateCtx.block) return { result: {
1355
- id: callId,
1356
- content: `Blocked: ${gateCtx.reason}`
1357
- } };
1558
+ if (gateCtx.block) {
1559
+ await fireDispatched(ctx, {
1560
+ turnId,
1561
+ callId,
1562
+ name: call.name,
1563
+ displayName,
1564
+ input: gateCtx.input,
1565
+ outcome: "gate-block",
1566
+ reason: gateCtx.reason,
1567
+ runToolCounts
1568
+ });
1569
+ return { result: {
1570
+ id: callId,
1571
+ content: `Blocked: ${gateCtx.reason}`,
1572
+ isError: true
1573
+ } };
1574
+ }
1358
1575
  ctx.runToolCounts[call.name] = (ctx.runToolCounts[call.name] ?? 0) + 1;
1359
- if (gateCtx.result !== void 0) return { result: {
1360
- id: callId,
1361
- content: await emitToolResult(ctx, {
1576
+ if (gateCtx.result !== void 0) {
1577
+ await fireDispatched(ctx, {
1578
+ turnId,
1579
+ callId,
1580
+ name: call.name,
1581
+ displayName,
1582
+ input: gateCtx.input,
1583
+ outcome: "gate-substitute",
1584
+ runToolCounts
1585
+ });
1586
+ const emitted = await emitToolResult(ctx, {
1362
1587
  turnId,
1363
1588
  callId,
1364
1589
  name: call.name,
@@ -1367,8 +1592,13 @@ async function executeSingleTool(ctx, call, turnId) {
1367
1592
  output: gateCtx.result,
1368
1593
  isError: false,
1369
1594
  runToolCounts
1370
- })
1371
- } };
1595
+ });
1596
+ return { result: {
1597
+ id: callId,
1598
+ content: emitted.output,
1599
+ ...emitted.isError ? { isError: true } : {}
1600
+ } };
1601
+ }
1372
1602
  let effectiveInput = gateCtx.input;
1373
1603
  if (!toolDef) {
1374
1604
  const unknownCtx = {
@@ -1381,6 +1611,7 @@ async function executeSingleTool(ctx, call, turnId) {
1381
1611
  };
1382
1612
  await ctx.hooks.callHook("tool:unknown", unknownCtx);
1383
1613
  const content = unknownCtx.result ?? `Tool error: Unknown tool: ${call.name}`;
1614
+ const isError = unknownCtx.result === void 0;
1384
1615
  if (!unknownCtx.suppressError) {
1385
1616
  const err = /* @__PURE__ */ new Error(`Unknown tool: ${call.name}`);
1386
1617
  await ctx.hooks.callHook("tool:error", {
@@ -1392,9 +1623,19 @@ async function executeSingleTool(ctx, call, turnId) {
1392
1623
  error: err
1393
1624
  });
1394
1625
  }
1626
+ await fireDispatched(ctx, {
1627
+ turnId,
1628
+ callId,
1629
+ name: call.name,
1630
+ displayName,
1631
+ input: effectiveInput,
1632
+ outcome: "unknown",
1633
+ runToolCounts
1634
+ });
1395
1635
  return { result: {
1396
1636
  id: callId,
1397
- content
1637
+ content,
1638
+ ...isError ? { isError: true } : {}
1398
1639
  } };
1399
1640
  }
1400
1641
  const validation = validateToolArgs(effectiveInput, toolDef.spec.inputSchema);
@@ -1408,9 +1649,19 @@ async function executeSingleTool(ctx, call, turnId) {
1408
1649
  reason: validation.error ?? "invalid input",
1409
1650
  schema: toolDef.spec.inputSchema
1410
1651
  });
1652
+ await fireDispatched(ctx, {
1653
+ turnId,
1654
+ callId,
1655
+ name: call.name,
1656
+ displayName,
1657
+ input: effectiveInput,
1658
+ outcome: "invalid-input",
1659
+ runToolCounts
1660
+ });
1411
1661
  return { result: {
1412
1662
  id: callId,
1413
- content: `Validation error: ${validation.error}`
1663
+ content: `Validation error: ${validation.error}`,
1664
+ isError: true
1414
1665
  } };
1415
1666
  }
1416
1667
  effectiveInput = validation.coercedInput ?? effectiveInput;
@@ -1424,6 +1675,15 @@ async function executeSingleTool(ctx, call, turnId) {
1424
1675
  coercions,
1425
1676
  schema: toolDef.spec.inputSchema
1426
1677
  });
1678
+ await fireDispatched(ctx, {
1679
+ turnId,
1680
+ callId,
1681
+ name: call.name,
1682
+ displayName,
1683
+ input: effectiveInput,
1684
+ outcome: "execute",
1685
+ runToolCounts
1686
+ });
1427
1687
  await ctx.hooks.callHook("tool:before", {
1428
1688
  turnId,
1429
1689
  callId,
@@ -1470,26 +1730,54 @@ async function executeSingleTool(ctx, call, turnId) {
1470
1730
  output = errorCtx.result ?? `Tool error: ${error.message}`;
1471
1731
  isError = true;
1472
1732
  }
1733
+ const emitted = await emitToolResult(ctx, {
1734
+ turnId,
1735
+ callId,
1736
+ name: call.name,
1737
+ displayName,
1738
+ input: effectiveInput,
1739
+ output,
1740
+ isError,
1741
+ runToolCounts,
1742
+ ...coercions ? { coercions } : {}
1743
+ });
1473
1744
  return { result: {
1474
1745
  id: callId,
1475
- content: await emitToolResult(ctx, {
1476
- turnId,
1477
- callId,
1478
- name: call.name,
1479
- displayName,
1480
- input: effectiveInput,
1481
- output,
1482
- isError,
1483
- runToolCounts,
1484
- ...coercions ? { coercions } : {}
1485
- })
1746
+ content: emitted.output,
1747
+ ...emitted.isError ? { isError: true } : {}
1486
1748
  } };
1487
1749
  }
1488
1750
  /**
1751
+ * Fire `tool:dispatched` with the resolved path discriminator. Every code
1752
+ * path in {@link executeSingleTool} that produces a tool_result must call
1753
+ * this exactly once — that contract is what makes `tool:dispatched` ↔
1754
+ * `tool:after` a guaranteed symmetric pairing for live-event consumers
1755
+ * (the chat layer, SDK consumers reconstructing wire history from events,
1756
+ * tracing spans that want to record refused calls separately from
1757
+ * executed ones).
1758
+ *
1759
+ * Helper exists to centralize the optional-field plumbing for `reason`
1760
+ * (only set on `gate-block`) without sprinkling spread-conditional logic
1761
+ * across five call sites.
1762
+ */
1763
+ async function fireDispatched(ctx, params) {
1764
+ const { reason, ...rest } = params;
1765
+ await ctx.hooks.callHook("tool:dispatched", {
1766
+ ...rest,
1767
+ ...reason !== void 0 ? { reason } : {}
1768
+ });
1769
+ }
1770
+ /**
1489
1771
  * Shared post-output emission: fire `tool:transform` (mutate-allowed), strip
1490
1772
  * images for non-vision providers, fire `tool:after`. Used by both the
1491
1773
  * gate-substitute (Z20) and post-execute paths so they stay byte-for-byte
1492
1774
  * identical from the consumer's perspective.
1775
+ *
1776
+ * Returns both the (possibly transformed) output and the final `isError`
1777
+ * flag — `tool:transform` listeners can flip the flag in either direction
1778
+ * (e.g. rewrite a structured error response to a graceful retry hint), and
1779
+ * the caller needs the post-transform value to populate `ToolResult.isError`
1780
+ * on the wire.
1493
1781
  */
1494
1782
  async function emitToolResult(ctx, params) {
1495
1783
  const { turnId, callId, name, displayName, input, runToolCounts, coercions } = params;
@@ -1535,7 +1823,10 @@ async function emitToolResult(ctx, params) {
1535
1823
  runToolCounts,
1536
1824
  ...coercions ? { coercions } : {}
1537
1825
  });
1538
- return output;
1826
+ return {
1827
+ output,
1828
+ isError
1829
+ };
1539
1830
  }
1540
1831
  async function executeToolsSequential(ctx, toolCalls, turnId) {
1541
1832
  const results = [];
@@ -1544,14 +1835,16 @@ async function executeToolsSequential(ctx, toolCalls, turnId) {
1544
1835
  if (ctx.signal.aborted) {
1545
1836
  for (let j = i; j < toolCalls.length; j++) results.push({
1546
1837
  id: toolCalls[j].id,
1547
- content: "Aborted: run was cancelled"
1838
+ content: INTERRUPT_MESSAGE_FOR_TOOL_USE,
1839
+ isError: true
1548
1840
  });
1549
1841
  return results;
1550
1842
  }
1551
1843
  if (ctx.steeringQueue.length > 0) {
1552
1844
  for (let j = i; j < toolCalls.length; j++) results.push({
1553
1845
  id: toolCalls[j].id,
1554
- content: "Skipped: steering message received"
1846
+ content: TOOL_USE_SKIPPED_MESSAGE,
1847
+ isError: true
1555
1848
  });
1556
1849
  return results;
1557
1850
  }
@@ -1561,11 +1854,13 @@ async function executeToolsSequential(ctx, toolCalls, turnId) {
1561
1854
  } catch (err) {
1562
1855
  results.push({
1563
1856
  id: call.id,
1564
- content: `Error: ${errorMessage(err)}`
1857
+ content: `Error: ${errorMessage(err)}`,
1858
+ isError: true
1565
1859
  });
1566
1860
  for (let j = i + 1; j < toolCalls.length; j++) results.push({
1567
1861
  id: toolCalls[j].id,
1568
- content: "Skipped: previous tool call threw"
1862
+ content: TOOL_USE_AFTER_ERROR_MESSAGE,
1863
+ isError: true
1569
1864
  });
1570
1865
  return results;
1571
1866
  }
@@ -1576,9 +1871,12 @@ async function executeToolsParallel(ctx, toolCalls, turnId) {
1576
1871
  const executions = toolCalls.map((call) => executeSingleTool(ctx, call, turnId));
1577
1872
  return (await Promise.allSettled(executions)).map((s, i) => {
1578
1873
  if (s.status === "fulfilled") return s.value.result;
1874
+ const reason = s.reason;
1875
+ const isAbort = ctx.signal.aborted || reason instanceof Error && reason.name === "AbortError";
1579
1876
  return {
1580
1877
  id: toolCalls[i].id,
1581
- content: `Error: ${s.reason instanceof Error ? s.reason.message : String(s.reason)}`
1878
+ content: isAbort ? INTERRUPT_MESSAGE_FOR_TOOL_USE : `Error: ${reason instanceof Error ? reason.message : String(reason)}`,
1879
+ isError: true
1582
1880
  };
1583
1881
  });
1584
1882
  }
@@ -2157,8 +2455,10 @@ const HOOK_EVENT_SET = new Set([
2157
2455
  "stream:text",
2158
2456
  "stream:end",
2159
2457
  "stream:thinking",
2458
+ "stream:error",
2160
2459
  "oauth:refresh",
2161
2460
  "tool:gate",
2461
+ "tool:dispatched",
2162
2462
  "tool:before",
2163
2463
  "tool:after",
2164
2464
  "tool:error",
@@ -2175,8 +2475,10 @@ const HOOK_EVENT_SET = new Set([
2175
2475
  "child:stream:text",
2176
2476
  "child:stream:thinking",
2177
2477
  "child:stream:end",
2478
+ "child:stream:error",
2178
2479
  "child:tool:gate",
2179
2480
  "child:mcp:tool:gate",
2481
+ "child:tool:dispatched",
2180
2482
  "child:tool:before",
2181
2483
  "child:tool:after",
2182
2484
  "child:tool:transform",
@@ -2205,6 +2507,7 @@ const HOOK_EVENT_SET = new Set([
2205
2507
  "output",
2206
2508
  "budget:exceeded",
2207
2509
  "tool-budget:exceeded",
2510
+ "pairing:repair",
2208
2511
  "agent:abort",
2209
2512
  "agent:done",
2210
2513
  "session:start",
@@ -2224,6 +2527,16 @@ function isKnownHookEvent(event) {
2224
2527
  * loop from leaving the persisted session with an orphan tool_use — which
2225
2528
  * Anthropic rejects on resume.
2226
2529
  *
2530
+ * Each synthetic result carries the shared
2531
+ * {@link SYNTHETIC_TOOL_RESULT_PLACEHOLDER} text (so consumers can pattern-
2532
+ * match the same way they would on a live pre-send repair) and
2533
+ * `isError: true` so the model treats it as a failed call.
2534
+ *
2535
+ * Fires `pairing:repair` (mode `orphan-tool-use-append`) for each synthetic
2536
+ * result, mirroring the wire-level repair pass's observability so postmortem
2537
+ * dashboards see the same telemetry shape regardless of where the orphan
2538
+ * was detected.
2539
+ *
2227
2540
  * No-op when:
2228
2541
  * - The trailing turn isn't an assistant turn (already closed, or session
2229
2542
  * ends with the seeded user prompt).
@@ -2231,7 +2544,7 @@ function isKnownHookEvent(event) {
2231
2544
  * - All tool_use ids are already answered by a tool_result somewhere later
2232
2545
  * in the conversation (defensive — shouldn't happen but cheap to check).
2233
2546
  */
2234
- function synthesizeMissingToolResults(turns, syntheticTurnId, runId, provider) {
2547
+ async function synthesizeMissingToolResults(turns, syntheticTurnId, runId, provider, hooks) {
2235
2548
  if (turns.length === 0) return;
2236
2549
  const last = turns[turns.length - 1];
2237
2550
  if (last.role !== "assistant") return;
@@ -2244,7 +2557,8 @@ function synthesizeMissingToolResults(turns, syntheticTurnId, runId, provider) {
2244
2557
  if (dangling.length === 0) return;
2245
2558
  const results = dangling.map((id) => ({
2246
2559
  id,
2247
- content: "Aborted: run failed before tool execution completed"
2560
+ content: SYNTHETIC_TOOL_RESULT_PLACEHOLDER,
2561
+ isError: true
2248
2562
  }));
2249
2563
  const msg = provider.toolResultsMessage(results);
2250
2564
  turns.push({
@@ -2254,6 +2568,11 @@ function synthesizeMissingToolResults(turns, syntheticTurnId, runId, provider) {
2254
2568
  content: msg.content,
2255
2569
  createdAt: Date.now()
2256
2570
  });
2571
+ for (const callId of dangling) await hooks.callHook("pairing:repair", {
2572
+ mode: "orphan-tool-use-append",
2573
+ callId,
2574
+ messageIndex: turns.length - 2
2575
+ });
2257
2576
  }
2258
2577
  function resolveBehavior(agentBehavior, runBehavior) {
2259
2578
  return {
@@ -2278,7 +2597,8 @@ function resolveBehavior(agentBehavior, runBehavior) {
2278
2597
  toolSearch: runBehavior?.toolSearch ?? agentBehavior?.toolSearch,
2279
2598
  persistThreshold: runBehavior?.persistThreshold ?? agentBehavior?.persistThreshold,
2280
2599
  persistExcludeTools: runBehavior?.persistExcludeTools ?? agentBehavior?.persistExcludeTools,
2281
- persistDir: runBehavior?.persistDir ?? agentBehavior?.persistDir
2600
+ persistDir: runBehavior?.persistDir ?? agentBehavior?.persistDir,
2601
+ strictToolPairing: runBehavior?.strictToolPairing ?? agentBehavior?.strictToolPairing ?? false
2282
2602
  };
2283
2603
  }
2284
2604
  /**
@@ -2366,7 +2686,7 @@ function buildSearchableCatalog(entries, options) {
2366
2686
  }
2367
2687
  const serverNames = [...byServer.keys()].sort();
2368
2688
  const parts = [];
2369
- if (options.discoveryToolName) parts.push("The following tools are available but their input schemas are NOT loaded in your context.", `Call the \`${options.discoveryToolName}\` tool to load schemas before invoking them. Surfaced tools persist for the rest of the run.`, "");
2689
+ if (options.discoveryToolName) parts.push("The following tools are available but their input schemas are NOT loaded in your context.", `Call the \`${options.discoveryToolName}\` tool to load schemas for any tool below that you have not already surfaced. Surfaced tools persist for the rest of the run.`, "");
2370
2690
  parts.push("<searchable_tools>");
2371
2691
  for (const server of serverNames) {
2372
2692
  parts.push(` <server name="${escapeXml(server)}">`);
@@ -2494,9 +2814,14 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2494
2814
  if (running) throw new Error("Agent is already running. Use steer() or followUp() to queue messages, or waitForIdle().");
2495
2815
  const hasSessionTurns = session && session.turns.length > 0;
2496
2816
  if (!options.prompt && !hasSessionTurns) throw new Error("prompt is required when no session with existing turns is provided");
2497
- if (!options.prompt && hasSessionTurns) {
2498
- const lastTurn = session.turns.at(-1);
2499
- if (lastTurn && lastTurn.role !== "user") throw new Error("cannot resume without prompt: last session turn must be a user message");
2817
+ let resumeFilteredTurns;
2818
+ if (hasSessionTurns) resumeFilteredTurns = filterUnresolvedToolUses(session.turns);
2819
+ if (!options.prompt && resumeFilteredTurns) {
2820
+ const lastTurn = resumeFilteredTurns.at(-1);
2821
+ if (lastTurn && lastTurn.role !== "user") {
2822
+ const detail = detectTurnInterruption(resumeFilteredTurns) === "completed" ? "last turn is a completed assistant message" : "last turn is mid-stream assistant content";
2823
+ throw new Error(`cannot resume without prompt: ${detail}. Pass a prompt to agent.run({ prompt: … }).`);
2824
+ }
2500
2825
  }
2501
2826
  let externalAbortListener;
2502
2827
  const externalSignal = options.signal;
@@ -2561,7 +2886,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2561
2886
  const thinking = options.thinking ?? "off";
2562
2887
  const model = options.model ?? provider.meta.defaultModel;
2563
2888
  const resolvedBehavior = resolveBehavior(agentBehavior, options.behavior);
2564
- const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets, elideStaleReads, toolDisclosure, toolSearch, persistThreshold, persistExcludeTools, persistDir } = resolvedBehavior;
2889
+ const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets, elideStaleReads, toolDisclosure, toolSearch, persistThreshold, persistExcludeTools, persistDir, strictToolPairing } = resolvedBehavior;
2565
2890
  let system = options.system || agentSystem || "You are a helpful assistant.";
2566
2891
  if (skillsCatalog) system = `${system}\n\n${skillsCatalog}`;
2567
2892
  const runBaseTools = options.tools !== void 0 ? options.tools : mcpConnection ? {
@@ -2626,7 +2951,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2626
2951
  if (isResume) {
2627
2952
  const childRunIds = new Set(session.runs.filter((r) => (r.depth ?? 0) > 0).map((r) => r.id));
2628
2953
  const resumed = childRunIds.size === 0 ? session.turns : session.turns.filter((t) => !t.runId || !childRunIds.has(t.runId));
2629
- turns.push(...resumed);
2954
+ const filteredForRuntime = resumeFilteredTurns && resumed === session.turns ? resumeFilteredTurns : filterUnresolvedToolUses(resumed);
2955
+ turns.push(...filteredForRuntime);
2630
2956
  }
2631
2957
  const runTurnStart = turns.length;
2632
2958
  if (options.system) await hooks.callHook("system:before", { system: options.system });
@@ -2669,7 +2995,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2669
2995
  const unregisterToolResultsSync = session ? hooks.hook("tool-results:after", persistPendingTurns) : void 0;
2670
2996
  async function flushTurns(opts = {}) {
2671
2997
  if (!session) return;
2672
- if (opts.failureFallback) synthesizeMissingToolResults(turns, await session.generateTurnId?.() ?? crypto.randomUUID(), runId, provider);
2998
+ if (opts.failureFallback) await synthesizeMissingToolResults(turns, await session.generateTurnId?.() ?? crypto.randomUUID(), runId, provider, hooks);
2673
2999
  const remaining = turns.slice(lastPersistedTurnCount);
2674
3000
  if (remaining.length > 0) {
2675
3001
  await session.appendTurns(remaining);
@@ -2748,6 +3074,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2748
3074
  ...persistThreshold !== void 0 ? { persistThreshold } : {},
2749
3075
  ...persistExcludeTools !== void 0 ? { persistExcludeTools } : {},
2750
3076
  ...persistDir !== void 0 ? { persistDir } : {},
3077
+ ...strictToolPairing ? { strictToolPairing: true } : {},
3078
+ providerName: provider.name,
2751
3079
  runStartMs,
2752
3080
  runToolCounts: {}
2753
3081
  });
@@ -4161,6 +4489,8 @@ const BUBBLED_EVENTS = [
4161
4489
  "stream:text",
4162
4490
  "stream:thinking",
4163
4491
  "stream:end",
4492
+ "stream:error",
4493
+ "tool:dispatched",
4164
4494
  "tool:before",
4165
4495
  "tool:after",
4166
4496
  "tool:error",
@@ -4175,6 +4505,8 @@ const CHILD_EVENT_NAME = {
4175
4505
  "stream:text": "child:stream:text",
4176
4506
  "stream:thinking": "child:stream:thinking",
4177
4507
  "stream:end": "child:stream:end",
4508
+ "stream:error": "child:stream:error",
4509
+ "tool:dispatched": "child:tool:dispatched",
4178
4510
  "tool:before": "child:tool:before",
4179
4511
  "tool:after": "child:tool:after",
4180
4512
  "tool:error": "child:tool:error",
@@ -4571,6 +4903,6 @@ const writeFile$1 = {
4571
4903
  }
4572
4904
  };
4573
4905
  //#endregion
4574
- export { maybePersistToolResult as C, cleanupPersistedSession as S, getReadState as T, createSkillsReadTool as _, multiEdit as a, PERSISTENCE_PREVIEW_BYTES as b, grep as c, resolveOldString as d, styleReplacementForVia as f, createSkillsRunScriptTool as g, createSkillsUseTool as h, readFile$1 as i, glob as l, createToolSearchTool as m, createSpawnTool as n, listFiles as o, createAgent as p, shell as r, createInteractionTool as s, writeFile$1 as t, edit as u, validateToolArgs as v, resolvePersistDir as w, buildPersistedStub as x, PERSISTED_STUB_PREFIX as y };
4906
+ export { PERSISTENCE_PREVIEW_BYTES as C, resolvePersistDir as D, maybePersistToolResult as E, getReadState as O, PERSISTED_STUB_PREFIX as S, cleanupPersistedSession as T, createSkillsReadTool as _, multiEdit as a, TOOL_USE_SKIPPED_MESSAGE as b, grep as c, resolveOldString as d, styleReplacementForVia as f, createSkillsRunScriptTool as g, createSkillsUseTool as h, readFile$1 as i, glob as l, createToolSearchTool as m, createSpawnTool as n, listFiles as o, createAgent as p, shell as r, createInteractionTool as s, writeFile$1 as t, edit as u, INTERRUPT_MESSAGE_FOR_TOOL_USE as v, buildPersistedStub as w, validateToolArgs as x, TOOL_USE_AFTER_ERROR_MESSAGE as y };
4575
4907
 
4576
- //# sourceMappingURL=tools-CMVruxF0.js.map
4908
+ //# sourceMappingURL=tools-CWEDS2ZT.js.map