@librechat/agents 3.3.13 → 3.4.1

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 (192) hide show
  1. package/dist/cjs/graphs/Graph.cjs +121 -27
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hooks/HookRegistry.cjs +83 -0
  4. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  5. package/dist/cjs/hooks/executeHooks.cjs +60 -14
  6. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  7. package/dist/cjs/hooks/index.cjs.map +1 -1
  8. package/dist/cjs/hooks/types.cjs +2 -0
  9. package/dist/cjs/hooks/types.cjs.map +1 -1
  10. package/dist/cjs/llm/anthropic/index.cjs +35 -206
  11. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  12. package/dist/cjs/llm/bedrock/index.cjs +121 -241
  13. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  14. package/dist/cjs/llm/google/index.cjs +19 -12
  15. package/dist/cjs/llm/google/index.cjs.map +1 -1
  16. package/dist/cjs/llm/init.cjs +3 -3
  17. package/dist/cjs/llm/invoke.cjs +2 -2
  18. package/dist/cjs/llm/mistral/index.cjs +26 -0
  19. package/dist/cjs/llm/mistral/index.cjs.map +1 -0
  20. package/dist/cjs/llm/openai/index.cjs +82 -80
  21. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  22. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  23. package/dist/cjs/llm/providers.cjs +19 -19
  24. package/dist/cjs/llm/providers.cjs.map +1 -1
  25. package/dist/cjs/llm/stream/chunkAdapters.cjs +198 -0
  26. package/dist/cjs/llm/stream/chunkAdapters.cjs.map +1 -0
  27. package/dist/cjs/llm/stream/smoother.cjs +369 -0
  28. package/dist/cjs/llm/stream/smoother.cjs.map +1 -0
  29. package/dist/cjs/llm/vertexai/index.cjs +13 -1
  30. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  31. package/dist/cjs/main.cjs +25 -16
  32. package/dist/cjs/messages/format.cjs +40 -23
  33. package/dist/cjs/messages/format.cjs.map +1 -1
  34. package/dist/cjs/run.cjs +82 -15
  35. package/dist/cjs/run.cjs.map +1 -1
  36. package/dist/cjs/session/AgentSession.cjs +37 -2
  37. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  38. package/dist/cjs/stream.cjs +1 -1
  39. package/dist/cjs/summarization/node.cjs +6 -3
  40. package/dist/cjs/summarization/node.cjs.map +1 -1
  41. package/dist/cjs/tools/BashExecutor.cjs +1 -1
  42. package/dist/cjs/tools/CodeExecutor.cjs +1 -1
  43. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +1 -1
  44. package/dist/cjs/tools/ToolNode.cjs +316 -94
  45. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  46. package/dist/cjs/tools/ToolSearch.cjs +1 -1
  47. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +664 -80
  48. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  49. package/dist/cjs/tools/subagent/SubagentReplay.cjs +175 -0
  50. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -0
  51. package/dist/cjs/tools/toolOutputReferences.cjs +20 -0
  52. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  53. package/dist/cjs/utils/index.cjs +2 -2
  54. package/dist/cjs/utils/tokens.cjs +76 -29
  55. package/dist/cjs/utils/tokens.cjs.map +1 -1
  56. package/dist/esm/graphs/Graph.mjs +121 -27
  57. package/dist/esm/graphs/Graph.mjs.map +1 -1
  58. package/dist/esm/hooks/HookRegistry.mjs +83 -0
  59. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  60. package/dist/esm/hooks/executeHooks.mjs +60 -14
  61. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  62. package/dist/esm/hooks/index.mjs.map +1 -1
  63. package/dist/esm/hooks/types.mjs +2 -1
  64. package/dist/esm/hooks/types.mjs.map +1 -1
  65. package/dist/esm/llm/anthropic/index.mjs +34 -205
  66. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  67. package/dist/esm/llm/bedrock/index.mjs +120 -240
  68. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  69. package/dist/esm/llm/google/index.mjs +19 -12
  70. package/dist/esm/llm/google/index.mjs.map +1 -1
  71. package/dist/esm/llm/init.mjs +1 -1
  72. package/dist/esm/llm/invoke.mjs +2 -2
  73. package/dist/esm/llm/mistral/index.mjs +26 -0
  74. package/dist/esm/llm/mistral/index.mjs.map +1 -0
  75. package/dist/esm/llm/openai/index.mjs +82 -80
  76. package/dist/esm/llm/openai/index.mjs.map +1 -1
  77. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  78. package/dist/esm/llm/providers.mjs +3 -3
  79. package/dist/esm/llm/providers.mjs.map +1 -1
  80. package/dist/esm/llm/stream/chunkAdapters.mjs +195 -0
  81. package/dist/esm/llm/stream/chunkAdapters.mjs.map +1 -0
  82. package/dist/esm/llm/stream/smoother.mjs +365 -0
  83. package/dist/esm/llm/stream/smoother.mjs.map +1 -0
  84. package/dist/esm/llm/vertexai/index.mjs +13 -1
  85. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  86. package/dist/esm/main.mjs +13 -11
  87. package/dist/esm/messages/format.mjs +40 -23
  88. package/dist/esm/messages/format.mjs.map +1 -1
  89. package/dist/esm/run.mjs +83 -16
  90. package/dist/esm/run.mjs.map +1 -1
  91. package/dist/esm/session/AgentSession.mjs +37 -2
  92. package/dist/esm/session/AgentSession.mjs.map +1 -1
  93. package/dist/esm/stream.mjs +1 -1
  94. package/dist/esm/summarization/node.mjs +6 -3
  95. package/dist/esm/summarization/node.mjs.map +1 -1
  96. package/dist/esm/tools/BashExecutor.mjs +1 -1
  97. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  98. package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
  99. package/dist/esm/tools/ToolNode.mjs +317 -95
  100. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  101. package/dist/esm/tools/ToolSearch.mjs +1 -1
  102. package/dist/esm/tools/subagent/SubagentExecutor.mjs +665 -81
  103. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  104. package/dist/esm/tools/subagent/SubagentReplay.mjs +168 -0
  105. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -0
  106. package/dist/esm/tools/toolOutputReferences.mjs +20 -0
  107. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  108. package/dist/esm/utils/index.mjs +2 -2
  109. package/dist/esm/utils/tokens.mjs +76 -30
  110. package/dist/esm/utils/tokens.mjs.map +1 -1
  111. package/dist/types/graphs/Graph.d.ts +19 -1
  112. package/dist/types/hooks/HookRegistry.d.ts +10 -1
  113. package/dist/types/hooks/executeHooks.d.ts +5 -1
  114. package/dist/types/hooks/index.d.ts +2 -2
  115. package/dist/types/hooks/types.d.ts +12 -0
  116. package/dist/types/index.d.ts +3 -0
  117. package/dist/types/llm/google/index.d.ts +2 -0
  118. package/dist/types/llm/mistral/index.d.ts +11 -0
  119. package/dist/types/llm/openai/index.d.ts +20 -4
  120. package/dist/types/llm/openrouter/index.d.ts +4 -1
  121. package/dist/types/llm/stream/chunkAdapters.d.ts +48 -0
  122. package/dist/types/llm/stream/smoother.d.ts +95 -0
  123. package/dist/types/llm/vertexai/index.d.ts +2 -0
  124. package/dist/types/run.d.ts +4 -1
  125. package/dist/types/session/AgentSession.d.ts +1 -0
  126. package/dist/types/tools/ToolNode.d.ts +23 -15
  127. package/dist/types/tools/subagent/SubagentExecutor.d.ts +49 -7
  128. package/dist/types/tools/subagent/SubagentReplay.d.ts +81 -0
  129. package/dist/types/tools/toolOutputReferences.d.ts +12 -0
  130. package/dist/types/types/graph.d.ts +5 -5
  131. package/dist/types/types/hitl.d.ts +15 -0
  132. package/dist/types/types/llm.d.ts +21 -14
  133. package/dist/types/utils/tokens.d.ts +10 -0
  134. package/package.json +6 -2
  135. package/src/graphs/Graph.ts +257 -52
  136. package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +48 -9
  137. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1 -1
  138. package/src/graphs/__tests__/Graph.subagentResumeState.test.ts +80 -0
  139. package/src/hooks/HookRegistry.ts +151 -1
  140. package/src/hooks/__tests__/HookRegistry.test.ts +96 -0
  141. package/src/hooks/__tests__/executeHooks.test.ts +160 -0
  142. package/src/hooks/executeHooks.ts +133 -15
  143. package/src/hooks/index.ts +3 -1
  144. package/src/hooks/types.ts +16 -0
  145. package/src/index.ts +8 -0
  146. package/src/llm/anthropic/index.ts +85 -354
  147. package/src/llm/bedrock/index.ts +174 -390
  148. package/src/llm/bedrock/llm.spec.ts +2 -0
  149. package/src/llm/bedrock/streamSealDispatch.test.ts +76 -0
  150. package/src/llm/custom-chat-models.smoke.test.ts +16 -1
  151. package/src/llm/google/index.ts +17 -18
  152. package/src/llm/google/streamSmoothing.test.ts +121 -0
  153. package/src/llm/mistral/index.ts +33 -0
  154. package/src/llm/mistral/streamSmoothing.test.ts +97 -0
  155. package/src/llm/openai/deepseek.test.ts +56 -0
  156. package/src/llm/openai/index.ts +119 -126
  157. package/src/llm/openrouter/index.ts +4 -1
  158. package/src/llm/providers.ts +3 -3
  159. package/src/llm/stream/chunkAdapters.test.ts +202 -0
  160. package/src/llm/stream/chunkAdapters.ts +317 -0
  161. package/src/llm/stream/reassembly.test.ts +241 -0
  162. package/src/llm/stream/smoother.bench.test.ts +155 -0
  163. package/src/llm/stream/smoother.test.ts +519 -0
  164. package/src/llm/stream/smoother.ts +574 -0
  165. package/src/llm/vertexai/index.ts +17 -1
  166. package/src/llm/vertexai/streamSmoothing.test.ts +109 -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 +164 -18
  172. package/src/session/AgentSession.ts +52 -2
  173. package/src/session/__tests__/JsonlSessionStore.test.ts +53 -0
  174. package/src/specs/subagent.test.ts +44 -0
  175. package/src/specs/tokens.test.ts +159 -14
  176. package/src/summarization/__tests__/node.test.ts +32 -14
  177. package/src/summarization/node.ts +15 -11
  178. package/src/tools/ToolNode.ts +631 -157
  179. package/src/tools/__tests__/SubagentExecutor.test.ts +590 -7
  180. package/src/tools/__tests__/SubagentReplay.test.ts +300 -0
  181. package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +208 -2
  182. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +486 -2
  183. package/src/tools/__tests__/hitl.test.ts +234 -5
  184. package/src/tools/__tests__/subagentHooks.test.ts +1040 -62
  185. package/src/tools/__tests__/toolOutputReferences.test.ts +19 -2
  186. package/src/tools/subagent/SubagentExecutor.ts +1438 -136
  187. package/src/tools/subagent/SubagentReplay.ts +575 -0
  188. package/src/tools/toolOutputReferences.ts +40 -1
  189. package/src/types/graph.ts +5 -5
  190. package/src/types/hitl.ts +16 -0
  191. package/src/types/llm.ts +53 -36
  192. package/src/utils/tokens.ts +115 -30
