@sema-agent/core 2.2.0 → 2.4.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 (70) 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 +275 -127
  4. package/dist/agents/teacher.js +51 -23
  5. package/dist/brain/errors.d.ts +1 -0
  6. package/dist/brain/errors.js +14 -0
  7. package/dist/brain/stream-engine.js +3 -3
  8. package/dist/core/context-edit.js +2 -1
  9. package/dist/core/runner/assemble-result.d.ts +1 -0
  10. package/dist/core/runner/assemble-result.js +12 -8
  11. package/dist/core/runner/prepare-task.js +32 -7
  12. package/dist/core/runner/runtask.js +18 -5
  13. package/dist/core/runner/tool-output-projection.js +2 -1
  14. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +37 -0
  15. package/dist/core/store-contracts/checkpoint-store-contract.js +195 -0
  16. package/dist/core/store-contracts/contract-harness.d.ts +6 -0
  17. package/dist/core/store-contracts/contract-harness.js +16 -0
  18. package/dist/core/store-contracts/contract-kit-version.d.ts +1 -0
  19. package/dist/core/store-contracts/contract-kit-version.js +2 -0
  20. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +3 -0
  21. package/dist/core/store-contracts/file-snapshot-store-contract.js +126 -0
  22. package/dist/core/store-contracts/mailbox-store-contract.d.ts +6 -0
  23. package/dist/core/store-contracts/mailbox-store-contract.js +193 -0
  24. package/dist/core/store-contracts/session-repo-contract.d.ts +3 -0
  25. package/dist/core/store-contracts/session-repo-contract.js +36 -0
  26. package/dist/core/store-contracts/tool-result-store-contract.d.ts +3 -0
  27. package/dist/core/store-contracts/tool-result-store-contract.js +35 -0
  28. package/dist/core/task-notification.d.ts +2 -0
  29. package/dist/core/task-registry-agent.d.ts +7 -1
  30. package/dist/core/task-registry-agent.js +31 -2
  31. package/dist/core/task-registry-monitor.js +71 -6
  32. package/dist/core/task-registry-shared.d.ts +18 -1
  33. package/dist/core/task-registry-shared.js +5 -2
  34. package/dist/core/task-registry.d.ts +9 -0
  35. package/dist/core/task-registry.js +52 -7
  36. package/dist/core/tool-result-store.d.ts +3 -2
  37. package/dist/core/tool-result-store.js +12 -4
  38. package/dist/core/tools.d.ts +2 -0
  39. package/dist/core/tools.js +9 -0
  40. package/dist/core/trace.d.ts +7 -0
  41. package/dist/core/workflow-journal-store.d.ts +16 -0
  42. package/dist/core/workflow-journal-store.js +28 -0
  43. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
  44. package/dist/engine/lsp/node-lsp-manager.js +16 -0
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.js +1 -0
  47. package/dist/orchestration/builtin-workflows.d.ts +1 -1
  48. package/dist/orchestration/builtin-workflows.js +11 -2
  49. package/dist/orchestration/workflow-governance.d.ts +6 -1
  50. package/dist/orchestration/workflow-governance.js +24 -4
  51. package/dist/orchestration/workflow-primitives.js +7 -1
  52. package/dist/orchestration/workflow.d.ts +16 -0
  53. package/dist/orchestration/workflow.js +94 -21
  54. package/dist/tools/fs/fs-bash.d.ts +7 -1
  55. package/dist/tools/fs/fs-bash.js +51 -20
  56. package/dist/tools/fs/fs-read.js +22 -11
  57. package/dist/tools/fs/fs-search-tools.js +3 -3
  58. package/dist/tools/fs/fs-shared.d.ts +20 -7
  59. package/dist/tools/fs/fs-shared.js +17 -3
  60. package/dist/tools/fs/fs-write.js +4 -4
  61. package/dist/tools/fs/index.d.ts +2 -0
  62. package/dist/tools/fs/index.js +7 -1
  63. package/dist/tools/fs/repo-map.js +2 -2
  64. package/dist/tools/fs/safety.d.ts +10 -0
  65. package/dist/tools/fs/safety.js +15 -1
  66. package/dist/tools/monitor.d.ts +2 -0
  67. package/dist/tools/monitor.js +20 -4
  68. package/dist/tools/web.js +6 -2
  69. package/dist/tools/worktree.js +46 -25
  70. 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) {
@@ -415,12 +416,18 @@ export function createSubagentResume(deps) {
415
416
  void _;
416
417
  const child = await stream.result();
417
418
  let status = child.status === "completed" ? "completed" : abort.signal.aborted ? "killed" : "failed";
419
+ const errCodeRevive = status === "failed" ? (child.errorCode ?? extractErrorCode(child.errorMessage)) : undefined;
420
+ const errClassRevive = status === "failed"
421
+ ? classifySubagentError({ status: "failed", ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}), ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}) })
422
+ : undefined;
418
423
  if (reviveCycle !== undefined && deps.registry !== undefined && deps.taskId !== undefined) {
419
424
  try {
420
425
  const winner = deps.registry.settleRevivedAgent(deps.taskId, reviveCycle, {
421
426
  status,
422
427
  ...resultSettleFields(child.result),
423
428
  ...(status !== "completed" ? { error: (child.errorMessage ?? `resumed run ${status}`).slice(0, 500) } : {}),
429
+ ...(errCodeRevive !== undefined ? { errorCode: errCodeRevive } : {}),
430
+ ...(errClassRevive !== undefined ? { retryable: errClassRevive.retryable } : {}),
424
431
  });
425
432
  if (winner !== undefined)
426
433
  status = winner;
@@ -431,21 +438,27 @@ export function createSubagentResume(deps) {
431
438
  const stoppedByRevive = status === "killed" ? deps.registry?.getStopAttribution(deps.taskId ?? "") ?? "system" : undefined;
432
439
  const completionIdRevive = deps.registry?.getCompletionId(deps.taskId ?? "");
433
440
  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
- });
441
+ const failReasonRevive = status === "failed" ? child.errorMessage : undefined;
442
+ const reviveTerminalSummary = failReasonRevive !== undefined
443
+ ? `Agent "${reviveName}" (resumed) failed: ${failReasonRevive}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300)
444
+ : `Agent "${reviveName}" (resumed) ${status === "killed" ? "stopped" : child.status === "completed" ? "finished" : String(child.status)}${ccElapsedTag(Date.now() - reviveStartedAt)}`;
445
+ const ownsTerminalFacesRevive = deps.registry !== undefined && deps.taskId !== undefined ? deps.registry.claimAgentTerminalNotify(deps.taskId) : true;
446
+ if (ownsTerminalFacesRevive)
447
+ reviveEmit?.({
448
+ kind: "terminal",
449
+ taskId: deps.taskId,
450
+ sessionScoped: deps.sessionScoped === true,
451
+ sessionId: entry.childSessionId,
452
+ transcriptId: entry.childSessionId,
453
+ ...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
454
+ status,
455
+ seq: entry.cycleSeq,
456
+ ...(stoppedByRevive !== undefined ? { stoppedBy: stoppedByRevive } : {}),
457
+ summary: reviveTerminalSummary,
458
+ resumable: status !== "killed",
459
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: child.stats.costMicroUsd },
460
+ ...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
461
+ });
449
462
  const resumeFrame = {
450
463
  task_id: deps.taskId ?? entry.childSessionId,
451
464
  ...(entry.childSessionId !== undefined ? { sessionId: entry.childSessionId } : {}),
@@ -454,7 +467,9 @@ export function createSubagentResume(deps) {
454
467
  status,
455
468
  ...(stoppedByRevive !== undefined ? { stoppedBy: stoppedByRevive } : {}),
456
469
  seq: entry.cycleSeq,
457
- summary: `Agent "${reviveName}" (resumed) ${status === "killed" ? "stopped" : child.status === "completed" ? "finished" : String(child.status)}${ccElapsedTag(Date.now() - reviveStartedAt)}`,
470
+ summary: reviveTerminalSummary,
471
+ ...(failReasonRevive !== undefined ? { error: failReasonRevive.slice(0, REPORT_FIELD_MAX) } : {}),
472
+ ...(status === "failed" && errCodeRevive !== undefined ? { errorCode: errCodeRevive } : {}),
458
473
  ...(child.result ? { result: notifyResultField(child.result) } : {}),
459
474
  ...(status === "killed" && child.result ? { partial: true } : {}),
460
475
  ...resumeResidual(),
@@ -462,7 +477,7 @@ export function createSubagentResume(deps) {
462
477
  usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: child.stats.costMicroUsd },
463
478
  ...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
464
479
  };
465
- const gateOwner = status === "completed" && deps.notify !== undefined && deps.registry !== undefined && ledger.get(deps.parentToolCallId) === entry
480
+ const gateOwner = ownsTerminalFacesRevive && status === "completed" && deps.notify !== undefined && deps.registry !== undefined && ledger.get(deps.parentToolCallId) === entry
466
481
  ? entry.childSessionId
467
482
  : undefined;
468
483
  const aliveBgCount = gateOwner !== undefined ? deps.registry.countRunningBackgroundForOwner(gateOwner) : 0;
@@ -476,7 +491,7 @@ export function createSubagentResume(deps) {
476
491
  notify: deps.notify,
477
492
  });
478
493
  }
479
- else {
494
+ else if (ownsTerminalFacesRevive) {
480
495
  try {
481
496
  deps.notify?.(resumeFrame, { priority: "later" });
482
497
  }
@@ -485,11 +500,16 @@ export function createSubagentResume(deps) {
485
500
  }
486
501
  }
487
502
  catch (e) {
503
+ const msgRevive = e instanceof Error ? e.message : String(e);
504
+ const errCodeReviveReject = abort.signal.aborted ? undefined : extractErrorCode(msgRevive);
505
+ const errClassReviveReject = abort.signal.aborted ? undefined : classifySubagentError({ status: "failed", errorMessage: msgRevive });
488
506
  if (reviveCycle !== undefined && deps.registry !== undefined && deps.taskId !== undefined) {
489
507
  try {
490
508
  deps.registry.settleRevivedAgent(deps.taskId, reviveCycle, {
491
509
  status: abort.signal.aborted ? "killed" : "failed",
492
- error: (e instanceof Error ? e.message : String(e)).slice(0, 500),
510
+ error: msgRevive.slice(0, 500),
511
+ ...(errCodeReviveReject !== undefined ? { errorCode: errCodeReviveReject } : {}),
512
+ ...(errClassReviveReject !== undefined ? { retryable: errClassReviveReject.retryable } : {}),
493
513
  });
494
514
  }
495
515
  catch {
@@ -498,31 +518,36 @@ export function createSubagentResume(deps) {
498
518
  const stoppedByReject = abort.signal.aborted ? deps.registry?.getStopAttribution(deps.taskId ?? "") ?? "system" : undefined;
499
519
  const completionIdRejectRevive = deps.registry?.getCompletionId(deps.taskId ?? "");
500
520
  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,
521
+ const ownsTerminalFacesReviveReject = deps.registry !== undefined && deps.taskId !== undefined ? deps.registry.claimAgentTerminalNotify(deps.taskId) : true;
522
+ if (ownsTerminalFacesReviveReject)
523
+ reviveEmit?.({
524
+ kind: "terminal",
525
+ taskId: deps.taskId,
526
+ sessionScoped: deps.sessionScoped === true,
527
+ sessionId: entry.childSessionId,
528
+ transcriptId: entry.childSessionId,
529
+ ...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
520
530
  status: abort.signal.aborted ? "killed" : "failed",
521
531
  seq: entry.cycleSeq,
522
532
  ...(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),
533
+ summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : "failed"}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300),
524
534
  ...(completionIdRejectRevive !== undefined ? { completionId: completionIdRejectRevive } : {}),
525
- }, { priority: "later" });
535
+ });
536
+ try {
537
+ if (ownsTerminalFacesReviveReject)
538
+ deps.notify?.({
539
+ task_id: deps.taskId ?? entry.childSessionId,
540
+ ...(entry.childSessionId !== undefined ? { sessionId: entry.childSessionId } : {}),
541
+ task_type: "background_agent",
542
+ toolUseId: deps.parentToolCallId,
543
+ status: abort.signal.aborted ? "killed" : "failed",
544
+ seq: entry.cycleSeq,
545
+ ...(stoppedByReject !== undefined ? { stoppedBy: stoppedByReject } : {}),
546
+ summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : `failed: ${msgRevive}`}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300),
547
+ ...(abort.signal.aborted ? {} : { error: msgRevive.slice(0, REPORT_FIELD_MAX) }),
548
+ ...(!abort.signal.aborted && errCodeReviveReject !== undefined ? { errorCode: errCodeReviveReject } : {}),
549
+ ...(completionIdRejectRevive !== undefined ? { completionId: completionIdRejectRevive } : {}),
550
+ }, { priority: "later" });
526
551
  }
527
552
  catch {
528
553
  }
@@ -1588,6 +1613,40 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1588
1613
  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
1614
  }
1590
1615
  bg.registry.bindBackgroundAgentSession(taskId, forkedId);
1616
+ const forkBgHangAt = Date.now();
1617
+ bg.registry.attachAgentTerminalNotifier(taskId, () => {
1618
+ const stoppedByReap = bg.registry.getStopAttribution(taskId) ?? "system";
1619
+ const completionIdReap = bg.registry.getCompletionId(taskId);
1620
+ const summaryReap = ccCompletionText(shortDesc, "killed", "killed", Date.now() - forkBgHangAt);
1621
+ sinkEmit({
1622
+ kind: "terminal",
1623
+ taskId,
1624
+ sessionScoped: sessionScopedBg === true,
1625
+ sessionId: forkedId,
1626
+ transcriptId: forkedId,
1627
+ parentToolCallId: ctx.toolCallId,
1628
+ status: "killed",
1629
+ stoppedBy: stoppedByReap,
1630
+ summary: summaryReap,
1631
+ resumable: false,
1632
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
1633
+ });
1634
+ try {
1635
+ notify?.({
1636
+ task_id: taskId,
1637
+ task_type: "background_agent",
1638
+ toolUseId: ctx.toolCallId,
1639
+ status: "killed",
1640
+ stoppedBy: stoppedByReap,
1641
+ summary: summaryReap,
1642
+ sessionId: forkedId,
1643
+ resumable: false,
1644
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
1645
+ }, { priority: "later" });
1646
+ }
1647
+ catch {
1648
+ }
1649
+ });
1591
1650
  if (agentName !== undefined) {
1592
1651
  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
1652
  }
@@ -1683,10 +1742,18 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1683
1742
  }
1684
1743
  const okBg = child.status === "completed";
1685
1744
  const reapedBg = !okBg && abort.signal.aborted;
1745
+ const collateralBg = reapedBg && bg.registry.getAccessibleTask(taskId, { ...(bgOwner !== undefined ? { owner: bgOwner } : {}), ...(bgScope !== undefined ? { scope: bgScope } : {}) })?.status === "running";
1746
+ const failedFork = !okBg && !reapedBg;
1747
+ const errCodeFork = failedFork ? (child.errorCode ?? extractErrorCode(child.errorMessage)) : undefined;
1748
+ const errClassFork = failedFork
1749
+ ? classifySubagentError({ status: "failed", ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}), ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}) })
1750
+ : undefined;
1686
1751
  const settledBg = bg.registry.settleBackgroundAgent(taskId, {
1687
1752
  status: okBg ? "completed" : reapedBg ? "killed" : "failed",
1688
1753
  ...resultSettleFields(child.result),
1689
- ...(!okBg ? { error: reapedBg ? BG_AGENT_REAP_STOP_ERROR : child.errorMessage ?? String(child.status) } : {}),
1754
+ ...(!okBg ? { error: reapedBg ? (collateralBg ? BG_AGENT_COLLATERAL_REAP_REASON : BG_AGENT_REAP_STOP_ERROR) : child.errorMessage ?? String(child.status) } : {}),
1755
+ ...(errCodeFork !== undefined ? { errorCode: errCodeFork } : {}),
1756
+ ...(errClassFork !== undefined ? { retryable: errClassFork.retryable } : {}),
1690
1757
  }) ?? (abort.signal.aborted ? "killed" : okBg ? "completed" : "failed");
1691
1758
  const stoppedByBg = settledBg === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
1692
1759
  const completionIdFork = bg.registry.getCompletionId(taskId);
@@ -1701,37 +1768,46 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1701
1768
  const residualFork = residualFields();
1702
1769
  const resumableFork = false;
1703
1770
  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,
1771
+ const failReasonFork = settledBg === "failed" ? child.errorMessage : undefined;
1772
+ const forkTerminalSummary = failReasonFork !== undefined
1773
+ ? `Agent "${shortDesc}" failed: ${failReasonFork}${ccElapsedTag(Date.now() - forkBgStartedAt)}`.slice(0, 300)
1774
+ : `${ccCompletionText(shortDesc, settledBg, String(child.status), Date.now() - forkBgStartedAt)}`;
1775
+ const ownsTerminalFacesFork = bg.registry.claimAgentTerminalNotify(taskId);
1776
+ if (ownsTerminalFacesFork)
1777
+ sinkEmit({
1778
+ kind: "terminal",
1779
+ taskId,
1780
+ sessionScoped: sessionScopedBg === true,
1781
+ sessionId: forkTranscriptId,
1782
+ ...(stillbornFork ? {} : { transcriptId: forkTranscriptId }),
1783
+ parentToolCallId: ctx.toolCallId,
1724
1784
  status: settledBg,
1725
1785
  ...(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 } : {}),
1786
+ summary: forkTerminalSummary,
1730
1787
  ...residualFork,
1731
1788
  resumable: resumableFork,
1732
1789
  usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
1733
1790
  ...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
1734
- }, { priority: "later" });
1791
+ });
1792
+ try {
1793
+ if (ownsTerminalFacesFork)
1794
+ notify?.({
1795
+ task_id: taskId,
1796
+ task_type: "background_agent",
1797
+ toolUseId: ctx.toolCallId,
1798
+ status: settledBg,
1799
+ ...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
1800
+ summary: forkTerminalSummary,
1801
+ ...(failReasonFork !== undefined ? { error: failReasonFork.slice(0, REPORT_FIELD_MAX) } : {}),
1802
+ ...(settledBg === "failed" && errCodeFork !== undefined ? { errorCode: errCodeFork } : {}),
1803
+ ...(child.sessionId ? { sessionId: child.sessionId } : {}),
1804
+ ...(child.result ? { result: notifyResultField(child.result) } : {}),
1805
+ ...(settledBg === "killed" && child.result ? { partial: true } : {}),
1806
+ ...residualFork,
1807
+ resumable: resumableFork,
1808
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
1809
+ ...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
1810
+ }, { priority: "later" });
1735
1811
  }
1736
1812
  catch {
1737
1813
  }
@@ -1740,36 +1816,46 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1740
1816
  dropHostAbortListener();
1741
1817
  void finishWorktree();
1742
1818
  const killed = abort.signal.aborted;
1819
+ const msgFork = e instanceof Error ? e.message : String(e);
1820
+ const errCodeForkReject = killed ? undefined : extractErrorCode(msgFork);
1821
+ const errClassForkReject = killed ? undefined : classifySubagentError({ status: "failed", errorMessage: msgFork });
1743
1822
  const settledBg = bg.registry.settleBackgroundAgent(taskId, {
1744
1823
  status: killed ? "killed" : "failed",
1745
- error: killed ? BG_AGENT_REAP_STOP_ERROR : e instanceof Error ? e.message : String(e),
1824
+ error: killed ? BG_AGENT_REAP_STOP_ERROR : msgFork,
1825
+ ...(errCodeForkReject !== undefined ? { errorCode: errCodeForkReject } : {}),
1826
+ ...(errClassForkReject !== undefined ? { retryable: errClassForkReject.retryable } : {}),
1746
1827
  }) ?? (killed ? "killed" : "failed");
1747
1828
  const stoppedByBg = settledBg === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
1748
1829
  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,
1830
+ const summaryBg = `${shortDesc} — ${settledBg === "failed" ? `failed: ${msgFork}` : settledBg}`.slice(0, 300);
1831
+ const ownsTerminalFacesForkReject = bg.registry.claimAgentTerminalNotify(taskId);
1832
+ if (ownsTerminalFacesForkReject)
1833
+ sinkEmit({
1834
+ kind: "terminal",
1835
+ taskId,
1836
+ sessionScoped: sessionScopedBg === true,
1768
1837
  sessionId: forkedId,
1838
+ transcriptId: forkedId,
1839
+ parentToolCallId: ctx.toolCallId,
1840
+ status: settledBg,
1769
1841
  ...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
1770
1842
  summary: summaryBg,
1771
1843
  ...(completionIdForkReject !== undefined ? { completionId: completionIdForkReject } : {}),
1772
- }, { priority: "later" });
1844
+ });
1845
+ try {
1846
+ if (ownsTerminalFacesForkReject)
1847
+ notify?.({
1848
+ task_id: taskId,
1849
+ task_type: "background_agent",
1850
+ toolUseId: ctx.toolCallId,
1851
+ status: settledBg,
1852
+ sessionId: forkedId,
1853
+ ...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
1854
+ summary: summaryBg,
1855
+ ...(settledBg === "failed" ? { error: msgFork.slice(0, REPORT_FIELD_MAX) } : {}),
1856
+ ...(settledBg === "failed" && errCodeForkReject !== undefined ? { errorCode: errCodeForkReject } : {}),
1857
+ ...(completionIdForkReject !== undefined ? { completionId: completionIdForkReject } : {}),
1858
+ }, { priority: "later" });
1773
1859
  }
1774
1860
  catch {
1775
1861
  }
@@ -1956,6 +2042,39 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1956
2042
  const bgStartedAt = Date.now();
1957
2043
  stepRecorder.lockTo(bgChildSessionId);
1958
2044
  bg.registry.bindBackgroundAgentSession(taskId, bgChildSessionId);
2045
+ bg.registry.attachAgentTerminalNotifier(taskId, () => {
2046
+ const stoppedByReap = bg.registry.getStopAttribution(taskId) ?? "system";
2047
+ const completionIdReap = bg.registry.getCompletionId(taskId);
2048
+ const summaryReap = ccCompletionText(shortDesc, "killed", "killed", Date.now() - bgStartedAt);
2049
+ sinkEmit({
2050
+ kind: "terminal",
2051
+ taskId,
2052
+ sessionScoped: sessionScopedBg === true,
2053
+ sessionId: bgChildSessionId,
2054
+ transcriptId: bgChildSessionId,
2055
+ parentToolCallId: ctx.toolCallId,
2056
+ status: "killed",
2057
+ stoppedBy: stoppedByReap,
2058
+ summary: summaryReap,
2059
+ resumable: false,
2060
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
2061
+ });
2062
+ try {
2063
+ notify?.({
2064
+ task_id: taskId,
2065
+ task_type: "background_agent",
2066
+ toolUseId: ctx.toolCallId,
2067
+ status: "killed",
2068
+ stoppedBy: stoppedByReap,
2069
+ summary: summaryReap,
2070
+ sessionId: bgChildSessionId,
2071
+ resumable: false,
2072
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
2073
+ }, { priority: "later" });
2074
+ }
2075
+ catch {
2076
+ }
2077
+ });
1959
2078
  const bgDurableProbe = bg.registry.durableAgentRowProbe(taskId);
1960
2079
  const bgRowConfirmed = async () => (bgDurableProbe ? await bgDurableProbe().catch(() => false) : false);
1961
2080
  sinkEmit({
@@ -2259,11 +2378,19 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2259
2378
  }
2260
2379
  const ok = child.status === "completed";
2261
2380
  const reaped = !ok && abort.signal.aborted;
2381
+ const collateral = reaped && bg.registry.getAccessibleTask(taskId, { ...(bgOwner !== undefined ? { owner: bgOwner } : {}), ...(bgScope !== undefined ? { scope: bgScope } : {}) })?.status === "running";
2382
+ const failedBg = !ok && !reaped;
2383
+ const errCodeBg = failedBg ? (child.errorCode ?? extractErrorCode(child.errorMessage)) : undefined;
2384
+ const errClassBg = failedBg
2385
+ ? classifySubagentError({ status: "failed", ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}), ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}) })
2386
+ : undefined;
2262
2387
  const settled = bg.registry.settleBackgroundAgent(taskId, {
2263
2388
  status: ok ? "completed" : reaped ? "killed" : "failed",
2264
2389
  seq: seqAtSettle ?? 1,
2265
2390
  ...resultSettleFields(child.result),
2266
- ...(!ok ? { error: reaped ? BG_AGENT_REAP_STOP_ERROR : child.errorMessage ?? String(child.status) } : {}),
2391
+ ...(!ok ? { error: reaped ? (collateral ? BG_AGENT_COLLATERAL_REAP_REASON : BG_AGENT_REAP_STOP_ERROR) : child.errorMessage ?? String(child.status) } : {}),
2392
+ ...(errCodeBg !== undefined ? { errorCode: errCodeBg } : {}),
2393
+ ...(errClassBg !== undefined ? { retryable: errClassBg.retryable } : {}),
2267
2394
  }) ??
2268
2395
  (abort.signal.aborted ? "killed" : ok ? "completed" : "failed");
2269
2396
  if (!bgRetain && reviveRow === undefined && !(await bgRowConfirmed())) {
@@ -2279,21 +2406,27 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2279
2406
  const observerNote = observerNoteFor(await startBoundedObserverDrain());
2280
2407
  const residual = residualFields();
2281
2408
  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
- });
2409
+ const failReasonBg = settled === "failed" ? child.errorMessage : undefined;
2410
+ const bgTerminalSummary = failReasonBg !== undefined
2411
+ ? `Agent "${shortDesc}" failed: ${failReasonBg}${ccElapsedTag(Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300)
2412
+ : `${ccCompletionText(shortDesc, settled, String(child.status), Date.now() - bgStartedAt)}${observerNote}`;
2413
+ const ownsTerminalFaces = bg.registry.claimAgentTerminalNotify(taskId);
2414
+ if (ownsTerminalFaces)
2415
+ sinkEmit({
2416
+ kind: "terminal",
2417
+ taskId,
2418
+ sessionScoped: sessionScopedBg === true,
2419
+ ...(child.sessionId ? { sessionId: child.sessionId, transcriptId: child.sessionId } : {}),
2420
+ parentToolCallId: ctx.toolCallId,
2421
+ status: settled,
2422
+ ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2423
+ ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2424
+ summary: bgTerminalSummary,
2425
+ ...residual,
2426
+ resumable: resumableBg,
2427
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
2428
+ ...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
2429
+ });
2297
2430
  const completionFrame = {
2298
2431
  task_id: taskId,
2299
2432
  task_type: "background_agent",
@@ -2301,7 +2434,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2301
2434
  status: settled,
2302
2435
  ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2303
2436
  ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2304
- summary: `${ccCompletionText(shortDesc, settled, String(child.status), Date.now() - bgStartedAt)}${observerNote}`,
2437
+ summary: bgTerminalSummary,
2438
+ ...(failReasonBg !== undefined ? { error: failReasonBg.slice(0, REPORT_FIELD_MAX) } : {}),
2439
+ ...(settled === "failed" && errCodeBg !== undefined ? { errorCode: errCodeBg } : {}),
2305
2440
  ...(child.sessionId ? { sessionId: child.sessionId } : {}),
2306
2441
  ...(child.result ? { result: notifyResultField(child.result) } : {}),
2307
2442
  ...(settled === "killed" && child.result ? { partial: true } : {}),
@@ -2310,7 +2445,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2310
2445
  usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
2311
2446
  ...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
2312
2447
  };
2313
- const gateOwner = settled === "completed" && notify !== undefined ? bgRetain?.childSessionId : undefined;
2448
+ const gateOwner = ownsTerminalFaces && settled === "completed" && notify !== undefined ? bgRetain?.childSessionId : undefined;
2314
2449
  const aliveBgCount = gateOwner !== undefined ? bg.registry.countRunningBackgroundForOwner(gateOwner) : 0;
2315
2450
  if (gateOwner !== undefined && bgRetain !== undefined && aliveBgCount > 0) {
2316
2451
  parkCompletionNotify({
@@ -2322,7 +2457,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2322
2457
  notify: notify,
2323
2458
  });
2324
2459
  }
2325
- else {
2460
+ else if (ownsTerminalFaces) {
2326
2461
  try {
2327
2462
  notify?.(completionFrame, { priority: "later" });
2328
2463
  }
@@ -2343,7 +2478,15 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2343
2478
  bgRetainLedger?.markSettled(ctx.toolCallId);
2344
2479
  }
2345
2480
  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");
2481
+ const errCodeBgReject = killed ? undefined : extractErrorCode(msg);
2482
+ const errClassBgReject = killed ? undefined : classifySubagentError({ status: "failed", errorMessage: msg });
2483
+ const settled = bg.registry.settleBackgroundAgent(taskId, {
2484
+ status: killed ? "killed" : "failed",
2485
+ error: msg,
2486
+ seq: seqAtSettle ?? 1,
2487
+ ...(errCodeBgReject !== undefined ? { errorCode: errCodeBgReject } : {}),
2488
+ ...(errClassBgReject !== undefined ? { retryable: errClassBgReject.retryable } : {}),
2489
+ }) ?? (killed ? "killed" : "failed");
2347
2490
  if (!bgRetain && reviveRow === undefined && !(await bgRowConfirmed())) {
2348
2491
  try {
2349
2492
  await opts.runner.sessions.release(bgChildSessionId);
@@ -2355,31 +2498,36 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2355
2498
  const stoppedBy = settled === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
2356
2499
  const completionIdBgReject = bg.registry.getCompletionId(taskId);
2357
2500
  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,
2501
+ const ownsTerminalFacesReject = bg.registry.claimAgentTerminalNotify(taskId);
2502
+ if (ownsTerminalFacesReject)
2503
+ sinkEmit({
2504
+ kind: "terminal",
2505
+ taskId,
2506
+ sessionScoped: sessionScopedBg === true,
2377
2507
  sessionId: bgChildSessionId,
2508
+ transcriptId: bgChildSessionId,
2509
+ parentToolCallId: ctx.toolCallId,
2510
+ status: settled,
2378
2511
  ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2379
2512
  ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2380
2513
  summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300),
2381
2514
  ...(completionIdBgReject !== undefined ? { completionId: completionIdBgReject } : {}),
2382
- }, { priority: "later" });
2515
+ });
2516
+ try {
2517
+ if (ownsTerminalFacesReject)
2518
+ notify?.({
2519
+ task_id: taskId,
2520
+ task_type: "background_agent",
2521
+ toolUseId: ctx.toolCallId,
2522
+ status: settled,
2523
+ sessionId: bgChildSessionId,
2524
+ ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2525
+ ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2526
+ summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300),
2527
+ ...(settled === "failed" ? { error: msg.slice(0, REPORT_FIELD_MAX) } : {}),
2528
+ ...(settled === "failed" && errCodeBgReject !== undefined ? { errorCode: errCodeBgReject } : {}),
2529
+ ...(completionIdBgReject !== undefined ? { completionId: completionIdBgReject } : {}),
2530
+ }, { priority: "later" });
2383
2531
  }
2384
2532
  catch {
2385
2533
  }