zidane 5.2.1 → 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 (67) hide show
  1. package/README.md +7 -5
  2. package/dist/{agent-CGQajqtC.d.ts → agent-bKs7MRT2.d.ts} +429 -4
  3. package/dist/agent-bKs7MRT2.d.ts.map +1 -0
  4. package/dist/chat.d.ts +212 -58
  5. package/dist/chat.d.ts.map +1 -1
  6. package/dist/chat.js +2 -2
  7. package/dist/{errors-COmsomd5.js → errors-Byb0F8B9.js} +44 -2
  8. package/dist/errors-Byb0F8B9.js.map +1 -0
  9. package/dist/{index-DwbcFBr_.d.ts → index-BlMvPh9X.d.ts} +29 -3
  10. package/dist/index-BlMvPh9X.d.ts.map +1 -0
  11. package/dist/{index-BDP6mA3Y.d.ts → index-CTmNaIDb.d.ts} +2 -2
  12. package/dist/{index-BDP6mA3Y.d.ts.map → index-CTmNaIDb.d.ts.map} +1 -1
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +10 -10
  15. package/dist/{interpolate-BhmHKD6x.js → interpolate-ERgZUxgg.js} +2 -2
  16. package/dist/{interpolate-BhmHKD6x.js.map → interpolate-ERgZUxgg.js.map} +1 -1
  17. package/dist/{login-D7Tp-K5f.js → login-CNS9_8Ue.js} +3 -3
  18. package/dist/{login-D7Tp-K5f.js.map → login-CNS9_8Ue.js.map} +1 -1
  19. package/dist/{mcp-B1psg7jf.js → mcp-ZsSFo4Dp.js} +2 -2
  20. package/dist/{mcp-B1psg7jf.js.map → mcp-ZsSFo4Dp.js.map} +1 -1
  21. package/dist/mcp.d.ts +1 -1
  22. package/dist/mcp.js +1 -1
  23. package/dist/{messages-DsbMYNmt.js → messages-D0xT979U.js} +631 -68
  24. package/dist/messages-D0xT979U.js.map +1 -0
  25. package/dist/{presets-AgF0RFx1.js → presets-h5i3kpOP.js} +2 -2
  26. package/dist/{presets-AgF0RFx1.js.map → presets-h5i3kpOP.js.map} +1 -1
  27. package/dist/presets.d.ts +2 -2
  28. package/dist/presets.js +1 -1
  29. package/dist/{providers-v1Rn2rqG.js → providers-x3LZByR5.js} +38 -6
  30. package/dist/providers-x3LZByR5.js.map +1 -0
  31. package/dist/providers.d.ts +2 -2
  32. package/dist/providers.js +3 -3
  33. package/dist/session/sqlite.d.ts +1 -1
  34. package/dist/session/sqlite.js +1 -1
  35. package/dist/{session-DOJgRXvF.js → session-BHZwxmfr.js} +2 -2
  36. package/dist/{session-DOJgRXvF.js.map → session-BHZwxmfr.js.map} +1 -1
  37. package/dist/session.d.ts +1 -1
  38. package/dist/session.js +2 -2
  39. package/dist/skills.d.ts +2 -2
  40. package/dist/skills.js +1 -1
  41. package/dist/{tools-BRbbfdJh.js → tools-CWEDS2ZT.js} +251 -47
  42. package/dist/tools-CWEDS2ZT.js.map +1 -0
  43. package/dist/tools.d.ts +2 -2
  44. package/dist/tools.js +1 -1
  45. package/dist/{transcript-anchors-BBuIoU0x.d.ts → transcript-anchors-DOUqyvXR.d.ts} +28 -4
  46. package/dist/transcript-anchors-DOUqyvXR.d.ts.map +1 -0
  47. package/dist/tui.d.ts +29 -3
  48. package/dist/tui.d.ts.map +1 -1
  49. package/dist/tui.js +363 -28
  50. package/dist/tui.js.map +1 -1
  51. package/dist/{turn-operations-gJ0qtLPv.js → turn-operations-D9HvatsR.js} +396 -89
  52. package/dist/turn-operations-D9HvatsR.js.map +1 -0
  53. package/dist/types-IcokUOyC.js.map +1 -1
  54. package/dist/types.d.ts +2 -2
  55. package/dist/types.js +1 -1
  56. package/docs/ARCHITECTURE.md +3 -2
  57. package/docs/CHAT.md +55 -16
  58. package/docs/TUI.md +22 -2
  59. package/package.json +1 -1
  60. package/dist/agent-CGQajqtC.d.ts.map +0 -1
  61. package/dist/errors-COmsomd5.js.map +0 -1
  62. package/dist/index-DwbcFBr_.d.ts.map +0 -1
  63. package/dist/messages-DsbMYNmt.js.map +0 -1
  64. package/dist/providers-v1Rn2rqG.js.map +0 -1
  65. package/dist/tools-BRbbfdJh.js.map +0 -1
  66. package/dist/transcript-anchors-BBuIoU0x.d.ts.map +0 -1
  67. package/dist/turn-operations-gJ0qtLPv.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";
