@librechat/agents 4.0.0 → 4.0.2

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 (144) hide show
  1. package/dist/cjs/decisions/deadline.cjs +52 -0
  2. package/dist/cjs/decisions/deadline.cjs.map +1 -0
  3. package/dist/cjs/decisions/dialect.cjs +88 -0
  4. package/dist/cjs/decisions/dialect.cjs.map +1 -0
  5. package/dist/cjs/decisions/http.cjs +77 -0
  6. package/dist/cjs/decisions/http.cjs.map +1 -0
  7. package/dist/cjs/decisions/index.cjs +7 -0
  8. package/dist/cjs/decisions/presets.cjs +82 -0
  9. package/dist/cjs/decisions/presets.cjs.map +1 -0
  10. package/dist/cjs/decisions/questions.cjs +70 -0
  11. package/dist/cjs/decisions/questions.cjs.map +1 -0
  12. package/dist/cjs/decisions/structuredChat.cjs +178 -0
  13. package/dist/cjs/decisions/structuredChat.cjs.map +1 -0
  14. package/dist/cjs/decisions/traceMarker.cjs +6 -0
  15. package/dist/cjs/decisions/traceMarker.cjs.map +1 -0
  16. package/dist/cjs/decisions/transport.cjs +166 -0
  17. package/dist/cjs/decisions/transport.cjs.map +1 -0
  18. package/dist/cjs/decisions/types.cjs +44 -0
  19. package/dist/cjs/decisions/types.cjs.map +1 -0
  20. package/dist/cjs/graphs/Graph.cjs +4 -1
  21. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  22. package/dist/cjs/langfuseToolOutputTracing.cjs +6 -1
  23. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  24. package/dist/cjs/main.cjs +39 -0
  25. package/dist/cjs/rerank/index.cjs +2 -0
  26. package/dist/cjs/rerank/search.cjs +45 -0
  27. package/dist/cjs/rerank/search.cjs.map +1 -0
  28. package/dist/cjs/rerank/systemone.cjs +70 -0
  29. package/dist/cjs/rerank/systemone.cjs.map +1 -0
  30. package/dist/cjs/run.cjs +4 -0
  31. package/dist/cjs/run.cjs.map +1 -1
  32. package/dist/cjs/tools/BashExecutor.cjs +1 -1
  33. package/dist/cjs/tools/CodeExecutor.cjs +1 -1
  34. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +1 -1
  35. package/dist/cjs/tools/search/rerankers.cjs +1 -0
  36. package/dist/cjs/tools/search/search.cjs +3 -3
  37. package/dist/cjs/tools/search/search.cjs.map +1 -1
  38. package/dist/cjs/tools/search/tool.cjs +4 -3
  39. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  40. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +12 -0
  41. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
  42. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +87 -11
  43. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  44. package/dist/cjs/tools/subagent/diagnostics.cjs +73 -0
  45. package/dist/cjs/tools/subagent/diagnostics.cjs.map +1 -0
  46. package/dist/cjs/tools/subagent/index.cjs +1 -0
  47. package/dist/esm/decisions/deadline.mjs +52 -0
  48. package/dist/esm/decisions/deadline.mjs.map +1 -0
  49. package/dist/esm/decisions/dialect.mjs +87 -0
  50. package/dist/esm/decisions/dialect.mjs.map +1 -0
  51. package/dist/esm/decisions/http.mjs +75 -0
  52. package/dist/esm/decisions/http.mjs.map +1 -0
  53. package/dist/esm/decisions/index.mjs +8 -0
  54. package/dist/esm/decisions/presets.mjs +77 -0
  55. package/dist/esm/decisions/presets.mjs.map +1 -0
  56. package/dist/esm/decisions/questions.mjs +67 -0
  57. package/dist/esm/decisions/questions.mjs.map +1 -0
  58. package/dist/esm/decisions/structuredChat.mjs +178 -0
  59. package/dist/esm/decisions/structuredChat.mjs.map +1 -0
  60. package/dist/esm/decisions/traceMarker.mjs +6 -0
  61. package/dist/esm/decisions/traceMarker.mjs.map +1 -0
  62. package/dist/esm/decisions/transport.mjs +164 -0
  63. package/dist/esm/decisions/transport.mjs.map +1 -0
  64. package/dist/esm/decisions/types.mjs +39 -0
  65. package/dist/esm/decisions/types.mjs.map +1 -0
  66. package/dist/esm/graphs/Graph.mjs +4 -1
  67. package/dist/esm/graphs/Graph.mjs.map +1 -1
  68. package/dist/esm/langfuseToolOutputTracing.mjs +6 -1
  69. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  70. package/dist/esm/main.mjs +13 -1
  71. package/dist/esm/rerank/index.mjs +3 -0
  72. package/dist/esm/rerank/search.mjs +45 -0
  73. package/dist/esm/rerank/search.mjs.map +1 -0
  74. package/dist/esm/rerank/systemone.mjs +70 -0
  75. package/dist/esm/rerank/systemone.mjs.map +1 -0
  76. package/dist/esm/run.mjs +4 -0
  77. package/dist/esm/run.mjs.map +1 -1
  78. package/dist/esm/tools/BashExecutor.mjs +1 -1
  79. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  80. package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
  81. package/dist/esm/tools/search/rerankers.mjs +1 -1
  82. package/dist/esm/tools/search/search.mjs +3 -3
  83. package/dist/esm/tools/search/search.mjs.map +1 -1
  84. package/dist/esm/tools/search/tool.mjs +4 -3
  85. package/dist/esm/tools/search/tool.mjs.map +1 -1
  86. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +12 -1
  87. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
  88. package/dist/esm/tools/subagent/SubagentExecutor.mjs +88 -12
  89. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  90. package/dist/esm/tools/subagent/diagnostics.mjs +71 -0
  91. package/dist/esm/tools/subagent/diagnostics.mjs.map +1 -0
  92. package/dist/esm/tools/subagent/index.mjs +1 -0
  93. package/dist/types/decisions/deadline.d.ts +3 -0
  94. package/dist/types/decisions/dialect.d.ts +11 -0
  95. package/dist/types/decisions/http.d.ts +22 -0
  96. package/dist/types/decisions/index.d.ts +10 -0
  97. package/dist/types/decisions/presets.d.ts +17 -0
  98. package/dist/types/decisions/questions.d.ts +8 -0
  99. package/dist/types/decisions/structuredChat.d.ts +16 -0
  100. package/dist/types/decisions/traceMarker.d.ts +2 -0
  101. package/dist/types/decisions/transport.d.ts +29 -0
  102. package/dist/types/decisions/types.d.ts +119 -0
  103. package/dist/types/graphs/Graph.d.ts +2 -1
  104. package/dist/types/index.d.ts +2 -0
  105. package/dist/types/rerank/index.d.ts +4 -0
  106. package/dist/types/rerank/search.d.ts +7 -0
  107. package/dist/types/rerank/systemone.d.ts +13 -0
  108. package/dist/types/rerank/types.d.ts +24 -0
  109. package/dist/types/run.d.ts +1 -0
  110. package/dist/types/tools/search/types.d.ts +3 -1
  111. package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +4 -0
  112. package/dist/types/tools/subagent/SubagentExecutor.d.ts +13 -0
  113. package/dist/types/tools/subagent/diagnostics.d.ts +44 -0
  114. package/dist/types/tools/subagent/index.d.ts +2 -0
  115. package/dist/types/types/graph.d.ts +3 -0
  116. package/dist/types/types/run.d.ts +2 -0
  117. package/package.json +3 -3
  118. package/src/decisions/deadline.ts +92 -0
  119. package/src/decisions/dialect.ts +200 -0
  120. package/src/decisions/http.ts +175 -0
  121. package/src/decisions/index.ts +14 -0
  122. package/src/decisions/presets.ts +111 -0
  123. package/src/decisions/questions.ts +128 -0
  124. package/src/decisions/structuredChat.ts +364 -0
  125. package/src/decisions/traceMarker.ts +2 -0
  126. package/src/decisions/transport.ts +335 -0
  127. package/src/decisions/types.ts +193 -0
  128. package/src/graphs/Graph.ts +4 -0
  129. package/src/index.ts +2 -0
  130. package/src/langfuseToolOutputTracing.ts +8 -1
  131. package/src/rerank/index.ts +4 -0
  132. package/src/rerank/search.ts +85 -0
  133. package/src/rerank/systemone.ts +140 -0
  134. package/src/rerank/types.ts +28 -0
  135. package/src/run.ts +4 -0
  136. package/src/tools/search/search.ts +4 -4
  137. package/src/tools/search/tool.ts +27 -25
  138. package/src/tools/search/types.ts +4 -1
  139. package/src/tools/subagent/SubagentExecutionRegistry.ts +21 -0
  140. package/src/tools/subagent/SubagentExecutor.ts +224 -36
  141. package/src/tools/subagent/diagnostics.ts +142 -0
  142. package/src/tools/subagent/index.ts +11 -0
  143. package/src/types/graph.ts +3 -0
  144. package/src/types/run.ts +2 -0