@@ -1,27 +1,206 @@
1
+ const require_SubagentReplay = require("./SubagentReplay.cjs");
1
2
  require("../../common/enum.cjs");
2
3
  require("../../common/index.cjs");
3
4
  const require_streamLimits = require("../../llm/streamLimits.cjs");
4
5
  const require_executeHooks = require("../../hooks/executeHooks.cjs");
6
+ const require_types = require("../../hooks/types.cjs");
5
7
  require("../../hooks/index.cjs");
6
8
  let nanoid = require("nanoid");
7
9
  let _langchain_core_callbacks_base = require("@langchain/core/callbacks/base");
8
10
  let _langchain_core_messages = require("@langchain/core/messages");
11
+ let _langchain_langgraph = require("@langchain/langgraph");
12
+ let _langchain_core_singletons = require("@langchain/core/singletons");
9
13
  //#region src/tools/subagent/SubagentExecutor.ts
10
14
  const DEFAULT_MAX_TURNS = 25;
11
15
  const RECURSION_MULTIPLIER = 3;
12
16
  const ERROR_MESSAGE_MAX_CHARS = 200;
13
17
  const MAX_PENDING_SUBAGENT_UPDATES = 64;
18
+ const TEXT_DELTA_CONTENT_TYPE = `text_delta`;
14
19
  const HOOK_FALLBACK = Object.freeze({
15
20
  additionalContexts: [],
16
21
  injectedMessages: [],
17
22
  errors: []
18
23
  });
19
24
  const LANGGRAPH_RUNTIME_CONFIG_PREFIX = "__pregel_";
25
+ const LANGGRAPH_RESUME_MAP_CONFIG_KEY = "__pregel_resume_map";
20
26
  const LANGGRAPH_CHECKPOINT_CONFIG_KEYS = new Set([
21
27
  "checkpoint_id",
22
28
  "checkpoint_map",
23
29
  "checkpoint_ns"
24
30
  ]);