@@ -756,6 +756,30 @@ function formatValue(value) {
756
756
  //#region src/loop.ts
757
757
  const IMAGE_OMITTED_MARKER = "[image omitted — model does not support vision]";
758
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
+ /**
759
783
  * Compute the effective thinking budget for a given run-relative turn, given
760
784
  * the configured decay schedule. Pure helper — exported for tests and so
761
785
  * downstream tooling can preview decay curves without spinning up the loop.
@@ -1057,6 +1081,32 @@ function invalidateReadStateForElidedPaths(session, elidedPaths) {
1057
1081
  const suffixes = elidedPaths.map((p) => `::${p}`);
1058
1082
  for (const key of [...readState.keys()]) if (suffixes.some((s) => key.endsWith(s))) readState.delete(key);
1059
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
+ }
1060
1110
  function sanitizeStoredToolResults(provider, messages) {
1061
1111
  if (provider.meta.capabilities?.vision !== false) return messages;
1062
1112
  return messages.map((msg) => {
@@ -1188,6 +1238,27 @@ function wrapProviderError(err, ctx) {
1188
1238
  cause: err
1189
1239
  });
1190
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
+ }
1191
1262
  async function executeTurn(ctx, turn) {
1192
1263
  const turnId = await ctx.generateTurnId();
1193
1264
  let canonicalMessages = turnsToMessages(applyCompactSummaryCutoff(ctx.turns));
@@ -1219,7 +1290,7 @@ async function executeTurn(ctx, turn) {
1219
1290
  const transformCtx = { messages: streamOptions.messages };
1220
1291
  await ctx.hooks.callHook("context:transform", transformCtx);
1221
1292
  streamOptions.messages = transformCtx.messages;
1222
- streamOptions.messages = sanitizeOrphanedToolCalls(streamOptions.messages);
1293
+ streamOptions.messages = applyPairingRepair(ctx, streamOptions.messages, turnId);
1223
1294
  const systemCtx = {
1224
1295
  system: streamOptions.system,
1225
1296
  messages: streamOptions.messages,
@@ -1265,12 +1336,13 @@ async function executeTurn(ctx, turn) {
1265
1336
  input: 0,
1266
1337
  output: 0
1267
1338
  };
1339
+ const placeholderText = wasAborted ? "[⏹ Streaming was aborted.]" : buildStreamErrorPlaceholder(err);
1268
1340
  const errorContent = currentText ? [{
1269
1341
  type: "text",
1270
1342
  text: currentText
1271
1343
  }] : [{
1272
1344
  type: "text",
1273
- text: wasAborted ? "[⏹ Streaming was aborted.]" : "[✗ Streaming failed before any output.]"
1345
+ text: placeholderText
1274
1346
  }];
1275
1347
  const errorTurn = {
1276
1348
  id: turnId,
@@ -1281,6 +1353,10 @@ async function executeTurn(ctx, turn) {
1281
1353
  createdAt: Date.now()
1282
1354
  };
1283
1355
  ctx.turns.push(errorTurn);
1356
+ if (!wasAborted) await ctx.hooks.callHook("stream:error", {
1357
+ err,
1358
+ turnId
1359
+ });
1284
1360
  await ctx.hooks.callHook("turn:after", {
1285
1361
  turn,
1286
1362
  turnId,
@@ -1333,7 +1409,7 @@ async function executeTurn(ctx, turn) {
1333
1409
  description: "Return the final structured output matching the required schema.",
1334
1410
  inputSchema: ctx.schema
1335
1411
  };
1336
- const schemaMessages = sanitizeOrphanedToolCalls(rewriteMessagesToWire(turnsToMessages(applyCompactSummaryCutoff(ctx.turns)), ctx.aliasMaps));
1412
+ const schemaMessages = applyPairingRepair(ctx, rewriteMessagesToWire(turnsToMessages(applyCompactSummaryCutoff(ctx.turns)), ctx.aliasMaps), turnId);
1337
1413
  let schemaResult;
1338
1414
  try {
1339
1415
  schemaResult = await ctx.provider.stream({
@@ -1479,14 +1555,35 @@ async function executeSingleTool(ctx, call, turnId) {
1479
1555
  runToolCounts
1480
1556
  };
1481
1557
  await ctx.hooks.callHook("tool:gate", gateCtx);
1482
- if (gateCtx.block) return { result: {
1483
- id: callId,
1484
- content: `Blocked: ${gateCtx.reason}`
1485
- } };
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
+ }
1486
1575
  ctx.runToolCounts[call.name] = (ctx.runToolCounts[call.name] ?? 0) + 1;
1487
- if (gateCtx.result !== void 0) return { result: {
1488
- id: callId,
1489
- 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, {
1490
1587
  turnId,
1491
1588
  callId,
1492
1589
  name: call.name,
@@ -1495,8 +1592,13 @@ async function executeSingleTool(ctx, call, turnId) {
1495
1592
  output: gateCtx.result,
1496
1593
  isError: false,
1497
1594
  runToolCounts
1498
- })
1499
- } };
1595
+ });
1596
+ return { result: {
1597
+ id: callId,
1598
+ content: emitted.output,
1599
+ ...emitted.isError ? { isError: true } : {}
1600
+ } };
1601
+ }
1500
1602
  let effectiveInput = gateCtx.input;
1501
1603
  if (!toolDef) {
1502
1604
  const unknownCtx = {
@@ -1509,6 +1611,7 @@ async function executeSingleTool(ctx, call, turnId) {
1509
1611
  };
1510
1612
  await ctx.hooks.callHook("tool:unknown", unknownCtx);
1511
1613
  const content = unknownCtx.result ?? `Tool error: Unknown tool: ${call.name}`;
1614
+ const isError = unknownCtx.result === void 0;
1512
1615
  if (!unknownCtx.suppressError) {
1513
1616
  const err = /* @__PURE__ */ new Error(`Unknown tool: ${call.name}`);
1514
1617
  await ctx.hooks.callHook("tool:error", {
@@ -1520,9 +1623,19 @@ async function executeSingleTool(ctx, call, turnId) {
1520
1623
  error: err
1521
1624
  });
1522
1625
  }
1626
+ await fireDispatched(ctx, {
1627
+ turnId,
1628
+ callId,
1629
+ name: call.name,
1630
+ displayName,
1631
+ input: effectiveInput,
1632
+ outcome: "unknown",
1633
+ runToolCounts
1634
+ });
1523
1635
  return { result: {
1524
1636
  id: callId,
1525
- content
1637
+ content,
1638
+ ...isError ? { isError: true } : {}
1526
1639
  } };
1527
1640
  }