@@ -425,6 +425,7 @@ export class SubagentExecutionRecord<
425
425
  TSettledOutput = never,
426
426
  > {
427
427
  private identityValue?: SubagentExecutionIdentity;
428
+ private attemptedIdentityValue?: SubagentExecutionIdentity;
428
429
  private bindingValue?: SubagentDefinitionBinding;
429
430
  private bindingAuthority?: 'provisional' | 'effective';
430
431
  private invocationValue?: SubagentInvocationBinding;
@@ -488,6 +489,24 @@ export class SubagentExecutionRecord<
488
489
  return this.identityValue;
489
490
  }
490
491
 
492
+ /** Correlation only; never committed identity or resume authority. */
493
+ get attemptedIdentity(): SubagentExecutionIdentity | undefined {
494
+ return this.attemptedIdentityValue;
495
+ }
496
+
497
+ recordIdentityAttempt(identity: SubagentExecutionIdentity): void {
498
+ const current = this.attemptedIdentityValue;
499
+ if (
500
+ current != null &&
501
+ current.childRunId === identity.childRunId &&
502
+ current.childThreadId === identity.childThreadId &&
503
+ current.approvalExecutionScope === identity.approvalExecutionScope
504
+ ) {
505
+ return;
506
+ }
507
+ this.attemptedIdentityValue = Object.freeze({ ...identity });
508
+ }
509
+
491
510
  get binding(): SubagentDefinitionBinding | undefined {
492
511
  return this.bindingValue;
493
512
  }
