@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.
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +57 -33
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +81 -33
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs +14 -0
- package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs.map +1 -0
- package/dist/cjs/graphs/createGraph.cjs +15 -0
- package/dist/cjs/graphs/createGraph.cjs.map +1 -0
- package/dist/cjs/graphs/index.cjs +1 -0
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
- package/dist/cjs/main.cjs +20 -11
- package/dist/cjs/run.cjs +48 -38
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/JsonlSessionStore.cjs +2 -2
- package/dist/cjs/tools/SubagentTool.cjs +4 -4
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +13 -9
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/eagerEventExecution.cjs +1 -0
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +3 -3
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +2 -2
- package/dist/cjs/tools/search/rerankers.cjs +154 -1
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +4 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +601 -0
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +672 -343
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs +3 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs +278 -0
- package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -0
- package/dist/cjs/tools/subagent/index.cjs +1 -0
- package/dist/cjs/tools/subagent/runtimeLimits.cjs +5 -0
- package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +57 -33
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +81 -33
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/graphs/applyGraphRuntimeConfig.mjs +14 -0
- package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -0
- package/dist/esm/graphs/createGraph.mjs +15 -0
- package/dist/esm/graphs/createGraph.mjs.map +1 -0
- package/dist/esm/graphs/index.mjs +1 -0
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
- package/dist/esm/main.mjs +12 -10
- package/dist/esm/run.mjs +48 -38
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/JsonlSessionStore.mjs +2 -2
- package/dist/esm/tools/SubagentTool.mjs +4 -4
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +13 -9
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/eagerEventExecution.mjs +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +3 -3
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +2 -2
- package/dist/esm/tools/search/rerankers.mjs +154 -1
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +4 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +597 -0
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +672 -343
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs +3 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs +272 -0
- package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -0
- package/dist/esm/tools/subagent/index.mjs +1 -0
- package/dist/esm/tools/subagent/runtimeLimits.mjs +5 -0
- package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +1 -1
- package/dist/types/graphs/Graph.d.ts +7 -2
- package/dist/types/graphs/MultiAgentGraph.d.ts +7 -1
- package/dist/types/graphs/applyGraphRuntimeConfig.d.ts +3 -0
- package/dist/types/graphs/createGraph.d.ts +13 -0
- package/dist/types/graphs/graphFactory.d.ts +13 -0
- package/dist/types/graphs/index.d.ts +2 -0
- package/dist/types/run.d.ts +2 -2
- package/dist/types/tools/SubagentTool.d.ts +1 -1
- package/dist/types/tools/search/rerankers.d.ts +28 -0
- package/dist/types/tools/search/types.d.ts +38 -1
- package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +171 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +32 -37
- package/dist/types/tools/subagent/SubagentReplay.d.ts +5 -1
- package/dist/types/tools/subagent/childGraphConfig.d.ts +37 -0
- package/dist/types/tools/subagent/index.d.ts +1 -1
- package/dist/types/tools/subagent/runtimeLimits.d.ts +4 -0
- package/dist/types/types/graph.d.ts +184 -19
- package/package.json +3 -1
- package/src/agents/AgentContext.ts +1 -1
- package/src/graphs/Graph.ts +111 -60
- package/src/graphs/MultiAgentGraph.ts +149 -47
- package/src/graphs/__tests__/MultiAgentGraph.test.ts +86 -0
- package/src/graphs/__tests__/composition.smoke.test.ts +152 -0
- package/src/graphs/__tests__/createGraph.test.ts +206 -0
- package/src/graphs/applyGraphRuntimeConfig.ts +25 -0
- package/src/graphs/createGraph.ts +32 -0
- package/src/graphs/graphFactory.ts +12 -0
- package/src/graphs/index.ts +2 -0
- package/src/run.ts +50 -38
- package/src/specs/graph-subagent.live.test.ts +242 -0
- package/src/specs/graph-subagent.test.ts +322 -0
- package/src/specs/langfuse-routing.integration.test.ts +120 -1
- package/src/specs/subagent.test.ts +130 -4
- package/src/tools/SubagentTool.ts +4 -4
- package/src/tools/ToolNode.ts +22 -10
- package/src/tools/__tests__/SubagentExecutor.test.ts +198 -9
- package/src/tools/__tests__/SubagentReplay.test.ts +29 -0
- package/src/tools/__tests__/directToolHITLResumeScope.test.ts +28 -2
- package/src/tools/__tests__/subagentHooks.test.ts +90 -3
- package/src/tools/search/rag-api-reranker.test.ts +891 -0
- package/src/tools/search/rerankers.ts +260 -0
- package/src/tools/search/tool.ts +6 -0
- package/src/tools/search/types.ts +45 -1
- package/src/tools/subagent/SubagentExecutionRegistry.ts +1159 -0
- package/src/tools/subagent/SubagentExecutor.ts +1245 -563
- package/src/tools/subagent/SubagentReplay.ts +10 -1
- package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +637 -0
- package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +3054 -0
- package/src/tools/subagent/__tests__/childGraphConfig.test.ts +521 -0
- package/src/tools/subagent/childGraphConfig.ts +694 -0
- package/src/tools/subagent/index.ts +6 -0
- package/src/tools/subagent/runtimeLimits.ts +7 -0
- package/src/types/graph.ts +217 -19
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
2
3
|
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
3
4
|
import { AsyncLocalStorageProviderSingleton } from '@langchain/core/singletons';
|
|
4
5
|
import {
|
|
@@ -34,6 +35,8 @@ import type {
|
|
|
34
35
|
AgentInputs,
|
|
35
36
|
BaseGraphState,
|
|
36
37
|
CompiledStateWorkflow,
|
|
38
|
+
EventHandler,
|
|
39
|
+
MultiAgentGraphState,
|
|
37
40
|
HumanInTheLoopConfig,
|
|
38
41
|
InjectedMessage,
|
|
39
42
|
MessageDeltaEvent,
|
|
@@ -42,9 +45,14 @@ import type {
|
|
|
42
45
|
RunStep,
|
|
43
46
|
RunStepDeltaEvent,
|
|
44
47
|
StandardGraphInput,
|
|
48
|
+
ExecutableSubagentConfigEntry,
|
|
45
49
|
ResolvedSubagentConfig,
|
|
50
|
+
ResolvedSubagentConfigEntry,
|
|
51
|
+
SubagentExecutionContext,
|
|
52
|
+
SubagentResolveConfigurable,
|
|
53
|
+
SubagentResolveRequestContext,
|
|
54
|
+
SubagentResolveUserContext,
|
|
46
55
|
StepCompleted,
|
|
47
|
-
SubagentConfig,
|
|
48
56
|
SubagentUpdateEvent,
|
|
49
57
|
SubagentUpdatePhase,
|
|
50
58
|
SubagentUsageSink,
|
|
@@ -55,16 +63,34 @@ import type {
|
|
|
55
63
|
ToolApprovalDecision,
|
|
56
64
|
ToolApprovalDecisionMap,
|
|
57
65
|
} from '@/types';
|
|
66
|
+
import type {
|
|
67
|
+
PreparedSubagentExecutionIdentity,
|
|
68
|
+
SubagentDefinitionBinding,
|
|
69
|
+
SubagentExecutionIdentity,
|
|
70
|
+
SubagentExecutionPreparationLease,
|
|
71
|
+
SubagentExecutionRecord,
|
|
72
|
+
} from './SubagentExecutionRegistry';
|
|
58
73
|
import type {
|
|
59
74
|
SubagentResumeExecution,
|
|
60
75
|
SubagentResumeManifest,
|
|
61
76
|
SubagentCheckpointReference,
|
|
62
77
|
SettledSubagentToolOutput,
|
|
63
78
|
} from './SubagentReplay';
|
|
64
|
-
import type {
|
|
65
|
-
|
|
79
|
+
import type {
|
|
80
|
+
AggregatedHookResult,
|
|
81
|
+
HookRegistry,
|
|
82
|
+
ToolApprovalReplaySnapshot,
|
|
83
|
+
} from '@/hooks';
|
|
84
|
+
import type { GraphFactory } from '@/graphs/graphFactory';
|
|
66
85
|
import type { StandardGraph } from '@/graphs/Graph';
|
|
67
86
|
import type { HandlerRegistry } from '@/events';
|
|
87
|
+
import {
|
|
88
|
+
getSubagentApprovalExecutionScope,
|
|
89
|
+
SubagentDefinitionBindingError,
|
|
90
|
+
SubagentExecutionRegistry,
|
|
91
|
+
SubagentInvocationBindingError,
|
|
92
|
+
SubagentSettlementBindingError,
|
|
93
|
+
} from './SubagentExecutionRegistry';
|
|
68
94
|
import {
|
|
69
95
|
getSubagentResumeManifest,
|
|
70
96
|
attachSubagentResumeManifest,
|
|
@@ -76,6 +102,10 @@ import {
|
|
|
76
102
|
StreamLimitExceededError,
|
|
77
103
|
RUN_BREAKER_SCOPE_CONFIG_KEY,
|
|
78
104
|
} from '@/llm/streamLimits';
|
|
105
|
+
import {
|
|
106
|
+
DEFAULT_SUBAGENT_MAX_TURNS,
|
|
107
|
+
SUBAGENT_RECURSION_MULTIPLIER,
|
|
108
|
+
} from './runtimeLimits';
|
|
79
109
|
import {
|
|
80
110
|
ContentTypes,
|
|
81
111
|
Constants,
|
|
@@ -87,12 +117,59 @@ import {
|
|
|
87
117
|
executeHooks,
|
|
88
118
|
TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY,
|
|
89
119
|
} from '@/hooks';
|
|
120
|
+
import {
|
|
121
|
+
createChildGraphPlan,
|
|
122
|
+
isGraphSubagentConfig,
|
|
123
|
+
} from './childGraphConfig';
|
|
124
|
+
import { stableStringify } from '@/tools/eagerEventExecution';
|
|
125
|
+
import { composeAbortSignals } from '@/utils/misc';
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
buildChildInputs,
|
|
129
|
+
isGraphSubagentConfig,
|
|
130
|
+
normalizeSubagentConfigs,
|
|
131
|
+
normalizeSubagentConfigEntries,
|
|
132
|
+
resolveSubagentConfigs,
|
|
133
|
+
resolveSubagentConfigEntries,
|
|
134
|
+
} from './childGraphConfig';
|
|
90
135
|
|
|
91
|
-
const DEFAULT_MAX_TURNS = 25;
|
|
92
|
-
const RECURSION_MULTIPLIER = 3;
|
|
93
136
|
const ERROR_MESSAGE_MAX_CHARS = 200;
|
|
94
|
-
const
|
|
137
|
+
const MAX_QUEUED_SUBAGENT_UPDATES = 64;
|
|
138
|
+
const SUBAGENT_UPDATE_HANDLER_TIMEOUT_MS = 5_000;
|
|
95
139
|
const TEXT_DELTA_CONTENT_TYPE = `${ContentTypes.TEXT}_delta`;
|
|
140
|
+
const SUBAGENT_RESOLUTION_ERROR_MESSAGE =
|
|
141
|
+
'Subagent error: Unable to initialize the selected subagent.';
|
|
142
|
+
const SUBAGENT_CONFIG_CHANGED_MESSAGE =
|
|
143
|
+
'Subagent error: Subagent configuration changed since this execution was paused.';
|
|
144
|
+
const SUBAGENT_INVOCATION_CHANGED_MESSAGE =
|
|
145
|
+
'Subagent error: Subagent invocation changed for this execution.';
|
|
146
|
+
|
|
147
|
+
async function dispatchObservationalSubagentUpdate(
|
|
148
|
+
handler: EventHandler,
|
|
149
|
+
event: SubagentUpdateEvent
|
|
150
|
+
): Promise<boolean> {
|
|
151
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
152
|
+
try {
|
|
153
|
+
return await Promise.race([
|
|
154
|
+
Promise.resolve()
|
|
155
|
+
.then(() => handler.handle(GraphEvents.ON_SUBAGENT_UPDATE, event))
|
|
156
|
+
.then(
|
|
157
|
+
() => true,
|
|
158
|
+
() => true
|
|
159
|
+
),
|
|
160
|
+
new Promise<boolean>((resolve) => {
|
|
161
|
+
timeout = setTimeout(
|
|
162
|
+
() => resolve(false),
|
|
163
|
+
SUBAGENT_UPDATE_HANDLER_TIMEOUT_MS
|
|
164
|
+
);
|
|
165
|
+
}),
|
|
166
|
+
]);
|
|
167
|
+
} finally {
|
|
168
|
+
if (timeout != null) {
|
|
169
|
+
clearTimeout(timeout);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
96
173
|
|
|
97
174
|
const HOOK_FALLBACK: AggregatedHookResult = Object.freeze({
|
|
98
175
|
additionalContexts: [] as string[],
|
|
@@ -201,6 +278,7 @@ type QueuedSubagentUpdate = {
|
|
|
201
278
|
eventName: string;
|
|
202
279
|
phase: SubagentUpdatePhase;
|
|
203
280
|
data: unknown;
|
|
281
|
+
memberAgentId?: string;
|
|
204
282
|
};
|
|
205
283
|
|
|
206
284
|
type ForwarderCallback = {
|
|
@@ -212,7 +290,7 @@ type StatefulCompiledWorkflow = Omit<CompiledStateWorkflow, 'invoke'> & {
|
|
|
212
290
|
invoke(
|
|
213
291
|
input: BaseGraphState | Command | null,
|
|
214
292
|
config?: RunnableConfig
|
|
215
|
-
): Promise<
|
|
293
|
+
): Promise<MultiAgentGraphState>;
|
|
216
294
|
getState(config: RunnableConfig): Promise<StateSnapshot>;
|
|
217
295
|
updateState?(
|
|
218
296
|
config: RunnableConfig,
|
|
@@ -235,6 +313,7 @@ type ActiveChildRun = {
|
|
|
235
313
|
pendingInterrupts: Interrupt[];
|
|
236
314
|
invokeConfig?: RunnableConfig;
|
|
237
315
|
childAgentId: string;
|
|
316
|
+
checkpointNodeId: string;
|
|
238
317
|
childRunId: string;
|
|
239
318
|
};
|
|
240
319
|
|
|
@@ -261,18 +340,54 @@ type SubagentCheckpointMarker = {
|
|
|
261
340
|
settledOutput?: PersistedToolOutput;
|
|
262
341
|
};
|
|
263
342
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
343
|
+
function isResumeExecutionCompatible(
|
|
344
|
+
resumeExecution: SubagentResumeExecution | undefined,
|
|
345
|
+
subagentType: string | undefined,
|
|
346
|
+
executableConfig: ExecutableSubagentConfigEntry | undefined,
|
|
347
|
+
hasPersistedTypeEvidence = false
|
|
348
|
+
): boolean {
|
|
349
|
+
if (resumeExecution == null) {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
if (
|
|
353
|
+
subagentType == null ||
|
|
354
|
+
executableConfig == null ||
|
|
355
|
+
resumeExecution.configId !== executableConfig.configId
|
|
356
|
+
) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
if (resumeExecution.subagentType != null) {
|
|
360
|
+
return resumeExecution.subagentType === subagentType;
|
|
361
|
+
}
|
|
362
|
+
if (hasPersistedTypeEvidence) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
return (
|
|
366
|
+
isGraphSubagentConfig(executableConfig) ||
|
|
367
|
+
executableConfig.agentInputs != null
|
|
368
|
+
);
|
|
369
|
+
}
|
|
270
370
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
};
|
|
371
|
+
function getSubagentTypeFromArgs(args: unknown): string | undefined {
|
|
372
|
+
if (!isObjectLike(args)) {
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
return asNonEmptyString((args as { subagent_type?: unknown }).subagent_type);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function getSubagentDescriptionFromArgs(args: unknown): string | undefined {
|
|
379
|
+
if (!isObjectLike(args)) {
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
const description = (args as { description?: unknown }).description;
|
|
383
|
+
return typeof description === 'string' && description.trim().length > 0
|
|
384
|
+
? description
|
|
385
|
+
: undefined;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function getSubagentType(call: ToolCall): string | undefined {
|
|
389
|
+
return getSubagentTypeFromArgs(call.args);
|
|
390
|
+
}
|
|
276
391
|
|
|
277
392
|
const LANGGRAPH_RUNTIME_CONFIG_PREFIX = '__pregel_';
|
|
278
393
|
const LANGGRAPH_RESUME_MAP_CONFIG_KEY = '__pregel_resume_map';
|
|
@@ -285,6 +400,7 @@ const SUBAGENT_CHECKPOINT_MARKER_KEY = '__librechat_subagent_checkpoint';
|
|
|
285
400
|
const SUBAGENT_HOOK_SESSION_KEY = '__librechat_subagent_hook_session';
|
|
286
401
|
const SUBAGENT_RUN_ID_KEY = '__librechat_subagent_run_id';
|
|
287
402
|
const SUBAGENT_REPLAY_NODE = 'subagent-replay';
|
|
403
|
+
export const DEFAULT_SUBAGENT_DESCRIPTION = 'No task description provided';
|
|
288
404
|
|
|
289
405
|
function isCheckpointSaver(value: unknown): value is BaseCheckpointSaver {
|
|
290
406
|
if (value == null || typeof value !== 'object') {
|
|
@@ -480,6 +596,10 @@ function serializeToolOutput(
|
|
|
480
596
|
};
|
|
481
597
|
}
|
|
482
598
|
|
|
599
|
+
function getSettlementFingerprint(output: PersistedToolOutput): string {
|
|
600
|
+
return createHash('sha256').update(stableStringify(output)).digest('hex');
|
|
601
|
+
}
|
|
602
|
+
|
|
483
603
|
function deserializeToolOutput(
|
|
484
604
|
output: PersistedToolOutput
|
|
485
605
|
): SettledSubagentToolOutput {
|
|
@@ -504,61 +624,6 @@ function deserializeToolOutput(
|
|
|
504
624
|
};
|
|
505
625
|
}
|
|
506
626
|
|
|
507
|
-
function getParentCheckpointFork(
|
|
508
|
-
configurable: Record<string, unknown> | undefined
|
|
509
|
-
): string {
|
|
510
|
-
const checkpointId = configurable?.checkpoint_id;
|
|
511
|
-
return typeof checkpointId === 'string' && checkpointId.length > 0
|
|
512
|
-
? checkpointId
|
|
513
|
-
: 'root';
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
function getResumeAttemptId(
|
|
517
|
-
configurable: Record<string, unknown> | undefined,
|
|
518
|
-
fallback: string
|
|
519
|
-
): string {
|
|
520
|
-
const attemptId = configurable?.[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
|
|
521
|
-
return typeof attemptId === 'string' && attemptId.length > 0
|
|
522
|
-
? attemptId
|
|
523
|
-
: fallback;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
function getApprovalExecutionScope(
|
|
527
|
-
childRunId: string,
|
|
528
|
-
resumeAttemptId: string
|
|
529
|
-
): string {
|
|
530
|
-
return `subagent-approval:${Buffer.from(
|
|
531
|
-
JSON.stringify([childRunId, resumeAttemptId])
|
|
532
|
-
).toString('base64url')}`;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
function getChildThreadId(args: {
|
|
536
|
-
parentRunId: string;
|
|
537
|
-
parentAgentId?: string;
|
|
538
|
-
threadId?: string;
|
|
539
|
-
parentToolCallId: string;
|
|
540
|
-
parentConfigurable?: Record<string, unknown>;
|
|
541
|
-
branchId?: string;
|
|
542
|
-
}): string {
|
|
543
|
-
const durableParentId = args.threadId ?? args.parentRunId;
|
|
544
|
-
const parentFork = getParentCheckpointFork(args.parentConfigurable);
|
|
545
|
-
const parentBatch =
|
|
546
|
-
args.parentConfigurable?.[SUBAGENT_PARENT_BATCH_CONFIG_KEY];
|
|
547
|
-
const identity = [
|
|
548
|
-
durableParentId,
|
|
549
|
-
parentFork,
|
|
550
|
-
args.parentAgentId ?? 'agent',
|
|
551
|
-
args.parentToolCallId,
|
|
552
|
-
typeof parentBatch === 'string' && parentBatch.length > 0
|
|
553
|
-
? parentBatch
|
|
554
|
-
: 'batch',
|
|
555
|
-
];
|
|
556
|
-
if (args.branchId != null) {
|
|
557
|
-
identity.push(args.branchId);
|
|
558
|
-
}
|
|
559
|
-
return `subagent:${Buffer.from(JSON.stringify(identity)).toString('base64url')}`;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
627
|
function isToolApprovalPayload(
|
|
563
628
|
value: unknown
|
|
564
629
|
): value is ToolApprovalInterruptPayload {
|
|
@@ -575,12 +640,13 @@ function addSubagentScope(
|
|
|
575
640
|
resumeManifest?: SubagentResumeManifest
|
|
576
641
|
): Interrupt[] {
|
|
577
642
|
return interrupts.map((childInterrupt) => {
|
|
578
|
-
|
|
579
|
-
|
|
643
|
+
let payload = childInterrupt.value;
|
|
644
|
+
if (isToolApprovalPayload(payload)) {
|
|
645
|
+
payload = {
|
|
580
646
|
...childInterrupt.value,
|
|
581
647
|
subagent: childInterrupt.value.subagent ?? scope,
|
|
582
|
-
}
|
|
583
|
-
|
|
648
|
+
};
|
|
649
|
+
}
|
|
584
650
|
return {
|
|
585
651
|
...childInterrupt,
|
|
586
652
|
value:
|
|
@@ -656,6 +722,8 @@ export type SubagentExecuteParams = {
|
|
|
656
722
|
description: string;
|
|
657
723
|
subagentType: string;
|
|
658
724
|
threadId?: string;
|
|
725
|
+
/** Signal attached to this specific parent tool invocation. */
|
|
726
|
+
signal?: AbortSignal;
|
|
659
727
|
/**
|
|
660
728
|
* Breaker controller captured at the parent TOOL BATCH's entry, before
|
|
661
729
|
* PreToolUse hooks. Preferred over the live scope accessor: a graph reset
|
|
@@ -720,7 +788,7 @@ export type SubagentExecuteResult = {
|
|
|
720
788
|
export type ChildGraphFactory = (input: StandardGraphInput) => StandardGraph;
|
|
721
789
|
|
|
722
790
|
export type SubagentExecutorOptions = {
|
|
723
|
-
configs:
|
|
791
|
+
configs: ReadonlyMap<string, ExecutableSubagentConfigEntry>;
|
|
724
792
|
parentSignal?: AbortSignal;
|
|
725
793
|
/** Run-scoped breaker abort shared by every executor of one graph, so a
|
|
726
794
|
* child tripping a stream limit stops subagents running under OTHER
|
|
@@ -736,6 +804,8 @@ export type SubagentExecutorOptions = {
|
|
|
736
804
|
hookRegistry?: HookRegistry;
|
|
737
805
|
parentRunId: string;
|
|
738
806
|
parentAgentId?: string;
|
|
807
|
+
/** Root identity, policy session, and lineage inherited by a child graph. */
|
|
808
|
+
executionContext?: SubagentExecutionContext;
|
|
739
809
|
langfuse?: StandardGraphInput['langfuse'];
|
|
740
810
|
tokenCounter?: TokenCounter;
|
|
741
811
|
/**
|
|
@@ -760,6 +830,9 @@ export type SubagentExecutorOptions = {
|
|
|
760
830
|
* module dependency.
|
|
761
831
|
*/
|
|
762
832
|
createChildGraph: ChildGraphFactory;
|
|
833
|
+
/** Preferred polymorphic child constructor. The legacy standard-only
|
|
834
|
+
* factory remains required for source compatibility. */
|
|
835
|
+
createChildGraphByKind?: GraphFactory;
|
|
763
836
|
/**
|
|
764
837
|
* Parent's event handler registry. When provided, child-graph events are
|
|
765
838
|
* forwarded through this registry so hosts can:
|
|
@@ -784,8 +857,15 @@ export type SubagentExecutorOptions = {
|
|
|
784
857
|
usageSink?: SubagentUsageSink;
|
|
785
858
|
};
|
|
786
859
|
|
|
860
|
+
type DurableExecutionRecord = SubagentExecutionRecord<
|
|
861
|
+
SubagentExecuteResult,
|
|
862
|
+
ResolvedSubagentConfig,
|
|
863
|
+
ActiveChildRun,
|
|
864
|
+
PersistedToolOutput
|
|
865
|
+
>;
|
|
866
|
+
|
|
787
867
|
export class SubagentExecutor {
|
|
788
|
-
private readonly configs:
|
|
868
|
+
private readonly configs: ReadonlyMap<string, ExecutableSubagentConfigEntry>;
|
|
789
869
|
private readonly parentSignal?: AbortSignal;
|
|
790
870
|
/** Aborted when a child trips a stream circuit breaker: parallel sibling
|
|
791
871
|
* subagents run concurrently on the parent's signal, and rejecting the
|
|
@@ -798,6 +878,7 @@ export class SubagentExecutor {
|
|
|
798
878
|
private readonly hookRegistry?: HookRegistry;
|
|
799
879
|
private readonly parentRunId: string;
|
|
800
880
|
private readonly parentAgentId?: string;
|
|
881
|
+
private readonly executionContext: SubagentExecutionContext;
|
|
801
882
|
private readonly langfuse?: StandardGraphInput['langfuse'];
|
|
802
883
|
private readonly tokenCounter?: TokenCounter;
|
|
803
884
|
private readonly streamLimits?: StandardGraphInput['streamLimits'];
|
|
@@ -805,22 +886,14 @@ export class SubagentExecutor {
|
|
|
805
886
|
private readonly checkpointer?: BaseCheckpointSaver;
|
|
806
887
|
private readonly maxDepth: number;
|
|
807
888
|
private readonly createChildGraph: ChildGraphFactory;
|
|
889
|
+
private readonly createChildGraphByKind?: GraphFactory;
|
|
808
890
|
private readonly usageSink?: SubagentUsageSink;
|
|
809
|
-
private readonly
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
>();
|
|
816
|
-
private readonly childExecutionIdentities = new Map<
|
|
817
|
-
string,
|
|
818
|
-
Pick<
|
|
819
|
-
ChildExecutionIdentity,
|
|
820
|
-
'childRunId' | 'childThreadId' | 'approvalExecutionScope'
|
|
821
|
-
>
|
|
822
|
-
>();
|
|
823
|
-
private readonly activeChildRuns = new Map<string, ActiveChildRun>();
|
|
891
|
+
private readonly executions: SubagentExecutionRegistry<
|
|
892
|
+
SubagentExecuteResult,
|
|
893
|
+
ResolvedSubagentConfig,
|
|
894
|
+
ActiveChildRun,
|
|
895
|
+
PersistedToolOutput
|
|
896
|
+
>;
|
|
824
897
|
private replayCheckpointWorkflow?: ReplayCheckpointWorkflow;
|
|
825
898
|
private readonly resolveParentHandlerRegistry?: () =>
|
|
826
899
|
| HandlerRegistry
|
|
@@ -833,6 +906,12 @@ export class SubagentExecutor {
|
|
|
833
906
|
this.hookRegistry = options.hookRegistry;
|
|
834
907
|
this.parentRunId = options.parentRunId;
|
|
835
908
|
this.parentAgentId = options.parentAgentId;
|
|
909
|
+
this.executionContext = options.executionContext ?? {
|
|
910
|
+
rootRunId: options.parentRunId,
|
|
911
|
+
hookSessionId: options.parentRunId,
|
|
912
|
+
depth: 0,
|
|
913
|
+
ancestry: [],
|
|
914
|
+
};
|
|
836
915
|
this.langfuse = options.langfuse;
|
|
837
916
|
this.tokenCounter = options.tokenCounter;
|
|
838
917
|
this.streamLimits = options.streamLimits;
|
|
@@ -840,8 +919,15 @@ export class SubagentExecutor {
|
|
|
840
919
|
this.checkpointer = isCheckpointSaver(options.checkpointer)
|
|
841
920
|
? options.checkpointer
|
|
842
921
|
: undefined;
|
|
922
|
+
this.executions = new SubagentExecutionRegistry({
|
|
923
|
+
parentRunId: options.parentRunId,
|
|
924
|
+
parentAgentId: options.parentAgentId,
|
|
925
|
+
durable:
|
|
926
|
+
options.humanInTheLoop?.enabled === true && this.checkpointer != null,
|
|
927
|
+
});
|
|
843
928
|
this.maxDepth = options.maxDepth ?? 1;
|
|
844
929
|
this.createChildGraph = options.createChildGraph;
|
|
930
|
+
this.createChildGraphByKind = options.createChildGraphByKind;
|
|
845
931
|
this.usageSink = options.usageSink;
|
|
846
932
|
const rawRegistry = options.parentHandlerRegistry;
|
|
847
933
|
if (typeof rawRegistry === 'function') {
|
|
@@ -860,12 +946,17 @@ export class SubagentExecutor {
|
|
|
860
946
|
/** One signal that fires on the parent's abort or the breaker abort,
|
|
861
947
|
* collapsed to a single signal when possible (mirrors
|
|
862
948
|
* `composeAbortSignals` in Graph.ts). */
|
|
863
|
-
private composeChildSignal(
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
949
|
+
private composeChildSignal(
|
|
950
|
+
breaker: AbortController,
|
|
951
|
+
executionSignal?: AbortSignal
|
|
952
|
+
): AbortSignal {
|
|
953
|
+
const parentAndBreaker = composeAbortSignals(
|
|
954
|
+
this.parentSignal,
|
|
955
|
+
breaker.signal
|
|
956
|
+
);
|
|
957
|
+
return (
|
|
958
|
+
composeAbortSignals(parentAndBreaker, executionSignal) ?? breaker.signal
|
|
959
|
+
);
|
|
869
960
|
}
|
|
870
961
|
|
|
871
962
|
/** Snapshot of the parent's registry at the moment a subagent is dispatched. */
|
|
@@ -873,6 +964,74 @@ export class SubagentExecutor {
|
|
|
873
964
|
return this.resolveParentHandlerRegistry?.();
|
|
874
965
|
}
|
|
875
966
|
|
|
967
|
+
private bindExecutionDefinition(
|
|
968
|
+
execution: DurableExecutionRecord,
|
|
969
|
+
binding: SubagentDefinitionBinding,
|
|
970
|
+
authority: 'provisional' | 'effective'
|
|
971
|
+
): boolean {
|
|
972
|
+
try {
|
|
973
|
+
execution.bindDefinition(binding, authority);
|
|
974
|
+
return true;
|
|
975
|
+
} catch (error) {
|
|
976
|
+
if (error instanceof SubagentDefinitionBindingError) {
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
throw error;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/** Resolve one lazy descriptor per stable child execution. Concurrent
|
|
984
|
+
* duplicate dispatches share the same in-flight resolution; HITL re-entry
|
|
985
|
+
* on this executor reuses the resolved config until the child settles. */
|
|
986
|
+
private resolveExecutionConfig(
|
|
987
|
+
config: ExecutableSubagentConfigEntry,
|
|
988
|
+
execution: DurableExecutionRecord,
|
|
989
|
+
context: {
|
|
990
|
+
childRunId: string;
|
|
991
|
+
childSignal: AbortSignal;
|
|
992
|
+
threadId?: string;
|
|
993
|
+
parentToolCallId?: string;
|
|
994
|
+
parentConfigurable?: Record<string, unknown>;
|
|
995
|
+
}
|
|
996
|
+
): Promise<ResolvedSubagentConfigEntry> {
|
|
997
|
+
if (isGraphSubagentConfig(config) || config.agentInputs != null) {
|
|
998
|
+
return Promise.resolve(config as ResolvedSubagentConfigEntry);
|
|
999
|
+
}
|
|
1000
|
+
const resolver = config.resolveAgentInputs;
|
|
1001
|
+
const configId = config.configId;
|
|
1002
|
+
return execution.resolveConfig(
|
|
1003
|
+
(): Promise<ResolvedSubagentConfig> =>
|
|
1004
|
+
resolveAgentInputsWithSignal(
|
|
1005
|
+
(): Promise<AgentInputs> =>
|
|
1006
|
+
resolver({
|
|
1007
|
+
descriptor: {
|
|
1008
|
+
type: config.type,
|
|
1009
|
+
name: config.name,
|
|
1010
|
+
description: config.description,
|
|
1011
|
+
configId,
|
|
1012
|
+
},
|
|
1013
|
+
executionId: context.childRunId,
|
|
1014
|
+
parentRunId: this.parentRunId,
|
|
1015
|
+
parentAgentId: this.parentAgentId,
|
|
1016
|
+
parentToolCallId: context.parentToolCallId,
|
|
1017
|
+
threadId: context.threadId,
|
|
1018
|
+
signal: context.childSignal,
|
|
1019
|
+
configurable:
|
|
1020
|
+
context.parentConfigurable == null
|
|
1021
|
+
? undefined
|
|
1022
|
+
: sanitizeResolverConfigurable(context.parentConfigurable),
|
|
1023
|
+
}),
|
|
1024
|
+
context.childSignal
|
|
1025
|
+
).then(
|
|
1026
|
+
(agentInputs): ResolvedSubagentConfig => ({
|
|
1027
|
+
...config,
|
|
1028
|
+
agentInputs,
|
|
1029
|
+
})
|
|
1030
|
+
),
|
|
1031
|
+
context.childSignal
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
876
1035
|
/**
|
|
877
1036
|
* Keeps the original child thread as an immutable resume source once a
|
|
878
1037
|
* different parent Run reconstructs it. Each rebuilt parent gets a private
|
|
@@ -880,60 +1039,138 @@ export class SubagentExecutor {
|
|
|
880
1039
|
* activity and usage correlation across the interrupt boundary.
|
|
881
1040
|
*/
|
|
882
1041
|
private async resolveChildExecutionIdentity(
|
|
883
|
-
|
|
884
|
-
): Promise<
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1042
|
+
execution: DurableExecutionRecord
|
|
1043
|
+
): Promise<SubagentExecutionIdentity> {
|
|
1044
|
+
return execution.resolveIdentity(async () => {
|
|
1045
|
+
const preparation = await this.createChildExecutionIdentity(execution);
|
|
1046
|
+
const { identity } = preparation;
|
|
1047
|
+
return {
|
|
1048
|
+
identity,
|
|
1049
|
+
lease: preparation.lease,
|
|
1050
|
+
commit: (lease): void => {
|
|
1051
|
+
const { resumeExecution } = execution;
|
|
1052
|
+
if (resumeExecution != null) {
|
|
1053
|
+
this.executions.retireResumeSources(
|
|
1054
|
+
execution,
|
|
1055
|
+
new Set(
|
|
1056
|
+
resumeExecution.checkpoints.map(
|
|
1057
|
+
(checkpoint) => checkpoint.threadId
|
|
1058
|
+
)
|
|
1059
|
+
),
|
|
1060
|
+
(source) => {
|
|
1061
|
+
if (source.activeRun != null) {
|
|
1062
|
+
this.clearChildGraph(source.activeRun.graph);
|
|
1063
|
+
}
|
|
1064
|
+
if (source.identity != null) {
|
|
1065
|
+
this.hookRegistry?.clearSession(
|
|
1066
|
+
source.identity.approvalExecutionScope
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
preparation.commit?.(lease);
|
|
1073
|
+
if (
|
|
1074
|
+
this.humanInTheLoop?.enabled === true &&
|
|
1075
|
+
this.checkpointer != null
|
|
1076
|
+
) {
|
|
1077
|
+
this.executions.rememberCheckpointThread(identity.childThreadId);
|
|
1078
|
+
}
|
|
1079
|
+
},
|
|
1080
|
+
rollback: async (lease): Promise<void> => {
|
|
1081
|
+
await preparation.rollback?.(lease);
|
|
1082
|
+
},
|
|
1083
|
+
};
|
|
896
1084
|
});
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
private async createChildExecutionIdentity(
|
|
1088
|
+
execution: DurableExecutionRecord
|
|
1089
|
+
): Promise<PreparedSubagentExecutionIdentity> {
|
|
1090
|
+
const {
|
|
1091
|
+
baseChildThreadId,
|
|
1092
|
+
branchChildThreadId,
|
|
1093
|
+
currentChildRunId,
|
|
1094
|
+
explicitResumeAttempt,
|
|
1095
|
+
resumeAttemptId,
|
|
1096
|
+
} = execution.address;
|
|
897
1097
|
if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null) {
|
|
898
|
-
return {
|
|
1098
|
+
return this.prepareIdentity(execution, {
|
|
899
1099
|
childRunId: currentChildRunId,
|
|
900
1100
|
childThreadId: baseChildThreadId,
|
|
901
1101
|
approvalExecutionScope: currentChildRunId,
|
|
902
|
-
};
|
|
1102
|
+
});
|
|
903
1103
|
}
|
|
904
1104
|
|
|
905
|
-
const
|
|
906
|
-
const resumeExecution = resumeManifest?.executions.find(
|
|
907
|
-
(execution) => execution.parentToolCallId === params.parentToolCallId
|
|
908
|
-
);
|
|
909
|
-
const branchChildThreadId = getChildThreadId({
|
|
910
|
-
parentRunId: this.parentRunId,
|
|
911
|
-
parentAgentId: this.parentAgentId,
|
|
912
|
-
threadId: params.threadId,
|
|
913
|
-
parentToolCallId: params.parentToolCallId,
|
|
914
|
-
parentConfigurable: params.parentConfigurable,
|
|
915
|
-
branchId: resumeAttemptId,
|
|
916
|
-
});
|
|
1105
|
+
const { resumeExecution } = execution;
|
|
917
1106
|
if (resumeExecution != null) {
|
|
918
|
-
const approvalExecutionScope =
|
|
1107
|
+
const approvalExecutionScope = getSubagentApprovalExecutionScope(
|
|
919
1108
|
resumeExecution.childRunId,
|
|
920
1109
|
resumeAttemptId
|
|
921
1110
|
);
|
|
922
|
-
|
|
923
|
-
resumeExecution.checkpoints,
|
|
924
|
-
branchChildThreadId
|
|
925
|
-
);
|
|
926
|
-
this.hookRegistry?.restorePendingToolApprovals(
|
|
927
|
-
approvalExecutionScope,
|
|
928
|
-
approvalExecutionScope,
|
|
929
|
-
resumeExecution.approvalReplays
|
|
930
|
-
);
|
|
931
|
-
this.checkpointThreadIds.add(branchChildThreadId);
|
|
932
|
-
return {
|
|
1111
|
+
const identity = {
|
|
933
1112
|
childRunId: resumeExecution.childRunId,
|
|
934
1113
|
childThreadId: branchChildThreadId,
|
|
935
1114
|
approvalExecutionScope,
|
|
936
|
-
|
|
1115
|
+
};
|
|
1116
|
+
const lease = this.executions.beginIdentityPreparation(
|
|
1117
|
+
execution,
|
|
1118
|
+
identity
|
|
1119
|
+
);
|
|
1120
|
+
await this.prepareCheckpointFork(
|
|
1121
|
+
resumeExecution.checkpoints,
|
|
1122
|
+
branchChildThreadId,
|
|
1123
|
+
lease
|
|
1124
|
+
);
|
|
1125
|
+
let previousApprovals: ToolApprovalReplaySnapshot[] = [];
|
|
1126
|
+
let approvalsMutated = false;
|
|
1127
|
+
return {
|
|
1128
|
+
identity,
|
|
1129
|
+
lease,
|
|
1130
|
+
commit: (currentLease): void => {
|
|
1131
|
+
const hookRegistry = this.hookRegistry;
|
|
1132
|
+
if (hookRegistry == null) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
previousApprovals = hookRegistry.snapshotPendingToolApprovals(
|
|
1136
|
+
approvalExecutionScope,
|
|
1137
|
+
approvalExecutionScope
|
|
1138
|
+
);
|
|
1139
|
+
const approvalReplayCreatesScope =
|
|
1140
|
+
previousApprovals.length === 0 &&
|
|
1141
|
+
resumeExecution.approvalReplays.length > 0;
|
|
1142
|
+
const approvalsChanged =
|
|
1143
|
+
previousApprovals.length > 0 ||
|
|
1144
|
+
resumeExecution.approvalReplays.length > 0;
|
|
1145
|
+
if (approvalReplayCreatesScope) {
|
|
1146
|
+
currentLease.markApprovalScopeCreated(async (): Promise<void> => {
|
|
1147
|
+
hookRegistry.restorePendingToolApprovals(
|
|
1148
|
+
approvalExecutionScope,
|
|
1149
|
+
approvalExecutionScope,
|
|
1150
|
+
previousApprovals
|
|
1151
|
+
);
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
approvalsMutated = approvalsChanged && !approvalReplayCreatesScope;
|
|
1155
|
+
hookRegistry.restorePendingToolApprovals(
|
|
1156
|
+
approvalExecutionScope,
|
|
1157
|
+
approvalExecutionScope,
|
|
1158
|
+
resumeExecution.approvalReplays
|
|
1159
|
+
);
|
|
1160
|
+
},
|
|
1161
|
+
rollback: async (currentLease): Promise<void> => {
|
|
1162
|
+
if (
|
|
1163
|
+
approvalsMutated &&
|
|
1164
|
+
currentLease.ownsApprovalScope &&
|
|
1165
|
+
this.hookRegistry != null
|
|
1166
|
+
) {
|
|
1167
|
+
this.hookRegistry.restorePendingToolApprovals(
|
|
1168
|
+
approvalExecutionScope,
|
|
1169
|
+
approvalExecutionScope,
|
|
1170
|
+
previousApprovals
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
937
1174
|
};
|
|
938
1175
|
}
|
|
939
1176
|
|
|
@@ -943,37 +1180,46 @@ export class SubagentExecutor {
|
|
|
943
1180
|
if (branchTuple != null) {
|
|
944
1181
|
const childRunId =
|
|
945
1182
|
getSubagentRunId(getTupleMessages(branchTuple)) ?? currentChildRunId;
|
|
946
|
-
this.
|
|
947
|
-
return {
|
|
1183
|
+
return this.prepareIdentity(execution, {
|
|
948
1184
|
childRunId,
|
|
949
1185
|
childThreadId: branchChildThreadId,
|
|
950
|
-
approvalExecutionScope:
|
|
1186
|
+
approvalExecutionScope: getSubagentApprovalExecutionScope(
|
|
951
1187
|
childRunId,
|
|
952
1188
|
resumeAttemptId
|
|
953
1189
|
),
|
|
954
|
-
};
|
|
1190
|
+
});
|
|
955
1191
|
}
|
|
956
1192
|
|
|
957
1193
|
const baseTuple = await this.checkpointer.getTuple({
|
|
958
1194
|
configurable: { thread_id: baseChildThreadId },
|
|
959
1195
|
});
|
|
1196
|
+
if (baseTuple == null) {
|
|
1197
|
+
return this.prepareIdentity(execution, {
|
|
1198
|
+
childRunId: currentChildRunId,
|
|
1199
|
+
childThreadId: explicitResumeAttempt
|
|
1200
|
+
? branchChildThreadId
|
|
1201
|
+
: baseChildThreadId,
|
|
1202
|
+
approvalExecutionScope: getSubagentApprovalExecutionScope(
|
|
1203
|
+
currentChildRunId,
|
|
1204
|
+
resumeAttemptId
|
|
1205
|
+
),
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
960
1208
|
const persistedChildRunId = getSubagentRunId(getTupleMessages(baseTuple));
|
|
961
1209
|
if (
|
|
962
|
-
|
|
963
|
-
persistedChildRunId == null ||
|
|
964
|
-
persistedChildRunId === currentChildRunId
|
|
1210
|
+
!explicitResumeAttempt &&
|
|
1211
|
+
(persistedChildRunId == null || persistedChildRunId === currentChildRunId)
|
|
965
1212
|
) {
|
|
966
|
-
|
|
967
|
-
return {
|
|
968
|
-
childRunId
|
|
1213
|
+
const childRunId = persistedChildRunId ?? currentChildRunId;
|
|
1214
|
+
return this.prepareIdentity(execution, {
|
|
1215
|
+
childRunId,
|
|
969
1216
|
childThreadId: baseChildThreadId,
|
|
970
|
-
approvalExecutionScope:
|
|
971
|
-
|
|
1217
|
+
approvalExecutionScope: getSubagentApprovalExecutionScope(
|
|
1218
|
+
childRunId,
|
|
972
1219
|
resumeAttemptId
|
|
973
1220
|
),
|
|
974
|
-
};
|
|
1221
|
+
});
|
|
975
1222
|
}
|
|
976
|
-
|
|
977
1223
|
const sourceCheckpoints =
|
|
978
1224
|
await this.getLatestCheckpointSnapshot(baseChildThreadId);
|
|
979
1225
|
if (sourceCheckpoints.length === 0) {
|
|
@@ -981,16 +1227,77 @@ export class SubagentExecutor {
|
|
|
981
1227
|
`Cannot fork subagent checkpoint thread "${baseChildThreadId}" without a checkpoint ID.`
|
|
982
1228
|
);
|
|
983
1229
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
childRunId: persistedChildRunId,
|
|
1230
|
+
const childRunId = persistedChildRunId ?? currentChildRunId;
|
|
1231
|
+
const identity = {
|
|
1232
|
+
childRunId,
|
|
988
1233
|
childThreadId: branchChildThreadId,
|
|
989
|
-
approvalExecutionScope:
|
|
990
|
-
|
|
1234
|
+
approvalExecutionScope: getSubagentApprovalExecutionScope(
|
|
1235
|
+
childRunId,
|
|
991
1236
|
resumeAttemptId
|
|
992
1237
|
),
|
|
993
1238
|
};
|
|
1239
|
+
const lease = this.executions.beginIdentityPreparation(execution, identity);
|
|
1240
|
+
await this.prepareCheckpointFork(
|
|
1241
|
+
sourceCheckpoints,
|
|
1242
|
+
branchChildThreadId,
|
|
1243
|
+
lease
|
|
1244
|
+
);
|
|
1245
|
+
return {
|
|
1246
|
+
identity,
|
|
1247
|
+
lease,
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
private prepareIdentity(
|
|
1252
|
+
execution: DurableExecutionRecord,
|
|
1253
|
+
identity: SubagentExecutionIdentity
|
|
1254
|
+
): PreparedSubagentExecutionIdentity {
|
|
1255
|
+
return {
|
|
1256
|
+
identity,
|
|
1257
|
+
lease: this.executions.beginIdentityPreparation(execution, identity),
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
private async prepareCheckpointFork(
|
|
1262
|
+
sources: ReadonlyArray<SubagentCheckpointReference>,
|
|
1263
|
+
targetThreadId: string,
|
|
1264
|
+
lease: SubagentExecutionPreparationLease
|
|
1265
|
+
): Promise<void> {
|
|
1266
|
+
const checkpointer = this.checkpointer;
|
|
1267
|
+
try {
|
|
1268
|
+
if (
|
|
1269
|
+
checkpointer == null ||
|
|
1270
|
+
sources.length === 0 ||
|
|
1271
|
+
sources.every((source) => source.threadId === targetThreadId)
|
|
1272
|
+
) {
|
|
1273
|
+
lease.assertOwned();
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
const targetExists = await this.checkpointThreadExists(targetThreadId);
|
|
1277
|
+
lease.assertOwned();
|
|
1278
|
+
if (!targetExists) {
|
|
1279
|
+
lease.markCheckpointCreated(() =>
|
|
1280
|
+
checkpointer.deleteThread(targetThreadId)
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
await this.forkCheckpointSnapshot(sources, targetThreadId);
|
|
1284
|
+
lease.assertOwned();
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
return lease.rollback(error);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
private async checkpointThreadExists(threadId: string): Promise<boolean> {
|
|
1291
|
+
if (this.checkpointer == null) {
|
|
1292
|
+
return false;
|
|
1293
|
+
}
|
|
1294
|
+
for await (const _tuple of this.checkpointer.list(
|
|
1295
|
+
{ configurable: { thread_id: threadId } },
|
|
1296
|
+
{ limit: 1 }
|
|
1297
|
+
)) {
|
|
1298
|
+
return true;
|
|
1299
|
+
}
|
|
1300
|
+
return false;
|
|
994
1301
|
}
|
|
995
1302
|
|
|
996
1303
|
/** Captures one exact checkpoint head per namespace for a child thread. */
|
|
@@ -1130,28 +1437,26 @@ export class SubagentExecutor {
|
|
|
1130
1437
|
}
|
|
1131
1438
|
|
|
1132
1439
|
private async createResumeManifest(
|
|
1133
|
-
|
|
1440
|
+
records: ReadonlyArray<DurableExecutionRecord>
|
|
1134
1441
|
): Promise<SubagentResumeManifest | undefined> {
|
|
1135
1442
|
if (this.checkpointer == null) {
|
|
1136
1443
|
return undefined;
|
|
1137
1444
|
}
|
|
1138
1445
|
const executions: SubagentResumeExecution[] = [];
|
|
1139
|
-
for (const
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
!parentToolCallIds.has(parentToolCallId)
|
|
1143
|
-
) {
|
|
1446
|
+
for (const record of records) {
|
|
1447
|
+
const { identity, binding, activeRun } = record.snapshot;
|
|
1448
|
+
if (identity == null) {
|
|
1144
1449
|
continue;
|
|
1145
1450
|
}
|
|
1451
|
+
const { parentToolCallId } = record.address;
|
|
1146
1452
|
const checkpoints = await this.getLatestCheckpointSnapshot(
|
|
1147
1453
|
identity.childThreadId
|
|
1148
1454
|
);
|
|
1149
1455
|
if (checkpoints.length === 0) {
|
|
1150
1456
|
continue;
|
|
1151
1457
|
}
|
|
1152
|
-
const activeChildRun = this.activeChildRuns.get(identity.childThreadId);
|
|
1153
1458
|
const configuredHookSessionId =
|
|
1154
|
-
|
|
1459
|
+
activeRun?.invokeConfig?.configurable?.run_id;
|
|
1155
1460
|
const hookSessionId =
|
|
1156
1461
|
typeof configuredHookSessionId === 'string' &&
|
|
1157
1462
|
configuredHookSessionId.length > 0
|
|
@@ -1162,12 +1467,12 @@ export class SubagentExecutor {
|
|
|
1162
1467
|
identity.approvalExecutionScope,
|
|
1163
1468
|
identity.approvalExecutionScope
|
|
1164
1469
|
) ?? [];
|
|
1165
|
-
const descendant =
|
|
1470
|
+
const descendant = activeRun?.pendingInterrupts
|
|
1166
1471
|
.map((pendingInterrupt) =>
|
|
1167
1472
|
getSubagentResumeManifest(pendingInterrupt.value)
|
|
1168
1473
|
)
|
|
1169
1474
|
.find((manifest) => manifest != null);
|
|
1170
|
-
const graphState =
|
|
1475
|
+
const graphState = activeRun?.graph.createSubagentResumeState(
|
|
1171
1476
|
hookSessionId
|
|
1172
1477
|
) ?? {
|
|
1173
1478
|
toolCallSteps: [],
|
|
@@ -1179,6 +1484,10 @@ export class SubagentExecutor {
|
|
|
1179
1484
|
executions.push({
|
|
1180
1485
|
parentToolCallId,
|
|
1181
1486
|
childRunId: identity.childRunId,
|
|
1487
|
+
...(binding?.subagentType == null
|
|
1488
|
+
? {}
|
|
1489
|
+
: { subagentType: binding.subagentType }),
|
|
1490
|
+
...(binding?.configId == null ? {} : { configId: binding.configId }),
|
|
1182
1491
|
approvalExecutionScope: identity.approvalExecutionScope,
|
|
1183
1492
|
checkpoints,
|
|
1184
1493
|
graphState,
|
|
@@ -1196,25 +1505,22 @@ export class SubagentExecutor {
|
|
|
1196
1505
|
}
|
|
1197
1506
|
|
|
1198
1507
|
getResumeManifest(
|
|
1199
|
-
parentToolCallIds?: ReadonlySet<string
|
|
1508
|
+
parentToolCallIds?: ReadonlySet<string>,
|
|
1509
|
+
config?: RunnableConfig
|
|
1200
1510
|
): Promise<SubagentResumeManifest | undefined> {
|
|
1201
|
-
return this.createResumeManifest(
|
|
1511
|
+
return this.createResumeManifest(
|
|
1512
|
+
this.executions.selectForResume({ parentToolCallIds, config })
|
|
1513
|
+
);
|
|
1202
1514
|
}
|
|
1203
1515
|
|
|
1204
1516
|
getChildCheckpointThreadIds(): string[] {
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
activeChildRun.graph
|
|
1209
|
-
)) {
|
|
1210
|
-
threadIds.add(threadId);
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
return [...threadIds];
|
|
1517
|
+
return this.executions.getCheckpointThreadIds((activeRun) =>
|
|
1518
|
+
this.getGraphChildCheckpointThreadIds(activeRun.graph)
|
|
1519
|
+
);
|
|
1214
1520
|
}
|
|
1215
1521
|
|
|
1216
1522
|
resetCheckpointThreadIds(): void {
|
|
1217
|
-
this.
|
|
1523
|
+
this.executions.resetCheckpointThreadIds();
|
|
1218
1524
|
}
|
|
1219
1525
|
|
|
1220
1526
|
private getGraphChildCheckpointThreadIds(graph: StandardGraph): string[] {
|
|
@@ -1225,25 +1531,54 @@ export class SubagentExecutor {
|
|
|
1225
1531
|
}
|
|
1226
1532
|
|
|
1227
1533
|
private clearChildGraph(graph: StandardGraph): void {
|
|
1228
|
-
|
|
1229
|
-
this.
|
|
1230
|
-
|
|
1534
|
+
this.executions.rememberCheckpointThread(
|
|
1535
|
+
...this.getGraphChildCheckpointThreadIds(graph)
|
|
1536
|
+
);
|
|
1231
1537
|
graph.clearHeavyState();
|
|
1232
1538
|
}
|
|
1233
1539
|
|
|
1234
1540
|
clearHeavyState(): void {
|
|
1235
|
-
|
|
1236
|
-
|
|
1541
|
+
this.executions.clear((record) => {
|
|
1542
|
+
if (record.activeRun != null) {
|
|
1543
|
+
this.clearChildGraph(record.activeRun.graph);
|
|
1544
|
+
}
|
|
1545
|
+
if (record.identity != null) {
|
|
1546
|
+
this.hookRegistry?.clearSession(record.identity.approvalExecutionScope);
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
this.replayCheckpointWorkflow = undefined;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
private async getResumeCheckpointMarker(
|
|
1553
|
+
resumeExecution: SubagentResumeExecution,
|
|
1554
|
+
parentToolCallId: string
|
|
1555
|
+
): Promise<
|
|
1556
|
+
{ marker: SubagentCheckpointMarker; messages: BaseMessage[] } | undefined
|
|
1557
|
+
> {
|
|
1558
|
+
if (this.checkpointer == null) {
|
|
1559
|
+
return undefined;
|
|
1237
1560
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1561
|
+
let fallback:
|
|
1562
|
+
| { marker: SubagentCheckpointMarker; messages: BaseMessage[] }
|
|
1563
|
+
| undefined;
|
|
1564
|
+
for (const checkpoint of resumeExecution.checkpoints) {
|
|
1565
|
+
const tuple = await this.checkpointer.getTuple({
|
|
1566
|
+
configurable: {
|
|
1567
|
+
thread_id: checkpoint.threadId,
|
|
1568
|
+
checkpoint_ns: checkpoint.checkpointNs,
|
|
1569
|
+
checkpoint_id: checkpoint.checkpointId,
|
|
1570
|
+
},
|
|
1571
|
+
});
|
|
1572
|
+
const messages = getTupleMessages(tuple);
|
|
1573
|
+
const marker = getSubagentCheckpointMarker(messages, parentToolCallId);
|
|
1574
|
+
if (marker?.settledOutput != null) {
|
|
1575
|
+
return { marker, messages };
|
|
1576
|
+
}
|
|
1577
|
+
if (marker != null && fallback == null) {
|
|
1578
|
+
fallback = { marker, messages };
|
|
1579
|
+
}
|
|
1242
1580
|
}
|
|
1243
|
-
|
|
1244
|
-
this.startedChildRuns.clear();
|
|
1245
|
-
this.completedChildRuns.clear();
|
|
1246
|
-
this.replayCheckpointWorkflow = undefined;
|
|
1581
|
+
return fallback;
|
|
1247
1582
|
}
|
|
1248
1583
|
|
|
1249
1584
|
async getSettledToolOutput(
|
|
@@ -1263,25 +1598,109 @@ export class SubagentExecutor {
|
|
|
1263
1598
|
| Record<string, unknown>
|
|
1264
1599
|
| undefined;
|
|
1265
1600
|
const threadId = parentConfigurable?.thread_id;
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
parentConfigurable,
|
|
1271
|
-
});
|
|
1272
|
-
this.childExecutionIdentities.set(parentToolCallId, {
|
|
1273
|
-
childRunId,
|
|
1274
|
-
childThreadId,
|
|
1275
|
-
approvalExecutionScope,
|
|
1276
|
-
});
|
|
1277
|
-
this.checkpointThreadIds.add(childThreadId);
|
|
1278
|
-
const checkpoint = await this.checkpointer.getTuple({
|
|
1279
|
-
configurable: { thread_id: childThreadId },
|
|
1601
|
+
const execution = this.executions.open({
|
|
1602
|
+
threadId: typeof threadId === 'string' ? threadId : undefined,
|
|
1603
|
+
parentToolCallId,
|
|
1604
|
+
parentConfigurable,
|
|
1280
1605
|
});
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1606
|
+
const { resumeExecution } = execution;
|
|
1607
|
+
const inProcessSettledOutput = execution.settledOutput;
|
|
1608
|
+
if (inProcessSettledOutput != null) {
|
|
1609
|
+
const persistedSubagentType = getSubagentTypeFromArgs(
|
|
1610
|
+
inProcessSettledOutput.resolvedArgs
|
|
1611
|
+
);
|
|
1612
|
+
const subagentType =
|
|
1613
|
+
persistedSubagentType ??
|
|
1614
|
+
execution.binding?.subagentType ??
|
|
1615
|
+
getSubagentType(call);
|
|
1616
|
+
const executableConfig =
|
|
1617
|
+
subagentType == null ? undefined : this.configs.get(subagentType);
|
|
1618
|
+
if (
|
|
1619
|
+
!isResumeExecutionCompatible(
|
|
1620
|
+
resumeExecution,
|
|
1621
|
+
subagentType,
|
|
1622
|
+
executableConfig,
|
|
1623
|
+
persistedSubagentType != null
|
|
1624
|
+
)
|
|
1625
|
+
) {
|
|
1626
|
+
return undefined;
|
|
1627
|
+
}
|
|
1628
|
+
return deserializeToolOutput(inProcessSettledOutput);
|
|
1629
|
+
}
|
|
1630
|
+
let resumeCheckpoint:
|
|
1631
|
+
| { marker: SubagentCheckpointMarker; messages: BaseMessage[] }
|
|
1632
|
+
| undefined;
|
|
1633
|
+
if (resumeExecution != null) {
|
|
1634
|
+
resumeCheckpoint = await this.getResumeCheckpointMarker(
|
|
1635
|
+
resumeExecution,
|
|
1636
|
+
parentToolCallId
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
let marker = resumeCheckpoint?.marker;
|
|
1640
|
+
let messages = resumeCheckpoint?.messages ?? [];
|
|
1641
|
+
let persistedSubagentType = getSubagentTypeFromArgs(
|
|
1642
|
+
marker?.settledOutput?.resolvedArgs
|
|
1283
1643
|
);
|
|
1284
|
-
|
|
1644
|
+
let subagentType =
|
|
1645
|
+
persistedSubagentType ??
|
|
1646
|
+
resumeExecution?.subagentType ??
|
|
1647
|
+
getSubagentType(call);
|
|
1648
|
+
let executableConfig =
|
|
1649
|
+
subagentType == null ? undefined : this.configs.get(subagentType);
|
|
1650
|
+
if (
|
|
1651
|
+
!isResumeExecutionCompatible(
|
|
1652
|
+
resumeExecution,
|
|
1653
|
+
subagentType,
|
|
1654
|
+
executableConfig,
|
|
1655
|
+
persistedSubagentType != null
|
|
1656
|
+
)
|
|
1657
|
+
) {
|
|
1658
|
+
this.executions.remove(execution);
|
|
1659
|
+
return undefined;
|
|
1660
|
+
}
|
|
1661
|
+
const { childThreadId } =
|
|
1662
|
+
await this.resolveChildExecutionIdentity(execution);
|
|
1663
|
+
if (marker == null) {
|
|
1664
|
+
const checkpoint = await this.checkpointer.getTuple({
|
|
1665
|
+
configurable: { thread_id: childThreadId },
|
|
1666
|
+
});
|
|
1667
|
+
messages = getCheckpointMessages(
|
|
1668
|
+
checkpoint?.checkpoint.channel_values.messages
|
|
1669
|
+
);
|
|
1670
|
+
marker = getSubagentCheckpointMarker(messages, parentToolCallId);
|
|
1671
|
+
persistedSubagentType = getSubagentTypeFromArgs(
|
|
1672
|
+
marker?.settledOutput?.resolvedArgs
|
|
1673
|
+
);
|
|
1674
|
+
if (persistedSubagentType != null) {
|
|
1675
|
+
subagentType = persistedSubagentType;
|
|
1676
|
+
executableConfig = this.configs.get(subagentType);
|
|
1677
|
+
}
|
|
1678
|
+
if (
|
|
1679
|
+
!isResumeExecutionCompatible(
|
|
1680
|
+
resumeExecution,
|
|
1681
|
+
subagentType,
|
|
1682
|
+
executableConfig,
|
|
1683
|
+
persistedSubagentType != null
|
|
1684
|
+
)
|
|
1685
|
+
) {
|
|
1686
|
+
this.executions.remove(execution);
|
|
1687
|
+
return undefined;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
const configId = executableConfig?.configId;
|
|
1691
|
+
const bound = this.bindExecutionDefinition(
|
|
1692
|
+
execution,
|
|
1693
|
+
{
|
|
1694
|
+
...(subagentType == null ? {} : { subagentType }),
|
|
1695
|
+
...(configId == null ? {} : { configId }),
|
|
1696
|
+
},
|
|
1697
|
+
persistedSubagentType != null || resumeExecution?.subagentType != null
|
|
1698
|
+
? 'effective'
|
|
1699
|
+
: 'provisional'
|
|
1700
|
+
);
|
|
1701
|
+
if (!bound) {
|
|
1702
|
+
return undefined;
|
|
1703
|
+
}
|
|
1285
1704
|
const persistedHookSessionId =
|
|
1286
1705
|
marker?.hookSessionId ?? getSubagentHookSessionId(messages);
|
|
1287
1706
|
const currentHookSessionId = parentConfigurable?.run_id;
|
|
@@ -1306,9 +1725,10 @@ export class SubagentExecutor {
|
|
|
1306
1725
|
settled: SettledSubagentToolOutput
|
|
1307
1726
|
): Promise<void> {
|
|
1308
1727
|
const parentToolCallId = call.id;
|
|
1728
|
+
const checkpointer = this.checkpointer;
|
|
1309
1729
|
if (
|
|
1310
1730
|
this.humanInTheLoop?.enabled !== true ||
|
|
1311
|
-
|
|
1731
|
+
checkpointer == null ||
|
|
1312
1732
|
parentToolCallId == null ||
|
|
1313
1733
|
parentToolCallId === ''
|
|
1314
1734
|
) {
|
|
@@ -1318,52 +1738,117 @@ export class SubagentExecutor {
|
|
|
1318
1738
|
| Record<string, unknown>
|
|
1319
1739
|
| undefined;
|
|
1320
1740
|
const threadId = parentConfigurable?.thread_id;
|
|
1321
|
-
const
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
parentConfigurable,
|
|
1326
|
-
});
|
|
1327
|
-
this.childExecutionIdentities.set(parentToolCallId, {
|
|
1328
|
-
childRunId,
|
|
1329
|
-
childThreadId,
|
|
1330
|
-
approvalExecutionScope,
|
|
1741
|
+
const execution = this.executions.open({
|
|
1742
|
+
threadId: typeof threadId === 'string' ? threadId : undefined,
|
|
1743
|
+
parentToolCallId,
|
|
1744
|
+
parentConfigurable,
|
|
1331
1745
|
});
|
|
1332
|
-
|
|
1333
|
-
const
|
|
1746
|
+
const { resumeExecution } = execution;
|
|
1747
|
+
const resolvedSubagentType = getSubagentTypeFromArgs(settled.resolvedArgs);
|
|
1748
|
+
const resolvedDescription = getSubagentDescriptionFromArgs(
|
|
1749
|
+
settled.resolvedArgs
|
|
1750
|
+
);
|
|
1751
|
+
const boundInvocation = execution.invocation;
|
|
1752
|
+
if (
|
|
1753
|
+
boundInvocation != null &&
|
|
1754
|
+
resolvedDescription != null &&
|
|
1755
|
+
resolvedDescription !== boundInvocation.description
|
|
1756
|
+
) {
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
const description =
|
|
1760
|
+
boundInvocation?.description ??
|
|
1761
|
+
resolvedDescription ??
|
|
1762
|
+
getSubagentDescriptionFromArgs(call.args) ??
|
|
1763
|
+
DEFAULT_SUBAGENT_DESCRIPTION;
|
|
1764
|
+
const subagentType =
|
|
1765
|
+
resolvedSubagentType ??
|
|
1766
|
+
execution.binding?.subagentType ??
|
|
1767
|
+
getSubagentType(call);
|
|
1768
|
+
const executableConfig =
|
|
1769
|
+
subagentType == null ? undefined : this.configs.get(subagentType);
|
|
1770
|
+
const configId =
|
|
1771
|
+
resolvedSubagentType == null
|
|
1772
|
+
? (execution.binding?.configId ??
|
|
1773
|
+
executableConfig?.configId ??
|
|
1774
|
+
resumeExecution?.configId)
|
|
1775
|
+
: executableConfig?.configId;
|
|
1776
|
+
if (subagentType == null) {
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
if (
|
|
1780
|
+
!isResumeExecutionCompatible(
|
|
1781
|
+
resumeExecution,
|
|
1782
|
+
subagentType,
|
|
1783
|
+
executableConfig
|
|
1784
|
+
)
|
|
1785
|
+
) {
|
|
1786
|
+
this.executions.remove(execution);
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1334
1789
|
const persistedOutput = serializeToolOutput(settled);
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1790
|
+
const invocation = boundInvocation ?? {
|
|
1791
|
+
description,
|
|
1792
|
+
subagentType,
|
|
1793
|
+
...(configId == null ? {} : { configId }),
|
|
1794
|
+
};
|
|
1795
|
+
try {
|
|
1796
|
+
await execution.settle(
|
|
1797
|
+
{
|
|
1798
|
+
definitionAuthority:
|
|
1799
|
+
resolvedSubagentType == null ? 'provisional' : 'effective',
|
|
1800
|
+
fingerprint: getSettlementFingerprint(persistedOutput),
|
|
1801
|
+
invocation,
|
|
1802
|
+
subagentType,
|
|
1803
|
+
...(configId == null ? {} : { configId }),
|
|
1804
|
+
},
|
|
1805
|
+
persistedOutput,
|
|
1806
|
+
async (): Promise<void> => {
|
|
1807
|
+
const { childRunId, childThreadId } =
|
|
1808
|
+
await this.resolveChildExecutionIdentity(execution);
|
|
1809
|
+
const activeChildRun = execution.activeRun;
|
|
1810
|
+
if (activeChildRun != null) {
|
|
1811
|
+
await this.persistChildCheckpointMarker(
|
|
1812
|
+
activeChildRun,
|
|
1813
|
+
parentToolCallId,
|
|
1814
|
+
persistedOutput
|
|
1815
|
+
);
|
|
1816
|
+
this.clearChildGraph(activeChildRun.graph);
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
this.replayCheckpointWorkflow ??=
|
|
1820
|
+
createReplayCheckpointWorkflow(checkpointer);
|
|
1821
|
+
await this.replayCheckpointWorkflow.updateState(
|
|
1822
|
+
{ configurable: { thread_id: childThreadId } },
|
|
1823
|
+
{
|
|
1824
|
+
messages: [
|
|
1825
|
+
createSubagentCheckpointMarkerMessage({
|
|
1826
|
+
version: 1,
|
|
1827
|
+
parentToolCallId,
|
|
1828
|
+
lifecycleComplete: true,
|
|
1829
|
+
hookSessionId:
|
|
1830
|
+
typeof parentConfigurable?.run_id === 'string'
|
|
1831
|
+
? parentConfigurable.run_id
|
|
1832
|
+
: this.parentRunId,
|
|
1833
|
+
childRunId,
|
|
1834
|
+
settledOutput: persistedOutput,
|
|
1835
|
+
}),
|
|
1836
|
+
],
|
|
1837
|
+
},
|
|
1838
|
+
SUBAGENT_REPLAY_NODE
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1340
1841
|
);
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1842
|
+
} catch (error) {
|
|
1843
|
+
if (
|
|
1844
|
+
error instanceof SubagentDefinitionBindingError ||
|
|
1845
|
+
error instanceof SubagentInvocationBindingError ||
|
|
1846
|
+
error instanceof SubagentSettlementBindingError
|
|
1847
|
+
) {
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
throw error;
|
|
1344
1851
|
}
|
|
1345
|
-
this.replayCheckpointWorkflow ??= createReplayCheckpointWorkflow(
|
|
1346
|
-
this.checkpointer
|
|
1347
|
-
);
|
|
1348
|
-
await this.replayCheckpointWorkflow.updateState(
|
|
1349
|
-
{ configurable: { thread_id: childThreadId } },
|
|
1350
|
-
{
|
|
1351
|
-
messages: [
|
|
1352
|
-
createSubagentCheckpointMarkerMessage({
|
|
1353
|
-
version: 1,
|
|
1354
|
-
parentToolCallId,
|
|
1355
|
-
lifecycleComplete: true,
|
|
1356
|
-
hookSessionId:
|
|
1357
|
-
typeof parentConfigurable?.run_id === 'string'
|
|
1358
|
-
? parentConfigurable.run_id
|
|
1359
|
-
: this.parentRunId,
|
|
1360
|
-
childRunId,
|
|
1361
|
-
settledOutput: persistedOutput,
|
|
1362
|
-
}),
|
|
1363
|
-
],
|
|
1364
|
-
},
|
|
1365
|
-
SUBAGENT_REPLAY_NODE
|
|
1366
|
-
);
|
|
1367
1852
|
}
|
|
1368
1853
|
|
|
1369
1854
|
private async persistChildCheckpointMarker(
|
|
@@ -1396,11 +1881,83 @@ export class SubagentExecutor {
|
|
|
1396
1881
|
}),
|
|
1397
1882
|
],
|
|
1398
1883
|
},
|
|
1399
|
-
activeChildRun.
|
|
1884
|
+
activeChildRun.checkpointNodeId
|
|
1400
1885
|
);
|
|
1401
1886
|
}
|
|
1402
1887
|
|
|
1403
|
-
|
|
1888
|
+
execute(params: SubagentExecuteParams): Promise<SubagentExecuteResult> {
|
|
1889
|
+
const executableConfig = this.configs.get(params.subagentType);
|
|
1890
|
+
if (executableConfig == null) {
|
|
1891
|
+
const available = [...this.configs.keys()].join(', ');
|
|
1892
|
+
return Promise.resolve({
|
|
1893
|
+
content: `Error: Unknown subagent type "${params.subagentType}". Available types: ${available}`,
|
|
1894
|
+
messages: [],
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
if (this.maxDepth <= 0) {
|
|
1898
|
+
return Promise.resolve({
|
|
1899
|
+
content: 'Error: Maximum subagent nesting depth exceeded.',
|
|
1900
|
+
messages: [],
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
if (
|
|
1904
|
+
isGraphSubagentConfig(executableConfig) &&
|
|
1905
|
+
this.humanInTheLoop?.enabled === true
|
|
1906
|
+
) {
|
|
1907
|
+
return Promise.resolve({
|
|
1908
|
+
content:
|
|
1909
|
+
'Error: Human-in-the-loop execution is not yet supported for graph subagents.',
|
|
1910
|
+
messages: [],
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
if (
|
|
1914
|
+
this.humanInTheLoop?.enabled === true &&
|
|
1915
|
+
(params.parentToolCallId == null || params.parentToolCallId === '')
|
|
1916
|
+
) {
|
|
1917
|
+
return Promise.resolve({
|
|
1918
|
+
content:
|
|
1919
|
+
'Error: Resumable subagent execution requires a parent tool call ID.',
|
|
1920
|
+
messages: [],
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
const execution = this.executions.open({
|
|
1924
|
+
threadId: params.threadId,
|
|
1925
|
+
parentToolCallId: params.parentToolCallId ?? nanoid(8),
|
|
1926
|
+
parentConfigurable: params.parentConfigurable,
|
|
1927
|
+
});
|
|
1928
|
+
try {
|
|
1929
|
+
return execution.execute(
|
|
1930
|
+
{
|
|
1931
|
+
description: params.description,
|
|
1932
|
+
subagentType: params.subagentType,
|
|
1933
|
+
...(executableConfig.configId == null
|
|
1934
|
+
? {}
|
|
1935
|
+
: { configId: executableConfig.configId }),
|
|
1936
|
+
},
|
|
1937
|
+
() => this.executeOnce(params, execution, executableConfig)
|
|
1938
|
+
);
|
|
1939
|
+
} catch (error) {
|
|
1940
|
+
if (error instanceof SubagentDefinitionBindingError) {
|
|
1941
|
+
return Promise.resolve({
|
|
1942
|
+
content: SUBAGENT_CONFIG_CHANGED_MESSAGE,
|
|
1943
|
+
messages: [],
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
if (error instanceof SubagentInvocationBindingError) {
|
|
1947
|
+
return Promise.resolve({
|
|
1948
|
+
content: SUBAGENT_INVOCATION_CHANGED_MESSAGE,
|
|
1949
|
+
messages: [],
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
throw error;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
private async executeOnce(
|
|
1957
|
+
params: SubagentExecuteParams,
|
|
1958
|
+
execution: DurableExecutionRecord,
|
|
1959
|
+
executableConfig: ExecutableSubagentConfigEntry
|
|
1960
|
+
): Promise<SubagentExecuteResult> {
|
|
1404
1961
|
const { description, subagentType, threadId, parentToolCallId } = params;
|
|
1405
1962
|
/** Captured ONCE per execution, preferring the controller the parent
|
|
1406
1963
|
* tool batch captured at ITS entry (before PreToolUse hooks): a failed
|
|
@@ -1410,61 +1967,75 @@ export class SubagentExecutor {
|
|
|
1410
1967
|
* new run's breaker from an old child's stream-limit breach. Signal and
|
|
1411
1968
|
* trip target both bind to this capture. */
|
|
1412
1969
|
const childBreaker = params.breaker ?? this.resolveBreakerController();
|
|
1413
|
-
const childSignal = this.composeChildSignal(childBreaker);
|
|
1414
|
-
const config = this.configs.get(subagentType);
|
|
1970
|
+
const childSignal = this.composeChildSignal(childBreaker, params.signal);
|
|
1415
1971
|
|
|
1416
|
-
|
|
1417
|
-
|
|
1972
|
+
const { parentToolCallId: executionSuffix } = execution.address;
|
|
1973
|
+
const { resumeExecution } = execution;
|
|
1974
|
+
if (
|
|
1975
|
+
!isResumeExecutionCompatible(
|
|
1976
|
+
resumeExecution,
|
|
1977
|
+
subagentType,
|
|
1978
|
+
executableConfig
|
|
1979
|
+
)
|
|
1980
|
+
) {
|
|
1981
|
+
this.executions.remove(execution);
|
|
1418
1982
|
return {
|
|
1419
|
-
content:
|
|
1983
|
+
content: SUBAGENT_CONFIG_CHANGED_MESSAGE,
|
|
1420
1984
|
messages: [],
|
|
1421
1985
|
};
|
|
1422
1986
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1987
|
+
let identity: SubagentExecutionIdentity;
|
|
1988
|
+
try {
|
|
1989
|
+
identity = await this.resolveChildExecutionIdentity(execution);
|
|
1990
|
+
execution.assertUsable(childSignal);
|
|
1991
|
+
} catch (error) {
|
|
1992
|
+
if (error instanceof StreamLimitExceededError) {
|
|
1993
|
+
throw error;
|
|
1994
|
+
}
|
|
1425
1995
|
return {
|
|
1426
|
-
content:
|
|
1996
|
+
content: SUBAGENT_RESOLUTION_ERROR_MESSAGE,
|
|
1427
1997
|
messages: [],
|
|
1428
1998
|
};
|
|
1429
1999
|
}
|
|
2000
|
+
const { childRunId, childThreadId, approvalExecutionScope } = identity;
|
|
2001
|
+
const bound = this.bindExecutionDefinition(
|
|
2002
|
+
execution,
|
|
2003
|
+
{
|
|
2004
|
+
subagentType,
|
|
2005
|
+
...(executableConfig.configId == null
|
|
2006
|
+
? {}
|
|
2007
|
+
: { configId: executableConfig.configId }),
|
|
2008
|
+
},
|
|
2009
|
+
'effective'
|
|
2010
|
+
);
|
|
2011
|
+
if (!bound) {
|
|
2012
|
+
return { content: SUBAGENT_CONFIG_CHANGED_MESSAGE, messages: [] };
|
|
2013
|
+
}
|
|
2014
|
+
const completedChildResult = execution.completedResult;
|
|
2015
|
+
if (completedChildResult != null) {
|
|
2016
|
+
return completedChildResult;
|
|
2017
|
+
}
|
|
1430
2018
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
2019
|
+
let config: ResolvedSubagentConfigEntry;
|
|
2020
|
+
try {
|
|
2021
|
+
config = await this.resolveExecutionConfig(executableConfig, execution, {
|
|
2022
|
+
childRunId,
|
|
2023
|
+
childSignal,
|
|
2024
|
+
threadId,
|
|
2025
|
+
parentToolCallId,
|
|
2026
|
+
parentConfigurable: params.parentConfigurable,
|
|
2027
|
+
});
|
|
2028
|
+
execution.assertUsable(childSignal);
|
|
2029
|
+
} catch (error) {
|
|
2030
|
+
if (error instanceof StreamLimitExceededError) {
|
|
2031
|
+
throw error;
|
|
2032
|
+
}
|
|
1435
2033
|
return {
|
|
1436
|
-
content:
|
|
1437
|
-
'Error: Resumable subagent execution requires a parent tool call ID.',
|
|
2034
|
+
content: SUBAGENT_RESOLUTION_ERROR_MESSAGE,
|
|
1438
2035
|
messages: [],
|
|
1439
2036
|
};
|
|
1440
2037
|
}
|
|
1441
2038
|
|
|
1442
|
-
const executionSuffix = parentToolCallId ?? nanoid(8);
|
|
1443
|
-
const {
|
|
1444
|
-
childRunId,
|
|
1445
|
-
childThreadId,
|
|
1446
|
-
approvalExecutionScope,
|
|
1447
|
-
resumeExecution,
|
|
1448
|
-
} = await this.resolveChildExecutionIdentity({
|
|
1449
|
-
threadId,
|
|
1450
|
-
parentToolCallId: executionSuffix,
|
|
1451
|
-
parentConfigurable: params.parentConfigurable,
|
|
1452
|
-
});
|
|
1453
|
-
this.childExecutionIdentities.set(executionSuffix, {
|
|
1454
|
-
childRunId,
|
|
1455
|
-
childThreadId,
|
|
1456
|
-
approvalExecutionScope,
|
|
1457
|
-
});
|
|
1458
|
-
const childExecutionKey = childThreadId;
|
|
1459
|
-
const childAgentId =
|
|
1460
|
-
config.agentInputs.agentId ||
|
|
1461
|
-
`${this.parentAgentId ?? 'agent'}_sub_${executionSuffix}`;
|
|
1462
|
-
const completedChildResult =
|
|
1463
|
-
this.completedChildResults.get(childExecutionKey);
|
|
1464
|
-
if (completedChildResult != null) {
|
|
1465
|
-
return completedChildResult;
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
2039
|
const parentRegistry = this.getParentHandlerRegistry();
|
|
1469
2040
|
const forwardingEnabled = parentRegistry != null;
|
|
1470
2041
|
/**
|
|
@@ -1478,79 +2049,121 @@ export class SubagentExecutor {
|
|
|
1478
2049
|
*/
|
|
1479
2050
|
const hasToolExecuteHandler =
|
|
1480
2051
|
parentRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) != null;
|
|
1481
|
-
const
|
|
2052
|
+
const childPlan = createChildGraphPlan({
|
|
1482
2053
|
config,
|
|
1483
|
-
|
|
1484
|
-
this.
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
2054
|
+
executionSuffix,
|
|
2055
|
+
parentAgentId: this.parentAgentId,
|
|
2056
|
+
parentMaxDepth: this.maxDepth,
|
|
2057
|
+
keepToolDefinitions: hasToolExecuteHandler,
|
|
2058
|
+
});
|
|
2059
|
+
const childAgentId = childPlan.subjectAgentId;
|
|
2060
|
+
const currentHookSessionId =
|
|
2061
|
+
asNonEmptyString(params.parentConfigurable?.run_id) ??
|
|
2062
|
+
this.executionContext.hookSessionId;
|
|
2063
|
+
const childExecutionContext: SubagentExecutionContext = {
|
|
2064
|
+
rootRunId: this.executionContext.rootRunId,
|
|
2065
|
+
hookSessionId: currentHookSessionId,
|
|
2066
|
+
depth: this.executionContext.depth + 1,
|
|
2067
|
+
ancestry: [
|
|
2068
|
+
...this.executionContext.ancestry,
|
|
2069
|
+
{
|
|
2070
|
+
subagentRunId: childRunId,
|
|
2071
|
+
subagentType,
|
|
2072
|
+
subagentKind: childPlan.kind,
|
|
2073
|
+
subagentAgentId: childAgentId,
|
|
2074
|
+
parentRunId: this.parentRunId,
|
|
2075
|
+
parentAgentId: this.parentAgentId,
|
|
2076
|
+
parentToolCallId,
|
|
2077
|
+
},
|
|
2078
|
+
],
|
|
2079
|
+
};
|
|
2080
|
+
const maxTurns = config.maxTurns ?? DEFAULT_SUBAGENT_MAX_TURNS;
|
|
2081
|
+
const memberRecursionLimit = maxTurns * SUBAGENT_RECURSION_MULTIPLIER;
|
|
2082
|
+
const recursionLimit =
|
|
2083
|
+
memberRecursionLimit *
|
|
2084
|
+
(childPlan.kind === 'graph' ? childPlan.agents.length : 1);
|
|
1488
2085
|
|
|
1489
2086
|
const hostUsageSink = this.usageSink;
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
2087
|
+
let subagentUsageSink: SubagentUsageSink | undefined;
|
|
2088
|
+
if (hostUsageSink != null) {
|
|
2089
|
+
subagentUsageSink = (event): void | Promise<void> =>
|
|
2090
|
+
hostUsageSink({
|
|
2091
|
+
...event,
|
|
2092
|
+
runId: this.executionContext.rootRunId,
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
const cachedChildRun = execution.activeRun;
|
|
2096
|
+
const childGraphInput: StandardGraphInput = {
|
|
2097
|
+
runId: childRunId,
|
|
2098
|
+
signal: childSignal,
|
|
2099
|
+
agents: childPlan.agents,
|
|
2100
|
+
langfuse: this.langfuse,
|
|
2101
|
+
tokenCounter: this.tokenCounter,
|
|
2102
|
+
streamLimits: this.streamLimits,
|
|
2103
|
+
subagentScope: true,
|
|
2104
|
+
subagentExecutionContext: childExecutionContext,
|
|
2105
|
+
/**
|
|
2106
|
+
* Forwarded so the child graph's own `SubagentExecutor` (created in
|
|
2107
|
+
* its `createAgentNode` when `allowNested` keeps subagentConfigs)
|
|
2108
|
+
* reports nested-child usage through the same host sink. Each nesting
|
|
2109
|
+
* level attaches its own capture callback — `workflow.invoke` replaces
|
|
2110
|
+
* the inherited callback chain, so a single top-level handler would
|
|
2111
|
+
* never see grandchild model calls.
|
|
2112
|
+
*/
|
|
2113
|
+
subagentUsageSink,
|
|
2114
|
+
};
|
|
2115
|
+
let childGraph = cachedChildRun?.graph;
|
|
2116
|
+
if (childGraph == null && childPlan.kind === 'graph') {
|
|
2117
|
+
if (this.createChildGraphByKind == null) {
|
|
2118
|
+
return {
|
|
2119
|
+
content:
|
|
2120
|
+
'Error: Graph subagent execution requires a polymorphic child graph factory.',
|
|
2121
|
+
messages: [],
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
childGraph = this.createChildGraphByKind({
|
|
2125
|
+
kind: 'multi-agent',
|
|
2126
|
+
input: {
|
|
2127
|
+
...childGraphInput,
|
|
2128
|
+
edges: childPlan.edges,
|
|
2129
|
+
resultAgentId: childPlan.resultAgentId,
|
|
2130
|
+
memberRecursionLimit,
|
|
2131
|
+
},
|
|
1523
2132
|
});
|
|
2133
|
+
}
|
|
2134
|
+
childGraph ??= this.createChildGraph(childGraphInput);
|
|
1524
2135
|
let forwarding: ForwarderCallback | undefined;
|
|
1525
2136
|
if (forwardingEnabled) {
|
|
1526
2137
|
forwarding = this.createForwarderCallback({
|
|
1527
2138
|
parentRegistry: parentRegistry!,
|
|
1528
2139
|
subagentType,
|
|
2140
|
+
subagentKind: childPlan.kind,
|
|
1529
2141
|
subagentAgentId: childAgentId,
|
|
1530
2142
|
childRunId,
|
|
1531
2143
|
parentToolCallId,
|
|
2144
|
+
executionContext: childExecutionContext,
|
|
1532
2145
|
});
|
|
1533
2146
|
}
|
|
1534
2147
|
const forwarder = forwarding?.handler;
|
|
1535
|
-
let childAlreadyStarted =
|
|
1536
|
-
let childAlreadyCompleted =
|
|
2148
|
+
let childAlreadyStarted = execution.started;
|
|
2149
|
+
let childAlreadyCompleted = execution.completed;
|
|
1537
2150
|
|
|
1538
|
-
let result:
|
|
2151
|
+
let result: MultiAgentGraphState | undefined;
|
|
1539
2152
|
let recoveredComplete = false;
|
|
1540
2153
|
let recoveredInProgress = false;
|
|
1541
2154
|
try {
|
|
1542
2155
|
const workflow = (cachedChildRun?.workflow ??
|
|
1543
2156
|
childGraph.createWorkflow()) as StatefulCompiledWorkflow;
|
|
1544
|
-
const activeChildRun =
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
2157
|
+
const activeChildRun = execution.activate(
|
|
2158
|
+
cachedChildRun ?? {
|
|
2159
|
+
graph: childGraph,
|
|
2160
|
+
workflow,
|
|
2161
|
+
pendingInterrupts: [],
|
|
2162
|
+
childAgentId,
|
|
2163
|
+
checkpointNodeId: childPlan.checkpointNodeId,
|
|
2164
|
+
childRunId,
|
|
2165
|
+
}
|
|
2166
|
+
);
|
|
1554
2167
|
/**
|
|
1555
2168
|
* When `parentHandlerRegistry` is provided (forwarding mode), attach a
|
|
1556
2169
|
* lightweight callback that intercepts the child's `on_custom_event`
|
|
@@ -1584,11 +2197,12 @@ export class SubagentExecutor {
|
|
|
1584
2197
|
createUsageCaptureHandler({
|
|
1585
2198
|
sink: this.usageSink,
|
|
1586
2199
|
subagentType,
|
|
2200
|
+
subagentKind: childPlan.kind,
|
|
1587
2201
|
subagentRunId: childRunId,
|
|
1588
2202
|
subagentAgentId: childAgentId,
|
|
1589
2203
|
parentRunId: this.parentRunId,
|
|
1590
|
-
|
|
1591
|
-
|
|
2204
|
+
executionContext: childExecutionContext,
|
|
2205
|
+
memberInputs: childPlan.memberInputs,
|
|
1592
2206
|
})
|
|
1593
2207
|
);
|
|
1594
2208
|
}
|
|
@@ -1608,46 +2222,35 @@ export class SubagentExecutor {
|
|
|
1608
2222
|
*/
|
|
1609
2223
|
const inheritedConfigurable: Record<string, unknown> =
|
|
1610
2224
|
sanitizeChildConfigurable(params.parentConfigurable);
|
|
1611
|
-
const resumeAttemptId =
|
|
1612
|
-
params.parentConfigurable,
|
|
1613
|
-
this.parentRunId
|
|
1614
|
-
);
|
|
1615
|
-
const currentHookSessionId =
|
|
1616
|
-
typeof inheritedConfigurable.run_id === 'string' &&
|
|
1617
|
-
inheritedConfigurable.run_id.length > 0
|
|
1618
|
-
? inheritedConfigurable.run_id
|
|
1619
|
-
: this.parentRunId;
|
|
2225
|
+
const { resumeAttemptId } = execution.address;
|
|
1620
2226
|
if (cachedChildRun == null && resumeExecution != null) {
|
|
1621
2227
|
childGraph.restoreSubagentResumeState(
|
|
1622
2228
|
resumeExecution.graphState,
|
|
1623
2229
|
currentHookSessionId
|
|
1624
2230
|
);
|
|
1625
2231
|
}
|
|
2232
|
+
const childConfigurable: Record<string, unknown> = {
|
|
2233
|
+
...inheritedConfigurable,
|
|
2234
|
+
};
|
|
2235
|
+
if (this.humanInTheLoop?.enabled === true) {
|
|
2236
|
+
childConfigurable[TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY] =
|
|
2237
|
+
approvalExecutionScope;
|
|
2238
|
+
}
|
|
2239
|
+
if (resumeExecution?.descendant != null) {
|
|
2240
|
+
childConfigurable[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY] =
|
|
2241
|
+
resumeExecution.descendant;
|
|
2242
|
+
childConfigurable[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY] = resumeAttemptId;
|
|
2243
|
+
}
|
|
2244
|
+
childConfigurable.thread_id =
|
|
2245
|
+
this.humanInTheLoop?.enabled === true
|
|
2246
|
+
? childThreadId
|
|
2247
|
+
: (inheritedConfigurable.thread_id ?? childRunId);
|
|
1626
2248
|
const childInvokeConfig = {
|
|
1627
|
-
recursionLimit
|
|
2249
|
+
recursionLimit,
|
|
1628
2250
|
signal: childSignal,
|
|
1629
2251
|
callbacks,
|
|
1630
2252
|
runName: `subagent:${subagentType}`,
|
|
1631
|
-
configurable:
|
|
1632
|
-
...inheritedConfigurable,
|
|
1633
|
-
...(this.humanInTheLoop?.enabled === true
|
|
1634
|
-
? {
|
|
1635
|
-
[TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY]:
|
|
1636
|
-
approvalExecutionScope,
|
|
1637
|
-
}
|
|
1638
|
-
: {}),
|
|
1639
|
-
...(resumeExecution?.descendant == null
|
|
1640
|
-
? {}
|
|
1641
|
-
: {
|
|
1642
|
-
[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]:
|
|
1643
|
-
resumeExecution.descendant,
|
|
1644
|
-
[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: resumeAttemptId,
|
|
1645
|
-
}),
|
|
1646
|
-
thread_id:
|
|
1647
|
-
this.humanInTheLoop?.enabled === true
|
|
1648
|
-
? childThreadId
|
|
1649
|
-
: (inheritedConfigurable.thread_id ?? childRunId),
|
|
1650
|
-
},
|
|
2253
|
+
configurable: childConfigurable,
|
|
1651
2254
|
};
|
|
1652
2255
|
activeChildRun.invokeConfig = childInvokeConfig;
|
|
1653
2256
|
if (cachedChildRun == null && this.humanInTheLoop?.enabled === true) {
|
|
@@ -1669,12 +2272,12 @@ export class SubagentExecutor {
|
|
|
1669
2272
|
const persistedInterrupts = getPersistedInterrupts(persistedState);
|
|
1670
2273
|
if (persistedInterrupts.length > 0) {
|
|
1671
2274
|
activeChildRun.pendingInterrupts = persistedInterrupts;
|
|
1672
|
-
|
|
2275
|
+
execution.markStarted();
|
|
1673
2276
|
childAlreadyStarted = true;
|
|
1674
2277
|
} else if (persistedState.next.length > 0) {
|
|
1675
2278
|
recoveredInProgress = true;
|
|
1676
2279
|
childAlreadyStarted = true;
|
|
1677
|
-
|
|
2280
|
+
execution.markStarted();
|
|
1678
2281
|
} else if (persistedState.next.length === 0) {
|
|
1679
2282
|
const persistedMessages = getPersistedMessages(persistedState);
|
|
1680
2283
|
if (persistedMessages != null) {
|
|
@@ -1686,7 +2289,7 @@ export class SubagentExecutor {
|
|
|
1686
2289
|
recoveredComplete = true;
|
|
1687
2290
|
childAlreadyStarted = true;
|
|
1688
2291
|
childAlreadyCompleted = marker?.lifecycleComplete === true;
|
|
1689
|
-
|
|
2292
|
+
execution.markStarted();
|
|
1690
2293
|
}
|
|
1691
2294
|
}
|
|
1692
2295
|
}
|
|
@@ -1722,54 +2325,59 @@ export class SubagentExecutor {
|
|
|
1722
2325
|
|
|
1723
2326
|
if (
|
|
1724
2327
|
!childAlreadyStarted &&
|
|
1725
|
-
this.hookRegistry?.hasHookFor(
|
|
1726
|
-
|
|
2328
|
+
this.hookRegistry?.hasHookFor(
|
|
2329
|
+
'SubagentStart',
|
|
2330
|
+
currentHookSessionId
|
|
2331
|
+
) === true
|
|
1727
2332
|
) {
|
|
1728
2333
|
const hookResult = await executeHooks({
|
|
1729
2334
|
registry: this.hookRegistry,
|
|
1730
2335
|
input: {
|
|
1731
2336
|
hook_event_name: 'SubagentStart',
|
|
1732
|
-
runId:
|
|
2337
|
+
runId: currentHookSessionId,
|
|
1733
2338
|
threadId,
|
|
1734
2339
|
parentAgentId: this.parentAgentId,
|
|
1735
2340
|
agentId: childAgentId,
|
|
1736
2341
|
agentType: subagentType,
|
|
1737
2342
|
inputs: [new HumanMessage(description)],
|
|
1738
2343
|
},
|
|
1739
|
-
sessionId:
|
|
2344
|
+
sessionId: currentHookSessionId,
|
|
1740
2345
|
matchQuery: subagentType,
|
|
1741
2346
|
}).catch((): AggregatedHookResult => HOOK_FALLBACK);
|
|
1742
2347
|
|
|
1743
2348
|
if (hookResult.decision === 'deny' || hookResult.decision === 'ask') {
|
|
1744
2349
|
this.clearChildGraph(childGraph);
|
|
1745
|
-
|
|
2350
|
+
execution.releaseActiveRun();
|
|
2351
|
+
this.executions.remove(execution);
|
|
1746
2352
|
return {
|
|
1747
2353
|
content: `Blocked: ${hookResult.reason ?? 'Blocked by hook'}`,
|
|
1748
2354
|
messages: [],
|
|
1749
2355
|
};
|
|
1750
2356
|
}
|
|
1751
2357
|
}
|
|
1752
|
-
|
|
2358
|
+
execution.markStarted();
|
|
1753
2359
|
|
|
1754
2360
|
if (forwarder && !childAlreadyStarted) {
|
|
1755
2361
|
await this.emitSubagentUpdate(parentRegistry!, {
|
|
1756
2362
|
childRunId,
|
|
1757
2363
|
subagentType,
|
|
2364
|
+
subagentKind: childPlan.kind,
|
|
1758
2365
|
subagentAgentId: childAgentId,
|
|
1759
2366
|
parentToolCallId,
|
|
2367
|
+
executionContext: childExecutionContext,
|
|
1760
2368
|
phase: 'start',
|
|
1761
2369
|
label: `Subagent "${subagentType}" started`,
|
|
1762
2370
|
});
|
|
1763
2371
|
}
|
|
1764
2372
|
|
|
1765
|
-
let childResult:
|
|
2373
|
+
let childResult: MultiAgentGraphState;
|
|
1766
2374
|
if (this.humanInTheLoop?.enabled === true) {
|
|
1767
2375
|
/** Execute as an independently checkpointed root instead of inheriting
|
|
1768
2376
|
* the parent's Pregel namespace. Parent decisions are routed explicitly
|
|
1769
2377
|
* by interrupt id, so concurrent children keep isolated resume state. */
|
|
1770
2378
|
childResult = await AsyncLocalStorageProviderSingleton.runWithConfig(
|
|
1771
2379
|
childInvokeConfig,
|
|
1772
|
-
(): Promise<
|
|
2380
|
+
(): Promise<MultiAgentGraphState> =>
|
|
1773
2381
|
workflow.invoke(childInput, childInvokeConfig)
|
|
1774
2382
|
);
|
|
1775
2383
|
} else {
|
|
@@ -1781,18 +2389,19 @@ export class SubagentExecutor {
|
|
|
1781
2389
|
if (childInterrupts != null && childInterrupts.length > 0) {
|
|
1782
2390
|
throw new GraphInterrupt(childInterrupts);
|
|
1783
2391
|
}
|
|
1784
|
-
result =
|
|
2392
|
+
result = childResult;
|
|
1785
2393
|
}
|
|
1786
2394
|
} catch (error) {
|
|
1787
2395
|
if (isGraphInterrupt(error)) {
|
|
1788
|
-
const activeChildRun =
|
|
2396
|
+
const activeChildRun = execution.activeRun;
|
|
1789
2397
|
if (activeChildRun != null) {
|
|
1790
2398
|
activeChildRun.pendingInterrupts = error.interrupts;
|
|
2399
|
+
execution.markInterrupted();
|
|
1791
2400
|
}
|
|
1792
2401
|
const resumeManifest =
|
|
1793
2402
|
activeChildRun == null || parentToolCallId == null
|
|
1794
2403
|
? undefined
|
|
1795
|
-
: await this.createResumeManifest();
|
|
2404
|
+
: await this.createResumeManifest([execution]);
|
|
1796
2405
|
await forwarding?.drain();
|
|
1797
2406
|
throw new GraphInterrupt(
|
|
1798
2407
|
addSubagentScope(
|
|
@@ -1823,15 +2432,17 @@ export class SubagentExecutor {
|
|
|
1823
2432
|
await this.emitSubagentUpdate(parentRegistry!, {
|
|
1824
2433
|
childRunId,
|
|
1825
2434
|
subagentType,
|
|
2435
|
+
subagentKind: childPlan.kind,
|
|
1826
2436
|
subagentAgentId: childAgentId,
|
|
1827
2437
|
parentToolCallId,
|
|
2438
|
+
executionContext: childExecutionContext,
|
|
1828
2439
|
phase: 'error',
|
|
1829
2440
|
label: `Subagent "${subagentType}" errored: ${errorMessage}`,
|
|
1830
2441
|
data: { message: errorMessage },
|
|
1831
2442
|
});
|
|
1832
2443
|
}
|
|
1833
2444
|
this.clearChildGraph(childGraph);
|
|
1834
|
-
|
|
2445
|
+
execution.markFailed();
|
|
1835
2446
|
/**
|
|
1836
2447
|
* A tripped stream circuit breaker is a safety abort, not a recoverable
|
|
1837
2448
|
* subagent failure: converting it into a tool result would let the
|
|
@@ -1851,11 +2462,15 @@ export class SubagentExecutor {
|
|
|
1851
2462
|
if (result == null) {
|
|
1852
2463
|
throw new Error('Subagent completed without producing graph state.');
|
|
1853
2464
|
}
|
|
1854
|
-
const filteredContent =
|
|
2465
|
+
const filteredContent =
|
|
2466
|
+
childPlan.kind === 'graph'
|
|
2467
|
+
? filterGraphSubagentResult(result, childPlan.resultAgentId)
|
|
2468
|
+
: filterSubagentResult(result.messages);
|
|
1855
2469
|
|
|
1856
2470
|
if (
|
|
1857
2471
|
!childAlreadyCompleted &&
|
|
1858
|
-
this.hookRegistry?.hasHookFor('SubagentStop',
|
|
2472
|
+
this.hookRegistry?.hasHookFor('SubagentStop', currentHookSessionId) ===
|
|
2473
|
+
true
|
|
1859
2474
|
) {
|
|
1860
2475
|
/**
|
|
1861
2476
|
* Awaited (not fire-and-forget) for deterministic test synchronization
|
|
@@ -1867,13 +2482,13 @@ export class SubagentExecutor {
|
|
|
1867
2482
|
registry: this.hookRegistry,
|
|
1868
2483
|
input: {
|
|
1869
2484
|
hook_event_name: 'SubagentStop',
|
|
1870
|
-
runId:
|
|
2485
|
+
runId: currentHookSessionId,
|
|
1871
2486
|
threadId,
|
|
1872
2487
|
agentId: childAgentId,
|
|
1873
2488
|
agentType: subagentType,
|
|
1874
2489
|
messages: result.messages,
|
|
1875
2490
|
},
|
|
1876
|
-
sessionId:
|
|
2491
|
+
sessionId: currentHookSessionId,
|
|
1877
2492
|
matchQuery: subagentType,
|
|
1878
2493
|
}).catch(() => {
|
|
1879
2494
|
/* SubagentStop is observational — swallow errors */
|
|
@@ -1885,14 +2500,16 @@ export class SubagentExecutor {
|
|
|
1885
2500
|
await this.emitSubagentUpdate(parentRegistry!, {
|
|
1886
2501
|
childRunId,
|
|
1887
2502
|
subagentType,
|
|
2503
|
+
subagentKind: childPlan.kind,
|
|
1888
2504
|
subagentAgentId: childAgentId,
|
|
1889
2505
|
parentToolCallId,
|
|
2506
|
+
executionContext: childExecutionContext,
|
|
1890
2507
|
phase: 'stop',
|
|
1891
2508
|
label: `Subagent "${subagentType}" finished`,
|
|
1892
2509
|
});
|
|
1893
2510
|
}
|
|
1894
2511
|
if (!childAlreadyCompleted) {
|
|
1895
|
-
const activeChildRun =
|
|
2512
|
+
const activeChildRun = execution.activeRun;
|
|
1896
2513
|
if (activeChildRun != null && parentToolCallId != null) {
|
|
1897
2514
|
await this.persistChildCheckpointMarker(
|
|
1898
2515
|
activeChildRun,
|
|
@@ -1900,7 +2517,6 @@ export class SubagentExecutor {
|
|
|
1900
2517
|
);
|
|
1901
2518
|
}
|
|
1902
2519
|
}
|
|
1903
|
-
this.completedChildRuns.add(childExecutionKey);
|
|
1904
2520
|
|
|
1905
2521
|
this.clearChildGraph(childGraph);
|
|
1906
2522
|
|
|
@@ -1908,7 +2524,7 @@ export class SubagentExecutor {
|
|
|
1908
2524
|
content: filteredContent,
|
|
1909
2525
|
messages: result.messages,
|
|
1910
2526
|
};
|
|
1911
|
-
|
|
2527
|
+
execution.markCompleted(completedResult);
|
|
1912
2528
|
return completedResult;
|
|
1913
2529
|
}
|
|
1914
2530
|
|
|
@@ -1922,8 +2538,11 @@ export class SubagentExecutor {
|
|
|
1922
2538
|
args: {
|
|
1923
2539
|
childRunId: string;
|
|
1924
2540
|
subagentType: string;
|
|
2541
|
+
subagentKind: 'agent' | 'graph';
|
|
1925
2542
|
subagentAgentId: string;
|
|
2543
|
+
memberAgentId?: string;
|
|
1926
2544
|
parentToolCallId?: string;
|
|
2545
|
+
executionContext: SubagentExecutionContext;
|
|
1927
2546
|
phase: SubagentUpdatePhase;
|
|
1928
2547
|
data?: unknown;
|
|
1929
2548
|
label?: string;
|
|
@@ -1934,22 +2553,23 @@ export class SubagentExecutor {
|
|
|
1934
2553
|
return;
|
|
1935
2554
|
}
|
|
1936
2555
|
const event: SubagentUpdateEvent = {
|
|
1937
|
-
runId:
|
|
2556
|
+
runId: args.executionContext.rootRunId,
|
|
2557
|
+
parentRunId: this.parentRunId,
|
|
1938
2558
|
subagentRunId: args.childRunId,
|
|
1939
2559
|
subagentType: args.subagentType,
|
|
2560
|
+
subagentKind: args.subagentKind,
|
|
1940
2561
|
subagentAgentId: args.subagentAgentId,
|
|
2562
|
+
memberAgentId: args.memberAgentId,
|
|
1941
2563
|
parentAgentId: this.parentAgentId,
|
|
1942
2564
|
parentToolCallId: args.parentToolCallId,
|
|
2565
|
+
depth: args.executionContext.depth,
|
|
2566
|
+
ancestry: args.executionContext.ancestry,
|
|
1943
2567
|
phase: args.phase,
|
|
1944
2568
|
data: args.data,
|
|
1945
2569
|
label: args.label,
|
|
1946
2570
|
timestamp: new Date().toISOString(),
|
|
1947
2571
|
};
|
|
1948
|
-
|
|
1949
|
-
await handler.handle(GraphEvents.ON_SUBAGENT_UPDATE, event);
|
|
1950
|
-
} catch {
|
|
1951
|
-
/* observational — swallow */
|
|
1952
|
-
}
|
|
2572
|
+
await dispatchObservationalSubagentUpdate(handler, event);
|
|
1953
2573
|
}
|
|
1954
2574
|
|
|
1955
2575
|
/**
|
|
@@ -1967,60 +2587,72 @@ export class SubagentExecutor {
|
|
|
1967
2587
|
private createForwarderCallback(args: {
|
|
1968
2588
|
parentRegistry: HandlerRegistry;
|
|
1969
2589
|
subagentType: string;
|
|
2590
|
+
subagentKind: 'agent' | 'graph';
|
|
1970
2591
|
subagentAgentId: string;
|
|
1971
2592
|
childRunId: string;
|
|
1972
2593
|
parentToolCallId?: string;
|
|
2594
|
+
executionContext: SubagentExecutionContext;
|
|
1973
2595
|
}): ForwarderCallback {
|
|
1974
2596
|
const {
|
|
1975
2597
|
parentRegistry,
|
|
1976
2598
|
subagentType,
|
|
2599
|
+
subagentKind,
|
|
1977
2600
|
subagentAgentId,
|
|
1978
2601
|
childRunId,
|
|
1979
2602
|
parentToolCallId,
|
|
2603
|
+
executionContext,
|
|
1980
2604
|
} = args;
|
|
1981
|
-
const
|
|
2605
|
+
const immediateParentRunId = this.parentRunId;
|
|
1982
2606
|
const parentAgentId = this.parentAgentId;
|
|
1983
2607
|
|
|
1984
2608
|
const wrap = async (
|
|
1985
2609
|
eventName: string,
|
|
1986
2610
|
phase: SubagentUpdatePhase,
|
|
1987
|
-
data: unknown
|
|
1988
|
-
|
|
2611
|
+
data: unknown,
|
|
2612
|
+
memberAgentId?: string
|
|
2613
|
+
): Promise<boolean> => {
|
|
1989
2614
|
const handler = parentRegistry.getHandler(GraphEvents.ON_SUBAGENT_UPDATE);
|
|
1990
2615
|
if (!handler) {
|
|
1991
|
-
return;
|
|
1992
|
-
}
|
|
1993
|
-
try {
|
|
1994
|
-
const event: SubagentUpdateEvent = {
|
|
1995
|
-
runId: parentRunId,
|
|
1996
|
-
subagentRunId: childRunId,
|
|
1997
|
-
subagentType,
|
|
1998
|
-
subagentAgentId,
|
|
1999
|
-
parentAgentId,
|
|
2000
|
-
parentToolCallId,
|
|
2001
|
-
phase,
|
|
2002
|
-
data: sanitizeForwardedSubagentUpdateData(eventName, data),
|
|
2003
|
-
label: summarizeEvent(eventName, data),
|
|
2004
|
-
timestamp: new Date().toISOString(),
|
|
2005
|
-
};
|
|
2006
|
-
await handler.handle(GraphEvents.ON_SUBAGENT_UPDATE, event);
|
|
2007
|
-
} catch {
|
|
2008
|
-
/* observational — swallow */
|
|
2616
|
+
return true;
|
|
2009
2617
|
}
|
|
2618
|
+
const event: SubagentUpdateEvent = {
|
|
2619
|
+
runId: executionContext.rootRunId,
|
|
2620
|
+
parentRunId: immediateParentRunId,
|
|
2621
|
+
subagentRunId: childRunId,
|
|
2622
|
+
subagentType,
|
|
2623
|
+
subagentKind,
|
|
2624
|
+
subagentAgentId,
|
|
2625
|
+
memberAgentId,
|
|
2626
|
+
parentAgentId,
|
|
2627
|
+
parentToolCallId,
|
|
2628
|
+
depth: executionContext.depth,
|
|
2629
|
+
ancestry: executionContext.ancestry,
|
|
2630
|
+
phase,
|
|
2631
|
+
data: sanitizeForwardedSubagentUpdateData(eventName, data),
|
|
2632
|
+
label: summarizeEvent(eventName, data),
|
|
2633
|
+
timestamp: new Date().toISOString(),
|
|
2634
|
+
};
|
|
2635
|
+
return dispatchObservationalSubagentUpdate(handler, event);
|
|
2010
2636
|
};
|
|
2011
2637
|
|
|
2012
2638
|
const queuedUpdates: QueuedSubagentUpdate[] = [];
|
|
2013
2639
|
let drainPromise: Promise<void> | undefined;
|
|
2640
|
+
let queueOpen = true;
|
|
2014
2641
|
|
|
2015
2642
|
const enqueue = (update: QueuedSubagentUpdate): void => {
|
|
2016
|
-
if (
|
|
2643
|
+
if (!queueOpen) {
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
if (queuedUpdates.length >= MAX_QUEUED_SUBAGENT_UPDATES) {
|
|
2017
2647
|
const dropIndex = queuedUpdates.findIndex((queued) =>
|
|
2018
|
-
|
|
2648
|
+
isLowPrioritySubagentUpdatePhase(queued.phase)
|
|
2019
2649
|
);
|
|
2020
2650
|
if (dropIndex >= 0) {
|
|
2021
2651
|
queuedUpdates.splice(dropIndex, 1);
|
|
2022
|
-
} else if (
|
|
2652
|
+
} else if (isLowPrioritySubagentUpdatePhase(update.phase)) {
|
|
2023
2653
|
return;
|
|
2654
|
+
} else {
|
|
2655
|
+
queuedUpdates.shift();
|
|
2024
2656
|
}
|
|
2025
2657
|
}
|
|
2026
2658
|
queuedUpdates.push(update);
|
|
@@ -2037,7 +2669,16 @@ export class SubagentExecutor {
|
|
|
2037
2669
|
if (update == null) {
|
|
2038
2670
|
continue;
|
|
2039
2671
|
}
|
|
2040
|
-
await wrap(
|
|
2672
|
+
const handlerSettled = await wrap(
|
|
2673
|
+
update.eventName,
|
|
2674
|
+
update.phase,
|
|
2675
|
+
update.data,
|
|
2676
|
+
update.memberAgentId
|
|
2677
|
+
);
|
|
2678
|
+
if (!handlerSettled) {
|
|
2679
|
+
queueOpen = false;
|
|
2680
|
+
queuedUpdates.length = 0;
|
|
2681
|
+
}
|
|
2041
2682
|
}
|
|
2042
2683
|
})();
|
|
2043
2684
|
try {
|
|
@@ -2053,17 +2694,23 @@ export class SubagentExecutor {
|
|
|
2053
2694
|
const scheduleWrap = (
|
|
2054
2695
|
eventName: string,
|
|
2055
2696
|
phase: SubagentUpdatePhase,
|
|
2056
|
-
data: unknown
|
|
2697
|
+
data: unknown,
|
|
2698
|
+
memberAgentId?: string
|
|
2057
2699
|
): void => {
|
|
2058
|
-
enqueue({ eventName, phase, data });
|
|
2700
|
+
enqueue({ eventName, phase, data, memberAgentId });
|
|
2059
2701
|
void drain();
|
|
2060
2702
|
};
|
|
2061
2703
|
|
|
2062
2704
|
const handler = BaseCallbackHandler.fromMethods({
|
|
2063
2705
|
[Callback.CUSTOM_EVENT]: async (
|
|
2064
2706
|
eventName: string,
|
|
2065
|
-
data: unknown
|
|
2707
|
+
data: unknown,
|
|
2708
|
+
_runId?: string,
|
|
2709
|
+
_tags?: string[],
|
|
2710
|
+
metadata?: Record<string, unknown>
|
|
2066
2711
|
): Promise<void> => {
|
|
2712
|
+
const memberAgentId =
|
|
2713
|
+
asNonEmptyString(metadata?.agentId) ?? getEventAgentId(data);
|
|
2067
2714
|
if (eventName === GraphEvents.ON_TOOL_EXECUTE) {
|
|
2068
2715
|
const toolHandler = parentRegistry.getHandler(
|
|
2069
2716
|
GraphEvents.ON_TOOL_EXECUTE
|
|
@@ -2078,28 +2725,28 @@ export class SubagentExecutor {
|
|
|
2078
2725
|
* We also surface a short notice in the subagent-update stream so
|
|
2079
2726
|
* the UI can show "calling <tool>" for each tool the child spawns.
|
|
2080
2727
|
*/
|
|
2081
|
-
scheduleWrap(eventName, 'run_step', data);
|
|
2728
|
+
scheduleWrap(eventName, 'run_step', data, memberAgentId);
|
|
2082
2729
|
return;
|
|
2083
2730
|
}
|
|
2084
2731
|
|
|
2085
2732
|
if (eventName === GraphEvents.ON_RUN_STEP) {
|
|
2086
|
-
scheduleWrap(eventName, 'run_step', data);
|
|
2733
|
+
scheduleWrap(eventName, 'run_step', data, memberAgentId);
|
|
2087
2734
|
return;
|
|
2088
2735
|
}
|
|
2089
2736
|
if (eventName === GraphEvents.ON_RUN_STEP_DELTA) {
|
|
2090
|
-
scheduleWrap(eventName, 'run_step_delta', data);
|
|
2737
|
+
scheduleWrap(eventName, 'run_step_delta', data, memberAgentId);
|
|
2091
2738
|
return;
|
|
2092
2739
|
}
|
|
2093
2740
|
if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
2094
|
-
scheduleWrap(eventName, 'run_step_completed', data);
|
|
2741
|
+
scheduleWrap(eventName, 'run_step_completed', data, memberAgentId);
|
|
2095
2742
|
return;
|
|
2096
2743
|
}
|
|
2097
2744
|
if (eventName === GraphEvents.ON_MESSAGE_DELTA) {
|
|
2098
|
-
scheduleWrap(eventName, 'message_delta', data);
|
|
2745
|
+
scheduleWrap(eventName, 'message_delta', data, memberAgentId);
|
|
2099
2746
|
return;
|
|
2100
2747
|
}
|
|
2101
2748
|
if (eventName === GraphEvents.ON_REASONING_DELTA) {
|
|
2102
|
-
scheduleWrap(eventName, 'reasoning_delta', data);
|
|
2749
|
+
scheduleWrap(eventName, 'reasoning_delta', data, memberAgentId);
|
|
2103
2750
|
return;
|
|
2104
2751
|
}
|
|
2105
2752
|
},
|
|
@@ -2134,29 +2781,25 @@ export class SubagentExecutor {
|
|
|
2134
2781
|
function createUsageCaptureHandler(args: {
|
|
2135
2782
|
sink: SubagentUsageSink;
|
|
2136
2783
|
subagentType: string;
|
|
2784
|
+
subagentKind: 'agent' | 'graph';
|
|
2137
2785
|
subagentRunId: string;
|
|
2138
2786
|
subagentAgentId: string;
|
|
2139
2787
|
parentRunId: string;
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
* `INVOKED_PROVIDER` metadata (hosts key pricing/cache semantics off it).
|
|
2143
|
-
*/
|
|
2144
|
-
provider?: string;
|
|
2145
|
-
/**
|
|
2146
|
-
* Child config's model, used when a call carries neither `ls_model_name`
|
|
2147
|
-
* nor `INVOKED_MODEL` metadata.
|
|
2148
|
-
*/
|
|
2149
|
-
fallbackModel?: string;
|
|
2788
|
+
executionContext: SubagentExecutionContext;
|
|
2789
|
+
memberInputs: ReadonlyMap<string, AgentInputs>;
|
|
2150
2790
|
}): BaseCallbackHandler {
|
|
2151
2791
|
const {
|
|
2152
2792
|
sink,
|
|
2153
2793
|
subagentType,
|
|
2794
|
+
subagentKind,
|
|
2154
2795
|
subagentRunId,
|
|
2155
2796
|
subagentAgentId,
|
|
2156
2797
|
parentRunId,
|
|
2157
|
-
|
|
2158
|
-
|
|
2798
|
+
executionContext,
|
|
2799
|
+
memberInputs,
|
|
2159
2800
|
} = args;
|
|
2801
|
+
const defaultMember =
|
|
2802
|
+
memberInputs.size === 1 ? memberInputs.entries().next().value : undefined;
|
|
2160
2803
|
/**
|
|
2161
2804
|
* Per-call attribution keyed by LangChain callback runId. `model` joins
|
|
2162
2805
|
* `ls_model_name` (provider-reported) with `INVOKED_MODEL` (stamped by
|
|
@@ -2168,7 +2811,7 @@ function createUsageCaptureHandler(args: {
|
|
|
2168
2811
|
*/
|
|
2169
2812
|
const callInfoByCallId = new Map<
|
|
2170
2813
|
string,
|
|
2171
|
-
{ model?: string; provider?: string }
|
|
2814
|
+
{ model?: string; provider?: string; memberAgentId?: string }
|
|
2172
2815
|
>();
|
|
2173
2816
|
const handler = BaseCallbackHandler.fromMethods({
|
|
2174
2817
|
handleChatModelStart: (
|
|
@@ -2186,18 +2829,30 @@ function createUsageCaptureHandler(args: {
|
|
|
2186
2829
|
const callProvider = asNonEmptyString(
|
|
2187
2830
|
metadata?.[Constants.INVOKED_PROVIDER]
|
|
2188
2831
|
);
|
|
2189
|
-
|
|
2832
|
+
const memberAgentId = asNonEmptyString(metadata?.agentId);
|
|
2833
|
+
if (callModel != null || callProvider != null || memberAgentId != null) {
|
|
2190
2834
|
callInfoByCallId.set(runId, {
|
|
2191
2835
|
model: callModel,
|
|
2192
2836
|
provider: callProvider,
|
|
2837
|
+
memberAgentId,
|
|
2193
2838
|
});
|
|
2194
2839
|
}
|
|
2195
2840
|
},
|
|
2196
2841
|
handleLLMEnd: async (output: LLMResult, runId: string): Promise<void> => {
|
|
2197
2842
|
const callInfo = callInfoByCallId.get(runId);
|
|
2198
2843
|
callInfoByCallId.delete(runId);
|
|
2199
|
-
const
|
|
2200
|
-
const
|
|
2844
|
+
const observedMemberAgentId = callInfo?.memberAgentId;
|
|
2845
|
+
const memberAgentId =
|
|
2846
|
+
observedMemberAgentId != null && memberInputs.has(observedMemberAgentId)
|
|
2847
|
+
? observedMemberAgentId
|
|
2848
|
+
: defaultMember?.[0];
|
|
2849
|
+
const memberInput =
|
|
2850
|
+
(memberAgentId == null ? undefined : memberInputs.get(memberAgentId)) ??
|
|
2851
|
+
defaultMember?.[1];
|
|
2852
|
+
const model =
|
|
2853
|
+
callInfo?.model ??
|
|
2854
|
+
(memberInput == null ? undefined : extractConfiguredModel(memberInput));
|
|
2855
|
+
const callProvider = callInfo?.provider ?? memberInput?.provider;
|
|
2201
2856
|
for (const generationGroup of output.generations) {
|
|
2202
2857
|
/**
|
|
2203
2858
|
* At most ONE event per generation group: each group is one
|
|
@@ -2227,9 +2882,14 @@ function createUsageCaptureHandler(args: {
|
|
|
2227
2882
|
model,
|
|
2228
2883
|
provider: callProvider,
|
|
2229
2884
|
subagentType,
|
|
2885
|
+
subagentKind,
|
|
2230
2886
|
subagentRunId,
|
|
2231
2887
|
subagentAgentId,
|
|
2232
|
-
|
|
2888
|
+
memberAgentId,
|
|
2889
|
+
parentRunId,
|
|
2890
|
+
depth: executionContext.depth,
|
|
2891
|
+
ancestry: executionContext.ancestry,
|
|
2892
|
+
runId: executionContext.rootRunId,
|
|
2233
2893
|
});
|
|
2234
2894
|
} catch {
|
|
2235
2895
|
/* observational — a throwing/rejecting host sink must not break the child run */
|
|
@@ -2255,6 +2915,19 @@ function asNonEmptyString(value: unknown): string | undefined {
|
|
|
2255
2915
|
return typeof value === 'string' && value !== '' ? value : undefined;
|
|
2256
2916
|
}
|
|
2257
2917
|
|
|
2918
|
+
function getEventAgentId(data: unknown): string | undefined {
|
|
2919
|
+
if (data == null || typeof data !== 'object') {
|
|
2920
|
+
return undefined;
|
|
2921
|
+
}
|
|
2922
|
+
const event = data as {
|
|
2923
|
+
agentId?: unknown;
|
|
2924
|
+
result?: { agentId?: unknown };
|
|
2925
|
+
};
|
|
2926
|
+
return (
|
|
2927
|
+
asNonEmptyString(event.agentId) ?? asNonEmptyString(event.result?.agentId)
|
|
2928
|
+
);
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2258
2931
|
/**
|
|
2259
2932
|
* Best-effort read of the configured model from a subagent's client
|
|
2260
2933
|
* options. Providers disagree on the key (`model` vs `modelName`), and the
|
|
@@ -2276,6 +2949,63 @@ function extractConfiguredModel(agentInputs: AgentInputs): string | undefined {
|
|
|
2276
2949
|
return undefined;
|
|
2277
2950
|
}
|
|
2278
2951
|
|
|
2952
|
+
function sanitizeResolverConfigurable(
|
|
2953
|
+
parentConfigurable: Record<string, unknown>
|
|
2954
|
+
): SubagentResolveConfigurable | undefined {
|
|
2955
|
+
const configurable = sanitizeChildConfigurable(parentConfigurable);
|
|
2956
|
+
const requestBody = createResolveRequestContext(configurable.requestBody);
|
|
2957
|
+
const user = createResolveUserContext(configurable.user);
|
|
2958
|
+
const userId = asNonEmptyString(configurable.user_id) ?? user?.id;
|
|
2959
|
+
if (requestBody == null && user == null && userId == null) {
|
|
2960
|
+
return undefined;
|
|
2961
|
+
}
|
|
2962
|
+
return {
|
|
2963
|
+
...(requestBody == null ? {} : { requestBody }),
|
|
2964
|
+
...(user == null ? {} : { user }),
|
|
2965
|
+
...(userId == null ? {} : { user_id: userId }),
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
function createResolveRequestContext(
|
|
2970
|
+
value: unknown
|
|
2971
|
+
): SubagentResolveRequestContext | undefined {
|
|
2972
|
+
if (!isObjectLike(value)) {
|
|
2973
|
+
return undefined;
|
|
2974
|
+
}
|
|
2975
|
+
const source = value as SubagentResolveRequestContext;
|
|
2976
|
+
const conversationId = asNonEmptyString(source.conversationId);
|
|
2977
|
+
const messageId = asNonEmptyString(source.messageId);
|
|
2978
|
+
const parentMessageId = asNonEmptyString(source.parentMessageId);
|
|
2979
|
+
if (conversationId == null && messageId == null && parentMessageId == null) {
|
|
2980
|
+
return undefined;
|
|
2981
|
+
}
|
|
2982
|
+
return {
|
|
2983
|
+
...(conversationId == null ? {} : { conversationId }),
|
|
2984
|
+
...(messageId == null ? {} : { messageId }),
|
|
2985
|
+
...(parentMessageId == null ? {} : { parentMessageId }),
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
function createResolveUserContext(
|
|
2990
|
+
value: unknown
|
|
2991
|
+
): SubagentResolveUserContext | undefined {
|
|
2992
|
+
if (!isObjectLike(value)) {
|
|
2993
|
+
return undefined;
|
|
2994
|
+
}
|
|
2995
|
+
const source = value as SubagentResolveUserContext;
|
|
2996
|
+
const id = asNonEmptyString(source.id);
|
|
2997
|
+
const role = asNonEmptyString(source.role);
|
|
2998
|
+
const tenantId = asNonEmptyString(source.tenantId);
|
|
2999
|
+
if (id == null && role == null && tenantId == null) {
|
|
3000
|
+
return undefined;
|
|
3001
|
+
}
|
|
3002
|
+
return {
|
|
3003
|
+
...(id == null ? {} : { id }),
|
|
3004
|
+
...(role == null ? {} : { role }),
|
|
3005
|
+
...(tenantId == null ? {} : { tenantId }),
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
|
|
2279
3009
|
function sanitizeChildConfigurable(
|
|
2280
3010
|
parentConfigurable: Record<string, unknown> | undefined
|
|
2281
3011
|
): Record<string, unknown> {
|
|
@@ -2296,6 +3026,7 @@ function isLangGraphRuntimeConfigKey(key: string): boolean {
|
|
|
2296
3026
|
key === SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY ||
|
|
2297
3027
|
key === SUBAGENT_RESUME_MANIFEST_CONFIG_KEY ||
|
|
2298
3028
|
key === SUBAGENT_PARENT_BATCH_CONFIG_KEY ||
|
|
3029
|
+
key === TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY ||
|
|
2299
3030
|
/** The parent batch's breaker scope must not leak into the child
|
|
2300
3031
|
* workflow's configurable — children own separate controllers. */
|
|
2301
3032
|
key === RUN_BREAKER_SCOPE_CONFIG_KEY
|
|
@@ -2327,7 +3058,7 @@ export function sanitizeForwardedSubagentUpdateData(
|
|
|
2327
3058
|
return undefined;
|
|
2328
3059
|
}
|
|
2329
3060
|
|
|
2330
|
-
function
|
|
3061
|
+
function isLowPrioritySubagentUpdatePhase(phase: SubagentUpdatePhase): boolean {
|
|
2331
3062
|
return (
|
|
2332
3063
|
phase === 'message_delta' ||
|
|
2333
3064
|
phase === 'reasoning_delta' ||
|
|
@@ -2643,6 +3374,24 @@ export function summarizeEvent(eventName: string, data: unknown): string {
|
|
|
2643
3374
|
return eventName;
|
|
2644
3375
|
}
|
|
2645
3376
|
|
|
3377
|
+
/**
|
|
3378
|
+
* Reads only the result member captured by MultiAgentGraph. Worker output is
|
|
3379
|
+
* never used as a fallback when the designated result member is textless.
|
|
3380
|
+
*/
|
|
3381
|
+
export function filterGraphSubagentResult(
|
|
3382
|
+
state: MultiAgentGraphState,
|
|
3383
|
+
resultAgentId: string
|
|
3384
|
+
): string {
|
|
3385
|
+
const result = state.subagentResult;
|
|
3386
|
+
if (result == null || result.agentId !== resultAgentId) {
|
|
3387
|
+
return `Error: Graph subagent result agent "${resultAgentId}" did not produce a result.`;
|
|
3388
|
+
}
|
|
3389
|
+
if (result.message == null) {
|
|
3390
|
+
return 'Task completed';
|
|
3391
|
+
}
|
|
3392
|
+
return filterSubagentResult([result.message]);
|
|
3393
|
+
}
|
|
3394
|
+
|
|
2646
3395
|
/**
|
|
2647
3396
|
* Walk messages from last to first, returning the text content of the most
|
|
2648
3397
|
* recent AIMessage that has any. Non-text blocks (tool_use, thinking,
|
|
@@ -2741,113 +3490,46 @@ export function filterSubagentResult(messages: BaseMessage[]): string {
|
|
|
2741
3490
|
return 'Task completed';
|
|
2742
3491
|
}
|
|
2743
3492
|
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
*/
|
|
2749
|
-
export function resolveSubagentConfigs(
|
|
2750
|
-
configs: SubagentConfig[],
|
|
2751
|
-
parentContext: AgentContext
|
|
2752
|
-
): ResolvedSubagentConfig[] {
|
|
2753
|
-
const resolved = configs
|
|
2754
|
-
.map((config) => {
|
|
2755
|
-
if (config.agentInputs != null) {
|
|
2756
|
-
return config as ResolvedSubagentConfig;
|
|
2757
|
-
}
|
|
2758
|
-
if (config.self !== true || parentContext._sourceInputs == null) {
|
|
2759
|
-
return null;
|
|
2760
|
-
}
|
|
2761
|
-
return {
|
|
2762
|
-
...config,
|
|
2763
|
-
agentInputs: { ...parentContext._sourceInputs },
|
|
2764
|
-
} as ResolvedSubagentConfig;
|
|
2765
|
-
})
|
|
2766
|
-
.filter((c): c is ResolvedSubagentConfig => c != null);
|
|
2767
|
-
|
|
2768
|
-
const seenTypes = new Set<string>();
|
|
2769
|
-
for (const config of resolved) {
|
|
2770
|
-
if (seenTypes.has(config.type)) {
|
|
2771
|
-
throw new Error(
|
|
2772
|
-
`Duplicate subagent type "${config.type}". Each SubagentConfig must have a unique "type" field.`
|
|
2773
|
-
);
|
|
2774
|
-
}
|
|
2775
|
-
seenTypes.add(config.type);
|
|
3493
|
+
function truncateErrorMessage(error: unknown): string {
|
|
3494
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3495
|
+
if (message.length <= ERROR_MESSAGE_MAX_CHARS) {
|
|
3496
|
+
return message;
|
|
2776
3497
|
}
|
|
2777
|
-
|
|
2778
|
-
return resolved;
|
|
3498
|
+
return `${message.slice(0, ERROR_MESSAGE_MAX_CHARS)}...`;
|
|
2779
3499
|
}
|
|
2780
3500
|
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
*
|
|
2788
|
-
* When `keepToolDefinitions` is `true`, the child retains the parent's
|
|
2789
|
-
* `toolDefinitions` so event-driven tools remain usable. This is only safe
|
|
2790
|
-
* when the caller has wired a forwarder for `ON_TOOL_EXECUTE` to a
|
|
2791
|
-
* registered handler — otherwise the child will hang on tool dispatch.
|
|
2792
|
-
*
|
|
2793
|
-
* @remarks Advanced utility: exported primarily for testing and by
|
|
2794
|
-
* {@link SubagentExecutor}. Host applications configuring subagents should
|
|
2795
|
-
* not need to call this directly — it is invoked internally when a subagent
|
|
2796
|
-
* tool is dispatched. The depth-countdown contract (parent's `maxDepth` in,
|
|
2797
|
-
* child's decremented `maxSubagentDepth` on the returned inputs) is the
|
|
2798
|
-
* mechanism that bounds nesting across graph boundaries; callers must
|
|
2799
|
-
* respect it.
|
|
2800
|
-
*/
|
|
2801
|
-
export function buildChildInputs(
|
|
2802
|
-
config: ResolvedSubagentConfig,
|
|
2803
|
-
childAgentId: string,
|
|
2804
|
-
parentMaxDepth: number,
|
|
2805
|
-
keepToolDefinitions: boolean = false
|
|
2806
|
-
): AgentInputs {
|
|
2807
|
-
const { agentInputs } = config;
|
|
2808
|
-
const childInputs: AgentInputs = {
|
|
2809
|
-
...agentInputs,
|
|
2810
|
-
agentId: childAgentId,
|
|
2811
|
-
toolDefinitions: keepToolDefinitions
|
|
2812
|
-
? agentInputs.toolDefinitions
|
|
2813
|
-
: undefined,
|
|
2814
|
-
/**
|
|
2815
|
-
* Subagents run in an isolated context by contract. Parent-run-scoped
|
|
2816
|
-
* fields that would otherwise survive the shallow-spread clone — the
|
|
2817
|
-
* cross-run conversation summary and the prior-turn tool-discovery
|
|
2818
|
-
* set — are cleared here so the child starts fresh. Host applications
|
|
2819
|
-
* that want a subagent to see parent context must thread it in
|
|
2820
|
-
* explicitly (e.g. via the `description` argument to the subagent
|
|
2821
|
-
* tool), not via inherited state.
|
|
2822
|
-
*/
|
|
2823
|
-
initialSummary: undefined,
|
|
2824
|
-
discoveredTools: undefined,
|
|
2825
|
-
/**
|
|
2826
|
-
* Host-supplied direct tools are scrubbed from INHERITED configs only.
|
|
2827
|
-
* A self-spawn config's `agentInputs` is a shallow spread of the parent's
|
|
2828
|
-
* `_sourceInputs`, so without this a parent-scoped graph tool (e.g. an
|
|
2829
|
-
* interrupt-raising ask_user_question) would silently become available to
|
|
2830
|
-
* the child. An EXPLICIT child config that lists its own `graphTools` is a
|
|
2831
|
-
* deliberate host choice and keeps them (Codex #289 P2); with HITL enabled,
|
|
2832
|
-
* those tools use the shared checkpointer and can pause and resume safely.
|
|
2833
|
-
*/
|
|
2834
|
-
graphTools: config.self === true ? undefined : agentInputs.graphTools,
|
|
2835
|
-
};
|
|
2836
|
-
|
|
2837
|
-
if (config.allowNested === true) {
|
|
2838
|
-
childInputs.maxSubagentDepth = Math.max(0, parentMaxDepth - 1);
|
|
2839
|
-
} else {
|
|
2840
|
-
childInputs.subagentConfigs = undefined;
|
|
2841
|
-
childInputs.maxSubagentDepth = undefined;
|
|
3501
|
+
function resolveAgentInputsWithSignal(
|
|
3502
|
+
resolve: () => Promise<AgentInputs>,
|
|
3503
|
+
signal: AbortSignal
|
|
3504
|
+
): Promise<AgentInputs> {
|
|
3505
|
+
if (signal.aborted) {
|
|
3506
|
+
return Promise.reject(getAbortReason(signal));
|
|
2842
3507
|
}
|
|
2843
3508
|
|
|
2844
|
-
return
|
|
3509
|
+
return new Promise<AgentInputs>((resolvePromise, rejectPromise) => {
|
|
3510
|
+
const cleanup = (): void => signal.removeEventListener('abort', onAbort);
|
|
3511
|
+
const onAbort = (): void => {
|
|
3512
|
+
cleanup();
|
|
3513
|
+
rejectPromise(getAbortReason(signal));
|
|
3514
|
+
};
|
|
3515
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
3516
|
+
Promise.resolve()
|
|
3517
|
+
.then(resolve)
|
|
3518
|
+
.then(
|
|
3519
|
+
(agentInputs) => {
|
|
3520
|
+
cleanup();
|
|
3521
|
+
resolvePromise(agentInputs);
|
|
3522
|
+
},
|
|
3523
|
+
(error: unknown) => {
|
|
3524
|
+
cleanup();
|
|
3525
|
+
rejectPromise(error);
|
|
3526
|
+
}
|
|
3527
|
+
);
|
|
3528
|
+
});
|
|
2845
3529
|
}
|
|
2846
3530
|
|
|
2847
|
-
function
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
}
|
|
2852
|
-
return `${message.slice(0, ERROR_MESSAGE_MAX_CHARS)}...`;
|
|
3531
|
+
function getAbortReason(signal: AbortSignal): Error {
|
|
3532
|
+
return signal.reason instanceof Error
|
|
3533
|
+
? signal.reason
|
|
3534
|
+
: new Error('Subagent resolution aborted.');
|
|
2853
3535
|
}
|