@librechat/agents 3.4.1 → 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 (128) hide show
  1. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  2. package/dist/cjs/graphs/Graph.cjs +57 -33
  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/createWorkspacePolicyHook.cjs +1 -1
  12. package/dist/cjs/main.cjs +20 -11
  13. package/dist/cjs/run.cjs +48 -38
  14. package/dist/cjs/run.cjs.map +1 -1
  15. package/dist/cjs/session/JsonlSessionStore.cjs +2 -2
  16. package/dist/cjs/tools/SubagentTool.cjs +4 -4
  17. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  18. package/dist/cjs/tools/ToolNode.cjs +13 -9
  19. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  20. package/dist/cjs/tools/eagerEventExecution.cjs +1 -0
  21. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +3 -3
  22. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +2 -2
  23. package/dist/cjs/tools/search/rerankers.cjs +154 -1
  24. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  25. package/dist/cjs/tools/search/tool.cjs +4 -1
  26. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  27. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +601 -0
  28. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -0
  29. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +672 -343
  30. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  31. package/dist/cjs/tools/subagent/SubagentReplay.cjs +3 -1
  32. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  33. package/dist/cjs/tools/subagent/childGraphConfig.cjs +278 -0
  34. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -0
  35. package/dist/cjs/tools/subagent/index.cjs +1 -0
  36. package/dist/cjs/tools/subagent/runtimeLimits.cjs +5 -0
  37. package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -0
  38. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  39. package/dist/esm/graphs/Graph.mjs +57 -33
  40. package/dist/esm/graphs/Graph.mjs.map +1 -1
  41. package/dist/esm/graphs/MultiAgentGraph.mjs +81 -33
  42. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  43. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs +14 -0
  44. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -0
  45. package/dist/esm/graphs/createGraph.mjs +15 -0
  46. package/dist/esm/graphs/createGraph.mjs.map +1 -0
  47. package/dist/esm/graphs/index.mjs +1 -0
  48. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
  49. package/dist/esm/main.mjs +12 -10
  50. package/dist/esm/run.mjs +48 -38
  51. package/dist/esm/run.mjs.map +1 -1
  52. package/dist/esm/session/JsonlSessionStore.mjs +2 -2
  53. package/dist/esm/tools/SubagentTool.mjs +4 -4
  54. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  55. package/dist/esm/tools/ToolNode.mjs +13 -9
  56. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  57. package/dist/esm/tools/eagerEventExecution.mjs +1 -1
  58. package/dist/esm/tools/local/LocalExecutionEngine.mjs +3 -3
  59. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +2 -2
  60. package/dist/esm/tools/search/rerankers.mjs +154 -1
  61. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  62. package/dist/esm/tools/search/tool.mjs +4 -1
  63. package/dist/esm/tools/search/tool.mjs.map +1 -1
  64. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +597 -0
  65. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -0
  66. package/dist/esm/tools/subagent/SubagentExecutor.mjs +672 -343
  67. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  68. package/dist/esm/tools/subagent/SubagentReplay.mjs +3 -1
  69. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  70. package/dist/esm/tools/subagent/childGraphConfig.mjs +272 -0
  71. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -0
  72. package/dist/esm/tools/subagent/index.mjs +1 -0
  73. package/dist/esm/tools/subagent/runtimeLimits.mjs +5 -0
  74. package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -0
  75. package/dist/types/agents/AgentContext.d.ts +1 -1
  76. package/dist/types/graphs/Graph.d.ts +7 -2
  77. package/dist/types/graphs/MultiAgentGraph.d.ts +7 -1
  78. package/dist/types/graphs/applyGraphRuntimeConfig.d.ts +3 -0
  79. package/dist/types/graphs/createGraph.d.ts +13 -0
  80. package/dist/types/graphs/graphFactory.d.ts +13 -0
  81. package/dist/types/graphs/index.d.ts +2 -0
  82. package/dist/types/run.d.ts +2 -2
  83. package/dist/types/tools/SubagentTool.d.ts +1 -1
  84. package/dist/types/tools/search/rerankers.d.ts +28 -0
  85. package/dist/types/tools/search/types.d.ts +38 -1
  86. package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +171 -0
  87. package/dist/types/tools/subagent/SubagentExecutor.d.ts +32 -37
  88. package/dist/types/tools/subagent/SubagentReplay.d.ts +5 -1
  89. package/dist/types/tools/subagent/childGraphConfig.d.ts +37 -0
  90. package/dist/types/tools/subagent/index.d.ts +1 -1
  91. package/dist/types/tools/subagent/runtimeLimits.d.ts +4 -0
  92. package/dist/types/types/graph.d.ts +184 -19
  93. package/package.json +3 -1
  94. package/src/agents/AgentContext.ts +1 -1
  95. package/src/graphs/Graph.ts +111 -60
  96. package/src/graphs/MultiAgentGraph.ts +149 -47
  97. package/src/graphs/__tests__/MultiAgentGraph.test.ts +86 -0
  98. package/src/graphs/__tests__/composition.smoke.test.ts +152 -0
  99. package/src/graphs/__tests__/createGraph.test.ts +206 -0
  100. package/src/graphs/applyGraphRuntimeConfig.ts +25 -0
  101. package/src/graphs/createGraph.ts +32 -0
  102. package/src/graphs/graphFactory.ts +12 -0
  103. package/src/graphs/index.ts +2 -0
  104. package/src/run.ts +50 -38
  105. package/src/specs/graph-subagent.live.test.ts +242 -0
  106. package/src/specs/graph-subagent.test.ts +322 -0
  107. package/src/specs/langfuse-routing.integration.test.ts +120 -1
  108. package/src/specs/subagent.test.ts +130 -4
  109. package/src/tools/SubagentTool.ts +4 -4
  110. package/src/tools/ToolNode.ts +22 -10
  111. package/src/tools/__tests__/SubagentExecutor.test.ts +198 -9
  112. package/src/tools/__tests__/SubagentReplay.test.ts +29 -0
  113. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +28 -2
  114. package/src/tools/__tests__/subagentHooks.test.ts +90 -3
  115. package/src/tools/search/rag-api-reranker.test.ts +891 -0
  116. package/src/tools/search/rerankers.ts +260 -0
  117. package/src/tools/search/tool.ts +6 -0
  118. package/src/tools/search/types.ts +45 -1
  119. package/src/tools/subagent/SubagentExecutionRegistry.ts +1159 -0
  120. package/src/tools/subagent/SubagentExecutor.ts +1245 -563
  121. package/src/tools/subagent/SubagentReplay.ts +10 -1
  122. package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +637 -0
  123. package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +3054 -0
  124. package/src/tools/subagent/__tests__/childGraphConfig.test.ts +521 -0
  125. package/src/tools/subagent/childGraphConfig.ts +694 -0
  126. package/src/tools/subagent/index.ts +6 -0
  127. package/src/tools/subagent/runtimeLimits.ts +7 -0
  128. package/src/types/graph.ts +217 -19
@@ -1,26 +1,63 @@
1
1
  const require_SubagentReplay = require("./SubagentReplay.cjs");
2
+ const require_misc = require("../../utils/misc.cjs");
2
3
  require("../../common/enum.cjs");
3
4
  require("../../common/index.cjs");
5
+ const require_eagerEventExecution = require("../eagerEventExecution.cjs");
4
6
  const require_streamLimits = require("../../llm/streamLimits.cjs");
7
+ const require_SubagentExecutionRegistry = require("./SubagentExecutionRegistry.cjs");
8
+ require("./runtimeLimits.cjs");
5
9
  const require_executeHooks = require("../../hooks/executeHooks.cjs");
6
10
  const require_types = require("../../hooks/types.cjs");
7
11
  require("../../hooks/index.cjs");
12
+ const require_childGraphConfig = require("./childGraphConfig.cjs");
8
13
  let nanoid = require("nanoid");
9
14
  let _langchain_core_callbacks_base = require("@langchain/core/callbacks/base");
10
15
  let _langchain_core_messages = require("@langchain/core/messages");
11
16
  let _langchain_langgraph = require("@langchain/langgraph");
12
17
  let _langchain_core_singletons = require("@langchain/core/singletons");
18
+ let crypto = require("crypto");
13
19
  //#region src/tools/subagent/SubagentExecutor.ts
14
- const DEFAULT_MAX_TURNS = 25;
15
- const RECURSION_MULTIPLIER = 3;
16
20
  const ERROR_MESSAGE_MAX_CHARS = 200;
