@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
@@ -1,28 +1,223 @@
1
+ import { SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY, SUBAGENT_RESUME_MANIFEST_CONFIG_KEY, attachSubagentResumeManifest, getSubagentResumeManifest } from "./SubagentReplay.mjs";
2
+ import { composeAbortSignals } from "../../utils/misc.mjs";
1
3
  import "../../common/enum.mjs";
2
4
  import "../../common/index.mjs";
5
+ import { stableStringify } from "../eagerEventExecution.mjs";
3
6
  import { StreamLimitExceededError } from "../../llm/streamLimits.mjs";
7
+ import { SubagentDefinitionBindingError, SubagentExecutionRegistry, SubagentInvocationBindingError, SubagentSettlementBindingError, getSubagentApprovalExecutionScope } from "./SubagentExecutionRegistry.mjs";
8
+ import "./runtimeLimits.mjs";
4
9
  import { executeHooks } from "../../hooks/executeHooks.mjs";
10
+ import { TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY } from "../../hooks/types.mjs";
5
11
  import "../../hooks/index.mjs";
12
+ import { createChildGraphPlan, isGraphSubagentConfig } from "./childGraphConfig.mjs";
6
13
  import { nanoid } from "nanoid";
7
14
  import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
8
- import { HumanMessage } from "@langchain/core/messages";
15
+ import { AIMessage, BaseMessage, HumanMessage, ToolMessage } from "@langchain/core/messages";
16
+ import { Command, END, GraphInterrupt, INTERRUPT, MessagesAnnotation, START, StateGraph, copyCheckpoint, isGraphInterrupt, isInterrupted } from "@langchain/langgraph";
17
+ import { AsyncLocalStorageProviderSingleton } from "@langchain/core/singletons";
18
+ import { createHash } from "crypto";
9
19
  //#region src/tools/subagent/SubagentExecutor.ts
10
- const DEFAULT_MAX_TURNS = 25;
11
- const RECURSION_MULTIPLIER = 3;
12
20
  const ERROR_MESSAGE_MAX_CHARS = 200;
13
- const MAX_PENDING_SUBAGENT_UPDATES = 64;
21
+ const MAX_QUEUED_SUBAGENT_UPDATES = 64;
22
+ const SUBAGENT_UPDATE_HANDLER_TIMEOUT_MS = 5e3;
14
23
  const TEXT_DELTA_CONTENT_TYPE = `text_delta`;
24
+ const SUBAGENT_RESOLUTION_ERROR_MESSAGE = "Subagent error: Unable to initialize the selected subagent.";
25
+ const SUBAGENT_CONFIG_CHANGED_MESSAGE = "Subagent error: Subagent configuration changed since this execution was paused.";
26
+ const SUBAGENT_INVOCATION_CHANGED_MESSAGE = "Subagent error: Subagent invocation changed for this execution.";
27
+ async function dispatchObservationalSubagentUpdate(handler, event) {
28
+ let timeout;
29
+ try {
30
+ return await Promise.race([Promise.resolve().then(() => handler.handle("on_subagent_update", event)).then(() => true, () => true), new Promise((resolve) => {
31
+ timeout = setTimeout(() => resolve(false), SUBAGENT_UPDATE_HANDLER_TIMEOUT_MS);
32
+ })]);
33
+ } finally {
34
+ if (timeout != null) clearTimeout(timeout);
35
+ }
36
+ }
15
37
  const HOOK_FALLBACK = Object.freeze({
16
38
  additionalContexts: [],
17
39
  injectedMessages: [],
18
40
  errors: []
19
41
  });
42
+ function isResumeExecutionCompatible(resumeExecution, subagentType, executableConfig, hasPersistedTypeEvidence = false) {
43
+ if (resumeExecution == null) return true;
44
+ if (subagentType == null || executableConfig == null || resumeExecution.configId !== executableConfig.configId) return false;
45
+ if (resumeExecution.subagentType != null) return resumeExecution.subagentType === subagentType;
46
+ if (hasPersistedTypeEvidence) return true;
47
+ return isGraphSubagentConfig(executableConfig) || executableConfig.agentInputs != null;
48
+ }
49
+ function getSubagentTypeFromArgs(args) {
50
+ if (!isObjectLike(args)) return;
51
+ return asNonEmptyString(args.subagent_type);
52
+ }
53
+ function getSubagentDescriptionFromArgs(args) {
54
+ if (!isObjectLike(args)) return;
55
+ const description = args.description;
56
+ return typeof description === "string" && description.trim().length > 0 ? description : void 0;
57
+ }
58
+ function getSubagentType(call) {
59
+ return getSubagentTypeFromArgs(call.args);
60
+ }
20
61
  const LANGGRAPH_RUNTIME_CONFIG_PREFIX = "__pregel_";
62
+ const LANGGRAPH_RESUME_MAP_CONFIG_KEY = "__pregel_resume_map";
21
63
  const LANGGRAPH_CHECKPOINT_CONFIG_KEYS = new Set([
22
64
  "checkpoint_id",
23
65
  "checkpoint_map",
24
66
  "checkpoint_ns"
25
67
  ]);
