@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
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { SubagentTaskClaim, SubagentTaskControlCommand, SubagentTaskControlResult, SubagentTaskSnapshot, SubagentTaskStartRequest, SubagentTaskStartResult, SubagentTaskStore } from '@/types';
|
|
2
|
+
export interface InMemorySubagentTaskStoreOptions {
|
|
3
|
+
completedTtlMs?: number;
|
|
4
|
+
maxControlMessageChars?: number;
|
|
5
|
+
maxControlsPerTask?: number;
|
|
6
|
+
maxErrorChars?: number;
|
|
7
|
+
maxResultChars?: number;
|
|
8
|
+
maxRunningPerScope?: number;
|
|
9
|
+
maxRunningTotal?: number;
|
|
10
|
+
maxTasksPerScope?: number;
|
|
11
|
+
maxTasksTotal?: number;
|
|
12
|
+
taskTimeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Bounded process-local task ownership for detached subagents. Terminal tasks
|
|
16
|
+
* keep only a bounded claimable result: the child graph, checkpoint, and full
|
|
17
|
+
* transcript are released. Hosts that need later child-chat continuation may
|
|
18
|
+
* replace this store and persist the canonical messages returned by `run`.
|
|
19
|
+
* This default deliberately makes no restart or cross-replica durability
|
|
20
|
+
* claim.
|
|
21
|
+
*/
|
|
22
|
+
export declare class InMemorySubagentTaskStore implements SubagentTaskStore {
|
|
23
|
+
private readonly buckets;
|
|
24
|
+
private readonly options;
|
|
25
|
+
private runningTasks;
|
|
26
|
+
private totalTasks;
|
|
27
|
+
constructor(options?: InMemorySubagentTaskStoreOptions);
|
|
28
|
+
start(request: SubagentTaskStartRequest): SubagentTaskStartResult;
|
|
29
|
+
get(scopeId: string, taskId: string): SubagentTaskSnapshot | undefined;
|
|
30
|
+
list(scopeId: string): SubagentTaskSnapshot[];
|
|
31
|
+
claim(scopeId: string, taskId: string): SubagentTaskClaim;
|
|
32
|
+
control(scopeId: string, taskId: string, command: SubagentTaskControlCommand): SubagentTaskControlResult;
|
|
33
|
+
private getBucket;
|
|
34
|
+
private find;
|
|
35
|
+
private makeRoom;
|
|
36
|
+
private makeGlobalRoom;
|
|
37
|
+
private sweepBucket;
|
|
38
|
+
private removeTask;
|
|
39
|
+
private dropEmptyBucket;
|
|
40
|
+
private scheduleExpiry;
|
|
41
|
+
private clearTaskExpiry;
|
|
42
|
+
private clearTaskTimeout;
|
|
43
|
+
private finishWithError;
|
|
44
|
+
private createRuntime;
|
|
45
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { BaseMessage } from '@langchain/core/messages';
|
|
2
2
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
3
3
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
4
|
-
import type { MultiAgentGraphState, HumanInTheLoopConfig, StandardGraphInput, ExecutableSubagentConfigEntry, SubagentExecutionContext, SubagentUsageSink, TokenCounter } from '@/types';
|
|
4
|
+
import type { MultiAgentGraphState, HumanInTheLoopConfig, StandardGraphInput, ExecutableSubagentConfigEntry, SubagentExecutionContext, SubagentTaskConfig, SubagentTaskRuntime, SubagentUsageSink, TokenCounter } from '@/types';
|
|
5
5
|
import type { SubagentResumeManifest, SettledSubagentToolOutput } from './SubagentReplay';
|
|
6
|
-
import type { HookRegistry } from '@/hooks';
|
|
7
6
|
import type { GraphFactory } from '@/graphs/graphFactory';
|
|
8
7
|
import type { StandardGraph } from '@/graphs/Graph';
|
|
9
|
-
import
|
|
8
|
+
import { HookRegistry } from '@/hooks';
|
|
9
|
+
import { HandlerRegistry } from '@/events';
|
|
10
10
|
export { buildChildInputs, isGraphSubagentConfig, normalizeSubagentConfigs, normalizeSubagentConfigEntries, resolveSubagentConfigs, resolveSubagentConfigEntries, } from './childGraphConfig';
|
|
11
11
|
export declare const DEFAULT_SUBAGENT_DESCRIPTION = "No task description provided";
|
|
12
12
|
export type SubagentExecuteParams = {
|
|
@@ -62,10 +62,16 @@ export type SubagentExecuteParams = {
|
|
|
62
62
|
* rather than sharing parent's host context.
|
|
63
63
|
*/
|
|
64
64
|
parentConfigurable?: Record<string, unknown>;
|
|
65
|
+
/** Dedicated hook session used by a detached task. @internal */
|
|
66
|
+
hookSessionId?: string;
|
|
67
|
+
/** Process-local task controls consumed by the child graph. @internal */
|
|
68
|
+
taskRuntime?: SubagentTaskRuntime;
|
|
65
69
|
};
|
|
66
70
|
export type SubagentExecuteResult = {
|
|
67
71
|
content: string;
|
|
68
72
|
messages: BaseMessage[];
|
|
73
|
+
/** Tagged internal failure; foreground callers retain the legacy content. */
|
|
74
|
+
error?: string;
|
|
69
75
|
};
|
|
70
76
|
/**
|
|
71
77
|
* Factory that constructs a child graph for subagent execution. Injected
|
|
@@ -121,6 +127,11 @@ export type SubagentExecutorOptions = {
|
|
|
121
127
|
/** Preferred polymorphic child constructor. The legacy standard-only
|
|
122
128
|
* factory remains required for source compatibility. */
|
|
123
129
|
createChildGraphByKind?: GraphFactory;
|
|
130
|
+
/**
|
|
131
|
+
* Captures a child-graph factory and its run-scoped host dependencies
|
|
132
|
+
* synchronously, before a detached task can outlive parent cleanup.
|
|
133
|
+
*/
|
|
134
|
+
createDetachedChildGraphFactory?: (parentHandlerRegistry: HandlerRegistry) => GraphFactory;
|
|
124
135
|
/**
|
|
125
136
|
* Parent's event handler registry. When provided, child-graph events are
|
|
126
137
|
* forwarded through this registry so hosts can:
|
|
@@ -143,6 +154,8 @@ export type SubagentExecutorOptions = {
|
|
|
143
154
|
* nested subagents report through the same sink.
|
|
144
155
|
*/
|
|
145
156
|
usageSink?: SubagentUsageSink;
|
|
157
|
+
/** Host-owned process-local task namespace for detached execution. */
|
|
158
|
+
taskConfig?: SubagentTaskConfig;
|
|
146
159
|
};
|
|
147
160
|
export declare class SubagentExecutor {
|
|
148
161
|
private readonly configs;
|
|
@@ -167,7 +180,9 @@ export declare class SubagentExecutor {
|
|
|
167
180
|
private readonly maxDepth;
|
|
168
181
|
private readonly createChildGraph;
|
|
169
182
|
private readonly createChildGraphByKind?;
|
|
183
|
+
private readonly createDetachedChildGraphFactory?;
|
|
170
184
|
private readonly usageSink?;
|
|
185
|
+
private readonly taskConfig?;
|
|
171
186
|
private readonly executions;
|
|
172
187
|
private replayCheckpointWorkflow?;
|
|
173
188
|
private readonly resolveParentHandlerRegistry?;
|
|
@@ -181,6 +196,14 @@ export declare class SubagentExecutor {
|
|
|
181
196
|
private composeChildSignal;
|
|
182
197
|
/** Snapshot of the parent's registry at the moment a subagent is dispatched. */
|
|
183
198
|
private getParentHandlerRegistry;
|
|
199
|
+
/**
|
|
200
|
+
* Starts one independently-owned executor behind the configured task store.
|
|
201
|
+
* The parent ToolNode receives the handle synchronously; the detached clone
|
|
202
|
+
* is not registered on the parent graph, so end-of-turn cleanup cannot
|
|
203
|
+
* invalidate or clear a child that intentionally outlives that turn.
|
|
204
|
+
*/
|
|
205
|
+
executeInBackground(params: SubagentExecuteParams): string;
|
|
206
|
+
private executeDetached;
|
|
184
207
|
private bindExecutionDefinition;
|
|
185
208
|
/** Resolve one lazy descriptor per stable child execution. Concurrent
|
|
186
209
|
* duplicate dispatches share the same in-flight resolution; HITL re-entry
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { DEFAULT_SUBAGENT_DESCRIPTION, SubagentExecutor, filterSubagentResult, filterGraphSubagentResult, isGraphSubagentConfig, normalizeSubagentConfigs, normalizeSubagentConfigEntries, resolveSubagentConfigs, resolveSubagentConfigEntries, buildChildInputs, summarizeEvent, } from './SubagentExecutor';
|
|
2
2
|
export type { SubagentExecuteParams, SubagentExecuteResult, SubagentExecutorOptions, ChildGraphFactory, } from './SubagentExecutor';
|
|
3
|
+
export { InMemorySubagentTaskStore } from './InMemorySubagentTaskStore';
|
|
4
|
+
export type { InMemorySubagentTaskStoreOptions } from './InMemorySubagentTaskStore';
|
|
@@ -9,6 +9,7 @@ import type { RunStep, RunStepDeltaEvent, RunStepResumeState, RunStepClosedEvent
|
|
|
9
9
|
import type { ToolMap, ToolSessionMap, ToolEndEvent, GenericTool, LCTool, ToolExecuteBatchRequest } from '@/types/tools';
|
|
10
10
|
import type { TokenCounter, StreamLimits, StreamPreemption, TokenBudgetBreakdown } from '@/types/run';
|
|
11
11
|
import type { Providers, Callback, GraphNodeKeys } from '@/common';
|
|
12
|
+
import type { SubagentTaskConfig } from '@/types/subagentTasks';
|
|
12
13
|
import type { StandardGraph, MultiAgentGraph } from '@/graphs';
|
|
13
14
|
import type { ClientOptions } from '@/types/llm';
|
|
14
15
|
/** Interface for bound model with stream and invoke methods */
|
|
@@ -239,6 +240,12 @@ export type StandardGraphInput = {
|
|
|
239
240
|
* they already flow through the registry's `CHAT_MODEL_END` handler.
|
|
240
241
|
*/
|
|
241
242
|
subagentUsageSink?: SubagentUsageSink;
|
|
243
|
+
/**
|
|
244
|
+
* Optional host-owned process-local task namespace for detached subagents.
|
|
245
|
+
* Presence enables `run_in_background` on the subagent tool. Child graphs
|
|
246
|
+
* do not inherit it, keeping background nesting disabled for the MVP.
|
|
247
|
+
*/
|
|
248
|
+
subagentTasks?: SubagentTaskConfig;
|
|
242
249
|
/**
|
|
243
250
|
* True when this graph IS a subagent child run (set by `SubagentExecutor`
|
|
244
251
|
* when it constructs the child graph). Drives the hook-input `agentId`
|
|
@@ -250,10 +257,10 @@ export type StandardGraphInput = {
|
|
|
250
257
|
*/
|
|
251
258
|
subagentScope?: boolean;
|
|
252
259
|
/**
|
|
253
|
-
* Cooperative preemption, forwarded from `RunConfig.preemption`.
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
260
|
+
* Cooperative preemption, forwarded from `RunConfig.preemption`. Ordinary
|
|
261
|
+
* child graphs do not inherit it. Detached subagent tasks may receive their
|
|
262
|
+
* own internal parent-control source so an interrupt can reuse the same
|
|
263
|
+
* provider-safe sealing path without targeting the top-level conversation.
|
|
257
264
|
*/
|
|
258
265
|
preemption?: StreamPreemption;
|
|
259
266
|
/**
|
|
@@ -4,6 +4,7 @@ import type { BaseMessage } from '@langchain/core/messages';
|
|
|
4
4
|
import type { StructuredTool } from '@langchain/core/tools';
|
|
5
5
|
import type * as z from 'zod';
|
|
6
6
|
import type { ToolSessionMap, ToolExecutionConfig, ToolOutputReferencesConfig, EagerEventToolExecutionConfig } from '@/types/tools';
|
|
7
|
+
import type { SubagentTaskConfig } from '@/types/subagentTasks';
|
|
7
8
|
import type { HumanInTheLoopConfig } from '@/types/hitl';
|
|
8
9
|
import type { HookRegistry } from '@/hooks';
|
|
9
10
|
import type * as s from '@/types/stream';
|
|
@@ -215,6 +216,11 @@ export type RunConfig = {
|
|
|
215
216
|
* the registered `CHAT_MODEL_END` handler as usual.
|
|
216
217
|
*/
|
|
217
218
|
subagentUsageSink?: g.SubagentUsageSink;
|
|
219
|
+
/**
|
|
220
|
+
* Trusted process-local task namespace for detached subagent execution.
|
|
221
|
+
* Omit to preserve foreground-only subagent behavior.
|
|
222
|
+
*/
|
|
223
|
+
subagentTasks?: SubagentTaskConfig;
|
|
218
224
|
/**
|
|
219
225
|
* Pre-constructed hook registry for this run. Hooks fire at lifecycle
|
|
220
226
|
* points in `processStream` (RunStart, UserPromptSubmit, Stop,
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
2
|
+
import type { SubagentUpdateEvent } from './graph';
|
|
3
|
+
import type { InjectedMessage } from './tools';
|
|
4
|
+
/** Terminal and in-flight states for a detached subagent task. */
|
|
5
|
+
export type SubagentTaskStatus = 'running' | 'completed' | 'error' | 'cancelled';
|
|
6
|
+
/** Where a pending parent message may enter the child run. */
|
|
7
|
+
export type SubagentTaskBoundary = 'preempt' | 'tool' | 'turn';
|
|
8
|
+
/** Parent-to-child control operations accepted while a task is running. */
|
|
9
|
+
export type SubagentTaskControlCommand = {
|
|
10
|
+
action: 'steer' | 'queue' | 'interrupt';
|
|
11
|
+
message: string;
|
|
12
|
+
} | {
|
|
13
|
+
action: 'cancel';
|
|
14
|
+
} | {
|
|
15
|
+
action: 'cancel_message';
|
|
16
|
+
controlId: string;
|
|
17
|
+
};
|
|
18
|
+
/** Small, payload-free progress view safe to retain between parent turns. */
|
|
19
|
+
export interface SubagentTaskProgress {
|
|
20
|
+
phase: SubagentUpdateEvent['phase'];
|
|
21
|
+
at: number;
|
|
22
|
+
eventCount: number;
|
|
23
|
+
label?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Read-only task metadata. Results are exposed only through `claim`. */
|
|
26
|
+
export interface SubagentTaskSnapshot {
|
|
27
|
+
/** Handle for this child-conversation execution within its trusted scope. */
|
|
28
|
+
taskId: string;
|
|
29
|
+
subagentType: string;
|
|
30
|
+
status: SubagentTaskStatus;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
updatedAt: number;
|
|
33
|
+
resultAvailable: boolean;
|
|
34
|
+
resultClaimed: boolean;
|
|
35
|
+
pendingControls: number;
|
|
36
|
+
progress?: SubagentTaskProgress;
|
|
37
|
+
error?: string;
|
|
38
|
+
}
|
|
39
|
+
export type SubagentTaskClaim = {
|
|
40
|
+
status: 'running';
|
|
41
|
+
task: SubagentTaskSnapshot;
|
|
42
|
+
} | {
|
|
43
|
+
status: 'completed';
|
|
44
|
+
task: SubagentTaskSnapshot;
|
|
45
|
+
result: string;
|
|
46
|
+
} | {
|
|
47
|
+
status: 'error';
|
|
48
|
+
task: SubagentTaskSnapshot;
|
|
49
|
+
error: string;
|
|
50
|
+
} | {
|
|
51
|
+
status: 'cancelled';
|
|
52
|
+
task: SubagentTaskSnapshot;
|
|
53
|
+
error: string;
|
|
54
|
+
} | {
|
|
55
|
+
status: 'claimed';
|
|
56
|
+
task: SubagentTaskSnapshot;
|
|
57
|
+
} | {
|
|
58
|
+
status: 'not_found';
|
|
59
|
+
};
|
|
60
|
+
export type SubagentTaskControlResult = {
|
|
61
|
+
status: 'accepted';
|
|
62
|
+
task: SubagentTaskSnapshot;
|
|
63
|
+
controlId?: string;
|
|
64
|
+
} | {
|
|
65
|
+
status: 'cancelled';
|
|
66
|
+
task: SubagentTaskSnapshot;
|
|
67
|
+
} | {
|
|
68
|
+
status: 'not_running';
|
|
69
|
+
task: SubagentTaskSnapshot;
|
|
70
|
+
} | {
|
|
71
|
+
status: 'not_found';
|
|
72
|
+
} | {
|
|
73
|
+
status: 'control_not_found';
|
|
74
|
+
task: SubagentTaskSnapshot;
|
|
75
|
+
} | {
|
|
76
|
+
status: 'invalid';
|
|
77
|
+
message: string;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Child-side view supplied to one detached execution. It intentionally owns
|
|
81
|
+
* only cancellation, bounded message drains, and payload-free progress — no
|
|
82
|
+
* request/response object or host transport can leak into retained task state.
|
|
83
|
+
*/
|
|
84
|
+
export interface SubagentTaskRuntime {
|
|
85
|
+
readonly taskId: string;
|
|
86
|
+
readonly signal: AbortSignal;
|
|
87
|
+
shouldPreempt(): boolean;
|
|
88
|
+
drain(boundary: SubagentTaskBoundary): InjectedMessage[];
|
|
89
|
+
closeTurn(): {
|
|
90
|
+
closed: boolean;
|
|
91
|
+
messages: InjectedMessage[];
|
|
92
|
+
};
|
|
93
|
+
reportProgress(event: SubagentUpdateEvent): void;
|
|
94
|
+
}
|
|
95
|
+
export interface SubagentTaskStartRequest {
|
|
96
|
+
scopeId: string;
|
|
97
|
+
idempotencyKey: string;
|
|
98
|
+
/** Stable hash of model-writable inputs used to reject conflicting replays. */
|
|
99
|
+
requestFingerprint?: string;
|
|
100
|
+
subagentType: string;
|
|
101
|
+
/**
|
|
102
|
+
* Starts one ephemeral execution lease. The canonical child transcript is
|
|
103
|
+
* returned so a host-owned store may persist it for a later fresh run;
|
|
104
|
+
* retaining a graph/checkpoint after terminal completion is unnecessary.
|
|
105
|
+
*/
|
|
106
|
+
run(runtime: SubagentTaskRuntime): Promise<{
|
|
107
|
+
content: string;
|
|
108
|
+
messages?: BaseMessage[];
|
|
109
|
+
}>;
|
|
110
|
+
}
|
|
111
|
+
export type SubagentTaskStartResult = {
|
|
112
|
+
accepted: true;
|
|
113
|
+
isNew: boolean;
|
|
114
|
+
task: SubagentTaskSnapshot;
|
|
115
|
+
} | {
|
|
116
|
+
accepted: false;
|
|
117
|
+
reason: 'capacity';
|
|
118
|
+
} | {
|
|
119
|
+
accepted: false;
|
|
120
|
+
reason: 'conflict';
|
|
121
|
+
task: SubagentTaskSnapshot;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Host-replaceable store contract used by the SDK's subagent tool. The store
|
|
125
|
+
* should normally outlive individual `Run` instances. Durable hosts may
|
|
126
|
+
* persist the transcript returned by `run` under the task/conversation
|
|
127
|
+
* lineage and start a fresh execution for a later turn.
|
|
128
|
+
*/
|
|
129
|
+
export interface SubagentTaskStore {
|
|
130
|
+
start(request: SubagentTaskStartRequest): SubagentTaskStartResult;
|
|
131
|
+
get(scopeId: string, taskId: string): SubagentTaskSnapshot | undefined;
|
|
132
|
+
list(scopeId: string): SubagentTaskSnapshot[];
|
|
133
|
+
claim(scopeId: string, taskId: string): SubagentTaskClaim;
|
|
134
|
+
control(scopeId: string, taskId: string, command: SubagentTaskControlCommand): SubagentTaskControlResult;
|
|
135
|
+
}
|
|
136
|
+
/** Trusted, host-selected task namespace. It is never model-writable. */
|
|
137
|
+
export interface SubagentTaskConfig {
|
|
138
|
+
store: SubagentTaskStore;
|
|
139
|
+
scopeId: string;
|
|
140
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@librechat/agents",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.4",
|
|
4
4
|
"reova": {
|
|
5
5
|
"enabled": true,
|
|
6
6
|
"endpoint": "https://telemetry.reo.dev/data"
|
|
@@ -218,7 +218,7 @@
|
|
|
218
218
|
"label:rescore": "node ./src/scripts/activity-labels/rescore.cjs"
|
|
219
219
|
},
|
|
220
220
|
"overrides": {
|
|
221
|
-
"@langchain/openai": "
|
|
221
|
+
"@langchain/openai": "$@langchain/openai",
|
|
222
222
|
"@browserbasehq/stagehand": {
|
|
223
223
|
"openai": "$openai"
|
|
224
224
|
},
|
|
@@ -234,7 +234,7 @@
|
|
|
234
234
|
"@aws-sdk/client-bedrock-runtime": "^3.1075.0",
|
|
235
235
|
"@langchain/anthropic": "1.5.2",
|
|
236
236
|
"@langchain/aws": "^1.4.2",
|
|
237
|
-
"@langchain/core": "
|
|
237
|
+
"@langchain/core": "1.2.8",
|
|
238
238
|
"@langchain/deepseek": "^1.1.3",
|
|
239
239
|
"@langchain/google-common": "2.2.0",
|
|
240
240
|
"@langchain/google-gauth": "2.2.0",
|
|
@@ -242,7 +242,7 @@
|
|
|
242
242
|
"@langchain/google-vertexai": "2.2.0",
|
|
243
243
|
"@langchain/langgraph": "1.4.8",
|
|
244
244
|
"@langchain/mistralai": "^1.2.0",
|
|
245
|
-
"@langchain/openai": "1.5.
|
|
245
|
+
"@langchain/openai": "1.5.8",
|
|
246
246
|
"@langchain/textsplitters": "^1.0.1",
|
|
247
247
|
"@langchain/xai": "^1.4.3",
|
|
248
248
|
"@langfuse/core": "^5.4.1",
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -1312,6 +1312,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1312
1312
|
subagentUsageSink?: t.SubagentUsageSink;
|
|
1313
1313
|
/** See {@link t.StandardGraphInput.subagentScope}. */
|
|
1314
1314
|
subagentScope: boolean;
|
|
1315
|
+
/** See {@link t.StandardGraphInput.subagentTasks}. */
|
|
1316
|
+
subagentTasks: t.SubagentTaskConfig | undefined;
|
|
1315
1317
|
/** See {@link t.StandardGraphInput.subagentExecutionContext}. */
|
|
1316
1318
|
private readonly subagentExecutionContext?: t.SubagentExecutionContext;
|
|
1317
1319
|
/** See {@link t.StandardGraphInput.preemption}. */
|
|
@@ -1445,6 +1447,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1445
1447
|
indexTokenCountMap,
|
|
1446
1448
|
calibrationRatio,
|
|
1447
1449
|
subagentUsageSink,
|
|
1450
|
+
subagentTasks,
|
|
1448
1451
|
subagentScope,
|
|
1449
1452
|
subagentExecutionContext,
|
|
1450
1453
|
preemption,
|
|
@@ -1469,6 +1472,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1469
1472
|
this.signal = signal;
|
|
1470
1473
|
this.langfuse = langfuse;
|
|
1471
1474
|
this.subagentUsageSink = subagentUsageSink;
|
|
1475
|
+
this.subagentTasks = subagentTasks;
|
|
1472
1476
|
this.subagentScope = subagentScope === true;
|
|
1473
1477
|
this.subagentExecutionContext = subagentExecutionContext;
|
|
1474
1478
|
this.preemption = preemption;
|
|
@@ -1695,8 +1699,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1695
1699
|
* budget is taken by {@link claimPreemptSeal} once the accumulated chunk is
|
|
1696
1700
|
* known to be safe, so a chunk that cannot seal never spends budget.
|
|
1697
1701
|
*
|
|
1698
|
-
*
|
|
1699
|
-
*
|
|
1702
|
+
* Ordinary subagent scopes never receive `preemption`. A detached child may
|
|
1703
|
+
* receive a dedicated parent-control preemption source, in which case the
|
|
1704
|
+
* same provider-safe seal path is intentionally reused inside that child.
|
|
1700
1705
|
*/
|
|
1701
1706
|
/** Internal seal preconditions only — no host callback, no side effects. */
|
|
1702
1707
|
private canClaimPreemptSeal(): boolean {
|
|
@@ -1710,7 +1715,6 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1710
1715
|
const runId =
|
|
1711
1716
|
(this.config?.configurable?.run_id as string | undefined) ?? this.runId;
|
|
1712
1717
|
return (
|
|
1713
|
-
!this.subagentScope &&
|
|
1714
1718
|
this.preemption != null &&
|
|
1715
1719
|
!this.preemptSealInFlight &&
|
|
1716
1720
|
this.preemptSealBudgetUsed < resolveMaxSeals(this.preemption.maxSeals) &&
|
|
@@ -2024,12 +2028,36 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2024
2028
|
GraphEvents.ON_RUN_STEP_CLOSED
|
|
2025
2029
|
);
|
|
2026
2030
|
if (handler) {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2031
|
+
/**
|
|
2032
|
+
* Isolated, unlike the other dual-dispatch sites, because this one
|
|
2033
|
+
* reports state that is already committed: the step was stamped
|
|
2034
|
+
* terminal and untracked above, and nothing a failed delivery can do
|
|
2035
|
+
* will undo that. Propagating instead costs two things.
|
|
2036
|
+
*
|
|
2037
|
+
* First, it fails an entire run over an observational event —
|
|
2038
|
+
* `closeOpenMessageStep` awaits this inside the stream loop on every
|
|
2039
|
+
* CHAT_MODEL_END, where a rejection sets `streamThrew` and fires the
|
|
2040
|
+
* StopFailure hooks for a response that was fully delivered.
|
|
2041
|
+
*
|
|
2042
|
+
* Second, and worse, it skips the secondary custom-event dispatch
|
|
2043
|
+
* below. That channel exists precisely as the fallback for when the
|
|
2044
|
+
* primary path does not deliver, so letting the primary's failure
|
|
2045
|
+
* suppress it removes the redundancy exactly when it is needed.
|
|
2046
|
+
*
|
|
2047
|
+
* `closeUnfinishedRunSteps` and `dispatchRunStep` already wrap their
|
|
2048
|
+
* own calls for the same reason; this closes the gap inside, which
|
|
2049
|
+
* those wrappers cannot reach.
|
|
2050
|
+
*/
|
|
2051
|
+
try {
|
|
2052
|
+
await handler.handle(
|
|
2053
|
+
GraphEvents.ON_RUN_STEP_CLOSED,
|
|
2054
|
+
closedEvent,
|
|
2055
|
+
options?.metadata,
|
|
2056
|
+
this
|
|
2057
|
+
);
|
|
2058
|
+
} catch (_e) {
|
|
2059
|
+
/** Host delivery failure must not fail the run or block the echo */
|
|
2060
|
+
}
|
|
2033
2061
|
this.handlerDispatchedStepIds.add(stepId);
|
|
2034
2062
|
}
|
|
2035
2063
|
const unmarkHandlerDispatchedEvent = handler
|
|
@@ -4745,19 +4773,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4745
4773
|
}
|
|
4746
4774
|
const getParentHandlerRegistry = (): HandlerRegistry | undefined =>
|
|
4747
4775
|
this.handlerRegistry ?? this.parentToolHandlerRegistry;
|
|
4748
|
-
const
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
const childHandlerRegistry = createChildHandlerRegistry(
|
|
4755
|
-
getParentHandlerRegistry()
|
|
4756
|
-
);
|
|
4757
|
-
// Pure execution-ordering hint (unlike `humanInTheLoop`). It only
|
|
4758
|
-
// reorders tools already in the child's direct group; it does not
|
|
4759
|
-
// force a schema-only event tool onto the direct execution path.
|
|
4760
|
-
applyGraphRuntimeConfig(childGraph, {
|
|
4776
|
+
const snapshotChildGraphFactory = (
|
|
4777
|
+
parentHandlerRegistry: HandlerRegistry | undefined
|
|
4778
|
+
): GraphFactory => {
|
|
4779
|
+
const graphFactory = this.graphFactory;
|
|
4780
|
+
const subagentModelOverride = this.subagentModelOverride;
|
|
4781
|
+
const runtimeConfig = {
|
|
4761
4782
|
hookRegistry: this.hookRegistry,
|
|
4762
4783
|
humanInTheLoop: this.humanInTheLoop,
|
|
4763
4784
|
toolOutputReferences: this.toolOutputReferences,
|
|
@@ -4765,18 +4786,33 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4765
4786
|
codeSessionToolNames: this.codeSessionToolNames,
|
|
4766
4787
|
interruptingToolNames: this.interruptingToolNames,
|
|
4767
4788
|
toolExecution: this.toolExecution,
|
|
4768
|
-
}
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
childHandlerRegistry
|
|
4777
|
-
|
|
4778
|
-
|
|
4789
|
+
};
|
|
4790
|
+
const checkpointer = this.compileOptions?.checkpointer;
|
|
4791
|
+
return (request): StandardGraph => {
|
|
4792
|
+
const childGraph = graphFactory(request);
|
|
4793
|
+
if (subagentModelOverride != null) {
|
|
4794
|
+
childGraph.overrideModel = subagentModelOverride;
|
|
4795
|
+
childGraph.setSubagentModelOverride(subagentModelOverride);
|
|
4796
|
+
}
|
|
4797
|
+
const childHandlerRegistry = createChildHandlerRegistry(
|
|
4798
|
+
parentHandlerRegistry
|
|
4799
|
+
);
|
|
4800
|
+
// Pure execution-ordering hint (unlike `humanInTheLoop`). It only
|
|
4801
|
+
// reorders tools already in the child's direct group; it does not
|
|
4802
|
+
// force a schema-only event tool onto the direct execution path.
|
|
4803
|
+
applyGraphRuntimeConfig(childGraph, runtimeConfig);
|
|
4804
|
+
if (runtimeConfig.humanInTheLoop?.enabled === true) {
|
|
4805
|
+
childGraph.compileOptions = { checkpointer };
|
|
4806
|
+
}
|
|
4807
|
+
childGraph.parentToolHandlerRegistry = childHandlerRegistry;
|
|
4808
|
+
childGraph.eventToolExecutionAvailable =
|
|
4809
|
+
childHandlerRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) !=
|
|
4810
|
+
null;
|
|
4811
|
+
return childGraph;
|
|
4812
|
+
};
|
|
4779
4813
|
};
|
|
4814
|
+
const createConfiguredChildGraph: GraphFactory = (request) =>
|
|
4815
|
+
snapshotChildGraphFactory(getParentHandlerRegistry())(request);
|
|
4780
4816
|
const executor = new SubagentExecutor({
|
|
4781
4817
|
configs: new Map(
|
|
4782
4818
|
executableConfigs.map((config) => [config.type, config])
|
|
@@ -4796,6 +4832,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4796
4832
|
langfuse: this.langfuse,
|
|
4797
4833
|
tokenCounter: agentContext.tokenCounter,
|
|
4798
4834
|
usageSink: this.subagentUsageSink,
|
|
4835
|
+
taskConfig: this.subagentTasks,
|
|
4799
4836
|
streamLimits: this.streamLimits,
|
|
4800
4837
|
humanInTheLoop: this.humanInTheLoop,
|
|
4801
4838
|
checkpointer: this.compileOptions?.checkpointer,
|
|
@@ -4806,6 +4843,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4806
4843
|
input,
|
|
4807
4844
|
}),
|
|
4808
4845
|
createChildGraphByKind: createConfiguredChildGraph,
|
|
4846
|
+
createDetachedChildGraphFactory: (
|
|
4847
|
+
parentHandlerRegistry
|
|
4848
|
+
): GraphFactory =>
|
|
4849
|
+
snapshotChildGraphFactory(parentHandlerRegistry),
|
|
4809
4850
|
});
|
|
4810
4851
|
this.registerSubagentExecutor(executor);
|
|
4811
4852
|
|
|
@@ -4813,6 +4854,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4813
4854
|
const input = rawInput as {
|
|
4814
4855
|
description?: string;
|
|
4815
4856
|
subagent_type?: string;
|
|
4857
|
+
run_in_background?: boolean;
|
|
4816
4858
|
};
|
|
4817
4859
|
const description =
|
|
4818
4860
|
typeof input.description === 'string' &&
|
|
@@ -4845,7 +4887,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4845
4887
|
const batchScope = config.configurable?.[
|
|
4846
4888
|
RUN_BREAKER_SCOPE_CONFIG_KEY
|
|
4847
4889
|
] as RunBreakerScope | undefined;
|
|
4848
|
-
const
|
|
4890
|
+
const executeParams = {
|
|
4849
4891
|
description,
|
|
4850
4892
|
subagentType,
|
|
4851
4893
|
threadId,
|
|
@@ -4861,9 +4903,15 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4861
4903
|
parentConfigurable: config.configurable as
|
|
4862
4904
|
| Record<string, unknown>
|
|
4863
4905
|
| undefined,
|
|
4864
|
-
}
|
|
4906
|
+
};
|
|
4907
|
+
if (input.run_in_background === true) {
|
|
4908
|
+
return executor.executeInBackground(executeParams);
|
|
4909
|
+
}
|
|
4910
|
+
const result = await executor.execute(executeParams);
|
|
4865
4911
|
return result.content;
|
|
4866
|
-
}, buildSubagentToolParams(executableConfigs
|
|
4912
|
+
}, buildSubagentToolParams(executableConfigs, {
|
|
4913
|
+
background: this.subagentTasks != null,
|
|
4914
|
+
}));
|
|
4867
4915
|
const replayableSubagentTool = subagentTool as typeof subagentTool &
|
|
4868
4916
|
ReplayableSubagentTool;
|
|
4869
4917
|
replayableSubagentTool[SUBAGENT_REPLAY_CONTROLLER] = {
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
ToolApprovalReplaySnapshot,
|
|
7
7
|
AggregatedHookResult,
|
|
8
8
|
} from './types';
|
|
9
|
+
import { HOOK_EVENTS } from './types';
|
|
9
10
|
|
|
10
11
|
function serializeApprovalKey(key: ToolApprovalReplayKey): string {
|
|
11
12
|
return JSON.stringify([key.executionScope, key.agentId, key.toolUseId]);
|
|
@@ -227,6 +228,23 @@ export class HookRegistry {
|
|
|
227
228
|
}
|
|
228
229
|
}
|
|
229
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Takes an isolated policy snapshot for work that may outlive the source
|
|
233
|
+
* run. Global and source-session matchers become global to the returned
|
|
234
|
+
* task-local registry, so parent cleanup and one-shot hook consumption
|
|
235
|
+
* cannot mutate the detached child (or vice versa). Runtime halt signals
|
|
236
|
+
* and pending approvals are intentionally not copied.
|
|
237
|
+
*/
|
|
238
|
+
forkSession(sourceSessionId: string): HookRegistry {
|
|
239
|
+
const fork = new HookRegistry();
|
|
240
|
+
for (const event of HOOK_EVENTS) {
|
|
241
|
+
for (const matcher of this.getMatchers(event, sourceSessionId)) {
|
|
242
|
+
fork.register(event, matcher);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return fork;
|
|
246
|
+
}
|
|
247
|
+
|
|
230
248
|
getPendingToolApproval(
|
|
231
249
|
sessionId: string,
|
|
232
250
|
key: ToolApprovalReplayKey
|