@librechat/agents 3.4.0 → 3.4.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 (204) hide show
  1. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  2. package/dist/cjs/graphs/Graph.cjs +165 -57
  3. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs +81 -33
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  6. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs +14 -0
  7. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs.map +1 -0
  8. package/dist/cjs/graphs/createGraph.cjs +15 -0
  9. package/dist/cjs/graphs/createGraph.cjs.map +1 -0
  10. package/dist/cjs/graphs/index.cjs +1 -0
  11. package/dist/cjs/hooks/HookRegistry.cjs +83 -0
  12. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  13. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
  14. package/dist/cjs/hooks/executeHooks.cjs +60 -14
  15. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  16. package/dist/cjs/hooks/index.cjs.map +1 -1
  17. package/dist/cjs/hooks/types.cjs +2 -0
  18. package/dist/cjs/hooks/types.cjs.map +1 -1
  19. package/dist/cjs/llm/init.cjs +3 -3
  20. package/dist/cjs/llm/invoke.cjs +2 -2
  21. package/dist/cjs/main.cjs +34 -24
  22. package/dist/cjs/messages/format.cjs +40 -23
  23. package/dist/cjs/messages/format.cjs.map +1 -1
  24. package/dist/cjs/run.cjs +130 -53
  25. package/dist/cjs/run.cjs.map +1 -1
  26. package/dist/cjs/session/AgentSession.cjs +37 -2
  27. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  28. package/dist/cjs/session/JsonlSessionStore.cjs +2 -2
  29. package/dist/cjs/stream.cjs +1 -1
  30. package/dist/cjs/summarization/node.cjs +6 -3
  31. package/dist/cjs/summarization/node.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/SubagentTool.cjs +4 -4
  36. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  37. package/dist/cjs/tools/ToolNode.cjs +321 -95
  38. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  39. package/dist/cjs/tools/ToolSearch.cjs +1 -1
  40. package/dist/cjs/tools/eagerEventExecution.cjs +1 -0
  41. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +3 -3
  42. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +2 -2
  43. package/dist/cjs/tools/search/rerankers.cjs +154 -1
  44. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  45. package/dist/cjs/tools/search/tool.cjs +4 -1
  46. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  47. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +601 -0
  48. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -0
  49. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1114 -233
  50. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  51. package/dist/cjs/tools/subagent/SubagentReplay.cjs +177 -0
  52. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -0
  53. package/dist/cjs/tools/subagent/childGraphConfig.cjs +278 -0
  54. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -0
  55. package/dist/cjs/tools/subagent/index.cjs +1 -0
  56. package/dist/cjs/tools/subagent/runtimeLimits.cjs +5 -0
  57. package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -0
  58. package/dist/cjs/tools/toolOutputReferences.cjs +20 -0
  59. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  60. package/dist/cjs/utils/index.cjs +2 -2
  61. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  62. package/dist/esm/graphs/Graph.mjs +165 -57
  63. package/dist/esm/graphs/Graph.mjs.map +1 -1
  64. package/dist/esm/graphs/MultiAgentGraph.mjs +81 -33
  65. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  66. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs +14 -0
  67. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -0
  68. package/dist/esm/graphs/createGraph.mjs +15 -0
  69. package/dist/esm/graphs/createGraph.mjs.map +1 -0
  70. package/dist/esm/graphs/index.mjs +1 -0
  71. package/dist/esm/hooks/HookRegistry.mjs +83 -0
  72. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  73. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
  74. package/dist/esm/hooks/executeHooks.mjs +60 -14
  75. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  76. package/dist/esm/hooks/index.mjs.map +1 -1
  77. package/dist/esm/hooks/types.mjs +2 -1
  78. package/dist/esm/hooks/types.mjs.map +1 -1
  79. package/dist/esm/llm/init.mjs +1 -1
  80. package/dist/esm/llm/invoke.mjs +2 -2
  81. package/dist/esm/main.mjs +21 -19
  82. package/dist/esm/messages/format.mjs +40 -23
  83. package/dist/esm/messages/format.mjs.map +1 -1
  84. package/dist/esm/run.mjs +131 -54
  85. package/dist/esm/run.mjs.map +1 -1
  86. package/dist/esm/session/AgentSession.mjs +37 -2
  87. package/dist/esm/session/AgentSession.mjs.map +1 -1
  88. package/dist/esm/session/JsonlSessionStore.mjs +2 -2
  89. package/dist/esm/stream.mjs +1 -1
  90. package/dist/esm/summarization/node.mjs +6 -3
  91. package/dist/esm/summarization/node.mjs.map +1 -1
  92. package/dist/esm/tools/BashExecutor.mjs +1 -1
  93. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  94. package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
  95. package/dist/esm/tools/SubagentTool.mjs +4 -4
  96. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  97. package/dist/esm/tools/ToolNode.mjs +322 -96
  98. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  99. package/dist/esm/tools/ToolSearch.mjs +1 -1
  100. package/dist/esm/tools/eagerEventExecution.mjs +1 -1
  101. package/dist/esm/tools/local/LocalExecutionEngine.mjs +3 -3
  102. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +2 -2
  103. package/dist/esm/tools/search/rerankers.mjs +154 -1
  104. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  105. package/dist/esm/tools/search/tool.mjs +4 -1
  106. package/dist/esm/tools/search/tool.mjs.map +1 -1
  107. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +597 -0
  108. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -0
  109. package/dist/esm/tools/subagent/SubagentExecutor.mjs +1114 -233
  110. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  111. package/dist/esm/tools/subagent/SubagentReplay.mjs +170 -0
  112. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -0
  113. package/dist/esm/tools/subagent/childGraphConfig.mjs +272 -0
  114. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -0
  115. package/dist/esm/tools/subagent/index.mjs +1 -0
  116. package/dist/esm/tools/subagent/runtimeLimits.mjs +5 -0
  117. package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -0
  118. package/dist/esm/tools/toolOutputReferences.mjs +20 -0
  119. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  120. package/dist/esm/utils/index.mjs +2 -2
  121. package/dist/types/agents/AgentContext.d.ts +1 -1
  122. package/dist/types/graphs/Graph.d.ts +22 -2
  123. package/dist/types/graphs/MultiAgentGraph.d.ts +7 -1
  124. package/dist/types/graphs/applyGraphRuntimeConfig.d.ts +3 -0
  125. package/dist/types/graphs/createGraph.d.ts +13 -0
  126. package/dist/types/graphs/graphFactory.d.ts +13 -0
  127. package/dist/types/graphs/index.d.ts +2 -0
  128. package/dist/types/hooks/HookRegistry.d.ts +10 -1
  129. package/dist/types/hooks/executeHooks.d.ts +5 -1
  130. package/dist/types/hooks/index.d.ts +2 -2
  131. package/dist/types/hooks/types.d.ts +12 -0
  132. package/dist/types/run.d.ts +6 -3
  133. package/dist/types/session/AgentSession.d.ts +1 -0
  134. package/dist/types/tools/SubagentTool.d.ts +1 -1
  135. package/dist/types/tools/ToolNode.d.ts +23 -15
  136. package/dist/types/tools/search/rerankers.d.ts +28 -0
  137. package/dist/types/tools/search/types.d.ts +38 -1
  138. package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +171 -0
  139. package/dist/types/tools/subagent/SubagentExecutor.d.ts +70 -36
  140. package/dist/types/tools/subagent/SubagentReplay.d.ts +85 -0
  141. package/dist/types/tools/subagent/childGraphConfig.d.ts +37 -0
  142. package/dist/types/tools/subagent/index.d.ts +1 -1
  143. package/dist/types/tools/subagent/runtimeLimits.d.ts +4 -0
  144. package/dist/types/tools/toolOutputReferences.d.ts +12 -0
  145. package/dist/types/types/graph.d.ts +189 -24
  146. package/dist/types/types/hitl.d.ts +15 -0
  147. package/package.json +8 -2
  148. package/src/agents/AgentContext.ts +1 -1
  149. package/src/graphs/Graph.ts +349 -104
  150. package/src/graphs/MultiAgentGraph.ts +149 -47
  151. package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +48 -9
  152. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1 -1
  153. package/src/graphs/__tests__/Graph.subagentResumeState.test.ts +80 -0
  154. package/src/graphs/__tests__/MultiAgentGraph.test.ts +86 -0
  155. package/src/graphs/__tests__/composition.smoke.test.ts +152 -0
  156. package/src/graphs/__tests__/createGraph.test.ts +206 -0
  157. package/src/graphs/applyGraphRuntimeConfig.ts +25 -0
  158. package/src/graphs/createGraph.ts +32 -0
  159. package/src/graphs/graphFactory.ts +12 -0
  160. package/src/graphs/index.ts +2 -0
  161. package/src/hooks/HookRegistry.ts +151 -1
  162. package/src/hooks/__tests__/HookRegistry.test.ts +96 -0
  163. package/src/hooks/__tests__/executeHooks.test.ts +160 -0
  164. package/src/hooks/executeHooks.ts +133 -15
  165. package/src/hooks/index.ts +3 -1
  166. package/src/hooks/types.ts +16 -0
  167. package/src/messages/format.ts +54 -26
  168. package/src/messages/formatAgentMessages.reducer.test.ts +162 -0
  169. package/src/messages/formatAgentMessages.steer.test.ts +16 -11
  170. package/src/messages/formatAgentMessages.test.ts +7 -2
  171. package/src/run.ts +214 -56
  172. package/src/session/AgentSession.ts +52 -2
  173. package/src/session/__tests__/JsonlSessionStore.test.ts +53 -0
  174. package/src/specs/graph-subagent.live.test.ts +242 -0
  175. package/src/specs/graph-subagent.test.ts +322 -0
  176. package/src/specs/langfuse-routing.integration.test.ts +120 -1
  177. package/src/specs/subagent.test.ts +130 -4
  178. package/src/summarization/__tests__/node.test.ts +32 -14
  179. package/src/summarization/node.ts +15 -11
  180. package/src/tools/SubagentTool.ts +4 -4
  181. package/src/tools/ToolNode.ts +643 -157
  182. package/src/tools/__tests__/SubagentExecutor.test.ts +740 -15
  183. package/src/tools/__tests__/SubagentReplay.test.ts +329 -0
  184. package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +208 -2
  185. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +512 -2
  186. package/src/tools/__tests__/hitl.test.ts +234 -5
  187. package/src/tools/__tests__/subagentHooks.test.ts +1126 -61
  188. package/src/tools/__tests__/toolOutputReferences.test.ts +19 -2
  189. package/src/tools/search/rag-api-reranker.test.ts +891 -0
  190. package/src/tools/search/rerankers.ts +260 -0
  191. package/src/tools/search/tool.ts +6 -0
  192. package/src/tools/search/types.ts +45 -1
  193. package/src/tools/subagent/SubagentExecutionRegistry.ts +1159 -0
  194. package/src/tools/subagent/SubagentExecutor.ts +2232 -312
  195. package/src/tools/subagent/SubagentReplay.ts +584 -0
  196. package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +637 -0
  197. package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +3054 -0
  198. package/src/tools/subagent/__tests__/childGraphConfig.test.ts +521 -0
  199. package/src/tools/subagent/childGraphConfig.ts +694 -0
  200. package/src/tools/subagent/index.ts +6 -0
  201. package/src/tools/subagent/runtimeLimits.ts +7 -0
  202. package/src/tools/toolOutputReferences.ts +40 -1
  203. package/src/types/graph.ts +222 -24
  204. package/src/types/hitl.ts +16 -0
