@sema-agent/core 2.3.0 → 2.5.0

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 (83) hide show
  1. package/dist/agents/send-message-tool.d.ts +4 -0
  2. package/dist/agents/send-message-tool.js +37 -24
  3. package/dist/agents/subagent.js +279 -128
  4. package/dist/brain/errors.d.ts +1 -0
  5. package/dist/brain/errors.js +14 -0
  6. package/dist/brain/stream-engine.js +3 -3
  7. package/dist/core/auto-compaction.d.ts +2 -0
  8. package/dist/core/auto-compaction.js +2 -1
  9. package/dist/core/context-edit.js +2 -1
  10. package/dist/core/mcp.js +4 -1
  11. package/dist/core/runner/prepare-task.d.ts +5 -0
  12. package/dist/core/runner/prepare-task.js +41 -2
  13. package/dist/core/runner/runtask.js +39 -9
  14. package/dist/core/runner/tool-disclosure.d.ts +1 -0
  15. package/dist/core/runner/tool-disclosure.js +16 -5
  16. package/dist/core/runner/tool-output-projection.js +2 -1
  17. package/dist/core/session-reconcile.d.ts +1 -0
  18. package/dist/core/session-reconcile.js +40 -20
  19. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +37 -0
  20. package/dist/core/store-contracts/checkpoint-store-contract.js +195 -0
  21. package/dist/core/store-contracts/contract-harness.d.ts +6 -0
  22. package/dist/core/store-contracts/contract-harness.js +16 -0
  23. package/dist/core/store-contracts/contract-kit-version.d.ts +1 -0
  24. package/dist/core/store-contracts/contract-kit-version.js +2 -0
  25. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +3 -0
  26. package/dist/core/store-contracts/file-snapshot-store-contract.js +126 -0
  27. package/dist/core/store-contracts/mailbox-store-contract.d.ts +6 -0
  28. package/dist/core/store-contracts/mailbox-store-contract.js +193 -0
  29. package/dist/core/store-contracts/session-repo-contract.d.ts +3 -0
  30. package/dist/core/store-contracts/session-repo-contract.js +36 -0
  31. package/dist/core/store-contracts/tool-result-store-contract.d.ts +3 -0
  32. package/dist/core/store-contracts/tool-result-store-contract.js +35 -0
  33. package/dist/core/task-notification.d.ts +2 -0
  34. package/dist/core/task-registry-agent.d.ts +6 -0
  35. package/dist/core/task-registry-agent.js +24 -1
  36. package/dist/core/task-registry-monitor.js +6 -6
  37. package/dist/core/task-registry-shared.d.ts +9 -2
  38. package/dist/core/task-registry-shared.js +1 -1
  39. package/dist/core/task-registry.d.ts +8 -0
  40. package/dist/core/task-registry.js +59 -4
  41. package/dist/core/tool-result-store.d.ts +3 -2
  42. package/dist/core/tool-result-store.js +12 -4
  43. package/dist/core/trace.d.ts +7 -0
  44. package/dist/core/types.d.ts +10 -3
  45. package/dist/engine/compaction/compaction.d.ts +5 -0
  46. package/dist/engine/compaction/compaction.js +68 -2
  47. package/dist/engine/compaction/utils.d.ts +6 -0
  48. package/dist/engine/compaction/utils.js +53 -3
  49. package/dist/engine/harness/messages.d.ts +1 -1
  50. package/dist/engine/harness/messages.js +11 -3
  51. package/dist/engine/loop/types.d.ts +2 -0
  52. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
  53. package/dist/engine/lsp/node-lsp-manager.js +16 -0
  54. package/dist/engine/session/import-validate.js +30 -1
  55. package/dist/engine/session/session.js +7 -5
  56. package/dist/index.d.ts +8 -0
  57. package/dist/index.js +8 -0
  58. package/dist/internal/harness.d.ts +1 -1
  59. package/dist/internal/harness.js +1 -1
  60. package/dist/orchestration/builtin-workflows.d.ts +1 -1
  61. package/dist/orchestration/builtin-workflows.js +11 -2
  62. package/dist/orchestration/workflow-governance.d.ts +6 -1
  63. package/dist/orchestration/workflow-governance.js +24 -4
  64. package/dist/orchestration/workflow-primitives.js +7 -1
  65. package/dist/orchestration/workflow-types.d.ts +1 -0
  66. package/dist/orchestration/workflow.d.ts +1 -0
  67. package/dist/orchestration/workflow.js +41 -3
  68. package/dist/tools/fs/fs-bash.d.ts +7 -1
  69. package/dist/tools/fs/fs-bash.js +59 -22
  70. package/dist/tools/fs/fs-read.js +22 -11
  71. package/dist/tools/fs/fs-search-tools.js +3 -3
  72. package/dist/tools/fs/fs-shared.d.ts +20 -7
  73. package/dist/tools/fs/fs-shared.js +17 -3
  74. package/dist/tools/fs/fs-write.js +4 -4
  75. package/dist/tools/fs/index.d.ts +2 -0
  76. package/dist/tools/fs/index.js +7 -1
  77. package/dist/tools/fs/repo-map.js +2 -2
  78. package/dist/tools/fs/safety.d.ts +10 -0
  79. package/dist/tools/fs/safety.js +15 -1
  80. package/dist/tools/monitor.js +18 -4
  81. package/dist/tools/web.js +6 -2
  82. package/dist/tools/worktree.js +46 -25
  83. package/package.json +1 -1
