@librechat/agents 4.0.1 → 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 (138) 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/search/rerankers.cjs +1 -0
  33. package/dist/cjs/tools/search/search.cjs +3 -3
  34. package/dist/cjs/tools/search/search.cjs.map +1 -1
  35. package/dist/cjs/tools/search/tool.cjs +4 -3
  36. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  37. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +11 -0
  38. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
  39. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +87 -14
  40. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  41. package/dist/cjs/tools/subagent/diagnostics.cjs +45 -2
  42. package/dist/cjs/tools/subagent/diagnostics.cjs.map +1 -1
  43. package/dist/cjs/tools/subagent/index.cjs +1 -0
  44. package/dist/esm/decisions/deadline.mjs +52 -0
  45. package/dist/esm/decisions/deadline.mjs.map +1 -0
  46. package/dist/esm/decisions/dialect.mjs +87 -0
  47. package/dist/esm/decisions/dialect.mjs.map +1 -0
  48. package/dist/esm/decisions/http.mjs +75 -0
  49. package/dist/esm/decisions/http.mjs.map +1 -0
  50. package/dist/esm/decisions/index.mjs +8 -0
  51. package/dist/esm/decisions/presets.mjs +77 -0
  52. package/dist/esm/decisions/presets.mjs.map +1 -0
  53. package/dist/esm/decisions/questions.mjs +67 -0
  54. package/dist/esm/decisions/questions.mjs.map +1 -0
  55. package/dist/esm/decisions/structuredChat.mjs +178 -0
  56. package/dist/esm/decisions/structuredChat.mjs.map +1 -0
  57. package/dist/esm/decisions/traceMarker.mjs +6 -0
  58. package/dist/esm/decisions/traceMarker.mjs.map +1 -0
  59. package/dist/esm/decisions/transport.mjs +164 -0
  60. package/dist/esm/decisions/transport.mjs.map +1 -0
  61. package/dist/esm/decisions/types.mjs +39 -0
  62. package/dist/esm/decisions/types.mjs.map +1 -0
  63. package/dist/esm/graphs/Graph.mjs +4 -1
  64. package/dist/esm/graphs/Graph.mjs.map +1 -1
  65. package/dist/esm/langfuseToolOutputTracing.mjs +6 -1
  66. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  67. package/dist/esm/main.mjs +13 -1
  68. package/dist/esm/rerank/index.mjs +3 -0
  69. package/dist/esm/rerank/search.mjs +45 -0
  70. package/dist/esm/rerank/search.mjs.map +1 -0
  71. package/dist/esm/rerank/systemone.mjs +70 -0
  72. package/dist/esm/rerank/systemone.mjs.map +1 -0
  73. package/dist/esm/run.mjs +4 -0
  74. package/dist/esm/run.mjs.map +1 -1
  75. package/dist/esm/tools/search/rerankers.mjs +1 -1
  76. package/dist/esm/tools/search/search.mjs +3 -3
  77. package/dist/esm/tools/search/search.mjs.map +1 -1
  78. package/dist/esm/tools/search/tool.mjs +4 -3
  79. package/dist/esm/tools/search/tool.mjs.map +1 -1
  80. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +11 -0
  81. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
  82. package/dist/esm/tools/subagent/SubagentExecutor.mjs +88 -15
  83. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  84. package/dist/esm/tools/subagent/diagnostics.mjs +44 -3
  85. package/dist/esm/tools/subagent/diagnostics.mjs.map +1 -1
  86. package/dist/esm/tools/subagent/index.mjs +1 -0
  87. package/dist/types/decisions/deadline.d.ts +3 -0
  88. package/dist/types/decisions/dialect.d.ts +11 -0
  89. package/dist/types/decisions/http.d.ts +22 -0
  90. package/dist/types/decisions/index.d.ts +10 -0
  91. package/dist/types/decisions/presets.d.ts +17 -0
  92. package/dist/types/decisions/questions.d.ts +8 -0
  93. package/dist/types/decisions/structuredChat.d.ts +16 -0
  94. package/dist/types/decisions/traceMarker.d.ts +2 -0
  95. package/dist/types/decisions/transport.d.ts +29 -0
  96. package/dist/types/decisions/types.d.ts +119 -0
  97. package/dist/types/graphs/Graph.d.ts +2 -1
  98. package/dist/types/index.d.ts +2 -0
  99. package/dist/types/rerank/index.d.ts +4 -0
  100. package/dist/types/rerank/search.d.ts +7 -0
  101. package/dist/types/rerank/systemone.d.ts +13 -0
  102. package/dist/types/rerank/types.d.ts +24 -0
  103. package/dist/types/run.d.ts +1 -0
  104. package/dist/types/tools/search/types.d.ts +3 -1
  105. package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +4 -0
  106. package/dist/types/tools/subagent/SubagentExecutor.d.ts +13 -0
  107. package/dist/types/tools/subagent/diagnostics.d.ts +32 -8
  108. package/dist/types/tools/subagent/index.d.ts +2 -0
  109. package/dist/types/types/graph.d.ts +3 -0
  110. package/dist/types/types/run.d.ts +2 -0
  111. package/package.json +1 -1
  112. package/src/decisions/deadline.ts +92 -0
  113. package/src/decisions/dialect.ts +200 -0
  114. package/src/decisions/http.ts +175 -0
  115. package/src/decisions/index.ts +14 -0
  116. package/src/decisions/presets.ts +111 -0
  117. package/src/decisions/questions.ts +128 -0
  118. package/src/decisions/structuredChat.ts +364 -0
  119. package/src/decisions/traceMarker.ts +2 -0
  120. package/src/decisions/transport.ts +335 -0
  121. package/src/decisions/types.ts +193 -0
  122. package/src/graphs/Graph.ts +4 -0
  123. package/src/index.ts +2 -0
  124. package/src/langfuseToolOutputTracing.ts +8 -1
  125. package/src/rerank/index.ts +4 -0
  126. package/src/rerank/search.ts +85 -0
  127. package/src/rerank/systemone.ts +140 -0
  128. package/src/rerank/types.ts +28 -0
  129. package/src/run.ts +4 -0
  130. package/src/tools/search/search.ts +4 -4
  131. package/src/tools/search/tool.ts +27 -25
  132. package/src/tools/search/types.ts +4 -1
  133. package/src/tools/subagent/SubagentExecutionRegistry.ts +21 -0
  134. package/src/tools/subagent/SubagentExecutor.ts +190 -22
  135. package/src/tools/subagent/diagnostics.ts +88 -9
  136. package/src/tools/subagent/index.ts +11 -0
  137. package/src/types/graph.ts +3 -0
  138. 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,