31
+ const SUBAGENT_CHECKPOINT_MARKER_KEY = "__librechat_subagent_checkpoint";
32
+ const SUBAGENT_HOOK_SESSION_KEY = "__librechat_subagent_hook_session";
33
+ const SUBAGENT_RUN_ID_KEY = "__librechat_subagent_run_id";
34
+ const SUBAGENT_REPLAY_NODE = "subagent-replay";
35
+ function isCheckpointSaver(value) {
36
+ if (value == null || typeof value !== "object") return false;
37
+ const candidate = value;
38
+ return typeof candidate.getTuple === "function" && typeof candidate.list === "function" && typeof candidate.put === "function" && typeof candidate.putWrites === "function" && typeof candidate.deleteThread === "function";
39
+ }
40
+ function isSubagentCheckpointMarker(value) {
41
+ if (value == null || typeof value !== "object") return false;
42
+ const marker = value;
43
+ return marker.version === 1 && marker.lifecycleComplete === true && typeof marker.parentToolCallId === "string" && (marker.hookSessionId == null || typeof marker.hookSessionId === "string") && (marker.childRunId == null || typeof marker.childRunId === "string") && (marker.settledOutput == null || isPersistedToolOutput(marker.settledOutput));
44
+ }
45
+ function isPersistedToolOutput(value) {
46
+ if (value == null || typeof value !== "object") return false;
47
+ const output = value;
48
+ const contentIsValid = typeof output.content === "string" || Array.isArray(output.content);
49
+ const statusIsValid = output.status == null || output.status === "success" || output.status === "error";
50
+ const contextsAreValid = Array.isArray(output.additionalContexts) && output.additionalContexts.every((context) => typeof context === "string");
51
+ const resolvedArgsAreValid = output.resolvedArgs == null || typeof output.resolvedArgs === "object" && !Array.isArray(output.resolvedArgs);
52
+ return contentIsValid && typeof output.toolCallId === "string" && output.additionalKwargs != null && typeof output.additionalKwargs === "object" && output.responseMetadata != null && typeof output.responseMetadata === "object" && statusIsValid && contextsAreValid && resolvedArgsAreValid && (output.referenceContent == null || typeof output.referenceContent === "string");
53
+ }
54
+ function getSubagentCheckpointMarker(messages, parentToolCallId) {
55
+ for (let i = messages.length - 1; i >= 0; i--) {
56
+ const marker = messages[i].additional_kwargs[SUBAGENT_CHECKPOINT_MARKER_KEY];
57
+ if (isSubagentCheckpointMarker(marker) && marker.parentToolCallId === parentToolCallId) return marker;
58
+ }
59
+ }
60
+ function getSubagentHookSessionId(messages) {
61
+ for (let i = messages.length - 1; i >= 0; i--) {
62
+ const sessionId = messages[i].additional_kwargs[SUBAGENT_HOOK_SESSION_KEY];
63
+ if (typeof sessionId === "string" && sessionId.length > 0) return sessionId;
64
+ }
65
+ }
66
+ function getSubagentRunId(messages) {
67
+ for (let i = messages.length - 1; i >= 0; i--) {
68
+ const runId = messages[i].additional_kwargs[SUBAGENT_RUN_ID_KEY];
69
+ if (typeof runId === "string" && runId.length > 0) return runId;
70
+ const marker = messages[i].additional_kwargs[SUBAGENT_CHECKPOINT_MARKER_KEY];
71
+ if (isSubagentCheckpointMarker(marker) && marker.childRunId != null && marker.childRunId.length > 0) return marker.childRunId;
72
+ }
73
+ }
74
+ function isSubagentCheckpointMarkerMessage(message) {
75
+ return isSubagentCheckpointMarker(message.additional_kwargs[SUBAGENT_CHECKPOINT_MARKER_KEY]);
76
+ }
77
+ function createSubagentCheckpointMarkerMessage(marker) {
78
+ return new _langchain_core_messages.AIMessage({
79
+ content: "",
80
+ additional_kwargs: { [SUBAGENT_CHECKPOINT_MARKER_KEY]: marker }
81
+ });
82
+ }
83
+ function getCheckpointMessages(value) {
84
+ return Array.isArray(value) ? value.filter(_langchain_core_messages.BaseMessage.isInstance) : [];
85
+ }
86
+ function getConfigurableString(config, key) {
87
+ const value = config?.configurable?.[key];
88
+ return typeof value === "string" && value.length > 0 ? value : void 0;
89
+ }
90
+ function getTupleMessages(tuple) {
91
+ return getCheckpointMessages(tuple?.checkpoint.channel_values.messages);
92
+ }
93
+ function getCheckpointReference(tuple) {
94
+ const configurable = tuple?.config.configurable;
95
+ const threadId = configurable?.thread_id;
96
+ const checkpointId = configurable?.checkpoint_id;
97
+ const checkpointNs = configurable?.checkpoint_ns ?? "";
98
+ if (typeof threadId !== "string" || threadId.length === 0 || typeof checkpointId !== "string" || checkpointId.length === 0 || typeof checkpointNs !== "string") return;
99
+ return {
100
+ threadId,
101
+ checkpointId,
102
+ checkpointNs
103
+ };
104
+ }
105
+ function serializeToolOutput(settled) {
106
+ const { output } = settled;
107
+ return {
108
+ content: output.content,
109
+ toolCallId: output.tool_call_id,
110
+ ...output.id == null ? {} : { id: output.id },
111
+ ...output.name == null ? {} : { name: output.name },
112
+ ...output.status == null ? {} : { status: output.status },
113
+ additionalKwargs: output.additional_kwargs,
114
+ responseMetadata: output.response_metadata,
115
+ ...output.metadata == null ? {} : { metadata: output.metadata },
116
+ additionalContexts: settled.additionalContexts,
117
+ ...settled.resolvedArgs == null ? {} : { resolvedArgs: settled.resolvedArgs },
118
+ ...settled.referenceContent == null ? {} : { referenceContent: settled.referenceContent }
119
+ };
120
+ }
121
+ function deserializeToolOutput(output) {
122
+ return {
123
+ output: new _langchain_core_messages.ToolMessage({
124
+ content: output.content,
125
+ tool_call_id: output.toolCallId,
126
+ ...output.id == null ? {} : { id: output.id },
127
+ ...output.name == null ? {} : { name: output.name },
128
+ ...output.status == null ? {} : { status: output.status },
129
+ additional_kwargs: output.additionalKwargs,
130
+ response_metadata: output.responseMetadata,
131
+ ...output.metadata == null ? {} : { metadata: output.metadata }
132
+ }),
133
+ additionalContexts: output.additionalContexts,
134
+ ...output.resolvedArgs == null ? {} : { resolvedArgs: output.resolvedArgs },
135
+ ...output.referenceContent == null ? {} : { referenceContent: output.referenceContent }
136
+ };
137
+ }
138
+ function getParentCheckpointFork(configurable) {
139
+ const checkpointId = configurable?.checkpoint_id;
140
+ return typeof checkpointId === "string" && checkpointId.length > 0 ? checkpointId : "root";
141
+ }
142
+ function getResumeAttemptId(configurable, fallback) {
143
+ const attemptId = configurable?.[require_SubagentReplay.SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
144
+ return typeof attemptId === "string" && attemptId.length > 0 ? attemptId : fallback;
145
+ }
146
+ function getApprovalExecutionScope(childRunId, resumeAttemptId) {
147
+ return `subagent-approval:${Buffer.from(JSON.stringify([childRunId, resumeAttemptId])).toString("base64url")}`;
148
+ }
149
+ function getChildThreadId(args) {
150
+ const durableParentId = args.threadId ?? args.parentRunId;
151
+ const parentFork = getParentCheckpointFork(args.parentConfigurable);
152
+ const parentBatch = args.parentConfigurable?.[require_SubagentReplay.SUBAGENT_PARENT_BATCH_CONFIG_KEY];
153
+ const identity = [
154
+ durableParentId,
155
+ parentFork,
156
+ args.parentAgentId ?? "agent",
157
+ args.parentToolCallId,
158
+ typeof parentBatch === "string" && parentBatch.length > 0 ? parentBatch : "batch"
159
+ ];
160
+ if (args.branchId != null) identity.push(args.branchId);
161
+ return `subagent:${Buffer.from(JSON.stringify(identity)).toString("base64url")}`;
162
+ }
163
+ function isToolApprovalPayload(value) {
164
+ return value != null && typeof value === "object" && value.type === "tool_approval";
165
+ }
166
+ function addSubagentScope(interrupts, scope, resumeManifest) {
167
+ return interrupts.map((childInterrupt) => {
168
+ const payload = isToolApprovalPayload(childInterrupt.value) ? {
169
+ ...childInterrupt.value,
170
+ subagent: childInterrupt.value.subagent ?? scope
171
+ } : childInterrupt.value;
172
+ return {
173
+ ...childInterrupt,
174
+ value: resumeManifest == null ? payload : require_SubagentReplay.attachSubagentResumeManifest(payload, resumeManifest)
175
+ };
176
+ });
177
+ }
178
+ function getChildResumeMap(pendingInterrupts, parentConfigurable) {
179
+ const resumeMap = parentConfigurable?.[LANGGRAPH_RESUME_MAP_CONFIG_KEY];
180
+ if (resumeMap == null || typeof resumeMap !== "object") return;
181
+ const parentResumeMap = resumeMap;
182
+ const childResumeMap = {};
183
+ for (const childInterrupt of pendingInterrupts) {
184
+ const interruptId = childInterrupt.id;
185
+ if (typeof interruptId === "string" && Object.prototype.hasOwnProperty.call(parentResumeMap, interruptId)) childResumeMap[interruptId] = parentResumeMap[interruptId];
186
+ }
187
+ return Object.keys(childResumeMap).length > 0 ? childResumeMap : void 0;
188
+ }
189
+ function getPersistedInterrupts(snapshot) {
190
+ const interrupts = [];
191
+ for (const task of snapshot.tasks) for (const pendingInterrupt of task.interrupts) interrupts.push(pendingInterrupt);
192
+ return interrupts;
193
+ }
194
+ function getPersistedMessages(snapshot) {
195
+ if (snapshot.values == null || typeof snapshot.values !== "object") return;
196
+ const values = snapshot.values;
197
+ if (!Array.isArray(values.messages) || values.messages.length === 0) return;
198
+ const messages = values.messages.filter((message) => !isSubagentCheckpointMarkerMessage(message));
199
+ return messages.length > 0 ? messages : void 0;
200
+ }
201
+ function createReplayCheckpointWorkflow(checkpointer) {
202
+ return new _langchain_langgraph.StateGraph(_langchain_langgraph.MessagesAnnotation).addNode(SUBAGENT_REPLAY_NODE, (state) => state).addEdge(_langchain_langgraph.START, SUBAGENT_REPLAY_NODE).addEdge(SUBAGENT_REPLAY_NODE, _langchain_langgraph.END).compile({ checkpointer });
203
+ }
25
204
  var SubagentExecutor = class {
26
205
  configs;
27
206
  parentSignal;
@@ -39,9 +218,18 @@ var SubagentExecutor = class {
39
218
  langfuse;
40
219
  tokenCounter;
41
220
  streamLimits;
221
+ humanInTheLoop;
222
+ checkpointer;
42
223
  maxDepth;
43
224
  createChildGraph;
44
225
  usageSink;
226
+ checkpointThreadIds = /* @__PURE__ */ new Set();
227
+ startedChildRuns = /* @__PURE__ */ new Set();
228
+ completedChildRuns = /* @__PURE__ */ new Set();
229
+ completedChildResults = /* @__PURE__ */ new Map();
230
+ childExecutionIdentities = /* @__PURE__ */ new Map();
231
+ activeChildRuns = /* @__PURE__ */ new Map();
232
+ replayCheckpointWorkflow;
45
233
  resolveParentHandlerRegistry;
46
234
  constructor(options) {
47
235
  this.configs = options.configs;
@@ -53,6 +241,8 @@ var SubagentExecutor = class {
53
241
  this.langfuse = options.langfuse;
54
242
  this.tokenCounter = options.tokenCounter;
55
243
  this.streamLimits = options.streamLimits;
244
+ this.humanInTheLoop = options.humanInTheLoop;
245
+ this.checkpointer = isCheckpointSaver(options.checkpointer) ? options.checkpointer : void 0;
56
246
  this.maxDepth = options.maxDepth ?? 1;
57
247
  this.createChildGraph = options.createChildGraph;
58
248
  this.usageSink = options.usageSink;
@@ -77,6 +267,274 @@ var SubagentExecutor = class {
77
267
  getParentHandlerRegistry() {
78
268
  return this.resolveParentHandlerRegistry?.();
79
269
  }
270
+ /**
271
+ * Keeps the original child thread as an immutable resume source once a
272
+ * different parent Run reconstructs it. Each rebuilt parent gets a private
273
+ * checkpoint fork, while the persisted child run ID remains stable for
274
+ * activity and usage correlation across the interrupt boundary.
275
+ */
276
+ async resolveChildExecutionIdentity(params) {
277
+ const currentChildRunId = `${this.parentRunId}_sub_${params.parentToolCallId}`;
278
+ const resumeAttemptId = getResumeAttemptId(params.parentConfigurable, this.parentRunId);
279
+ const baseChildThreadId = getChildThreadId({
280
+ parentRunId: this.parentRunId,
281
+ parentAgentId: this.parentAgentId,
282
+ threadId: params.threadId,
283
+ parentToolCallId: params.parentToolCallId,
284
+ parentConfigurable: params.parentConfigurable
285
+ });
286
+ if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null) return {
287
+ childRunId: currentChildRunId,
288
+ childThreadId: baseChildThreadId,
289
+ approvalExecutionScope: currentChildRunId
290
+ };
291
+ const resumeExecution = require_SubagentReplay.getSubagentResumeManifest(params.parentConfigurable)?.executions.find((execution) => execution.parentToolCallId === params.parentToolCallId);
292
+ const branchChildThreadId = getChildThreadId({
293
+ parentRunId: this.parentRunId,
294
+ parentAgentId: this.parentAgentId,
295
+ threadId: params.threadId,
296
+ parentToolCallId: params.parentToolCallId,
297
+ parentConfigurable: params.parentConfigurable,
298
+ branchId: resumeAttemptId
299
+ });
300
+ if (resumeExecution != null) {
301
+ const approvalExecutionScope = getApprovalExecutionScope(resumeExecution.childRunId, resumeAttemptId);
302
+ await this.forkCheckpointSnapshot(resumeExecution.checkpoints, branchChildThreadId);
303
+ this.hookRegistry?.restorePendingToolApprovals(approvalExecutionScope, approvalExecutionScope, resumeExecution.approvalReplays);
304
+ this.checkpointThreadIds.add(branchChildThreadId);
305
+ return {
306
+ childRunId: resumeExecution.childRunId,
307
+ childThreadId: branchChildThreadId,
308
+ approvalExecutionScope,
309
+ resumeExecution
310
+ };
311
+ }
312
+ const branchTuple = await this.checkpointer.getTuple({ configurable: { thread_id: branchChildThreadId } });
313
+ if (branchTuple != null) {
314
+ const childRunId = getSubagentRunId(getTupleMessages(branchTuple)) ?? currentChildRunId;
315
+ this.checkpointThreadIds.add(branchChildThreadId);
316
+ return {
317
+ childRunId,
318
+ childThreadId: branchChildThreadId,
319
+ approvalExecutionScope: getApprovalExecutionScope(childRunId, resumeAttemptId)
320
+ };
321
+ }
322
+ const baseTuple = await this.checkpointer.getTuple({ configurable: { thread_id: baseChildThreadId } });
323
+ const persistedChildRunId = getSubagentRunId(getTupleMessages(baseTuple));
324
+ if (baseTuple == null || persistedChildRunId == null || persistedChildRunId === currentChildRunId) {
325
+ this.checkpointThreadIds.add(baseChildThreadId);
326
+ return {
327
+ childRunId: persistedChildRunId ?? currentChildRunId,
328
+ childThreadId: baseChildThreadId,
329
+ approvalExecutionScope: getApprovalExecutionScope(persistedChildRunId ?? currentChildRunId, resumeAttemptId)
330
+ };
331
+ }
332
+ const sourceCheckpoints = await this.getLatestCheckpointSnapshot(baseChildThreadId);
333
+ if (sourceCheckpoints.length === 0) throw new Error(`Cannot fork subagent checkpoint thread "${baseChildThreadId}" without a checkpoint ID.`);
334
+ await this.forkCheckpointSnapshot(sourceCheckpoints, branchChildThreadId);
335
+ this.checkpointThreadIds.add(branchChildThreadId);
336
+ return {
337
+ childRunId: persistedChildRunId,
338
+ childThreadId: branchChildThreadId,
339
+ approvalExecutionScope: getApprovalExecutionScope(persistedChildRunId, resumeAttemptId)
340
+ };
341
+ }
342
+ /** Captures one exact checkpoint head per namespace for a child thread. */
343
+ async getLatestCheckpointSnapshot(threadId) {
344
+ if (this.checkpointer == null) return [];
345
+ const checkpointsByNamespace = /* @__PURE__ */ new Map();
346
+ for await (const tuple of this.checkpointer.list({ configurable: { thread_id: threadId } })) {
347
+ const checkpoint = getCheckpointReference(tuple);
348
+ const current = checkpoint == null ? void 0 : checkpointsByNamespace.get(checkpoint.checkpointNs);
349
+ if (checkpoint != null && (current == null || checkpoint.checkpointId.localeCompare(current.checkpointId) > 0)) checkpointsByNamespace.set(checkpoint.checkpointNs, checkpoint);
350
+ }
351
+ return [...checkpointsByNamespace.values()].sort((left, right) => left.checkpointNs.localeCompare(right.checkpointNs));
352
+ }
353
+ /** Copies exact checkpoint lineages, including pending task writes. */
354
+ async forkCheckpointSnapshot(sources, targetThreadId) {
355
+ if (this.checkpointer == null || sources.length === 0 || sources.every((source) => source.threadId === targetThreadId)) return;
356
+ for (const source of sources) {
357
+ const tuples = [];
358
+ const visited = /* @__PURE__ */ new Set();
359
+ let tuple = await this.checkpointer.getTuple({ configurable: {
360
+ thread_id: source.threadId,
361
+ checkpoint_ns: source.checkpointNs,
362
+ checkpoint_id: source.checkpointId
363
+ } });
364
+ if (tuple == null) throw new Error(`Subagent checkpoint "${source.checkpointId}" is unavailable.`);
365
+ for (;;) {
366
+ const reference = getCheckpointReference(tuple);
367
+ if (reference == null) throw new Error("Subagent checkpoint lineage contains an invalid tuple.");
368
+ if (reference.threadId !== source.threadId || reference.checkpointNs !== source.checkpointNs) throw new Error("Subagent checkpoint lineage escapes its source namespace.");
369
+ const lineageKey = JSON.stringify([
370
+ reference.threadId,
371
+ reference.checkpointNs,
372
+ reference.checkpointId
373
+ ]);
374
+ if (visited.has(lineageKey)) throw new Error("Subagent checkpoint lineage contains a cycle.");
375
+ visited.add(lineageKey);
376
+ tuples.push(tuple);
377
+ if (tuple.parentConfig == null) break;
378
+ tuple = await this.checkpointer.getTuple(tuple.parentConfig);
379
+ if (tuple == null) throw new Error("Subagent checkpoint lineage is incomplete.");
380
+ }
381
+ tuples.reverse();
382
+ for (const lineageTuple of tuples) {
383
+ const checkpointNs = getConfigurableString(lineageTuple.config, "checkpoint_ns") ?? "";
384
+ const parentCheckpointId = getConfigurableString(lineageTuple.parentConfig, "checkpoint_id");
385
+ const storedConfig = await this.checkpointer.put({ configurable: {
386
+ thread_id: targetThreadId,
387
+ checkpoint_ns: checkpointNs,
388
+ ...parentCheckpointId == null ? {} : { checkpoint_id: parentCheckpointId }
389
+ } }, (0, _langchain_langgraph.copyCheckpoint)(lineageTuple.checkpoint), lineageTuple.metadata ?? {
390
+ source: "fork",
391
+ step: -1,
392
+ parents: {}
393
+ }, lineageTuple.checkpoint.channel_versions);
394
+ const writesByTask = /* @__PURE__ */ new Map();
395
+ for (const [taskId, channel, value] of lineageTuple.pendingWrites ?? []) {
396
+ let writes = writesByTask.get(taskId);
397
+ if (writes == null) {
398
+ writes = [];
399
+ writesByTask.set(taskId, writes);
400
+ }
401
+ writes.push([channel, value]);
402
+ }
403
+ for (const [taskId, writes] of writesByTask) await this.checkpointer.putWrites(storedConfig, writes, taskId);
404
+ }
405
+ }
406
+ }
407
+ async createResumeManifest(parentToolCallIds) {
408
+ if (this.checkpointer == null) return;
409
+ const executions = [];
410
+ for (const [parentToolCallId, identity] of this.childExecutionIdentities) {
411
+ if (parentToolCallIds != null && !parentToolCallIds.has(parentToolCallId)) continue;
412
+ const checkpoints = await this.getLatestCheckpointSnapshot(identity.childThreadId);
413
+ if (checkpoints.length === 0) continue;
414
+ const activeChildRun = this.activeChildRuns.get(identity.childThreadId);
415
+ const configuredHookSessionId = activeChildRun?.invokeConfig?.configurable?.run_id;
416
+ const hookSessionId = typeof configuredHookSessionId === "string" && configuredHookSessionId.length > 0 ? configuredHookSessionId : this.parentRunId;
417
+ const approvalReplays = this.hookRegistry?.snapshotPendingToolApprovals(identity.approvalExecutionScope, identity.approvalExecutionScope) ?? [];
418
+ const descendant = activeChildRun?.pendingInterrupts.map((pendingInterrupt) => require_SubagentReplay.getSubagentResumeManifest(pendingInterrupt.value)).find((manifest) => manifest != null);
419
+ const graphState = activeChildRun?.graph.createSubagentResumeState(hookSessionId) ?? {
420
+ toolCallSteps: [],
421
+ toolSessions: [],
422
+ toolNodes: [],
423
+ eagerToolUsage: [],
424
+ eagerToolSuppressions: []
425
+ };
426
+ executions.push({
427
+ parentToolCallId,
428
+ childRunId: identity.childRunId,
429
+ approvalExecutionScope: identity.approvalExecutionScope,
430
+ checkpoints,
431
+ graphState,
432
+ approvalReplays,
433
+ ...descendant == null ? {} : { descendant }
434
+ });
435
+ }
436
+ if (executions.length === 0) return;
437
+ return {
438
+ version: 1,
439
+ executions
440
+ };
441
+ }
442
+ getResumeManifest(parentToolCallIds) {
443
+ return this.createResumeManifest(parentToolCallIds);
444
+ }
445
+ getChildCheckpointThreadIds() {
446
+ const threadIds = new Set(this.checkpointThreadIds);
447
+ for (const activeChildRun of this.activeChildRuns.values()) for (const threadId of this.getGraphChildCheckpointThreadIds(activeChildRun.graph)) threadIds.add(threadId);
448
+ return [...threadIds];
449
+ }
450
+ resetCheckpointThreadIds() {
451
+ this.checkpointThreadIds.clear();
452
+ }
453
+ getGraphChildCheckpointThreadIds(graph) {
454
+ return graph.getChildCheckpointThreadIds?.() ?? [];
455
+ }
456
+ clearChildGraph(graph) {
457
+ for (const threadId of this.getGraphChildCheckpointThreadIds(graph)) this.checkpointThreadIds.add(threadId);
458
+ graph.clearHeavyState();
459
+ }
460
+ clearHeavyState() {
461
+ for (const activeChildRun of this.activeChildRuns.values()) this.clearChildGraph(activeChildRun.graph);
462
+ this.activeChildRuns.clear();
463
+ this.completedChildResults.clear();
464
+ for (const identity of this.childExecutionIdentities.values()) this.hookRegistry?.clearSession(identity.approvalExecutionScope);
465
+ this.childExecutionIdentities.clear();
466
+ this.startedChildRuns.clear();
467
+ this.completedChildRuns.clear();
468
+ this.replayCheckpointWorkflow = void 0;
469
+ }
470
+ async getSettledToolOutput(call, config) {
471
+ const parentToolCallId = call.id;
472
+ if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null || parentToolCallId == null || parentToolCallId === "") return;
473
+ const parentConfigurable = config.configurable;
474
+ const threadId = parentConfigurable?.thread_id;
475
+ const { childRunId, childThreadId, approvalExecutionScope } = await this.resolveChildExecutionIdentity({
476
+ threadId: typeof threadId === "string" ? threadId : void 0,
477
+ parentToolCallId,
478
+ parentConfigurable
479
+ });
480
+ this.childExecutionIdentities.set(parentToolCallId, {
481
+ childRunId,
482
+ childThreadId,
483
+ approvalExecutionScope
484
+ });
485
+ this.checkpointThreadIds.add(childThreadId);
486
+ const messages = getCheckpointMessages((await this.checkpointer.getTuple({ configurable: { thread_id: childThreadId } }))?.checkpoint.channel_values.messages);
487
+ const marker = getSubagentCheckpointMarker(messages, parentToolCallId);
488
+ const persistedHookSessionId = marker?.hookSessionId ?? getSubagentHookSessionId(messages);
489
+ const currentHookSessionId = parentConfigurable?.run_id;
490
+ if (persistedHookSessionId != null && typeof currentHookSessionId === "string" && currentHookSessionId.length > 0) this.hookRegistry?.copySession(persistedHookSessionId, currentHookSessionId);
491
+ return marker?.settledOutput == null ? void 0 : deserializeToolOutput(marker.settledOutput);
492
+ }
493
+ async persistSettledToolOutput(call, config, settled) {
494
+ const parentToolCallId = call.id;
495
+ if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null || parentToolCallId == null || parentToolCallId === "") return;
496
+ const parentConfigurable = config.configurable;
497
+ const threadId = parentConfigurable?.thread_id;
498
+ const { childRunId, childThreadId, approvalExecutionScope } = await this.resolveChildExecutionIdentity({
499
+ threadId: typeof threadId === "string" ? threadId : void 0,
500
+ parentToolCallId,
501
+ parentConfigurable
502
+ });
503
+ this.childExecutionIdentities.set(parentToolCallId, {
504
+ childRunId,
505
+ childThreadId,
506
+ approvalExecutionScope
507
+ });
508
+ this.checkpointThreadIds.add(childThreadId);
509
+ const activeChildRun = this.activeChildRuns.get(childThreadId);
510
+ const persistedOutput = serializeToolOutput(settled);
511
+ if (activeChildRun != null) {
512
+ await this.persistChildCheckpointMarker(activeChildRun, parentToolCallId, persistedOutput);
513
+ this.clearChildGraph(activeChildRun.graph);
514
+ this.activeChildRuns.delete(childThreadId);
515
+ return;
516
+ }
517
+ this.replayCheckpointWorkflow ??= createReplayCheckpointWorkflow(this.checkpointer);
518
+ await this.replayCheckpointWorkflow.updateState({ configurable: { thread_id: childThreadId } }, { messages: [createSubagentCheckpointMarkerMessage({
519
+ version: 1,
520
+ parentToolCallId,
521
+ lifecycleComplete: true,
522
+ hookSessionId: typeof parentConfigurable?.run_id === "string" ? parentConfigurable.run_id : this.parentRunId,
523
+ childRunId,
524
+ settledOutput: persistedOutput
525
+ })] }, SUBAGENT_REPLAY_NODE);
526
+ }
527
+ async persistChildCheckpointMarker(activeChildRun, parentToolCallId, settledOutput) {
528
+ if (this.humanInTheLoop?.enabled !== true || activeChildRun.workflow.updateState == null || activeChildRun.invokeConfig == null) return;
529
+ await activeChildRun.workflow.updateState(activeChildRun.invokeConfig, { messages: [createSubagentCheckpointMarkerMessage({
530
+ version: 1,
531
+ parentToolCallId,
532
+ lifecycleComplete: true,
533
+ hookSessionId: typeof activeChildRun.invokeConfig.configurable?.run_id === "string" ? activeChildRun.invokeConfig.configurable.run_id : this.parentRunId,
534
+ childRunId: activeChildRun.childRunId,
535
+ ...settledOutput == null ? {} : { settledOutput }
536
+ })] }, activeChildRun.childAgentId);
537
+ }
80
538
  async execute(params) {
81
539
  const { description, subagentType, threadId, parentToolCallId } = params;
82
540
  /** Captured ONCE per execution, preferring the controller the parent
@@ -97,32 +555,25 @@ var SubagentExecutor = class {
97
555
  content: "Error: Maximum subagent nesting depth exceeded.",
98
556
  messages: []
99
557
  };
100
- const childAgentId = config.agentInputs.agentId || `${this.parentAgentId ?? "agent"}_sub_${(0, nanoid.nanoid)(8)}`;
101
- if (this.hookRegistry?.hasHookFor("SubagentStart", this.parentRunId) === true) {
102
- const hookResult = await require_executeHooks.executeHooks({
103
- registry: this.hookRegistry,
104
- input: {
105
- hook_event_name: "SubagentStart",
106
- runId: this.parentRunId,
107
- threadId,
108
- parentAgentId: this.parentAgentId,
109
- agentId: childAgentId,
110
- agentType: subagentType,
111
- inputs: [new _langchain_core_messages.HumanMessage(description)]
112
- },
113
- sessionId: this.parentRunId,
114
- matchQuery: subagentType
115
- }).catch(() => HOOK_FALLBACK);
116
- /**
117
- * `ask` is treated identically to `deny` in the subagent context:
118
- * subagents are non-interactive, so there is no prompt path for `ask`.
119
- * Both decisions block execution and return a "Blocked" tool result.
120
- */
121
- if (hookResult.decision === "deny" || hookResult.decision === "ask") return {
122
- content: `Blocked: ${hookResult.reason ?? "Blocked by hook"}`,
123
- messages: []
124
- };
125
- }
558
+ if (this.humanInTheLoop?.enabled === true && (parentToolCallId == null || parentToolCallId === "")) return {
559
+ content: "Error: Resumable subagent execution requires a parent tool call ID.",
560
+ messages: []
561
+ };
562
+ const executionSuffix = parentToolCallId ?? (0, nanoid.nanoid)(8);
563
+ const { childRunId, childThreadId, approvalExecutionScope, resumeExecution } = await this.resolveChildExecutionIdentity({
564
+ threadId,
565
+ parentToolCallId: executionSuffix,
566
+ parentConfigurable: params.parentConfigurable
567
+ });
568
+ this.childExecutionIdentities.set(executionSuffix, {
569
+ childRunId,
570
+ childThreadId,
571
+ approvalExecutionScope
572
+ });
573
+ const childExecutionKey = childThreadId;
574
+ const childAgentId = config.agentInputs.agentId || `${this.parentAgentId ?? "agent"}_sub_${executionSuffix}`;
575
+ const completedChildResult = this.completedChildResults.get(childExecutionKey);
576
+ if (completedChildResult != null) return completedChildResult;
126
577
  const parentRegistry = this.getParentHandlerRegistry();
127
578
  const forwardingEnabled = parentRegistry != null;
128
579
  /**
@@ -136,10 +587,10 @@ var SubagentExecutor = class {
136
587
  */
137
588
  const hasToolExecuteHandler = parentRegistry?.getHandler("on_tool_execute") != null;
138
589
  const childInputs = buildChildInputs(config, childAgentId, this.maxDepth, hasToolExecuteHandler);
139
- const childRunId = `${this.parentRunId}_sub_${(0, nanoid.nanoid)(8)}`;
140
590
  const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
141
591
  const hostUsageSink = this.usageSink;
142
- const childGraph = this.createChildGraph({
592
+ const cachedChildRun = this.activeChildRuns.get(childExecutionKey);
593
+ const childGraph = cachedChildRun?.graph ?? this.createChildGraph({
143
594
  runId: childRunId,
144
595
  signal: childSignal,
145
596
  agents: [childInputs],
@@ -176,17 +627,21 @@ var SubagentExecutor = class {
176
627
  parentToolCallId
177
628
  });
178
629
  const forwarder = forwarding?.handler;
179
- if (forwarder) await this.emitSubagentUpdate(parentRegistry, {
180
- childRunId,
181
- subagentType,
182
- subagentAgentId: childAgentId,
183
- parentToolCallId,
184
- phase: "start",
185
- label: `Subagent "${subagentType}" started`
186
- });
630
+ let childAlreadyStarted = this.startedChildRuns.has(childExecutionKey);
631
+ let childAlreadyCompleted = this.completedChildRuns.has(childExecutionKey);
187
632
  let result;
633
+ let recoveredComplete = false;
634
+ let recoveredInProgress = false;
188
635
  try {
189
- const workflow = childGraph.createWorkflow();
636
+ const workflow = cachedChildRun?.workflow ?? childGraph.createWorkflow();
637
+ const activeChildRun = cachedChildRun ?? {
638
+ graph: childGraph,
639
+ workflow,
640
+ pendingInterrupts: [],
641
+ childAgentId,
642
+ childRunId
643
+ };
644
+ if (cachedChildRun == null) this.activeChildRuns.set(childExecutionKey, activeChildRun);
190
645
  /**
191
646
  * When `parentHandlerRegistry` is provided (forwarding mode), attach a
192
647
  * lightweight callback that intercepts the child's `on_custom_event`
@@ -224,46 +679,136 @@ var SubagentExecutor = class {
224
679
  }));
225
680
  const callbacks = callbackHandlers;
226
681
  /**
227
- * Inherit the parent's host `configurable` host-set fields
228
- * (`requestBody`, `user`, `userMCPAuthMap`, etc.) AND the run-
229
- * identity fields (`run_id`, `parent_run_id`, `thread_id`) all
230
- * propagate. LangGraph's own runtime keys are excluded because the
231
- * child graph creates its own scratchpad/checkpoint/abort plumbing.
682
+ * Inherit the parent's host `configurable` while binding LangGraph's
683
+ * checkpoint identity to a stable child id derived from the durable
684
+ * parent thread and checkpoint fork. The parent thread id cannot be
685
+ * reused here: parent and child share one checkpointer when nested HITL
686
+ * is enabled, and root checkpoint namespaces are normalized by
687
+ * LangGraph, so a shared `thread_id` would collide with the parent.
232
688
  *
233
- * Run-identity propagation is intentional and matches the
234
- * convention this executor itself already uses for `SubagentStart`
235
- * / `SubagentStop` hooks (`sessionId: this.parentRunId`): the
236
- * subagent runs under the parent's session scope, not its own.
237
- * Forwarding `run_id` / `parent_run_id` / `thread_id` makes
238
- * `ToolNode`'s hook lookups (`hasHookFor(eventName, runId)`),
239
- * `ToolOutputReferenceRegistry` keying, and trace lineage all
240
- * resolve to the parent's session for tools dispatched from the
241
- * subagent — so `PreToolUse` / `PostToolUse` hooks the host
242
- * registered against the parent's run fire for subagent tool
243
- * calls too. "Same run" matches the user-perceptual mental model.
244
- *
245
- * `thread_id` falls back to `childRunId` only when the parent
246
- * didn't supply one (legacy behavior preserved for hosts that
247
- * never set thread_id).
248
- *
249
- * NOTE: a future revision will likely make this configurable per
250
- * spawn type — e.g. a background / async subagent that runs after
251
- * the parent's run completes wants isolation, not inheritance.
252
- * For now the inheritance path matches LibreChat's primary use
253
- * case (synchronous subagents within a single user turn).
689
+ * `run_id` still propagates as the parent run id, which is the key used
690
+ * for session-scoped hook lookup. Child hook inputs therefore retain
691
+ * the parent policy scope while their `threadId` truthfully identifies
692
+ * the independently checkpointed child execution.
254
693
  */
255
694
  const inheritedConfigurable = sanitizeChildConfigurable(params.parentConfigurable);
256
- result = await workflow.invoke({ messages: [new _langchain_core_messages.HumanMessage(description)] }, {
695
+ const resumeAttemptId = getResumeAttemptId(params.parentConfigurable, this.parentRunId);
696
+ const currentHookSessionId = typeof inheritedConfigurable.run_id === "string" && inheritedConfigurable.run_id.length > 0 ? inheritedConfigurable.run_id : this.parentRunId;
697
+ if (cachedChildRun == null && resumeExecution != null) childGraph.restoreSubagentResumeState(resumeExecution.graphState, currentHookSessionId);
698
+ const childInvokeConfig = {
257
699
  recursionLimit: maxTurns * RECURSION_MULTIPLIER,
258
700
  signal: childSignal,
259
701
  callbacks,
260
702
  runName: `subagent:${subagentType}`,
261
703
  configurable: {
262
- thread_id: childRunId,
263
- ...inheritedConfigurable
704
+ ...inheritedConfigurable,
705
+ ...this.humanInTheLoop?.enabled === true ? { [require_types.TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY]: approvalExecutionScope } : {},
706
+ ...resumeExecution?.descendant == null ? {} : {
707
+ [require_SubagentReplay.SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: resumeExecution.descendant,
708
+ [require_SubagentReplay.SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: resumeAttemptId
709
+ },
710
+ thread_id: this.humanInTheLoop?.enabled === true ? childThreadId : inheritedConfigurable.thread_id ?? childRunId
264
711
  }
265
- });
712
+ };
713
+ activeChildRun.invokeConfig = childInvokeConfig;
714
+ if (cachedChildRun == null && this.humanInTheLoop?.enabled === true) {
715
+ /** Rehydrate child-owned interrupt state when a host rebuilds Run
716
+ * around the same durable checkpointer after a process boundary. */
717
+ const persistedState = await workflow.getState(childInvokeConfig);
718
+ const checkpointMessages = getCheckpointMessages(persistedState.values?.messages);
719
+ const persistedHookSessionId = getSubagentHookSessionId(checkpointMessages);
720
+ if (persistedHookSessionId != null) this.hookRegistry?.copySession(persistedHookSessionId, currentHookSessionId);
721
+ const persistedInterrupts = getPersistedInterrupts(persistedState);
722
+ if (persistedInterrupts.length > 0) {
723
+ activeChildRun.pendingInterrupts = persistedInterrupts;
724
+ this.startedChildRuns.add(childExecutionKey);
725
+ childAlreadyStarted = true;
726
+ } else if (persistedState.next.length > 0) {
727
+ recoveredInProgress = true;
728
+ childAlreadyStarted = true;
729
+ this.startedChildRuns.add(childExecutionKey);
730
+ } else if (persistedState.next.length === 0) {
731
+ const persistedMessages = getPersistedMessages(persistedState);
732
+ if (persistedMessages != null) {
733
+ const marker = getSubagentCheckpointMarker(checkpointMessages, executionSuffix);
734
+ result = { messages: persistedMessages };
735
+ recoveredComplete = true;
736
+ childAlreadyStarted = true;
737
+ childAlreadyCompleted = marker?.lifecycleComplete === true;
738
+ this.startedChildRuns.add(childExecutionKey);
739
+ }
740
+ }
741
+ }
742
+ if (!recoveredComplete) {
743
+ const childResumeMap = getChildResumeMap(activeChildRun.pendingInterrupts, params.parentConfigurable);
744
+ if (activeChildRun.pendingInterrupts.length > 0 && childResumeMap == null) throw new _langchain_langgraph.GraphInterrupt(activeChildRun.pendingInterrupts);
745
+ let childInput;
746
+ if (childResumeMap != null) childInput = new _langchain_langgraph.Command({ resume: childResumeMap });
747
+ else if (recoveredInProgress) childInput = null;
748
+ else childInput = { messages: [new _langchain_core_messages.HumanMessage({
749
+ content: description,
750
+ additional_kwargs: {
751
+ [SUBAGENT_HOOK_SESSION_KEY]: currentHookSessionId,
752
+ [SUBAGENT_RUN_ID_KEY]: childRunId
753
+ }
754
+ })] };
755
+ if (!childAlreadyStarted && this.hookRegistry?.hasHookFor("SubagentStart", this.parentRunId) === true) {
756
+ const hookResult = await require_executeHooks.executeHooks({
757
+ registry: this.hookRegistry,
758
+ input: {
759
+ hook_event_name: "SubagentStart",
760
+ runId: this.parentRunId,
761
+ threadId,
762
+ parentAgentId: this.parentAgentId,
763
+ agentId: childAgentId,
764
+ agentType: subagentType,
765
+ inputs: [new _langchain_core_messages.HumanMessage(description)]
766
+ },
767
+ sessionId: this.parentRunId,
768
+ matchQuery: subagentType
769
+ }).catch(() => HOOK_FALLBACK);
770
+ if (hookResult.decision === "deny" || hookResult.decision === "ask") {
771
+ this.clearChildGraph(childGraph);
772
+ this.activeChildRuns.delete(childExecutionKey);
773
+ return {
774
+ content: `Blocked: ${hookResult.reason ?? "Blocked by hook"}`,
775
+ messages: []
776
+ };
777
+ }
778
+ }
779
+ this.startedChildRuns.add(childExecutionKey);
780
+ if (forwarder && !childAlreadyStarted) await this.emitSubagentUpdate(parentRegistry, {
781
+ childRunId,
782
+ subagentType,
783
+ subagentAgentId: childAgentId,
784
+ parentToolCallId,
785
+ phase: "start",
786
+ label: `Subagent "${subagentType}" started`
787
+ });
788
+ let childResult;
789
+ if (this.humanInTheLoop?.enabled === true)
790
+ /** Execute as an independently checkpointed root instead of inheriting
791
+ * the parent's Pregel namespace. Parent decisions are routed explicitly
792
+ * by interrupt id, so concurrent children keep isolated resume state. */
793
+ childResult = await _langchain_core_singletons.AsyncLocalStorageProviderSingleton.runWithConfig(childInvokeConfig, () => workflow.invoke(childInput, childInvokeConfig));
794
+ else childResult = await workflow.invoke(childInput, childInvokeConfig);
795
+ const childInterrupts = (0, _langchain_langgraph.isInterrupted)(childResult) ? childResult[_langchain_langgraph.INTERRUPT] : void 0;
796
+ if (childInterrupts != null && childInterrupts.length > 0) throw new _langchain_langgraph.GraphInterrupt(childInterrupts);
797
+ result = { messages: childResult.messages };
798
+ }
266
799
  } catch (error) {
800
+ if ((0, _langchain_langgraph.isGraphInterrupt)(error)) {
801
+ const activeChildRun = this.activeChildRuns.get(childExecutionKey);
802
+ if (activeChildRun != null) activeChildRun.pendingInterrupts = error.interrupts;
803
+ const resumeManifest = activeChildRun == null || parentToolCallId == null ? void 0 : await this.createResumeManifest();
804
+ await forwarding?.drain();
805
+ throw new _langchain_langgraph.GraphInterrupt(addSubagentScope(error.interrupts, {
806
+ run_id: childRunId,
807
+ agent_id: childAgentId,
808
+ subagent_type: subagentType,
809
+ parent_tool_call_id: parentToolCallId
810
+ }, resumeManifest));
811
+ }
267
812
  /** Aborted before any observational work below: parallel siblings — in
268
813
  * this executor and, via the graph-scoped breaker, under other
269
814
  * parallel agent nodes — stream on the composed child signal, and
@@ -285,7 +830,8 @@ var SubagentExecutor = class {
285
830
  data: { message: errorMessage }
286
831
  });
287
832
  }
288
- childGraph.clearHeavyState();
833
+ this.clearChildGraph(childGraph);
834
+ this.activeChildRuns.delete(childExecutionKey);
289
835
  /**
290
836
  * A tripped stream circuit breaker is a safety abort, not a recoverable
291
837
  * subagent failure: converting it into a tool result would let the
@@ -299,8 +845,9 @@ var SubagentExecutor = class {
299
845
  messages: []
300
846
  };
301
847
  }
848
+ if (result == null) throw new Error("Subagent completed without producing graph state.");
302
849
  const filteredContent = filterSubagentResult(result.messages);
303
- if (this.hookRegistry?.hasHookFor("SubagentStop", this.parentRunId) === true)
850
+ if (!childAlreadyCompleted && this.hookRegistry?.hasHookFor("SubagentStop", this.parentRunId) === true)
304
851
  /**
305
852
  * Awaited (not fire-and-forget) for deterministic test synchronization
306
853
  * and consistency with PostCompact. The parent is already waiting on the
@@ -320,7 +867,7 @@ var SubagentExecutor = class {
320
867
  sessionId: this.parentRunId,
321
868
  matchQuery: subagentType
322
869
  }).catch(() => {});
323
- if (forwarding) {
870
+ if (forwarding && !childAlreadyCompleted) {
324
871
  await forwarding.drain();
325
872
  await this.emitSubagentUpdate(parentRegistry, {
326
873
  childRunId,
@@ -331,11 +878,18 @@ var SubagentExecutor = class {
331
878
  label: `Subagent "${subagentType}" finished`
332
879
  });
333
880
  }
334
- childGraph.clearHeavyState();
335
- return {
881
+ if (!childAlreadyCompleted) {
882
+ const activeChildRun = this.activeChildRuns.get(childExecutionKey);
883
+ if (activeChildRun != null && parentToolCallId != null) await this.persistChildCheckpointMarker(activeChildRun, parentToolCallId);
884
+ }
885
+ this.completedChildRuns.add(childExecutionKey);
886
+ this.clearChildGraph(childGraph);
887
+ const completedResult = {
336
888
  content: filteredContent,
337
889
  messages: result.messages
338
890
  };
891
+ this.completedChildResults.set(childExecutionKey, completedResult);
892
+ return completedResult;
339
893
  }
340
894
  /**
341
895
  * Emits a single {@link GraphEvents.ON_SUBAGENT_UPDATE} envelope through the
@@ -582,7 +1136,7 @@ function sanitizeChildConfigurable(parentConfigurable) {
582
1136
  return Object.fromEntries(Object.entries(parentConfigurable).filter(([key]) => !isLangGraphRuntimeConfigKey(key)));
583
1137
  }
584
1138
  function isLangGraphRuntimeConfigKey(key) {
585
- return key.startsWith(LANGGRAPH_RUNTIME_CONFIG_PREFIX) || LANGGRAPH_CHECKPOINT_CONFIG_KEYS.has(key) || key === "lc_run_breaker_scope";
1139
+ return key.startsWith(LANGGRAPH_RUNTIME_CONFIG_PREFIX) || LANGGRAPH_CHECKPOINT_CONFIG_KEYS.has(key) || key === "__librechat_subagent_resume_attempt" || key === "__librechat_subagent_resume_manifest" || key === "__librechat_subagent_parent_batch" || key === "lc_run_breaker_scope";
586
1140
  }
587
1141
  function sanitizeForwardedSubagentUpdateData(eventName, data) {
588
1142
  if (eventName === "on_tool_execute") return sanitizeToolExecuteUpdateData(data);
@@ -767,6 +1321,9 @@ function summarizeEvent(eventName, data) {
767
1321
  * pure tool_use (e.g. the subagent hit `maxTurns` mid-tool-call), the walk
768
1322
  * continues to earlier AIMessages so partial progress is salvaged — this
769
1323
  * matches Claude Code's behavior in `agentToolUtils.finalizeAgentTool`.
1324
+ * Consecutive streamed text-delta blocks with the same provider index are
1325
+ * coalesced without adding whitespace. Annotation-only text blocks are
1326
+ * ignored; complete text blocks and distinct delta indexes remain separated.
770
1327
  * Returns "Task completed" only when no AIMessage in the history contains
771
1328
  * any text.
772
1329
  */
@@ -780,8 +1337,36 @@ function filterSubagentResult(messages) {
780
1337
  }
781
1338
  if (!Array.isArray(content)) continue;
782
1339
  const textParts = [];
783
- for (const block of content) if (typeof block === "string") textParts.push(block);
784
- else if ("type" in block && block.type === "text" && "text" in block) textParts.push(block.text);
1340
+ let textDeltaParts = [];
1341
+ let textDeltaIndex;
1342
+ const flushTextDeltaParts = () => {
1343
+ if (textDeltaParts.length === 0) return;
1344
+ textParts.push(textDeltaParts.join(""));
1345
+ textDeltaParts = [];
1346
+ textDeltaIndex = void 0;
1347
+ };
1348
+ for (const block of content) {
1349
+ if (typeof block === "string") {
1350
+ flushTextDeltaParts();
1351
+ if (block !== "") textParts.push(block);
1352
+ continue;
1353
+ }
1354
+ const type = "type" in block && typeof block.type === "string" ? block.type : "";
1355
+ const isTextDelta = type === TEXT_DELTA_CONTENT_TYPE;
1356
+ const text = (type === "text" || isTextDelta) && "text" in block && typeof block.text === "string" ? block.text : "";
1357
+ if (isTextDelta) {
1358
+ if (text === "") continue;
1359
+ const index = "index" in block && (typeof block.index === "string" || typeof block.index === "number") ? block.index : void 0;
1360
+ if (textDeltaIndex != null && index != null && index !== textDeltaIndex) flushTextDeltaParts();
1361
+ textDeltaIndex ??= index;
1362
+ textDeltaParts.push(text);
1363
+ continue;
1364
+ }
1365
+ if (type === "text" && text === "") continue;
1366
+ flushTextDeltaParts();
1367
+ if (text !== "") textParts.push(text);
1368
+ }
1369
+ flushTextDeltaParts();
785
1370
  if (textParts.length > 0) return textParts.join("\n");
786
1371
  }
787
1372
  return "Task completed";
@@ -848,11 +1433,10 @@ function buildChildInputs(config, childAgentId, parentMaxDepth, keepToolDefiniti
848
1433
  * Host-supplied direct tools are scrubbed from INHERITED configs only.
849
1434
  * A self-spawn config's `agentInputs` is a shallow spread of the parent's
850
1435
  * `_sourceInputs`, so without this a parent-scoped graph tool (e.g. an
851
- * interrupt-raising ask_user_question, which needs the parent's
852
- * checkpointer child graphs compile without one) would silently leak
853
- * into the child and deterministically throw `No checkpointer set`. An
854
- * EXPLICIT child config that lists its own `graphTools` is a deliberate
855
- * host choice and keeps them (Codex #289 P2).
1436
+ * interrupt-raising ask_user_question) would silently become available to
1437
+ * the child. An EXPLICIT child config that lists its own `graphTools` is a
1438
+ * deliberate host choice and keeps them (Codex #289 P2); with HITL enabled,
1439
+ * those tools use the shared checkpointer and can pause and resume safely.
856
1440
  */
857
1441
  graphTools: config.self === true ? void 0 : agentInputs.graphTools
858
1442
  };