@@ -197,6 +197,7 @@ const ccElapsedTag = (ms) => {
197
197
  return sec >= 60 ? ` \u00b7 ${Math.floor(sec / 60)}m${sec % 60}s` : ` \u00b7 ${sec}s`;
198
198
  };
199
199
  const ccCompletionText = (desc, settled, rawStatus, elapsedMs) => `Agent "${desc}" ${settled === "killed" ? "stopped" : settled === "completed" ? "finished" : rawStatus}${ccElapsedTag(elapsedMs)}`;
200
+ const BG_AGENT_COLLATERAL_REAP_REASON = "its parent run ended";
200
201
  const RESERVED_AGENT_NAMES = new Set([OUTPUT_TOOL_NAME, TOOL_SEARCH_NAME, OFFLOAD_TOOL_NAME, REPORT_BLOCKED_TOOL_NAME, DEFAULT_SUBAGENT_TOOL_NAME, LEGACY_SUBAGENT_TOOL_NAME]);
201
202
  export const REPORT_FIELD_MAX = 300;
202
203
  function configError(message, code) {
@@ -220,6 +221,9 @@ export function classifySubagentError(child) {
220
221
  : "logic";
221
222
  return { errorKind, retryable: errorKind !== "logic" };
222
223
  }
224
+ function errorKindClause(c) {
225
+ return c !== undefined ? ` (error_kind: ${c.errorKind}, retryable: ${c.retryable})` : "";
226
+ }
223
227
  const FAILED_SESSION_RETAIN_TTL_MS = 15 * 60 * 1000;
224
228
  const PARTIAL_FINDINGS_MAX_CHARS = 1200;
225
229
  const BG_NOTIFY_DRAIN_WINDOW_MS = 2_000;
@@ -415,12 +419,18 @@ export function createSubagentResume(deps) {
415
419
  void _;
416
420
  const child = await stream.result();
417
421
  let status = child.status === "completed" ? "completed" : abort.signal.aborted ? "killed" : "failed";
422
+ const errCodeRevive = status === "failed" ? (child.errorCode ?? extractErrorCode(child.errorMessage)) : undefined;
423
+ const errClassRevive = status === "failed"
424
+ ? classifySubagentError({ status: "failed", ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}), ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}) })
425
+ : undefined;
418
426
  if (reviveCycle !== undefined && deps.registry !== undefined && deps.taskId !== undefined) {
419
427
  try {
420
428
  const winner = deps.registry.settleRevivedAgent(deps.taskId, reviveCycle, {
421
429
  status,
422
430
  ...resultSettleFields(child.result),
423
431
  ...(status !== "completed" ? { error: (child.errorMessage ?? `resumed run ${status}`).slice(0, 500) } : {}),
432
+ ...(errCodeRevive !== undefined ? { errorCode: errCodeRevive } : {}),
433
+ ...(errClassRevive !== undefined ? { retryable: errClassRevive.retryable, errorKind: errClassRevive.errorKind } : {}),
424
434
  });
425
435
  if (winner !== undefined)
426
436
  status = winner;
@@ -431,21 +441,27 @@ export function createSubagentResume(deps) {
431
441
  const stoppedByRevive = status === "killed" ? deps.registry?.getStopAttribution(deps.taskId ?? "") ?? "system" : undefined;
432
442
  const completionIdRevive = deps.registry?.getCompletionId(deps.taskId ?? "");
433
443
  const reviveName = deps.rowDescription ?? `sub-agent ${marker}`;
434
- reviveEmit?.({
435
- kind: "terminal",
436
- taskId: deps.taskId,
437
- sessionScoped: deps.sessionScoped === true,
438
- sessionId: entry.childSessionId,
439
- transcriptId: entry.childSessionId,
440
- ...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
441
- status,
442
- seq: entry.cycleSeq,
443
- ...(stoppedByRevive !== undefined ? { stoppedBy: stoppedByRevive } : {}),
444
- summary: `Agent "${reviveName}" (resumed) ${status === "killed" ? "stopped" : child.status === "completed" ? "finished" : String(child.status)}${ccElapsedTag(Date.now() - reviveStartedAt)}`,
445
- resumable: status !== "killed",
446
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: child.stats.costMicroUsd },
447
- ...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
448
- });
444
+ const failReasonRevive = status === "failed" ? child.errorMessage : undefined;
445
+ const reviveTerminalSummary = failReasonRevive !== undefined
446
+ ? `Agent "${reviveName}" (resumed) failed: ${failReasonRevive}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300) + errorKindClause(errClassRevive)
447
+ : `Agent "${reviveName}" (resumed) ${status === "killed" ? "stopped" : child.status === "completed" ? "finished" : String(child.status)}${ccElapsedTag(Date.now() - reviveStartedAt)}`;
448
+ const ownsTerminalFacesRevive = deps.registry !== undefined && deps.taskId !== undefined ? deps.registry.claimAgentTerminalNotify(deps.taskId) : true;
449
+ if (ownsTerminalFacesRevive)
450
+ reviveEmit?.({
451
+ kind: "terminal",
452
+ taskId: deps.taskId,
453
+ sessionScoped: deps.sessionScoped === true,
454
+ sessionId: entry.childSessionId,
455
+ transcriptId: entry.childSessionId,
456
+ ...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
457
+ status,
458
+ seq: entry.cycleSeq,
459
+ ...(stoppedByRevive !== undefined ? { stoppedBy: stoppedByRevive } : {}),
460
+ summary: reviveTerminalSummary,
461
+ resumable: status !== "killed",
462
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: child.stats.costMicroUsd },
463
+ ...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
464
+ });
449
465
  const resumeFrame = {
450
466
  task_id: deps.taskId ?? entry.childSessionId,
451
467
  ...(entry.childSessionId !== undefined ? { sessionId: entry.childSessionId } : {}),
@@ -454,7 +470,9 @@ export function createSubagentResume(deps) {
454
470
  status,
455
471
  ...(stoppedByRevive !== undefined ? { stoppedBy: stoppedByRevive } : {}),
456
472
  seq: entry.cycleSeq,
457
- summary: `Agent "${reviveName}" (resumed) ${status === "killed" ? "stopped" : child.status === "completed" ? "finished" : String(child.status)}${ccElapsedTag(Date.now() - reviveStartedAt)}`,
473
+ summary: reviveTerminalSummary,
474
+ ...(failReasonRevive !== undefined ? { error: failReasonRevive.slice(0, REPORT_FIELD_MAX) } : {}),
475
+ ...(status === "failed" && errCodeRevive !== undefined ? { errorCode: errCodeRevive } : {}),
458
476
  ...(child.result ? { result: notifyResultField(child.result) } : {}),
459
477
  ...(status === "killed" && child.result ? { partial: true } : {}),
460
478
  ...resumeResidual(),
@@ -462,7 +480,7 @@ export function createSubagentResume(deps) {
462
480
  usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: child.stats.costMicroUsd },
463
481
  ...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
464
482
  };
465
- const gateOwner = status === "completed" && deps.notify !== undefined && deps.registry !== undefined && ledger.get(deps.parentToolCallId) === entry
483
+ const gateOwner = ownsTerminalFacesRevive && status === "completed" && deps.notify !== undefined && deps.registry !== undefined && ledger.get(deps.parentToolCallId) === entry
466
484
  ? entry.childSessionId
467
485
  : undefined;
468
486
  const aliveBgCount = gateOwner !== undefined ? deps.registry.countRunningBackgroundForOwner(gateOwner) : 0;
@@ -476,7 +494,7 @@ export function createSubagentResume(deps) {
476
494
  notify: deps.notify,
477
495
  });
478
496
  }
479
- else {
497
+ else if (ownsTerminalFacesRevive) {
480
498
  try {
481
499
  deps.notify?.(resumeFrame, { priority: "later" });
482
500
  }
@@ -485,11 +503,16 @@ export function createSubagentResume(deps) {
485
503
  }
486
504
  }
487
505
  catch (e) {
506
+ const msgRevive = e instanceof Error ? e.message : String(e);
507
+ const errCodeReviveReject = abort.signal.aborted ? undefined : extractErrorCode(msgRevive);
508
+ const errClassReviveReject = abort.signal.aborted ? undefined : classifySubagentError({ status: "failed", errorMessage: msgRevive });
488
509
  if (reviveCycle !== undefined && deps.registry !== undefined && deps.taskId !== undefined) {
489
510
  try {
490
511
  deps.registry.settleRevivedAgent(deps.taskId, reviveCycle, {
491
512
  status: abort.signal.aborted ? "killed" : "failed",
492
- error: (e instanceof Error ? e.message : String(e)).slice(0, 500),
513
+ error: msgRevive.slice(0, 500),
514
+ ...(errCodeReviveReject !== undefined ? { errorCode: errCodeReviveReject } : {}),
515
+ ...(errClassReviveReject !== undefined ? { retryable: errClassReviveReject.retryable, errorKind: errClassReviveReject.errorKind } : {}),
493
516
  });
494
517
  }
495
518
  catch {
@@ -498,31 +521,36 @@ export function createSubagentResume(deps) {
498
521
  const stoppedByReject = abort.signal.aborted ? deps.registry?.getStopAttribution(deps.taskId ?? "") ?? "system" : undefined;
499
522
  const completionIdRejectRevive = deps.registry?.getCompletionId(deps.taskId ?? "");
500
523
  const rejectName = deps.rowDescription ?? `sub-agent ${marker}`;
501
- reviveEmit?.({
502
- kind: "terminal",
503
- taskId: deps.taskId,
504
- sessionScoped: deps.sessionScoped === true,
505
- sessionId: entry.childSessionId,
506
- transcriptId: entry.childSessionId,
507
- ...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
508
- status: abort.signal.aborted ? "killed" : "failed",
509
- seq: entry.cycleSeq,
510
- ...(stoppedByReject !== undefined ? { stoppedBy: stoppedByReject } : {}),
511
- summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : "failed"}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300),
512
- ...(completionIdRejectRevive !== undefined ? { completionId: completionIdRejectRevive } : {}),
513
- });
514
- try {
515
- deps.notify?.({
516
- task_id: deps.taskId ?? entry.childSessionId,
517
- ...(entry.childSessionId !== undefined ? { sessionId: entry.childSessionId } : {}),
518
- task_type: "background_agent",
519
- toolUseId: deps.parentToolCallId,
524
+ const ownsTerminalFacesReviveReject = deps.registry !== undefined && deps.taskId !== undefined ? deps.registry.claimAgentTerminalNotify(deps.taskId) : true;
525
+ if (ownsTerminalFacesReviveReject)
526
+ reviveEmit?.({
527
+ kind: "terminal",
528
+ taskId: deps.taskId,
529
+ sessionScoped: deps.sessionScoped === true,
530
+ sessionId: entry.childSessionId,
531
+ transcriptId: entry.childSessionId,
532
+ ...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
520
533
  status: abort.signal.aborted ? "killed" : "failed",
521
534
  seq: entry.cycleSeq,
522
535
  ...(stoppedByReject !== undefined ? { stoppedBy: stoppedByReject } : {}),
523
- summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : `failed: ${e instanceof Error ? e.message : String(e)}`}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300),
536
+ summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : "failed"}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300) + (abort.signal.aborted ? "" : errorKindClause(errClassReviveReject)),
524
537
  ...(completionIdRejectRevive !== undefined ? { completionId: completionIdRejectRevive } : {}),
525
- }, { priority: "later" });
538
+ });
539
+ try {
540
+ if (ownsTerminalFacesReviveReject)
541
+ deps.notify?.({
542
+ task_id: deps.taskId ?? entry.childSessionId,
543
+ ...(entry.childSessionId !== undefined ? { sessionId: entry.childSessionId } : {}),
544
+ task_type: "background_agent",
545
+ toolUseId: deps.parentToolCallId,
546
+ status: abort.signal.aborted ? "killed" : "failed",
547
+ seq: entry.cycleSeq,
548
+ ...(stoppedByReject !== undefined ? { stoppedBy: stoppedByReject } : {}),
549
+ summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : `failed: ${msgRevive}`}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300) + (abort.signal.aborted ? "" : errorKindClause(errClassReviveReject)),
550
+ ...(abort.signal.aborted ? {} : { error: msgRevive.slice(0, REPORT_FIELD_MAX) }),
551
+ ...(!abort.signal.aborted && errCodeReviveReject !== undefined ? { errorCode: errCodeReviveReject } : {}),
552
+ ...(completionIdRejectRevive !== undefined ? { completionId: completionIdRejectRevive } : {}),
553
+ }, { priority: "later" });
526
554
  }
527
555
  catch {
528
556
  }
@@ -1588,6 +1616,40 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1588
1616
  return { isError: true, content: `Sub-agent not started in background: ${e instanceof Error ? e.message : String(e)}${wt ? `\n${wt}` : ""}`, details: { error: "register_failed" } };
1589
1617
  }
1590
1618
  bg.registry.bindBackgroundAgentSession(taskId, forkedId);
1619
+ const forkBgHangAt = Date.now();
1620
+ bg.registry.attachAgentTerminalNotifier(taskId, () => {
1621
+ const stoppedByReap = bg.registry.getStopAttribution(taskId) ?? "system";
1622
+ const completionIdReap = bg.registry.getCompletionId(taskId);
1623
+ const summaryReap = ccCompletionText(shortDesc, "killed", "killed", Date.now() - forkBgHangAt);
1624
+ sinkEmit({
1625
+ kind: "terminal",
1626
+ taskId,
1627
+ sessionScoped: sessionScopedBg === true,
1628
+ sessionId: forkedId,
1629
+ transcriptId: forkedId,
1630
+ parentToolCallId: ctx.toolCallId,
1631
+ status: "killed",
1632
+ stoppedBy: stoppedByReap,
1633
+ summary: summaryReap,
1634
+ resumable: false,
1635
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
1636
+ });
1637
+ try {
1638
+ notify?.({
1639
+ task_id: taskId,
1640
+ task_type: "background_agent",
1641
+ toolUseId: ctx.toolCallId,
1642
+ status: "killed",
1643
+ stoppedBy: stoppedByReap,
1644
+ summary: summaryReap,
1645
+ sessionId: forkedId,
1646
+ resumable: false,
1647
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
1648
+ }, { priority: "later" });
1649
+ }
1650
+ catch {
1651
+ }
1652
+ });
1591
1653
  if (agentName !== undefined) {
1592
1654
  recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, sessionId: forkedId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: Date.now() });
1593
1655
  }
@@ -1683,10 +1745,18 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1683
1745
  }
1684
1746
  const okBg = child.status === "completed";
1685
1747
  const reapedBg = !okBg && abort.signal.aborted;
1748
+ const collateralBg = reapedBg && bg.registry.getAccessibleTask(taskId, { ...(bgOwner !== undefined ? { owner: bgOwner } : {}), ...(bgScope !== undefined ? { scope: bgScope } : {}) })?.status === "running";
1749
+ const failedFork = !okBg && !reapedBg;
1750
+ const errCodeFork = failedFork ? (child.errorCode ?? extractErrorCode(child.errorMessage)) : undefined;
1751
+ const errClassFork = failedFork
1752
+ ? classifySubagentError({ status: "failed", ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}), ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}) })
1753
+ : undefined;
1686
1754
  const settledBg = bg.registry.settleBackgroundAgent(taskId, {
1687
1755
  status: okBg ? "completed" : reapedBg ? "killed" : "failed",
1688
1756
  ...resultSettleFields(child.result),
1689
- ...(!okBg ? { error: reapedBg ? BG_AGENT_REAP_STOP_ERROR : child.errorMessage ?? String(child.status) } : {}),
1757
+ ...(!okBg ? { error: reapedBg ? (collateralBg ? BG_AGENT_COLLATERAL_REAP_REASON : BG_AGENT_REAP_STOP_ERROR) : child.errorMessage ?? String(child.status) } : {}),
1758
+ ...(errCodeFork !== undefined ? { errorCode: errCodeFork } : {}),
1759
+ ...(errClassFork !== undefined ? { retryable: errClassFork.retryable, errorKind: errClassFork.errorKind } : {}),
1690
1760
  }) ?? (abort.signal.aborted ? "killed" : okBg ? "completed" : "failed");
1691
1761
  const stoppedByBg = settledBg === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
1692
1762
  const completionIdFork = bg.registry.getCompletionId(taskId);
@@ -1701,37 +1771,46 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1701
1771
  const residualFork = residualFields();
1702
1772
  const resumableFork = false;
1703
1773
  const forkTranscriptId = child.sessionId ?? forkedId;
1704
- sinkEmit({
1705
- kind: "terminal",
1706
- taskId,
1707
- sessionScoped: sessionScopedBg === true,
1708
- sessionId: forkTranscriptId,
1709
- ...(stillbornFork ? {} : { transcriptId: forkTranscriptId }),
1710
- parentToolCallId: ctx.toolCallId,
1711
- status: settledBg,
1712
- ...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
1713
- summary: `${ccCompletionText(shortDesc, settledBg, String(child.status), Date.now() - forkBgStartedAt)}`,
1714
- ...residualFork,
1715
- resumable: resumableFork,
1716
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
1717
- ...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
1718
- });
1719
- try {
1720
- notify?.({
1721
- task_id: taskId,
1722
- task_type: "background_agent",
1723
- toolUseId: ctx.toolCallId,
1774
+ const failReasonFork = settledBg === "failed" ? child.errorMessage : undefined;
1775
+ const forkTerminalSummary = failReasonFork !== undefined
1776
+ ? `Agent "${shortDesc}" failed: ${failReasonFork}${ccElapsedTag(Date.now() - forkBgStartedAt)}`.slice(0, 300) + errorKindClause(errClassFork)
1777
+ : `${ccCompletionText(shortDesc, settledBg, String(child.status), Date.now() - forkBgStartedAt)}`;
1778
+ const ownsTerminalFacesFork = bg.registry.claimAgentTerminalNotify(taskId);
1779
+ if (ownsTerminalFacesFork)
1780
+ sinkEmit({
1781
+ kind: "terminal",
1782
+ taskId,
1783
+ sessionScoped: sessionScopedBg === true,
1784
+ sessionId: forkTranscriptId,
1785
+ ...(stillbornFork ? {} : { transcriptId: forkTranscriptId }),
1786
+ parentToolCallId: ctx.toolCallId,
1724
1787
  status: settledBg,
1725
1788
  ...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
1726
- summary: `${ccCompletionText(shortDesc, settledBg, String(child.status), Date.now() - forkBgStartedAt)}`,
1727
- ...(child.sessionId ? { sessionId: child.sessionId } : {}),
1728
- ...(child.result ? { result: notifyResultField(child.result) } : {}),
1729
- ...(settledBg === "killed" && child.result ? { partial: true } : {}),
1789
+ summary: forkTerminalSummary,
1730
1790
  ...residualFork,
1731
1791
  resumable: resumableFork,
1732
1792
  usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
1733
1793
  ...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
1734
- }, { priority: "later" });
1794
+ });
1795
+ try {
1796
+ if (ownsTerminalFacesFork)
1797
+ notify?.({
1798
+ task_id: taskId,
1799
+ task_type: "background_agent",
1800
+ toolUseId: ctx.toolCallId,
1801
+ status: settledBg,
1802
+ ...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
1803
+ summary: forkTerminalSummary,
1804
+ ...(failReasonFork !== undefined ? { error: failReasonFork.slice(0, REPORT_FIELD_MAX) } : {}),
1805
+ ...(settledBg === "failed" && errCodeFork !== undefined ? { errorCode: errCodeFork } : {}),
1806
+ ...(child.sessionId ? { sessionId: child.sessionId } : {}),
1807
+ ...(child.result ? { result: notifyResultField(child.result) } : {}),
1808
+ ...(settledBg === "killed" && child.result ? { partial: true } : {}),
1809
+ ...residualFork,
1810
+ resumable: resumableFork,
1811
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
1812
+ ...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
1813
+ }, { priority: "later" });
1735
1814
  }
1736
1815
  catch {
1737
1816
  }
@@ -1740,36 +1819,46 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1740
1819
  dropHostAbortListener();
1741
1820
  void finishWorktree();
1742
1821
  const killed = abort.signal.aborted;
1822
+ const msgFork = e instanceof Error ? e.message : String(e);
1823
+ const errCodeForkReject = killed ? undefined : extractErrorCode(msgFork);
1824
+ const errClassForkReject = killed ? undefined : classifySubagentError({ status: "failed", errorMessage: msgFork });
1743
1825
  const settledBg = bg.registry.settleBackgroundAgent(taskId, {
1744
1826
  status: killed ? "killed" : "failed",
1745
- error: killed ? BG_AGENT_REAP_STOP_ERROR : e instanceof Error ? e.message : String(e),
1827
+ error: killed ? BG_AGENT_REAP_STOP_ERROR : msgFork,
1828
+ ...(errCodeForkReject !== undefined ? { errorCode: errCodeForkReject } : {}),
1829
+ ...(errClassForkReject !== undefined ? { retryable: errClassForkReject.retryable, errorKind: errClassForkReject.errorKind } : {}),
1746
1830
  }) ?? (killed ? "killed" : "failed");
1747
1831
  const stoppedByBg = settledBg === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
1748
1832
  const completionIdForkReject = bg.registry.getCompletionId(taskId);
1749
- const summaryBg = `${shortDesc} — ${settledBg === "failed" ? `failed: ${e instanceof Error ? e.message : String(e)}` : settledBg}`.slice(0, 300);
1750
- sinkEmit({
1751
- kind: "terminal",
1752
- taskId,
1753
- sessionScoped: sessionScopedBg === true,
1754
- sessionId: forkedId,
1755
- transcriptId: forkedId,
1756
- parentToolCallId: ctx.toolCallId,
1757
- status: settledBg,
1758
- ...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
1759
- summary: summaryBg,
1760
- ...(completionIdForkReject !== undefined ? { completionId: completionIdForkReject } : {}),
1761
- });
1762
- try {
1763
- notify?.({
1764
- task_id: taskId,
1765
- task_type: "background_agent",
1766
- toolUseId: ctx.toolCallId,
1767
- status: settledBg,
1833
+ const summaryBg = `${shortDesc} — ${settledBg === "failed" ? `failed: ${msgFork}` : settledBg}`.slice(0, 300) + (settledBg === "failed" ? errorKindClause(errClassForkReject) : "");
1834
+ const ownsTerminalFacesForkReject = bg.registry.claimAgentTerminalNotify(taskId);
1835
+ if (ownsTerminalFacesForkReject)
1836
+ sinkEmit({
1837
+ kind: "terminal",
1838
+ taskId,
1839
+ sessionScoped: sessionScopedBg === true,
1768
1840
  sessionId: forkedId,
1841
+ transcriptId: forkedId,
1842
+ parentToolCallId: ctx.toolCallId,
1843
+ status: settledBg,
1769
1844
  ...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
1770
1845
  summary: summaryBg,
1771
1846
  ...(completionIdForkReject !== undefined ? { completionId: completionIdForkReject } : {}),
1772
- }, { priority: "later" });
1847
+ });
1848
+ try {
1849
+ if (ownsTerminalFacesForkReject)
1850
+ notify?.({
1851
+ task_id: taskId,
1852
+ task_type: "background_agent",
1853
+ toolUseId: ctx.toolCallId,
1854
+ status: settledBg,
1855
+ sessionId: forkedId,
1856
+ ...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
1857
+ summary: summaryBg,
1858
+ ...(settledBg === "failed" ? { error: msgFork.slice(0, REPORT_FIELD_MAX) } : {}),
1859
+ ...(settledBg === "failed" && errCodeForkReject !== undefined ? { errorCode: errCodeForkReject } : {}),
1860
+ ...(completionIdForkReject !== undefined ? { completionId: completionIdForkReject } : {}),
1861
+ }, { priority: "later" });
1773
1862
  }
1774
1863
  catch {
1775
1864
  }
@@ -1956,6 +2045,39 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1956
2045
  const bgStartedAt = Date.now();
1957
2046
  stepRecorder.lockTo(bgChildSessionId);
1958
2047
  bg.registry.bindBackgroundAgentSession(taskId, bgChildSessionId);
2048
+ bg.registry.attachAgentTerminalNotifier(taskId, () => {
2049
+ const stoppedByReap = bg.registry.getStopAttribution(taskId) ?? "system";
2050
+ const completionIdReap = bg.registry.getCompletionId(taskId);
2051
+ const summaryReap = ccCompletionText(shortDesc, "killed", "killed", Date.now() - bgStartedAt);
2052
+ sinkEmit({
2053
+ kind: "terminal",
2054
+ taskId,
2055
+ sessionScoped: sessionScopedBg === true,
2056
+ sessionId: bgChildSessionId,
2057
+ transcriptId: bgChildSessionId,
2058
+ parentToolCallId: ctx.toolCallId,
2059
+ status: "killed",
2060
+ stoppedBy: stoppedByReap,
2061
+ summary: summaryReap,
2062
+ resumable: false,
2063
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
2064
+ });
2065
+ try {
2066
+ notify?.({
2067
+ task_id: taskId,
2068
+ task_type: "background_agent",
2069
+ toolUseId: ctx.toolCallId,
2070
+ status: "killed",
2071
+ stoppedBy: stoppedByReap,
2072
+ summary: summaryReap,
2073
+ sessionId: bgChildSessionId,
2074
+ resumable: false,
2075
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
2076
+ }, { priority: "later" });
2077
+ }
2078
+ catch {
2079
+ }
2080
+ });
1959
2081
  const bgDurableProbe = bg.registry.durableAgentRowProbe(taskId);
1960
2082
  const bgRowConfirmed = async () => (bgDurableProbe ? await bgDurableProbe().catch(() => false) : false);
1961
2083
  sinkEmit({
@@ -2259,11 +2381,19 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2259
2381
  }
2260
2382
  const ok = child.status === "completed";
2261
2383
  const reaped = !ok && abort.signal.aborted;
2384
+ const collateral = reaped && bg.registry.getAccessibleTask(taskId, { ...(bgOwner !== undefined ? { owner: bgOwner } : {}), ...(bgScope !== undefined ? { scope: bgScope } : {}) })?.status === "running";
2385
+ const failedBg = !ok && !reaped;
2386
+ const errCodeBg = failedBg ? (child.errorCode ?? extractErrorCode(child.errorMessage)) : undefined;
2387
+ const errClassBg = failedBg
2388
+ ? classifySubagentError({ status: "failed", ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}), ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}) })
2389
+ : undefined;
2262
2390
  const settled = bg.registry.settleBackgroundAgent(taskId, {
2263
2391
  status: ok ? "completed" : reaped ? "killed" : "failed",
2264
2392
  seq: seqAtSettle ?? 1,
2265
2393
  ...resultSettleFields(child.result),
2266
- ...(!ok ? { error: reaped ? BG_AGENT_REAP_STOP_ERROR : child.errorMessage ?? String(child.status) } : {}),
2394
+ ...(!ok ? { error: reaped ? (collateral ? BG_AGENT_COLLATERAL_REAP_REASON : BG_AGENT_REAP_STOP_ERROR) : child.errorMessage ?? String(child.status) } : {}),
2395
+ ...(errCodeBg !== undefined ? { errorCode: errCodeBg } : {}),
2396
+ ...(errClassBg !== undefined ? { retryable: errClassBg.retryable, errorKind: errClassBg.errorKind } : {}),
2267
2397
  }) ??
2268
2398
  (abort.signal.aborted ? "killed" : ok ? "completed" : "failed");
2269
2399
  if (!bgRetain && reviveRow === undefined && !(await bgRowConfirmed())) {
@@ -2279,21 +2409,27 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2279
2409
  const observerNote = observerNoteFor(await startBoundedObserverDrain());
2280
2410
  const residual = residualFields();
2281
2411
  const resumableBg = bgRetain !== undefined && settled !== "killed";
2282
- sinkEmit({
2283
- kind: "terminal",
2284
- taskId,
2285
- sessionScoped: sessionScopedBg === true,
2286
- ...(child.sessionId ? { sessionId: child.sessionId, transcriptId: child.sessionId } : {}),
2287
- parentToolCallId: ctx.toolCallId,
2288
- status: settled,
2289
- ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2290
- ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2291
- summary: `${ccCompletionText(shortDesc, settled, String(child.status), Date.now() - bgStartedAt)}${observerNote}`,
2292
- ...residual,
2293
- resumable: resumableBg,
2294
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
2295
- ...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
2296
- });
2412
+ const failReasonBg = settled === "failed" ? child.errorMessage : undefined;
2413
+ const bgTerminalSummary = failReasonBg !== undefined
2414
+ ? `Agent "${shortDesc}" failed: ${failReasonBg}${ccElapsedTag(Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300) + errorKindClause(errClassBg)
2415
+ : `${ccCompletionText(shortDesc, settled, String(child.status), Date.now() - bgStartedAt)}${observerNote}`;
2416
+ const ownsTerminalFaces = bg.registry.claimAgentTerminalNotify(taskId);
2417
+ if (ownsTerminalFaces)
2418
+ sinkEmit({
2419
+ kind: "terminal",
2420
+ taskId,
2421
+ sessionScoped: sessionScopedBg === true,
2422
+ ...(child.sessionId ? { sessionId: child.sessionId, transcriptId: child.sessionId } : {}),
2423
+ parentToolCallId: ctx.toolCallId,
2424
+ status: settled,
2425
+ ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2426
+ ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2427
+ summary: bgTerminalSummary,
2428
+ ...residual,
2429
+ resumable: resumableBg,
2430
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
2431
+ ...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
2432
+ });
2297
2433
  const completionFrame = {
2298
2434
  task_id: taskId,
2299
2435
  task_type: "background_agent",
@@ -2301,7 +2437,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2301
2437
  status: settled,
2302
2438
  ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2303
2439
  ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2304
- summary: `${ccCompletionText(shortDesc, settled, String(child.status), Date.now() - bgStartedAt)}${observerNote}`,
2440
+ summary: bgTerminalSummary,
2441
+ ...(failReasonBg !== undefined ? { error: failReasonBg.slice(0, REPORT_FIELD_MAX) } : {}),
2442
+ ...(settled === "failed" && errCodeBg !== undefined ? { errorCode: errCodeBg } : {}),
2305
2443
  ...(child.sessionId ? { sessionId: child.sessionId } : {}),
2306
2444
  ...(child.result ? { result: notifyResultField(child.result) } : {}),
2307
2445
  ...(settled === "killed" && child.result ? { partial: true } : {}),
@@ -2310,7 +2448,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2310
2448
  usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
2311
2449
  ...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
2312
2450
  };
2313
- const gateOwner = settled === "completed" && notify !== undefined ? bgRetain?.childSessionId : undefined;
2451
+ const gateOwner = ownsTerminalFaces && settled === "completed" && notify !== undefined ? bgRetain?.childSessionId : undefined;
2314
2452
  const aliveBgCount = gateOwner !== undefined ? bg.registry.countRunningBackgroundForOwner(gateOwner) : 0;
2315
2453
  if (gateOwner !== undefined && bgRetain !== undefined && aliveBgCount > 0) {
2316
2454
  parkCompletionNotify({
@@ -2322,7 +2460,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2322
2460
  notify: notify,
2323
2461
  });
2324
2462
  }
2325
- else {
2463
+ else if (ownsTerminalFaces) {
2326
2464
  try {
2327
2465
  notify?.(completionFrame, { priority: "later" });
2328
2466
  }
@@ -2343,7 +2481,15 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2343
2481
  bgRetainLedger?.markSettled(ctx.toolCallId);
2344
2482
  }
2345
2483
  const msg = e instanceof Error ? e.message : String(e);
2346
- const settled = bg.registry.settleBackgroundAgent(taskId, { status: killed ? "killed" : "failed", error: msg, seq: seqAtSettle ?? 1 }) ?? (killed ? "killed" : "failed");
2484
+ const errCodeBgReject = killed ? undefined : extractErrorCode(msg);
2485
+ const errClassBgReject = killed ? undefined : classifySubagentError({ status: "failed", errorMessage: msg });
2486
+ const settled = bg.registry.settleBackgroundAgent(taskId, {
2487
+ status: killed ? "killed" : "failed",
2488
+ error: msg,
2489
+ seq: seqAtSettle ?? 1,
2490
+ ...(errCodeBgReject !== undefined ? { errorCode: errCodeBgReject } : {}),
2491
+ ...(errClassBgReject !== undefined ? { retryable: errClassBgReject.retryable, errorKind: errClassBgReject.errorKind } : {}),
2492
+ }) ?? (killed ? "killed" : "failed");
2347
2493
  if (!bgRetain && reviveRow === undefined && !(await bgRowConfirmed())) {
2348
2494
  try {
2349
2495
  await opts.runner.sessions.release(bgChildSessionId);
@@ -2355,31 +2501,36 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2355
2501
  const stoppedBy = settled === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
2356
2502
  const completionIdBgReject = bg.registry.getCompletionId(taskId);
2357
2503
  const observerNote = observerNoteFor(await startBoundedObserverDrain());
2358
- sinkEmit({
2359
- kind: "terminal",
2360
- taskId,
2361
- sessionScoped: sessionScopedBg === true,
2362
- sessionId: bgChildSessionId,
2363
- transcriptId: bgChildSessionId,
2364
- parentToolCallId: ctx.toolCallId,
2365
- status: settled,
2366
- ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2367
- ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2368
- summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300),
2369
- ...(completionIdBgReject !== undefined ? { completionId: completionIdBgReject } : {}),
2370
- });
2371
- try {
2372
- notify?.({
2373
- task_id: taskId,
2374
- task_type: "background_agent",
2375
- toolUseId: ctx.toolCallId,
2376
- status: settled,
2504
+ const ownsTerminalFacesReject = bg.registry.claimAgentTerminalNotify(taskId);
2505
+ if (ownsTerminalFacesReject)
2506
+ sinkEmit({
2507
+ kind: "terminal",
2508
+ taskId,
2509
+ sessionScoped: sessionScopedBg === true,
2377
2510
  sessionId: bgChildSessionId,
2511
+ transcriptId: bgChildSessionId,
2512
+ parentToolCallId: ctx.toolCallId,
2513
+ status: settled,
2378
2514
  ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2379
2515
  ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2380
- summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300),
2516
+ summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300) + (settled === "failed" ? errorKindClause(errClassBgReject) : ""),
2381
2517
  ...(completionIdBgReject !== undefined ? { completionId: completionIdBgReject } : {}),
2382
- }, { priority: "later" });
2518
+ });
2519
+ try {
2520
+ if (ownsTerminalFacesReject)
2521
+ notify?.({
2522
+ task_id: taskId,
2523
+ task_type: "background_agent",
2524
+ toolUseId: ctx.toolCallId,
2525
+ status: settled,
2526
+ sessionId: bgChildSessionId,
2527
+ ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2528
+ ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2529
+ summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300) + (settled === "failed" ? errorKindClause(errClassBgReject) : ""),
2530
+ ...(settled === "failed" ? { error: msg.slice(0, REPORT_FIELD_MAX) } : {}),
2531
+ ...(settled === "failed" && errCodeBgReject !== undefined ? { errorCode: errCodeBgReject } : {}),
2532
+ ...(completionIdBgReject !== undefined ? { completionId: completionIdBgReject } : {}),
2533
+ }, { priority: "later" });
2383
2534
  }
2384
2535
  catch {
2385
2536
  }
@@ -12,4 +12,5 @@ export declare class BrainError extends Error {
12
12
  export declare function classifyHttp(status: number): BrainErrorCode;
13
13
  export declare function extractErrorCode(errorMessage: string | undefined): BrainErrorCode | undefined;
14
14
  export declare function stripErrorCodePrefix(errorMessage: string): string;
15
+ export declare function describeNetworkError(e: unknown): string;
15
16
  export {};
@@ -42,3 +42,17 @@ export function extractErrorCode(errorMessage) {
42
42
  export function stripErrorCodePrefix(errorMessage) {
43
43
  return errorMessage.replace(CODE_PREFIX_RE, "");
44
44
  }
45
+ export function describeNetworkError(e) {
46
+ const top = e instanceof Error ? e.message : String(e);
47
+ const parts = [];
48
+ let cur = e instanceof Error ? e.cause : undefined;
49
+ for (let depth = 0; depth < 4 && cur !== undefined && cur !== null; depth++) {
50
+ const code = typeof cur.code === "string" ? cur.code : undefined;
51
+ const msg = cur instanceof Error ? cur.message : String(cur);
52
+ const piece = code !== undefined && !msg.includes(code) ? `${code}: ${msg}` : msg;
53
+ if (piece.length > 0 && piece !== top && !parts.includes(piece))
54
+ parts.push(piece);
55
+ cur = cur instanceof Error ? cur.cause : undefined;
56
+ }
57
+ return parts.length > 0 ? `${top} (${parts.join(" ← ")})` : top;
58
+ }