@@ -17,12 +17,23 @@ import type {
17
17
  MessageContent,
18
18
  } from '@langchain/core/messages';
19
19
  import type { ToolCall } from '@langchain/core/messages/tool';
20
+ import type {
21
+ ReplayableSubagentTool,
22
+ SubagentGraphResumeState,
23
+ SubagentResumeManifest,
24
+ SubagentToolNodeResumeState,
25
+ SettledSubagentToolOutput,
26
+ } from '@/tools/subagent/SubagentReplay';
20
27
  import type {
21
28
  ResolvedStreamLimits,
22
29
  RunBreakerScope,
23
30
  StreamedToolCallArgTally,
24
31
  StreamDeltaEventTally,
25
32
  } from '@/llm/streamLimits';
33
+ import type {
34
+ GraphFactory,
35
+ GraphFactoryDependencies,
36
+ } from '@/graphs/graphFactory';
26
37
  import type { OverflowRecoveryPlan } from '@/llm/contextOverflowRecovery';
27
38
  import type { FallbackErrorContext } from '@/llm/invoke';
28
39
  import type { HookRegistry } from '@/hooks';
@@ -60,14 +71,6 @@ import {
60
71
  appendPredecessorHandoffCue,
61
72
  removePredecessorHandoffCue,
62
73
  } from '@/messages';