1528
1641
  const validation = validateToolArgs(effectiveInput, toolDef.spec.inputSchema);
@@ -1536,9 +1649,19 @@ async function executeSingleTool(ctx, call, turnId) {
1536
1649
  reason: validation.error ?? "invalid input",
1537
1650
  schema: toolDef.spec.inputSchema
1538
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
+ });
1539
1661
  return { result: {
1540
1662
  id: callId,
1541
- content: `Validation error: ${validation.error}`
1663
+ content: `Validation error: ${validation.error}`,
1664
+ isError: true
1542
1665
  } };
1543
1666
  }
1544
1667
  effectiveInput = validation.coercedInput ?? effectiveInput;
@@ -1552,6 +1675,15 @@ async function executeSingleTool(ctx, call, turnId) {
1552
1675
  coercions,
1553
1676
  schema: toolDef.spec.inputSchema
1554
1677
  });
1678
+ await fireDispatched(ctx, {
1679
+ turnId,
1680
+ callId,
1681
+ name: call.name,
1682
+ displayName,
1683
+ input: effectiveInput,
1684
+ outcome: "execute",
1685
+ runToolCounts
1686
+ });
1555
1687
  await ctx.hooks.callHook("tool:before", {
1556
1688
  turnId,
1557
1689
  callId,
@@ -1598,26 +1730,54 @@ async function executeSingleTool(ctx, call, turnId) {
1598
1730
  output = errorCtx.result ?? `Tool error: ${error.message}`;
1599
1731
  isError = true;
1600
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
+ });
1601
1744
  return { result: {
1602
1745
  id: callId,
1603
- content: await emitToolResult(ctx, {
1604
- turnId,
1605
- callId,
1606
- name: call.name,
1607
- displayName,
1608
- input: effectiveInput,
1609
- output,
1610
- isError,
1611
- runToolCounts,
1612
- ...coercions ? { coercions } : {}
1613
- })
1746
+ content: emitted.output,
1747
+ ...emitted.isError ? { isError: true } : {}
1614
1748
  } };
1615
1749
  }