@@ -141,7 +151,6 @@ import { stripRunStepResumeState } from '@/tools/runStepResume';
141
151
  import { seedAgentInitialSessions } from '@/utils/toolSessions';
142
152
  import { stableStringify } from '@/tools/eagerEventExecution';
143
153
  import { convertInjectedMessages } from '@/messages/injected';
144
- import { logSubagentResolutionFailure } from './diagnostics';
145
154
  import { resolveClientOptionsModel } from '@/llm/request';
146
155
  import { isBackgroundDenyMode } from '@/types/hitl';
147
156
  import { composeAbortSignals } from '@/utils/misc';
@@ -162,13 +171,19 @@ const MAX_QUEUED_SUBAGENT_UPDATES = 64;
162
171
  const MAX_BACKGROUND_SUBAGENT_SEALS = 32;
163
172
  const SUBAGENT_UPDATE_HANDLER_TIMEOUT_MS = 5_000;
164
173
  const TEXT_DELTA_CONTENT_TYPE = `${ContentTypes.TEXT}_delta`;
165
- const SUBAGENT_RESOLUTION_ERROR_MESSAGE =
166
- 'Subagent error: Unable to initialize the selected subagent.';
167
174
  const SUBAGENT_CONFIG_CHANGED_MESSAGE =
168
175
  'Subagent error: Subagent configuration changed since this execution was paused.';