68
+ const SUBAGENT_CHECKPOINT_MARKER_KEY = "__librechat_subagent_checkpoint";
69
+ const SUBAGENT_HOOK_SESSION_KEY = "__librechat_subagent_hook_session";
70
+ const SUBAGENT_RUN_ID_KEY = "__librechat_subagent_run_id";
71
+ const SUBAGENT_REPLAY_NODE = "subagent-replay";
72
+ const DEFAULT_SUBAGENT_DESCRIPTION = "No task description provided";
73
+ function isCheckpointSaver(value) {
74
+ if (value == null || typeof value !== "object") return false;
75
+ const candidate = value;
76
+ return typeof candidate.getTuple === "function" && typeof candidate.list === "function" && typeof candidate.put === "function" && typeof candidate.putWrites === "function" && typeof candidate.deleteThread === "function";
77
+ }
78
+ function isSubagentCheckpointMarker(value) {
79
+ if (value == null || typeof value !== "object") return false;
80
+ const marker = value;
81
+ 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));
82
+ }
83
+ function isPersistedToolOutput(value) {
84
+ if (value == null || typeof value !== "object") return false;
85
+ const output = value;
86
+ const contentIsValid = typeof output.content === "string" || Array.isArray(output.content);
87
+ const statusIsValid = output.status == null || output.status === "success" || output.status === "error";
88
+ const contextsAreValid = Array.isArray(output.additionalContexts) && output.additionalContexts.every((context) => typeof context === "string");
89
+ const resolvedArgsAreValid = output.resolvedArgs == null || typeof output.resolvedArgs === "object" && !Array.isArray(output.resolvedArgs);
90
+ 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");
91
+ }
92
+ function getSubagentCheckpointMarker(messages, parentToolCallId) {
93
+ for (let i = messages.length - 1; i >= 0; i--) {
94
+ const marker = messages[i].additional_kwargs[SUBAGENT_CHECKPOINT_MARKER_KEY];
95
+ if (isSubagentCheckpointMarker(marker) && marker.parentToolCallId === parentToolCallId) return marker;
96
+ }
97
+ }
98
+ function getSubagentHookSessionId(messages) {
99
+ for (let i = messages.length - 1; i >= 0; i--) {
100
+ const sessionId = messages[i].additional_kwargs[SUBAGENT_HOOK_SESSION_KEY];
101
+ if (typeof sessionId === "string" && sessionId.length > 0) return sessionId;
102
+ }
103
+ }
104
+ function getSubagentRunId(messages) {
105
+ for (let i = messages.length - 1; i >= 0; i--) {
106
+ const runId = messages[i].additional_kwargs[SUBAGENT_RUN_ID_KEY];
107
+ if (typeof runId === "string" && runId.length > 0) return runId;
108
+ const marker = messages[i].additional_kwargs[SUBAGENT_CHECKPOINT_MARKER_KEY];
109
+ if (isSubagentCheckpointMarker(marker) && marker.childRunId != null && marker.childRunId.length > 0) return marker.childRunId;
110
+ }
111
+ }
112
+ function isSubagentCheckpointMarkerMessage(message) {
113
+ return isSubagentCheckpointMarker(message.additional_kwargs[SUBAGENT_CHECKPOINT_MARKER_KEY]);
114
+ }
115
+ function createSubagentCheckpointMarkerMessage(marker) {
116
+ return new AIMessage({
117
+ content: "",
118
+ additional_kwargs: { [SUBAGENT_CHECKPOINT_MARKER_KEY]: marker }
119
+ });
120
+ }
121
+ function getCheckpointMessages(value) {
122
+ return Array.isArray(value) ? value.filter(BaseMessage.isInstance) : [];
123
+ }
124
+ function getConfigurableString(config, key) {
125
+ const value = config?.configurable?.[key];
126
+ return typeof value === "string" && value.length > 0 ? value : void 0;
127
+ }
128
+ function getTupleMessages(tuple) {
129
+ return getCheckpointMessages(tuple?.checkpoint.channel_values.messages);
130
+ }
131
+ function getCheckpointReference(tuple) {
132
+ const configurable = tuple?.config.configurable;
133
+ const threadId = configurable?.thread_id;
134
+ const checkpointId = configurable?.checkpoint_id;
135
+ const checkpointNs = configurable?.checkpoint_ns ?? "";
136
+ if (typeof threadId !== "string" || threadId.length === 0 || typeof checkpointId !== "string" || checkpointId.length === 0 || typeof checkpointNs !== "string") return;
137
+ return {
138
+ threadId,
139
+ checkpointId,
140
+ checkpointNs
141
+ };
142
+ }
143
+ function serializeToolOutput(settled) {
144
+ const { output } = settled;
145
+ return {
146
+ content: output.content,
147
+ toolCallId: output.tool_call_id,
148
+ ...output.id == null ? {} : { id: output.id },
149
+ ...output.name == null ? {} : { name: output.name },
150
+ ...output.status == null ? {} : { status: output.status },
151
+ additionalKwargs: output.additional_kwargs,
152
+ responseMetadata: output.response_metadata,
153
+ ...output.metadata == null ? {} : { metadata: output.metadata },
154
+ additionalContexts: settled.additionalContexts,
155
+ ...settled.resolvedArgs == null ? {} : { resolvedArgs: settled.resolvedArgs },
156
+ ...settled.referenceContent == null ? {} : { referenceContent: settled.referenceContent }
157
+ };
158
+ }
159
+ function getSettlementFingerprint(output) {
160
+ return createHash("sha256").update(stableStringify(output)).digest("hex");
161
+ }
162
+ function deserializeToolOutput(output) {
163
+ return {
164
+ output: new ToolMessage({
165
+ content: output.content,
166
+ tool_call_id: output.toolCallId,
167
+ ...output.id == null ? {} : { id: output.id },
168
+ ...output.name == null ? {} : { name: output.name },
169
+ ...output.status == null ? {} : { status: output.status },
170
+ additional_kwargs: output.additionalKwargs,
171
+ response_metadata: output.responseMetadata,
172
+ ...output.metadata == null ? {} : { metadata: output.metadata }
173
+ }),
174
+ additionalContexts: output.additionalContexts,
175
+ ...output.resolvedArgs == null ? {} : { resolvedArgs: output.resolvedArgs },
176
+ ...output.referenceContent == null ? {} : { referenceContent: output.referenceContent }
177
+ };
178
+ }
179
+ function isToolApprovalPayload(value) {
180
+ return value != null && typeof value === "object" && value.type === "tool_approval";
181
+ }
182
+ function addSubagentScope(interrupts, scope, resumeManifest) {
183
+ return interrupts.map((childInterrupt) => {
184
+ let payload = childInterrupt.value;
185
+ if (isToolApprovalPayload(payload)) payload = {
186
+ ...childInterrupt.value,
187
+ subagent: childInterrupt.value.subagent ?? scope
188
+ };
189
+ return {
190
+ ...childInterrupt,
191
+ value: resumeManifest == null ? payload : attachSubagentResumeManifest(payload, resumeManifest)
192
+ };
193
+ });
194
+ }
195
+ function getChildResumeMap(pendingInterrupts, parentConfigurable) {
196
+ const resumeMap = parentConfigurable?.[LANGGRAPH_RESUME_MAP_CONFIG_KEY];
197
+ if (resumeMap == null || typeof resumeMap !== "object") return;
198
+ const parentResumeMap = resumeMap;
199
+ const childResumeMap = {};
200
+ for (const childInterrupt of pendingInterrupts) {
201
+ const interruptId = childInterrupt.id;
202
+ if (typeof interruptId === "string" && Object.prototype.hasOwnProperty.call(parentResumeMap, interruptId)) childResumeMap[interruptId] = parentResumeMap[interruptId];
203
+ }
204
+ return Object.keys(childResumeMap).length > 0 ? childResumeMap : void 0;
205
+ }
206
+ function getPersistedInterrupts(snapshot) {
207
+ const interrupts = [];
208
+ for (const task of snapshot.tasks) for (const pendingInterrupt of task.interrupts) interrupts.push(pendingInterrupt);
209
+ return interrupts;
210
+ }
211
+ function getPersistedMessages(snapshot) {
212
+ if (snapshot.values == null || typeof snapshot.values !== "object") return;
213
+ const values = snapshot.values;
214
+ if (!Array.isArray(values.messages) || values.messages.length === 0) return;
215
+ const messages = values.messages.filter((message) => !isSubagentCheckpointMarkerMessage(message));
216
+ return messages.length > 0 ? messages : void 0;
217
+ }
218
+ function createReplayCheckpointWorkflow(checkpointer) {
219
+ return new StateGraph(MessagesAnnotation).addNode(SUBAGENT_REPLAY_NODE, (state) => state).addEdge(START, SUBAGENT_REPLAY_NODE).addEdge(SUBAGENT_REPLAY_NODE, END).compile({ checkpointer });
220
+ }
26
221
  var SubagentExecutor = class {
27
222
  configs;
28
223
  parentSignal;
@@ -37,12 +232,18 @@ var SubagentExecutor = class {
37
232
  hookRegistry;
38
233
  parentRunId;
39
234
  parentAgentId;
235
+ executionContext;
40
236
  langfuse;
41
237
  tokenCounter;
42
238
  streamLimits;
239
+ humanInTheLoop;
240
+ checkpointer;
43
241
  maxDepth;
44
242
  createChildGraph;
243
+ createChildGraphByKind;
45
244
  usageSink;
245
+ executions;
246
+ replayCheckpointWorkflow;
46
247
  resolveParentHandlerRegistry;
47
248
  constructor(options) {
48
249
  this.configs = options.configs;
@@ -51,11 +252,25 @@ var SubagentExecutor = class {
51
252
  this.hookRegistry = options.hookRegistry;
52
253
  this.parentRunId = options.parentRunId;
53
254
  this.parentAgentId = options.parentAgentId;
255
+ this.executionContext = options.executionContext ?? {
256
+ rootRunId: options.parentRunId,
257
+ hookSessionId: options.parentRunId,
258
+ depth: 0,
259
+ ancestry: []
260
+ };
54
261
  this.langfuse = options.langfuse;
55
262
  this.tokenCounter = options.tokenCounter;
56
263
  this.streamLimits = options.streamLimits;
264
+ this.humanInTheLoop = options.humanInTheLoop;
265
+ this.checkpointer = isCheckpointSaver(options.checkpointer) ? options.checkpointer : void 0;
266
+ this.executions = new SubagentExecutionRegistry({
267
+ parentRunId: options.parentRunId,
268
+ parentAgentId: options.parentAgentId,
269
+ durable: options.humanInTheLoop?.enabled === true && this.checkpointer != null
270
+ });
57
271
  this.maxDepth = options.maxDepth ?? 1;
58
272
  this.createChildGraph = options.createChildGraph;
273
+ this.createChildGraphByKind = options.createChildGraphByKind;
59
274
  this.usageSink = options.usageSink;
60
275
  const rawRegistry = options.parentHandlerRegistry;
61
276
  if (typeof rawRegistry === "function") this.resolveParentHandlerRegistry = rawRegistry;
@@ -69,16 +284,501 @@ var SubagentExecutor = class {
69
284
  /** One signal that fires on the parent's abort or the breaker abort,
70
285
  * collapsed to a single signal when possible (mirrors
71
286
  * `composeAbortSignals` in Graph.ts). */
72
- composeChildSignal(breaker) {
73
- const child = breaker.signal;
74
- if (this.parentSignal == null || this.parentSignal === child) return child;
75
- return AbortSignal.any([this.parentSignal, child]);
287
+ composeChildSignal(breaker, executionSignal) {
288
+ return composeAbortSignals(composeAbortSignals(this.parentSignal, breaker.signal), executionSignal) ?? breaker.signal;
76
289
  }
77
290
  /** Snapshot of the parent's registry at the moment a subagent is dispatched. */
78
291
  getParentHandlerRegistry() {
79
292
  return this.resolveParentHandlerRegistry?.();
80
293
  }
81
- async execute(params) {
294
+ bindExecutionDefinition(execution, binding, authority) {
295
+ try {
296
+ execution.bindDefinition(binding, authority);
297
+ return true;
298
+ } catch (error) {
299
+ if (error instanceof SubagentDefinitionBindingError) return false;
300
+ throw error;
301
+ }
302
+ }
303
+ /** Resolve one lazy descriptor per stable child execution. Concurrent
304
+ * duplicate dispatches share the same in-flight resolution; HITL re-entry
305
+ * on this executor reuses the resolved config until the child settles. */
306
+ resolveExecutionConfig(config, execution, context) {
307
+ if (isGraphSubagentConfig(config) || config.agentInputs != null) return Promise.resolve(config);
308
+ const resolver = config.resolveAgentInputs;
309
+ const configId = config.configId;
310
+ return execution.resolveConfig(() => resolveAgentInputsWithSignal(() => resolver({
311
+ descriptor: {
312
+ type: config.type,
313
+ name: config.name,
314
+ description: config.description,
315
+ configId
316
+ },
317
+ executionId: context.childRunId,
318
+ parentRunId: this.parentRunId,
319
+ parentAgentId: this.parentAgentId,
320
+ parentToolCallId: context.parentToolCallId,
321
+ threadId: context.threadId,
322
+ signal: context.childSignal,
323
+ configurable: context.parentConfigurable == null ? void 0 : sanitizeResolverConfigurable(context.parentConfigurable)
324
+ }), context.childSignal).then((agentInputs) => ({
325
+ ...config,
326
+ agentInputs
327
+ })), context.childSignal);
328
+ }
329
+ /**
330
+ * Keeps the original child thread as an immutable resume source once a
331
+ * different parent Run reconstructs it. Each rebuilt parent gets a private
332
+ * checkpoint fork, while the persisted child run ID remains stable for
333
+ * activity and usage correlation across the interrupt boundary.
334
+ */
335
+ async resolveChildExecutionIdentity(execution) {
336
+ return execution.resolveIdentity(async () => {
337
+ const preparation = await this.createChildExecutionIdentity(execution);
338
+ const { identity } = preparation;
339
+ return {
340
+ identity,
341
+ lease: preparation.lease,
342
+ commit: (lease) => {
343
+ const { resumeExecution } = execution;
344
+ if (resumeExecution != null) this.executions.retireResumeSources(execution, new Set(resumeExecution.checkpoints.map((checkpoint) => checkpoint.threadId)), (source) => {
345
+ if (source.activeRun != null) this.clearChildGraph(source.activeRun.graph);
346
+ if (source.identity != null) this.hookRegistry?.clearSession(source.identity.approvalExecutionScope);
347
+ });
348
+ preparation.commit?.(lease);
349
+ if (this.humanInTheLoop?.enabled === true && this.checkpointer != null) this.executions.rememberCheckpointThread(identity.childThreadId);
350
+ },
351
+ rollback: async (lease) => {
352
+ await preparation.rollback?.(lease);
353
+ }
354
+ };
355
+ });
356
+ }
357
+ async createChildExecutionIdentity(execution) {
358
+ const { baseChildThreadId, branchChildThreadId, currentChildRunId, explicitResumeAttempt, resumeAttemptId } = execution.address;
359
+ if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null) return this.prepareIdentity(execution, {
360
+ childRunId: currentChildRunId,
361
+ childThreadId: baseChildThreadId,
362
+ approvalExecutionScope: currentChildRunId
363
+ });
364
+ const { resumeExecution } = execution;
365
+ if (resumeExecution != null) {
366
+ const approvalExecutionScope = getSubagentApprovalExecutionScope(resumeExecution.childRunId, resumeAttemptId);
367
+ const identity = {
368
+ childRunId: resumeExecution.childRunId,
369
+ childThreadId: branchChildThreadId,
370
+ approvalExecutionScope
371
+ };
372
+ const lease = this.executions.beginIdentityPreparation(execution, identity);
373
+ await this.prepareCheckpointFork(resumeExecution.checkpoints, branchChildThreadId, lease);
374
+ let previousApprovals = [];
375
+ let approvalsMutated = false;
376
+ return {
377
+ identity,
378
+ lease,
379
+ commit: (currentLease) => {
380
+ const hookRegistry = this.hookRegistry;
381
+ if (hookRegistry == null) return;
382
+ previousApprovals = hookRegistry.snapshotPendingToolApprovals(approvalExecutionScope, approvalExecutionScope);
383
+ const approvalReplayCreatesScope = previousApprovals.length === 0 && resumeExecution.approvalReplays.length > 0;
384
+ const approvalsChanged = previousApprovals.length > 0 || resumeExecution.approvalReplays.length > 0;
385
+ if (approvalReplayCreatesScope) currentLease.markApprovalScopeCreated(async () => {
386
+ hookRegistry.restorePendingToolApprovals(approvalExecutionScope, approvalExecutionScope, previousApprovals);
387
+ });
388
+ approvalsMutated = approvalsChanged && !approvalReplayCreatesScope;
389
+ hookRegistry.restorePendingToolApprovals(approvalExecutionScope, approvalExecutionScope, resumeExecution.approvalReplays);
390
+ },
391
+ rollback: async (currentLease) => {
392
+ if (approvalsMutated && currentLease.ownsApprovalScope && this.hookRegistry != null) this.hookRegistry.restorePendingToolApprovals(approvalExecutionScope, approvalExecutionScope, previousApprovals);
393
+ }
394
+ };
395
+ }
396
+ const branchTuple = await this.checkpointer.getTuple({ configurable: { thread_id: branchChildThreadId } });
397
+ if (branchTuple != null) {
398
+ const childRunId = getSubagentRunId(getTupleMessages(branchTuple)) ?? currentChildRunId;
399
+ return this.prepareIdentity(execution, {
400
+ childRunId,
401
+ childThreadId: branchChildThreadId,
402
+ approvalExecutionScope: getSubagentApprovalExecutionScope(childRunId, resumeAttemptId)
403
+ });
404
+ }
405
+ const baseTuple = await this.checkpointer.getTuple({ configurable: { thread_id: baseChildThreadId } });
406
+ if (baseTuple == null) return this.prepareIdentity(execution, {
407
+ childRunId: currentChildRunId,
408
+ childThreadId: explicitResumeAttempt ? branchChildThreadId : baseChildThreadId,
409
+ approvalExecutionScope: getSubagentApprovalExecutionScope(currentChildRunId, resumeAttemptId)
410
+ });
411
+ const persistedChildRunId = getSubagentRunId(getTupleMessages(baseTuple));
412
+ if (!explicitResumeAttempt && (persistedChildRunId == null || persistedChildRunId === currentChildRunId)) {
413
+ const childRunId = persistedChildRunId ?? currentChildRunId;
414
+ return this.prepareIdentity(execution, {
415
+ childRunId,
416
+ childThreadId: baseChildThreadId,
417
+ approvalExecutionScope: getSubagentApprovalExecutionScope(childRunId, resumeAttemptId)
418
+ });
419
+ }
420
+ const sourceCheckpoints = await this.getLatestCheckpointSnapshot(baseChildThreadId);
421
+ if (sourceCheckpoints.length === 0) throw new Error(`Cannot fork subagent checkpoint thread "${baseChildThreadId}" without a checkpoint ID.`);
422
+ const childRunId = persistedChildRunId ?? currentChildRunId;
423
+ const identity = {
424
+ childRunId,
425
+ childThreadId: branchChildThreadId,
426
+ approvalExecutionScope: getSubagentApprovalExecutionScope(childRunId, resumeAttemptId)
427
+ };
428
+ const lease = this.executions.beginIdentityPreparation(execution, identity);
429
+ await this.prepareCheckpointFork(sourceCheckpoints, branchChildThreadId, lease);
430
+ return {
431
+ identity,
432
+ lease
433
+ };
434
+ }
435
+ prepareIdentity(execution, identity) {
436
+ return {
437
+ identity,
438
+ lease: this.executions.beginIdentityPreparation(execution, identity)
439
+ };
440
+ }
441
+ async prepareCheckpointFork(sources, targetThreadId, lease) {
442
+ const checkpointer = this.checkpointer;
443
+ try {
444
+ if (checkpointer == null || sources.length === 0 || sources.every((source) => source.threadId === targetThreadId)) {
445
+ lease.assertOwned();
446
+ return;
447
+ }
448
+ const targetExists = await this.checkpointThreadExists(targetThreadId);
449
+ lease.assertOwned();
450
+ if (!targetExists) lease.markCheckpointCreated(() => checkpointer.deleteThread(targetThreadId));
451
+ await this.forkCheckpointSnapshot(sources, targetThreadId);
452
+ lease.assertOwned();
453
+ } catch (error) {
454
+ return lease.rollback(error);
455
+ }
456
+ }
457
+ async checkpointThreadExists(threadId) {
458
+ if (this.checkpointer == null) return false;
459
+ for await (const _tuple of this.checkpointer.list({ configurable: { thread_id: threadId } }, { limit: 1 })) return true;
460
+ return false;
461
+ }
462
+ /** Captures one exact checkpoint head per namespace for a child thread. */
463
+ async getLatestCheckpointSnapshot(threadId) {
464
+ if (this.checkpointer == null) return [];
465
+ const checkpointsByNamespace = /* @__PURE__ */ new Map();
466
+ for await (const tuple of this.checkpointer.list({ configurable: { thread_id: threadId } })) {
467
+ const checkpoint = getCheckpointReference(tuple);
468
+ const current = checkpoint == null ? void 0 : checkpointsByNamespace.get(checkpoint.checkpointNs);
469
+ if (checkpoint != null && (current == null || checkpoint.checkpointId.localeCompare(current.checkpointId) > 0)) checkpointsByNamespace.set(checkpoint.checkpointNs, checkpoint);
470
+ }
471
+ return [...checkpointsByNamespace.values()].sort((left, right) => left.checkpointNs.localeCompare(right.checkpointNs));
472
+ }
473
+ /** Copies exact checkpoint lineages, including pending task writes. */
474
+ async forkCheckpointSnapshot(sources, targetThreadId) {
475
+ if (this.checkpointer == null || sources.length === 0 || sources.every((source) => source.threadId === targetThreadId)) return;
476
+ for (const source of sources) {
477
+ const tuples = [];
478
+ const visited = /* @__PURE__ */ new Set();
479
+ let tuple = await this.checkpointer.getTuple({ configurable: {
480
+ thread_id: source.threadId,
481
+ checkpoint_ns: source.checkpointNs,
482
+ checkpoint_id: source.checkpointId
483
+ } });
484
+ if (tuple == null) throw new Error(`Subagent checkpoint "${source.checkpointId}" is unavailable.`);
485
+ for (;;) {
486
+ const reference = getCheckpointReference(tuple);
487
+ if (reference == null) throw new Error("Subagent checkpoint lineage contains an invalid tuple.");
488
+ if (reference.threadId !== source.threadId || reference.checkpointNs !== source.checkpointNs) throw new Error("Subagent checkpoint lineage escapes its source namespace.");
489
+ const lineageKey = JSON.stringify([
490
+ reference.threadId,
491
+ reference.checkpointNs,
492
+ reference.checkpointId
493
+ ]);
494
+ if (visited.has(lineageKey)) throw new Error("Subagent checkpoint lineage contains a cycle.");
495
+ visited.add(lineageKey);
496
+ tuples.push(tuple);
497
+ if (tuple.parentConfig == null) break;
498
+ tuple = await this.checkpointer.getTuple(tuple.parentConfig);
499
+ if (tuple == null) throw new Error("Subagent checkpoint lineage is incomplete.");
500
+ }
501
+ tuples.reverse();
502
+ for (const lineageTuple of tuples) {
503
+ const checkpointNs = getConfigurableString(lineageTuple.config, "checkpoint_ns") ?? "";
504
+ const parentCheckpointId = getConfigurableString(lineageTuple.parentConfig, "checkpoint_id");
505
+ const storedConfig = await this.checkpointer.put({ configurable: {
506
+ thread_id: targetThreadId,
507
+ checkpoint_ns: checkpointNs,
508
+ ...parentCheckpointId == null ? {} : { checkpoint_id: parentCheckpointId }
509
+ } }, copyCheckpoint(lineageTuple.checkpoint), lineageTuple.metadata ?? {
510
+ source: "fork",
511
+ step: -1,
512
+ parents: {}
513
+ }, lineageTuple.checkpoint.channel_versions);
514
+ const writesByTask = /* @__PURE__ */ new Map();
515
+ for (const [taskId, channel, value] of lineageTuple.pendingWrites ?? []) {
516
+ let writes = writesByTask.get(taskId);
517
+ if (writes == null) {
518
+ writes = [];
519
+ writesByTask.set(taskId, writes);
520
+ }
521
+ writes.push([channel, value]);
522
+ }
523
+ for (const [taskId, writes] of writesByTask) await this.checkpointer.putWrites(storedConfig, writes, taskId);
524
+ }
525
+ }
526
+ }
527
+ async createResumeManifest(records) {
528
+ if (this.checkpointer == null) return;
529
+ const executions = [];
530
+ for (const record of records) {
531
+ const { identity, binding, activeRun } = record.snapshot;
532
+ if (identity == null) continue;
533
+ const { parentToolCallId } = record.address;
534
+ const checkpoints = await this.getLatestCheckpointSnapshot(identity.childThreadId);
535
+ if (checkpoints.length === 0) continue;
536
+ const configuredHookSessionId = activeRun?.invokeConfig?.configurable?.run_id;
537
+ const hookSessionId = typeof configuredHookSessionId === "string" && configuredHookSessionId.length > 0 ? configuredHookSessionId : this.parentRunId;
538
+ const approvalReplays = this.hookRegistry?.snapshotPendingToolApprovals(identity.approvalExecutionScope, identity.approvalExecutionScope) ?? [];
539
+ const descendant = activeRun?.pendingInterrupts.map((pendingInterrupt) => getSubagentResumeManifest(pendingInterrupt.value)).find((manifest) => manifest != null);
540
+ const graphState = activeRun?.graph.createSubagentResumeState(hookSessionId) ?? {
541
+ toolCallSteps: [],
542
+ toolSessions: [],
543
+ toolNodes: [],
544
+ eagerToolUsage: [],
545
+ eagerToolSuppressions: []
546
+ };
547
+ executions.push({
548
+ parentToolCallId,
549
+ childRunId: identity.childRunId,
550
+ ...binding?.subagentType == null ? {} : { subagentType: binding.subagentType },
551
+ ...binding?.configId == null ? {} : { configId: binding.configId },
552
+ approvalExecutionScope: identity.approvalExecutionScope,
553
+ checkpoints,
554
+ graphState,
555
+ approvalReplays,
556
+ ...descendant == null ? {} : { descendant }
557
+ });
558
+ }
559
+ if (executions.length === 0) return;
560
+ return {
561
+ version: 1,
562
+ executions
563
+ };
564
+ }
565
+ getResumeManifest(parentToolCallIds, config) {
566
+ return this.createResumeManifest(this.executions.selectForResume({
567
+ parentToolCallIds,
568
+ config
569
+ }));
570
+ }
571
+ getChildCheckpointThreadIds() {
572
+ return this.executions.getCheckpointThreadIds((activeRun) => this.getGraphChildCheckpointThreadIds(activeRun.graph));
573
+ }
574
+ resetCheckpointThreadIds() {
575
+ this.executions.resetCheckpointThreadIds();
576
+ }
577
+ getGraphChildCheckpointThreadIds(graph) {
578
+ return graph.getChildCheckpointThreadIds?.() ?? [];
579
+ }
580
+ clearChildGraph(graph) {
581
+ this.executions.rememberCheckpointThread(...this.getGraphChildCheckpointThreadIds(graph));
582
+ graph.clearHeavyState();
583
+ }
584
+ clearHeavyState() {
585
+ this.executions.clear((record) => {
586
+ if (record.activeRun != null) this.clearChildGraph(record.activeRun.graph);
587
+ if (record.identity != null) this.hookRegistry?.clearSession(record.identity.approvalExecutionScope);
588
+ });
589
+ this.replayCheckpointWorkflow = void 0;
590
+ }
591
+ async getResumeCheckpointMarker(resumeExecution, parentToolCallId) {
592
+ if (this.checkpointer == null) return;
593
+ let fallback;
594
+ for (const checkpoint of resumeExecution.checkpoints) {
595
+ const messages = getTupleMessages(await this.checkpointer.getTuple({ configurable: {
596
+ thread_id: checkpoint.threadId,
597
+ checkpoint_ns: checkpoint.checkpointNs,
598
+ checkpoint_id: checkpoint.checkpointId
599
+ } }));
600
+ const marker = getSubagentCheckpointMarker(messages, parentToolCallId);
601
+ if (marker?.settledOutput != null) return {
602
+ marker,
603
+ messages
604
+ };
605
+ if (marker != null && fallback == null) fallback = {
606
+ marker,
607
+ messages
608
+ };
609
+ }
610
+ return fallback;
611
+ }
612
+ async getSettledToolOutput(call, config) {
613
+ const parentToolCallId = call.id;
614
+ if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null || parentToolCallId == null || parentToolCallId === "") return;
615
+ const parentConfigurable = config.configurable;
616
+ const threadId = parentConfigurable?.thread_id;
617
+ const execution = this.executions.open({
618
+ threadId: typeof threadId === "string" ? threadId : void 0,
619
+ parentToolCallId,
620
+ parentConfigurable
621
+ });
622
+ const { resumeExecution } = execution;
623
+ const inProcessSettledOutput = execution.settledOutput;
624
+ if (inProcessSettledOutput != null) {
625
+ const persistedSubagentType = getSubagentTypeFromArgs(inProcessSettledOutput.resolvedArgs);
626
+ const subagentType = persistedSubagentType ?? execution.binding?.subagentType ?? getSubagentType(call);
627
+ if (!isResumeExecutionCompatible(resumeExecution, subagentType, subagentType == null ? void 0 : this.configs.get(subagentType), persistedSubagentType != null)) return;
628
+ return deserializeToolOutput(inProcessSettledOutput);
629
+ }
630
+ let resumeCheckpoint;
631
+ if (resumeExecution != null) resumeCheckpoint = await this.getResumeCheckpointMarker(resumeExecution, parentToolCallId);
632
+ let marker = resumeCheckpoint?.marker;
633
+ let messages = resumeCheckpoint?.messages ?? [];
634
+ let persistedSubagentType = getSubagentTypeFromArgs(marker?.settledOutput?.resolvedArgs);
635
+ let subagentType = persistedSubagentType ?? resumeExecution?.subagentType ?? getSubagentType(call);
636
+ let executableConfig = subagentType == null ? void 0 : this.configs.get(subagentType);
637
+ if (!isResumeExecutionCompatible(resumeExecution, subagentType, executableConfig, persistedSubagentType != null)) {
638
+ this.executions.remove(execution);
639
+ return;
640
+ }
641
+ const { childThreadId } = await this.resolveChildExecutionIdentity(execution);
642
+ if (marker == null) {
643
+ messages = getCheckpointMessages((await this.checkpointer.getTuple({ configurable: { thread_id: childThreadId } }))?.checkpoint.channel_values.messages);
644
+ marker = getSubagentCheckpointMarker(messages, parentToolCallId);
645
+ persistedSubagentType = getSubagentTypeFromArgs(marker?.settledOutput?.resolvedArgs);
646
+ if (persistedSubagentType != null) {
647
+ subagentType = persistedSubagentType;
648
+ executableConfig = this.configs.get(subagentType);
649
+ }
650
+ if (!isResumeExecutionCompatible(resumeExecution, subagentType, executableConfig, persistedSubagentType != null)) {
651
+ this.executions.remove(execution);
652
+ return;
653
+ }
654
+ }
655
+ const configId = executableConfig?.configId;
656
+ if (!this.bindExecutionDefinition(execution, {
657
+ ...subagentType == null ? {} : { subagentType },
658
+ ...configId == null ? {} : { configId }
659
+ }, persistedSubagentType != null || resumeExecution?.subagentType != null ? "effective" : "provisional")) return;
660
+ const persistedHookSessionId = marker?.hookSessionId ?? getSubagentHookSessionId(messages);
661
+ const currentHookSessionId = parentConfigurable?.run_id;
662
+ if (persistedHookSessionId != null && typeof currentHookSessionId === "string" && currentHookSessionId.length > 0) this.hookRegistry?.copySession(persistedHookSessionId, currentHookSessionId);
663
+ return marker?.settledOutput == null ? void 0 : deserializeToolOutput(marker.settledOutput);
664
+ }
665
+ async persistSettledToolOutput(call, config, settled) {
666
+ const parentToolCallId = call.id;
667
+ const checkpointer = this.checkpointer;
668
+ if (this.humanInTheLoop?.enabled !== true || checkpointer == null || parentToolCallId == null || parentToolCallId === "") return;
669
+ const parentConfigurable = config.configurable;
670
+ const threadId = parentConfigurable?.thread_id;
671
+ const execution = this.executions.open({
672
+ threadId: typeof threadId === "string" ? threadId : void 0,
673
+ parentToolCallId,
674
+ parentConfigurable
675
+ });
676
+ const { resumeExecution } = execution;
677
+ const resolvedSubagentType = getSubagentTypeFromArgs(settled.resolvedArgs);
678
+ const resolvedDescription = getSubagentDescriptionFromArgs(settled.resolvedArgs);
679
+ const boundInvocation = execution.invocation;
680
+ if (boundInvocation != null && resolvedDescription != null && resolvedDescription !== boundInvocation.description) return;
681
+ const description = boundInvocation?.description ?? resolvedDescription ?? getSubagentDescriptionFromArgs(call.args) ?? "No task description provided";
682
+ const subagentType = resolvedSubagentType ?? execution.binding?.subagentType ?? getSubagentType(call);
683
+ const executableConfig = subagentType == null ? void 0 : this.configs.get(subagentType);
684
+ const configId = resolvedSubagentType == null ? execution.binding?.configId ?? executableConfig?.configId ?? resumeExecution?.configId : executableConfig?.configId;
685
+ if (subagentType == null) return;
686
+ if (!isResumeExecutionCompatible(resumeExecution, subagentType, executableConfig)) {
687
+ this.executions.remove(execution);
688
+ return;
689
+ }
690
+ const persistedOutput = serializeToolOutput(settled);
691
+ const invocation = boundInvocation ?? {
692
+ description,
693
+ subagentType,
694
+ ...configId == null ? {} : { configId }
695
+ };
696
+ try {
697
+ await execution.settle({
698
+ definitionAuthority: resolvedSubagentType == null ? "provisional" : "effective",
699
+ fingerprint: getSettlementFingerprint(persistedOutput),
700
+ invocation,
701
+ subagentType,
702
+ ...configId == null ? {} : { configId }
703
+ }, persistedOutput, async () => {
704
+ const { childRunId, childThreadId } = await this.resolveChildExecutionIdentity(execution);
705
+ const activeChildRun = execution.activeRun;
706
+ if (activeChildRun != null) {
707
+ await this.persistChildCheckpointMarker(activeChildRun, parentToolCallId, persistedOutput);
708
+ this.clearChildGraph(activeChildRun.graph);
709
+ return;
710
+ }
711
+ this.replayCheckpointWorkflow ??= createReplayCheckpointWorkflow(checkpointer);
712
+ await this.replayCheckpointWorkflow.updateState({ configurable: { thread_id: childThreadId } }, { messages: [createSubagentCheckpointMarkerMessage({
713
+ version: 1,
714
+ parentToolCallId,
715
+ lifecycleComplete: true,
716
+ hookSessionId: typeof parentConfigurable?.run_id === "string" ? parentConfigurable.run_id : this.parentRunId,
717
+ childRunId,
718
+ settledOutput: persistedOutput
719
+ })] }, SUBAGENT_REPLAY_NODE);
720
+ });
721
+ } catch (error) {
722
+ if (error instanceof SubagentDefinitionBindingError || error instanceof SubagentInvocationBindingError || error instanceof SubagentSettlementBindingError) return;
723
+ throw error;
724
+ }
725
+ }
726
+ async persistChildCheckpointMarker(activeChildRun, parentToolCallId, settledOutput) {
727
+ if (this.humanInTheLoop?.enabled !== true || activeChildRun.workflow.updateState == null || activeChildRun.invokeConfig == null) return;
728
+ await activeChildRun.workflow.updateState(activeChildRun.invokeConfig, { messages: [createSubagentCheckpointMarkerMessage({
729
+ version: 1,
730
+ parentToolCallId,
731
+ lifecycleComplete: true,
732
+ hookSessionId: typeof activeChildRun.invokeConfig.configurable?.run_id === "string" ? activeChildRun.invokeConfig.configurable.run_id : this.parentRunId,
733
+ childRunId: activeChildRun.childRunId,
734
+ ...settledOutput == null ? {} : { settledOutput }
735
+ })] }, activeChildRun.checkpointNodeId);
736
+ }
737
+ execute(params) {
738
+ const executableConfig = this.configs.get(params.subagentType);
739
+ if (executableConfig == null) {
740
+ const available = [...this.configs.keys()].join(", ");
741
+ return Promise.resolve({
742
+ content: `Error: Unknown subagent type "${params.subagentType}". Available types: ${available}`,
743
+ messages: []
744
+ });
745
+ }
746
+ if (this.maxDepth <= 0) return Promise.resolve({
747
+ content: "Error: Maximum subagent nesting depth exceeded.",
748
+ messages: []
749
+ });
750
+ if (isGraphSubagentConfig(executableConfig) && this.humanInTheLoop?.enabled === true) return Promise.resolve({
751
+ content: "Error: Human-in-the-loop execution is not yet supported for graph subagents.",
752
+ messages: []
753
+ });
754
+ if (this.humanInTheLoop?.enabled === true && (params.parentToolCallId == null || params.parentToolCallId === "")) return Promise.resolve({
755
+ content: "Error: Resumable subagent execution requires a parent tool call ID.",
756
+ messages: []
757
+ });
758
+ const execution = this.executions.open({
759
+ threadId: params.threadId,
760
+ parentToolCallId: params.parentToolCallId ?? nanoid(8),
761
+ parentConfigurable: params.parentConfigurable
762
+ });
763
+ try {
764
+ return execution.execute({
765
+ description: params.description,
766
+ subagentType: params.subagentType,
767
+ ...executableConfig.configId == null ? {} : { configId: executableConfig.configId }
768
+ }, () => this.executeOnce(params, execution, executableConfig));
769
+ } catch (error) {
770
+ if (error instanceof SubagentDefinitionBindingError) return Promise.resolve({
771
+ content: SUBAGENT_CONFIG_CHANGED_MESSAGE,
772
+ messages: []
773
+ });
774
+ if (error instanceof SubagentInvocationBindingError) return Promise.resolve({
775
+ content: SUBAGENT_INVOCATION_CHANGED_MESSAGE,
776
+ messages: []
777
+ });
778
+ throw error;
779
+ }
780
+ }
781
+ async executeOnce(params, execution, executableConfig) {
82
782
  const { description, subagentType, threadId, parentToolCallId } = params;
83
783
  /** Captured ONCE per execution, preferring the controller the parent
84
784
  * tool batch captured at ITS entry (before PreToolUse hooks): a failed
@@ -88,39 +788,51 @@ var SubagentExecutor = class {
88
788
  * new run's breaker from an old child's stream-limit breach. Signal and
89
789
  * trip target both bind to this capture. */
90
790
  const childBreaker = params.breaker ?? this.resolveBreakerController();
91
- const childSignal = this.composeChildSignal(childBreaker);
92
- const config = this.configs.get(subagentType);
93
- if (!config) return {
94
- content: `Error: Unknown subagent type "${subagentType}". Available types: ${[...this.configs.keys()].join(", ")}`,
95
- messages: []
96
- };
97
- if (this.maxDepth <= 0) return {
98
- content: "Error: Maximum subagent nesting depth exceeded.",
791
+ const childSignal = this.composeChildSignal(childBreaker, params.signal);
792
+ const { parentToolCallId: executionSuffix } = execution.address;
793
+ const { resumeExecution } = execution;
794
+ if (!isResumeExecutionCompatible(resumeExecution, subagentType, executableConfig)) {
795
+ this.executions.remove(execution);
796
+ return {
797
+ content: SUBAGENT_CONFIG_CHANGED_MESSAGE,
798
+ messages: []
799
+ };
800
+ }
801
+ let identity;
802
+ try {
803
+ identity = await this.resolveChildExecutionIdentity(execution);
804
+ execution.assertUsable(childSignal);
805
+ } catch (error) {
806
+ if (error instanceof StreamLimitExceededError) throw error;
807
+ return {
808
+ content: SUBAGENT_RESOLUTION_ERROR_MESSAGE,
809
+ messages: []
810
+ };
811
+ }
812
+ const { childRunId, childThreadId, approvalExecutionScope } = identity;
813
+ if (!this.bindExecutionDefinition(execution, {
814
+ subagentType,
815
+ ...executableConfig.configId == null ? {} : { configId: executableConfig.configId }
816
+ }, "effective")) return {
817
+ content: SUBAGENT_CONFIG_CHANGED_MESSAGE,
99
818
  messages: []
100
819
  };
101
- const childAgentId = config.agentInputs.agentId || `${this.parentAgentId ?? "agent"}_sub_${nanoid(8)}`;
102
- if (this.hookRegistry?.hasHookFor("SubagentStart", this.parentRunId) === true) {
103
- const hookResult = await executeHooks({
104
- registry: this.hookRegistry,
105
- input: {
106
- hook_event_name: "SubagentStart",
107
- runId: this.parentRunId,
108
- threadId,
109
- parentAgentId: this.parentAgentId,
110
- agentId: childAgentId,
111
- agentType: subagentType,
112
- inputs: [new HumanMessage(description)]
113
- },
114
- sessionId: this.parentRunId,
115
- matchQuery: subagentType
116
- }).catch(() => HOOK_FALLBACK);
117
- /**
118
- * `ask` is treated identically to `deny` in the subagent context:
119
- * subagents are non-interactive, so there is no prompt path for `ask`.
120
- * Both decisions block execution and return a "Blocked" tool result.
121
- */
122
- if (hookResult.decision === "deny" || hookResult.decision === "ask") return {
123
- content: `Blocked: ${hookResult.reason ?? "Blocked by hook"}`,
820
+ const completedChildResult = execution.completedResult;
821
+ if (completedChildResult != null) return completedChildResult;
822
+ let config;
823
+ try {
824
+ config = await this.resolveExecutionConfig(executableConfig, execution, {
825
+ childRunId,
826
+ childSignal,
827
+ threadId,
828
+ parentToolCallId,
829
+ parentConfigurable: params.parentConfigurable
830
+ });
831
+ execution.assertUsable(childSignal);
832
+ } catch (error) {
833
+ if (error instanceof StreamLimitExceededError) throw error;
834
+ return {
835
+ content: SUBAGENT_RESOLUTION_ERROR_MESSAGE,
124
836
  messages: []
125
837
  };
126
838
  }
@@ -136,18 +848,47 @@ var SubagentExecutor = class {
136
848
  * recoverable "no tools" path for registry-but-no-handler configs.
137
849
  */
138
850
  const hasToolExecuteHandler = parentRegistry?.getHandler("on_tool_execute") != null;
139
- const childInputs = buildChildInputs(config, childAgentId, this.maxDepth, hasToolExecuteHandler);
140
- const childRunId = `${this.parentRunId}_sub_${nanoid(8)}`;
141
- const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
851
+ const childPlan = createChildGraphPlan({
852
+ config,
853
+ executionSuffix,
854
+ parentAgentId: this.parentAgentId,
855
+ parentMaxDepth: this.maxDepth,
856
+ keepToolDefinitions: hasToolExecuteHandler
857
+ });
858
+ const childAgentId = childPlan.subjectAgentId;
859
+ const currentHookSessionId = asNonEmptyString(params.parentConfigurable?.run_id) ?? this.executionContext.hookSessionId;
860
+ const childExecutionContext = {
861
+ rootRunId: this.executionContext.rootRunId,
862
+ hookSessionId: currentHookSessionId,
863
+ depth: this.executionContext.depth + 1,
864
+ ancestry: [...this.executionContext.ancestry, {
865
+ subagentRunId: childRunId,
866
+ subagentType,
867
+ subagentKind: childPlan.kind,
868
+ subagentAgentId: childAgentId,
869
+ parentRunId: this.parentRunId,
870
+ parentAgentId: this.parentAgentId,
871
+ parentToolCallId
872
+ }]
873
+ };
874
+ const memberRecursionLimit = (config.maxTurns ?? 25) * 3;
875
+ const recursionLimit = memberRecursionLimit * (childPlan.kind === "graph" ? childPlan.agents.length : 1);
142
876
  const hostUsageSink = this.usageSink;
143
- const childGraph = this.createChildGraph({
877
+ let subagentUsageSink;
878
+ if (hostUsageSink != null) subagentUsageSink = (event) => hostUsageSink({
879
+ ...event,
880
+ runId: this.executionContext.rootRunId
881
+ });
882
+ const cachedChildRun = execution.activeRun;
883
+ const childGraphInput = {
144
884
  runId: childRunId,
145
885
  signal: childSignal,
146
- agents: [childInputs],
886
+ agents: childPlan.agents,
147
887
  langfuse: this.langfuse,
148
888
  tokenCounter: this.tokenCounter,
149
889
  streamLimits: this.streamLimits,
150
890
  subagentScope: true,
891
+ subagentExecutionContext: childExecutionContext,
151
892
  /**
152
893
  * Forwarded so the child graph's own `SubagentExecutor` (created in
153
894
  * its `createAgentNode` when `allowNested` keeps subagentConfigs)
@@ -155,39 +896,52 @@ var SubagentExecutor = class {
155
896
  * level attaches its own capture callback — `workflow.invoke` replaces
156
897
  * the inherited callback chain, so a single top-level handler would
157
898
  * never see grandchild model calls.
158
- *
159
- * The wrapper rewrites `runId` to THIS executor's parent run: nested
160
- * executors emit with their own `parentRunId` (a `*_sub_*` child id),
161
- * and each wrapper layer rewrites upward, so by the time an event
162
- * reaches the host sink its `runId` is the ROOT run — hosts keying
163
- * billing by run id never see intermediate child run ids there
164
- * (`subagentRunId` still identifies the emitting child).
165
899
  */
166
- subagentUsageSink: hostUsageSink == null ? void 0 : (event) => hostUsageSink({
167
- ...event,
168
- runId: this.parentRunId
169
- })
170
- });
900
+ subagentUsageSink
901
+ };
902
+ let childGraph = cachedChildRun?.graph;
903
+ if (childGraph == null && childPlan.kind === "graph") {
904
+ if (this.createChildGraphByKind == null) return {
905
+ content: "Error: Graph subagent execution requires a polymorphic child graph factory.",
906
+ messages: []
907
+ };
908
+ childGraph = this.createChildGraphByKind({
909
+ kind: "multi-agent",
910
+ input: {
911
+ ...childGraphInput,
912
+ edges: childPlan.edges,
913
+ resultAgentId: childPlan.resultAgentId,
914
+ memberRecursionLimit
915
+ }
916
+ });
917
+ }
918
+ childGraph ??= this.createChildGraph(childGraphInput);
171
919
  let forwarding;
172
920
  if (forwardingEnabled) forwarding = this.createForwarderCallback({
173
921
  parentRegistry,
174
922
  subagentType,
923
+ subagentKind: childPlan.kind,
175
924
  subagentAgentId: childAgentId,
176
925
  childRunId,
177
- parentToolCallId
178
- });
179
- const forwarder = forwarding?.handler;
180
- if (forwarder) await this.emitSubagentUpdate(parentRegistry, {
181
- childRunId,
182
- subagentType,
183
- subagentAgentId: childAgentId,
184
926
  parentToolCallId,
185
- phase: "start",
186
- label: `Subagent "${subagentType}" started`
927
+ executionContext: childExecutionContext
187
928
  });
929
+ const forwarder = forwarding?.handler;
930
+ let childAlreadyStarted = execution.started;
931
+ let childAlreadyCompleted = execution.completed;
188
932
  let result;
933
+ let recoveredComplete = false;
934
+ let recoveredInProgress = false;
189
935
  try {
190
- const workflow = childGraph.createWorkflow();
936
+ const workflow = cachedChildRun?.workflow ?? childGraph.createWorkflow();
937
+ const activeChildRun = execution.activate(cachedChildRun ?? {
938
+ graph: childGraph,
939
+ workflow,
940
+ pendingInterrupts: [],
941
+ childAgentId,
942
+ checkpointNodeId: childPlan.checkpointNodeId,
943
+ childRunId
944
+ });
191
945
  /**
192
946
  * When `parentHandlerRegistry` is provided (forwarding mode), attach a
193
947
  * lightweight callback that intercepts the child's `on_custom_event`
@@ -217,54 +971,149 @@ var SubagentExecutor = class {
217
971
  if (this.usageSink) callbackHandlers.push(createUsageCaptureHandler({
218
972
  sink: this.usageSink,
219
973
  subagentType,
974
+ subagentKind: childPlan.kind,
220
975
  subagentRunId: childRunId,
221
976
  subagentAgentId: childAgentId,
222
977
  parentRunId: this.parentRunId,
223
- provider: config.agentInputs.provider,
224
- fallbackModel: extractConfiguredModel(config.agentInputs)
978
+ executionContext: childExecutionContext,
979
+ memberInputs: childPlan.memberInputs
225
980
  }));
226
981
  const callbacks = callbackHandlers;
227
982
  /**
228
- * Inherit the parent's host `configurable` host-set fields
229
- * (`requestBody`, `user`, `userMCPAuthMap`, etc.) AND the run-
230
- * identity fields (`run_id`, `parent_run_id`, `thread_id`) all
231
- * propagate. LangGraph's own runtime keys are excluded because the
232
- * child graph creates its own scratchpad/checkpoint/abort plumbing.
233
- *
234
- * Run-identity propagation is intentional and matches the
235
- * convention this executor itself already uses for `SubagentStart`
236
- * / `SubagentStop` hooks (`sessionId: this.parentRunId`): the
237
- * subagent runs under the parent's session scope, not its own.
238
- * Forwarding `run_id` / `parent_run_id` / `thread_id` makes
239
- * `ToolNode`'s hook lookups (`hasHookFor(eventName, runId)`),
240
- * `ToolOutputReferenceRegistry` keying, and trace lineage all
241
- * resolve to the parent's session for tools dispatched from the
242
- * subagent — so `PreToolUse` / `PostToolUse` hooks the host
243
- * registered against the parent's run fire for subagent tool
244
- * calls too. "Same run" matches the user-perceptual mental model.
983
+ * Inherit the parent's host `configurable` while binding LangGraph's
984
+ * checkpoint identity to a stable child id derived from the durable
985
+ * parent thread and checkpoint fork. The parent thread id cannot be
986
+ * reused here: parent and child share one checkpointer when nested HITL
987
+ * is enabled, and root checkpoint namespaces are normalized by
988
+ * LangGraph, so a shared `thread_id` would collide with the parent.
245
989
  *
246
- * `thread_id` falls back to `childRunId` only when the parent
247
- * didn't supply one (legacy behavior preserved for hosts that
248
- * never set thread_id).
249
- *
250
- * NOTE: a future revision will likely make this configurable per
251
- * spawn type — e.g. a background / async subagent that runs after
252
- * the parent's run completes wants isolation, not inheritance.
253
- * For now the inheritance path matches LibreChat's primary use
254
- * case (synchronous subagents within a single user turn).
990
+ * `run_id` still propagates as the parent run id, which is the key used
991
+ * for session-scoped hook lookup. Child hook inputs therefore retain
992
+ * the parent policy scope while their `threadId` truthfully identifies
993
+ * the independently checkpointed child execution.
255
994
  */
256
995
  const inheritedConfigurable = sanitizeChildConfigurable(params.parentConfigurable);
257
- result = await workflow.invoke({ messages: [new HumanMessage(description)] }, {
258
- recursionLimit: maxTurns * RECURSION_MULTIPLIER,
996
+ const { resumeAttemptId } = execution.address;
997
+ if (cachedChildRun == null && resumeExecution != null) childGraph.restoreSubagentResumeState(resumeExecution.graphState, currentHookSessionId);
998
+ const childConfigurable = { ...inheritedConfigurable };
999
+ if (this.humanInTheLoop?.enabled === true) childConfigurable[TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY] = approvalExecutionScope;
1000
+ if (resumeExecution?.descendant != null) {
1001
+ childConfigurable[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY] = resumeExecution.descendant;
1002
+ childConfigurable[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY] = resumeAttemptId;
1003
+ }
1004
+ childConfigurable.thread_id = this.humanInTheLoop?.enabled === true ? childThreadId : inheritedConfigurable.thread_id ?? childRunId;
1005
+ const childInvokeConfig = {
1006
+ recursionLimit,
259
1007
  signal: childSignal,
260
1008
  callbacks,
261
1009
  runName: `subagent:${subagentType}`,
262
- configurable: {
263
- thread_id: childRunId,
264
- ...inheritedConfigurable
1010
+ configurable: childConfigurable
1011
+ };
1012
+ activeChildRun.invokeConfig = childInvokeConfig;
1013
+ if (cachedChildRun == null && this.humanInTheLoop?.enabled === true) {
1014
+ /** Rehydrate child-owned interrupt state when a host rebuilds Run
1015
+ * around the same durable checkpointer after a process boundary. */
1016
+ const persistedState = await workflow.getState(childInvokeConfig);
1017
+ const checkpointMessages = getCheckpointMessages(persistedState.values?.messages);
1018
+ const persistedHookSessionId = getSubagentHookSessionId(checkpointMessages);
1019
+ if (persistedHookSessionId != null) this.hookRegistry?.copySession(persistedHookSessionId, currentHookSessionId);
1020
+ const persistedInterrupts = getPersistedInterrupts(persistedState);
1021
+ if (persistedInterrupts.length > 0) {
1022
+ activeChildRun.pendingInterrupts = persistedInterrupts;
1023
+ execution.markStarted();
1024
+ childAlreadyStarted = true;
1025
+ } else if (persistedState.next.length > 0) {
1026
+ recoveredInProgress = true;
1027
+ childAlreadyStarted = true;
1028
+ execution.markStarted();
1029
+ } else if (persistedState.next.length === 0) {
1030
+ const persistedMessages = getPersistedMessages(persistedState);
1031
+ if (persistedMessages != null) {
1032
+ const marker = getSubagentCheckpointMarker(checkpointMessages, executionSuffix);
1033
+ result = { messages: persistedMessages };
1034
+ recoveredComplete = true;
1035
+ childAlreadyStarted = true;
1036
+ childAlreadyCompleted = marker?.lifecycleComplete === true;
1037
+ execution.markStarted();
1038
+ }
265
1039
  }
266
- });
1040
+ }
1041
+ if (!recoveredComplete) {
1042
+ const childResumeMap = getChildResumeMap(activeChildRun.pendingInterrupts, params.parentConfigurable);
1043
+ if (activeChildRun.pendingInterrupts.length > 0 && childResumeMap == null) throw new GraphInterrupt(activeChildRun.pendingInterrupts);
1044
+ let childInput;
1045
+ if (childResumeMap != null) childInput = new Command({ resume: childResumeMap });
1046
+ else if (recoveredInProgress) childInput = null;
1047
+ else childInput = { messages: [new HumanMessage({
1048
+ content: description,
1049
+ additional_kwargs: {
1050
+ [SUBAGENT_HOOK_SESSION_KEY]: currentHookSessionId,
1051
+ [SUBAGENT_RUN_ID_KEY]: childRunId
1052
+ }
1053
+ })] };
1054
+ if (!childAlreadyStarted && this.hookRegistry?.hasHookFor("SubagentStart", currentHookSessionId) === true) {
1055
+ const hookResult = await executeHooks({
1056
+ registry: this.hookRegistry,
1057
+ input: {
1058
+ hook_event_name: "SubagentStart",
1059
+ runId: currentHookSessionId,
1060
+ threadId,
1061
+ parentAgentId: this.parentAgentId,
1062
+ agentId: childAgentId,
1063
+ agentType: subagentType,
1064
+ inputs: [new HumanMessage(description)]
1065
+ },
1066
+ sessionId: currentHookSessionId,
1067
+ matchQuery: subagentType
1068
+ }).catch(() => HOOK_FALLBACK);
1069
+ if (hookResult.decision === "deny" || hookResult.decision === "ask") {
1070
+ this.clearChildGraph(childGraph);
1071
+ execution.releaseActiveRun();
1072
+ this.executions.remove(execution);
1073
+ return {
1074
+ content: `Blocked: ${hookResult.reason ?? "Blocked by hook"}`,
1075
+ messages: []
1076
+ };
1077
+ }
1078
+ }
1079
+ execution.markStarted();
1080
+ if (forwarder && !childAlreadyStarted) await this.emitSubagentUpdate(parentRegistry, {
1081
+ childRunId,
1082
+ subagentType,
1083
+ subagentKind: childPlan.kind,
1084
+ subagentAgentId: childAgentId,
1085
+ parentToolCallId,
1086
+ executionContext: childExecutionContext,
1087
+ phase: "start",
1088
+ label: `Subagent "${subagentType}" started`
1089
+ });
1090
+ let childResult;
1091
+ if (this.humanInTheLoop?.enabled === true)
1092
+ /** Execute as an independently checkpointed root instead of inheriting
1093
+ * the parent's Pregel namespace. Parent decisions are routed explicitly
1094
+ * by interrupt id, so concurrent children keep isolated resume state. */
1095
+ childResult = await AsyncLocalStorageProviderSingleton.runWithConfig(childInvokeConfig, () => workflow.invoke(childInput, childInvokeConfig));
1096
+ else childResult = await workflow.invoke(childInput, childInvokeConfig);
1097
+ const childInterrupts = isInterrupted(childResult) ? childResult[INTERRUPT] : void 0;
1098
+ if (childInterrupts != null && childInterrupts.length > 0) throw new GraphInterrupt(childInterrupts);
1099
+ result = childResult;
1100
+ }
267
1101
  } catch (error) {
1102
+ if (isGraphInterrupt(error)) {
1103
+ const activeChildRun = execution.activeRun;
1104
+ if (activeChildRun != null) {
1105
+ activeChildRun.pendingInterrupts = error.interrupts;
1106
+ execution.markInterrupted();
1107
+ }
1108
+ const resumeManifest = activeChildRun == null || parentToolCallId == null ? void 0 : await this.createResumeManifest([execution]);
1109
+ await forwarding?.drain();
1110
+ throw new GraphInterrupt(addSubagentScope(error.interrupts, {
1111
+ run_id: childRunId,
1112
+ agent_id: childAgentId,
1113
+ subagent_type: subagentType,
1114
+ parent_tool_call_id: parentToolCallId
1115
+ }, resumeManifest));
1116
+ }
268
1117
  /** Aborted before any observational work below: parallel siblings — in
269
1118
  * this executor and, via the graph-scoped breaker, under other
270
1119
  * parallel agent nodes — stream on the composed child signal, and
@@ -279,14 +1128,17 @@ var SubagentExecutor = class {
279
1128
  await this.emitSubagentUpdate(parentRegistry, {
280
1129
  childRunId,
281
1130
  subagentType,
1131
+ subagentKind: childPlan.kind,
282
1132
  subagentAgentId: childAgentId,
283
1133
  parentToolCallId,
1134
+ executionContext: childExecutionContext,
284
1135
  phase: "error",
285
1136
  label: `Subagent "${subagentType}" errored: ${errorMessage}`,
286
1137
  data: { message: errorMessage }
287
1138
  });
288
1139
  }
289
- childGraph.clearHeavyState();
1140
+ this.clearChildGraph(childGraph);
1141
+ execution.markFailed();
290
1142
  /**
291
1143
  * A tripped stream circuit breaker is a safety abort, not a recoverable
292
1144
  * subagent failure: converting it into a tool result would let the
@@ -300,8 +1152,9 @@ var SubagentExecutor = class {
300
1152
  messages: []
301
1153
  };
302
1154
  }
303
- const filteredContent = filterSubagentResult(result.messages);
304
- if (this.hookRegistry?.hasHookFor("SubagentStop", this.parentRunId) === true)
1155
+ if (result == null) throw new Error("Subagent completed without producing graph state.");
1156
+ const filteredContent = childPlan.kind === "graph" ? filterGraphSubagentResult(result, childPlan.resultAgentId) : filterSubagentResult(result.messages);
1157
+ if (!childAlreadyCompleted && this.hookRegistry?.hasHookFor("SubagentStop", currentHookSessionId) === true)
305
1158
  /**
306
1159
  * Awaited (not fire-and-forget) for deterministic test synchronization
307
1160
  * and consistency with PostCompact. The parent is already waiting on the
@@ -312,31 +1165,39 @@ var SubagentExecutor = class {
312
1165
  registry: this.hookRegistry,
313
1166
  input: {
314
1167
  hook_event_name: "SubagentStop",
315
- runId: this.parentRunId,
1168
+ runId: currentHookSessionId,
316
1169
  threadId,
317
1170
  agentId: childAgentId,
318
1171
  agentType: subagentType,
319
1172
  messages: result.messages
320
1173
  },
321
- sessionId: this.parentRunId,
1174
+ sessionId: currentHookSessionId,
322
1175
  matchQuery: subagentType
323
1176
  }).catch(() => {});
324
- if (forwarding) {
1177
+ if (forwarding && !childAlreadyCompleted) {
325
1178
  await forwarding.drain();
326
1179
  await this.emitSubagentUpdate(parentRegistry, {
327
1180
  childRunId,
328
1181
  subagentType,
1182
+ subagentKind: childPlan.kind,
329
1183
  subagentAgentId: childAgentId,
330
1184
  parentToolCallId,
1185
+ executionContext: childExecutionContext,
331
1186
  phase: "stop",
332
1187
  label: `Subagent "${subagentType}" finished`
333
1188
  });
334
1189
  }
335
- childGraph.clearHeavyState();
336
- return {
1190
+ if (!childAlreadyCompleted) {
1191
+ const activeChildRun = execution.activeRun;
1192
+ if (activeChildRun != null && parentToolCallId != null) await this.persistChildCheckpointMarker(activeChildRun, parentToolCallId);
1193
+ }
1194
+ this.clearChildGraph(childGraph);
1195
+ const completedResult = {
337
1196
  content: filteredContent,
338
1197
  messages: result.messages
339
1198
  };
1199
+ execution.markCompleted(completedResult);
1200
+ return completedResult;
340
1201
  }
341
1202
  /**
342
1203
  * Emits a single {@link GraphEvents.ON_SUBAGENT_UPDATE} envelope through the
@@ -346,21 +1207,23 @@ var SubagentExecutor = class {
346
1207
  async emitSubagentUpdate(parentRegistry, args) {
347
1208
  const handler = parentRegistry.getHandler("on_subagent_update");
348
1209
  if (!handler) return;
349
- const event = {
350
- runId: this.parentRunId,
1210
+ await dispatchObservationalSubagentUpdate(handler, {
1211
+ runId: args.executionContext.rootRunId,
1212
+ parentRunId: this.parentRunId,
351
1213
  subagentRunId: args.childRunId,
352
1214
  subagentType: args.subagentType,
1215
+ subagentKind: args.subagentKind,
353
1216
  subagentAgentId: args.subagentAgentId,
1217
+ memberAgentId: args.memberAgentId,
354
1218
  parentAgentId: this.parentAgentId,
355
1219
  parentToolCallId: args.parentToolCallId,
1220
+ depth: args.executionContext.depth,
1221
+ ancestry: args.executionContext.ancestry,
356
1222
  phase: args.phase,
357
1223
  data: args.data,
358
1224
  label: args.label,
359
1225
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
360
- };
361
- try {
362
- await handler.handle("on_subagent_update", event);
363
- } catch {}
1226
+ });
364
1227
  }
365
1228
  /**
366
1229
  * Builds a BaseCallbackHandler that intercepts the child graph's custom
@@ -375,35 +1238,40 @@ var SubagentExecutor = class {
375
1238
  * cares about; host apps can extend by registering more phases).
376
1239
  */
377
1240
  createForwarderCallback(args) {
378
- const { parentRegistry, subagentType, subagentAgentId, childRunId, parentToolCallId } = args;
379
- const parentRunId = this.parentRunId;
1241
+ const { parentRegistry, subagentType, subagentKind, subagentAgentId, childRunId, parentToolCallId, executionContext } = args;
1242
+ const immediateParentRunId = this.parentRunId;
380
1243
  const parentAgentId = this.parentAgentId;
381
- const wrap = async (eventName, phase, data) => {
1244
+ const wrap = async (eventName, phase, data, memberAgentId) => {
382
1245
  const handler = parentRegistry.getHandler("on_subagent_update");
383
- if (!handler) return;
384
- try {
385
- const event = {
386
- runId: parentRunId,
387
- subagentRunId: childRunId,
388
- subagentType,
389
- subagentAgentId,
390
- parentAgentId,
391
- parentToolCallId,
392
- phase,
393
- data: sanitizeForwardedSubagentUpdateData(eventName, data),
394
- label: summarizeEvent(eventName, data),
395
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
396
- };
397
- await handler.handle("on_subagent_update", event);
398
- } catch {}
1246
+ if (!handler) return true;
1247
+ return dispatchObservationalSubagentUpdate(handler, {
1248
+ runId: executionContext.rootRunId,
1249
+ parentRunId: immediateParentRunId,
1250
+ subagentRunId: childRunId,
1251
+ subagentType,
1252
+ subagentKind,
1253
+ subagentAgentId,
1254
+ memberAgentId,
1255
+ parentAgentId,
1256
+ parentToolCallId,
1257
+ depth: executionContext.depth,
1258
+ ancestry: executionContext.ancestry,
1259
+ phase,
1260
+ data: sanitizeForwardedSubagentUpdateData(eventName, data),
1261
+ label: summarizeEvent(eventName, data),
1262
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1263
+ });
399
1264
  };
400
1265
  const queuedUpdates = [];
401
1266
  let drainPromise;
1267
+ let queueOpen = true;
402
1268
  const enqueue = (update) => {
403
- if (queuedUpdates.length >= MAX_PENDING_SUBAGENT_UPDATES) {
404
- const dropIndex = queuedUpdates.findIndex((queued) => isDroppableSubagentUpdatePhase(queued.phase));
1269
+ if (!queueOpen) return;
1270
+ if (queuedUpdates.length >= MAX_QUEUED_SUBAGENT_UPDATES) {
1271
+ const dropIndex = queuedUpdates.findIndex((queued) => isLowPrioritySubagentUpdatePhase(queued.phase));
405
1272
  if (dropIndex >= 0) queuedUpdates.splice(dropIndex, 1);
406
- else if (isDroppableSubagentUpdatePhase(update.phase)) return;
1273
+ else if (isLowPrioritySubagentUpdatePhase(update.phase)) return;
1274
+ else queuedUpdates.shift();
407
1275
  }
408
1276
  queuedUpdates.push(update);
409
1277
  };
@@ -416,7 +1284,10 @@ var SubagentExecutor = class {
416
1284
  while (queuedUpdates.length > 0) {
417
1285
  const update = queuedUpdates.shift();
418
1286
  if (update == null) continue;
419
- await wrap(update.eventName, update.phase, update.data);
1287
+ if (!await wrap(update.eventName, update.phase, update.data, update.memberAgentId)) {
1288
+ queueOpen = false;
1289
+ queuedUpdates.length = 0;
1290
+ }
420
1291
  }
421
1292
  })();
422
1293
  try {
@@ -426,15 +1297,17 @@ var SubagentExecutor = class {
426
1297
  if (queuedUpdates.length > 0) await drain();
427
1298
  }
428
1299
  };
429
- const scheduleWrap = (eventName, phase, data) => {
1300
+ const scheduleWrap = (eventName, phase, data, memberAgentId) => {
430
1301
  enqueue({
431
1302
  eventName,
432
1303
  phase,
433
- data
1304
+ data,
1305
+ memberAgentId
434
1306
  });
435
1307
  drain();
436
1308
  };
437
- const handler = BaseCallbackHandler.fromMethods({ ["handleCustomEvent"]: async (eventName, data) => {
1309
+ const handler = BaseCallbackHandler.fromMethods({ ["handleCustomEvent"]: async (eventName, data, _runId, _tags, metadata) => {
1310
+ const memberAgentId = asNonEmptyString(metadata?.agentId) ?? getEventAgentId(data);
438
1311
  if (eventName === "on_tool_execute") {
439
1312
  const toolHandler = parentRegistry.getHandler("on_tool_execute");
440
1313
  if (toolHandler) await toolHandler.handle("on_tool_execute", data);
@@ -442,27 +1315,27 @@ var SubagentExecutor = class {
442
1315
  * We also surface a short notice in the subagent-update stream so
443
1316
  * the UI can show "calling <tool>" for each tool the child spawns.
444
1317
  */
445
- scheduleWrap(eventName, "run_step", data);
1318
+ scheduleWrap(eventName, "run_step", data, memberAgentId);
446
1319
  return;
447
1320
  }
448
1321
  if (eventName === "on_run_step") {
449
- scheduleWrap(eventName, "run_step", data);
1322
+ scheduleWrap(eventName, "run_step", data, memberAgentId);
450
1323
  return;
451
1324
  }
452
1325
  if (eventName === "on_run_step_delta") {
453
- scheduleWrap(eventName, "run_step_delta", data);
1326
+ scheduleWrap(eventName, "run_step_delta", data, memberAgentId);
454
1327
  return;
455
1328
  }
456
1329
  if (eventName === "on_run_step_completed") {
457
- scheduleWrap(eventName, "run_step_completed", data);
1330
+ scheduleWrap(eventName, "run_step_completed", data, memberAgentId);
458
1331
  return;
459
1332
  }
460
1333
  if (eventName === "on_message_delta") {
461
- scheduleWrap(eventName, "message_delta", data);
1334
+ scheduleWrap(eventName, "message_delta", data, memberAgentId);
462
1335
  return;
463
1336
  }
464
1337
  if (eventName === "on_reasoning_delta") {
465
- scheduleWrap(eventName, "reasoning_delta", data);
1338
+ scheduleWrap(eventName, "reasoning_delta", data, memberAgentId);
466
1339
  return;
467
1340
  }
468
1341
  } });
@@ -496,7 +1369,8 @@ var SubagentExecutor = class {
496
1369
  * `subagentUsageSink` on the child graph's input.
497
1370
  */
498
1371
  function createUsageCaptureHandler(args) {
499
- const { sink, subagentType, subagentRunId, subagentAgentId, parentRunId, provider, fallbackModel } = args;
1372
+ const { sink, subagentType, subagentKind, subagentRunId, subagentAgentId, parentRunId, executionContext, memberInputs } = args;
1373
+ const defaultMember = memberInputs.size === 1 ? memberInputs.entries().next().value : void 0;
500
1374
  /**
501
1375
  * Per-call attribution keyed by LangChain callback runId. `model` joins
502
1376
  * `ls_model_name` (provider-reported) with `INVOKED_MODEL` (stamped by
@@ -511,16 +1385,21 @@ function createUsageCaptureHandler(args) {
511
1385
  handleChatModelStart: (_llm, _messages, runId, _parentRunId, _extraParams, _tags, metadata) => {
512
1386
  const callModel = asNonEmptyString(metadata?.ls_model_name) ?? asNonEmptyString(metadata?.["__invoked_model"]);
513
1387
  const callProvider = asNonEmptyString(metadata?.["__invoked_provider"]);
514
- if (callModel != null || callProvider != null) callInfoByCallId.set(runId, {
1388
+ const memberAgentId = asNonEmptyString(metadata?.agentId);
1389
+ if (callModel != null || callProvider != null || memberAgentId != null) callInfoByCallId.set(runId, {
515
1390
  model: callModel,
516
- provider: callProvider
1391
+ provider: callProvider,
1392
+ memberAgentId
517
1393
  });
518
1394
  },
519
1395
  handleLLMEnd: async (output, runId) => {
520
1396
  const callInfo = callInfoByCallId.get(runId);
521
1397
  callInfoByCallId.delete(runId);
522
- const model = callInfo?.model ?? fallbackModel;
523
- const callProvider = callInfo?.provider ?? provider;
1398
+ const observedMemberAgentId = callInfo?.memberAgentId;
1399
+ const memberAgentId = observedMemberAgentId != null && memberInputs.has(observedMemberAgentId) ? observedMemberAgentId : defaultMember?.[0];
1400
+ const memberInput = (memberAgentId == null ? void 0 : memberInputs.get(memberAgentId)) ?? defaultMember?.[1];
1401
+ const model = callInfo?.model ?? (memberInput == null ? void 0 : extractConfiguredModel(memberInput));
1402
+ const callProvider = callInfo?.provider ?? memberInput?.provider;
524
1403
  for (const generationGroup of output.generations)
525
1404
  /**
526
1405
  * At most ONE event per generation group: each group is one
@@ -545,9 +1424,14 @@ function createUsageCaptureHandler(args) {
545
1424
  model,
546
1425
  provider: callProvider,
547
1426
  subagentType,
1427
+ subagentKind,
548
1428
  subagentRunId,
549
1429
  subagentAgentId,
550
- runId: parentRunId
1430
+ memberAgentId,
1431
+ parentRunId,
1432
+ depth: executionContext.depth,
1433
+ ancestry: executionContext.ancestry,
1434
+ runId: executionContext.rootRunId
551
1435
  });
552
1436
  } catch {}
553
1437
  break;
@@ -568,6 +1452,11 @@ function createUsageCaptureHandler(args) {
568
1452
  function asNonEmptyString(value) {
569
1453
  return typeof value === "string" && value !== "" ? value : void 0;
570
1454
  }
1455
+ function getEventAgentId(data) {
1456
+ if (data == null || typeof data !== "object") return;
1457
+ const event = data;
1458
+ return asNonEmptyString(event.agentId) ?? asNonEmptyString(event.result?.agentId);
1459
+ }
571
1460
  /**
572
1461
  * Best-effort read of the configured model from a subagent's client
573
1462
  * options. Providers disagree on the key (`model` vs `modelName`), and the
@@ -578,12 +1467,50 @@ function extractConfiguredModel(agentInputs) {
578
1467
  if (typeof clientOptions?.model === "string" && clientOptions.model !== "") return clientOptions.model;
579
1468
  if (typeof clientOptions?.modelName === "string" && clientOptions.modelName !== "") return clientOptions.modelName;
580
1469
  }
1470
+ function sanitizeResolverConfigurable(parentConfigurable) {
1471
+ const configurable = sanitizeChildConfigurable(parentConfigurable);
1472
+ const requestBody = createResolveRequestContext(configurable.requestBody);
1473
+ const user = createResolveUserContext(configurable.user);
1474
+ const userId = asNonEmptyString(configurable.user_id) ?? user?.id;
1475
+ if (requestBody == null && user == null && userId == null) return;
1476
+ return {
1477
+ ...requestBody == null ? {} : { requestBody },
1478
+ ...user == null ? {} : { user },
1479
+ ...userId == null ? {} : { user_id: userId }
1480
+ };
1481
+ }
1482
+ function createResolveRequestContext(value) {
1483
+ if (!isObjectLike(value)) return;
1484
+ const source = value;
1485
+ const conversationId = asNonEmptyString(source.conversationId);
1486
+ const messageId = asNonEmptyString(source.messageId);
1487
+ const parentMessageId = asNonEmptyString(source.parentMessageId);
1488
+ if (conversationId == null && messageId == null && parentMessageId == null) return;
1489
+ return {
1490
+ ...conversationId == null ? {} : { conversationId },
1491
+ ...messageId == null ? {} : { messageId },
1492
+ ...parentMessageId == null ? {} : { parentMessageId }
1493
+ };
1494
+ }
1495
+ function createResolveUserContext(value) {
1496
+ if (!isObjectLike(value)) return;
1497
+ const source = value;
1498
+ const id = asNonEmptyString(source.id);
1499
+ const role = asNonEmptyString(source.role);
1500
+ const tenantId = asNonEmptyString(source.tenantId);
1501
+ if (id == null && role == null && tenantId == null) return;
1502
+ return {
1503
+ ...id == null ? {} : { id },
1504
+ ...role == null ? {} : { role },
1505
+ ...tenantId == null ? {} : { tenantId }
1506
+ };
1507
+ }
581
1508
  function sanitizeChildConfigurable(parentConfigurable) {
582
1509
  if (parentConfigurable == null) return {};
583
1510
  return Object.fromEntries(Object.entries(parentConfigurable).filter(([key]) => !isLangGraphRuntimeConfigKey(key)));
584
1511
  }
585
1512
  function isLangGraphRuntimeConfigKey(key) {
586
- return key.startsWith(LANGGRAPH_RUNTIME_CONFIG_PREFIX) || LANGGRAPH_CHECKPOINT_CONFIG_KEYS.has(key) || key === "lc_run_breaker_scope";
1513
+ 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 === "__librechat_tool_approval_execution_scope" || key === "lc_run_breaker_scope";
587
1514
  }
588
1515
  function sanitizeForwardedSubagentUpdateData(eventName, data) {
589
1516
  if (eventName === "on_tool_execute") return sanitizeToolExecuteUpdateData(data);
@@ -593,7 +1520,7 @@ function sanitizeForwardedSubagentUpdateData(eventName, data) {
593
1520
  if (eventName === "on_message_delta") return sanitizeMessageDeltaUpdateData(data);
594
1521
  if (eventName === "on_reasoning_delta") return sanitizeReasoningDeltaUpdateData(data);
595
1522
  }
596
- function isDroppableSubagentUpdatePhase(phase) {
1523
+ function isLowPrioritySubagentUpdatePhase(phase) {
597
1524
  return phase === "message_delta" || phase === "reasoning_delta" || phase === "run_step_delta";
598
1525
  }
599
1526
  function sanitizeToolExecuteUpdateData(data) {
@@ -762,6 +1689,16 @@ function summarizeEvent(eventName, data) {
762
1689
  return eventName;
763
1690
  }
764
1691
  /**
1692
+ * Reads only the result member captured by MultiAgentGraph. Worker output is
1693
+ * never used as a fallback when the designated result member is textless.
1694
+ */
1695
+ function filterGraphSubagentResult(state, resultAgentId) {
1696
+ const result = state.subagentResult;
1697
+ if (result == null || result.agentId !== resultAgentId) return `Error: Graph subagent result agent "${resultAgentId}" did not produce a result.`;
1698
+ if (result.message == null) return "Task completed";
1699
+ return filterSubagentResult([result.message]);
1700
+ }
1701
+ /**
765
1702
  * Walk messages from last to first, returning the text content of the most
766
1703
  * recent AIMessage that has any. Non-text blocks (tool_use, thinking,
767
1704
  * redacted_thinking, tool_result) are stripped. If the last AIMessage is
@@ -818,89 +1755,33 @@ function filterSubagentResult(messages) {
818
1755
  }
819
1756
  return "Task completed";
820
1757
  }
821
- /**
822
- * Resolve self-spawn configs by filling in agentInputs from the parent context.
823
- * Returns configs with agentInputs guaranteed present. Throws on duplicate
824
- * `type` values to prevent silent config shadowing.
825
- */
826
- function resolveSubagentConfigs(configs, parentContext) {
827
- const resolved = configs.map((config) => {
828
- if (config.agentInputs != null) return config;
829
- if (config.self !== true || parentContext._sourceInputs == null) return null;
830
- return {
831
- ...config,
832
- agentInputs: { ...parentContext._sourceInputs }
833
- };
834
- }).filter((c) => c != null);
835
- const seenTypes = /* @__PURE__ */ new Set();
836
- for (const config of resolved) {
837
- if (seenTypes.has(config.type)) throw new Error(`Duplicate subagent type "${config.type}". Each SubagentConfig must have a unique "type" field.`);
838
- seenTypes.add(config.type);
839
- }
840
- return resolved;
841
- }
842
- /**
843
- * Build child AgentInputs from a resolved config, stripping nesting and
844
- * (optionally) event-driven fields. When `allowNested: true`, the child's
845
- * `maxSubagentDepth` is decremented so that depth is consumed as the call
846
- * chain deepens across graph boundaries — the parent's executor-level check
847
- * alone cannot see into the child graph's separate executor.
848
- *
849
- * When `keepToolDefinitions` is `true`, the child retains the parent's
850
- * `toolDefinitions` so event-driven tools remain usable. This is only safe
851
- * when the caller has wired a forwarder for `ON_TOOL_EXECUTE` to a
852
- * registered handler — otherwise the child will hang on tool dispatch.
853
- *
854
- * @remarks Advanced utility: exported primarily for testing and by
855
- * {@link SubagentExecutor}. Host applications configuring subagents should
856
- * not need to call this directly — it is invoked internally when a subagent
857
- * tool is dispatched. The depth-countdown contract (parent's `maxDepth` in,
858
- * child's decremented `maxSubagentDepth` on the returned inputs) is the
859
- * mechanism that bounds nesting across graph boundaries; callers must
860
- * respect it.
861
- */
862
- function buildChildInputs(config, childAgentId, parentMaxDepth, keepToolDefinitions = false) {
863
- const { agentInputs } = config;
864
- const childInputs = {
865
- ...agentInputs,
866
- agentId: childAgentId,
867
- toolDefinitions: keepToolDefinitions ? agentInputs.toolDefinitions : void 0,
868
- /**
869
- * Subagents run in an isolated context by contract. Parent-run-scoped
870
- * fields that would otherwise survive the shallow-spread clone — the
871
- * cross-run conversation summary and the prior-turn tool-discovery
872
- * set — are cleared here so the child starts fresh. Host applications
873
- * that want a subagent to see parent context must thread it in
874
- * explicitly (e.g. via the `description` argument to the subagent
875
- * tool), not via inherited state.
876
- */
877
- initialSummary: void 0,
878
- discoveredTools: void 0,
879
- /**
880
- * Host-supplied direct tools are scrubbed from INHERITED configs only.
881
- * A self-spawn config's `agentInputs` is a shallow spread of the parent's
882
- * `_sourceInputs`, so without this a parent-scoped graph tool (e.g. an
883
- * interrupt-raising ask_user_question, which needs the parent's
884
- * checkpointer — child graphs compile without one) would silently leak
885
- * into the child and deterministically throw `No checkpointer set`. An
886
- * EXPLICIT child config that lists its own `graphTools` is a deliberate
887
- * host choice and keeps them (Codex #289 P2).
888
- */
889
- graphTools: config.self === true ? void 0 : agentInputs.graphTools
890
- };
891
- if (config.allowNested === true) childInputs.maxSubagentDepth = Math.max(0, parentMaxDepth - 1);
892
- else {
893
- childInputs.subagentConfigs = void 0;
894
- childInputs.maxSubagentDepth = void 0;
895
- }
896
- return childInputs;
897
- }
898
1758
  function truncateErrorMessage(error) {
899
1759
  const message = error instanceof Error ? error.message : String(error);
900
1760
  if (message.length <= ERROR_MESSAGE_MAX_CHARS) return message;
901
1761
  return `${message.slice(0, ERROR_MESSAGE_MAX_CHARS)}...`;
902
1762
  }
1763
+ function resolveAgentInputsWithSignal(resolve, signal) {
1764
+ if (signal.aborted) return Promise.reject(getAbortReason(signal));
1765
+ return new Promise((resolvePromise, rejectPromise) => {
1766
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
1767
+ const onAbort = () => {
1768
+ cleanup();
1769
+ rejectPromise(getAbortReason(signal));
1770
+ };
1771
+ signal.addEventListener("abort", onAbort, { once: true });
1772
+ Promise.resolve().then(resolve).then((agentInputs) => {
1773
+ cleanup();
1774
+ resolvePromise(agentInputs);
1775
+ }, (error) => {
1776
+ cleanup();
1777
+ rejectPromise(error);
1778
+ });
1779
+ });
1780
+ }
1781
+ function getAbortReason(signal) {
1782
+ return signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("Subagent resolution aborted.");
1783
+ }
903
1784
  //#endregion
904
- export { SubagentExecutor, buildChildInputs, filterSubagentResult, resolveSubagentConfigs, summarizeEvent };
1785
+ export { DEFAULT_SUBAGENT_DESCRIPTION, SubagentExecutor, filterGraphSubagentResult, filterSubagentResult, summarizeEvent };
905
1786
 
906
1787
  //# sourceMappingURL=SubagentExecutor.mjs.map