langflower 0.0.4 → 0.0.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/README.md +20 -15
- package/docs/public/README.md +15 -0
- package/docs/public/configuration.md +56 -0
- package/docs/public/extending.md +58 -0
- package/docs/public/getting-started.md +61 -0
- package/docs/public/how-it-works.md +39 -0
- package/docs/public/product.md +43 -0
- package/docs/public/using-the-editor.md +53 -0
- package/docs/public/workflows.md +49 -0
- package/package.json +3 -2
- package/ui-dist/chunk-6DQ2XSRW.js +4 -0
- package/ui-dist/{chunk-2AICAC67.js → chunk-Y6VMBLSJ.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/main-RCFTD5AZ.js +311 -0
- package/ui-dist/styles-EOXODOI7.css +1 -0
- package/vendor/common-nodes/dist/ai/critique/node.d.ts +141 -23
- package/vendor/common-nodes/dist/ai/critique/node.js +33 -31
- package/vendor/common-nodes/dist/ai/fake-llm/node.d.ts +6 -6
- package/vendor/common-nodes/dist/ai/llm-compaction-ui-schema.d.ts +1 -1
- package/vendor/common-nodes/dist/ai/llm-compaction-ui-schema.js +2 -1
- package/vendor/common-nodes/dist/ai/llm-panel-ui-schema.d.ts +7 -21
- package/vendor/common-nodes/dist/ai/llm-panel-ui-schema.js +8 -4
- package/vendor/common-nodes/dist/ai/llm-session-shell.d.ts +2 -0
- package/vendor/common-nodes/dist/ai/llm-session-shell.js +3 -3
- package/vendor/common-nodes/dist/ai/normalize-max-iterations.d.ts +4 -3
- package/vendor/common-nodes/dist/ai/normalize-max-iterations.js +4 -3
- package/vendor/common-nodes/dist/ai/openai/normalize-compaction-params.d.ts +2 -2
- package/vendor/common-nodes/dist/ai/openai/normalize-compaction-params.js +1 -1
- package/vendor/common-nodes/dist/ai/openai-llm/node.d.ts +9 -9
- package/vendor/common-nodes/dist/ai/resolve-chat-provider-model.d.ts +10 -0
- package/vendor/common-nodes/dist/ai/resolve-chat-provider-model.js +8 -0
- package/vendor/common-nodes/dist/ai/review/node.d.ts +138 -15
- package/vendor/common-nodes/dist/ai/review/node.js +29 -22
- package/vendor/common-nodes/dist/ai/run-host-services.d.ts +8 -0
- package/vendor/common-nodes/dist/ai/sub-agent/node.d.ts +9 -9
- package/vendor/common-nodes/dist/ai/sub-agent/node.js +4 -3
- package/vendor/common-nodes/dist/hitl/chat-input/node.js +1 -0
- package/vendor/common-nodes/dist/hitl/review-gate/node.js +5 -0
- package/vendor/node-sdk/dist/node-factory/define-llm-node/default-llm-ports.js +2 -0
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-reactive-node.d.ts +1 -1
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/io-helpers.d.ts +5 -12
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/port-meta.d.ts +13 -0
- package/vendor/runtime/dist/runtime-runner.js +7 -4
- package/vendor/runtime/dist/runtime.d.ts +1 -1
- package/vendor/runtime/dist/types.d.ts +12 -0
- package/vendor/server/dist/bridge/attach-langflower-bridge.d.ts +4 -1
- package/vendor/server/dist/bridge/attach-langflower-bridge.js +16 -5
- package/vendor/server/dist/bridge/bridge-event-log.d.ts +12 -0
- package/vendor/server/dist/bridge/bridge-event-log.js +199 -0
- package/vendor/server/dist/bridge/build-execution-context.d.ts +1 -1
- package/vendor/server/dist/bridge/build-execution-context.js +3 -0
- package/vendor/server/dist/bridge/emit-bootstrap.d.ts +4 -2
- package/vendor/server/dist/bridge/emit-bootstrap.js +6 -2
- package/vendor/server/dist/bridge/settings-draft-controller.d.ts +14 -0
- package/vendor/server/dist/bridge/settings-draft-controller.js +178 -0
- package/vendor/server/dist/bridge/wire-config-handlers.d.ts +8 -1
- package/vendor/server/dist/bridge/wire-config-handlers.js +44 -16
- package/vendor/server/dist/bridge/wire-editor-handlers.d.ts +8 -1
- package/vendor/server/dist/bridge/wire-editor-handlers.js +40 -1
- package/vendor/server/dist/bridge/wire-runner-handlers.js +3 -1
- package/vendor/server/dist/config/langflower-config.service.d.ts +2 -0
- package/vendor/server/dist/config/langflower-config.service.js +24 -1
- package/vendor/server/dist/config/resolve-draft-provider-credentials.d.ts +18 -0
- package/vendor/server/dist/config/resolve-draft-provider-credentials.js +60 -0
- package/vendor/server/dist/create-server.js +5 -3
- package/vendor/server/dist/session/build-session-bootstrap.d.ts +3 -1
- package/vendor/server/dist/session/build-session-bootstrap.js +9 -0
- package/vendor/server/dist/session/langflower-session.d.ts +7 -2
- package/vendor/server/dist/session/langflower-session.js +9 -1
- package/vendor/server/dist/session/settings-draft-session.d.ts +24 -0
- package/vendor/server/dist/session/settings-draft-session.js +82 -0
- package/vendor/server/skeleton/schemas/langflower-config.schema.json +4 -0
- package/vendor/server/skeleton/skills/langflower-helper/SKILL.md +20 -8
- package/vendor/server/skeleton/skills/langflower-helper/architecture.md +5 -0
- package/vendor/server/skeleton/skills/langflower-helper/layout.md +18 -16
- package/vendor/server/skeleton/workflows/simple-coder.json +249 -19
- package/vendor/server/skeleton/workflows/starter.json +31 -20
- package/vendor/shared/dist/langflower-bus-config.d.ts +50 -10
- package/vendor/shared/dist/langflower-bus-config.js +52 -11
- package/vendor/shared/dist/langflower-config/merge-langflower-config-layers.test.js +4 -0
- package/vendor/shared/dist/langflower-config/parse-default-chat-model.d.ts +14 -0
- package/vendor/shared/dist/langflower-config/parse-default-chat-model.js +38 -0
- package/vendor/shared/dist/langflower-config/parse-default-chat-model.test.d.ts +1 -0
- package/vendor/shared/dist/langflower-config/parse-default-chat-model.test.js +41 -0
- package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.d.ts +6 -0
- package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.js +5 -0
- package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.test.d.ts +1 -0
- package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.test.js +11 -0
- package/vendor/shared/dist/langflower-config/settings-draft.d.ts +44 -0
- package/vendor/shared/dist/langflower-config/settings-draft.js +143 -0
- package/vendor/shared/dist/langflower.d.ts +7 -2
- package/vendor/shared/dist/langflower.js +3 -0
- package/vendor/shared/dist/types/langflower-bootstrap.d.ts +6 -1
- package/vendor/shared/dist/types/langflower-config.d.ts +65 -0
- package/vendor/shared/dist/types/langflower-editor.d.ts +19 -0
- package/ui-dist/chunk-6ZABW4JL.js +0 -4
- package/ui-dist/main-XI7IOZKZ.js +0 -311
- package/ui-dist/styles-TQGRAC7Q.css +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { MetaFromStatefulObservable, PortMeta } from './port-meta.js';
|
|
1
|
+
import type { FeedPortMeta, MetaFromStatefulObservable, PortMeta } from './port-meta.js';
|
|
2
|
+
export type { FeedPortMeta, FeedRole } from './port-meta.js';
|
|
2
3
|
import { StatefulObservable } from '@rx-evo/stateful-observable';
|
|
3
4
|
import type { HitlInputConfig } from './hitl-config.js';
|
|
4
5
|
/** One choice for the `select` / `multiselect` / `radio` inline kinds. */
|
|
@@ -65,6 +66,8 @@ export type InputParams<T> = {
|
|
|
65
66
|
readonly hidden?: boolean;
|
|
66
67
|
readonly dynamic?: boolean;
|
|
67
68
|
readonly hitl?: HitlInputConfig;
|
|
69
|
+
/** Feed metadata for terminal input facts such as approve / deny. */
|
|
70
|
+
readonly feed?: FeedPortMeta;
|
|
68
71
|
};
|
|
69
72
|
/** Port metadata for an input — `InputParams` merged with `PortMeta`. */
|
|
70
73
|
export type InputPortMeta<T> = InputParams<T> & PortMeta;
|
|
@@ -85,8 +88,6 @@ export type InputPortMeta<T> = InputParams<T> & PortMeta;
|
|
|
85
88
|
*/
|
|
86
89
|
export declare function makeInput<T>(portId: string, meta: InputParams<T>): import("@rx-evo/stateful-observable").StatefulConnection<T, unknown, InputPortMeta<T>>;
|
|
87
90
|
export type InputConfig = ReturnType<typeof makeInput>;
|
|
88
|
-
/** Authoring `configureOutput(..., { feed: { role } })` — work-log density. */
|
|
89
|
-
export type FeedRole = 'none' | 'reasoning' | 'draft' | 'tool' | 'shell' | 'result' | 'recovery';
|
|
90
91
|
export type OutputParams = {
|
|
91
92
|
/**
|
|
92
93
|
* display name
|
|
@@ -113,14 +114,7 @@ export type OutputParams = {
|
|
|
113
114
|
/**
|
|
114
115
|
* if set - output will be connected to feed at sidebar
|
|
115
116
|
*/
|
|
116
|
-
readonly feed?:
|
|
117
|
-
readonly role?: FeedRole;
|
|
118
|
-
/**
|
|
119
|
-
* Output being emitted in chunks.
|
|
120
|
-
* Feed should add these chunks to feed node.
|
|
121
|
-
*/
|
|
122
|
-
readonly streaming?: boolean;
|
|
123
|
-
};
|
|
117
|
+
readonly feed?: FeedPortMeta;
|
|
124
118
|
};
|
|
125
119
|
type WithWireType = {
|
|
126
120
|
readonly wireType: string;
|
|
@@ -133,4 +127,3 @@ export declare function configureOutput<T>(portId: string, stream: StatefulObser
|
|
|
133
127
|
export type OutputConfig = ReturnType<typeof configureOutput>;
|
|
134
128
|
/** Serializable descriptor for one output port — `OutputConfig['meta']`. */
|
|
135
129
|
export type OutputPortMeta = MetaFromStatefulObservable<OutputConfig>;
|
|
136
|
-
export {};
|
|
@@ -12,6 +12,18 @@ export type WireType = string & {
|
|
|
12
12
|
__brand: 'RuntimeWireType';
|
|
13
13
|
};
|
|
14
14
|
export type InputPortMode = 'single' | 'merge' | 'combine' | 'zip' | 'bypass';
|
|
15
|
+
export type FeedRole = 'none' | 'reasoning' | 'draft' | 'tool' | 'shell' | 'result' | 'recovery';
|
|
16
|
+
/**
|
|
17
|
+
* Feed projection metadata shared by input and output port contracts.
|
|
18
|
+
*
|
|
19
|
+
* `streaming: true` keeps the visit open (chunks / interleaved streams).
|
|
20
|
+
* Omit it so the frame closes the visit; same-node frames while last still
|
|
21
|
+
* append to that visit.
|
|
22
|
+
*/
|
|
23
|
+
export type FeedPortMeta = {
|
|
24
|
+
readonly role?: FeedRole;
|
|
25
|
+
readonly streaming?: boolean;
|
|
26
|
+
};
|
|
15
27
|
/**
|
|
16
28
|
* Typed metadata on every `statefulConnection` / `statefulObservable` `meta`.
|
|
17
29
|
* Must stay structurally identical to runtime `PortMeta`.
|
|
@@ -23,6 +35,7 @@ export type PortMeta = {
|
|
|
23
35
|
readonly mode?: InputPortMode;
|
|
24
36
|
readonly fromInput?: string;
|
|
25
37
|
readonly defaultValue?: unknown;
|
|
38
|
+
readonly feed?: FeedPortMeta;
|
|
26
39
|
};
|
|
27
40
|
/** Extract the `meta` type carried by a {@link StatefulObservable}. */
|
|
28
41
|
export type MetaFromStatefulObservable<T extends StatefulObservable<unknown, unknown, unknown>> = T extends StatefulObservable<unknown, unknown, infer Meta> ? Meta : never;
|
|
@@ -239,7 +239,7 @@ export class RuntimeRunner {
|
|
|
239
239
|
}
|
|
240
240
|
this.eventsSubject.next(event);
|
|
241
241
|
}
|
|
242
|
-
emitPortEvent(runId, kind, nodeId, portId, portIdx, state, value, edgeIds) {
|
|
242
|
+
emitPortEvent(runId, kind, nodeId, portId, portIdx, state, value, edgeIds, feed) {
|
|
243
243
|
this.emitRunnerEvent({
|
|
244
244
|
kind,
|
|
245
245
|
runId,
|
|
@@ -249,6 +249,7 @@ export class RuntimeRunner {
|
|
|
249
249
|
edgeIds,
|
|
250
250
|
state,
|
|
251
251
|
value,
|
|
252
|
+
...(feed !== undefined ? { feed } : {}),
|
|
252
253
|
});
|
|
253
254
|
}
|
|
254
255
|
canPreparePushedInput(node, portId) {
|
|
@@ -278,8 +279,10 @@ export class RuntimeRunner {
|
|
|
278
279
|
const key = slotKey(cfg.nodeId, cfg.portId, 0);
|
|
279
280
|
const existing = run.pushedInputSources.get(key);
|
|
280
281
|
if (existing !== undefined) {
|
|
281
|
-
|
|
282
|
+
// Telemetry before deliver so HITL input-received precedes cascade
|
|
283
|
+
// (output-emitted / downstream input-received) in the event log.
|
|
282
284
|
this.emitPortEvent(run.runId, 'input-received', cfg.nodeId, cfg.portId, 0, 'value', cfg.payload, []);
|
|
285
|
+
existing.source.next(cfg.payload);
|
|
283
286
|
return run.runId;
|
|
284
287
|
}
|
|
285
288
|
if (run.wiredSlotKeys.has(key)) {
|
|
@@ -297,8 +300,8 @@ export class RuntimeRunner {
|
|
|
297
300
|
connection,
|
|
298
301
|
});
|
|
299
302
|
run.pushedInputSources.set(key, { source });
|
|
300
|
-
source.next(cfg.payload);
|
|
301
303
|
this.emitPortEvent(run.runId, 'input-received', cfg.nodeId, cfg.portId, 0, 'value', cfg.payload, []);
|
|
304
|
+
source.next(cfg.payload);
|
|
302
305
|
return run.runId;
|
|
303
306
|
}
|
|
304
307
|
/**
|
|
@@ -323,7 +326,7 @@ export class RuntimeRunner {
|
|
|
323
326
|
if (resolved === null) {
|
|
324
327
|
return;
|
|
325
328
|
}
|
|
326
|
-
this.emitPortEvent(run.runId, 'output-emitted', nodeId, portId, 0, resolved.state, resolved.value, edgeIds);
|
|
329
|
+
this.emitPortEvent(run.runId, 'output-emitted', nodeId, portId, 0, resolved.state, resolved.value, edgeIds, output.meta.feed);
|
|
327
330
|
if (resolved.state === 'value' &&
|
|
328
331
|
node !== false &&
|
|
329
332
|
node.stopsRun === true) {
|
|
@@ -6,7 +6,7 @@ export { RuntimeEditor } from './runtime-editor.js';
|
|
|
6
6
|
export { graphHasCycle } from './runtime-helpers.js';
|
|
7
7
|
export type { GraphCluster } from './runtime-helpers.js';
|
|
8
8
|
export { RuntimeRunner } from './runtime-runner.js';
|
|
9
|
-
export type { MetaFromStatefulObservable, NodeId, EdgeId, RunId, PortMeta, RuntimeEdge, RuntimeEditorApi, RuntimeInputReceivedEvent, RuntimeNode, RuntimeOutputEmittedEvent, RuntimePortSignalState, RuntimeResumeOptions, RuntimeRunnerApi, RuntimeRunnerEvent, RuntimeRunnerStatus, RuntimeSeedPortValue, RuntimeWireType, } from './types.js';
|
|
9
|
+
export type { MetaFromStatefulObservable, NodeId, EdgeId, RunId, PortMeta, RuntimeEdge, RuntimeEditorApi, RuntimeFeedPortMeta, RuntimeFeedRole, RuntimeInputReceivedEvent, RuntimeNode, RuntimeOutputEmittedEvent, RuntimePortSignalState, RuntimeResumeOptions, RuntimeRunnerApi, RuntimeRunnerEvent, RuntimeRunnerStatus, RuntimeSeedPortValue, RuntimeWireType, } from './types.js';
|
|
10
10
|
export type { RuntimeOptions };
|
|
11
11
|
export declare class RuntimeFacade {
|
|
12
12
|
readonly editor: RuntimeEditor;
|
|
@@ -14,6 +14,12 @@ export type RunId = string & {
|
|
|
14
14
|
readonly __brand: 'RunId';
|
|
15
15
|
};
|
|
16
16
|
type RuntimeInputPortMode = 'single' | 'merge' | 'combine' | 'zip' | 'bypass';
|
|
17
|
+
export type RuntimeFeedRole = 'none' | 'reasoning' | 'draft' | 'tool' | 'shell' | 'result' | 'recovery';
|
|
18
|
+
export type RuntimeFeedPortMeta = {
|
|
19
|
+
readonly role?: RuntimeFeedRole;
|
|
20
|
+
/** When true, feed visit stays open for chunks / interleaved streams. */
|
|
21
|
+
readonly streaming?: boolean;
|
|
22
|
+
};
|
|
17
23
|
/**
|
|
18
24
|
* Typed metadata attached to every `statefulConnection` / `statefulObservable`
|
|
19
25
|
* via the `meta` field. Replaces the old string-encoding scheme.
|
|
@@ -35,6 +41,8 @@ export type PortMeta = {
|
|
|
35
41
|
* `node.inputs` for the canvas.
|
|
36
42
|
*/
|
|
37
43
|
readonly defaultValue?: unknown;
|
|
44
|
+
/** Author-supplied feed role / streaming hint for the work log. */
|
|
45
|
+
readonly feed?: RuntimeFeedPortMeta;
|
|
38
46
|
};
|
|
39
47
|
/**
|
|
40
48
|
* Extract the `meta` type carried by a {@link StatefulObservable} /
|
|
@@ -120,6 +128,8 @@ export type RuntimeOutputEmittedEvent = {
|
|
|
120
128
|
readonly edgeIds: EdgeId[];
|
|
121
129
|
readonly state: RuntimePortSignalState;
|
|
122
130
|
readonly value: unknown;
|
|
131
|
+
/** Resolved author feed contract carried with the runtime fact. */
|
|
132
|
+
readonly feed?: RuntimeFeedPortMeta;
|
|
123
133
|
};
|
|
124
134
|
export type RuntimeInputReceivedEvent = {
|
|
125
135
|
readonly kind: 'input-received';
|
|
@@ -130,6 +140,8 @@ export type RuntimeInputReceivedEvent = {
|
|
|
130
140
|
readonly edgeIds: EdgeId[];
|
|
131
141
|
readonly state: RuntimePortSignalState;
|
|
132
142
|
readonly value: unknown;
|
|
143
|
+
/** Resolved author feed contract carried with the runtime fact. */
|
|
144
|
+
readonly feed?: RuntimeFeedPortMeta;
|
|
133
145
|
};
|
|
134
146
|
export type RuntimeRunnerEvent = RuntimeOutputEmittedEvent | RuntimeInputReceivedEvent | {
|
|
135
147
|
/**
|
|
@@ -4,13 +4,16 @@ import type { LangflowerBridge } from './langflower-bridge.types.js';
|
|
|
4
4
|
export type { LangflowerBridge } from './langflower-bridge.types.js';
|
|
5
5
|
export type AttachLangflowerBridgeOptions = {
|
|
6
6
|
readonly onRunSettled?: (status: TerminalExecutionProgressStatus) => void;
|
|
7
|
+
/** Initial effective `serverLogs` gate (defaults to enabled). */
|
|
8
|
+
readonly serverLogsEnabled?: boolean;
|
|
7
9
|
};
|
|
8
10
|
/**
|
|
9
11
|
* Wire `langflowerWsConfig` handlers onto the WS bridge.
|
|
10
12
|
*
|
|
11
13
|
* Call order (see also `BRIDGE.md`):
|
|
14
|
+
* 0. Diagnostic bridge JSONL log (gated by effective `serverLogs`)
|
|
12
15
|
* 1. Always-on runner telemetry fan-out
|
|
13
16
|
* 2. Connect / disconnect → index + bootstrap snapshots
|
|
14
17
|
* 3. Intent handlers by bus namespace (workflow → palette → editor → runner)
|
|
15
18
|
*/
|
|
16
|
-
export declare const attachLangflowerBridge: (bridge: LangflowerBridge, context: ServerContext, options?: AttachLangflowerBridgeOptions) => (() => void);
|
|
19
|
+
export declare const attachLangflowerBridge: (bridge: LangflowerBridge, context: ServerContext, options?: AttachLangflowerBridgeOptions) => (() => Promise<void>);
|
|
@@ -3,19 +3,22 @@ import { Subscription } from 'rxjs';
|
|
|
3
3
|
import { RunCheckpointSession } from '../checkpoint/run-checkpoint-session.js';
|
|
4
4
|
import { LangflowerSession } from '../session/langflower-session.js';
|
|
5
5
|
import { clearClientIndex, indexClient, unindexClient, } from './client-index.js';
|
|
6
|
+
import { attachBridgeEventLog } from './bridge-event-log.js';
|
|
6
7
|
import { emitBootstrap } from './emit-bootstrap.js';
|
|
7
8
|
import { forwardRunnerEvent } from './forward-runner-event.js';
|
|
8
|
-
import { wireConfigHandlers } from './wire-config-handlers.js';
|
|
9
|
+
import { applyServerLogsGate, wireConfigHandlers, } from './wire-config-handlers.js';
|
|
9
10
|
import { wireEditorHandlers } from './wire-editor-handlers.js';
|
|
10
11
|
import { wireCustomPaletteHandlers } from './wire-custom-palette-handlers.js';
|
|
11
12
|
import { wirePaletteHandlers } from './wire-palette-handlers.js';
|
|
12
13
|
import { wireProjectBootstrapHandlers } from './wire-project-bootstrap-handlers.js';
|
|
13
14
|
import { wireRunnerHandlers } from './wire-runner-handlers.js';
|
|
14
15
|
import { wireWorkflowHandlers } from './wire-workflow-handlers.js';
|
|
16
|
+
import { createSettingsDraftController } from './settings-draft-controller.js';
|
|
15
17
|
/**
|
|
16
18
|
* Wire `langflowerWsConfig` handlers onto the WS bridge.
|
|
17
19
|
*
|
|
18
20
|
* Call order (see also `BRIDGE.md`):
|
|
21
|
+
* 0. Diagnostic bridge JSONL log (gated by effective `serverLogs`)
|
|
19
22
|
* 1. Always-on runner telemetry fan-out
|
|
20
23
|
* 2. Connect / disconnect → index + bootstrap snapshots
|
|
21
24
|
* 3. Intent handlers by bus namespace (workflow → palette → editor → runner)
|
|
@@ -25,6 +28,8 @@ export const attachLangflowerBridge = (bridge, context, options = {}) => {
|
|
|
25
28
|
const checkpoints = new RunCheckpointSession(context.projectDir);
|
|
26
29
|
const rootSubscription = new Subscription();
|
|
27
30
|
const onRunSettled = options.onRunSettled;
|
|
31
|
+
const diagnosticLog = attachBridgeEventLog(bridge, context.projectDir, rootSubscription, { enabled: options.serverLogsEnabled !== false });
|
|
32
|
+
const draftController = createSettingsDraftController(bridge, context, session);
|
|
28
33
|
// 1. Always-on telemetry: subscribe once for the whole bridge lifetime,
|
|
29
34
|
// independent of clients/runs. Guarantees initial `pending` events from
|
|
30
35
|
// `runner.start()` are captured — a later per-client subscription would
|
|
@@ -45,7 +50,7 @@ export const attachLangflowerBridge = (bridge, context, options = {}) => {
|
|
|
45
50
|
indexClient(bridge, client);
|
|
46
51
|
const subscriptions = new Subscription();
|
|
47
52
|
rootSubscription.add(subscriptions);
|
|
48
|
-
void emitBootstrap(client, context, session, checkpoints);
|
|
53
|
+
void emitBootstrap(client, context, session, checkpoints, draftController);
|
|
49
54
|
subscriptions.add(client.disconnected$.subscribe(() => {
|
|
50
55
|
subscriptions.unsubscribe();
|
|
51
56
|
unindexClient(bridge, client.id);
|
|
@@ -55,13 +60,19 @@ export const attachLangflowerBridge = (bridge, context, options = {}) => {
|
|
|
55
60
|
rootSubscription.add(wireWorkflowHandlers(bridge, context, session));
|
|
56
61
|
rootSubscription.add(wirePaletteHandlers(bridge, context));
|
|
57
62
|
rootSubscription.add(wireCustomPaletteHandlers(bridge, context));
|
|
58
|
-
rootSubscription.add(wireConfigHandlers(bridge, context
|
|
63
|
+
rootSubscription.add(wireConfigHandlers(bridge, context, session, draftController, {
|
|
64
|
+
onEffectiveConfig: (config) => {
|
|
65
|
+
applyServerLogsGate(diagnosticLog.setEnabled, config);
|
|
66
|
+
},
|
|
67
|
+
}));
|
|
59
68
|
rootSubscription.add(wireProjectBootstrapHandlers(bridge, context, session));
|
|
60
|
-
rootSubscription.add(wireEditorHandlers(bridge, context, session));
|
|
69
|
+
rootSubscription.add(wireEditorHandlers(bridge, context, session, draftController));
|
|
61
70
|
rootSubscription.add(wireRunnerHandlers(bridge, context, session, checkpoints));
|
|
62
|
-
return () => {
|
|
71
|
+
return async () => {
|
|
72
|
+
diagnosticLog.writeServerClosing();
|
|
63
73
|
rootSubscription.unsubscribe();
|
|
64
74
|
clearClientIndex(bridge);
|
|
65
75
|
session.dispose();
|
|
76
|
+
await diagnosticLog.flush();
|
|
66
77
|
};
|
|
67
78
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Subscription } from 'rxjs';
|
|
2
|
+
import type { LangflowerBridge } from './langflower-bridge.types.js';
|
|
3
|
+
export type BridgeEventLog = {
|
|
4
|
+
readonly filePath: string;
|
|
5
|
+
setEnabled(enabled: boolean): void;
|
|
6
|
+
writeServerClosing(): void;
|
|
7
|
+
flush(): Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
export type AttachBridgeEventLogOptions = {
|
|
10
|
+
readonly enabled?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare const attachBridgeEventLog: (bridge: LangflowerBridge, projectDir: string, subscriptions: Subscription, options?: AttachBridgeEventLogOptions) => BridgeEventLog;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { langflowerWsConfig } from '@langflower/shared/langflower.js';
|
|
5
|
+
import { Subscription } from 'rxjs';
|
|
6
|
+
const REDACTED = '[REDACTED]';
|
|
7
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
const isSecretKey = (key) => {
|
|
9
|
+
const normalized = key.toLowerCase();
|
|
10
|
+
return (normalized.includes('apikey') ||
|
|
11
|
+
normalized.includes('authorization') ||
|
|
12
|
+
normalized.includes('cookie') ||
|
|
13
|
+
normalized.includes('token') ||
|
|
14
|
+
normalized.includes('password') ||
|
|
15
|
+
normalized.includes('secret') ||
|
|
16
|
+
normalized.includes('credential'));
|
|
17
|
+
};
|
|
18
|
+
const sanitize = (value, seen = new WeakSet()) => {
|
|
19
|
+
if (value === null ||
|
|
20
|
+
typeof value === 'string' ||
|
|
21
|
+
typeof value === 'number' ||
|
|
22
|
+
typeof value === 'boolean') {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
if (typeof value === 'bigint') {
|
|
26
|
+
return `${value}n`;
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === 'undefined') {
|
|
29
|
+
return '[undefined]';
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === 'symbol' || typeof value === 'function') {
|
|
32
|
+
return `[${typeof value}]`;
|
|
33
|
+
}
|
|
34
|
+
if (value instanceof Error) {
|
|
35
|
+
return {
|
|
36
|
+
name: value.name,
|
|
37
|
+
message: value.message,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (value instanceof Date) {
|
|
41
|
+
return value.toISOString();
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === 'object') {
|
|
44
|
+
if (seen.has(value)) {
|
|
45
|
+
return '[circular]';
|
|
46
|
+
}
|
|
47
|
+
seen.add(value);
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
return value.map((entry) => sanitize(entry, seen));
|
|
51
|
+
}
|
|
52
|
+
if (isRecord(value)) {
|
|
53
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
54
|
+
key,
|
|
55
|
+
isSecretKey(key) ? REDACTED : sanitize(entry, seen),
|
|
56
|
+
]));
|
|
57
|
+
}
|
|
58
|
+
return String(value);
|
|
59
|
+
};
|
|
60
|
+
const toSafeTimestamp = (date) => date.toISOString().replaceAll(':', '-').replaceAll('.', '-');
|
|
61
|
+
const asObservable = (value) => isRecord(value) && typeof value.subscribe === 'function'
|
|
62
|
+
? value
|
|
63
|
+
: undefined;
|
|
64
|
+
const asInboundEvent = (value) => isRecord(value) && typeof value.clientId === 'string' && 'payload' in value
|
|
65
|
+
? { clientId: value.clientId, payload: value.payload }
|
|
66
|
+
: undefined;
|
|
67
|
+
const logPath = (projectDir) => path.join(projectDir, '.langflower', 'logs', `${toSafeTimestamp(new Date())}-${process.pid}-${randomUUID()}.log`);
|
|
68
|
+
export const attachBridgeEventLog = (bridge, projectDir, subscriptions, options = {}) => {
|
|
69
|
+
const filePath = logPath(projectDir);
|
|
70
|
+
let enabled = options.enabled !== false;
|
|
71
|
+
let acceptingWrites = true;
|
|
72
|
+
let closed = false;
|
|
73
|
+
let reportedFailure = false;
|
|
74
|
+
let directoryReady = false;
|
|
75
|
+
let writeQueue = Promise.resolve();
|
|
76
|
+
const reportFailure = (error) => {
|
|
77
|
+
if (reportedFailure) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
reportedFailure = true;
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
process.stderr.write(`Langflower diagnostic logging disabled: ${message}\n`);
|
|
83
|
+
};
|
|
84
|
+
const write = (record) => {
|
|
85
|
+
if (!enabled || !acceptingWrites || closed) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const line = `${JSON.stringify(sanitize({
|
|
89
|
+
schemaVersion: 1,
|
|
90
|
+
timestamp: new Date().toISOString(),
|
|
91
|
+
...record,
|
|
92
|
+
}))}\n`;
|
|
93
|
+
writeQueue = writeQueue.then(async () => {
|
|
94
|
+
if (!enabled || !acceptingWrites) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
if (!directoryReady) {
|
|
99
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
100
|
+
directoryReady = true;
|
|
101
|
+
}
|
|
102
|
+
await fs.appendFile(filePath, line, 'utf8');
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
acceptingWrites = false;
|
|
106
|
+
reportFailure(error);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
const bridgeChannels = bridge;
|
|
111
|
+
const inboundKeys = Object.keys(langflowerWsConfig.fromClientToServer);
|
|
112
|
+
const outboundKeys = Object.keys(langflowerWsConfig.fromServerToClient);
|
|
113
|
+
for (const type of inboundKeys) {
|
|
114
|
+
const channel = asObservable(bridgeChannels[type]);
|
|
115
|
+
if (channel === undefined) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
subscriptions.add(channel.subscribe((value) => {
|
|
119
|
+
const event = asInboundEvent(value);
|
|
120
|
+
if (event !== undefined) {
|
|
121
|
+
write({
|
|
122
|
+
kind: 'frame',
|
|
123
|
+
direction: 'inbound',
|
|
124
|
+
clientId: event.clientId,
|
|
125
|
+
type,
|
|
126
|
+
payload: event.payload,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
for (const type of outboundKeys) {
|
|
132
|
+
const channel = asObservable(bridgeChannels[type]);
|
|
133
|
+
if (channel === undefined) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
subscriptions.add(channel.subscribe((payload) => {
|
|
137
|
+
write({
|
|
138
|
+
kind: 'frame',
|
|
139
|
+
direction: 'outbound',
|
|
140
|
+
scope: 'broadcast',
|
|
141
|
+
type,
|
|
142
|
+
payload,
|
|
143
|
+
});
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
subscriptions.add(bridge.status$.subscribe((status) => {
|
|
147
|
+
write({ kind: 'status', status });
|
|
148
|
+
}));
|
|
149
|
+
subscriptions.add(bridge.errors$.subscribe((error) => {
|
|
150
|
+
write({
|
|
151
|
+
kind: 'error',
|
|
152
|
+
error: {
|
|
153
|
+
code: error.code,
|
|
154
|
+
message: error.message,
|
|
155
|
+
...('cause' in error ? { cause: error.cause } : {}),
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}));
|
|
159
|
+
subscriptions.add(bridge.connections$.subscribe((client) => {
|
|
160
|
+
write({ kind: 'connection', clientId: client.id });
|
|
161
|
+
const clientSubscriptions = new Subscription();
|
|
162
|
+
subscriptions.add(clientSubscriptions);
|
|
163
|
+
const clientChannels = client;
|
|
164
|
+
for (const type of outboundKeys) {
|
|
165
|
+
const channel = asObservable(clientChannels[type]);
|
|
166
|
+
if (channel === undefined) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
clientSubscriptions.add(channel.subscribe((payload) => {
|
|
170
|
+
write({
|
|
171
|
+
kind: 'frame',
|
|
172
|
+
direction: 'outbound',
|
|
173
|
+
scope: 'client',
|
|
174
|
+
clientId: client.id,
|
|
175
|
+
type,
|
|
176
|
+
payload,
|
|
177
|
+
});
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
clientSubscriptions.add(client.disconnected$.subscribe(() => {
|
|
181
|
+
write({ kind: 'disconnection', clientId: client.id });
|
|
182
|
+
clientSubscriptions.unsubscribe();
|
|
183
|
+
}));
|
|
184
|
+
}));
|
|
185
|
+
write({ kind: 'server-started' });
|
|
186
|
+
return {
|
|
187
|
+
filePath,
|
|
188
|
+
setEnabled: (nextEnabled) => {
|
|
189
|
+
enabled = nextEnabled;
|
|
190
|
+
},
|
|
191
|
+
writeServerClosing: () => {
|
|
192
|
+
write({ kind: 'server-closing' });
|
|
193
|
+
closed = true;
|
|
194
|
+
},
|
|
195
|
+
flush: async () => {
|
|
196
|
+
await writeQueue;
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
};
|
|
@@ -2,7 +2,7 @@ import { type ExecutionContext } from '@langflower/node-sdk';
|
|
|
2
2
|
import type { LlmExecutionCaps } from '@langflower/node-sdk/llm';
|
|
3
3
|
import type { McpHandle } from '@langflower/node-sdk/mcp';
|
|
4
4
|
import type { RuntimeSeedPortValue } from '@langflower/runtime';
|
|
5
|
-
import type
|
|
5
|
+
import { type LangflowerConfig, type RunnerPermissionAskPayload } from '@langflower/shared/langflower.js';
|
|
6
6
|
import type { ServerContext } from '../server-context.js';
|
|
7
7
|
import type { LangflowerSession } from '../session/langflower-session.js';
|
|
8
8
|
type WorkflowGraphNode = {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseLlmRolePreset, resolveEffectiveSkillId, resolveEffectiveToolPermissions, toolPermissionsToEnabledIds, } from '@langflower/common-nodes/ai/llm-role-preset';
|
|
2
2
|
import { attachRunHostServices } from '@langflower/common-nodes/ai/run-host-services';
|
|
3
3
|
import { contextSymbol, } from '@langflower/node-sdk';
|
|
4
|
+
import { parseDefaultChatModel, } from '@langflower/shared/langflower.js';
|
|
4
5
|
import { collectEnabledMcpIdsFromNodes, createSystemMcpHandles, filterMcpFailuresForNode, filterMcpHandlesByIds, parseEnabledMcpIds, } from '@langflower/tools/create-system-mcp-handles';
|
|
5
6
|
import { createProjectHarness, } from '@langflower/tools/create-project-harness';
|
|
6
7
|
import { createWebFetch } from '@langflower/tools/create-web-fetch';
|
|
@@ -79,12 +80,14 @@ export const buildExecutionContext = async (context, runId, node, hooks, preload
|
|
|
79
80
|
...(toolHandles.length > 0 ? { toolHandles } : {}),
|
|
80
81
|
...(mcpHandles.length > 0 ? { mcpHandles } : {}),
|
|
81
82
|
};
|
|
83
|
+
const defaultChat = parseDefaultChatModel(config.model);
|
|
82
84
|
return attachRunHostServices(base, {
|
|
83
85
|
...(node.type !== 'common-fake-llm'
|
|
84
86
|
? {
|
|
85
87
|
createChatCompletionStream: bindCreateChatCompletionStream(context.langflowerConfigService),
|
|
86
88
|
}
|
|
87
89
|
: {}),
|
|
90
|
+
...(defaultChat !== null ? { defaultChat } : {}),
|
|
88
91
|
...(skillMarkdown.length > 0 ? { skillMarkdown } : {}),
|
|
89
92
|
...(agentsMarkdown.length > 0 ? { agentsMarkdown } : {}),
|
|
90
93
|
...(toolHarness.authorize !== undefined
|
|
@@ -2,13 +2,15 @@ import type { RunCheckpointSession } from '../checkpoint/run-checkpoint-session.
|
|
|
2
2
|
import type { ServerContext } from '../server-context.js';
|
|
3
3
|
import { LangflowerSession } from '../session/langflower-session.js';
|
|
4
4
|
import type { LangflowerClient } from './langflower-bridge.types.js';
|
|
5
|
+
import type { SettingsDraftController } from './settings-draft-controller.js';
|
|
5
6
|
/**
|
|
6
7
|
* Connect / reconnect bootstrap emit order (authoritative for docs):
|
|
7
8
|
* session.state.snapshot → runner.snapshot → executionFeed.snapshot →
|
|
8
9
|
* runner.checkpoints.snapshot → toolConfig.snapshot →
|
|
9
10
|
* workflow.list/current.snapshot → session.ready →
|
|
10
|
-
* langflower.config.snapshot →
|
|
11
|
+
* langflower.config.snapshot → langflower.config.draft.snapshot →
|
|
12
|
+
* (async) langflower.models.catalog.snapshot →
|
|
11
13
|
* permission.ask replay → palette.snapshot → customPalette.snapshot
|
|
12
14
|
* (custom snapshot is warm from createServer — emit only, no compile).
|
|
13
15
|
*/
|
|
14
|
-
export declare const emitBootstrap: (client: LangflowerClient, context: ServerContext, session: LangflowerSession, checkpoints: RunCheckpointSession) => Promise<void>;
|
|
16
|
+
export declare const emitBootstrap: (client: LangflowerClient, context: ServerContext, session: LangflowerSession, checkpoints: RunCheckpointSession, draftController?: SettingsDraftController) => Promise<void>;
|
|
@@ -11,11 +11,12 @@ import { pushModelsCatalogToClient } from './push-models-catalog.js';
|
|
|
11
11
|
* session.state.snapshot → runner.snapshot → executionFeed.snapshot →
|
|
12
12
|
* runner.checkpoints.snapshot → toolConfig.snapshot →
|
|
13
13
|
* workflow.list/current.snapshot → session.ready →
|
|
14
|
-
* langflower.config.snapshot →
|
|
14
|
+
* langflower.config.snapshot → langflower.config.draft.snapshot →
|
|
15
|
+
* (async) langflower.models.catalog.snapshot →
|
|
15
16
|
* permission.ask replay → palette.snapshot → customPalette.snapshot
|
|
16
17
|
* (custom snapshot is warm from createServer — emit only, no compile).
|
|
17
18
|
*/
|
|
18
|
-
export const emitBootstrap = async (client, context, session, checkpoints) => {
|
|
19
|
+
export const emitBootstrap = async (client, context, session, checkpoints, draftController) => {
|
|
19
20
|
const langflowerConfig = await context.langflowerConfigService.read();
|
|
20
21
|
if (langflowerConfig.currentWorkflowId !== undefined &&
|
|
21
22
|
session.activeWorkflow === null) {
|
|
@@ -56,6 +57,9 @@ export const emitBootstrap = async (client, context, session, checkpoints) => {
|
|
|
56
57
|
version: LangflowerSession.sessionReadyVersion,
|
|
57
58
|
});
|
|
58
59
|
clientEmit(client, 'langflower.config.snapshot', await buildLangflowerConfigSnapshot(context));
|
|
60
|
+
if (draftController !== undefined) {
|
|
61
|
+
await draftController.pushToClient(client, session.settings.scope);
|
|
62
|
+
}
|
|
59
63
|
// Live model catalogs — do not block palette / connect on models.list().
|
|
60
64
|
void pushModelsCatalogToClient(client, context);
|
|
61
65
|
// Re-surface in-flight permission asks after reconnect (tool loop still waiting).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { LangflowerConfig, LangflowerConfigDraftSnapshotPayload, LangflowerConfigSaveRequestedPayload, LangflowerConfigScope, SettingsDraft } from '@langflower/shared/langflower.js';
|
|
2
|
+
import type { ServerContext } from '../server-context.js';
|
|
3
|
+
import type { LangflowerSession } from '../session/langflower-session.js';
|
|
4
|
+
import type { LangflowerBridge, LangflowerClient } from './langflower-bridge.types.js';
|
|
5
|
+
export type SettingsDraftController = {
|
|
6
|
+
readonly ensureSeeded: (scope: LangflowerConfigScope) => Promise<void>;
|
|
7
|
+
readonly snapshotFor: (scope: LangflowerConfigScope) => Promise<LangflowerConfigDraftSnapshotPayload | null>;
|
|
8
|
+
readonly broadcast: (scope: LangflowerConfigScope) => Promise<void>;
|
|
9
|
+
readonly pushToClient: (client: LangflowerClient, scope: LangflowerConfigScope) => Promise<void>;
|
|
10
|
+
readonly patch: (scope: LangflowerConfigScope, draft: SettingsDraft) => Promise<void>;
|
|
11
|
+
readonly discard: (scope: LangflowerConfigScope) => Promise<void>;
|
|
12
|
+
readonly commitSave: (scope: LangflowerConfigScope, fallback?: LangflowerConfigSaveRequestedPayload) => Promise<LangflowerConfig | null>;
|
|
13
|
+
};
|
|
14
|
+
export declare const createSettingsDraftController: (bridge: LangflowerBridge, context: ServerContext, session: LangflowerSession) => SettingsDraftController;
|