169
176
  const SUBAGENT_INVOCATION_CHANGED_MESSAGE =
170
177
  'Subagent error: Subagent invocation changed for this execution.';
171
178
 
179
+ function isSubagentResolutionControlFlow(error: unknown): boolean {
180
+ try {
181
+ return error instanceof StreamLimitExceededError || isGraphInterrupt(error);
182
+ } catch {
183
+ return false;
184
+ }
185
+ }
186
+
172
187
  function seedChildGraphSessions(
173
188
  childGraph: StandardGraph,
174
189
  agents: AgentInputs[]
@@ -867,6 +882,11 @@ export type SubagentExecuteParams = {
867
882
  export type SubagentExecuteResult = SubagentContextResult & {
868
883
  /** Tagged internal failure; foreground callers retain the legacy content. */
869
884
  error?: string;
885
+ /** Safe startup classification retained for detached host delivery. */
886
+ resolutionFailure?: {
887
+ phase: SubagentResolutionPhase;
888
+ cause: SubagentResolutionCause;
889
+ };
870
890
  /** Completed child work whose host projection must be retried without re-execution. */
871
891
  retryableDelivery?: true;
872
892
  };
@@ -986,6 +1006,8 @@ export type SubagentExecutorOptions = {
986
1006
  /** Host-owned process-local task namespace for detached execution. */
987
1007
  taskConfig?: SubagentTaskConfig;
988
1008
  subagentContext?: SubagentContextAdapter;
1009
+ /** Host diagnostic sink and safe cause classifier for startup failures. */
1010
+ onResolutionFailure?: SubagentResolutionFailureHandler;
989
1011
  };
990
1012
 
991
1013
  type DurableExecutionRecord = SubagentExecutionRecord<
@@ -1024,6 +1046,7 @@ export class SubagentExecutor {
1024
1046
  private readonly usageSink?: SubagentUsageSink;
1025
1047
  private readonly taskConfig?: SubagentTaskConfig;
1026
1048
  private readonly subagentContext?: SubagentContextAdapter;
1049
+ private readonly onResolutionFailure?: SubagentResolutionFailureHandler;
1027
1050
  private readonly executions: SubagentExecutionRegistry<
1028
1051
  SubagentExecuteResult,
1029
1052
  ResolvedSubagentConfig,
@@ -1069,6 +1092,7 @@ export class SubagentExecutor {
1069
1092
  this.usageSink = options.usageSink;
1070
1093
  this.taskConfig = options.taskConfig;
1071
1094
  this.subagentContext = options.subagentContext;
1095
+ this.onResolutionFailure = options.onResolutionFailure;
1072
1096
  const rawRegistry = options.parentHandlerRegistry;
1073
1097
  if (typeof rawRegistry === 'function') {
1074
1098
  this.resolveParentHandlerRegistry = rawRegistry;
@@ -1318,6 +1342,7 @@ export class SubagentExecutor {
1318
1342
  tokenCounter: this.tokenCounter,
1319
1343
  usageSink: this.usageSink,
1320
1344
  subagentContext: this.subagentContext,
1345
+ onResolutionFailure: this.onResolutionFailure,
1321
1346
  streamLimits: this.streamLimits,
1322
1347
  humanInTheLoop:
1323
1348
  this.humanInTheLoop?.enabled === true ||
@@ -1369,6 +1394,12 @@ export class SubagentExecutor {
1369
1394
  result = await executeAttempt();
1370
1395
  }
1371
1396
  if (result.error != null) {
1397
+ if (result.resolutionFailure != null) {
1398
+ throw new SubagentResolutionError(
1399
+ result.resolutionFailure.phase,
1400
+ result.resolutionFailure.cause
1401
+ );
1402
+ }
1372
1403
  throw new Error(result.error);
1373
1404
  }
1374
1405
  if (this.humanInTheLoop?.enabled === true) {
@@ -1662,13 +1693,6 @@ export class SubagentExecutor {
1662
1693
  ),
1663
1694
  });
1664
1695
  }
1665
- const sourceCheckpoints =
1666
- await this.getLatestCheckpointSnapshot(baseChildThreadId);
1667
- if (sourceCheckpoints.length === 0) {
1668
- throw new Error(
1669
- `Cannot fork subagent checkpoint thread "${baseChildThreadId}" without a checkpoint ID.`
1670
- );
1671
- }
1672
1696
  const childRunId = persistedChildRunId ?? currentChildRunId;
1673
1697
  const identity = {
1674
1698
  childRunId,
@@ -1678,6 +1702,14 @@ export class SubagentExecutor {
1678
1702
  resumeAttemptId
1679
1703
  ),
1680
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
+ }
1681
1713
  const lease = this.executions.beginIdentityPreparation(execution, identity);
1682
1714
  await this.prepareCheckpointFork(
1683
1715
  sourceCheckpoints,
@@ -2083,9 +2115,10 @@ export class SubagentExecutor {
2083
2115
  config: RunnableConfig
2084
2116
  ): Promise<SettledSubagentToolOutput | undefined> {
2085
2117
  const parentToolCallId = call.id;
2118
+ const checkpointer = this.checkpointer;
2086
2119
  if (
2087
2120
  this.humanInTheLoop?.enabled !== true ||
2088
- this.checkpointer == null ||
2121
+ checkpointer == null ||
2089
2122
  parentToolCallId == null ||
2090
2123
  parentToolCallId === ''
2091
2124
  ) {
@@ -2100,6 +2133,44 @@ export class SubagentExecutor {
2100
2133
  parentToolCallId,
2101
2134
  parentConfigurable,
2102
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> {
2103
2174
  const { resumeExecution } = execution;
2104
2175
  const inProcessSettledOutput = execution.settledOutput;
2105
2176
  if (inProcessSettledOutput != null) {
@@ -2158,7 +2229,7 @@ export class SubagentExecutor {
2158
2229
  const { childThreadId } =
2159
2230
  await this.resolveChildExecutionIdentity(execution);
2160
2231
  if (marker == null) {
2161
- const checkpoint = await this.checkpointer.getTuple({
2232
+ const checkpoint = await checkpointer.getTuple({
2162
2233
  configurable: { thread_id: childThreadId },
2163
2234
  });
2164
2235
  messages = getCheckpointMessages(
@@ -2310,8 +2381,20 @@ export class SubagentExecutor {
2310
2381
  },
2311
2382
  persistedOutput,
2312
2383
  async (): Promise<void> => {
2313
- const { childRunId, childThreadId } =
2314
- 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;
2315
2398
  const activeChildRun = execution.activeRun;
2316
2399
  if (activeChildRun != null) {
2317
2400
  await this.persistChildCheckpointMarker(
@@ -2457,6 +2540,90 @@ export class SubagentExecutor {
2457
2540
  }
2458
2541
  }
2459
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
+
2460
2627
  private async executeOnce(
2461
2628
  params: SubagentExecuteParams,
2462
2629
  execution: DurableExecutionRecord,
@@ -2490,16 +2657,16 @@ export class SubagentExecutor {
2490
2657
  identity = await this.resolveChildExecutionIdentity(execution);
2491
2658
  execution.assertUsable(childSignal);
2492
2659
  } catch (error) {
2493
- if (error instanceof StreamLimitExceededError) {
2660
+ if (isSubagentResolutionControlFlow(error)) {
2494
2661
  throw error;
2495
2662
  }
2496
- logSubagentResolutionFailure(
2663
+ return this.createResolutionFailure(
2497
2664
  'identity',
2498
- executableConfig.type,
2665
+ params,
2666
+ execution,
2499
2667
  childSignal,
2500
2668
  error
2501
2669
  );
2502
- return createSubagentFailure(SUBAGENT_RESOLUTION_ERROR_MESSAGE);
2503
2670
  }
2504
2671
  const { childRunId, childThreadId, approvalExecutionScope } = identity;
2505
2672
  const bound = this.bindExecutionDefinition(
@@ -2526,16 +2693,16 @@ export class SubagentExecutor {
2526
2693
  });
2527
2694
  execution.assertUsable(childSignal);
2528
2695
  } catch (error) {
2529
- if (error instanceof StreamLimitExceededError) {
2696
+ if (isSubagentResolutionControlFlow(error)) {
2530
2697
  throw error;
2531
2698
  }
2532
- logSubagentResolutionFailure(
2699
+ return this.createResolutionFailure(
2533
2700
  'config',
2534
- executableConfig.type,
2701
+ params,
2702
+ execution,
2535
2703
  childSignal,
2536
2704
  error
2537
2705
  );
2538
- return createSubagentFailure(SUBAGENT_RESOLUTION_ERROR_MESSAGE);
2539
2706
  }
2540
2707
 
2541
2708
  const parentRegistry = this.getParentHandlerRegistry();
@@ -2667,6 +2834,7 @@ export class SubagentExecutor {
2667
2834
  subagentScope: true,
2668
2835
  subagentExecutionContext: childExecutionContext,
2669
2836
  subagentContext: this.subagentContext,
2837
+ onSubagentResolutionFailure: this.onResolutionFailure,
2670
2838
  ...(resumeExecution?.graphState.fadingTier == null
2671
2839
  ? {}
2672
2840
  : { fadingTier: resumeExecution.graphState.fadingTier }),
@@ -20,18 +20,77 @@ type SubagentErrorLabel =
20
20
  | (typeof SUBAGENT_ERROR_TYPES)[number][0]
21
21
  | ReturnType<typeof describeCodeApiError>['type'];
22
22
 
23
- /**
24
- * Follows the rule in `@/tools/diagnostics`: every field is a value this
25
- * module owns or a host-registered subagent type, never text from the error.
26
- * The failure the model sees is a fixed sentence, so this line is the only
27
- * operator account of it; the host logs its own error text where it throws.
28
- */
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. */
29
47
  export type SubagentResolutionDiagnostic = {
30
48
  phase: SubagentResolutionPhase;
31
49
  subagentType: string;
32
50
  aborted: boolean;
33
51
  type: SubagentErrorLabel;
34
- };
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
+ }
35
94
 
36
95
  function describeSubagentError(error: unknown): SubagentErrorLabel {
37
96
  try {
@@ -50,14 +109,34 @@ export function logSubagentResolutionFailure(
50
109
  phase: SubagentResolutionPhase,
51
110
  subagentType: string,
52
111
  signal: AbortSignal,
53
- error: unknown
54
- ): void {
112
+ error: unknown,
113
+ context?: SubagentResolutionContext,
114
+ onResolutionFailure?: SubagentResolutionFailureHandler
115
+ ): SubagentResolutionDiagnostic {
55
116
  const detail: SubagentResolutionDiagnostic = {
117
+ ...context,
56
118
  phase,
57
119
  subagentType,
58
120
  aborted: signal.aborted,
59
121
  type: describeSubagentError(error),
122
+ cause: 'unknown',
123
+ message: SUBAGENT_RESOLUTION_MESSAGES.unknown,
60
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
+ }
61
139
  // eslint-disable-next-line no-console
62
140
  console.warn('[SubagentExecutor] Subagent resolution failed', detail);
141
+ return detail;
63
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,