@@ -570,6 +589,7 @@ export class SubagentExecutionRecord<
570
589
  if (this.pendingIdentityResolution != null) {
571
590
  return this.pendingIdentityResolution;
572
591
  }
592
+ this.attemptedIdentityValue = undefined;
573
593
  const pending = Promise.resolve()
574
594
  .then(() => {
575
595
  this.assertCurrentIdentityResolution(pending);
@@ -929,6 +949,7 @@ export class SubagentExecutionRegistry<
929
949
  if (this.recordsByAddress.get(record.address.key) !== record) {
930
950
  throw new SubagentExecutionInvalidatedError();
931
951
  }
952
+ record.recordIdentityAttempt(identity);
932
953
  const checkpointKey = getPreparationResourceKey(
933
954
  'checkpoint',
934
955
  identity.childThreadId
@@ -92,6 +92,12 @@ import type {
92
92
  PreemptBoundaryHookOutput,
93
93
  ToolApprovalReplaySnapshot,
94
94
  } from '@/hooks';
95
+ import type {
96
+ SubagentResolutionPhase,
97
+ SubagentResolutionCause,
98
+ SubagentResolutionFailureHandler,
99
+ } from './diagnostics';
100
+ import type { RunBreakerScope } from '@/llm/streamLimits';
95
101
  import type { GraphFactory } from '@/graphs/graphFactory';
96
102
  import type { StandardGraph } from '@/graphs/Graph';
97
103
  import {
@@ -126,6 +132,10 @@ import {
126
132
  DEFAULT_SUBAGENT_MAX_TURNS,
127
133
  SUBAGENT_RECURSION_MULTIPLIER,
128
134
  } from './runtimeLimits';
135
+ import {
136
+ logSubagentResolutionFailure,
137
+ SubagentResolutionError,
138
+ } from './diagnostics';
129
139
  import {
130
140
  ContentTypes,
131
141
  Constants,
@@ -161,13 +171,19 @@ const MAX_QUEUED_SUBAGENT_UPDATES = 64;
161
171
  const MAX_BACKGROUND_SUBAGENT_SEALS = 32;
162
172
  const SUBAGENT_UPDATE_HANDLER_TIMEOUT_MS = 5_000;
163
173
  const TEXT_DELTA_CONTENT_TYPE = `${ContentTypes.TEXT}_delta`;
164
- const SUBAGENT_RESOLUTION_ERROR_MESSAGE =
165
- 'Subagent error: Unable to initialize the selected subagent.';
166
174
  const SUBAGENT_CONFIG_CHANGED_MESSAGE =
167
175
  'Subagent error: Subagent configuration changed since this execution was paused.';
168
176
  const SUBAGENT_INVOCATION_CHANGED_MESSAGE =
169
177
  'Subagent error: Subagent invocation changed for this execution.';
170
178
 
179
+ function isSubagentResolutionControlFlow(error: unknown): boolean {
180
+ try {
181
+ return error instanceof StreamLimitExceededError || isGraphInterrupt(error);
182
+ } catch {
183
+ return false;
184
+ }
185
+ }
186
+
171
187
  function seedChildGraphSessions(
172
188
  childGraph: StandardGraph,
173
189
  agents: AgentInputs[]
@@ -866,6 +882,11 @@ export type SubagentExecuteParams = {
866
882
  export type SubagentExecuteResult = SubagentContextResult & {
867
883
  /** Tagged internal failure; foreground callers retain the legacy content. */
868
884
  error?: string;
885
+ /** Safe startup classification retained for detached host delivery. */
886
+ resolutionFailure?: {
887
+ phase: SubagentResolutionPhase;
888
+ cause: SubagentResolutionCause;
889
+ };
869
890
  /** Completed child work whose host projection must be retried without re-execution. */
870
891
  retryableDelivery?: true;
871
892
  };
@@ -985,6 +1006,8 @@ export type SubagentExecutorOptions = {
985
1006
  /** Host-owned process-local task namespace for detached execution. */
986
1007
  taskConfig?: SubagentTaskConfig;
987
1008
  subagentContext?: SubagentContextAdapter;
1009
+ /** Host diagnostic sink and safe cause classifier for startup failures. */
1010
+ onResolutionFailure?: SubagentResolutionFailureHandler;
988
1011
  };
989
1012
 
990
1013
  type DurableExecutionRecord = SubagentExecutionRecord<
@@ -1023,6 +1046,7 @@ export class SubagentExecutor {
1023
1046
  private readonly usageSink?: SubagentUsageSink;
1024
1047
  private readonly taskConfig?: SubagentTaskConfig;
1025
1048
  private readonly subagentContext?: SubagentContextAdapter;
1049
+ private readonly onResolutionFailure?: SubagentResolutionFailureHandler;
1026
1050
  private readonly executions: SubagentExecutionRegistry<
1027
1051
  SubagentExecuteResult,
1028
1052
  ResolvedSubagentConfig,
@@ -1068,6 +1092,7 @@ export class SubagentExecutor {
1068
1092
  this.usageSink = options.usageSink;
1069
1093
  this.taskConfig = options.taskConfig;
1070
1094
  this.subagentContext = options.subagentContext;
1095
+ this.onResolutionFailure = options.onResolutionFailure;
1071
1096
  const rawRegistry = options.parentHandlerRegistry;
1072
1097
  if (typeof rawRegistry === 'function') {
1073
1098
  this.resolveParentHandlerRegistry = rawRegistry;
@@ -1317,6 +1342,7 @@ export class SubagentExecutor {
1317
1342
  tokenCounter: this.tokenCounter,
1318
1343
  usageSink: this.usageSink,
1319
1344
  subagentContext: this.subagentContext,
1345
+ onResolutionFailure: this.onResolutionFailure,
1320
1346
  streamLimits: this.streamLimits,
1321
1347
  humanInTheLoop:
1322
1348
  this.humanInTheLoop?.enabled === true ||
@@ -1358,10 +1384,7 @@ export class SubagentExecutor {
1358
1384
  };
1359
1385
  let deliveryAttempts = 1;
1360
1386
  let result = await executeAttempt();
1361
- while (
1362
- result.retryableDelivery === true &&
1363
- deliveryAttempts < 3
1364
- ) {
1387
+ while (result.retryableDelivery === true && deliveryAttempts < 3) {
1365
1388
  if (deliveryAttempts > 1) {
1366
1389
  await sleep(
1367
1390
  Math.min(100 * 2 ** Math.min(deliveryAttempts - 2, 6), 5_000)
@@ -1371,6 +1394,12 @@ export class SubagentExecutor {
1371
1394
  result = await executeAttempt();
1372
1395
  }
1373
1396
  if (result.error != null) {
1397
+ if (result.resolutionFailure != null) {
1398
+ throw new SubagentResolutionError(
1399
+ result.resolutionFailure.phase,
1400
+ result.resolutionFailure.cause
1401
+ );
1402
+ }
1374
1403
  throw new Error(result.error);
1375
1404
  }
1376
1405
  if (this.humanInTheLoop?.enabled === true) {
@@ -1664,13 +1693,6 @@ export class SubagentExecutor {
1664
1693
  ),
1665
1694
  });
1666
1695
  }
1667
- const sourceCheckpoints =
1668
- await this.getLatestCheckpointSnapshot(baseChildThreadId);
1669
- if (sourceCheckpoints.length === 0) {
1670
- throw new Error(
1671
- `Cannot fork subagent checkpoint thread "${baseChildThreadId}" without a checkpoint ID.`
1672
- );
1673
- }
1674
1696
  const childRunId = persistedChildRunId ?? currentChildRunId;
1675
1697
  const identity = {
1676
1698
  childRunId,
@@ -1680,6 +1702,14 @@ export class SubagentExecutor {
1680
1702
  resumeAttemptId
1681
1703
  ),
1682
1704
  };
1705
+ execution.recordIdentityAttempt(identity);
1706
+ const sourceCheckpoints =
1707
+ await this.getLatestCheckpointSnapshot(baseChildThreadId);
1708
+ if (sourceCheckpoints.length === 0) {
1709
+ throw new Error(
1710
+ `Cannot fork subagent checkpoint thread "${baseChildThreadId}" without a checkpoint ID.`
1711
+ );
1712
+ }
1683
1713
  const lease = this.executions.beginIdentityPreparation(execution, identity);
1684
1714
  await this.prepareCheckpointFork(
1685
1715
  sourceCheckpoints,
@@ -2085,9 +2115,10 @@ export class SubagentExecutor {
2085
2115
  config: RunnableConfig
2086
2116
  ): Promise<SettledSubagentToolOutput | undefined> {
2087
2117
  const parentToolCallId = call.id;
2118
+ const checkpointer = this.checkpointer;
2088
2119
  if (
2089
2120
  this.humanInTheLoop?.enabled !== true ||
2090
- this.checkpointer == null ||
2121
+ checkpointer == null ||
2091
2122
  parentToolCallId == null ||
2092
2123
  parentToolCallId === ''
2093
2124
  ) {
@@ -2102,6 +2133,44 @@ export class SubagentExecutor {
2102
2133
  parentToolCallId,
2103
2134
  parentConfigurable,
2104
2135
  });
2136
+ const signal = this.getReplaySignal(config);
2137
+ try {
2138
+ return await this.restoreSettledToolOutput(
2139
+ call,
2140
+ parentConfigurable,
2141
+ parentToolCallId,
2142
+ execution,
2143
+ checkpointer
2144
+ );
2145
+ } catch (error) {
2146
+ if (isSubagentResolutionControlFlow(error)) throw error;
2147
+ const failure = this.createReplayResolutionError(
2148
+ call,
2149
+ config,
2150
+ execution,
2151
+ signal,
2152
+ error
2153
+ );
2154
+ if (signal.aborted) throw failure;
2155
+ return {
2156
+ output: new ToolMessage({
2157
+ content: failure.message,
2158
+ name: call.name,
2159
+ tool_call_id: parentToolCallId,
2160
+ status: 'error',
2161
+ }),
2162
+ additionalContexts: [],
2163
+ };
2164
+ }
2165
+ }
2166
+
2167
+ private async restoreSettledToolOutput(
2168
+ call: ToolCall,
2169
+ parentConfigurable: Record<string, unknown> | undefined,
2170
+ parentToolCallId: string,
2171
+ execution: DurableExecutionRecord,
2172
+ checkpointer: BaseCheckpointSaver
2173
+ ): Promise<SettledSubagentToolOutput | undefined> {
2105
2174
  const { resumeExecution } = execution;
2106
2175
  const inProcessSettledOutput = execution.settledOutput;
2107
2176
  if (inProcessSettledOutput != null) {
@@ -2160,7 +2229,7 @@ export class SubagentExecutor {
2160
2229
  const { childThreadId } =
2161
2230
  await this.resolveChildExecutionIdentity(execution);
2162
2231
  if (marker == null) {
2163
- const checkpoint = await this.checkpointer.getTuple({
2232
+ const checkpoint = await checkpointer.getTuple({
2164
2233
  configurable: { thread_id: childThreadId },
2165
2234
  });
2166
2235
  messages = getCheckpointMessages(
@@ -2245,7 +2314,10 @@ export class SubagentExecutor {
2245
2314
  parentToolCallId,
2246
2315
  parentConfigurable,
2247
2316
  });
2248
- if (execution.completedResult != null && settled.output.status === 'error') {
2317
+ if (
2318
+ execution.completedResult != null &&
2319
+ settled.output.status === 'error'
2320
+ ) {
2249
2321
  return;
2250
2322
  }
2251
2323
  const { resumeExecution } = execution;
@@ -2309,8 +2381,20 @@ export class SubagentExecutor {
2309
2381
  },
2310
2382
  persistedOutput,
2311
2383
  async (): Promise<void> => {
2312
- const { childRunId, childThreadId } =
2313
- await this.resolveChildExecutionIdentity(execution);
2384
+ let identity: SubagentExecutionIdentity;
2385
+ try {
2386
+ identity = await this.resolveChildExecutionIdentity(execution);
2387
+ } catch (error) {
2388
+ if (isSubagentResolutionControlFlow(error)) throw error;
2389
+ throw this.createReplayResolutionError(
2390
+ call,
2391
+ config,
2392
+ execution,
2393
+ this.getReplaySignal(config),
2394
+ error
2395
+ );
2396
+ }
2397
+ const { childRunId, childThreadId } = identity;
2314
2398
  const activeChildRun = execution.activeRun;
2315
2399
  if (activeChildRun != null) {
2316
2400
  await this.persistChildCheckpointMarker(
@@ -2456,6 +2540,90 @@ export class SubagentExecutor {
2456
2540
  }
2457
2541
  }
2458
2542
 
2543
+ private getReplaySignal(config: RunnableConfig): AbortSignal {
2544
+ const scope = config.configurable?.[RUN_BREAKER_SCOPE_CONFIG_KEY] as
2545
+ | RunBreakerScope
2546
+ | undefined;
2547
+ return this.composeChildSignal(
2548
+ scope?.controller ?? this.resolveBreakerController(),
2549
+ config.signal
2550
+ );
2551
+ }
2552
+
2553
+ private createReplayResolutionError(
2554
+ call: ToolCall,
2555
+ config: RunnableConfig,
2556
+ execution: DurableExecutionRecord,
2557
+ signal: AbortSignal,
2558
+ error: unknown
2559
+ ): SubagentResolutionError {
2560
+ const selectedType =
2561
+ execution.binding?.subagentType ??
2562
+ execution.resumeExecution?.subagentType ??
2563
+ getSubagentType(call);
2564
+ const threadId = config.configurable?.thread_id;
2565
+ const failure = this.createResolutionFailure(
2566
+ 'identity',
2567
+ {
2568
+ subagentType:
2569
+ selectedType == null
2570
+ ? 'unknown'
2571
+ : (this.configs.get(selectedType)?.type ?? 'unknown'),
2572
+ description: DEFAULT_SUBAGENT_DESCRIPTION,
2573
+ parentToolCallId: call.id,
2574
+ threadId: typeof threadId === 'string' ? threadId : undefined,
2575
+ },
2576
+ execution,
2577
+ signal,
2578
+ error
2579
+ );
2580
+ return new SubagentResolutionError(
2581
+ 'identity',
2582
+ failure.resolutionFailure?.cause ?? 'unknown'
2583
+ );
2584
+ }
2585
+
2586
+ private createResolutionFailure(
2587
+ phase: SubagentResolutionPhase,
2588
+ params: SubagentExecuteParams,
2589
+ execution: DurableExecutionRecord,
2590
+ signal: AbortSignal,
2591
+ error: unknown
2592
+ ): SubagentExecuteResult {
2593
+ const identity = execution.identity ?? execution.attemptedIdentity;
2594
+ const resumeExecution =
2595
+ this.humanInTheLoop?.enabled === true && this.checkpointer != null
2596
+ ? execution.resumeExecution
2597
+ : undefined;
2598
+ const detail = logSubagentResolutionFailure(
2599
+ phase,
2600
+ params.subagentType,
2601
+ signal,
2602
+ error,
2603
+ {
2604
+ parentRunId: this.parentRunId,
2605
+ parentAgentId: this.parentAgentId,
2606
+ parentToolCallId: params.parentToolCallId,
2607
+ threadId: params.threadId,
2608
+ childRunId:
2609
+ identity?.childRunId ??
2610
+ resumeExecution?.childRunId ??
2611
+ execution.address.currentChildRunId,
2612
+ childThreadId:
2613
+ identity?.childThreadId ??
2614
+ (resumeExecution == null
2615
+ ? execution.address.baseChildThreadId
2616
+ : execution.address.branchChildThreadId),
2617
+ taskId: params.taskRuntime?.taskId,
2618
+ },
2619
+ this.onResolutionFailure
2620
+ );
2621
+ return {
2622
+ ...createSubagentFailure(detail.message),
2623
+ resolutionFailure: { phase, cause: detail.cause },
2624
+ };
2625
+ }
2626
+
2459
2627
  private async executeOnce(
2460
2628
  params: SubagentExecuteParams,
2461
2629
  execution: DurableExecutionRecord,
@@ -2489,10 +2657,16 @@ export class SubagentExecutor {
2489
2657
  identity = await this.resolveChildExecutionIdentity(execution);
2490
2658
  execution.assertUsable(childSignal);
2491
2659
  } catch (error) {
2492
- if (error instanceof StreamLimitExceededError) {
2660
+ if (isSubagentResolutionControlFlow(error)) {
2493
2661
  throw error;
2494
2662
  }
2495
- return createSubagentFailure(SUBAGENT_RESOLUTION_ERROR_MESSAGE);
2663
+ return this.createResolutionFailure(
2664
+ 'identity',
2665
+ params,
2666
+ execution,
2667
+ childSignal,
2668
+ error
2669
+ );
2496
2670
  }
2497
2671
  const { childRunId, childThreadId, approvalExecutionScope } = identity;
2498
2672
  const bound = this.bindExecutionDefinition(
@@ -2519,10 +2693,16 @@ export class SubagentExecutor {
2519
2693
  });
2520
2694
  execution.assertUsable(childSignal);
2521
2695
  } catch (error) {
2522
- if (error instanceof StreamLimitExceededError) {
2696
+ if (isSubagentResolutionControlFlow(error)) {
2523
2697
  throw error;
2524
2698
  }
2525
- return createSubagentFailure(SUBAGENT_RESOLUTION_ERROR_MESSAGE);
2699
+ return this.createResolutionFailure(
2700
+ 'config',
2701
+ params,
2702
+ execution,
2703
+ childSignal,
2704
+ error
2705
+ );
2526
2706
  }
2527
2707
 
2528
2708
  const parentRegistry = this.getParentHandlerRegistry();
@@ -2609,9 +2789,7 @@ export class SubagentExecutor {
2609
2789
  }
2610
2790
  } catch (error) {
2611
2791
  if (childSignal.aborted) {
2612
- throw childSignal.reason instanceof Error
2613
- ? childSignal.reason
2614
- : error;
2792
+ throw childSignal.reason instanceof Error ? childSignal.reason : error;
2615
2793
  }
2616
2794
  if (execution.completedResult != null) {
2617
2795
  return {
@@ -2656,6 +2834,7 @@ export class SubagentExecutor {
2656
2834
  subagentScope: true,
2657
2835
  subagentExecutionContext: childExecutionContext,
2658
2836
  subagentContext: this.subagentContext,
2837
+ onSubagentResolutionFailure: this.onResolutionFailure,
2659
2838
  ...(resumeExecution?.graphState.fadingTier == null
2660
2839
  ? {}
2661
2840
  : { fadingTier: resumeExecution.graphState.fadingTier }),
@@ -2958,10 +3137,11 @@ export class SubagentExecutor {
2958
3137
  },
2959
3138
  sessionId: currentHookSessionId,
2960
3139
  matchQuery: subagentType,
2961
- }).catch((): AggregatedHookResult =>
2962
- isBackgroundDenyMode(this.humanInTheLoop)
2963
- ? { ...HOOK_FALLBACK, hasHookFailures: true }
2964
- : HOOK_FALLBACK
3140
+ }).catch(
3141
+ (): AggregatedHookResult =>
3142
+ isBackgroundDenyMode(this.humanInTheLoop)
3143
+ ? { ...HOOK_FALLBACK, hasHookFailures: true }
3144
+ : HOOK_FALLBACK
2965
3145
  );
2966
3146
 
2967
3147
  const policyFailed =
@@ -3823,7 +4003,10 @@ export function sanitizeForwardedSubagentUpdateData(
3823
4003
  if (eventName === GraphEvents.ON_TOOL_CALLS_DISPATCHED) {
3824
4004
  if (!isObjectLike(data)) return undefined;
3825
4005
  const event = data as { dispatched_at?: number; toolCalls?: unknown };
3826
- if (!Array.isArray(event.toolCalls) || typeof event.dispatched_at !== 'number') {
4006
+ if (
4007
+ !Array.isArray(event.toolCalls) ||
4008
+ typeof event.dispatched_at !== 'number'
4009
+ ) {
3827
4010
  return undefined;
3828
4011
  }
3829
4012
  return {
@@ -3831,12 +4014,17 @@ export function sanitizeForwardedSubagentUpdateData(
3831
4014
  toolCalls: (event.toolCalls as unknown[]).flatMap((call) => {
3832
4015
  if (!isObjectLike(call)) return [];
3833
4016
  const entry = call as Record<string, unknown>;
3834
- if (typeof entry.id !== 'string' || typeof entry.name !== 'string') return [];
3835
- return [{
3836
- id: entry.id,
3837
- name: entry.name,
3838
- ...(typeof entry.stepId === 'string' ? { stepId: entry.stepId } : {}),
3839
- }];
4017
+ if (typeof entry.id !== 'string' || typeof entry.name !== 'string')
4018
+ return [];
4019
+ return [
4020
+ {
4021
+ id: entry.id,
4022
+ name: entry.name,
4023
+ ...(typeof entry.stepId === 'string'
4024
+ ? { stepId: entry.stepId }
4025
+ : {}),
4026
+ },
4027
+ ];
3840
4028
  }),
3841
4029
  };
3842
4030
  }
@@ -0,0 +1,142 @@
1
+ import {
2
+ SubagentSettlementBindingError,
3
+ SubagentDefinitionBindingError,
4
+ SubagentInvocationBindingError,
5
+ SubagentExecutionInvalidatedError,
6
+ } from './SubagentExecutionRegistry';
7
+ import { describeCodeApiError } from '@/tools/diagnostics';
8
+
9
+ /** Which step of child start-up failed: execution identity, then host config resolution. */
10
+ export type SubagentResolutionPhase = 'identity' | 'config';
11
+
12
+ const SUBAGENT_ERROR_TYPES = [
13
+ ['SubagentExecutionInvalidatedError', SubagentExecutionInvalidatedError],
14
+ ['SubagentDefinitionBindingError', SubagentDefinitionBindingError],
15
+ ['SubagentInvocationBindingError', SubagentInvocationBindingError],
16
+ ['SubagentSettlementBindingError', SubagentSettlementBindingError],
17
+ ] as const;
18
+
19
+ type SubagentErrorLabel =
20
+ | (typeof SUBAGENT_ERROR_TYPES)[number][0]
21
+ | ReturnType<typeof describeCodeApiError>['type'];
22
+
23
+ const SUBAGENT_RESOLUTION_MESSAGES = {
24
+ workspace_unavailable:
25
+ 'Subagent error: Workspace unavailable. Retry later or choose another workspace.',
26
+ agent_unavailable: 'Subagent error: Agent not found or not accessible.',
27
+ model_unavailable: 'Subagent error: Model or provider unavailable.',
28
+ configuration_changed:
29
+ 'Subagent error: Subagent configuration changed. Start a new execution.',
30
+ unknown: 'Subagent error: Unable to initialize the selected subagent.',
31
+ } as const;
32
+
33
+ /** Host classifications accepted at the public failure boundary. */
34
+ export type SubagentResolutionCause = keyof typeof SUBAGENT_RESOLUTION_MESSAGES;
35
+
36
+ export type SubagentResolutionContext = {
37
+ parentRunId: string;
38
+ parentAgentId?: string;
39
+ parentToolCallId?: string;
40
+ threadId?: string;
41
+ childRunId: string;
42
+ childThreadId: string;
43
+ taskId?: string;
44
+ };
45
+
46
+ /** No received error text, writable error names, or stacks enter this payload. */
47
+ export type SubagentResolutionDiagnostic = {
48
+ phase: SubagentResolutionPhase;
49
+ subagentType: string;
50
+ aborted: boolean;
51
+ type: SubagentErrorLabel;
52
+ cause: SubagentResolutionCause;
53
+ message: (typeof SUBAGENT_RESOLUTION_MESSAGES)[SubagentResolutionCause];
54
+ } & Partial<SubagentResolutionContext>;
55
+
56
+ /** The original error is private. Return a classification, never error text. */
57
+ export type SubagentResolutionFailureHandler = (
58
+ detail: Readonly<SubagentResolutionDiagnostic>,
59
+ error: unknown
60
+ ) => SubagentResolutionCause | void;
61
+
62
+ function normalizeResolutionCause(
63
+ cause: SubagentResolutionCause | void
64
+ ): SubagentResolutionCause {
65
+ switch (cause) {
66
+ case 'workspace_unavailable':
67
+ case 'agent_unavailable':
68
+ case 'model_unavailable':
69
+ case 'configuration_changed':
70
+ return cause;
71
+ default:
72
+ return 'unknown';
73
+ }
74
+ }
75
+
76
+ export function getSubagentResolutionFailureMessage(
77
+ cause: SubagentResolutionCause
78
+ ): SubagentResolutionDiagnostic['message'] {
79
+ return SUBAGENT_RESOLUTION_MESSAGES[normalizeResolutionCause(cause)];
80
+ }
81
+
82
+ /** Safe typed failure retained when detached execution crosses the host boundary. */
83
+ export class SubagentResolutionError extends Error {
84
+ readonly phase: SubagentResolutionPhase;
85
+ readonly resolutionCause: SubagentResolutionCause;
86
+
87
+ constructor(phase: SubagentResolutionPhase, cause: SubagentResolutionCause) {
88
+ super(getSubagentResolutionFailureMessage(cause));
89
+ this.name = 'SubagentResolutionError';
90
+ this.phase = phase;
91
+ this.resolutionCause = normalizeResolutionCause(cause);
92
+ }
93
+ }
94
+
95
+ function describeSubagentError(error: unknown): SubagentErrorLabel {
96
+ try {
97
+ for (const [label, constructor] of SUBAGENT_ERROR_TYPES) {
98
+ if (error instanceof constructor) {
99
+ return label;
100
+ }
101
+ }
102
+ } catch {
103
+ return 'UndescribableError';
104
+ }
105
+ return describeCodeApiError(error).type;
106
+ }
107
+
108
+ export function logSubagentResolutionFailure(
109
+ phase: SubagentResolutionPhase,
110
+ subagentType: string,
111
+ signal: AbortSignal,
112
+ error: unknown,
113
+ context?: SubagentResolutionContext,
114
+ onResolutionFailure?: SubagentResolutionFailureHandler
115
+ ): SubagentResolutionDiagnostic {
116
+ const detail: SubagentResolutionDiagnostic = {
117
+ ...context,
118
+ phase,
119
+ subagentType,
120
+ aborted: signal.aborted,
121
+ type: describeSubagentError(error),
122
+ cause: 'unknown',
123
+ message: SUBAGENT_RESOLUTION_MESSAGES.unknown,
124
+ };
125
+ if (onResolutionFailure != null) {
126
+ try {
127
+ const cause = normalizeResolutionCause(
128
+ onResolutionFailure(Object.freeze(detail), error)
129
+ );
130
+ return {
131
+ ...detail,
132
+ cause,
133
+ message: getSubagentResolutionFailureMessage(cause),
134
+ };
135
+ } catch {
136
+ // A broken diagnostic sink must not replace or expose the original failure.
137
+ }
138
+ }
139
+ // eslint-disable-next-line no-console
140
+ console.warn('[SubagentExecutor] Subagent resolution failed', detail);
141
+ return detail;
142
+ }
@@ -19,3 +19,14 @@ export type {
19
19
  } from './SubagentExecutor';
20
20
  export { InMemorySubagentTaskStore } from './InMemorySubagentTaskStore';
21
21
  export type { InMemorySubagentTaskStoreOptions } from './InMemorySubagentTaskStore';
22
+ export {
23
+ SubagentResolutionError,
24
+ getSubagentResolutionFailureMessage,
25
+ } from './diagnostics';
26
+ export type {
27
+ SubagentResolutionPhase,
28
+ SubagentResolutionCause,
29
+ SubagentResolutionContext,
30
+ SubagentResolutionDiagnostic,
31
+ SubagentResolutionFailureHandler,
32
+ } from './diagnostics';
@@ -44,6 +44,7 @@ import type {
44
44
  StreamPreemption,
45
45
  TokenBudgetBreakdown,
46
46
  } from '@/types/run';
47
+ import type { SubagentResolutionFailureHandler } from '@/tools/subagent/diagnostics';
47
48
  import type { SubagentTaskConfig } from '@/types/subagentTasks';
48
49
  import type { StandardGraph, MultiAgentGraph } from '@/graphs';
49
50
  import type { ProviderClientOptionsConfig } from '@/types/llm';
@@ -441,6 +442,8 @@ export type StandardGraphInput = {
441
442
  subagentTasks?: SubagentTaskConfig;
442
443
  /** Host-owned context and result projection for each isolated child execution. */
443
444
  subagentContext?: SubagentContextAdapter;
445
+ /** Receives private startup errors; returns only an SDK-owned public cause. */
446
+ onSubagentResolutionFailure?: SubagentResolutionFailureHandler;
444
447
  /**
445
448
  * True when this graph IS a subagent child run (set by `SubagentExecutor`
446
449
  * when it constructs the child graph). Drives the hook-input `agentId`
package/src/types/run.ts CHANGED
@@ -300,6 +300,8 @@ export type RunConfig = {
300
300
  subagentTasks?: SubagentTaskConfig;
301
301
  /** Authorizes and supplies host-owned context to isolated child runs. */
302
302
  subagentContext?: g.SubagentContextAdapter;
303
+ /** Host diagnostic sink for foreground, detached, and nested startup failures. */
304
+ onSubagentResolutionFailure?: g.StandardGraphInput['onSubagentResolutionFailure'];
303
305
  /**
304
306
  * Pre-constructed hook registry for this run. Hooks fire at lifecycle
305
307
  * points in `processStream` (RunStart, UserPromptSubmit, Stop,