@librechat/agents 3.8.4 → 3.8.6
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 +1 -1
- package/dist/cjs/common/constants.cjs +2 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +18 -9
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hitl/approvalReview.cjs +1 -1
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/main.cjs +7 -6
- package/dist/cjs/messages/index.cjs +1 -1
- package/dist/cjs/messages/prune.cjs +1 -1
- package/dist/cjs/run.cjs +7 -3
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +13 -3
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +3 -1
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +134 -32
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +1 -0
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +0 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +123 -29
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/toolBatchReplay.cjs +65 -13
- package/dist/cjs/tools/toolBatchReplay.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/esm/agents/AgentContext.mjs +1 -1
- package/dist/esm/common/constants.mjs +2 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +17 -8
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hitl/approvalReview.mjs +1 -1
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/main.mjs +6 -6
- package/dist/esm/messages/index.mjs +1 -1
- package/dist/esm/messages/prune.mjs +1 -1
- package/dist/esm/run.mjs +7 -3
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +13 -3
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +3 -1
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +135 -33
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +1 -0
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +0 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +123 -29
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/toolBatchReplay.mjs +64 -13
- package/dist/esm/tools/toolBatchReplay.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/types/common/constants.d.ts +2 -0
- package/dist/types/graphs/Graph.d.ts +3 -2
- package/dist/types/hooks/types.d.ts +3 -0
- package/dist/types/run.d.ts +1 -0
- package/dist/types/summarization/node.d.ts +1 -0
- package/dist/types/tools/ToolNode.d.ts +4 -2
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +7 -4
- package/dist/types/tools/toolBatchReplay.d.ts +6 -3
- package/dist/types/types/graph.d.ts +28 -0
- package/dist/types/types/run.d.ts +2 -0
- package/dist/types/types/tools.d.ts +11 -1
- package/package.json +1 -1
- package/src/common/constants.ts +3 -0
- package/src/graphs/Graph.ts +32 -18
- package/src/hooks/types.ts +3 -0
- package/src/run.ts +30 -15
- package/src/stream.ts +27 -5
- package/src/summarization/node.ts +3 -0
- package/src/tools/ToolNode.ts +528 -149
- package/src/tools/local/LocalProgrammaticToolCalling.ts +1 -0
- package/src/tools/subagent/SubagentExecutionRegistry.ts +0 -1
- package/src/tools/subagent/SubagentExecutor.ts +216 -48
- package/src/tools/toolBatchReplay.ts +121 -20
- package/src/types/graph.ts +37 -0
- package/src/types/run.ts +2 -0
- package/src/types/tools.ts +11 -1
- package/src/utils/llmConfig.ts +0 -1
|
@@ -214,6 +214,7 @@ export async function applyPreToolUseHooksForBridge(
|
|
|
214
214
|
runId: hookContext.runId,
|
|
215
215
|
threadId: hookContext.threadId,
|
|
216
216
|
agentId: hookContext.agentId,
|
|
217
|
+
executionContext: hookContext.executionContext,
|
|
217
218
|
executingAgentId: hookContext.executingAgentId,
|
|
218
219
|
toolName,
|
|
219
220
|
toolInput,
|
|
@@ -783,7 +783,6 @@ export class SubagentExecutionRecord<
|
|
|
783
783
|
this.transitionTo('completed', ['active']);
|
|
784
784
|
this.completedValue = true;
|
|
785
785
|
this.completedResultValue = result;
|
|
786
|
-
this.resolvedConfigValue = undefined;
|
|
787
786
|
}
|
|
788
787
|
|
|
789
788
|
releaseActiveRun(): TActiveRun | undefined {
|
|
@@ -52,6 +52,10 @@ import type {
|
|
|
52
52
|
ResolvedSubagentConfig,
|
|
53
53
|
ResolvedSubagentConfigEntry,
|
|
54
54
|
SubagentExecutionContext,
|
|
55
|
+
SubagentContextAdapter,
|
|
56
|
+
SubagentContextInput,
|
|
57
|
+
SubagentContextResult,
|
|
58
|
+
PreparedSubagentContext,
|
|
55
59
|
SubagentTaskConfig,
|
|
56
60
|
SubagentTaskRuntime,
|
|
57
61
|
SubagentResolveConfigurable,
|
|
@@ -88,11 +92,6 @@ import type {
|
|
|
88
92
|
ToolApprovalReplaySnapshot,
|
|
89
93
|
} from '@/hooks';
|
|
90
94
|
import type { GraphFactory } from '@/graphs/graphFactory';
|
|
91
|
-
import {
|
|
92
|
-
rebindToolBatchReplayScope,
|
|
93
|
-
rebindToolBatchReplayPayload,
|
|
94
|
-
restoreToolReplayConfig,
|
|
95
|
-
} from '@/tools/toolBatchReplay';
|
|
96
95
|
import type { StandardGraph } from '@/graphs/Graph';
|
|
97
96
|
import {
|
|
98
97
|
getSubagentApprovalExecutionScope,
|
|
@@ -108,6 +107,11 @@ import {
|
|
|
108
107
|
SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY,
|
|
109
108
|
SUBAGENT_RESUME_MANIFEST_CONFIG_KEY,
|
|
110
109
|
} from './SubagentReplay';
|
|
110
|
+
import {
|
|
111
|
+
rebindToolBatchReplayScope,
|
|
112
|
+
rebindToolBatchReplayPayload,
|
|
113
|
+
restoreToolReplayConfig,
|
|
114
|
+
} from '@/tools/toolBatchReplay';
|
|
111
115
|
import {
|
|
112
116
|
executeHooks,
|
|
113
117
|
HookRegistry,
|
|
@@ -138,6 +142,7 @@ import { stableStringify } from '@/tools/eagerEventExecution';
|
|
|
138
142
|
import { convertInjectedMessages } from '@/messages/injected';
|
|
139
143
|
import { resolveClientOptionsModel } from '@/llm/request';
|
|
140
144
|
import { composeAbortSignals } from '@/utils/misc';
|
|
145
|
+
import { sleep } from '@/utils/run';
|
|
141
146
|
import { HandlerRegistry } from '@/events';
|
|
142
147
|
|
|
143
148
|
export {
|
|
@@ -168,6 +173,31 @@ function seedChildGraphSessions(
|
|
|
168
173
|
seedAgentInitialSessions(childGraph.sessions, agents);
|
|
169
174
|
}
|
|
170
175
|
|
|
176
|
+
async function awaitWithAbort<T>(
|
|
177
|
+
promise: Promise<T>,
|
|
178
|
+
signal: AbortSignal
|
|
179
|
+
): Promise<T> {
|
|
180
|
+
signal.throwIfAborted();
|
|
181
|
+
let onAbort: (() => void) | undefined;
|
|
182
|
+
const aborted = new Promise<never>((_resolve, reject) => {
|
|
183
|
+
onAbort = (): void => {
|
|
184
|
+
reject(
|
|
185
|
+
signal.reason instanceof Error
|
|
186
|
+
? signal.reason
|
|
187
|
+
: new Error('Detached subagent task cancelled.')
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
191
|
+
});
|
|
192
|
+
try {
|
|
193
|
+
return await Promise.race([promise, aborted]);
|
|
194
|
+
} finally {
|
|
195
|
+
if (onAbort != null) {
|
|
196
|
+
signal.removeEventListener('abort', onAbort);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
171
201
|
async function dispatchObservationalSubagentUpdate(
|
|
172
202
|
handler: EventHandler,
|
|
173
203
|
event: SubagentUpdateEvent
|
|
@@ -831,11 +861,11 @@ export type SubagentExecuteParams = {
|
|
|
831
861
|
initialMessages?: BaseMessage[];
|
|
832
862
|
};
|
|
833
863
|
|
|
834
|
-
export type SubagentExecuteResult = {
|
|
835
|
-
content: string;
|
|
836
|
-
messages: BaseMessage[];
|
|
864
|
+
export type SubagentExecuteResult = SubagentContextResult & {
|
|
837
865
|
/** Tagged internal failure; foreground callers retain the legacy content. */
|
|
838
866
|
error?: string;
|
|
867
|
+
/** Completed child work whose host projection must be retried without re-execution. */
|
|
868
|
+
retryableDelivery?: true;
|
|
839
869
|
};
|
|
840
870
|
|
|
841
871
|
function createSubagentFailure(
|
|
@@ -931,6 +961,7 @@ export type SubagentExecutorOptions = {
|
|
|
931
961
|
usageSink?: SubagentUsageSink;
|
|
932
962
|
/** Host-owned process-local task namespace for detached execution. */
|
|
933
963
|
taskConfig?: SubagentTaskConfig;
|
|
964
|
+
subagentContext?: SubagentContextAdapter;
|
|
934
965
|
};
|
|
935
966
|
|
|
936
967
|
type DurableExecutionRecord = SubagentExecutionRecord<
|
|
@@ -968,6 +999,7 @@ export class SubagentExecutor {
|
|
|
968
999
|
) => GraphFactory;
|
|
969
1000
|
private readonly usageSink?: SubagentUsageSink;
|
|
970
1001
|
private readonly taskConfig?: SubagentTaskConfig;
|
|
1002
|
+
private readonly subagentContext?: SubagentContextAdapter;
|
|
971
1003
|
private readonly executions: SubagentExecutionRegistry<
|
|
972
1004
|
SubagentExecuteResult,
|
|
973
1005
|
ResolvedSubagentConfig,
|
|
@@ -1012,6 +1044,7 @@ export class SubagentExecutor {
|
|
|
1012
1044
|
options.createDetachedChildGraphFactory;
|
|
1013
1045
|
this.usageSink = options.usageSink;
|
|
1014
1046
|
this.taskConfig = options.taskConfig;
|
|
1047
|
+
this.subagentContext = options.subagentContext;
|
|
1015
1048
|
const rawRegistry = options.parentHandlerRegistry;
|
|
1016
1049
|
if (typeof rawRegistry === 'function') {
|
|
1017
1050
|
this.resolveParentHandlerRegistry = rawRegistry;
|
|
@@ -1257,6 +1290,7 @@ export class SubagentExecutor {
|
|
|
1257
1290
|
langfuse: this.langfuse,
|
|
1258
1291
|
tokenCounter: this.tokenCounter,
|
|
1259
1292
|
usageSink: this.usageSink,
|
|
1293
|
+
subagentContext: this.subagentContext,
|
|
1260
1294
|
streamLimits: this.streamLimits,
|
|
1261
1295
|
maxDepth: this.maxDepth,
|
|
1262
1296
|
createChildGraph:
|
|
@@ -1268,21 +1302,38 @@ export class SubagentExecutor {
|
|
|
1268
1302
|
detachedGraphFactory ?? this.createChildGraphByKind,
|
|
1269
1303
|
});
|
|
1270
1304
|
try {
|
|
1271
|
-
const
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1305
|
+
const executeAttempt = async (): Promise<SubagentExecuteResult> => {
|
|
1306
|
+
const result = await detached.execute({
|
|
1307
|
+
...params,
|
|
1308
|
+
signal: undefined,
|
|
1309
|
+
breaker: undefined,
|
|
1310
|
+
hookSessionId: taskHookSessionId,
|
|
1311
|
+
taskRuntime: runtime,
|
|
1312
|
+
parentConfigurable: {
|
|
1313
|
+
...params.parentConfigurable,
|
|
1314
|
+
run_id: taskHookSessionId,
|
|
1315
|
+
},
|
|
1316
|
+
});
|
|
1317
|
+
if (runtime.signal.aborted) {
|
|
1318
|
+
throw runtime.signal.reason instanceof Error
|
|
1319
|
+
? runtime.signal.reason
|
|
1320
|
+
: new Error('Detached subagent task cancelled.');
|
|
1321
|
+
}
|
|
1322
|
+
return result;
|
|
1323
|
+
};
|
|
1324
|
+
let deliveryAttempts = 1;
|
|
1325
|
+
let result = await executeAttempt();
|
|
1326
|
+
while (
|
|
1327
|
+
result.retryableDelivery === true &&
|
|
1328
|
+
deliveryAttempts < 3
|
|
1329
|
+
) {
|
|
1330
|
+
if (deliveryAttempts > 1) {
|
|
1331
|
+
await sleep(
|
|
1332
|
+
Math.min(100 * 2 ** Math.min(deliveryAttempts - 2, 6), 5_000)
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
deliveryAttempts += 1;
|
|
1336
|
+
result = await executeAttempt();
|
|
1286
1337
|
}
|
|
1287
1338
|
if (result.error != null) {
|
|
1288
1339
|
throw new Error(result.error);
|
|
@@ -1454,7 +1505,10 @@ export class SubagentExecutor {
|
|
|
1454
1505
|
resumeExecution.checkpoints,
|
|
1455
1506
|
branchChildThreadId,
|
|
1456
1507
|
lease,
|
|
1457
|
-
{
|
|
1508
|
+
{
|
|
1509
|
+
source: resumeExecution.approvalExecutionScope,
|
|
1510
|
+
target: approvalExecutionScope,
|
|
1511
|
+
}
|
|
1458
1512
|
);
|
|
1459
1513
|
let previousApprovals: ToolApprovalReplaySnapshot[] = [];
|
|
1460
1514
|
let approvalsMutated = false;
|
|
@@ -1765,11 +1819,20 @@ export class SubagentExecutor {
|
|
|
1765
1819
|
}
|
|
1766
1820
|
/** Repeated unconsumed forks must persist their current owner. */
|
|
1767
1821
|
const reboundValue =
|
|
1768
|
-
channel === INTERRUPT &&
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1822
|
+
channel === INTERRUPT &&
|
|
1823
|
+
approvalScope != null &&
|
|
1824
|
+
value != null &&
|
|
1825
|
+
typeof value === 'object' &&
|
|
1826
|
+
'value' in value
|
|
1827
|
+
? {
|
|
1828
|
+
...value,
|
|
1829
|
+
value: rebindToolBatchReplayPayload(
|
|
1830
|
+
value.value,
|
|
1831
|
+
approvalScope.source,
|
|
1832
|
+
approvalScope.target,
|
|
1833
|
+
targetThreadId
|
|
1834
|
+
),
|
|
1835
|
+
}
|
|
1773
1836
|
: value;
|
|
1774
1837
|
writes.push([channel, reboundValue]);
|
|
1775
1838
|
}
|
|
@@ -2114,6 +2177,9 @@ export class SubagentExecutor {
|
|
|
2114
2177
|
) {
|
|
2115
2178
|
return;
|
|
2116
2179
|
}
|
|
2180
|
+
if (settled.output.status === 'error' && config.signal?.aborted === true) {
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2117
2183
|
const parentConfigurable = config.configurable as
|
|
2118
2184
|
| Record<string, unknown>
|
|
2119
2185
|
| undefined;
|
|
@@ -2123,6 +2189,9 @@ export class SubagentExecutor {
|
|
|
2123
2189
|
parentToolCallId,
|
|
2124
2190
|
parentConfigurable,
|
|
2125
2191
|
});
|
|
2192
|
+
if (execution.completedResult != null && settled.output.status === 'error') {
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2126
2195
|
const { resumeExecution } = execution;
|
|
2127
2196
|
const resolvedSubagentType = getSubagentTypeFromArgs(settled.resolvedArgs);
|
|
2128
2197
|
const resolvedDescription = getSubagentDescriptionFromArgs(
|
|
@@ -2383,11 +2452,6 @@ export class SubagentExecutor {
|
|
|
2383
2452
|
if (!bound) {
|
|
2384
2453
|
return createSubagentFailure(SUBAGENT_CONFIG_CHANGED_MESSAGE);
|
|
2385
2454
|
}
|
|
2386
|
-
const completedChildResult = execution.completedResult;
|
|
2387
|
-
if (completedChildResult != null) {
|
|
2388
|
-
return completedChildResult;
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
2455
|
let config: ResolvedSubagentConfigEntry;
|
|
2392
2456
|
try {
|
|
2393
2457
|
config = await this.resolveExecutionConfig(executableConfig, execution, {
|
|
@@ -2447,6 +2511,63 @@ export class SubagentExecutor {
|
|
|
2447
2511
|
},
|
|
2448
2512
|
],
|
|
2449
2513
|
};
|
|
2514
|
+
const hostContextInput: SubagentContextInput = {
|
|
2515
|
+
executionContext: childExecutionContext,
|
|
2516
|
+
parentThreadId: threadId,
|
|
2517
|
+
memberAgentIds: childPlan.agents.map((agent) => agent.agentId),
|
|
2518
|
+
signal: childSignal,
|
|
2519
|
+
resumed: execution.started || resumeExecution != null,
|
|
2520
|
+
};
|
|
2521
|
+
let preparedContext: PreparedSubagentContext | undefined;
|
|
2522
|
+
try {
|
|
2523
|
+
preparedContext =
|
|
2524
|
+
this.subagentContext?.prepare == null
|
|
2525
|
+
? undefined
|
|
2526
|
+
: await awaitWithAbort(
|
|
2527
|
+
Promise.resolve(this.subagentContext.prepare(hostContextInput)),
|
|
2528
|
+
childSignal
|
|
2529
|
+
);
|
|
2530
|
+
execution.assertUsable(childSignal);
|
|
2531
|
+
for (const [agentId, sessions] of Object.entries(
|
|
2532
|
+
preparedContext?.agentSessions ?? {}
|
|
2533
|
+
)) {
|
|
2534
|
+
const member = childPlan.memberInputs.get(agentId);
|
|
2535
|
+
if (member == null) throw new Error('Unknown subagent context member.');
|
|
2536
|
+
const activeMember =
|
|
2537
|
+
execution.activeRun?.graph.agentContexts.get(agentId);
|
|
2538
|
+
if (
|
|
2539
|
+
activeMember != null &&
|
|
2540
|
+
activeMember.codeSessionKey !== sessions.codeSessionKey
|
|
2541
|
+
) {
|
|
2542
|
+
throw new Error(
|
|
2543
|
+
'Subagent session partition changed during execution.'
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
member.codeSessionKey = sessions.codeSessionKey;
|
|
2547
|
+
member.initialSessions = sessions.initialSessions;
|
|
2548
|
+
}
|
|
2549
|
+
} catch (error) {
|
|
2550
|
+
if (childSignal.aborted) {
|
|
2551
|
+
throw childSignal.reason instanceof Error
|
|
2552
|
+
? childSignal.reason
|
|
2553
|
+
: error;
|
|
2554
|
+
}
|
|
2555
|
+
if (execution.completedResult != null) {
|
|
2556
|
+
return {
|
|
2557
|
+
...createSubagentFailure(
|
|
2558
|
+
'Subagent context is unavailable or access was denied.'
|
|
2559
|
+
),
|
|
2560
|
+
retryableDelivery: true,
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
return createSubagentFailure(
|
|
2564
|
+
'Subagent context is unavailable or access was denied.'
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
const completedChildResult = execution.completedResult;
|
|
2568
|
+
if (completedChildResult != null) {
|
|
2569
|
+
return this.completeHostContext(hostContextInput, completedChildResult);
|
|
2570
|
+
}
|
|
2450
2571
|
const maxTurns = config.maxTurns ?? DEFAULT_SUBAGENT_MAX_TURNS;
|
|
2451
2572
|
const memberRecursionLimit = maxTurns * SUBAGENT_RECURSION_MULTIPLIER;
|
|
2452
2573
|
const recursionLimit =
|
|
@@ -2473,6 +2594,7 @@ export class SubagentExecutor {
|
|
|
2473
2594
|
streamLimits: this.streamLimits,
|
|
2474
2595
|
subagentScope: true,
|
|
2475
2596
|
subagentExecutionContext: childExecutionContext,
|
|
2597
|
+
subagentContext: this.subagentContext,
|
|
2476
2598
|
...(resumeExecution?.graphState.fadingTier == null
|
|
2477
2599
|
? {}
|
|
2478
2600
|
: { fadingTier: resumeExecution.graphState.fadingTier }),
|
|
@@ -2519,9 +2641,7 @@ export class SubagentExecutor {
|
|
|
2519
2641
|
if (params.taskRuntime != null) {
|
|
2520
2642
|
childGraph.hookRegistry = this.hookRegistry;
|
|
2521
2643
|
}
|
|
2522
|
-
|
|
2523
|
-
seedChildGraphSessions(childGraph, childPlan.agents);
|
|
2524
|
-
}
|
|
2644
|
+
seedChildGraphSessions(childGraph, childPlan.agents);
|
|
2525
2645
|
let forwarding: ForwarderCallback | undefined;
|
|
2526
2646
|
if (forwardingEnabled) {
|
|
2527
2647
|
forwarding = this.createForwarderCallback({
|
|
@@ -2623,6 +2743,8 @@ export class SubagentExecutor {
|
|
|
2623
2743
|
}
|
|
2624
2744
|
const childConfigurable: Record<string, unknown> = {
|
|
2625
2745
|
...inheritedConfigurable,
|
|
2746
|
+
...sanitizePreparedConfigurable(preparedContext?.configurable),
|
|
2747
|
+
executionContext: childExecutionContext,
|
|
2626
2748
|
};
|
|
2627
2749
|
if (this.humanInTheLoop?.enabled === true) {
|
|
2628
2750
|
childConfigurable[TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY] =
|
|
@@ -2663,11 +2785,18 @@ export class SubagentExecutor {
|
|
|
2663
2785
|
}
|
|
2664
2786
|
const persistedInterrupts = getPersistedInterrupts(persistedState);
|
|
2665
2787
|
if (persistedInterrupts.length > 0) {
|
|
2666
|
-
activeChildRun.pendingInterrupts =
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2788
|
+
activeChildRun.pendingInterrupts =
|
|
2789
|
+
resumeExecution == null
|
|
2790
|
+
? persistedInterrupts
|
|
2791
|
+
: persistedInterrupts.map((pending) => ({
|
|
2792
|
+
...pending,
|
|
2793
|
+
value: rebindToolBatchReplayPayload(
|
|
2794
|
+
pending.value,
|
|
2795
|
+
resumeExecution.approvalExecutionScope,
|
|
2796
|
+
approvalExecutionScope,
|
|
2797
|
+
childThreadId
|
|
2798
|
+
),
|
|
2799
|
+
}));
|
|
2671
2800
|
execution.markStarted();
|
|
2672
2801
|
childAlreadyStarted = true;
|
|
2673
2802
|
} else if (persistedState.next.length > 0) {
|
|
@@ -2702,14 +2831,18 @@ export class SubagentExecutor {
|
|
|
2702
2831
|
}
|
|
2703
2832
|
let childInput: BaseGraphState | Command | null;
|
|
2704
2833
|
if (childResumeMap != null) {
|
|
2705
|
-
const pending =
|
|
2706
|
-
|
|
2707
|
-
|
|
2834
|
+
const pending =
|
|
2835
|
+
activeChildRun.pendingInterrupts.find(
|
|
2836
|
+
(entry) =>
|
|
2837
|
+
entry.id != null &&
|
|
2838
|
+
Object.prototype.hasOwnProperty.call(childResumeMap, entry.id)
|
|
2839
|
+
) ?? activeChildRun.pendingInterrupts[0];
|
|
2708
2840
|
restoreToolReplayConfig(childConfigurable, pending.id, pending.value);
|
|
2709
2841
|
rebindToolBatchReplayScope(
|
|
2710
2842
|
childConfigurable,
|
|
2711
2843
|
resumeExecution?.approvalExecutionScope ?? approvalExecutionScope,
|
|
2712
|
-
approvalExecutionScope
|
|
2844
|
+
approvalExecutionScope,
|
|
2845
|
+
childThreadId
|
|
2713
2846
|
);
|
|
2714
2847
|
childInput = new Command({ resume: childResumeMap });
|
|
2715
2848
|
} else if (recoveredInProgress) {
|
|
@@ -2725,6 +2858,7 @@ export class SubagentExecutor {
|
|
|
2725
2858
|
[SUBAGENT_RUN_ID_KEY]: childRunId,
|
|
2726
2859
|
},
|
|
2727
2860
|
}),
|
|
2861
|
+
...(preparedContext?.messages ?? []),
|
|
2728
2862
|
],
|
|
2729
2863
|
};
|
|
2730
2864
|
}
|
|
@@ -2740,6 +2874,7 @@ export class SubagentExecutor {
|
|
|
2740
2874
|
registry: this.hookRegistry,
|
|
2741
2875
|
input: {
|
|
2742
2876
|
hook_event_name: 'SubagentStart',
|
|
2877
|
+
executionContext: childExecutionContext,
|
|
2743
2878
|
runId: currentHookSessionId,
|
|
2744
2879
|
threadId,
|
|
2745
2880
|
parentAgentId: this.parentAgentId,
|
|
@@ -2946,6 +3081,7 @@ export class SubagentExecutor {
|
|
|
2946
3081
|
registry: this.hookRegistry,
|
|
2947
3082
|
input: {
|
|
2948
3083
|
hook_event_name: 'SubagentStop',
|
|
3084
|
+
executionContext: childExecutionContext,
|
|
2949
3085
|
runId: currentHookSessionId,
|
|
2950
3086
|
threadId,
|
|
2951
3087
|
agentId: childAgentId,
|
|
@@ -2988,12 +3124,33 @@ export class SubagentExecutor {
|
|
|
2988
3124
|
|
|
2989
3125
|
this.clearChildGraph(childGraph);
|
|
2990
3126
|
|
|
2991
|
-
const completedResult = {
|
|
3127
|
+
const completedResult: SubagentExecuteResult = {
|
|
2992
3128
|
content: filteredContent,
|
|
2993
3129
|
messages: result.messages,
|
|
2994
3130
|
};
|
|
2995
3131
|
execution.markCompleted(completedResult);
|
|
2996
|
-
return completedResult;
|
|
3132
|
+
return this.completeHostContext(hostContextInput, completedResult);
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
private async completeHostContext(
|
|
3136
|
+
input: SubagentContextInput,
|
|
3137
|
+
result: SubagentExecuteResult
|
|
3138
|
+
): Promise<SubagentExecuteResult> {
|
|
3139
|
+
if (this.subagentContext?.complete == null) return result;
|
|
3140
|
+
try {
|
|
3141
|
+
input.signal.throwIfAborted();
|
|
3142
|
+
const completed = await awaitWithAbort(
|
|
3143
|
+
this.subagentContext.complete(input, result),
|
|
3144
|
+
input.signal
|
|
3145
|
+
);
|
|
3146
|
+
input.signal.throwIfAborted();
|
|
3147
|
+
return { ...result, content: completed.content };
|
|
3148
|
+
} catch {
|
|
3149
|
+
return {
|
|
3150
|
+
...createSubagentFailure('Subagent result delivery failed.'),
|
|
3151
|
+
retryableDelivery: true,
|
|
3152
|
+
};
|
|
3153
|
+
}
|
|
2997
3154
|
}
|
|
2998
3155
|
|
|
2999
3156
|
/**
|
|
@@ -3526,6 +3683,17 @@ function sanitizeChildConfigurable(
|
|
|
3526
3683
|
);
|
|
3527
3684
|
}
|
|
3528
3685
|
|
|
3686
|
+
function sanitizePreparedConfigurable(
|
|
3687
|
+
configurable: RunnableConfig['configurable']
|
|
3688
|
+
): Record<string, unknown> {
|
|
3689
|
+
const prepared = sanitizeChildConfigurable(configurable);
|
|
3690
|
+
delete prepared.run_id;
|
|
3691
|
+
delete prepared.parent_run_id;
|
|
3692
|
+
delete prepared.thread_id;
|
|
3693
|
+
delete prepared.executionContext;
|
|
3694
|
+
return prepared;
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3529
3697
|
function isLangGraphRuntimeConfigKey(key: string): boolean {
|
|
3530
3698
|
return (
|
|
3531
3699
|
key.startsWith(LANGGRAPH_RUNTIME_CONFIG_PREFIX) ||
|
|
@@ -46,6 +46,8 @@ export interface ToolBatchReplayRecord {
|
|
|
46
46
|
referenceState?: ToolOutputReferenceState;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export type ToolReplayResumeStatus = 'active' | 'stale' | 'unverifiable';
|
|
50
|
+
|
|
49
51
|
interface ToolBatchReplayState {
|
|
50
52
|
version: 1;
|
|
51
53
|
approvalOwner?: string;
|
|
@@ -62,6 +64,44 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
62
64
|
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
function bindLegacyReplayOwner(
|
|
68
|
+
owner: string,
|
|
69
|
+
configurable: Record<string, unknown>
|
|
70
|
+
): string {
|
|
71
|
+
let identity: unknown;
|
|
72
|
+
try {
|
|
73
|
+
identity = JSON.parse(owner);
|
|
74
|
+
} catch {
|
|
75
|
+
return owner;
|
|
76
|
+
}
|
|
77
|
+
if (!Array.isArray(identity) || identity.length !== 3) {
|
|
78
|
+
return owner;
|
|
79
|
+
}
|
|
80
|
+
return JSON.stringify([
|
|
81
|
+
...identity,
|
|
82
|
+
typeof configurable.user_id === 'string' ? configurable.user_id : '',
|
|
83
|
+
typeof configurable.thread_id === 'string' ? configurable.thread_id : '',
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function bindLegacyReplayState(
|
|
88
|
+
state: ToolBatchReplayState | undefined,
|
|
89
|
+
configurable: Record<string, unknown>
|
|
90
|
+
): ToolBatchReplayState | undefined {
|
|
91
|
+
if (state == null) return undefined;
|
|
92
|
+
return {
|
|
93
|
+
...state,
|
|
94
|
+
approvalOwner:
|
|
95
|
+
state.approvalOwner == null
|
|
96
|
+
? undefined
|
|
97
|
+
: bindLegacyReplayOwner(state.approvalOwner, configurable),
|
|
98
|
+
records: state.records.map((record) => ({
|
|
99
|
+
...record,
|
|
100
|
+
owner: bindLegacyReplayOwner(record.owner, configurable),
|
|
101
|
+
})),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
65
105
|
/** The only host-to-runtime entry point for checkpoint-owned tool replay state. */
|
|
66
106
|
export function restoreToolReplayConfig(
|
|
67
107
|
configurable: Record<string, unknown>,
|
|
@@ -70,7 +110,10 @@ export function restoreToolReplayConfig(
|
|
|
70
110
|
): void {
|
|
71
111
|
delete configurable[TOOL_BATCH_REPLAY_KEY];
|
|
72
112
|
delete configurable[TOOL_APPROVAL_REVIEW_CONFIG_KEY];
|
|
73
|
-
const state =
|
|
113
|
+
const state = bindLegacyReplayState(
|
|
114
|
+
getToolBatchReplayState(payload),
|
|
115
|
+
configurable
|
|
116
|
+
);
|
|
74
117
|
const publicPayload = getPublicToolInterruptPayload(payload);
|
|
75
118
|
const review = createToolApprovalReviewEvidence(
|
|
76
119
|
interruptId,
|
|
@@ -95,25 +138,40 @@ export function restoreToolReplayConfig(
|
|
|
95
138
|
* steps, so the presence of replay evidence alone does not imply a resume.
|
|
96
139
|
* A parent replaying a checkpointed child has no local resume value.
|
|
97
140
|
*/
|
|
98
|
-
export function
|
|
141
|
+
export function getToolReplayResumeStatus(
|
|
99
142
|
config: RunnableConfig,
|
|
100
143
|
interruptId: string | undefined,
|
|
101
144
|
isChildReplay: boolean
|
|
102
|
-
):
|
|
103
|
-
const resumeMap:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return false;
|
|
145
|
+
): ToolReplayResumeStatus {
|
|
146
|
+
const resumeMap: unknown = config.configurable?.__pregel_resume_map;
|
|
147
|
+
const scratchpad: unknown = config.configurable?.__pregel_scratchpad;
|
|
148
|
+
if (!isRecord(scratchpad) || !Array.isArray(scratchpad.resume)) {
|
|
149
|
+
return 'unverifiable';
|
|
108
150
|
}
|
|
109
|
-
|
|
110
|
-
|
|
151
|
+
/**
|
|
152
|
+
* LangGraph supports both an interrupt-id resume map and a task-local
|
|
153
|
+
* `nullResume` value. The latter is already scoped to the checkpoint task,
|
|
154
|
+
* so it remains a supported, verifiable resume without a map entry.
|
|
155
|
+
*/
|
|
156
|
+
if (scratchpad.nullResume !== undefined) {
|
|
157
|
+
return 'active';
|
|
111
158
|
}
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
159
|
+
const hasResume = scratchpad.resume.length > 0;
|
|
160
|
+
if (!isRecord(resumeMap)) {
|
|
161
|
+
return hasResume ? 'unverifiable' : 'stale';
|
|
162
|
+
}
|
|
163
|
+
if (interruptId == null) {
|
|
164
|
+
/** Legacy direct graph callers do not restore the ID into replay state. */
|
|
165
|
+
if (!hasResume) return 'stale';
|
|
166
|
+
return Object.keys(resumeMap).length === 1 ? 'active' : 'unverifiable';
|
|
167
|
+
}
|
|
168
|
+
if (!Object.prototype.hasOwnProperty.call(resumeMap, interruptId)) {
|
|
169
|
+
return hasResume ? 'unverifiable' : 'stale';
|
|
170
|
+
}
|
|
171
|
+
if (isChildReplay) {
|
|
172
|
+
return 'active';
|
|
173
|
+
}
|
|
174
|
+
return hasResume ? 'active' : 'stale';
|
|
117
175
|
}
|
|
118
176
|
|
|
119
177
|
export function getToolBatchReplayOwner(
|
|
@@ -126,9 +184,39 @@ export function getToolBatchReplayOwner(
|
|
|
126
184
|
? (config.configurable?.checkpoint_ns ?? '')
|
|
127
185
|
: '',
|
|
128
186
|
agentId,
|
|
187
|
+
typeof config.configurable?.user_id === 'string'
|
|
188
|
+
? config.configurable.user_id
|
|
189
|
+
: '',
|
|
190
|
+
typeof config.configurable?.thread_id === 'string'
|
|
191
|
+
? config.configurable.thread_id
|
|
192
|
+
: '',
|
|
129
193
|
]);
|
|
130
194
|
}
|
|
131
195
|
|
|
196
|
+
/** Remove stale authorization while retaining only settled results for this batch. */
|
|
197
|
+
export function clearStaleToolApprovalConfig(
|
|
198
|
+
configurable: Record<string, unknown>,
|
|
199
|
+
owner: string,
|
|
200
|
+
batch: string | undefined
|
|
201
|
+
): void {
|
|
202
|
+
delete configurable[TOOL_APPROVAL_REVIEW_CONFIG_KEY];
|
|
203
|
+
const state = getToolBatchReplayState(configurable);
|
|
204
|
+
const records = batch == null
|
|
205
|
+
? []
|
|
206
|
+
: (state?.records.filter(
|
|
207
|
+
(record) => record.owner === owner && record.batch === batch
|
|
208
|
+
) ?? []);
|
|
209
|
+
if (state == null || records.length === 0) {
|
|
210
|
+
delete configurable[TOOL_BATCH_REPLAY_KEY];
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
configurable[TOOL_BATCH_REPLAY_KEY] = {
|
|
214
|
+
version: 1,
|
|
215
|
+
records,
|
|
216
|
+
wrappedPayload: state.wrappedPayload,
|
|
217
|
+
} satisfies ToolBatchReplayState;
|
|
218
|
+
}
|
|
219
|
+
|
|
132
220
|
export function getToolBatchReplayScope(
|
|
133
221
|
config: RunnableConfig
|
|
134
222
|
): string | undefined {
|
|
@@ -147,7 +235,9 @@ export function isChildToolReplayOwner(
|
|
|
147
235
|
const manifest = requireValidSubagentResumeManifest(config.configurable);
|
|
148
236
|
if (manifest == null || trustedParentCallIds.size === 0) return false;
|
|
149
237
|
const identity: unknown = JSON.parse(owner);
|
|
150
|
-
if (!Array.isArray(identity) ||
|
|
238
|
+
if (!Array.isArray(identity) ||
|
|
239
|
+
(identity.length !== 3 && identity.length !== 5) ||
|
|
240
|
+
identity[1] !== '') return false;
|
|
151
241
|
const pending = manifest.executions.filter((execution) => trustedParentCallIds.has(execution.parentToolCallId));
|
|
152
242
|
while (pending.length > 0) {
|
|
153
243
|
const execution = pending.pop()!;
|
|
@@ -161,14 +251,19 @@ export function isChildToolReplayOwner(
|
|
|
161
251
|
export function rebindToolBatchReplayScope(
|
|
162
252
|
configurable: Record<string, unknown>,
|
|
163
253
|
sourceScope: string,
|
|
164
|
-
destinationScope: string
|
|
254
|
+
destinationScope: string,
|
|
255
|
+
destinationThreadId?: string
|
|
165
256
|
): void {
|
|
166
257
|
const rebind = (key: string): string => {
|
|
167
258
|
const parts: unknown = JSON.parse(key);
|
|
168
259
|
if (!Array.isArray(parts) || parts[0] !== sourceScope) {
|
|
169
260
|
return key;
|
|
170
261
|
}
|
|
171
|
-
|
|
262
|
+
const rebound = [destinationScope, ...parts.slice(1)];
|
|
263
|
+
if (rebound.length === 5 && destinationThreadId != null) {
|
|
264
|
+
rebound[4] = destinationThreadId;
|
|
265
|
+
}
|
|
266
|
+
return JSON.stringify(rebound);
|
|
172
267
|
};
|
|
173
268
|
const config = { configurable };
|
|
174
269
|
const review = getToolApprovalReviewEvidence(config);
|
|
@@ -197,7 +292,8 @@ export function rebindToolBatchReplayScope(
|
|
|
197
292
|
export function rebindToolBatchReplayPayload(
|
|
198
293
|
payload: unknown,
|
|
199
294
|
sourceScope: string,
|
|
200
|
-
destinationScope: string
|
|
295
|
+
destinationScope: string,
|
|
296
|
+
destinationThreadId?: string
|
|
201
297
|
): unknown {
|
|
202
298
|
const state = getToolBatchReplayState(payload);
|
|
203
299
|
const value = stripRunStepResumeState(payload);
|
|
@@ -205,7 +301,12 @@ export function rebindToolBatchReplayPayload(
|
|
|
205
301
|
return payload;
|
|
206
302
|
}
|
|
207
303
|
const configurable = { [TOOL_BATCH_REPLAY_KEY]: state };
|
|
208
|
-
rebindToolBatchReplayScope(
|
|
304
|
+
rebindToolBatchReplayScope(
|
|
305
|
+
configurable,
|
|
306
|
+
sourceScope,
|
|
307
|
+
destinationScope,
|
|
308
|
+
destinationThreadId
|
|
309
|
+
);
|
|
209
310
|
const rebound = {
|
|
210
311
|
...value,
|
|
211
312
|
[TOOL_BATCH_REPLAY_KEY]: configurable[TOOL_BATCH_REPLAY_KEY],
|