63
- import {
64
- attemptInvoke,
65
- tryFallbackProviders,
66
- getFallbackErrorContext,
67
- getFallbackOverflowCandidates,
68
- projectMessagesForProvider,
69
- resolveServingModelId,
70
- } from '@/llm/invoke';
71
74
  import {
72
75
  resetIfNotEmpty,
73
76
  isAnthropicLike,
@@ -79,6 +82,27 @@ import {
79
82
  joinKeys,
80
83
  sleep,
81
84
  } from '@/utils';
85
+ import {
86
+ attemptInvoke,
87
+ tryFallbackProviders,
88
+ getFallbackErrorContext,
89
+ getFallbackOverflowCandidates,
90
+ projectMessagesForProvider,
91
+ resolveServingModelId,
92
+ } from '@/llm/invoke';
93
+ import {
94
+ resolveStreamLimits,
95
+ StreamLimitExceededError,
96
+ sweepStaleStreamLimitEntries,
97
+ STREAM_LIMIT_EPOCH_KEY,
98
+ RUN_BREAKER_SCOPE_CONFIG_KEY,
99
+ } from '@/llm/streamLimits';
100
+ import {
101
+ DEFAULT_SUBAGENT_DESCRIPTION,
102
+ SubagentExecutor,
103
+ isGraphSubagentConfig,
104
+ normalizeSubagentConfigEntries,
105
+ } from '@/tools/subagent';
82
106
  import {
83
107
  Constants,
84
108
  GraphNodeKeys,
@@ -122,18 +146,12 @@ import {
122
146
  findCallback,
123
147
  type CallbackEntry,
124
148
  } from '@/utils/callbacks';
125
- import {
126
- resolveStreamLimits,
127
- StreamLimitExceededError,
128
- sweepStaleStreamLimitEntries,
129
- STREAM_LIMIT_EPOCH_KEY,
130
- RUN_BREAKER_SCOPE_CONFIG_KEY,
131
- } from '@/llm/streamLimits';
132
149
  import { partitionAndMarkOpenRouterToolCache } from '@/llm/openrouter/toolCache';
133
150
  import { ToolNode as CustomToolNode, toolsCondition } from '@/tools/ToolNode';
134
151
  import { shouldTraceToolNodeForLangfuse } from '@/langfuseToolOutputTracing';
135
152
  import { createLocalCodingToolBundle } from '@/tools/local/LocalCodingTools';
136
- import { SubagentExecutor, resolveSubagentConfigs } from '@/tools/subagent';
153
+ import { SUBAGENT_REPLAY_CONTROLLER } from '@/tools/subagent/SubagentReplay';
154
+ import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
137
155
  import { partitionAndMarkBedrockToolCache } from '@/llm/bedrock/toolCache';
138
156
  import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
139
157
  import { createCloudflareCodingToolBundle } from '@/tools/cloudflare';
@@ -171,15 +189,21 @@ const EMPTY_PREEMPT_BOUNDARY: PreemptBoundaryResult = {
171
189
  /** Minimum relative variance before calibrated toolSchemaTokens overrides current value. */
172
190
  const CALIBRATION_VARIANCE_THRESHOLD = 0.15;
173
191
 
174
- function createToolHandlerRegistry(
192
+ function createChildHandlerRegistry(
175
193
  source: HandlerRegistry | undefined
176
194
  ): HandlerRegistry | undefined {
177
195
  const toolHandler = source?.getHandler(GraphEvents.ON_TOOL_EXECUTE);
178
- if (toolHandler == null) {
196
+ const updateHandler = source?.getHandler(GraphEvents.ON_SUBAGENT_UPDATE);
197
+ if (toolHandler == null && updateHandler == null) {
179
198
  return undefined;
180
199
  }
181
200
  const registry = new HandlerRegistry();
182
- registry.register(GraphEvents.ON_TOOL_EXECUTE, toolHandler);
201
+ if (toolHandler != null) {
202
+ registry.register(GraphEvents.ON_TOOL_EXECUTE, toolHandler);
203
+ }
204
+ if (updateHandler != null) {
205
+ registry.register(GraphEvents.ON_SUBAGENT_UPDATE, updateHandler);
206
+ }
183
207
  return registry;
184
208
  }
185
209
 
@@ -630,6 +654,10 @@ export abstract class Graph<
630
654
  _TNodeName extends string = string,
631
655
  > {
632
656
  abstract resetValues(keepContent?: boolean, checkpointScope?: string): void;
657
+ restoreCheckpointMessages(
658
+ _messages: BaseMessage[],
659
+ _pendingMessages?: BaseMessage[]
660
+ ): void {}
633
661
  abstract initializeTools({
634
662
  currentTools,
635
663
  currentToolMap,
@@ -848,11 +876,18 @@ export abstract class Graph<
848
876
  // Flush each compiled ToolNode's direct-path turn cache so it
849
877
  // doesn't leak across Runs (Codex P2 #33). The cache survives
850
878
  // `run()` re-entry by design (resume-stable), but end-of-Run
851
- // is the right point to reset it.
879
+ // is the right point to reset it. Retain the registrations because
880
+ // the compiled workflow can be reused for later Runs; compilation
881
+ // will not register these instances again.
852
882
  for (const node of this._compiledToolNodes) {
853
883
  node.clearDirectPathTurns();
854
884
  }
855
- this._compiledToolNodes.clear();
885
+ // Subagent executors are likewise compiled once and reused. Clear
886
+ // their per-Run state without dropping the registrations needed by
887
+ // subsequent cleanup cycles.
888
+ for (const executor of this._subagentExecutors) {
889
+ executor.clearHeavyState();
890
+ }
856
891
  this.sessions.clear();
857
892
  }
858
893
 
@@ -904,10 +939,137 @@ export abstract class Graph<
904
939
  */
905
940
  protected registerCompiledToolNode(node: {
906
941
  clearDirectPathTurns(): void;
942
+ createSubagentResumeState(): SubagentToolNodeResumeState;
943
+ restoreSubagentResumeState(state: SubagentToolNodeResumeState): void;
907
944
  }): void {
908
945
  this._compiledToolNodes.add(node);
909
946
  }
910
947
 
948
+ protected registerSubagentExecutor(executor: SubagentExecutor): void {
949
+ this._subagentExecutors.add(executor);
950
+ }
951
+
952
+ protected resetSubagentCheckpointThreadIds(): void {
953
+ for (const executor of this._subagentExecutors) {
954
+ executor.resetCheckpointThreadIds();
955
+ }
956
+ }
957
+
958
+ getChildCheckpointThreadIds(): string[] {
959
+ const threadIds = new Set<string>();
960
+ for (const executor of this._subagentExecutors) {
961
+ for (const threadId of executor.getChildCheckpointThreadIds()) {
962
+ threadIds.add(threadId);
963
+ }
964
+ }
965
+ return [...threadIds];
966
+ }
967
+
968
+ createSubagentResumeState(runId: string): SubagentGraphResumeState {
969
+ return {
970
+ toolCallSteps: [...this.toolCallStepIds].map(([toolCallId, stepId]) => ({
971
+ toolCallId,
972
+ stepId,
973
+ })),
974
+ toolSessions: [...this.sessions].map(([toolName, context]) => ({
975
+ toolName,
976
+ context: {
977
+ ...context,
978
+ ...(context.files == null
979
+ ? {}
980
+ : { files: context.files.map((file) => ({ ...file })) }),
981
+ },
982
+ })),
983
+ toolNodes: [...this._compiledToolNodes].map((node) =>
984
+ node.createSubagentResumeState()
985
+ ),
986
+ eagerToolUsage: [
987
+ {
988
+ agentId: '',
989
+ toolUsageCounts: [...this.eagerEventToolUsageCount].map(
990
+ ([toolName, count]) => ({ toolName, count })
991
+ ),
992
+ },
993
+ ...[...this.eagerEventToolUsageCountsByAgentId].map(
994
+ ([agentId, usageCounts]) => ({
995
+ agentId,
996
+ toolUsageCounts: [...usageCounts].map(([toolName, count]) => ({
997
+ toolName,
998
+ count,
999
+ })),
1000
+ })
1001
+ ),
1002
+ ],
1003
+ eagerToolSuppressions: [...this.eagerEventToolSuppressions],
1004
+ ...(this._toolOutputRegistry == null
1005
+ ? {}
1006
+ : {
1007
+ toolOutputReferences: this._toolOutputRegistry.snapshotState(runId),
1008
+ }),
1009
+ };
1010
+ }
1011
+
1012
+ restoreSubagentResumeState(
1013
+ state: SubagentGraphResumeState,
1014
+ runId: string
1015
+ ): void {
1016
+ const toolNodesByKey = new Map(
1017
+ [...this._compiledToolNodes].map((node) => {
1018
+ const nodeState = node.createSubagentResumeState();
1019
+ return [nodeState.stateKey, node] as const;
1020
+ })
1021
+ );
1022
+ if (toolNodesByKey.size !== state.toolNodes.length) {
1023
+ throw new Error('Cannot restore changed subagent tool topology.');
1024
+ }
1025
+ for (const nodeState of state.toolNodes) {
1026
+ if (!toolNodesByKey.has(nodeState.stateKey)) {
1027
+ throw new Error(
1028
+ `Cannot restore subagent tool state for "${nodeState.stateKey}".`
1029
+ );
1030
+ }
1031
+ }
1032
+ const registry =
1033
+ state.toolOutputReferences == null
1034
+ ? undefined
1035
+ : this.getOrCreateToolOutputRegistry();
1036
+ if (state.toolOutputReferences != null && registry == null) {
1037
+ throw new Error('Cannot restore disabled tool output references.');
1038
+ }
1039
+
1040
+ this.toolCallStepIds.clear();
1041
+ for (const { toolCallId, stepId } of state.toolCallSteps) {
1042
+ this.toolCallStepIds.set(toolCallId, stepId);
1043
+ }
1044
+ this.sessions.clear();
1045
+ for (const { toolName, context } of state.toolSessions) {
1046
+ this.sessions.set(toolName, {
1047
+ ...context,
1048
+ ...(context.files == null
1049
+ ? {}
1050
+ : { files: context.files.map((file) => ({ ...file })) }),
1051
+ });
1052
+ }
1053
+ for (const nodeState of state.toolNodes) {
1054
+ const node = toolNodesByKey.get(nodeState.stateKey)!;
1055
+ node.restoreSubagentResumeState(nodeState);
1056
+ }
1057
+ this.clearEagerEventToolUsageCounts();
1058
+ for (const usageState of state.eagerToolUsage) {
1059
+ const usageCounts = this.getEagerEventToolUsageCount(usageState.agentId);
1060
+ for (const { toolName, count } of usageState.toolUsageCounts) {
1061
+ usageCounts.set(toolName, count);
1062
+ }
1063
+ }
1064
+ this.eagerEventToolSuppressions.clear();
1065
+ for (const toolName of state.eagerToolSuppressions) {
1066
+ this.eagerEventToolSuppressions.add(toolName);
1067
+ }
1068
+ if (state.toolOutputReferences != null && registry != null) {
1069
+ registry.restoreState(runId, state.toolOutputReferences);
1070
+ }
1071
+ }
1072
+
911
1073
  /**
912
1074
  * Returns the shared `ToolOutputReferenceRegistry` for this run,
913
1075
  * constructing it on first access. Returns `undefined` when the
@@ -957,7 +1119,10 @@ export abstract class Graph<
957
1119
  */
958
1120
  private _compiledToolNodes: Set<{
959
1121
  clearDirectPathTurns(): void;
1122
+ createSubagentResumeState(): SubagentToolNodeResumeState;
1123
+ restoreSubagentResumeState(state: SubagentToolNodeResumeState): void;
960
1124
  }> = new Set();
1125
+ private _subagentExecutors = new Set<SubagentExecutor>();
961
1126
  public getOrCreateFileCheckpointer(): t.LocalFileCheckpointer | undefined {
962
1127
  // Return the cached instance unconditionally if one exists. The
963
1128
  // toolExecution check below decides whether to *create* a new
@@ -999,11 +1164,15 @@ export abstract class Graph<
999
1164
  export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1000
1165
  overrideModel?: t.ChatModel;
1001
1166
  private subagentModelOverride?: t.ChatModel;
1167
+ private readonly graphFactory: GraphFactory;
1168
+ private readonly supportsMultiAgentChildren: boolean;
1002
1169
  /** Optional compile options passed into workflow.compile() */
1003
1170
  compileOptions?: t.CompileOptions | undefined;
1004
1171
  /** Whether the workflow was actually compiled with a checkpointer. */
1005
1172
  hasCompiledCheckpointer: boolean = false;
1006
1173
  messages: BaseMessage[] = [];
1174
+ /** Whether a rebuilt resume seeded the message baseline from its checkpoint. */
1175
+ private hasRestoredCheckpointMessages = false;
1007
1176
  /** Cached run messages preserved before clearHeavyState() so getRunMessages() works after cleanup. */
1008
1177
  private cachedRunMessages?: BaseMessage[];
1009
1178
  /** Per-agent discovery snapshots preserved before contexts are reset on cleanup. */
@@ -1045,6 +1214,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1045
1214
  subagentUsageSink?: t.SubagentUsageSink;
1046
1215
  /** See {@link t.StandardGraphInput.subagentScope}. */
1047
1216
  subagentScope: boolean;
1217
+ /** See {@link t.StandardGraphInput.subagentExecutionContext}. */
1218
+ private readonly subagentExecutionContext?: t.SubagentExecutionContext;
1048
1219
  /** See {@link t.StandardGraphInput.preemption}. */
1049
1220
  preemption?: t.StreamPreemption;
1050
1221
  /**
@@ -1058,8 +1229,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1058
1229
  * generation key + chunk index (see `resolveGenerationKey`). Per-run
1059
1230
  * accumulation state, cleared by both reset paths.
1060
1231
  */
1061
- streamedToolCallArgTallies: Map<string, StreamedToolCallArgTally> =
1062
- new Map();
1232
+ streamedToolCallArgTallies: Map<string, StreamedToolCallArgTally> = new Map();
1063
1233
  /** Streamed chunk events per model generation, keyed by generation key. */
1064
1234
  streamDeltaEventCounts: Map<string, StreamDeltaEventTally> = new Map();
1065
1235
  /** Per-chunk-object, per-generation charge balances (lazily created; see
@@ -1167,26 +1337,42 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1167
1337
  */
1168
1338
  pendingPreemptReturn = new Set<string>();
1169
1339
 
1170
- constructor({
1171
- runId,
1172
- signal,
1173
- agents,
1174
- langfuse,
1175
- tokenCounter,
1176
- indexTokenCountMap,
1177
- calibrationRatio,
1178
- subagentUsageSink,
1179
- subagentScope,
1180
- preemption,
1181
- streamLimits,
1182
- }: t.StandardGraphInput) {
1340
+ constructor(
1341
+ {
1342
+ runId,
1343
+ signal,
1344
+ agents,
1345
+ langfuse,
1346
+ tokenCounter,
1347
+ indexTokenCountMap,
1348
+ calibrationRatio,
1349
+ subagentUsageSink,
1350
+ subagentScope,
1351
+ subagentExecutionContext,
1352
+ preemption,
1353
+ streamLimits,
1354
+ }: t.StandardGraphInput,
1355
+ dependencies?: GraphFactoryDependencies
1356
+ ) {
1183
1357
  super();
1358
+ this.supportsMultiAgentChildren = dependencies != null;
1359
+ this.graphFactory =
1360
+ dependencies?.graphFactory ??
1361
+ ((request): StandardGraph => {
1362
+ if (request.kind !== 'standard') {
1363
+ throw new Error(
1364
+ 'A polymorphic graph factory is required for multi-agent graph construction.'
1365
+ );
1366
+ }
1367
+ return new StandardGraph(request.input);
1368
+ });
1184
1369
  this.runId = runId;
1185
1370
  this.langfuseScopeRunId = `${runId ?? 'graph'}:${nanoid()}`;
1186
1371
  this.signal = signal;
1187
1372
  this.langfuse = langfuse;
1188
1373
  this.subagentUsageSink = subagentUsageSink;
1189
1374
  this.subagentScope = subagentScope === true;
1375
+ this.subagentExecutionContext = subagentExecutionContext;
1190
1376
  this.preemption = preemption;
1191
1377
  this.streamLimits = resolveStreamLimits(streamLimits);
1192
1378
 
@@ -1213,7 +1399,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1213
1399
  /* Init */
1214
1400
 
1215
1401
  resetValues(keepContent?: boolean, checkpointScope?: string): void {
1402
+ this.resetSubagentCheckpointThreadIds();
1216
1403
  this.messages = [];
1404
+ this.hasRestoredCheckpointMessages = false;
1217
1405
  this.cachedRunMessages = undefined;
1218
1406
  this.cachedDiscoveredTools = undefined;
1219
1407
  this.config = resetIfNotEmpty(this.config, undefined);
@@ -1310,6 +1498,23 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1310
1498
  : undefined;
1311
1499
  }
1312
1500
 
1501
+ /** Seeds the sidecar message view that checkpoint restoration bypasses. */
1502
+ override restoreCheckpointMessages(
1503
+ messages: BaseMessage[],
1504
+ pendingMessages?: BaseMessage[]
1505
+ ): void {
1506
+ if (this.messages.length > 0) {
1507
+ return;
1508
+ }
1509
+ this.messages =
1510
+ pendingMessages == null
1511
+ ? [...messages]
1512
+ : messagesStateReducer(messages, pendingMessages);
1513
+ this.startIndex = this.messages.length;
1514
+ this.hasRestoredCheckpointMessages = true;
1515
+ this.cachedRunMessages = undefined;
1516
+ }
1517
+
1313
1518
  override clearHeavyState(): void {
1314
1519
  this.cachedRunMessages = this.messages.slice(this.startIndex);
1315
1520
  this.cachedDiscoveredTools = new Map(
@@ -1798,6 +2003,15 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1798
2003
  runLangfuse: this.langfuse,
1799
2004
  agentLangfuse: agentContext?.langfuse,
1800
2005
  });
2006
+ const interruptingToolNames = new Set(this.interruptingToolNames ?? []);
2007
+ if (
2008
+ this.humanInTheLoop?.enabled === true &&
2009
+ (agentContext?.subagentConfigs?.length ?? 0) > 0
2010
+ ) {
2011
+ interruptingToolNames.add(Constants.SUBAGENT);
2012
+ }
2013
+ const effectiveInterruptingToolNames =
2014
+ interruptingToolNames.size > 0 ? interruptingToolNames : undefined;
1801
2015
 
1802
2016
  if (eventDrivenMode) {
1803
2017
  const schemaTools = createSchemaOnlyTools(toolDefinitions);
@@ -1849,11 +2063,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1849
2063
  eagerEventToolSuppressions: this.eagerEventToolSuppressions,
1850
2064
  toolExecution: this.toolExecution,
1851
2065
  directToolNames: directToolNames.size > 0 ? directToolNames : undefined,
1852
- interruptingToolNames:
1853
- this.interruptingToolNames != null &&
1854
- this.interruptingToolNames.length > 0
1855
- ? new Set(this.interruptingToolNames)
1856
- : undefined,
2066
+ interruptingToolNames: effectiveInterruptingToolNames,
1857
2067
  maxContextTokens: agentContext?.maxContextTokens,
1858
2068
  maxToolResultChars: agentContext?.maxToolResultChars,
1859
2069
  toolOutputRegistry: this.getOrCreateToolOutputRegistry(),
@@ -1916,11 +2126,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1916
2126
  sessions: this.sessions,
1917
2127
  toolExecution: this.toolExecution,
1918
2128
  codeSessionToolNames: this.codeSessionToolNames,
1919
- interruptingToolNames:
1920
- this.interruptingToolNames != null &&
1921
- this.interruptingToolNames.length > 0
1922
- ? new Set(this.interruptingToolNames)
1923
- : undefined,
2129
+ interruptingToolNames: effectiveInterruptingToolNames,
1924
2130
  hookRegistry: this.hookRegistry,
1925
2131
  humanInTheLoop: this.humanInTheLoop,
1926
2132
  maxContextTokens: agentContext?.maxContextTokens,
@@ -3377,7 +3583,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3377
3583
  * would start new provider work after the run-wide breaker fired.
3378
3584
  * Rethrow the breaker's own stream-limit reason instead. */
3379
3585
  {
3380
- const trippedReason = this.resolveTrippedBreakerReason(attemptBreaker.signal);
3586
+ const trippedReason = this.resolveTrippedBreakerReason(
3587
+ attemptBreaker.signal
3588
+ );
3381
3589
  if (trippedReason != null) {
3382
3590
  throw trippedReason;
3383
3591
  }
@@ -3624,7 +3832,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3624
3832
  }
3625
3833
  {
3626
3834
  /** Same sibling-abort translation guard as the primary catch. */
3627
- const trippedReason = this.resolveTrippedBreakerReason(attemptBreaker.signal);
3835
+ const trippedReason = this.resolveTrippedBreakerReason(
3836
+ attemptBreaker.signal
3837
+ );
3628
3838
  if (trippedReason != null) {
3629
3839
  throw trippedReason;
3630
3840
  }
@@ -4037,15 +4247,57 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4037
4247
  agentContext.subagentConfigs.length > 0 &&
4038
4248
  effectiveSubagentDepth > 0
4039
4249
  ) {
4040
- const resolvedConfigs = resolveSubagentConfigs(
4250
+ const executableConfigs = normalizeSubagentConfigEntries(
4041
4251
  agentContext.subagentConfigs,
4042
4252
  agentContext
4043
4253
  );
4044
- if (resolvedConfigs.length > 0) {
4254
+ if (executableConfigs.length > 0) {
4255
+ if (
4256
+ !this.supportsMultiAgentChildren &&
4257
+ executableConfigs.some(isGraphSubagentConfig)
4258
+ ) {
4259
+ throw new Error(
4260
+ 'Graph subagents require constructing the parent with createGraph() or an injected GraphFactory dependency.'
4261
+ );
4262
+ }
4045
4263
  const getParentHandlerRegistry = (): HandlerRegistry | undefined =>
4046
4264
  this.handlerRegistry ?? this.parentToolHandlerRegistry;
4265
+ const createConfiguredChildGraph: GraphFactory = (request) => {
4266
+ const childGraph = this.graphFactory(request);
4267
+ if (this.subagentModelOverride != null) {
4268
+ childGraph.overrideModel = this.subagentModelOverride;
4269
+ childGraph.setSubagentModelOverride(this.subagentModelOverride);
4270
+ }
4271
+ const childHandlerRegistry = createChildHandlerRegistry(
4272
+ getParentHandlerRegistry()
4273
+ );
4274
+ // Pure execution-ordering hint (unlike `humanInTheLoop`). It only
4275
+ // reorders tools already in the child's direct group; it does not
4276
+ // force a schema-only event tool onto the direct execution path.
4277
+ applyGraphRuntimeConfig(childGraph, {
4278
+ hookRegistry: this.hookRegistry,
4279
+ humanInTheLoop: this.humanInTheLoop,
4280
+ toolOutputReferences: this.toolOutputReferences,
4281
+ eagerEventToolExecution: this.eagerEventToolExecution,
4282
+ codeSessionToolNames: this.codeSessionToolNames,
4283
+ interruptingToolNames: this.interruptingToolNames,
4284
+ toolExecution: this.toolExecution,
4285
+ });
4286
+ if (this.humanInTheLoop?.enabled === true) {
4287
+ childGraph.compileOptions = {
4288
+ checkpointer: this.compileOptions?.checkpointer,
4289
+ };
4290
+ }
4291
+ childGraph.parentToolHandlerRegistry = childHandlerRegistry;
4292
+ childGraph.eventToolExecutionAvailable =
4293
+ childHandlerRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) !=
4294
+ null;
4295
+ return childGraph;
4296
+ };
4047
4297
  const executor = new SubagentExecutor({
4048
- configs: new Map(resolvedConfigs.map((c) => [c.type, c])),
4298
+ configs: new Map(
4299
+ executableConfigs.map((config) => [config.type, config])
4300
+ ),
4049
4301
  parentSignal: this.signal,
4050
4302
  breakerScope: {
4051
4303
  controller: (): AbortController => this.breakerAbort,
@@ -4057,48 +4309,22 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4057
4309
  parentHandlerRegistry: getParentHandlerRegistry,
4058
4310
  parentRunId: this.runId ?? '',
4059
4311
  parentAgentId: agentContext.agentId,
4312
+ executionContext: this.subagentExecutionContext,
4060
4313
  langfuse: this.langfuse,
4061
4314
  tokenCounter: agentContext.tokenCounter,
4062
4315
  usageSink: this.subagentUsageSink,
4063
4316
  streamLimits: this.streamLimits,
4317
+ humanInTheLoop: this.humanInTheLoop,
4318
+ checkpointer: this.compileOptions?.checkpointer,
4064
4319
  maxDepth: effectiveSubagentDepth,
4065
- createChildGraph: (input): StandardGraph => {
4066
- const childGraph = new StandardGraph(input);
4067
- if (this.subagentModelOverride != null) {
4068
- childGraph.overrideModel = this.subagentModelOverride;
4069
- childGraph.setSubagentModelOverride(this.subagentModelOverride);
4070
- }
4071
- const toolHandlerRegistry = createToolHandlerRegistry(
4072
- getParentHandlerRegistry()
4073
- );
4074
- childGraph.hookRegistry = this.hookRegistry;
4075
- /**
4076
- * Do not propagate `humanInTheLoop` into the child graph yet:
4077
- * nested subagent interrupts need a stable child checkpoint and
4078
- * resume bridge. Child hooks still fire; `ask` decisions fail
4079
- * closed inside the subagent until that flow is implemented.
4080
- */
4081
- childGraph.toolOutputReferences = this.toolOutputReferences;
4082
- childGraph.eagerEventToolExecution = this.eagerEventToolExecution;
4083
- childGraph.codeSessionToolNames = this.codeSessionToolNames;
4084
- // Pure execution-ordering hint (unlike `humanInTheLoop` above).
4085
- // It ONLY reorders tools already in the child's direct group;
4086
- // it does not force a name onto the direct path (that fold-in
4087
- // was removed — Codex review of #294). So for a self-spawned
4088
- // child that scrubs inherited `graphTools` (keeping only the
4089
- // event `toolDefinition` / schema-only stub for a name like
4090
- // `ask_user_question`), the name isn't in the child's direct
4091
- // group and this is a no-op — the stub is still dispatched via
4092
- // ON_TOOL_EXECUTE, never invoked directly. Where the child DOES
4093
- // have the executable graphTool, the guard correctly applies.
4094
- childGraph.interruptingToolNames = this.interruptingToolNames;
4095
- childGraph.toolExecution = this.toolExecution;
4096
- childGraph.parentToolHandlerRegistry = toolHandlerRegistry;
4097
- childGraph.eventToolExecutionAvailable =
4098
- toolHandlerRegistry != null;
4099
- return childGraph;
4100
- },
4320
+ createChildGraph: (input): StandardGraph =>
4321
+ createConfiguredChildGraph({
4322
+ kind: 'standard',
4323
+ input,
4324
+ }),
4325
+ createChildGraphByKind: createConfiguredChildGraph,
4101
4326
  });
4327
+ this.registerSubagentExecutor(executor);
4102
4328
 
4103
4329
  const subagentTool = tool(async (rawInput, config) => {
4104
4330
  const input = rawInput as {
@@ -4109,24 +4335,26 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4109
4335
  typeof input.description === 'string' &&
4110
4336
  input.description.trim().length > 0
4111
4337
  ? input.description
4112
- : 'No task description provided';
4338
+ : DEFAULT_SUBAGENT_DESCRIPTION;
4113
4339
  const subagentType =
4114
4340
  typeof input.subagent_type === 'string' ? input.subagent_type : '';
4115
4341
  const threadId = config.configurable?.thread_id as string | undefined;
4116
- /**
4117
- * When the tool is dispatched from an LLM's `tool_call`, LangChain
4118
- * threads the originating `ToolCall` onto the RunnableConfig as
4119
- * `config.toolCall` (see `ToolRunnableConfig` in
4120
- * `@langchain/core/tools` internal but stable since ≥0.3.x).
4121
- * Surfacing its id lets hosts correlate `SubagentUpdateEvent`s
4122
- * back to the parent's `tool_call_id` deterministically — no
4123
- * temporal heuristics needed. If a future LangChain version
4124
- * changes the threading, the type-guarded read falls back to
4125
- * `undefined` and the correlation degrades gracefully.
4126
- */
4127
- const toolCall = (config as { toolCall?: { id?: string } }).toolCall;
4128
- const parentToolCallId =
4129
- typeof toolCall?.id === 'string' ? toolCall.id : undefined;
4342
+ /** Surface the parent call id so child checkpoints, interrupts, and
4343
+ * update events remain correlated across replay and resume. */
4344
+ const toolRuntime = config as {
4345
+ toolCallId?: string;
4346
+ toolCall?: { id?: string };
4347
+ };
4348
+ const toolCall = toolRuntime.toolCall;
4349
+ let parentToolCallId: string | undefined;
4350
+ if (
4351
+ typeof toolRuntime.toolCallId === 'string' &&
4352
+ toolRuntime.toolCallId !== ''
4353
+ ) {
4354
+ parentToolCallId = toolRuntime.toolCallId;
4355
+ } else if (typeof toolCall?.id === 'string' && toolCall.id !== '') {
4356
+ parentToolCallId = toolCall.id;
4357
+ }
4130
4358
  /** The parent tool batch's entry-captured scope (stamped by
4131
4359
  * ToolNode before PreToolUse hooks) — binds this child to the
4132
4360
  * run that dispatched it, not to whatever controller a reset
@@ -4138,6 +4366,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4138
4366
  description,
4139
4367
  subagentType,
4140
4368
  threadId,
4369
+ signal: config.signal,
4141
4370
  parentToolCallId,
4142
4371
  breaker: batchScope?.controller,
4143
4372
  /**
@@ -4151,7 +4380,23 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4151
4380
  | undefined,
4152
4381
  });
4153
4382
  return result.content;
4154
- }, buildSubagentToolParams(resolvedConfigs));
4383
+ }, buildSubagentToolParams(executableConfigs));
4384
+ const replayableSubagentTool = subagentTool as typeof subagentTool &
4385
+ ReplayableSubagentTool;
4386
+ replayableSubagentTool[SUBAGENT_REPLAY_CONTROLLER] = {
4387
+ getResumeManifest: (
4388
+ parentToolCallIds,
4389
+ config
4390
+ ): Promise<SubagentResumeManifest | undefined> =>
4391
+ executor.getResumeManifest(parentToolCallIds, config),
4392
+ getSettledOutput: (
4393
+ call,
4394
+ config
4395
+ ): Promise<SettledSubagentToolOutput | undefined> =>
4396
+ executor.getSettledToolOutput(call, config),
4397
+ persistSettledOutput: (call, config, output): Promise<void> =>
4398
+ executor.persistSettledToolOutput(call, config, output),
4399
+ };
4155
4400
 
4156
4401
  if (!agentContext.graphTools) {
4157
4402
  agentContext.graphTools = [];
@@ -4399,7 +4644,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4399
4644
  const StateAnnotation = Annotation.Root({
4400
4645
  messages: Annotation<BaseMessage[]>({
4401
4646
  reducer: (a, b) => {
4402
- if (!this.messages.length) {
4647
+ if (!this.messages.length && !this.hasRestoredCheckpointMessages) {
4403
4648
  this.startIndex = a.length + b.length;
4404
4649
  }
4405
4650
  const result = messagesStateReducer(a, b);