1616
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
+ /**
1617
1771
  * Shared post-output emission: fire `tool:transform` (mutate-allowed), strip
1618
1772
  * images for non-vision providers, fire `tool:after`. Used by both the
1619
1773
  * gate-substitute (Z20) and post-execute paths so they stay byte-for-byte
1620
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.
1621
1781
  */
1622
1782
  async function emitToolResult(ctx, params) {
1623
1783
  const { turnId, callId, name, displayName, input, runToolCounts, coercions } = params;
@@ -1663,7 +1823,10 @@ async function emitToolResult(ctx, params) {
1663
1823
  runToolCounts,
1664
1824
  ...coercions ? { coercions } : {}
1665
1825
  });
1666
- return output;
1826
+ return {
1827
+ output,
1828
+ isError
1829
+ };
1667
1830
  }
1668
1831
  async function executeToolsSequential(ctx, toolCalls, turnId) {
1669
1832
  const results = [];
@@ -1672,14 +1835,16 @@ async function executeToolsSequential(ctx, toolCalls, turnId) {
1672
1835
  if (ctx.signal.aborted) {
1673
1836
  for (let j = i; j < toolCalls.length; j++) results.push({
1674
1837
  id: toolCalls[j].id,
1675
- content: "Aborted: run was cancelled"
1838
+ content: INTERRUPT_MESSAGE_FOR_TOOL_USE,
1839
+ isError: true
1676
1840
  });
1677
1841
  return results;
1678
1842
  }
1679
1843
  if (ctx.steeringQueue.length > 0) {
1680
1844
  for (let j = i; j < toolCalls.length; j++) results.push({
1681
1845
  id: toolCalls[j].id,
1682
- content: "Skipped: steering message received"
1846
+ content: TOOL_USE_SKIPPED_MESSAGE,
1847
+ isError: true
1683
1848
  });
1684
1849
  return results;
1685
1850
  }
@@ -1689,11 +1854,13 @@ async function executeToolsSequential(ctx, toolCalls, turnId) {
1689
1854
  } catch (err) {
1690
1855
  results.push({
1691
1856
  id: call.id,
1692
- content: `Error: ${errorMessage(err)}`
1857
+ content: `Error: ${errorMessage(err)}`,
1858
+ isError: true
1693
1859
  });
1694
1860
  for (let j = i + 1; j < toolCalls.length; j++) results.push({
1695
1861
  id: toolCalls[j].id,
1696
- content: "Skipped: previous tool call threw"
1862
+ content: TOOL_USE_AFTER_ERROR_MESSAGE,
1863
+ isError: true
1697
1864
  });
1698
1865
  return results;
1699
1866
  }
@@ -1704,9 +1871,12 @@ async function executeToolsParallel(ctx, toolCalls, turnId) {
1704
1871
  const executions = toolCalls.map((call) => executeSingleTool(ctx, call, turnId));
1705
1872
  return (await Promise.allSettled(executions)).map((s, i) => {
1706
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";
1707
1876
  return {
1708
1877
  id: toolCalls[i].id,
1709
- 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
1710
1880
  };
1711
1881
  });
1712
1882
  }
@@ -2285,8 +2455,10 @@ const HOOK_EVENT_SET = new Set([
2285
2455
  "stream:text",
2286
2456
  "stream:end",
2287
2457
  "stream:thinking",
2458
+ "stream:error",
2288
2459
  "oauth:refresh",
2289
2460
  "tool:gate",
2461
+ "tool:dispatched",
2290
2462
  "tool:before",
2291
2463
  "tool:after",
2292
2464
  "tool:error",
@@ -2303,8 +2475,10 @@ const HOOK_EVENT_SET = new Set([
2303
2475
  "child:stream:text",
2304
2476
  "child:stream:thinking",
2305
2477
  "child:stream:end",
2478
+ "child:stream:error",
2306
2479
  "child:tool:gate",
2307
2480
  "child:mcp:tool:gate",
2481
+ "child:tool:dispatched",
2308
2482
  "child:tool:before",
2309
2483
  "child:tool:after",
2310
2484
  "child:tool:transform",
@@ -2333,6 +2507,7 @@ const HOOK_EVENT_SET = new Set([
2333
2507
  "output",
2334
2508
  "budget:exceeded",
2335
2509
  "tool-budget:exceeded",
2510
+ "pairing:repair",
2336
2511
  "agent:abort",
2337
2512
  "agent:done",
2338
2513
  "session:start",
@@ -2352,6 +2527,16 @@ function isKnownHookEvent(event) {
2352
2527
  * loop from leaving the persisted session with an orphan tool_use — which
2353
2528
  * Anthropic rejects on resume.
2354
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
+ *
2355
2540
  * No-op when:
2356
2541
  * - The trailing turn isn't an assistant turn (already closed, or session
2357
2542
  * ends with the seeded user prompt).
@@ -2359,7 +2544,7 @@ function isKnownHookEvent(event) {
2359
2544
  * - All tool_use ids are already answered by a tool_result somewhere later
2360
2545
  * in the conversation (defensive — shouldn't happen but cheap to check).
2361
2546
  */
2362
- function synthesizeMissingToolResults(turns, syntheticTurnId, runId, provider) {
2547
+ async function synthesizeMissingToolResults(turns, syntheticTurnId, runId, provider, hooks) {
2363
2548
  if (turns.length === 0) return;
2364
2549
  const last = turns[turns.length - 1];
2365
2550
  if (last.role !== "assistant") return;
@@ -2372,7 +2557,8 @@ function synthesizeMissingToolResults(turns, syntheticTurnId, runId, provider) {
2372
2557
  if (dangling.length === 0) return;
2373
2558
  const results = dangling.map((id) => ({
2374
2559
  id,
2375
- content: "Aborted: run failed before tool execution completed"
2560
+ content: SYNTHETIC_TOOL_RESULT_PLACEHOLDER,
2561
+ isError: true
2376
2562
  }));
2377
2563
  const msg = provider.toolResultsMessage(results);
2378
2564
  turns.push({
@@ -2382,6 +2568,11 @@ function synthesizeMissingToolResults(turns, syntheticTurnId, runId, provider) {
2382
2568
  content: msg.content,
2383
2569
  createdAt: Date.now()
2384
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
+ });
2385
2576
  }
2386
2577
  function resolveBehavior(agentBehavior, runBehavior) {
2387
2578
  return {
@@ -2406,7 +2597,8 @@ function resolveBehavior(agentBehavior, runBehavior) {
2406
2597
  toolSearch: runBehavior?.toolSearch ?? agentBehavior?.toolSearch,
2407
2598
  persistThreshold: runBehavior?.persistThreshold ?? agentBehavior?.persistThreshold,
2408
2599
  persistExcludeTools: runBehavior?.persistExcludeTools ?? agentBehavior?.persistExcludeTools,
2409
- persistDir: runBehavior?.persistDir ?? agentBehavior?.persistDir
2600
+ persistDir: runBehavior?.persistDir ?? agentBehavior?.persistDir,
2601
+ strictToolPairing: runBehavior?.strictToolPairing ?? agentBehavior?.strictToolPairing ?? false
2410
2602
  };
2411
2603
  }
2412
2604
  /**
@@ -2494,7 +2686,7 @@ function buildSearchableCatalog(entries, options) {
2494
2686
  }
2495
2687
  const serverNames = [...byServer.keys()].sort();
2496
2688
  const parts = [];
2497
- 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.`, "");
2498
2690
  parts.push("<searchable_tools>");
2499
2691
  for (const server of serverNames) {
2500
2692
  parts.push(` <server name="${escapeXml(server)}">`);
@@ -2622,9 +2814,14 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2622
2814
  if (running) throw new Error("Agent is already running. Use steer() or followUp() to queue messages, or waitForIdle().");
2623
2815
  const hasSessionTurns = session && session.turns.length > 0;
2624
2816
  if (!options.prompt && !hasSessionTurns) throw new Error("prompt is required when no session with existing turns is provided");
2625
- if (!options.prompt && hasSessionTurns) {
2626
- const lastTurn = session.turns.at(-1);
2627
- 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
+ }
2628
2825
  }
2629
2826
  let externalAbortListener;
2630
2827
  const externalSignal = options.signal;
@@ -2689,7 +2886,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2689
2886
  const thinking = options.thinking ?? "off";
2690
2887
  const model = options.model ?? provider.meta.defaultModel;
2691
2888
  const resolvedBehavior = resolveBehavior(agentBehavior, options.behavior);
2692
- 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;
2693
2890
  let system = options.system || agentSystem || "You are a helpful assistant.";
2694
2891
  if (skillsCatalog) system = `${system}\n\n${skillsCatalog}`;
2695
2892
  const runBaseTools = options.tools !== void 0 ? options.tools : mcpConnection ? {
@@ -2754,7 +2951,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2754
2951
  if (isResume) {
2755
2952
  const childRunIds = new Set(session.runs.filter((r) => (r.depth ?? 0) > 0).map((r) => r.id));
2756
2953
  const resumed = childRunIds.size === 0 ? session.turns : session.turns.filter((t) => !t.runId || !childRunIds.has(t.runId));
2757
- turns.push(...resumed);
2954
+ const filteredForRuntime = resumeFilteredTurns && resumed === session.turns ? resumeFilteredTurns : filterUnresolvedToolUses(resumed);
2955
+ turns.push(...filteredForRuntime);
2758
2956
  }
2759
2957
  const runTurnStart = turns.length;
2760
2958
  if (options.system) await hooks.callHook("system:before", { system: options.system });
@@ -2797,7 +2995,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2797
2995
  const unregisterToolResultsSync = session ? hooks.hook("tool-results:after", persistPendingTurns) : void 0;
2798
2996
  async function flushTurns(opts = {}) {
2799
2997
  if (!session) return;
2800
- 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);
2801
2999
  const remaining = turns.slice(lastPersistedTurnCount);
2802
3000
  if (remaining.length > 0) {
2803
3001
  await session.appendTurns(remaining);
@@ -2876,6 +3074,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2876
3074
  ...persistThreshold !== void 0 ? { persistThreshold } : {},
2877
3075
  ...persistExcludeTools !== void 0 ? { persistExcludeTools } : {},
2878
3076
  ...persistDir !== void 0 ? { persistDir } : {},
3077
+ ...strictToolPairing ? { strictToolPairing: true } : {},
3078
+ providerName: provider.name,
2879
3079
  runStartMs,
2880
3080
  runToolCounts: {}
2881
3081
  });
@@ -4289,6 +4489,8 @@ const BUBBLED_EVENTS = [
4289
4489
  "stream:text",
4290
4490
  "stream:thinking",
4291
4491
  "stream:end",
4492
+ "stream:error",
4493
+ "tool:dispatched",
4292
4494
  "tool:before",
4293
4495
  "tool:after",
4294
4496
  "tool:error",
@@ -4303,6 +4505,8 @@ const CHILD_EVENT_NAME = {
4303
4505
  "stream:text": "child:stream:text",
4304
4506
  "stream:thinking": "child:stream:thinking",
4305
4507
  "stream:end": "child:stream:end",
4508
+ "stream:error": "child:stream:error",
4509
+ "tool:dispatched": "child:tool:dispatched",
4306
4510
  "tool:before": "child:tool:before",
4307
4511
  "tool:after": "child:tool:after",
4308
4512
  "tool:error": "child:tool:error",
@@ -4699,6 +4903,6 @@ const writeFile$1 = {
4699
4903
  }
4700
4904
  };
4701
4905
  //#endregion
4702
- 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 };
4703
4907
 
4704
- //# sourceMappingURL=tools-BRbbfdJh.js.map
4908
+ //# sourceMappingURL=tools-CWEDS2ZT.js.map