@librechat/agents 3.6.2 → 3.6.4
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/graphs/Graph.cjs +35 -20
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +7 -1
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +1 -1
- package/dist/cjs/llm/openai/index.cjs +36 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +4 -1
- package/dist/cjs/messages/core.cjs +3 -0
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/run.cjs +11 -5
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs +8 -3
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +1 -1
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +399 -0
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +168 -60
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/index.cjs +1 -0
- package/dist/esm/graphs/Graph.mjs +35 -20
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +7 -1
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +1 -1
- package/dist/esm/llm/openai/index.mjs +37 -1
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -3
- package/dist/esm/messages/core.mjs +3 -1
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/run.mjs +11 -5
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs +8 -3
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +1 -1
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +399 -0
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +168 -60
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/index.mjs +1 -0
- package/dist/types/graphs/Graph.d.ts +6 -3
- package/dist/types/hooks/HookRegistry.d.ts +8 -0
- package/dist/types/messages/core.d.ts +2 -0
- package/dist/types/run.d.ts +1 -0
- package/dist/types/tools/SubagentTool.d.ts +3 -1
- package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +45 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +26 -3
- package/dist/types/tools/subagent/index.d.ts +2 -0
- package/dist/types/types/graph.d.ts +11 -4
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/run.d.ts +6 -0
- package/dist/types/types/subagentTasks.d.ts +140 -0
- package/package.json +4 -4
- package/src/graphs/Graph.ts +84 -36
- package/src/hooks/HookRegistry.ts +18 -0
- package/src/llm/openai/index.ts +96 -0
- package/src/messages/core.ts +5 -0
- package/src/run.ts +20 -5
- package/src/tools/SubagentTool.ts +20 -2
- package/src/tools/subagent/InMemorySubagentTaskStore.ts +624 -0
- package/src/tools/subagent/SubagentExecutor.ts +341 -74
- package/src/tools/subagent/index.ts +2 -0
- package/src/types/graph.ts +11 -4
- package/src/types/index.ts +1 -0
- package/src/types/run.ts +6 -0
- package/src/types/subagentTasks.ts +129 -0
|
@@ -52,6 +52,8 @@ import type {
|
|
|
52
52
|
ResolvedSubagentConfig,
|
|
53
53
|
ResolvedSubagentConfigEntry,
|
|
54
54
|
SubagentExecutionContext,
|
|
55
|
+
SubagentTaskConfig,
|
|
56
|
+
SubagentTaskRuntime,
|
|
55
57
|
SubagentResolveConfigurable,
|
|
56
58
|
SubagentResolveRequestContext,
|
|
57
59
|
SubagentResolveUserContext,
|
|
@@ -81,12 +83,12 @@ import type {
|
|
|
81
83
|
} from './SubagentReplay';
|
|
82
84
|
import type {
|
|
83
85
|
AggregatedHookResult,
|
|
84
|
-
|
|
86
|
+
PostToolBatchHookOutput,
|
|
87
|
+
PreemptBoundaryHookOutput,
|
|
85
88
|
ToolApprovalReplaySnapshot,
|
|
86
89
|
} from '@/hooks';
|
|
87
90
|
import type { GraphFactory } from '@/graphs/graphFactory';
|
|
88
91
|
import type { StandardGraph } from '@/graphs/Graph';
|
|
89
|
-
import type { HandlerRegistry } from '@/events';
|
|
90
92
|
import {
|
|
91
93
|
getSubagentApprovalExecutionScope,
|
|
92
94
|
SubagentDefinitionBindingError,
|
|
@@ -101,6 +103,11 @@ import {
|
|
|
101
103
|
SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY,
|
|
102
104
|
SUBAGENT_RESUME_MANIFEST_CONFIG_KEY,
|
|
103
105
|
} from './SubagentReplay';
|
|
106
|
+
import {
|
|
107
|
+
executeHooks,
|
|
108
|
+
HookRegistry,
|
|
109
|
+
TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY,
|
|
110
|
+
} from '@/hooks';
|
|
104
111
|
import {
|
|
105
112
|
StreamLimitExceededError,
|
|
106
113
|
RUN_BREAKER_SCOPE_CONFIG_KEY,
|
|
@@ -116,10 +123,6 @@ import {
|
|
|
116
123
|
Callback,
|
|
117
124
|
StepTypes,
|
|
118
125
|
} from '@/common';
|
|
119
|
-
import {
|
|
120
|
-
executeHooks,
|
|
121
|
-
TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY,
|
|
122
|
-
} from '@/hooks';
|
|
123
126
|
import {
|
|
124
127
|
createChildGraphPlan,
|
|
125
128
|
isGraphSubagentConfig,
|
|
@@ -127,7 +130,9 @@ import {
|
|
|
127
130
|
import { stripRunStepResumeState } from '@/tools/runStepResume';
|
|
128
131
|
import { seedAgentInitialSessions } from '@/utils/toolSessions';
|
|
129
132
|
import { stableStringify } from '@/tools/eagerEventExecution';
|
|
133
|
+
import { convertInjectedMessages } from '@/messages/injected';
|
|
130
134
|
import { composeAbortSignals } from '@/utils/misc';
|
|
135
|
+
import { HandlerRegistry } from '@/events';
|
|
131
136
|
|
|
132
137
|
export {
|
|
133
138
|
buildChildInputs,
|
|
@@ -140,6 +145,7 @@ export {
|
|
|
140
145
|
|
|
141
146
|
const ERROR_MESSAGE_MAX_CHARS = 200;
|
|
142
147
|
const MAX_QUEUED_SUBAGENT_UPDATES = 64;
|
|
148
|
+
const MAX_BACKGROUND_SUBAGENT_SEALS = 32;
|
|
143
149
|
const SUBAGENT_UPDATE_HANDLER_TIMEOUT_MS = 5_000;
|
|
144
150
|
const TEXT_DELTA_CONTENT_TYPE = `${ContentTypes.TEXT}_delta`;
|
|
145
151
|
const SUBAGENT_RESOLUTION_ERROR_MESSAGE =
|
|
@@ -615,6 +621,22 @@ function getSettlementFingerprint(output: PersistedToolOutput): string {
|
|
|
615
621
|
return createHash('sha256').update(stableStringify(output)).digest('hex');
|
|
616
622
|
}
|
|
617
623
|
|
|
624
|
+
function getBackgroundTaskFingerprint(
|
|
625
|
+
description: string,
|
|
626
|
+
subagentType: string
|
|
627
|
+
): string {
|
|
628
|
+
return createHash('sha256')
|
|
629
|
+
.update(stableStringify({ description, subagentType }))
|
|
630
|
+
.digest('hex');
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function getBackgroundTaskHookSessionId(
|
|
634
|
+
sourceHookSessionId: string,
|
|
635
|
+
taskId: string
|
|
636
|
+
): string {
|
|
637
|
+
return `${sourceHookSessionId}:subagent-task:${taskId}`;
|
|
638
|
+
}
|
|
639
|
+
|
|
618
640
|
function deserializeToolOutput(
|
|
619
641
|
output: PersistedToolOutput
|
|
620
642
|
): SettledSubagentToolOutput {
|
|
@@ -786,13 +808,26 @@ export type SubagentExecuteParams = {
|
|
|
786
808
|
* rather than sharing parent's host context.
|
|
787
809
|
*/
|
|
788
810
|
parentConfigurable?: Record<string, unknown>;
|
|
811
|
+
/** Dedicated hook session used by a detached task. @internal */
|
|
812
|
+
hookSessionId?: string;
|
|
813
|
+
/** Process-local task controls consumed by the child graph. @internal */
|
|
814
|
+
taskRuntime?: SubagentTaskRuntime;
|
|
789
815
|
};
|
|
790
816
|
|
|
791
817
|
export type SubagentExecuteResult = {
|
|
792
818
|
content: string;
|
|
793
819
|
messages: BaseMessage[];
|
|
820
|
+
/** Tagged internal failure; foreground callers retain the legacy content. */
|
|
821
|
+
error?: string;
|
|
794
822
|
};
|
|
795
823
|
|
|
824
|
+
function createSubagentFailure(
|
|
825
|
+
content: string,
|
|
826
|
+
error = content
|
|
827
|
+
): SubagentExecuteResult {
|
|
828
|
+
return { content, messages: [], error };
|
|
829
|
+
}
|
|
830
|
+
|
|
796
831
|
/**
|
|
797
832
|
* Factory that constructs a child graph for subagent execution. Injected
|
|
798
833
|
* rather than imported so that `SubagentExecutor` does not have a runtime
|
|
@@ -848,6 +883,13 @@ export type SubagentExecutorOptions = {
|
|
|
848
883
|
/** Preferred polymorphic child constructor. The legacy standard-only
|
|
849
884
|
* factory remains required for source compatibility. */
|
|
850
885
|
createChildGraphByKind?: GraphFactory;
|
|
886
|
+
/**
|
|
887
|
+
* Captures a child-graph factory and its run-scoped host dependencies
|
|
888
|
+
* synchronously, before a detached task can outlive parent cleanup.
|
|
889
|
+
*/
|
|
890
|
+
createDetachedChildGraphFactory?: (
|
|
891
|
+
parentHandlerRegistry: HandlerRegistry
|
|
892
|
+
) => GraphFactory;
|
|
851
893
|
/**
|
|
852
894
|
* Parent's event handler registry. When provided, child-graph events are
|
|
853
895
|
* forwarded through this registry so hosts can:
|
|
@@ -870,6 +912,8 @@ export type SubagentExecutorOptions = {
|
|
|
870
912
|
* nested subagents report through the same sink.
|
|
871
913
|
*/
|
|
872
914
|
usageSink?: SubagentUsageSink;
|
|
915
|
+
/** Host-owned process-local task namespace for detached execution. */
|
|
916
|
+
taskConfig?: SubagentTaskConfig;
|
|
873
917
|
};
|
|
874
918
|
|
|
875
919
|
type DurableExecutionRecord = SubagentExecutionRecord<
|
|
@@ -902,7 +946,11 @@ export class SubagentExecutor {
|
|
|
902
946
|
private readonly maxDepth: number;
|
|
903
947
|
private readonly createChildGraph: ChildGraphFactory;
|
|
904
948
|
private readonly createChildGraphByKind?: GraphFactory;
|
|
949
|
+
private readonly createDetachedChildGraphFactory?: (
|
|
950
|
+
parentHandlerRegistry: HandlerRegistry
|
|
951
|
+
) => GraphFactory;
|
|
905
952
|
private readonly usageSink?: SubagentUsageSink;
|
|
953
|
+
private readonly taskConfig?: SubagentTaskConfig;
|
|
906
954
|
private readonly executions: SubagentExecutionRegistry<
|
|
907
955
|
SubagentExecuteResult,
|
|
908
956
|
ResolvedSubagentConfig,
|
|
@@ -943,7 +991,10 @@ export class SubagentExecutor {
|
|
|
943
991
|
this.maxDepth = options.maxDepth ?? 1;
|
|
944
992
|
this.createChildGraph = options.createChildGraph;
|
|
945
993
|
this.createChildGraphByKind = options.createChildGraphByKind;
|
|
994
|
+
this.createDetachedChildGraphFactory =
|
|
995
|
+
options.createDetachedChildGraphFactory;
|
|
946
996
|
this.usageSink = options.usageSink;
|
|
997
|
+
this.taskConfig = options.taskConfig;
|
|
947
998
|
const rawRegistry = options.parentHandlerRegistry;
|
|
948
999
|
if (typeof rawRegistry === 'function') {
|
|
949
1000
|
this.resolveParentHandlerRegistry = rawRegistry;
|
|
@@ -979,6 +1030,199 @@ export class SubagentExecutor {
|
|
|
979
1030
|
return this.resolveParentHandlerRegistry?.();
|
|
980
1031
|
}
|
|
981
1032
|
|
|
1033
|
+
/**
|
|
1034
|
+
* Starts one independently-owned executor behind the configured task store.
|
|
1035
|
+
* The parent ToolNode receives the handle synchronously; the detached clone
|
|
1036
|
+
* is not registered on the parent graph, so end-of-turn cleanup cannot
|
|
1037
|
+
* invalidate or clear a child that intentionally outlives that turn.
|
|
1038
|
+
*/
|
|
1039
|
+
executeInBackground(params: SubagentExecuteParams): string {
|
|
1040
|
+
if (this.taskConfig == null) {
|
|
1041
|
+
return JSON.stringify({
|
|
1042
|
+
status: 'rejected',
|
|
1043
|
+
message: 'Background subagent execution is not enabled for this run.',
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
const executableConfig = this.configs.get(params.subagentType);
|
|
1047
|
+
if (executableConfig == null) {
|
|
1048
|
+
return JSON.stringify({
|
|
1049
|
+
status: 'rejected',
|
|
1050
|
+
message: `Unknown subagent type "${params.subagentType}".`,
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
if (this.maxDepth <= 0) {
|
|
1054
|
+
return JSON.stringify({
|
|
1055
|
+
status: 'rejected',
|
|
1056
|
+
message: 'Maximum subagent nesting depth exceeded.',
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
if (this.humanInTheLoop?.enabled === true) {
|
|
1060
|
+
return JSON.stringify({
|
|
1061
|
+
status: 'rejected',
|
|
1062
|
+
message:
|
|
1063
|
+
'Background subagent execution does not support human-in-the-loop pauses.',
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
const parentToolCallId = params.parentToolCallId?.trim();
|
|
1067
|
+
if (parentToolCallId == null || parentToolCallId === '') {
|
|
1068
|
+
return JSON.stringify({
|
|
1069
|
+
status: 'rejected',
|
|
1070
|
+
message:
|
|
1071
|
+
'Background subagent execution requires a parent tool call ID.',
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
const detachedHandlers = new HandlerRegistry();
|
|
1075
|
+
const sourceHookSessionId =
|
|
1076
|
+
asNonEmptyString(params.parentConfigurable?.run_id) ??
|
|
1077
|
+
this.executionContext.hookSessionId;
|
|
1078
|
+
const taskHookRegistry =
|
|
1079
|
+
this.hookRegistry?.forkSession(sourceHookSessionId) ?? new HookRegistry();
|
|
1080
|
+
const toolHandler = this.getParentHandlerRegistry()?.getHandler(
|
|
1081
|
+
GraphEvents.ON_TOOL_EXECUTE
|
|
1082
|
+
);
|
|
1083
|
+
if (toolHandler != null) {
|
|
1084
|
+
detachedHandlers.register(GraphEvents.ON_TOOL_EXECUTE, toolHandler);
|
|
1085
|
+
}
|
|
1086
|
+
const detachedGraphFactory =
|
|
1087
|
+
this.createDetachedChildGraphFactory?.(detachedHandlers);
|
|
1088
|
+
const started = this.taskConfig.store.start({
|
|
1089
|
+
scopeId: this.taskConfig.scopeId,
|
|
1090
|
+
idempotencyKey: JSON.stringify([
|
|
1091
|
+
this.parentRunId,
|
|
1092
|
+
this.parentAgentId ?? '',
|
|
1093
|
+
parentToolCallId,
|
|
1094
|
+
]),
|
|
1095
|
+
requestFingerprint: getBackgroundTaskFingerprint(
|
|
1096
|
+
params.description,
|
|
1097
|
+
params.subagentType
|
|
1098
|
+
),
|
|
1099
|
+
subagentType: params.subagentType,
|
|
1100
|
+
run: (runtime) =>
|
|
1101
|
+
this.executeDetached(
|
|
1102
|
+
params,
|
|
1103
|
+
runtime,
|
|
1104
|
+
detachedHandlers,
|
|
1105
|
+
taskHookRegistry,
|
|
1106
|
+
detachedGraphFactory
|
|
1107
|
+
),
|
|
1108
|
+
});
|
|
1109
|
+
if (!started.accepted) {
|
|
1110
|
+
if (started.reason === 'conflict') {
|
|
1111
|
+
return JSON.stringify({
|
|
1112
|
+
status: 'rejected',
|
|
1113
|
+
tool: Constants.SUBAGENT,
|
|
1114
|
+
message:
|
|
1115
|
+
'The same parent tool call ID was already used with different background subagent arguments.',
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
return JSON.stringify({
|
|
1119
|
+
status: 'rejected',
|
|
1120
|
+
tool: Constants.SUBAGENT,
|
|
1121
|
+
message:
|
|
1122
|
+
'Too many background subagent tasks are already running in this scope or process. Poll or cancel an existing task, or run this call in the foreground.',
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
return JSON.stringify({
|
|
1126
|
+
background_task_id: started.task.taskId,
|
|
1127
|
+
tool: Constants.SUBAGENT,
|
|
1128
|
+
subagent_type: params.subagentType,
|
|
1129
|
+
status: started.task.status,
|
|
1130
|
+
message: `${started.isNew ? 'Started' : 'Reused'} subagent "${params.subagentType}" background task. Poll the host background-task tool with background_task_id "${started.task.taskId}" to check progress or collect its result.`,
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
private async executeDetached(
|
|
1135
|
+
params: SubagentExecuteParams,
|
|
1136
|
+
runtime: SubagentTaskRuntime,
|
|
1137
|
+
detachedHandlers: HandlerRegistry,
|
|
1138
|
+
taskHookRegistry: HookRegistry,
|
|
1139
|
+
detachedGraphFactory?: GraphFactory
|
|
1140
|
+
): Promise<SubagentExecuteResult> {
|
|
1141
|
+
const sourceHookSessionId =
|
|
1142
|
+
asNonEmptyString(params.parentConfigurable?.run_id) ??
|
|
1143
|
+
this.executionContext.hookSessionId;
|
|
1144
|
+
const taskHookSessionId = getBackgroundTaskHookSessionId(
|
|
1145
|
+
sourceHookSessionId,
|
|
1146
|
+
runtime.taskId
|
|
1147
|
+
);
|
|
1148
|
+
const unregisterHooks: Array<() => void> = [];
|
|
1149
|
+
unregisterHooks.push(
|
|
1150
|
+
taskHookRegistry.registerSession(taskHookSessionId, 'PostToolBatch', {
|
|
1151
|
+
hooks: [
|
|
1152
|
+
(): PostToolBatchHookOutput => ({
|
|
1153
|
+
injectedMessages: runtime.drain('tool'),
|
|
1154
|
+
}),
|
|
1155
|
+
],
|
|
1156
|
+
}),
|
|
1157
|
+
taskHookRegistry.registerSession(taskHookSessionId, 'PreemptBoundary', {
|
|
1158
|
+
hooks: [
|
|
1159
|
+
(): PreemptBoundaryHookOutput => ({
|
|
1160
|
+
injectedMessages: runtime.drain('preempt'),
|
|
1161
|
+
}),
|
|
1162
|
+
],
|
|
1163
|
+
})
|
|
1164
|
+
);
|
|
1165
|
+
|
|
1166
|
+
detachedHandlers.register(GraphEvents.ON_SUBAGENT_UPDATE, {
|
|
1167
|
+
handle: (_event, data): void => {
|
|
1168
|
+
runtime.reportProgress(data as SubagentUpdateEvent);
|
|
1169
|
+
},
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
const detached = new SubagentExecutor({
|
|
1173
|
+
configs: this.configs,
|
|
1174
|
+
parentSignal: runtime.signal,
|
|
1175
|
+
hookRegistry: taskHookRegistry,
|
|
1176
|
+
parentHandlerRegistry: detachedHandlers,
|
|
1177
|
+
parentRunId: this.parentRunId,
|
|
1178
|
+
parentAgentId: this.parentAgentId,
|
|
1179
|
+
executionContext: {
|
|
1180
|
+
...this.executionContext,
|
|
1181
|
+
hookSessionId: taskHookSessionId,
|
|
1182
|
+
},
|
|
1183
|
+
langfuse: this.langfuse,
|
|
1184
|
+
tokenCounter: this.tokenCounter,
|
|
1185
|
+
usageSink: this.usageSink,
|
|
1186
|
+
streamLimits: this.streamLimits,
|
|
1187
|
+
maxDepth: this.maxDepth,
|
|
1188
|
+
createChildGraph:
|
|
1189
|
+
detachedGraphFactory == null
|
|
1190
|
+
? this.createChildGraph
|
|
1191
|
+
: (input): StandardGraph =>
|
|
1192
|
+
detachedGraphFactory({ kind: 'standard', input }),
|
|
1193
|
+
createChildGraphByKind:
|
|
1194
|
+
detachedGraphFactory ?? this.createChildGraphByKind,
|
|
1195
|
+
});
|
|
1196
|
+
try {
|
|
1197
|
+
const result = await detached.execute({
|
|
1198
|
+
...params,
|
|
1199
|
+
signal: undefined,
|
|
1200
|
+
breaker: undefined,
|
|
1201
|
+
hookSessionId: taskHookSessionId,
|
|
1202
|
+
taskRuntime: runtime,
|
|
1203
|
+
parentConfigurable: {
|
|
1204
|
+
...params.parentConfigurable,
|
|
1205
|
+
run_id: taskHookSessionId,
|
|
1206
|
+
},
|
|
1207
|
+
});
|
|
1208
|
+
if (runtime.signal.aborted) {
|
|
1209
|
+
throw runtime.signal.reason instanceof Error
|
|
1210
|
+
? runtime.signal.reason
|
|
1211
|
+
: new Error('Detached subagent task cancelled.');
|
|
1212
|
+
}
|
|
1213
|
+
if (result.error != null) {
|
|
1214
|
+
throw new Error(result.error);
|
|
1215
|
+
}
|
|
1216
|
+
return result;
|
|
1217
|
+
} finally {
|
|
1218
|
+
detached.clearHeavyState();
|
|
1219
|
+
for (const unregister of unregisterHooks) {
|
|
1220
|
+
unregister();
|
|
1221
|
+
}
|
|
1222
|
+
taskHookRegistry.clearSession(taskHookSessionId);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
982
1226
|
private bindExecutionDefinition(
|
|
983
1227
|
execution: DurableExecutionRecord,
|
|
984
1228
|
binding: SubagentDefinitionBinding,
|
|
@@ -1940,36 +2184,38 @@ export class SubagentExecutor {
|
|
|
1940
2184
|
const executableConfig = this.configs.get(params.subagentType);
|
|
1941
2185
|
if (executableConfig == null) {
|
|
1942
2186
|
const available = [...this.configs.keys()].join(', ');
|
|
1943
|
-
return Promise.resolve(
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
2187
|
+
return Promise.resolve(
|
|
2188
|
+
createSubagentFailure(
|
|
2189
|
+
`Error: Unknown subagent type "${params.subagentType}". Available types: ${available}`
|
|
2190
|
+
)
|
|
2191
|
+
);
|
|
1947
2192
|
}
|
|
1948
2193
|
if (this.maxDepth <= 0) {
|
|
1949
|
-
return Promise.resolve(
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
2194
|
+
return Promise.resolve(
|
|
2195
|
+
createSubagentFailure(
|
|
2196
|
+
'Error: Maximum subagent nesting depth exceeded.'
|
|
2197
|
+
)
|
|
2198
|
+
);
|
|
1953
2199
|
}
|
|
1954
2200
|
if (
|
|
1955
2201
|
isGraphSubagentConfig(executableConfig) &&
|
|
1956
2202
|
this.humanInTheLoop?.enabled === true
|
|
1957
2203
|
) {
|
|
1958
|
-
return Promise.resolve(
|
|
1959
|
-
|
|
1960
|
-
'Error: Human-in-the-loop execution is not yet supported for graph subagents.'
|
|
1961
|
-
|
|
1962
|
-
|
|
2204
|
+
return Promise.resolve(
|
|
2205
|
+
createSubagentFailure(
|
|
2206
|
+
'Error: Human-in-the-loop execution is not yet supported for graph subagents.'
|
|
2207
|
+
)
|
|
2208
|
+
);
|
|
1963
2209
|
}
|
|
1964
2210
|
if (
|
|
1965
2211
|
this.humanInTheLoop?.enabled === true &&
|
|
1966
2212
|
(params.parentToolCallId == null || params.parentToolCallId === '')
|
|
1967
2213
|
) {
|
|
1968
|
-
return Promise.resolve(
|
|
1969
|
-
|
|
1970
|
-
'Error: Resumable subagent execution requires a parent tool call ID.'
|
|
1971
|
-
|
|
1972
|
-
|
|
2214
|
+
return Promise.resolve(
|
|
2215
|
+
createSubagentFailure(
|
|
2216
|
+
'Error: Resumable subagent execution requires a parent tool call ID.'
|
|
2217
|
+
)
|
|
2218
|
+
);
|
|
1973
2219
|
}
|
|
1974
2220
|
const execution = this.executions.open({
|
|
1975
2221
|
threadId: params.threadId,
|
|
@@ -1989,16 +2235,14 @@ export class SubagentExecutor {
|
|
|
1989
2235
|
);
|
|
1990
2236
|
} catch (error) {
|
|
1991
2237
|
if (error instanceof SubagentDefinitionBindingError) {
|
|
1992
|
-
return Promise.resolve(
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
});
|
|
2238
|
+
return Promise.resolve(
|
|
2239
|
+
createSubagentFailure(SUBAGENT_CONFIG_CHANGED_MESSAGE)
|
|
2240
|
+
);
|
|
1996
2241
|
}
|
|
1997
2242
|
if (error instanceof SubagentInvocationBindingError) {
|
|
1998
|
-
return Promise.resolve(
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
});
|
|
2243
|
+
return Promise.resolve(
|
|
2244
|
+
createSubagentFailure(SUBAGENT_INVOCATION_CHANGED_MESSAGE)
|
|
2245
|
+
);
|
|
2002
2246
|
}
|
|
2003
2247
|
throw error;
|
|
2004
2248
|
}
|
|
@@ -2030,10 +2274,7 @@ export class SubagentExecutor {
|
|
|
2030
2274
|
)
|
|
2031
2275
|
) {
|
|
2032
2276
|
this.executions.remove(execution);
|
|
2033
|
-
return
|
|
2034
|
-
content: SUBAGENT_CONFIG_CHANGED_MESSAGE,
|
|
2035
|
-
messages: [],
|
|
2036
|
-
};
|
|
2277
|
+
return createSubagentFailure(SUBAGENT_CONFIG_CHANGED_MESSAGE);
|
|
2037
2278
|
}
|
|
2038
2279
|
let identity: SubagentExecutionIdentity;
|
|
2039
2280
|
try {
|
|
@@ -2043,10 +2284,7 @@ export class SubagentExecutor {
|
|
|
2043
2284
|
if (error instanceof StreamLimitExceededError) {
|
|
2044
2285
|
throw error;
|
|
2045
2286
|
}
|
|
2046
|
-
return
|
|
2047
|
-
content: SUBAGENT_RESOLUTION_ERROR_MESSAGE,
|
|
2048
|
-
messages: [],
|
|
2049
|
-
};
|
|
2287
|
+
return createSubagentFailure(SUBAGENT_RESOLUTION_ERROR_MESSAGE);
|
|
2050
2288
|
}
|
|
2051
2289
|
const { childRunId, childThreadId, approvalExecutionScope } = identity;
|
|
2052
2290
|
const bound = this.bindExecutionDefinition(
|
|
@@ -2060,7 +2298,7 @@ export class SubagentExecutor {
|
|
|
2060
2298
|
'effective'
|
|
2061
2299
|
);
|
|
2062
2300
|
if (!bound) {
|
|
2063
|
-
return
|
|
2301
|
+
return createSubagentFailure(SUBAGENT_CONFIG_CHANGED_MESSAGE);
|
|
2064
2302
|
}
|
|
2065
2303
|
const completedChildResult = execution.completedResult;
|
|
2066
2304
|
if (completedChildResult != null) {
|
|
@@ -2081,10 +2319,7 @@ export class SubagentExecutor {
|
|
|
2081
2319
|
if (error instanceof StreamLimitExceededError) {
|
|
2082
2320
|
throw error;
|
|
2083
2321
|
}
|
|
2084
|
-
return
|
|
2085
|
-
content: SUBAGENT_RESOLUTION_ERROR_MESSAGE,
|
|
2086
|
-
messages: [],
|
|
2087
|
-
};
|
|
2322
|
+
return createSubagentFailure(SUBAGENT_RESOLUTION_ERROR_MESSAGE);
|
|
2088
2323
|
}
|
|
2089
2324
|
|
|
2090
2325
|
const parentRegistry = this.getParentHandlerRegistry();
|
|
@@ -2109,6 +2344,7 @@ export class SubagentExecutor {
|
|
|
2109
2344
|
});
|
|
2110
2345
|
const childAgentId = childPlan.subjectAgentId;
|
|
2111
2346
|
const currentHookSessionId =
|
|
2347
|
+
asNonEmptyString(params.hookSessionId) ??
|
|
2112
2348
|
asNonEmptyString(params.parentConfigurable?.run_id) ??
|
|
2113
2349
|
this.executionContext.hookSessionId;
|
|
2114
2350
|
const childExecutionContext: SubagentExecutionContext = {
|
|
@@ -2132,7 +2368,8 @@ export class SubagentExecutor {
|
|
|
2132
2368
|
const memberRecursionLimit = maxTurns * SUBAGENT_RECURSION_MULTIPLIER;
|
|
2133
2369
|
const recursionLimit =
|
|
2134
2370
|
memberRecursionLimit *
|
|
2135
|
-
|
|
2371
|
+
(childPlan.kind === 'graph' ? childPlan.agents.length : 1) +
|
|
2372
|
+
(params.taskRuntime == null ? 0 : MAX_BACKGROUND_SUBAGENT_SEALS);
|
|
2136
2373
|
|
|
2137
2374
|
const hostUsageSink = this.usageSink;
|
|
2138
2375
|
let subagentUsageSink: SubagentUsageSink | undefined;
|
|
@@ -2162,15 +2399,22 @@ export class SubagentExecutor {
|
|
|
2162
2399
|
* never see grandchild model calls.
|
|
2163
2400
|
*/
|
|
2164
2401
|
subagentUsageSink,
|
|
2402
|
+
...(params.taskRuntime == null
|
|
2403
|
+
? {}
|
|
2404
|
+
: {
|
|
2405
|
+
preemption: {
|
|
2406
|
+
shouldPreempt: (): boolean =>
|
|
2407
|
+
params.taskRuntime?.shouldPreempt() === true,
|
|
2408
|
+
maxSeals: MAX_BACKGROUND_SUBAGENT_SEALS,
|
|
2409
|
+
},
|
|
2410
|
+
}),
|
|
2165
2411
|
};
|
|
2166
2412
|
let childGraph = cachedChildRun?.graph;
|
|
2167
2413
|
if (childGraph == null && childPlan.kind === 'graph') {
|
|
2168
2414
|
if (this.createChildGraphByKind == null) {
|
|
2169
|
-
return
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
messages: [],
|
|
2173
|
-
};
|
|
2415
|
+
return createSubagentFailure(
|
|
2416
|
+
'Error: Graph subagent execution requires a polymorphic child graph factory.'
|
|
2417
|
+
);
|
|
2174
2418
|
}
|
|
2175
2419
|
childGraph = this.createChildGraphByKind({
|
|
2176
2420
|
kind: 'multi-agent',
|
|
@@ -2183,6 +2427,9 @@ export class SubagentExecutor {
|
|
|
2183
2427
|
});
|
|
2184
2428
|
}
|
|
2185
2429
|
childGraph ??= this.createChildGraph(childGraphInput);
|
|
2430
|
+
if (params.taskRuntime != null) {
|
|
2431
|
+
childGraph.hookRegistry = this.hookRegistry;
|
|
2432
|
+
}
|
|
2186
2433
|
if (cachedChildRun == null) {
|
|
2187
2434
|
seedChildGraphSessions(childGraph, childPlan.agents);
|
|
2188
2435
|
}
|
|
@@ -2426,26 +2673,46 @@ export class SubagentExecutor {
|
|
|
2426
2673
|
});
|
|
2427
2674
|
}
|
|
2428
2675
|
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2676
|
+
for (;;) {
|
|
2677
|
+
let childResult: MultiAgentGraphState;
|
|
2678
|
+
if (this.humanInTheLoop?.enabled === true) {
|
|
2679
|
+
/** Execute as an independently checkpointed root instead of inheriting
|
|
2680
|
+
* the parent's Pregel namespace. Parent decisions are routed explicitly
|
|
2681
|
+
* by interrupt id, so concurrent children keep isolated resume state. */
|
|
2682
|
+
childResult =
|
|
2683
|
+
await AsyncLocalStorageProviderSingleton.runWithConfig(
|
|
2684
|
+
childInvokeConfig,
|
|
2685
|
+
(): Promise<MultiAgentGraphState> =>
|
|
2686
|
+
workflow.invoke(childInput, childInvokeConfig)
|
|
2687
|
+
);
|
|
2688
|
+
} else {
|
|
2689
|
+
childResult = await workflow.invoke(childInput, childInvokeConfig);
|
|
2690
|
+
}
|
|
2691
|
+
const childInterrupts = isInterrupted(childResult)
|
|
2692
|
+
? childResult[INTERRUPT]
|
|
2693
|
+
: undefined;
|
|
2694
|
+
if (childInterrupts != null && childInterrupts.length > 0) {
|
|
2695
|
+
throw new GraphInterrupt(childInterrupts);
|
|
2696
|
+
}
|
|
2697
|
+
result = childResult;
|
|
2698
|
+
const continuation = params.taskRuntime?.closeTurn();
|
|
2699
|
+
if (continuation == null || continuation.closed) {
|
|
2700
|
+
break;
|
|
2701
|
+
}
|
|
2702
|
+
/**
|
|
2703
|
+
* A queued control becomes the next user turn inside the same
|
|
2704
|
+
* detached task. Reset graph sidecars while carrying the complete
|
|
2705
|
+
* child transcript forward, preserving one task/trace identity and
|
|
2706
|
+
* avoiding a second subagent lifecycle or duplicate billing path.
|
|
2707
|
+
*/
|
|
2708
|
+
childGraph.resetValues(true);
|
|
2709
|
+
childInput = {
|
|
2710
|
+
messages: [
|
|
2711
|
+
...childResult.messages,
|
|
2712
|
+
...convertInjectedMessages(continuation.messages),
|
|
2713
|
+
],
|
|
2714
|
+
};
|
|
2447
2715
|
}
|
|
2448
|
-
result = childResult;
|
|
2449
2716
|
}
|
|
2450
2717
|
} catch (error) {
|
|
2451
2718
|
/** Stamped at failure, not after the error-envelope work below. */
|
|
@@ -2535,10 +2802,10 @@ export class SubagentExecutor {
|
|
|
2535
2802
|
if (error instanceof StreamLimitExceededError) {
|
|
2536
2803
|
throw error;
|
|
2537
2804
|
}
|
|
2538
|
-
return
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2805
|
+
return createSubagentFailure(
|
|
2806
|
+
`Subagent error: ${errorMessage}`,
|
|
2807
|
+
errorMessage
|
|
2808
|
+
);
|
|
2542
2809
|
}
|
|
2543
2810
|
|
|
2544
2811
|
/**
|
package/src/types/graph.ts
CHANGED
|
@@ -40,6 +40,7 @@ import type {
|
|
|
40
40
|
TokenBudgetBreakdown,
|
|
41
41
|
} from '@/types/run';
|
|
42
42
|
import type { Providers, Callback, GraphNodeKeys } from '@/common';
|
|
43
|
+
import type { SubagentTaskConfig } from '@/types/subagentTasks';
|
|
43
44
|
import type { StandardGraph, MultiAgentGraph } from '@/graphs';
|
|
44
45
|
import type { ClientOptions } from '@/types/llm';
|
|
45
46
|
|
|
@@ -352,6 +353,12 @@ export type StandardGraphInput = {
|
|
|
352
353
|
* they already flow through the registry's `CHAT_MODEL_END` handler.
|
|
353
354
|
*/
|
|
354
355
|
subagentUsageSink?: SubagentUsageSink;
|
|
356
|
+
/**
|
|
357
|
+
* Optional host-owned process-local task namespace for detached subagents.
|
|
358
|
+
* Presence enables `run_in_background` on the subagent tool. Child graphs
|
|
359
|
+
* do not inherit it, keeping background nesting disabled for the MVP.
|
|
360
|
+
*/
|
|
361
|
+
subagentTasks?: SubagentTaskConfig;
|
|
355
362
|
/**
|
|
356
363
|
* True when this graph IS a subagent child run (set by `SubagentExecutor`
|
|
357
364
|
* when it constructs the child graph). Drives the hook-input `agentId`
|
|
@@ -363,10 +370,10 @@ export type StandardGraphInput = {
|
|
|
363
370
|
*/
|
|
364
371
|
subagentScope?: boolean;
|
|
365
372
|
/**
|
|
366
|
-
* Cooperative preemption, forwarded from `RunConfig.preemption`.
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
373
|
+
* Cooperative preemption, forwarded from `RunConfig.preemption`. Ordinary
|
|
374
|
+
* child graphs do not inherit it. Detached subagent tasks may receive their
|
|
375
|
+
* own internal parent-control source so an interrupt can reuse the same
|
|
376
|
+
* provider-safe sealing path without targeting the top-level conversation.
|
|
370
377
|
*/
|
|
371
378
|
preemption?: StreamPreemption;
|
|
372
379
|
/**
|
package/src/types/index.ts
CHANGED
package/src/types/run.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
ToolOutputReferencesConfig,
|
|
11
11
|
EagerEventToolExecutionConfig,
|
|
12
12
|
} from '@/types/tools';
|
|
13
|
+
import type { SubagentTaskConfig } from '@/types/subagentTasks';
|
|
13
14
|
import type { HumanInTheLoopConfig } from '@/types/hitl';
|
|
14
15
|
import type { HookRegistry } from '@/hooks';
|
|
15
16
|
import type * as s from '@/types/stream';
|
|
@@ -234,6 +235,11 @@ export type RunConfig = {
|
|
|
234
235
|
* the registered `CHAT_MODEL_END` handler as usual.
|
|
235
236
|
*/
|
|
236
237
|
subagentUsageSink?: g.SubagentUsageSink;
|
|
238
|
+
/**
|
|
239
|
+
* Trusted process-local task namespace for detached subagent execution.
|
|
240
|
+
* Omit to preserve foreground-only subagent behavior.
|
|
241
|
+
*/
|
|
242
|
+
subagentTasks?: SubagentTaskConfig;
|
|
237
243
|
/**
|
|
238
244
|
* Pre-constructed hook registry for this run. Hooks fire at lifecycle
|
|
239
245
|
* points in `processStream` (RunStart, UserPromptSubmit, Stop,
|