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.
Files changed (98) hide show
  1. package/README.md +20 -15
  2. package/docs/public/README.md +15 -0
  3. package/docs/public/configuration.md +56 -0
  4. package/docs/public/extending.md +58 -0
  5. package/docs/public/getting-started.md +61 -0
  6. package/docs/public/how-it-works.md +39 -0
  7. package/docs/public/product.md +43 -0
  8. package/docs/public/using-the-editor.md +53 -0
  9. package/docs/public/workflows.md +49 -0
  10. package/package.json +3 -2
  11. package/ui-dist/chunk-6DQ2XSRW.js +4 -0
  12. package/ui-dist/{chunk-2AICAC67.js → chunk-Y6VMBLSJ.js} +1 -1
  13. package/ui-dist/index.html +2 -2
  14. package/ui-dist/main-RCFTD5AZ.js +311 -0
  15. package/ui-dist/styles-EOXODOI7.css +1 -0
  16. package/vendor/common-nodes/dist/ai/critique/node.d.ts +141 -23
  17. package/vendor/common-nodes/dist/ai/critique/node.js +33 -31
  18. package/vendor/common-nodes/dist/ai/fake-llm/node.d.ts +6 -6
  19. package/vendor/common-nodes/dist/ai/llm-compaction-ui-schema.d.ts +1 -1
  20. package/vendor/common-nodes/dist/ai/llm-compaction-ui-schema.js +2 -1
  21. package/vendor/common-nodes/dist/ai/llm-panel-ui-schema.d.ts +7 -21
  22. package/vendor/common-nodes/dist/ai/llm-panel-ui-schema.js +8 -4
  23. package/vendor/common-nodes/dist/ai/llm-session-shell.d.ts +2 -0
  24. package/vendor/common-nodes/dist/ai/llm-session-shell.js +3 -3
  25. package/vendor/common-nodes/dist/ai/normalize-max-iterations.d.ts +4 -3
  26. package/vendor/common-nodes/dist/ai/normalize-max-iterations.js +4 -3
  27. package/vendor/common-nodes/dist/ai/openai/normalize-compaction-params.d.ts +2 -2
  28. package/vendor/common-nodes/dist/ai/openai/normalize-compaction-params.js +1 -1
  29. package/vendor/common-nodes/dist/ai/openai-llm/node.d.ts +9 -9
  30. package/vendor/common-nodes/dist/ai/resolve-chat-provider-model.d.ts +10 -0
  31. package/vendor/common-nodes/dist/ai/resolve-chat-provider-model.js +8 -0
  32. package/vendor/common-nodes/dist/ai/review/node.d.ts +138 -15
  33. package/vendor/common-nodes/dist/ai/review/node.js +29 -22
  34. package/vendor/common-nodes/dist/ai/run-host-services.d.ts +8 -0
  35. package/vendor/common-nodes/dist/ai/sub-agent/node.d.ts +9 -9
  36. package/vendor/common-nodes/dist/ai/sub-agent/node.js +4 -3
  37. package/vendor/common-nodes/dist/hitl/chat-input/node.js +1 -0
  38. package/vendor/common-nodes/dist/hitl/review-gate/node.js +5 -0
  39. package/vendor/node-sdk/dist/node-factory/define-llm-node/default-llm-ports.js +2 -0
  40. package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-reactive-node.d.ts +1 -1
  41. package/vendor/node-sdk/dist/node-factory/define-reactive-node/io-helpers.d.ts +5 -12
  42. package/vendor/node-sdk/dist/node-factory/define-reactive-node/port-meta.d.ts +13 -0
  43. package/vendor/runtime/dist/runtime-runner.js +7 -4
  44. package/vendor/runtime/dist/runtime.d.ts +1 -1
  45. package/vendor/runtime/dist/types.d.ts +12 -0
  46. package/vendor/server/dist/bridge/attach-langflower-bridge.d.ts +4 -1
  47. package/vendor/server/dist/bridge/attach-langflower-bridge.js +16 -5
  48. package/vendor/server/dist/bridge/bridge-event-log.d.ts +12 -0
  49. package/vendor/server/dist/bridge/bridge-event-log.js +199 -0
  50. package/vendor/server/dist/bridge/build-execution-context.d.ts +1 -1
  51. package/vendor/server/dist/bridge/build-execution-context.js +3 -0
  52. package/vendor/server/dist/bridge/emit-bootstrap.d.ts +4 -2
  53. package/vendor/server/dist/bridge/emit-bootstrap.js +6 -2
  54. package/vendor/server/dist/bridge/settings-draft-controller.d.ts +14 -0
  55. package/vendor/server/dist/bridge/settings-draft-controller.js +178 -0
  56. package/vendor/server/dist/bridge/wire-config-handlers.d.ts +8 -1
  57. package/vendor/server/dist/bridge/wire-config-handlers.js +44 -16
  58. package/vendor/server/dist/bridge/wire-editor-handlers.d.ts +8 -1
  59. package/vendor/server/dist/bridge/wire-editor-handlers.js +40 -1
  60. package/vendor/server/dist/bridge/wire-runner-handlers.js +3 -1
  61. package/vendor/server/dist/config/langflower-config.service.d.ts +2 -0
  62. package/vendor/server/dist/config/langflower-config.service.js +24 -1
  63. package/vendor/server/dist/config/resolve-draft-provider-credentials.d.ts +18 -0
  64. package/vendor/server/dist/config/resolve-draft-provider-credentials.js +60 -0
  65. package/vendor/server/dist/create-server.js +5 -3
  66. package/vendor/server/dist/session/build-session-bootstrap.d.ts +3 -1
  67. package/vendor/server/dist/session/build-session-bootstrap.js +9 -0
  68. package/vendor/server/dist/session/langflower-session.d.ts +7 -2
  69. package/vendor/server/dist/session/langflower-session.js +9 -1
  70. package/vendor/server/dist/session/settings-draft-session.d.ts +24 -0
  71. package/vendor/server/dist/session/settings-draft-session.js +82 -0
  72. package/vendor/server/skeleton/schemas/langflower-config.schema.json +4 -0
  73. package/vendor/server/skeleton/skills/langflower-helper/SKILL.md +20 -8
  74. package/vendor/server/skeleton/skills/langflower-helper/architecture.md +5 -0
  75. package/vendor/server/skeleton/skills/langflower-helper/layout.md +18 -16
  76. package/vendor/server/skeleton/workflows/simple-coder.json +249 -19
  77. package/vendor/server/skeleton/workflows/starter.json +31 -20
  78. package/vendor/shared/dist/langflower-bus-config.d.ts +50 -10
  79. package/vendor/shared/dist/langflower-bus-config.js +52 -11
  80. package/vendor/shared/dist/langflower-config/merge-langflower-config-layers.test.js +4 -0
  81. package/vendor/shared/dist/langflower-config/parse-default-chat-model.d.ts +14 -0
  82. package/vendor/shared/dist/langflower-config/parse-default-chat-model.js +38 -0
  83. package/vendor/shared/dist/langflower-config/parse-default-chat-model.test.d.ts +1 -0
  84. package/vendor/shared/dist/langflower-config/parse-default-chat-model.test.js +41 -0
  85. package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.d.ts +6 -0
  86. package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.js +5 -0
  87. package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.test.d.ts +1 -0
  88. package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.test.js +11 -0
  89. package/vendor/shared/dist/langflower-config/settings-draft.d.ts +44 -0
  90. package/vendor/shared/dist/langflower-config/settings-draft.js +143 -0
  91. package/vendor/shared/dist/langflower.d.ts +7 -2
  92. package/vendor/shared/dist/langflower.js +3 -0
  93. package/vendor/shared/dist/types/langflower-bootstrap.d.ts +6 -1
  94. package/vendor/shared/dist/types/langflower-config.d.ts +65 -0
  95. package/vendor/shared/dist/types/langflower-editor.d.ts +19 -0
  96. package/ui-dist/chunk-6ZABW4JL.js +0 -4
  97. package/ui-dist/main-XI7IOZKZ.js +0 -311
  98. 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
- existing.source.next(cfg.payload);
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 { LangflowerConfig, RunnerPermissionAskPayload } from '@langflower/shared/langflower.js';
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 → (async) langflower.models.catalog.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 → (async) langflower.models.catalog.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;