17
- const MAX_PENDING_SUBAGENT_UPDATES = 64;
21
+ const MAX_QUEUED_SUBAGENT_UPDATES = 64;
22
+ const SUBAGENT_UPDATE_HANDLER_TIMEOUT_MS = 5e3;
18
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
+ }
19
37
  const HOOK_FALLBACK = Object.freeze({
20
38
  additionalContexts: [],
21
39
  injectedMessages: [],
22
40
  errors: []
23
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 require_childGraphConfig.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
+ }
24
61
  const LANGGRAPH_RUNTIME_CONFIG_PREFIX = "__pregel_";
25
62
  const LANGGRAPH_RESUME_MAP_CONFIG_KEY = "__pregel_resume_map";
26
63
  const LANGGRAPH_CHECKPOINT_CONFIG_KEYS = new Set([
@@ -32,6 +69,7 @@ const SUBAGENT_CHECKPOINT_MARKER_KEY = "__librechat_subagent_checkpoint";
32
69
  const SUBAGENT_HOOK_SESSION_KEY = "__librechat_subagent_hook_session";
33
70
  const SUBAGENT_RUN_ID_KEY = "__librechat_subagent_run_id";
34
71
  const SUBAGENT_REPLAY_NODE = "subagent-replay";
72
+ const DEFAULT_SUBAGENT_DESCRIPTION = "No task description provided";
35
73
  function isCheckpointSaver(value) {
36
74
  if (value == null || typeof value !== "object") return false;
37
75
  const candidate = value;
@@ -118,6 +156,9 @@ function serializeToolOutput(settled) {
118
156
  ...settled.referenceContent == null ? {} : { referenceContent: settled.referenceContent }
119
157
  };
120
158
  }
159
+ function getSettlementFingerprint(output) {
160
+ return (0, crypto.createHash)("sha256").update(require_eagerEventExecution.stableStringify(output)).digest("hex");
161
+ }
121
162
  function deserializeToolOutput(output) {
122
163
  return {
123
164
  output: new _langchain_core_messages.ToolMessage({
@@ -135,40 +176,16 @@ function deserializeToolOutput(output) {
135
176
  ...output.referenceContent == null ? {} : { referenceContent: output.referenceContent }
136
177
  };
137
178
  }
138
- function getParentCheckpointFork(configurable) {
139
- const checkpointId = configurable?.checkpoint_id;
140
- return typeof checkpointId === "string" && checkpointId.length > 0 ? checkpointId : "root";
141
- }
142
- function getResumeAttemptId(configurable, fallback) {
143
- const attemptId = configurable?.[require_SubagentReplay.SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
144
- return typeof attemptId === "string" && attemptId.length > 0 ? attemptId : fallback;
145
- }
146
- function getApprovalExecutionScope(childRunId, resumeAttemptId) {
147
- return `subagent-approval:${Buffer.from(JSON.stringify([childRunId, resumeAttemptId])).toString("base64url")}`;
148
- }
149
- function getChildThreadId(args) {
150
- const durableParentId = args.threadId ?? args.parentRunId;
151
- const parentFork = getParentCheckpointFork(args.parentConfigurable);
152
- const parentBatch = args.parentConfigurable?.[require_SubagentReplay.SUBAGENT_PARENT_BATCH_CONFIG_KEY];
153
- const identity = [
154
- durableParentId,
155
- parentFork,
156
- args.parentAgentId ?? "agent",
157
- args.parentToolCallId,
158
- typeof parentBatch === "string" && parentBatch.length > 0 ? parentBatch : "batch"
159
- ];
160
- if (args.branchId != null) identity.push(args.branchId);
161
- return `subagent:${Buffer.from(JSON.stringify(identity)).toString("base64url")}`;
162
- }
163
179
  function isToolApprovalPayload(value) {
164
180
  return value != null && typeof value === "object" && value.type === "tool_approval";
165
181
  }
166
182
  function addSubagentScope(interrupts, scope, resumeManifest) {
167
183
  return interrupts.map((childInterrupt) => {
168
- const payload = isToolApprovalPayload(childInterrupt.value) ? {
184
+ let payload = childInterrupt.value;
185
+ if (isToolApprovalPayload(payload)) payload = {
169
186
  ...childInterrupt.value,
170
187
  subagent: childInterrupt.value.subagent ?? scope
171
- } : childInterrupt.value;
188
+ };
172
189
  return {
173
190
  ...childInterrupt,
174
191
  value: resumeManifest == null ? payload : require_SubagentReplay.attachSubagentResumeManifest(payload, resumeManifest)
@@ -215,6 +232,7 @@ var SubagentExecutor = class {
215
232
  hookRegistry;
216
233
  parentRunId;
217
234
  parentAgentId;
235
+ executionContext;
218
236
  langfuse;
219
237
  tokenCounter;
220
238
  streamLimits;
@@ -222,13 +240,9 @@ var SubagentExecutor = class {
222
240
  checkpointer;
223
241
  maxDepth;
224
242
  createChildGraph;
243
+ createChildGraphByKind;
225
244
  usageSink;
226
- checkpointThreadIds = /* @__PURE__ */ new Set();
227
- startedChildRuns = /* @__PURE__ */ new Set();
228
- completedChildRuns = /* @__PURE__ */ new Set();
229
- completedChildResults = /* @__PURE__ */ new Map();
230
- childExecutionIdentities = /* @__PURE__ */ new Map();
231
- activeChildRuns = /* @__PURE__ */ new Map();
245
+ executions;
232
246
  replayCheckpointWorkflow;
233
247
  resolveParentHandlerRegistry;
234
248
  constructor(options) {
@@ -238,13 +252,25 @@ var SubagentExecutor = class {
238
252
  this.hookRegistry = options.hookRegistry;
239
253
  this.parentRunId = options.parentRunId;
240
254
  this.parentAgentId = options.parentAgentId;
255
+ this.executionContext = options.executionContext ?? {
256
+ rootRunId: options.parentRunId,
257
+ hookSessionId: options.parentRunId,
258
+ depth: 0,
259
+ ancestry: []
260
+ };
241
261
  this.langfuse = options.langfuse;
242
262
  this.tokenCounter = options.tokenCounter;
243
263
  this.streamLimits = options.streamLimits;
244
264
  this.humanInTheLoop = options.humanInTheLoop;
245
265
  this.checkpointer = isCheckpointSaver(options.checkpointer) ? options.checkpointer : void 0;
266
+ this.executions = new require_SubagentExecutionRegistry.SubagentExecutionRegistry({
267
+ parentRunId: options.parentRunId,
268
+ parentAgentId: options.parentAgentId,
269
+ durable: options.humanInTheLoop?.enabled === true && this.checkpointer != null
270
+ });
246
271
  this.maxDepth = options.maxDepth ?? 1;
247
272
  this.createChildGraph = options.createChildGraph;
273
+ this.createChildGraphByKind = options.createChildGraphByKind;
248
274
  this.usageSink = options.usageSink;
249
275
  const rawRegistry = options.parentHandlerRegistry;
250
276
  if (typeof rawRegistry === "function") this.resolveParentHandlerRegistry = rawRegistry;
@@ -258,87 +284,181 @@ var SubagentExecutor = class {
258
284
  /** One signal that fires on the parent's abort or the breaker abort,
259
285
  * collapsed to a single signal when possible (mirrors
260
286
  * `composeAbortSignals` in Graph.ts). */
261
- composeChildSignal(breaker) {
262
- const child = breaker.signal;
263
- if (this.parentSignal == null || this.parentSignal === child) return child;
264
- return AbortSignal.any([this.parentSignal, child]);
287
+ composeChildSignal(breaker, executionSignal) {
288
+ return require_misc.composeAbortSignals(require_misc.composeAbortSignals(this.parentSignal, breaker.signal), executionSignal) ?? breaker.signal;
265
289
  }
266
290
  /** Snapshot of the parent's registry at the moment a subagent is dispatched. */
267
291
  getParentHandlerRegistry() {
268
292
  return this.resolveParentHandlerRegistry?.();
269
293
  }
294
+ bindExecutionDefinition(execution, binding, authority) {
295
+ try {
296
+ execution.bindDefinition(binding, authority);
297
+ return true;
298
+ } catch (error) {
299
+ if (error instanceof require_SubagentExecutionRegistry.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 (require_childGraphConfig.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
+ }
270
329
  /**
271
330
  * Keeps the original child thread as an immutable resume source once a
272
331
  * different parent Run reconstructs it. Each rebuilt parent gets a private
273
332
  * checkpoint fork, while the persisted child run ID remains stable for
274
333
  * activity and usage correlation across the interrupt boundary.
275
334
  */
276
- async resolveChildExecutionIdentity(params) {
277
- const currentChildRunId = `${this.parentRunId}_sub_${params.parentToolCallId}`;
278
- const resumeAttemptId = getResumeAttemptId(params.parentConfigurable, this.parentRunId);
279
- const baseChildThreadId = getChildThreadId({
280
- parentRunId: this.parentRunId,
281
- parentAgentId: this.parentAgentId,
282
- threadId: params.threadId,
283
- parentToolCallId: params.parentToolCallId,
284
- parentConfigurable: params.parentConfigurable
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
+ };
285
355
  });
286
- if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null) return {
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, {
287
360
  childRunId: currentChildRunId,
288
361
  childThreadId: baseChildThreadId,
289
362
  approvalExecutionScope: currentChildRunId
290
- };
291
- const resumeExecution = require_SubagentReplay.getSubagentResumeManifest(params.parentConfigurable)?.executions.find((execution) => execution.parentToolCallId === params.parentToolCallId);
292
- const branchChildThreadId = getChildThreadId({
293
- parentRunId: this.parentRunId,
294
- parentAgentId: this.parentAgentId,
295
- threadId: params.threadId,
296
- parentToolCallId: params.parentToolCallId,
297
- parentConfigurable: params.parentConfigurable,
298
- branchId: resumeAttemptId
299
363
  });
364
+ const { resumeExecution } = execution;
300
365
  if (resumeExecution != null) {
301
- const approvalExecutionScope = getApprovalExecutionScope(resumeExecution.childRunId, resumeAttemptId);
302
- await this.forkCheckpointSnapshot(resumeExecution.checkpoints, branchChildThreadId);
303
- this.hookRegistry?.restorePendingToolApprovals(approvalExecutionScope, approvalExecutionScope, resumeExecution.approvalReplays);
304
- this.checkpointThreadIds.add(branchChildThreadId);
305
- return {
366
+ const approvalExecutionScope = require_SubagentExecutionRegistry.getSubagentApprovalExecutionScope(resumeExecution.childRunId, resumeAttemptId);
367
+ const identity = {
306
368
  childRunId: resumeExecution.childRunId,
307
369
  childThreadId: branchChildThreadId,
308
- approvalExecutionScope,
309
- resumeExecution
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
+ }
310
394
  };
311
395
  }
312
396
  const branchTuple = await this.checkpointer.getTuple({ configurable: { thread_id: branchChildThreadId } });
313
397
  if (branchTuple != null) {
314
398
  const childRunId = getSubagentRunId(getTupleMessages(branchTuple)) ?? currentChildRunId;
315
- this.checkpointThreadIds.add(branchChildThreadId);
316
- return {
399
+ return this.prepareIdentity(execution, {
317
400
  childRunId,
318
401
  childThreadId: branchChildThreadId,
319
- approvalExecutionScope: getApprovalExecutionScope(childRunId, resumeAttemptId)
320
- };
402
+ approvalExecutionScope: require_SubagentExecutionRegistry.getSubagentApprovalExecutionScope(childRunId, resumeAttemptId)
403
+ });
321
404
  }
322
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: require_SubagentExecutionRegistry.getSubagentApprovalExecutionScope(currentChildRunId, resumeAttemptId)
410
+ });
323
411
  const persistedChildRunId = getSubagentRunId(getTupleMessages(baseTuple));
324
- if (baseTuple == null || persistedChildRunId == null || persistedChildRunId === currentChildRunId) {
325
- this.checkpointThreadIds.add(baseChildThreadId);
326
- return {
327
- childRunId: persistedChildRunId ?? currentChildRunId,
412
+ if (!explicitResumeAttempt && (persistedChildRunId == null || persistedChildRunId === currentChildRunId)) {
413
+ const childRunId = persistedChildRunId ?? currentChildRunId;
414
+ return this.prepareIdentity(execution, {
415
+ childRunId,
328
416
  childThreadId: baseChildThreadId,
329
- approvalExecutionScope: getApprovalExecutionScope(persistedChildRunId ?? currentChildRunId, resumeAttemptId)
330
- };
417
+ approvalExecutionScope: require_SubagentExecutionRegistry.getSubagentApprovalExecutionScope(childRunId, resumeAttemptId)
418
+ });
331
419
  }
332
420
  const sourceCheckpoints = await this.getLatestCheckpointSnapshot(baseChildThreadId);
333
421
  if (sourceCheckpoints.length === 0) throw new Error(`Cannot fork subagent checkpoint thread "${baseChildThreadId}" without a checkpoint ID.`);
334
- await this.forkCheckpointSnapshot(sourceCheckpoints, branchChildThreadId);
335
- this.checkpointThreadIds.add(branchChildThreadId);
336
- return {
337
- childRunId: persistedChildRunId,
422
+ const childRunId = persistedChildRunId ?? currentChildRunId;
423
+ const identity = {
424
+ childRunId,
338
425
  childThreadId: branchChildThreadId,
339
- approvalExecutionScope: getApprovalExecutionScope(persistedChildRunId, resumeAttemptId)
426
+ approvalExecutionScope: require_SubagentExecutionRegistry.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
340
433
  };
341
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
+ }
342
462
  /** Captures one exact checkpoint head per namespace for a child thread. */
343
463
  async getLatestCheckpointSnapshot(threadId) {
344
464
  if (this.checkpointer == null) return [];
@@ -404,19 +524,20 @@ var SubagentExecutor = class {
404
524
  }
405
525
  }
406
526
  }
407
- async createResumeManifest(parentToolCallIds) {
527
+ async createResumeManifest(records) {
408
528
  if (this.checkpointer == null) return;
409
529
  const executions = [];
410
- for (const [parentToolCallId, identity] of this.childExecutionIdentities) {
411
- if (parentToolCallIds != null && !parentToolCallIds.has(parentToolCallId)) continue;
530
+ for (const record of records) {
531
+ const { identity, binding, activeRun } = record.snapshot;
532
+ if (identity == null) continue;
533
+ const { parentToolCallId } = record.address;
412
534
  const checkpoints = await this.getLatestCheckpointSnapshot(identity.childThreadId);
413
535
  if (checkpoints.length === 0) continue;
414
- const activeChildRun = this.activeChildRuns.get(identity.childThreadId);
415
- const configuredHookSessionId = activeChildRun?.invokeConfig?.configurable?.run_id;
536
+ const configuredHookSessionId = activeRun?.invokeConfig?.configurable?.run_id;
416
537
  const hookSessionId = typeof configuredHookSessionId === "string" && configuredHookSessionId.length > 0 ? configuredHookSessionId : this.parentRunId;
417
538
  const approvalReplays = this.hookRegistry?.snapshotPendingToolApprovals(identity.approvalExecutionScope, identity.approvalExecutionScope) ?? [];
418
- const descendant = activeChildRun?.pendingInterrupts.map((pendingInterrupt) => require_SubagentReplay.getSubagentResumeManifest(pendingInterrupt.value)).find((manifest) => manifest != null);
419
- const graphState = activeChildRun?.graph.createSubagentResumeState(hookSessionId) ?? {
539
+ const descendant = activeRun?.pendingInterrupts.map((pendingInterrupt) => require_SubagentReplay.getSubagentResumeManifest(pendingInterrupt.value)).find((manifest) => manifest != null);
540
+ const graphState = activeRun?.graph.createSubagentResumeState(hookSessionId) ?? {
420
541
  toolCallSteps: [],
421
542
  toolSessions: [],
422
543
  toolNodes: [],
@@ -426,6 +547,8 @@ var SubagentExecutor = class {
426
547
  executions.push({
427
548
  parentToolCallId,
428
549
  childRunId: identity.childRunId,
550
+ ...binding?.subagentType == null ? {} : { subagentType: binding.subagentType },
551
+ ...binding?.configId == null ? {} : { configId: binding.configId },
429
552
  approvalExecutionScope: identity.approvalExecutionScope,
430
553
  checkpoints,
431
554
  graphState,
@@ -439,52 +562,101 @@ var SubagentExecutor = class {
439
562
  executions
440
563
  };
441
564
  }
442
- getResumeManifest(parentToolCallIds) {
443
- return this.createResumeManifest(parentToolCallIds);
565
+ getResumeManifest(parentToolCallIds, config) {
566
+ return this.createResumeManifest(this.executions.selectForResume({
567
+ parentToolCallIds,
568
+ config
569
+ }));
444
570
  }
445
571
  getChildCheckpointThreadIds() {
446
- const threadIds = new Set(this.checkpointThreadIds);
447
- for (const activeChildRun of this.activeChildRuns.values()) for (const threadId of this.getGraphChildCheckpointThreadIds(activeChildRun.graph)) threadIds.add(threadId);
448
- return [...threadIds];
572
+ return this.executions.getCheckpointThreadIds((activeRun) => this.getGraphChildCheckpointThreadIds(activeRun.graph));
449
573
  }
450
574
  resetCheckpointThreadIds() {
451
- this.checkpointThreadIds.clear();
575
+ this.executions.resetCheckpointThreadIds();
452
576
  }
453
577
  getGraphChildCheckpointThreadIds(graph) {
454
578
  return graph.getChildCheckpointThreadIds?.() ?? [];
455
579
  }
456
580
  clearChildGraph(graph) {
457
- for (const threadId of this.getGraphChildCheckpointThreadIds(graph)) this.checkpointThreadIds.add(threadId);
581
+ this.executions.rememberCheckpointThread(...this.getGraphChildCheckpointThreadIds(graph));
458
582
  graph.clearHeavyState();
459
583
  }
460
584
  clearHeavyState() {
461
- for (const activeChildRun of this.activeChildRuns.values()) this.clearChildGraph(activeChildRun.graph);
462
- this.activeChildRuns.clear();
463
- this.completedChildResults.clear();
464
- for (const identity of this.childExecutionIdentities.values()) this.hookRegistry?.clearSession(identity.approvalExecutionScope);
465
- this.childExecutionIdentities.clear();
466
- this.startedChildRuns.clear();
467
- this.completedChildRuns.clear();
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
+ });
468
589
  this.replayCheckpointWorkflow = void 0;
469
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
+ }
470
612
  async getSettledToolOutput(call, config) {
471
613
  const parentToolCallId = call.id;
472
614
  if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null || parentToolCallId == null || parentToolCallId === "") return;
473
615
  const parentConfigurable = config.configurable;
474
616
  const threadId = parentConfigurable?.thread_id;
475
- const { childRunId, childThreadId, approvalExecutionScope } = await this.resolveChildExecutionIdentity({
617
+ const execution = this.executions.open({
476
618
  threadId: typeof threadId === "string" ? threadId : void 0,
477
619
  parentToolCallId,
478
620
  parentConfigurable
479
621
  });
480
- this.childExecutionIdentities.set(parentToolCallId, {
481
- childRunId,
482
- childThreadId,
483
- approvalExecutionScope
484
- });
485
- this.checkpointThreadIds.add(childThreadId);
486
- const messages = getCheckpointMessages((await this.checkpointer.getTuple({ configurable: { thread_id: childThreadId } }))?.checkpoint.channel_values.messages);
487
- const marker = getSubagentCheckpointMarker(messages, parentToolCallId);
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;
488
660
  const persistedHookSessionId = marker?.hookSessionId ?? getSubagentHookSessionId(messages);
489
661
  const currentHookSessionId = parentConfigurable?.run_id;
490
662
  if (persistedHookSessionId != null && typeof currentHookSessionId === "string" && currentHookSessionId.length > 0) this.hookRegistry?.copySession(persistedHookSessionId, currentHookSessionId);
@@ -492,37 +664,64 @@ var SubagentExecutor = class {
492
664
  }
493
665
  async persistSettledToolOutput(call, config, settled) {
494
666
  const parentToolCallId = call.id;
495
- if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null || parentToolCallId == null || parentToolCallId === "") return;
667
+ const checkpointer = this.checkpointer;
668
+ if (this.humanInTheLoop?.enabled !== true || checkpointer == null || parentToolCallId == null || parentToolCallId === "") return;
496
669
  const parentConfigurable = config.configurable;
497
670
  const threadId = parentConfigurable?.thread_id;
498
- const { childRunId, childThreadId, approvalExecutionScope } = await this.resolveChildExecutionIdentity({
671
+ const execution = this.executions.open({
499
672
  threadId: typeof threadId === "string" ? threadId : void 0,
500
673
  parentToolCallId,
501
674
  parentConfigurable
502
675
  });
503
- this.childExecutionIdentities.set(parentToolCallId, {
504
- childRunId,
505
- childThreadId,
506
- approvalExecutionScope
507
- });
508
- this.checkpointThreadIds.add(childThreadId);
509
- const activeChildRun = this.activeChildRuns.get(childThreadId);
510
- const persistedOutput = serializeToolOutput(settled);
511
- if (activeChildRun != null) {
512
- await this.persistChildCheckpointMarker(activeChildRun, parentToolCallId, persistedOutput);
513
- this.clearChildGraph(activeChildRun.graph);
514
- this.activeChildRuns.delete(childThreadId);
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);
515
688
  return;
516
689
  }
517
- this.replayCheckpointWorkflow ??= createReplayCheckpointWorkflow(this.checkpointer);
518
- await this.replayCheckpointWorkflow.updateState({ configurable: { thread_id: childThreadId } }, { messages: [createSubagentCheckpointMarkerMessage({
519
- version: 1,
520
- parentToolCallId,
521
- lifecycleComplete: true,
522
- hookSessionId: typeof parentConfigurable?.run_id === "string" ? parentConfigurable.run_id : this.parentRunId,
523
- childRunId,
524
- settledOutput: persistedOutput
525
- })] }, SUBAGENT_REPLAY_NODE);
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 require_SubagentExecutionRegistry.SubagentDefinitionBindingError || error instanceof require_SubagentExecutionRegistry.SubagentInvocationBindingError || error instanceof require_SubagentExecutionRegistry.SubagentSettlementBindingError) return;
723
+ throw error;
724
+ }
526
725
  }
527
726
  async persistChildCheckpointMarker(activeChildRun, parentToolCallId, settledOutput) {
528
727
  if (this.humanInTheLoop?.enabled !== true || activeChildRun.workflow.updateState == null || activeChildRun.invokeConfig == null) return;
@@ -533,9 +732,53 @@ var SubagentExecutor = class {
533
732
  hookSessionId: typeof activeChildRun.invokeConfig.configurable?.run_id === "string" ? activeChildRun.invokeConfig.configurable.run_id : this.parentRunId,
534
733
  childRunId: activeChildRun.childRunId,
535
734
  ...settledOutput == null ? {} : { settledOutput }
536
- })] }, activeChildRun.childAgentId);
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 (require_childGraphConfig.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 ?? (0, nanoid.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 require_SubagentExecutionRegistry.SubagentDefinitionBindingError) return Promise.resolve({
771
+ content: SUBAGENT_CONFIG_CHANGED_MESSAGE,
772
+ messages: []
773
+ });
774
+ if (error instanceof require_SubagentExecutionRegistry.SubagentInvocationBindingError) return Promise.resolve({
775
+ content: SUBAGENT_INVOCATION_CHANGED_MESSAGE,
776
+ messages: []
777
+ });
778
+ throw error;
779
+ }
537
780
  }
538
- async execute(params) {
781
+ async executeOnce(params, execution, executableConfig) {
539
782
  const { description, subagentType, threadId, parentToolCallId } = params;
540
783
  /** Captured ONCE per execution, preferring the controller the parent
541
784
  * tool batch captured at ITS entry (before PreToolUse hooks): a failed
@@ -545,35 +788,54 @@ var SubagentExecutor = class {
545
788
  * new run's breaker from an old child's stream-limit breach. Signal and
546
789
  * trip target both bind to this capture. */
547
790
  const childBreaker = params.breaker ?? this.resolveBreakerController();
548
- const childSignal = this.composeChildSignal(childBreaker);
549
- const config = this.configs.get(subagentType);
550
- if (!config) return {
551
- content: `Error: Unknown subagent type "${subagentType}". Available types: ${[...this.configs.keys()].join(", ")}`,
552
- messages: []
553
- };
554
- if (this.maxDepth <= 0) return {
555
- content: "Error: Maximum subagent nesting depth exceeded.",
556
- messages: []
557
- };
558
- if (this.humanInTheLoop?.enabled === true && (parentToolCallId == null || parentToolCallId === "")) return {
559
- content: "Error: Resumable subagent execution requires a parent tool call ID.",
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 require_streamLimits.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,
560
818
  messages: []
561
819
  };
562
- const executionSuffix = parentToolCallId ?? (0, nanoid.nanoid)(8);
563
- const { childRunId, childThreadId, approvalExecutionScope, resumeExecution } = await this.resolveChildExecutionIdentity({
564
- threadId,
565
- parentToolCallId: executionSuffix,
566
- parentConfigurable: params.parentConfigurable
567
- });
568
- this.childExecutionIdentities.set(executionSuffix, {
569
- childRunId,
570
- childThreadId,
571
- approvalExecutionScope
572
- });
573
- const childExecutionKey = childThreadId;
574
- const childAgentId = config.agentInputs.agentId || `${this.parentAgentId ?? "agent"}_sub_${executionSuffix}`;
575
- const completedChildResult = this.completedChildResults.get(childExecutionKey);
820
+ const completedChildResult = execution.completedResult;
576
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 require_streamLimits.StreamLimitExceededError) throw error;
834
+ return {
835
+ content: SUBAGENT_RESOLUTION_ERROR_MESSAGE,
836
+ messages: []
837
+ };
838
+ }
577
839
  const parentRegistry = this.getParentHandlerRegistry();
578
840
  const forwardingEnabled = parentRegistry != null;
579
841
  /**
@@ -586,18 +848,47 @@ var SubagentExecutor = class {
586
848
  * recoverable "no tools" path for registry-but-no-handler configs.
587
849
  */
588
850
  const hasToolExecuteHandler = parentRegistry?.getHandler("on_tool_execute") != null;
589
- const childInputs = buildChildInputs(config, childAgentId, this.maxDepth, hasToolExecuteHandler);
590
- const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
851
+ const childPlan = require_childGraphConfig.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);
591
876
  const hostUsageSink = this.usageSink;
592
- const cachedChildRun = this.activeChildRuns.get(childExecutionKey);
593
- const childGraph = cachedChildRun?.graph ?? 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 = {
594
884
  runId: childRunId,
595
885
  signal: childSignal,
596
- agents: [childInputs],
886
+ agents: childPlan.agents,
597
887
  langfuse: this.langfuse,
598
888
  tokenCounter: this.tokenCounter,
599
889
  streamLimits: this.streamLimits,
600
890
  subagentScope: true,
891
+ subagentExecutionContext: childExecutionContext,
601
892
  /**
602
893
  * Forwarded so the child graph's own `SubagentExecutor` (created in
603
894
  * its `createAgentNode` when `allowNested` keeps subagentConfigs)
@@ -605,43 +896,52 @@ var SubagentExecutor = class {
605
896
  * level attaches its own capture callback — `workflow.invoke` replaces
606
897
  * the inherited callback chain, so a single top-level handler would
607
898
  * never see grandchild model calls.
608
- *
609
- * The wrapper rewrites `runId` to THIS executor's parent run: nested
610
- * executors emit with their own `parentRunId` (a `*_sub_*` child id),
611
- * and each wrapper layer rewrites upward, so by the time an event
612
- * reaches the host sink its `runId` is the ROOT run — hosts keying
613
- * billing by run id never see intermediate child run ids there
614
- * (`subagentRunId` still identifies the emitting child).
615
899
  */
616
- subagentUsageSink: hostUsageSink == null ? void 0 : (event) => hostUsageSink({
617
- ...event,
618
- runId: this.parentRunId
619
- })
620
- });
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);
621
919
  let forwarding;
622
920
  if (forwardingEnabled) forwarding = this.createForwarderCallback({
623
921
  parentRegistry,
624
922
  subagentType,
923
+ subagentKind: childPlan.kind,
625
924
  subagentAgentId: childAgentId,
626
925
  childRunId,
627
- parentToolCallId
926
+ parentToolCallId,
927
+ executionContext: childExecutionContext
628
928
  });
629
929
  const forwarder = forwarding?.handler;
630
- let childAlreadyStarted = this.startedChildRuns.has(childExecutionKey);
631
- let childAlreadyCompleted = this.completedChildRuns.has(childExecutionKey);
930
+ let childAlreadyStarted = execution.started;
931
+ let childAlreadyCompleted = execution.completed;
632
932
  let result;
633
933
  let recoveredComplete = false;
634
934
  let recoveredInProgress = false;
635
935
  try {
636
936
  const workflow = cachedChildRun?.workflow ?? childGraph.createWorkflow();
637
- const activeChildRun = cachedChildRun ?? {
937
+ const activeChildRun = execution.activate(cachedChildRun ?? {
638
938
  graph: childGraph,
639
939
  workflow,
640
940
  pendingInterrupts: [],
641
941
  childAgentId,
942
+ checkpointNodeId: childPlan.checkpointNodeId,
642
943
  childRunId
643
- };
644
- if (cachedChildRun == null) this.activeChildRuns.set(childExecutionKey, activeChildRun);
944
+ });
645
945
  /**
646
946
  * When `parentHandlerRegistry` is provided (forwarding mode), attach a
647
947
  * lightweight callback that intercepts the child's `on_custom_event`
@@ -671,11 +971,12 @@ var SubagentExecutor = class {
671
971
  if (this.usageSink) callbackHandlers.push(createUsageCaptureHandler({
672
972
  sink: this.usageSink,
673
973
  subagentType,
974
+ subagentKind: childPlan.kind,
674
975
  subagentRunId: childRunId,
675
976
  subagentAgentId: childAgentId,
676
977
  parentRunId: this.parentRunId,
677
- provider: config.agentInputs.provider,
678
- fallbackModel: extractConfiguredModel(config.agentInputs)
978
+ executionContext: childExecutionContext,
979
+ memberInputs: childPlan.memberInputs
679
980
  }));
680
981
  const callbacks = callbackHandlers;
681
982
  /**
@@ -692,23 +993,21 @@ var SubagentExecutor = class {
692
993
  * the independently checkpointed child execution.
693
994
  */
694
995
  const inheritedConfigurable = sanitizeChildConfigurable(params.parentConfigurable);
695
- const resumeAttemptId = getResumeAttemptId(params.parentConfigurable, this.parentRunId);
696
- const currentHookSessionId = typeof inheritedConfigurable.run_id === "string" && inheritedConfigurable.run_id.length > 0 ? inheritedConfigurable.run_id : this.parentRunId;
996
+ const { resumeAttemptId } = execution.address;
697
997
  if (cachedChildRun == null && resumeExecution != null) childGraph.restoreSubagentResumeState(resumeExecution.graphState, currentHookSessionId);
998
+ const childConfigurable = { ...inheritedConfigurable };
999
+ if (this.humanInTheLoop?.enabled === true) childConfigurable[require_types.TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY] = approvalExecutionScope;
1000
+ if (resumeExecution?.descendant != null) {
1001
+ childConfigurable[require_SubagentReplay.SUBAGENT_RESUME_MANIFEST_CONFIG_KEY] = resumeExecution.descendant;
1002
+ childConfigurable[require_SubagentReplay.SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY] = resumeAttemptId;
1003
+ }
1004
+ childConfigurable.thread_id = this.humanInTheLoop?.enabled === true ? childThreadId : inheritedConfigurable.thread_id ?? childRunId;
698
1005
  const childInvokeConfig = {
699
- recursionLimit: maxTurns * RECURSION_MULTIPLIER,
1006
+ recursionLimit,
700
1007
  signal: childSignal,
701
1008
  callbacks,
702
1009
  runName: `subagent:${subagentType}`,
703
- configurable: {
704
- ...inheritedConfigurable,
705
- ...this.humanInTheLoop?.enabled === true ? { [require_types.TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY]: approvalExecutionScope } : {},
706
- ...resumeExecution?.descendant == null ? {} : {
707
- [require_SubagentReplay.SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: resumeExecution.descendant,
708
- [require_SubagentReplay.SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: resumeAttemptId
709
- },
710
- thread_id: this.humanInTheLoop?.enabled === true ? childThreadId : inheritedConfigurable.thread_id ?? childRunId
711
- }
1010
+ configurable: childConfigurable
712
1011
  };
713
1012
  activeChildRun.invokeConfig = childInvokeConfig;
714
1013
  if (cachedChildRun == null && this.humanInTheLoop?.enabled === true) {
@@ -721,12 +1020,12 @@ var SubagentExecutor = class {
721
1020
  const persistedInterrupts = getPersistedInterrupts(persistedState);
722
1021
  if (persistedInterrupts.length > 0) {
723
1022
  activeChildRun.pendingInterrupts = persistedInterrupts;
724
- this.startedChildRuns.add(childExecutionKey);
1023
+ execution.markStarted();
725
1024
  childAlreadyStarted = true;
726
1025
  } else if (persistedState.next.length > 0) {
727
1026
  recoveredInProgress = true;
728
1027
  childAlreadyStarted = true;
729
- this.startedChildRuns.add(childExecutionKey);
1028
+ execution.markStarted();
730
1029
  } else if (persistedState.next.length === 0) {
731
1030
  const persistedMessages = getPersistedMessages(persistedState);
732
1031
  if (persistedMessages != null) {
@@ -735,7 +1034,7 @@ var SubagentExecutor = class {
735
1034
  recoveredComplete = true;
736
1035
  childAlreadyStarted = true;
737
1036
  childAlreadyCompleted = marker?.lifecycleComplete === true;
738
- this.startedChildRuns.add(childExecutionKey);
1037
+ execution.markStarted();
739
1038
  }
740
1039
  }
741
1040
  }
@@ -752,36 +1051,39 @@ var SubagentExecutor = class {
752
1051
  [SUBAGENT_RUN_ID_KEY]: childRunId
753
1052
  }
754
1053
  })] };
755
- if (!childAlreadyStarted && this.hookRegistry?.hasHookFor("SubagentStart", this.parentRunId) === true) {
1054
+ if (!childAlreadyStarted && this.hookRegistry?.hasHookFor("SubagentStart", currentHookSessionId) === true) {
756
1055
  const hookResult = await require_executeHooks.executeHooks({
757
1056
  registry: this.hookRegistry,
758
1057
  input: {
759
1058
  hook_event_name: "SubagentStart",
760
- runId: this.parentRunId,
1059
+ runId: currentHookSessionId,
761
1060
  threadId,
762
1061
  parentAgentId: this.parentAgentId,
763
1062
  agentId: childAgentId,
764
1063
  agentType: subagentType,
765
1064
  inputs: [new _langchain_core_messages.HumanMessage(description)]
766
1065
  },
767
- sessionId: this.parentRunId,
1066
+ sessionId: currentHookSessionId,
768
1067
  matchQuery: subagentType
769
1068
  }).catch(() => HOOK_FALLBACK);
770
1069
  if (hookResult.decision === "deny" || hookResult.decision === "ask") {
771
1070
  this.clearChildGraph(childGraph);
772
- this.activeChildRuns.delete(childExecutionKey);
1071
+ execution.releaseActiveRun();
1072
+ this.executions.remove(execution);
773
1073
  return {
774
1074
  content: `Blocked: ${hookResult.reason ?? "Blocked by hook"}`,
775
1075
  messages: []
776
1076
  };
777
1077
  }
778
1078
  }
779
- this.startedChildRuns.add(childExecutionKey);
1079
+ execution.markStarted();
780
1080
  if (forwarder && !childAlreadyStarted) await this.emitSubagentUpdate(parentRegistry, {
781
1081
  childRunId,
782
1082
  subagentType,
1083
+ subagentKind: childPlan.kind,
783
1084
  subagentAgentId: childAgentId,
784
1085
  parentToolCallId,
1086
+ executionContext: childExecutionContext,
785
1087
  phase: "start",
786
1088
  label: `Subagent "${subagentType}" started`
787
1089
  });
@@ -794,13 +1096,16 @@ var SubagentExecutor = class {
794
1096
  else childResult = await workflow.invoke(childInput, childInvokeConfig);
795
1097
  const childInterrupts = (0, _langchain_langgraph.isInterrupted)(childResult) ? childResult[_langchain_langgraph.INTERRUPT] : void 0;
796
1098
  if (childInterrupts != null && childInterrupts.length > 0) throw new _langchain_langgraph.GraphInterrupt(childInterrupts);
797
- result = { messages: childResult.messages };
1099
+ result = childResult;
798
1100
  }
799
1101
  } catch (error) {
800
1102
  if ((0, _langchain_langgraph.isGraphInterrupt)(error)) {
801
- const activeChildRun = this.activeChildRuns.get(childExecutionKey);
802
- if (activeChildRun != null) activeChildRun.pendingInterrupts = error.interrupts;
803
- const resumeManifest = activeChildRun == null || parentToolCallId == null ? void 0 : await this.createResumeManifest();
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]);
804
1109
  await forwarding?.drain();
805
1110
  throw new _langchain_langgraph.GraphInterrupt(addSubagentScope(error.interrupts, {
806
1111
  run_id: childRunId,
@@ -823,15 +1128,17 @@ var SubagentExecutor = class {
823
1128
  await this.emitSubagentUpdate(parentRegistry, {
824
1129
  childRunId,
825
1130
  subagentType,
1131
+ subagentKind: childPlan.kind,
826
1132
  subagentAgentId: childAgentId,
827
1133
  parentToolCallId,
1134
+ executionContext: childExecutionContext,
828
1135
  phase: "error",
829
1136
  label: `Subagent "${subagentType}" errored: ${errorMessage}`,
830
1137
  data: { message: errorMessage }
831
1138
  });
832
1139
  }
833
1140
  this.clearChildGraph(childGraph);
834
- this.activeChildRuns.delete(childExecutionKey);
1141
+ execution.markFailed();
835
1142
  /**
836
1143
  * A tripped stream circuit breaker is a safety abort, not a recoverable
837
1144
  * subagent failure: converting it into a tool result would let the
@@ -846,8 +1153,8 @@ var SubagentExecutor = class {
846
1153
  };
847
1154
  }
848
1155
  if (result == null) throw new Error("Subagent completed without producing graph state.");
849
- const filteredContent = filterSubagentResult(result.messages);
850
- if (!childAlreadyCompleted && this.hookRegistry?.hasHookFor("SubagentStop", this.parentRunId) === true)
1156
+ const filteredContent = childPlan.kind === "graph" ? filterGraphSubagentResult(result, childPlan.resultAgentId) : filterSubagentResult(result.messages);
1157
+ if (!childAlreadyCompleted && this.hookRegistry?.hasHookFor("SubagentStop", currentHookSessionId) === true)
851
1158
  /**
852
1159
  * Awaited (not fire-and-forget) for deterministic test synchronization
853
1160
  * and consistency with PostCompact. The parent is already waiting on the
@@ -858,13 +1165,13 @@ var SubagentExecutor = class {
858
1165
  registry: this.hookRegistry,
859
1166
  input: {
860
1167
  hook_event_name: "SubagentStop",
861
- runId: this.parentRunId,
1168
+ runId: currentHookSessionId,
862
1169
  threadId,
863
1170
  agentId: childAgentId,
864
1171
  agentType: subagentType,
865
1172
  messages: result.messages
866
1173
  },
867
- sessionId: this.parentRunId,
1174
+ sessionId: currentHookSessionId,
868
1175
  matchQuery: subagentType
869
1176
  }).catch(() => {});
870
1177
  if (forwarding && !childAlreadyCompleted) {
@@ -872,23 +1179,24 @@ var SubagentExecutor = class {
872
1179
  await this.emitSubagentUpdate(parentRegistry, {
873
1180
  childRunId,
874
1181
  subagentType,
1182
+ subagentKind: childPlan.kind,
875
1183
  subagentAgentId: childAgentId,
876
1184
  parentToolCallId,
1185
+ executionContext: childExecutionContext,
877
1186
  phase: "stop",
878
1187
  label: `Subagent "${subagentType}" finished`
879
1188
  });
880
1189
  }
881
1190
  if (!childAlreadyCompleted) {
882
- const activeChildRun = this.activeChildRuns.get(childExecutionKey);
1191
+ const activeChildRun = execution.activeRun;
883
1192
  if (activeChildRun != null && parentToolCallId != null) await this.persistChildCheckpointMarker(activeChildRun, parentToolCallId);
884
1193
  }
885
- this.completedChildRuns.add(childExecutionKey);
886
1194
  this.clearChildGraph(childGraph);
887
1195
  const completedResult = {
888
1196
  content: filteredContent,
889
1197
  messages: result.messages
890
1198
  };
891
- this.completedChildResults.set(childExecutionKey, completedResult);
1199
+ execution.markCompleted(completedResult);
892
1200
  return completedResult;
893
1201
  }
894
1202
  /**
@@ -899,21 +1207,23 @@ var SubagentExecutor = class {
899
1207
  async emitSubagentUpdate(parentRegistry, args) {
900
1208
  const handler = parentRegistry.getHandler("on_subagent_update");
901
1209
  if (!handler) return;
902
- const event = {
903
- runId: this.parentRunId,
1210
+ await dispatchObservationalSubagentUpdate(handler, {
1211
+ runId: args.executionContext.rootRunId,
1212
+ parentRunId: this.parentRunId,
904
1213
  subagentRunId: args.childRunId,
905
1214
  subagentType: args.subagentType,
1215
+ subagentKind: args.subagentKind,
906
1216
  subagentAgentId: args.subagentAgentId,
1217
+ memberAgentId: args.memberAgentId,
907
1218
  parentAgentId: this.parentAgentId,
908
1219
  parentToolCallId: args.parentToolCallId,
1220
+ depth: args.executionContext.depth,
1221
+ ancestry: args.executionContext.ancestry,
909
1222
  phase: args.phase,
910
1223
  data: args.data,
911
1224
  label: args.label,
912
1225
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
913
- };
914
- try {
915
- await handler.handle("on_subagent_update", event);
916
- } catch {}
1226
+ });
917
1227
  }
918
1228
  /**
919
1229
  * Builds a BaseCallbackHandler that intercepts the child graph's custom
@@ -928,35 +1238,40 @@ var SubagentExecutor = class {
928
1238
  * cares about; host apps can extend by registering more phases).
929
1239
  */
930
1240
  createForwarderCallback(args) {
931
- const { parentRegistry, subagentType, subagentAgentId, childRunId, parentToolCallId } = args;
932
- const parentRunId = this.parentRunId;
1241
+ const { parentRegistry, subagentType, subagentKind, subagentAgentId, childRunId, parentToolCallId, executionContext } = args;
1242
+ const immediateParentRunId = this.parentRunId;
933
1243
  const parentAgentId = this.parentAgentId;
934
- const wrap = async (eventName, phase, data) => {
1244
+ const wrap = async (eventName, phase, data, memberAgentId) => {
935
1245
  const handler = parentRegistry.getHandler("on_subagent_update");
936
- if (!handler) return;
937
- try {
938
- const event = {
939
- runId: parentRunId,
940
- subagentRunId: childRunId,
941
- subagentType,
942
- subagentAgentId,
943
- parentAgentId,
944
- parentToolCallId,
945
- phase,
946
- data: sanitizeForwardedSubagentUpdateData(eventName, data),
947
- label: summarizeEvent(eventName, data),
948
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
949
- };
950
- await handler.handle("on_subagent_update", event);
951
- } 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
+ });
952
1264
  };
953
1265
  const queuedUpdates = [];
954
1266
  let drainPromise;
1267
+ let queueOpen = true;
955
1268
  const enqueue = (update) => {
956
- if (queuedUpdates.length >= MAX_PENDING_SUBAGENT_UPDATES) {
957
- 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));
958
1272
  if (dropIndex >= 0) queuedUpdates.splice(dropIndex, 1);
959
- else if (isDroppableSubagentUpdatePhase(update.phase)) return;
1273
+ else if (isLowPrioritySubagentUpdatePhase(update.phase)) return;
1274
+ else queuedUpdates.shift();
960
1275
  }
961
1276
  queuedUpdates.push(update);
962
1277
  };
@@ -969,7 +1284,10 @@ var SubagentExecutor = class {
969
1284
  while (queuedUpdates.length > 0) {
970
1285
  const update = queuedUpdates.shift();
971
1286
  if (update == null) continue;
972
- 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
+ }
973
1291
  }
974
1292
  })();
975
1293
  try {
@@ -979,15 +1297,17 @@ var SubagentExecutor = class {
979
1297
  if (queuedUpdates.length > 0) await drain();
980
1298
  }
981
1299
  };
982
- const scheduleWrap = (eventName, phase, data) => {
1300
+ const scheduleWrap = (eventName, phase, data, memberAgentId) => {
983
1301
  enqueue({
984
1302
  eventName,
985
1303
  phase,
986
- data
1304
+ data,
1305
+ memberAgentId
987
1306
  });
988
1307
  drain();
989
1308
  };
990
- const handler = _langchain_core_callbacks_base.BaseCallbackHandler.fromMethods({ ["handleCustomEvent"]: async (eventName, data) => {
1309
+ const handler = _langchain_core_callbacks_base.BaseCallbackHandler.fromMethods({ ["handleCustomEvent"]: async (eventName, data, _runId, _tags, metadata) => {
1310
+ const memberAgentId = asNonEmptyString(metadata?.agentId) ?? getEventAgentId(data);
991
1311
  if (eventName === "on_tool_execute") {
992
1312
  const toolHandler = parentRegistry.getHandler("on_tool_execute");
993
1313
  if (toolHandler) await toolHandler.handle("on_tool_execute", data);
@@ -995,27 +1315,27 @@ var SubagentExecutor = class {
995
1315
  * We also surface a short notice in the subagent-update stream so
996
1316
  * the UI can show "calling <tool>" for each tool the child spawns.
997
1317
  */
998
- scheduleWrap(eventName, "run_step", data);
1318
+ scheduleWrap(eventName, "run_step", data, memberAgentId);
999
1319
  return;
1000
1320
  }
1001
1321
  if (eventName === "on_run_step") {
1002
- scheduleWrap(eventName, "run_step", data);
1322
+ scheduleWrap(eventName, "run_step", data, memberAgentId);
1003
1323
  return;
1004
1324
  }
1005
1325
  if (eventName === "on_run_step_delta") {
1006
- scheduleWrap(eventName, "run_step_delta", data);
1326
+ scheduleWrap(eventName, "run_step_delta", data, memberAgentId);
1007
1327
  return;
1008
1328
  }
1009
1329
  if (eventName === "on_run_step_completed") {
1010
- scheduleWrap(eventName, "run_step_completed", data);
1330
+ scheduleWrap(eventName, "run_step_completed", data, memberAgentId);
1011
1331
  return;
1012
1332
  }
1013
1333
  if (eventName === "on_message_delta") {
1014
- scheduleWrap(eventName, "message_delta", data);
1334
+ scheduleWrap(eventName, "message_delta", data, memberAgentId);
1015
1335
  return;
1016
1336
  }
1017
1337
  if (eventName === "on_reasoning_delta") {
1018
- scheduleWrap(eventName, "reasoning_delta", data);
1338
+ scheduleWrap(eventName, "reasoning_delta", data, memberAgentId);
1019
1339
  return;
1020
1340
  }
1021
1341
  } });
@@ -1049,7 +1369,8 @@ var SubagentExecutor = class {
1049
1369
  * `subagentUsageSink` on the child graph's input.
1050
1370
  */
1051
1371
  function createUsageCaptureHandler(args) {
1052
- 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;
1053
1374
  /**
1054
1375
  * Per-call attribution keyed by LangChain callback runId. `model` joins
1055
1376
  * `ls_model_name` (provider-reported) with `INVOKED_MODEL` (stamped by
@@ -1064,16 +1385,21 @@ function createUsageCaptureHandler(args) {
1064
1385
  handleChatModelStart: (_llm, _messages, runId, _parentRunId, _extraParams, _tags, metadata) => {
1065
1386
  const callModel = asNonEmptyString(metadata?.ls_model_name) ?? asNonEmptyString(metadata?.["__invoked_model"]);
1066
1387
  const callProvider = asNonEmptyString(metadata?.["__invoked_provider"]);
1067
- 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, {
1068
1390
  model: callModel,
1069
- provider: callProvider
1391
+ provider: callProvider,
1392
+ memberAgentId
1070
1393
  });
1071
1394
  },
1072
1395
  handleLLMEnd: async (output, runId) => {
1073
1396
  const callInfo = callInfoByCallId.get(runId);
1074
1397
  callInfoByCallId.delete(runId);
1075
- const model = callInfo?.model ?? fallbackModel;
1076
- 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;
1077
1403
  for (const generationGroup of output.generations)
1078
1404
  /**
1079
1405
  * At most ONE event per generation group: each group is one
@@ -1098,9 +1424,14 @@ function createUsageCaptureHandler(args) {
1098
1424
  model,
1099
1425
  provider: callProvider,
1100
1426
  subagentType,
1427
+ subagentKind,
1101
1428
  subagentRunId,
1102
1429
  subagentAgentId,
1103
- runId: parentRunId
1430
+ memberAgentId,
1431
+ parentRunId,
1432
+ depth: executionContext.depth,
1433
+ ancestry: executionContext.ancestry,
1434
+ runId: executionContext.rootRunId
1104
1435
  });
1105
1436
  } catch {}
1106
1437
  break;
@@ -1121,6 +1452,11 @@ function createUsageCaptureHandler(args) {
1121
1452
  function asNonEmptyString(value) {
1122
1453
  return typeof value === "string" && value !== "" ? value : void 0;
1123
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
+ }
1124
1460
  /**
1125
1461
  * Best-effort read of the configured model from a subagent's client
1126
1462
  * options. Providers disagree on the key (`model` vs `modelName`), and the
@@ -1131,12 +1467,50 @@ function extractConfiguredModel(agentInputs) {
1131
1467
  if (typeof clientOptions?.model === "string" && clientOptions.model !== "") return clientOptions.model;
1132
1468
  if (typeof clientOptions?.modelName === "string" && clientOptions.modelName !== "") return clientOptions.modelName;
1133
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
+ }
1134
1508
  function sanitizeChildConfigurable(parentConfigurable) {
1135
1509
  if (parentConfigurable == null) return {};
1136
1510
  return Object.fromEntries(Object.entries(parentConfigurable).filter(([key]) => !isLangGraphRuntimeConfigKey(key)));
1137
1511
  }
1138
1512
  function isLangGraphRuntimeConfigKey(key) {
1139
- return key.startsWith(LANGGRAPH_RUNTIME_CONFIG_PREFIX) || LANGGRAPH_CHECKPOINT_CONFIG_KEYS.has(key) || key === "__librechat_subagent_resume_attempt" || key === "__librechat_subagent_resume_manifest" || key === "__librechat_subagent_parent_batch" || key === "lc_run_breaker_scope";
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";
1140
1514
  }
1141
1515
  function sanitizeForwardedSubagentUpdateData(eventName, data) {
1142
1516
  if (eventName === "on_tool_execute") return sanitizeToolExecuteUpdateData(data);
@@ -1146,7 +1520,7 @@ function sanitizeForwardedSubagentUpdateData(eventName, data) {
1146
1520
  if (eventName === "on_message_delta") return sanitizeMessageDeltaUpdateData(data);
1147
1521
  if (eventName === "on_reasoning_delta") return sanitizeReasoningDeltaUpdateData(data);
1148
1522
  }
1149
- function isDroppableSubagentUpdatePhase(phase) {
1523
+ function isLowPrioritySubagentUpdatePhase(phase) {
1150
1524
  return phase === "message_delta" || phase === "reasoning_delta" || phase === "run_step_delta";
1151
1525
  }
1152
1526
  function sanitizeToolExecuteUpdateData(data) {
@@ -1315,6 +1689,16 @@ function summarizeEvent(eventName, data) {
1315
1689
  return eventName;
1316
1690
  }
1317
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
+ /**
1318
1702
  * Walk messages from last to first, returning the text content of the most
1319
1703
  * recent AIMessage that has any. Non-text blocks (tool_use, thinking,
1320
1704
  * redacted_thinking, tool_result) are stripped. If the last AIMessage is
@@ -1371,92 +1755,37 @@ function filterSubagentResult(messages) {
1371
1755
  }
1372
1756
  return "Task completed";
1373
1757
  }
1374
- /**
1375
- * Resolve self-spawn configs by filling in agentInputs from the parent context.
1376
- * Returns configs with agentInputs guaranteed present. Throws on duplicate
1377
- * `type` values to prevent silent config shadowing.
1378
- */
1379
- function resolveSubagentConfigs(configs, parentContext) {
1380
- const resolved = configs.map((config) => {
1381
- if (config.agentInputs != null) return config;
1382
- if (config.self !== true || parentContext._sourceInputs == null) return null;
1383
- return {
1384
- ...config,
1385
- agentInputs: { ...parentContext._sourceInputs }
1386
- };
1387
- }).filter((c) => c != null);
1388
- const seenTypes = /* @__PURE__ */ new Set();
1389
- for (const config of resolved) {
1390
- if (seenTypes.has(config.type)) throw new Error(`Duplicate subagent type "${config.type}". Each SubagentConfig must have a unique "type" field.`);
1391
- seenTypes.add(config.type);
1392
- }
1393
- return resolved;
1394
- }
1395
- /**
1396
- * Build child AgentInputs from a resolved config, stripping nesting and
1397
- * (optionally) event-driven fields. When `allowNested: true`, the child's
1398
- * `maxSubagentDepth` is decremented so that depth is consumed as the call
1399
- * chain deepens across graph boundaries — the parent's executor-level check
1400
- * alone cannot see into the child graph's separate executor.
1401
- *
1402
- * When `keepToolDefinitions` is `true`, the child retains the parent's
1403
- * `toolDefinitions` so event-driven tools remain usable. This is only safe
1404
- * when the caller has wired a forwarder for `ON_TOOL_EXECUTE` to a
1405
- * registered handler — otherwise the child will hang on tool dispatch.
1406
- *
1407
- * @remarks Advanced utility: exported primarily for testing and by
1408
- * {@link SubagentExecutor}. Host applications configuring subagents should
1409
- * not need to call this directly — it is invoked internally when a subagent
1410
- * tool is dispatched. The depth-countdown contract (parent's `maxDepth` in,
1411
- * child's decremented `maxSubagentDepth` on the returned inputs) is the
1412
- * mechanism that bounds nesting across graph boundaries; callers must
1413
- * respect it.
1414
- */
1415
- function buildChildInputs(config, childAgentId, parentMaxDepth, keepToolDefinitions = false) {
1416
- const { agentInputs } = config;
1417
- const childInputs = {
1418
- ...agentInputs,
1419
- agentId: childAgentId,
1420
- toolDefinitions: keepToolDefinitions ? agentInputs.toolDefinitions : void 0,
1421
- /**
1422
- * Subagents run in an isolated context by contract. Parent-run-scoped
1423
- * fields that would otherwise survive the shallow-spread clone — the
1424
- * cross-run conversation summary and the prior-turn tool-discovery
1425
- * set — are cleared here so the child starts fresh. Host applications
1426
- * that want a subagent to see parent context must thread it in
1427
- * explicitly (e.g. via the `description` argument to the subagent
1428
- * tool), not via inherited state.
1429
- */
1430
- initialSummary: void 0,
1431
- discoveredTools: void 0,
1432
- /**
1433
- * Host-supplied direct tools are scrubbed from INHERITED configs only.
1434
- * A self-spawn config's `agentInputs` is a shallow spread of the parent's
1435
- * `_sourceInputs`, so without this a parent-scoped graph tool (e.g. an
1436
- * interrupt-raising ask_user_question) would silently become available to
1437
- * the child. An EXPLICIT child config that lists its own `graphTools` is a
1438
- * deliberate host choice and keeps them (Codex #289 P2); with HITL enabled,
1439
- * those tools use the shared checkpointer and can pause and resume safely.
1440
- */
1441
- graphTools: config.self === true ? void 0 : agentInputs.graphTools
1442
- };
1443
- if (config.allowNested === true) childInputs.maxSubagentDepth = Math.max(0, parentMaxDepth - 1);
1444
- else {
1445
- childInputs.subagentConfigs = void 0;
1446
- childInputs.maxSubagentDepth = void 0;
1447
- }
1448
- return childInputs;
1449
- }
1450
1758
  function truncateErrorMessage(error) {
1451
1759
  const message = error instanceof Error ? error.message : String(error);
1452
1760
  if (message.length <= ERROR_MESSAGE_MAX_CHARS) return message;
1453
1761
  return `${message.slice(0, ERROR_MESSAGE_MAX_CHARS)}...`;
1454
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
+ }
1455
1784
  //#endregion
1785
+ exports.DEFAULT_SUBAGENT_DESCRIPTION = DEFAULT_SUBAGENT_DESCRIPTION;
1456
1786
  exports.SubagentExecutor = SubagentExecutor;
1457
- exports.buildChildInputs = buildChildInputs;
1787
+ exports.filterGraphSubagentResult = filterGraphSubagentResult;
1458
1788
  exports.filterSubagentResult = filterSubagentResult;
1459
- exports.resolveSubagentConfigs = resolveSubagentConfigs;
1460
1789
  exports.summarizeEvent = summarizeEvent;
1461
1790
 
1462
1791
  //# sourceMappingURL=SubagentExecutor.cjs.map