langflower 0.0.9 → 0.0.10
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/package.json +2 -2
- package/ui-dist/index.html +2 -2
- package/ui-dist/main-Z7CPFXFA.js +313 -0
- package/ui-dist/styles-2TMDAUD7.css +1 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-reducer.js +0 -12
- package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-types.d.ts +2 -7
- package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-types.js +1 -1
- package/vendor/common-nodes/dist/ai/features/llm-loop/run-agent-loop.d.ts +0 -3
- package/vendor/common-nodes/dist/ai/features/llm-loop/run-agent-loop.js +22 -36
- package/vendor/common-nodes/dist/ai/features/llm-loop/run-llm-loop.d.ts +1 -7
- package/vendor/common-nodes/dist/ai/features/llm-loop/run-llm-loop.js +12 -35
- package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.d.ts +3 -10
- package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.js +9 -14
- package/vendor/common-nodes/dist/ai/features/path-choice/run-reactive-path-choice-loop.d.ts +0 -3
- package/vendor/common-nodes/dist/ai/features/path-choice/run-reactive-path-choice-loop.js +2 -15
- package/vendor/common-nodes/dist/ai/features/sub-agent-protocol.d.ts +1 -0
- package/vendor/common-nodes/dist/ai/features/sub-agent-protocol.js +1 -0
- package/vendor/common-nodes/dist/ai/features/ui-schema/llm-recovery-ui-schema.d.ts +1 -1
- package/vendor/common-nodes/dist/ai/features/ui-schema/llm-recovery-ui-schema.js +1 -1
- package/vendor/common-nodes/dist/ai/features/wait-for-subagent-result.d.ts +1 -0
- package/vendor/common-nodes/dist/ai/features/wait-for-subagent-result.js +1 -0
- package/vendor/common-nodes/dist/ai/nodes/critique/node.d.ts +3 -3
- package/vendor/common-nodes/dist/ai/nodes/critique/node.js +5 -18
- package/vendor/common-nodes/dist/ai/nodes/fake-llm/node.d.ts +3 -3
- package/vendor/common-nodes/dist/ai/nodes/fake-llm/node.js +3 -10
- package/vendor/common-nodes/dist/ai/nodes/openai-llm/node.d.ts +3 -3
- package/vendor/common-nodes/dist/ai/nodes/openai-llm/node.js +2 -9
- package/vendor/common-nodes/dist/ai/nodes/review/node.d.ts +3 -3
- package/vendor/common-nodes/dist/ai/nodes/review/node.js +5 -18
- package/vendor/common-nodes/dist/ai/nodes/sub-agent/node.d.ts +5 -5
- package/vendor/common-nodes/dist/ai/nodes/sub-agent/node.js +211 -96
- package/vendor/common-nodes/dist/catalog.js +2 -0
- package/vendor/common-nodes/dist/mcp/mcp-http/node.d.ts +4 -4
- package/vendor/common-nodes/dist/mcp/mcp-http/node.js +9 -10
- package/vendor/common-nodes/dist/mcp/mcp-stdio/node.d.ts +4 -4
- package/vendor/common-nodes/dist/mcp/mcp-stdio/node.js +9 -10
- package/vendor/common-nodes/dist/tools/collect-agent-tool-handles.d.ts +4 -4
- package/vendor/common-nodes/dist/tools/collect-agent-tool-handles.js +3 -11
- package/vendor/common-nodes/dist/tools/inventory-tool-round.d.ts +0 -10
- package/vendor/common-nodes/dist/tools/inventory-tool-round.js +0 -84
- package/vendor/common-nodes/dist/tools/tool-collection/node.d.ts +20 -0
- package/vendor/common-nodes/dist/tools/tool-collection/node.js +42 -0
- package/vendor/common-nodes/package.json +1 -5
- package/vendor/compiler/package.json +1 -1
- package/vendor/node-sdk/dist/node-factory/define-llm-node/default-llm-ports.d.ts +3 -7
- package/vendor/node-sdk/dist/node-factory/define-llm-node/default-llm-ports.js +4 -44
- package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.d.ts +3 -5
- package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.js +3 -5
- package/vendor/node-sdk/dist/node-factory/define-llm-node/llm-inventory-wire.d.ts +1 -0
- package/vendor/node-sdk/dist/node-factory/define-llm-node/llm-inventory-wire.js +1 -0
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.d.ts +3 -4
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.js +1 -1
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/types.d.ts +2 -4
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/ui-schema-inference.d.ts +1 -1
- package/vendor/node-sdk/package.json +1 -1
- package/vendor/runtime/dist/port-feed-override.d.ts +13 -0
- package/vendor/runtime/dist/port-feed-override.js +21 -0
- package/vendor/runtime/dist/port-signal-from-response.d.ts +15 -0
- package/vendor/runtime/dist/port-signal-from-response.js +48 -0
- package/vendor/runtime/dist/runtime-runner.d.ts +8 -3
- package/vendor/runtime/dist/runtime-runner.js +102 -81
- package/vendor/runtime/dist/runtime.d.ts +3 -2
- package/vendor/runtime/dist/runtime.js +1 -1
- package/vendor/runtime/dist/testing/workflows/workflow-events.d.ts +2 -0
- package/vendor/runtime/dist/testing/workflows/workflow-events.js +7 -5
- package/vendor/runtime/dist/types.d.ts +25 -8
- package/vendor/runtime/dist/types.js +3 -0
- package/vendor/runtime/package.json +1 -1
- package/vendor/server/dist/bridge/build-execution-context.js +3 -3
- package/vendor/server/dist/bridge/get-live-wired-tools.d.ts +3 -2
- package/vendor/server/dist/bridge/get-live-wired-tools.js +8 -5
- package/vendor/server/dist/bridge/wire-runner-handlers.js +2 -2
- package/vendor/server/dist/bridge/wire-workflow-handlers.js +23 -9
- package/vendor/server/dist/checkpoint/run-checkpoint-session.js +3 -3
- package/vendor/server/dist/session/reset-session-execution-feed.d.ts +6 -0
- package/vendor/server/dist/session/reset-session-execution-feed.js +8 -0
- package/vendor/server/skeleton/nodes/my-nodes/package.json +1 -1
- package/vendor/server/skeleton/skills/langflower-helper/SKILL.md +36 -22
- package/vendor/server/skeleton/skills/langflower-helper/architecture.md +7 -1
- package/vendor/server/skeleton/skills/langflower-helper/layout.md +3 -2
- package/vendor/server/skeleton/skills/langflower-workflow-writer/SKILL.md +12 -8
- package/vendor/server/skeleton/workflows/kb-create.json +15 -43
- package/vendor/server/skeleton/workflows/kb-navigate.json +8 -22
- package/vendor/server/skeleton/workflows/simple-coder.json +18 -46
- package/vendor/server/skeleton/workflows/starter.json +8 -22
- package/vendor/shared/dist/execution/derive-run-settle-outcome.js +2 -2
- package/vendor/shared/dist/execution/derive-run-settle-outcome.test.js +11 -8
- package/vendor/shared/dist/langflower-ws-waits.d.ts +3 -1
- package/vendor/shared/dist/langflower-ws-waits.js +2 -2
- package/ui-dist/main-2JJGCNTD.js +0 -313
- package/ui-dist/styles-EOXODOI7.css +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { defineLlmNode, isSteerControlPayload, toLlmRecoveryPortValue, } from '@langflower/node-sdk/llm';
|
|
2
|
-
import {
|
|
2
|
+
import { TOOL_HANDLE_WIRE_TYPE, } from '@langflower/node-sdk';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { catchError, concat, filter, firstValueFrom, map, mergeMap, of, Subject, switchMap, take, tap, timeout, TimeoutError, } from 'rxjs';
|
|
3
5
|
import { llmPanelUiSchema } from '../../features/ui-schema/llm-panel-ui-schema.js';
|
|
4
6
|
import { llmCompactionUiSchema } from '../../features/ui-schema/llm-compaction-ui-schema.js';
|
|
5
7
|
import { llmRecoveryUiSchema } from '../../features/ui-schema/llm-recovery-ui-schema.js';
|
|
6
8
|
import { AGENT_MAX_ITERATIONS_CAP, DEFAULT_AGENT_MAX_ITERATIONS, normalizeMaxIterations, } from '../../features/prompt/normalize-max-iterations.js';
|
|
7
|
-
import { appendToolInventory,
|
|
9
|
+
import { appendToolInventory, demuxByKind, } from '../../features/llm-session/llm-session-shell.js';
|
|
8
10
|
import { runAgentLoop, } from '../../features/llm-loop/run-agent-loop.js';
|
|
9
|
-
import { waitForSubagentResult } from '../../features/wait-for-subagent-result.js';
|
|
10
|
-
import { flattenSubAgentRegistrations, isSubAgentSpawnPayload, SUBAGENT_REGISTRATION_WIRE_TYPE, SUBAGENT_RESULT_WIRE_TYPE, SUBAGENT_SPAWN_WIRE_TYPE, } from '../../features/sub-agent-protocol.js';
|
|
11
11
|
import { collectAgentToolHandles } from '../../../tools/collect-agent-tool-handles.js';
|
|
12
12
|
import { parseLlmRolePreset, resolveEffectiveSkillId, } from '../../features/llm-role-preset.js';
|
|
13
13
|
import { resolveChatProviderModel } from '../../features/prompt/resolve-chat-provider-model.js';
|
|
@@ -24,23 +24,59 @@ const normalizeSkillIds = (raw) => {
|
|
|
24
24
|
.map((entry) => String(entry ?? '').trim())
|
|
25
25
|
.filter((entry) => entry.length > 0);
|
|
26
26
|
};
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const formatSpecialistHandleName = (displayName) => `${displayName}(subagent)`;
|
|
28
|
+
const slugToolId = (name, nodeId) => {
|
|
29
|
+
const slug = (raw) => raw
|
|
30
|
+
.replace(/[^a-zA-Z0-9_-]+/g, '_')
|
|
31
|
+
.replace(/^_+|_+$/g, '')
|
|
32
|
+
.slice(0, 64);
|
|
33
|
+
const fromName = slug(name.trim());
|
|
34
|
+
if (fromName.length > 0) {
|
|
35
|
+
return fromName;
|
|
30
36
|
}
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
const fromNode = slug(nodeId);
|
|
38
|
+
return fromNode.length > 0 ? fromNode : 'subagent';
|
|
39
|
+
};
|
|
40
|
+
const formatSpawnUserContent = (task, skillId) => {
|
|
41
|
+
if (skillId.length === 0) {
|
|
42
|
+
return task;
|
|
43
|
+
}
|
|
44
|
+
return `[skill:${skillId}]\n${task}`;
|
|
45
|
+
};
|
|
46
|
+
const buildSpecialistDescription = (name, description, skillIds) => {
|
|
47
|
+
const parts = [
|
|
48
|
+
`${name} is a canvas Sub-Agent — a separate specialist agent, not a regular tool (not files, shell, or MCP).`,
|
|
49
|
+
'Call this tool to delegate a task; the specialist runs its own model and tools and returns a text result.',
|
|
50
|
+
'Do not try to do this specialist work yourself when this tool is the right owner.',
|
|
51
|
+
];
|
|
52
|
+
if (description.trim().length > 0) {
|
|
53
|
+
parts.push(description.trim());
|
|
33
54
|
}
|
|
34
|
-
if (
|
|
35
|
-
|
|
55
|
+
if (skillIds.length > 0) {
|
|
56
|
+
parts.push(`Skills: ${skillIds.join(', ')}.`);
|
|
36
57
|
}
|
|
37
|
-
|
|
58
|
+
parts.push('Required argument: task — the assignment for this Sub-Agent.');
|
|
59
|
+
return parts.join(' ');
|
|
38
60
|
};
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
const buildSpecialistInputSchema = (skillIds) => {
|
|
62
|
+
const properties = {
|
|
63
|
+
task: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'Task to delegate to this Sub-Agent specialist',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
if (skillIds.length > 0) {
|
|
69
|
+
properties.skillId = {
|
|
70
|
+
type: 'string',
|
|
71
|
+
enum: [...skillIds],
|
|
72
|
+
description: 'Optional skill announced by this Sub-Agent Inspector selector',
|
|
73
|
+
};
|
|
42
74
|
}
|
|
43
|
-
return
|
|
75
|
+
return {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties,
|
|
78
|
+
required: ['task'],
|
|
79
|
+
};
|
|
44
80
|
};
|
|
45
81
|
const resolveTurnFactory = (context) => {
|
|
46
82
|
if (context.scriptedTurns !== undefined) {
|
|
@@ -48,33 +84,78 @@ const resolveTurnFactory = (context) => {
|
|
|
48
84
|
}
|
|
49
85
|
return context.factory;
|
|
50
86
|
};
|
|
51
|
-
const
|
|
87
|
+
const chatConfigError = (providerId, model, scripted) => {
|
|
52
88
|
if (scripted) {
|
|
53
|
-
return;
|
|
89
|
+
return undefined;
|
|
54
90
|
}
|
|
55
91
|
if (providerId.trim().length === 0) {
|
|
56
|
-
|
|
92
|
+
return 'Error: Provider is required for Sub-Agent chat';
|
|
57
93
|
}
|
|
58
94
|
if (model.trim().length === 0) {
|
|
59
|
-
|
|
95
|
+
return 'Error: Model is required for Sub-Agent chat';
|
|
60
96
|
}
|
|
97
|
+
return undefined;
|
|
61
98
|
};
|
|
62
|
-
const
|
|
99
|
+
const EMPTY_INVOKE_RESULT = 'Error: Sub-Agent returned no content';
|
|
100
|
+
const finalizeInvokeText = (responseText, lastDraft, lastReasoning) => {
|
|
101
|
+
if (responseText.trim().length > 0) {
|
|
102
|
+
return responseText;
|
|
103
|
+
}
|
|
104
|
+
if (lastDraft.trim().length > 0) {
|
|
105
|
+
return lastDraft;
|
|
106
|
+
}
|
|
107
|
+
if (lastReasoning.trim().length > 0) {
|
|
108
|
+
return lastReasoning;
|
|
109
|
+
}
|
|
110
|
+
return EMPTY_INVOKE_RESULT;
|
|
111
|
+
};
|
|
112
|
+
const seedInvokeHistory = (history, context) => {
|
|
113
|
+
const system = {
|
|
114
|
+
role: 'system',
|
|
115
|
+
content: appendToolInventory(context.effectiveSystemPrompt, context.tools),
|
|
116
|
+
};
|
|
117
|
+
if (history.length === 0) {
|
|
118
|
+
return [system];
|
|
119
|
+
}
|
|
120
|
+
if (history[0]?.role === 'system') {
|
|
121
|
+
return [system, ...history.slice(1)];
|
|
122
|
+
}
|
|
123
|
+
return [system, ...history];
|
|
124
|
+
};
|
|
125
|
+
const reduceInvokeHistory = (history, chunk) => {
|
|
126
|
+
if (chunk.kind === 'historySync') {
|
|
127
|
+
return [...chunk.messages];
|
|
128
|
+
}
|
|
129
|
+
if (chunk.kind === 'invokeDone' && chunk.text.trim().length > 0) {
|
|
130
|
+
return [...history, { role: 'assistant', content: chunk.text }];
|
|
131
|
+
}
|
|
132
|
+
return history;
|
|
133
|
+
};
|
|
134
|
+
const runSubAgentTurn = (context, turn, history) => {
|
|
135
|
+
const fail = (text) => of({ kind: 'response', text }, {
|
|
136
|
+
kind: 'invokeDone',
|
|
137
|
+
requestId: turn.requestId,
|
|
138
|
+
text,
|
|
139
|
+
});
|
|
63
140
|
const scripted = context.scriptedTurns !== undefined;
|
|
64
141
|
const factory = resolveTurnFactory(context);
|
|
65
142
|
if (factory === undefined) {
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
|
|
143
|
+
return fail('Error: Sub-Agent chat is only available during server workflow runs');
|
|
144
|
+
}
|
|
145
|
+
const configError = chatConfigError(context.providerId, context.model, scripted);
|
|
146
|
+
if (configError !== undefined) {
|
|
147
|
+
return fail(configError);
|
|
69
148
|
}
|
|
70
|
-
|
|
149
|
+
const userContent = formatSpawnUserContent(turn.task, turn.skillId);
|
|
150
|
+
let lastDraft = '';
|
|
151
|
+
let lastReasoning = '';
|
|
71
152
|
return concat(of({
|
|
72
153
|
kind: 'historySync',
|
|
73
154
|
messages: [
|
|
74
155
|
...history,
|
|
75
156
|
{
|
|
76
157
|
role: 'user',
|
|
77
|
-
content:
|
|
158
|
+
content: userContent,
|
|
78
159
|
},
|
|
79
160
|
],
|
|
80
161
|
}), runAgentLoop({
|
|
@@ -85,50 +166,78 @@ const runSubAgentTurn = (context, spawn, history, subagentResult$) => {
|
|
|
85
166
|
model: scripted
|
|
86
167
|
? context.model.trim() || 'scripted'
|
|
87
168
|
: context.model,
|
|
88
|
-
messages: [
|
|
89
|
-
...history,
|
|
90
|
-
{ role: 'user', content: formatSpawnUserContent(spawn) },
|
|
91
|
-
],
|
|
169
|
+
messages: [...history, { role: 'user', content: userContent }],
|
|
92
170
|
tools: context.tools,
|
|
93
171
|
toolCtx: context.toolCtx,
|
|
94
172
|
maxIterations: context.maxIterations,
|
|
95
173
|
compaction: context.compaction,
|
|
96
174
|
recovery: context.recovery,
|
|
97
|
-
subagentRegistrations: context.subagentRegistrations,
|
|
98
175
|
...(context.requestPermission !== undefined
|
|
99
176
|
? { requestPermission: context.requestPermission }
|
|
100
177
|
: {}),
|
|
101
178
|
...(context.steerControl$ !== undefined
|
|
102
179
|
? { steerControl$: context.steerControl$ }
|
|
103
180
|
: {}),
|
|
104
|
-
...(context.subagentRegistrations.length > 0
|
|
105
|
-
? {
|
|
106
|
-
waitForSubagentResult: (callId, signal) => waitForSubagentResult(subagentResult$, callId, signal, context.recovery.subagentTimeoutMs),
|
|
107
|
-
}
|
|
108
|
-
: {}),
|
|
109
181
|
}).pipe(mergeMap((chunk) => {
|
|
182
|
+
if (chunk.kind === 'reasoning' && chunk.text.length > 0) {
|
|
183
|
+
lastReasoning += chunk.text;
|
|
184
|
+
}
|
|
185
|
+
if (chunk.kind === 'draftResponse' && chunk.text.length > 0) {
|
|
186
|
+
lastDraft += chunk.text;
|
|
187
|
+
}
|
|
110
188
|
if (chunk.kind !== 'response') {
|
|
111
189
|
return of(chunk);
|
|
112
190
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
},
|
|
191
|
+
const text = finalizeInvokeText(chunk.text, lastDraft, lastReasoning);
|
|
192
|
+
return of({ kind: 'response', text }, {
|
|
193
|
+
kind: 'invokeDone',
|
|
194
|
+
requestId: turn.requestId,
|
|
195
|
+
text,
|
|
119
196
|
});
|
|
197
|
+
}), catchError((error) => {
|
|
198
|
+
const text = error instanceof Error
|
|
199
|
+
? `Error: ${error.message}`
|
|
200
|
+
: 'Error: Sub-Agent turn failed.';
|
|
201
|
+
return fail(text);
|
|
120
202
|
})));
|
|
121
203
|
};
|
|
204
|
+
const parseInvokeTurn = (raw, skillIds) => {
|
|
205
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
206
|
+
return {
|
|
207
|
+
ok: false,
|
|
208
|
+
text: 'Error: Sub-Agent invoke payload is invalid.',
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const record = raw;
|
|
212
|
+
const requestId = typeof record.requestId === 'string' && record.requestId.length > 0
|
|
213
|
+
? record.requestId
|
|
214
|
+
: randomUUID();
|
|
215
|
+
const task = typeof record.task === 'string' ? record.task.trim() : '';
|
|
216
|
+
const skillId = typeof record.skillId === 'string' ? record.skillId.trim() : '';
|
|
217
|
+
if (task.length === 0) {
|
|
218
|
+
return {
|
|
219
|
+
ok: false,
|
|
220
|
+
text: 'Error: Sub-Agent requires a non-empty task.',
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (skillId.length > 0 && !skillIds.includes(skillId)) {
|
|
224
|
+
return {
|
|
225
|
+
ok: false,
|
|
226
|
+
text: `Error: Skill «${skillId}» is not announced by this Sub-Agent.`,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return { ok: true, turn: { requestId, task, skillId } };
|
|
230
|
+
};
|
|
122
231
|
/**
|
|
123
|
-
* Sub-Agent: ordinary OpenAI-compatible agent
|
|
124
|
-
*
|
|
232
|
+
* Sub-Agent: ordinary OpenAI-compatible agent that announces one ToolHandle
|
|
233
|
+
* on `subagent-registration`. Parent invoke runs this node's in-node loop.
|
|
125
234
|
* @see docs/ADR.md ADR-021
|
|
126
235
|
*/
|
|
127
236
|
export const subAgentNode = defineLlmNode({
|
|
128
237
|
type: 'common-sub-agent',
|
|
129
238
|
displayName: 'Sub-Agent',
|
|
130
239
|
category: 'AI',
|
|
131
|
-
description: 'OpenAI-compatible specialist agent
|
|
240
|
+
description: 'OpenAI-compatible specialist agent. Wire `tools` into a parent agent; invoke runs this node in-graph.',
|
|
132
241
|
uiSchema: [
|
|
133
242
|
{
|
|
134
243
|
field: 'name',
|
|
@@ -153,47 +262,31 @@ export const subAgentNode = defineLlmNode({
|
|
|
153
262
|
...llmRecoveryUiSchema,
|
|
154
263
|
],
|
|
155
264
|
bind(ctx, { makeInput, configureOutput, combineInputs }, inventory) {
|
|
156
|
-
const { tools,
|
|
265
|
+
const { tools, steerControl } = inventory;
|
|
157
266
|
const systemPrompt = makeInput('systemPrompt', {
|
|
158
267
|
name: 'systemPrompt',
|
|
159
268
|
wireType: 'string',
|
|
160
269
|
inline: 'text-multiline',
|
|
161
270
|
defaultValue: '',
|
|
162
271
|
});
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const registration$ = combineInputs([ctx], ([ec]) => {
|
|
169
|
-
const skillIds = normalizeSkillIds(ec.params.skillIds);
|
|
170
|
-
const name = String(ec.params.name ?? 'Sub-Agent').trim();
|
|
171
|
-
const description = String(ec.params.description ?? '');
|
|
172
|
-
return {
|
|
173
|
-
targetNodeId: ec.nodeId,
|
|
174
|
-
name: name.length > 0 ? name : 'Sub-Agent',
|
|
175
|
-
description,
|
|
176
|
-
skills: skillIds.map((skillId) => ({
|
|
177
|
-
skillId,
|
|
178
|
-
description: skillId,
|
|
179
|
-
})),
|
|
180
|
-
};
|
|
181
|
-
});
|
|
182
|
-
const context$ = combineInputs([systemPrompt, tools, subagentRegistration, mcp, ctx], ([systemPromptValue, toolList, subagentList, mcpList, ec]) => {
|
|
272
|
+
const chunks$ = new Subject();
|
|
273
|
+
let latestContext;
|
|
274
|
+
let history = [];
|
|
275
|
+
let invokeTail = Promise.resolve();
|
|
276
|
+
const context$ = combineInputs([systemPrompt, tools, ctx], ([systemPromptValue, toolList, ec]) => {
|
|
183
277
|
const rolePreset = parseLlmRolePreset(ec.params.rolePreset);
|
|
184
278
|
const skillId = resolveEffectiveSkillId(rolePreset, ec.params.skillId);
|
|
185
279
|
const hostServices = getRunHostServices(ec);
|
|
186
280
|
const skillMarkdown = hostServices?.skillMarkdown ?? '';
|
|
187
281
|
const agentsMarkdown = hostServices?.agentsMarkdown ?? '';
|
|
282
|
+
const name = String(ec.params.name ?? 'Sub-Agent').trim();
|
|
283
|
+
const inspectorDescription = String(ec.params.description ?? '');
|
|
188
284
|
return {
|
|
189
285
|
prompt: '',
|
|
190
286
|
tools: collectAgentToolHandles({
|
|
191
287
|
toolHandles: ec.toolHandles,
|
|
192
288
|
toolsPort: toolList,
|
|
193
|
-
mcpHandles: ec.mcpHandles,
|
|
194
|
-
mcpPort: mcpList,
|
|
195
289
|
}),
|
|
196
|
-
subagentRegistrations: flattenSubAgentRegistrations(subagentList),
|
|
197
290
|
...resolveChatProviderModel(ec.params, hostServices),
|
|
198
291
|
skillId,
|
|
199
292
|
rolePreset,
|
|
@@ -234,26 +327,56 @@ export const subAgentNode = defineLlmNode({
|
|
|
234
327
|
factory: hostServices?.createChatCompletionStream,
|
|
235
328
|
scriptedTurns: parseScriptedTurns(ec.params['scriptedToolTurns']),
|
|
236
329
|
nodeId: ec.nodeId,
|
|
330
|
+
displayName: name.length > 0 ? name : 'Sub-Agent',
|
|
331
|
+
inspectorDescription,
|
|
237
332
|
skillIds: normalizeSkillIds(ec.params.skillIds),
|
|
238
333
|
};
|
|
239
334
|
});
|
|
240
|
-
|
|
241
|
-
|
|
335
|
+
// Inner chunks stay values. `statefulObservable({ input: chunks$ })`
|
|
336
|
+
// re-emits loading on every token; pipeValue fans it to all demux ports.
|
|
337
|
+
const cycle$ = context$.pipeValue(switchMap(() => chunks$));
|
|
338
|
+
const enqueueInvoke = (announced, args, _toolCtx) => {
|
|
339
|
+
const context = latestContext ?? announced;
|
|
340
|
+
const parsed = parseInvokeTurn({ ...args, requestId: randomUUID() }, announced.skillIds);
|
|
341
|
+
if (!parsed.ok) {
|
|
342
|
+
return Promise.resolve(parsed.text);
|
|
343
|
+
}
|
|
344
|
+
const run = () => {
|
|
345
|
+
history = seedInvokeHistory(history, context);
|
|
346
|
+
const timeoutMs = context.recovery.subagentTimeoutMs;
|
|
347
|
+
let done$ = runSubAgentTurn(context, parsed.turn, history).pipe(tap((chunk) => {
|
|
348
|
+
history = reduceInvokeHistory(history, chunk);
|
|
349
|
+
chunks$.next(chunk);
|
|
350
|
+
}), filter((chunk) => chunk.kind === 'invokeDone'), map((chunk) => chunk.text), take(1));
|
|
351
|
+
if (timeoutMs > 0) {
|
|
352
|
+
done$ = done$.pipe(timeout({ first: timeoutMs }), catchError((error) => of(error instanceof TimeoutError
|
|
353
|
+
? `Error: Sub-Agent timed out after ${timeoutMs}ms.`
|
|
354
|
+
: error instanceof Error
|
|
355
|
+
? `Error: ${error.message}`
|
|
356
|
+
: 'Error: Sub-Agent turn failed.')));
|
|
357
|
+
}
|
|
358
|
+
return firstValueFrom(done$, {
|
|
359
|
+
defaultValue: EMPTY_INVOKE_RESULT,
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
const next = invokeTail.then(run, run);
|
|
363
|
+
invokeTail = next.then(() => undefined, () => undefined);
|
|
364
|
+
return next;
|
|
365
|
+
};
|
|
366
|
+
const specialistTools$ = context$.pipeValue(tap((context) => {
|
|
367
|
+
latestContext = context;
|
|
368
|
+
}), map((context) => {
|
|
369
|
+
const handleName = formatSpecialistHandleName(context.displayName);
|
|
370
|
+
return [
|
|
242
371
|
{
|
|
243
|
-
|
|
244
|
-
|
|
372
|
+
toolId: slugToolId(handleName, context.nodeId),
|
|
373
|
+
name: handleName,
|
|
374
|
+
description: buildSpecialistDescription(handleName, context.inspectorDescription, context.skillIds),
|
|
375
|
+
inputSchema: buildSpecialistInputSchema(context.skillIds),
|
|
376
|
+
invoke: (args, toolCtx) => enqueueInvoke(context, args, toolCtx),
|
|
245
377
|
},
|
|
246
|
-
]
|
|
247
|
-
|
|
248
|
-
appendUserFeedbackToHistory: false,
|
|
249
|
-
session: undefined,
|
|
250
|
-
}), (context, turnPayload, history, _session) => {
|
|
251
|
-
const spawn = acceptSpawn(turnPayload, context.nodeId, context.skillIds);
|
|
252
|
-
if (spawn === null) {
|
|
253
|
-
return EMPTY;
|
|
254
|
-
}
|
|
255
|
-
return runSubAgentTurn(context, spawn, history, subagentResult.value$);
|
|
256
|
-
}, { primeTurn0: false });
|
|
378
|
+
];
|
|
379
|
+
}));
|
|
257
380
|
const reasoning$ = cycle$.pipeValue(demuxByKind('reasoning', (chunk) => chunk
|
|
258
381
|
.text));
|
|
259
382
|
const draftResponse$ = cycle$.pipeValue(demuxByKind('draftResponse', (chunk) => chunk
|
|
@@ -265,14 +388,12 @@ export const subAgentNode = defineLlmNode({
|
|
|
265
388
|
}));
|
|
266
389
|
const response$ = cycle$.pipeValue(demuxByKind('response', (chunk) => chunk
|
|
267
390
|
.text));
|
|
268
|
-
const result$ = cycle$.pipeValue(demuxByKind('subagentResult', (chunk) => chunk.payload));
|
|
269
|
-
const nestedSubagent$ = cycle$.pipeValue(demuxByKind('subagentSpawn', (chunk) => chunk
|
|
270
|
-
.payload));
|
|
271
391
|
return {
|
|
272
|
-
inputs: [systemPrompt
|
|
392
|
+
inputs: [systemPrompt],
|
|
273
393
|
outputs: [
|
|
274
|
-
configureOutput('registration',
|
|
275
|
-
|
|
394
|
+
configureOutput('subagent-registration', specialistTools$, {
|
|
395
|
+
name: 'subagent-registration',
|
|
396
|
+
wireType: TOOL_HANDLE_WIRE_TYPE,
|
|
276
397
|
}),
|
|
277
398
|
configureOutput('reasoning', reasoning$, {
|
|
278
399
|
wireType: 'string',
|
|
@@ -286,16 +407,10 @@ export const subAgentNode = defineLlmNode({
|
|
|
286
407
|
wireType: 'string',
|
|
287
408
|
feed: { role: 'result' },
|
|
288
409
|
}),
|
|
289
|
-
configureOutput('result', result$, {
|
|
290
|
-
wireType: SUBAGENT_RESULT_WIRE_TYPE,
|
|
291
|
-
// Protocol payload for parent wiring — not work-log content.
|
|
292
|
-
feed: { role: 'none' },
|
|
293
|
-
}),
|
|
294
410
|
],
|
|
295
411
|
inventoryOutputs: {
|
|
296
412
|
toolLog$,
|
|
297
413
|
recovery$,
|
|
298
|
-
subagent$: nestedSubagent$,
|
|
299
414
|
},
|
|
300
415
|
};
|
|
301
416
|
},
|
|
@@ -34,6 +34,7 @@ import { fetchUrlNode } from './crawl/fetch-url/node.js';
|
|
|
34
34
|
import { savePageNode } from './crawl/save-page/node.js';
|
|
35
35
|
import { memoryToolsNode } from './memory/memory-tools/node.js';
|
|
36
36
|
import { langflowerToolsNode } from './langflower-tools/node.js';
|
|
37
|
+
import { toolCollectionNode } from './tools/tool-collection/node.js';
|
|
37
38
|
const COMMON_REACTIVE_NODES = [
|
|
38
39
|
booleanNode,
|
|
39
40
|
numberNode,
|
|
@@ -66,6 +67,7 @@ const COMMON_REACTIVE_NODES = [
|
|
|
66
67
|
switchNode,
|
|
67
68
|
memoryToolsNode,
|
|
68
69
|
langflowerToolsNode,
|
|
70
|
+
toolCollectionNode,
|
|
69
71
|
fetchUrlNode,
|
|
70
72
|
extractLinksNode,
|
|
71
73
|
savePageNode,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Owns HTTP MCP connect/close (optional local launch); emits live
|
|
3
|
-
*
|
|
4
|
-
* tools/list.
|
|
5
|
-
* silent EMPTY).
|
|
2
|
+
* Owns HTTP MCP connect/close (optional local launch); emits live
|
|
3
|
+
* {@link ToolHandle}[] for LLM `tools`. Server name and tools come from MCP
|
|
4
|
+
* initialize / tools/list. Session stays in invoke closures.
|
|
5
|
+
* Connect/initialize/build failure → output port **error** (not silent EMPTY).
|
|
6
6
|
*/
|
|
7
7
|
export declare const mcpHttpNode: {
|
|
8
8
|
readonly emitOncePerActivation: boolean;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { defineReactiveNode } from '@langflower/node-sdk';
|
|
2
|
-
import { MCP_HANDLE_WIRE_TYPE } from '@langflower/node-sdk/mcp';
|
|
1
|
+
import { defineReactiveNode, TOOL_HANDLE_WIRE_TYPE, } from '@langflower/node-sdk';
|
|
3
2
|
import { buildMcpHandle } from '@langflower/tools/build-mcp-handle';
|
|
4
3
|
import { formatMcpConnectError } from '@langflower/tools/format-mcp-connect-error';
|
|
5
4
|
import { connectMcpHttpWithOptionalLaunch } from '@langflower/tools/mcp-http-client';
|
|
@@ -11,16 +10,16 @@ const paramsKey = (params) => JSON.stringify({
|
|
|
11
10
|
command: params.command,
|
|
12
11
|
});
|
|
13
12
|
/**
|
|
14
|
-
* Owns HTTP MCP connect/close (optional local launch); emits live
|
|
15
|
-
*
|
|
16
|
-
* tools/list.
|
|
17
|
-
* silent EMPTY).
|
|
13
|
+
* Owns HTTP MCP connect/close (optional local launch); emits live
|
|
14
|
+
* {@link ToolHandle}[] for LLM `tools`. Server name and tools come from MCP
|
|
15
|
+
* initialize / tools/list. Session stays in invoke closures.
|
|
16
|
+
* Connect/initialize/build failure → output port **error** (not silent EMPTY).
|
|
18
17
|
*/
|
|
19
18
|
export const mcpHttpNode = defineReactiveNode({
|
|
20
19
|
type: 'common-mcp-http',
|
|
21
20
|
displayName: 'MCP http',
|
|
22
21
|
category: 'Tools',
|
|
23
|
-
description: 'Connects to an MCP server over Streamable HTTP. Optional shell command launches a local process first; URL alone targets an already-running server.',
|
|
22
|
+
description: 'Connects to an MCP server over Streamable HTTP and emits its tools for LLM `tools` ports. Optional shell command launches a local process first; URL alone targets an already-running server.',
|
|
24
23
|
uiSchema: [],
|
|
25
24
|
bind(ctx, { makeInput, configureOutput, combineInputs }) {
|
|
26
25
|
const url = makeInput('url', {
|
|
@@ -82,7 +81,7 @@ export const mcpHttpNode = defineReactiveNode({
|
|
|
82
81
|
await session.close();
|
|
83
82
|
return;
|
|
84
83
|
}
|
|
85
|
-
subscriber.next(handle);
|
|
84
|
+
subscriber.next(handle.tools);
|
|
86
85
|
};
|
|
87
86
|
void run().catch(fail);
|
|
88
87
|
return () => {
|
|
@@ -94,8 +93,8 @@ export const mcpHttpNode = defineReactiveNode({
|
|
|
94
93
|
return {
|
|
95
94
|
inputs: [url, command],
|
|
96
95
|
outputs: [
|
|
97
|
-
configureOutput('
|
|
98
|
-
wireType:
|
|
96
|
+
configureOutput('tools', handle$, {
|
|
97
|
+
wireType: TOOL_HANDLE_WIRE_TYPE,
|
|
99
98
|
}),
|
|
100
99
|
],
|
|
101
100
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Owns stdio MCP connect/close; emits live {@link
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* silent EMPTY).
|
|
2
|
+
* Owns stdio MCP connect/close; emits live {@link ToolHandle}[] for LLM `tools`.
|
|
3
|
+
* Server name and tools come from MCP initialize / tools/list. Session stays
|
|
4
|
+
* in invoke closures. Connect/initialize/build failure → output port **error**
|
|
5
|
+
* (not silent EMPTY).
|
|
6
6
|
*/
|
|
7
7
|
export declare const mcpStdioNode: {
|
|
8
8
|
readonly emitOncePerActivation: boolean;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { defineReactiveNode } from '@langflower/node-sdk';
|
|
2
|
-
import { MCP_HANDLE_WIRE_TYPE } from '@langflower/node-sdk/mcp';
|
|
1
|
+
import { defineReactiveNode, TOOL_HANDLE_WIRE_TYPE, } from '@langflower/node-sdk';
|
|
3
2
|
import { buildMcpHandle } from '@langflower/tools/build-mcp-handle';
|
|
4
3
|
import { formatMcpConnectError } from '@langflower/tools/format-mcp-connect-error';
|
|
5
4
|
import { connectMcpStdioFromCli } from '@langflower/tools/mcp-stdio-client';
|
|
@@ -10,16 +9,16 @@ const paramsKey = (params) => JSON.stringify({
|
|
|
10
9
|
command: params.command,
|
|
11
10
|
});
|
|
12
11
|
/**
|
|
13
|
-
* Owns stdio MCP connect/close; emits live {@link
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* silent EMPTY).
|
|
12
|
+
* Owns stdio MCP connect/close; emits live {@link ToolHandle}[] for LLM `tools`.
|
|
13
|
+
* Server name and tools come from MCP initialize / tools/list. Session stays
|
|
14
|
+
* in invoke closures. Connect/initialize/build failure → output port **error**
|
|
15
|
+
* (not silent EMPTY).
|
|
17
16
|
*/
|
|
18
17
|
export const mcpStdioNode = defineReactiveNode({
|
|
19
18
|
type: 'common-mcp-stdio',
|
|
20
19
|
displayName: 'MCP stdio',
|
|
21
20
|
category: 'Tools',
|
|
22
|
-
description: 'Launches an MCP server over stdio from a shell command line and emits
|
|
21
|
+
description: 'Launches an MCP server over stdio from a shell command line and emits its tools for LLM `tools` ports.',
|
|
23
22
|
uiSchema: [],
|
|
24
23
|
bind(ctx, { makeInput, configureOutput, combineInputs }) {
|
|
25
24
|
const command = makeInput('command', {
|
|
@@ -71,7 +70,7 @@ export const mcpStdioNode = defineReactiveNode({
|
|
|
71
70
|
await client.close();
|
|
72
71
|
return;
|
|
73
72
|
}
|
|
74
|
-
subscriber.next(handle);
|
|
73
|
+
subscriber.next(handle.tools);
|
|
75
74
|
};
|
|
76
75
|
void run().catch(fail);
|
|
77
76
|
return () => {
|
|
@@ -83,8 +82,8 @@ export const mcpStdioNode = defineReactiveNode({
|
|
|
83
82
|
return {
|
|
84
83
|
inputs: [command],
|
|
85
84
|
outputs: [
|
|
86
|
-
configureOutput('
|
|
87
|
-
wireType:
|
|
85
|
+
configureOutput('tools', handle$, {
|
|
86
|
+
wireType: TOOL_HANDLE_WIRE_TYPE,
|
|
88
87
|
}),
|
|
89
88
|
],
|
|
90
89
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { ToolHandle } from '@langflower/node-sdk';
|
|
2
|
-
|
|
2
|
+
/** Flatten multi-wire values that may be single handles or arrays (packs). */
|
|
3
|
+
export declare const flattenToolHandles: (wired: readonly unknown[]) => readonly ToolHandle[];
|
|
3
4
|
/**
|
|
4
|
-
* Agent inventory: EC ∪ port for tools
|
|
5
|
+
* Agent inventory: EC ∪ port for tools.
|
|
5
6
|
* No catalog list, no enabledToolIds filter (server already filtered EC).
|
|
7
|
+
* Later `toolHandles` last-wins on `toolId` (jsonc MCP after builtins).
|
|
6
8
|
*/
|
|
7
9
|
export declare const collectAgentToolHandles: (options: {
|
|
8
10
|
readonly toolHandles: readonly ToolHandle[] | undefined;
|
|
9
11
|
readonly toolsPort: unknown;
|
|
10
|
-
readonly mcpHandles: readonly McpHandle[] | undefined;
|
|
11
|
-
readonly mcpPort: unknown;
|
|
12
12
|
}) => readonly ToolHandle[];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { flattenMcpHandles } from '../mcp/flatten-mcp-handles.js';
|
|
2
1
|
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
2
|
const isToolHandle = (value) => {
|
|
4
3
|
if (!isRecord(value)) {
|
|
@@ -12,7 +11,7 @@ const isToolHandle = (value) => {
|
|
|
12
11
|
typeof value.invoke === 'function');
|
|
13
12
|
};
|
|
14
13
|
/** Flatten multi-wire values that may be single handles or arrays (packs). */
|
|
15
|
-
const flattenToolHandles = (wired) => wired.flatMap((item) => {
|
|
14
|
+
export const flattenToolHandles = (wired) => wired.flatMap((item) => {
|
|
16
15
|
if (Array.isArray(item)) {
|
|
17
16
|
return item.filter(isToolHandle);
|
|
18
17
|
}
|
|
@@ -22,8 +21,9 @@ const flattenToolHandles = (wired) => wired.flatMap((item) => {
|
|
|
22
21
|
return [];
|
|
23
22
|
});
|
|
24
23
|
/**
|
|
25
|
-
* Agent inventory: EC ∪ port for tools
|
|
24
|
+
* Agent inventory: EC ∪ port for tools.
|
|
26
25
|
* No catalog list, no enabledToolIds filter (server already filtered EC).
|
|
26
|
+
* Later `toolHandles` last-wins on `toolId` (jsonc MCP after builtins).
|
|
27
27
|
*/
|
|
28
28
|
export const collectAgentToolHandles = (options) => {
|
|
29
29
|
const portTools = Array.isArray(options.toolsPort)
|
|
@@ -38,13 +38,5 @@ export const collectAgentToolHandles = (options) => {
|
|
|
38
38
|
for (const handle of options.toolHandles ?? []) {
|
|
39
39
|
byId.set(handle.toolId, handle);
|
|
40
40
|
}
|
|
41
|
-
for (const mcp of [
|
|
42
|
-
...(options.mcpHandles ?? []),
|
|
43
|
-
...flattenMcpHandles(options.mcpPort),
|
|
44
|
-
]) {
|
|
45
|
-
for (const handle of mcp.tools) {
|
|
46
|
-
byId.set(handle.toolId, handle);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
41
|
return [...byId.values()];
|
|
50
42
|
};
|
|
@@ -2,8 +2,6 @@ import type { ToolHandle } from '@langflower/node-sdk';
|
|
|
2
2
|
import { type Harness } from '@langflower/tools/create-project-harness';
|
|
3
3
|
import type { ToolHandlerContext } from '@langflower/tools/domain-tool-configs';
|
|
4
4
|
import type { ChatCompletionToolCall, ChatCompletionToolDefinition } from '../ai/features/chat-completion-stream.js';
|
|
5
|
-
import type { SubAgentRegistration, SubAgentSpawnPayload } from '../ai/features/sub-agent-protocol.js';
|
|
6
|
-
export declare const SPAWN_SUBAGENT_TOOL = "spawn_subagent";
|
|
7
5
|
export declare const previewToolLogText: (text: string) => string;
|
|
8
6
|
export declare const toChatToolDefinitions: (tools: readonly ToolHandle[]) => readonly ChatCompletionToolDefinition[];
|
|
9
7
|
export declare const parseToolArgs: (raw: string) => Readonly<Record<string, unknown>>;
|
|
@@ -20,11 +18,3 @@ export declare const invokeInventoryTool: (harness: Harness | undefined, tools:
|
|
|
20
18
|
readonly ok: boolean;
|
|
21
19
|
readonly text: string;
|
|
22
20
|
}>;
|
|
23
|
-
export declare const buildSpawnSubagentChatTool: (registrations: readonly SubAgentRegistration[]) => ChatCompletionToolDefinition;
|
|
24
|
-
export declare const resolveSpawnPayload: (call: ChatCompletionToolCall, registrations: readonly SubAgentRegistration[], openCallId: string | undefined) => {
|
|
25
|
-
readonly ok: true;
|
|
26
|
-
readonly payload: SubAgentSpawnPayload;
|
|
27
|
-
} | {
|
|
28
|
-
readonly ok: false;
|
|
29
|
-
readonly text: string;
|
|
30
|
-
};
|