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
@@ -4,15 +4,15 @@
4
4
  "name": "Starter",
5
5
  "description": "Chat loop with an onboarding agent (skill langflower-helper) plus a Writer Sub-Agent (workflow-writer + node-writer). After bootstrap — product capabilities, workflows, editor chrome, and custom nodes.",
6
6
  "createdAt": "2026-07-24T00:00:00.000Z",
7
- "updatedAt": "2026-07-29T00:00:00.000Z"
7
+ "updatedAt": "2026-08-05T16:18:12.801Z"
8
8
  },
9
9
  "graph": {
10
10
  "viewport": {
11
- "x": 0,
12
- "y": 0,
11
+ "x": -117.86676025390625,
12
+ "y": -161.066650390625,
13
13
  "scale": 0.85,
14
- "width": 1100,
15
- "height": 800
14
+ "width": 719.0625,
15
+ "height": 1005.328125
16
16
  },
17
17
  "nodes": [
18
18
  {
@@ -36,9 +36,18 @@
36
36
  "skillId": "langflower-helper",
37
37
  "providerId": "",
38
38
  "model": "",
39
- "enabledToolIds": ["read", "glob", "grep"],
40
39
  "maxIterations": 8,
41
- "maxFeedbackTurns": 4
40
+ "maxFeedbackTurns": 4,
41
+ "toolPermissions": {
42
+ "read": "allow",
43
+ "glob": "allow",
44
+ "grep": "allow",
45
+ "edit": "allow",
46
+ "write": "allow",
47
+ "create": "allow",
48
+ "delete": "ask",
49
+ "bash": "ask"
50
+ }
42
51
  },
43
52
  "inputs": {
44
53
  "systemPrompt": "You are the Langflower onboarding assistant. Use the langflower-helper skill. The user already started Langflower and configured a provider — do not lead with CLI start or provider setup. Help with what Langflower can do, workflows, editor chrome, and custom nodes. Keep answers short and actionable.\n\nWhen the user asks to draft or edit a workflow JSON or a custom node pack, spawn_subagent the Writer specialist (skills langflower-workflow-writer and langflower-node-writer) with a clear task; then summarize the Writer result for the user."
@@ -64,25 +73,27 @@
64
73
  "rolePreset": "custom",
65
74
  "providerId": "",
66
75
  "model": "",
67
- "enabledToolIds": [
68
- "read",
69
- "glob",
70
- "grep",
71
- "write",
72
- "edit",
73
- "create"
74
- ],
75
76
  "maxIterations": 16,
76
77
  "contextSize": 32768,
77
- "compactOnError": false
78
+ "compactOnError": false,
79
+ "toolPermissions": {
80
+ "read": "allow",
81
+ "glob": "allow",
82
+ "grep": "allow",
83
+ "edit": "allow",
84
+ "write": "allow",
85
+ "create": "allow",
86
+ "delete": "ask",
87
+ "bash": "ask"
88
+ }
78
89
  },
79
90
  "inputs": {
80
91
  "systemPrompt": "You are Writer. Follow langflower-workflow-writer and/or langflower-node-writer for the task. Prefer editing files under .langflower/workflows/ and .langflower/nodes/. Use only catalog node types and real ports. Return a short summary of files changed and any open risks."
81
92
  },
82
93
  "ui": {
83
94
  "position": {
84
- "x": 580,
85
- "y": 40
95
+ "x": 320,
96
+ "y": 1060
86
97
  },
87
98
  "label": "Writer"
88
99
  }
@@ -94,8 +105,8 @@
94
105
  "inputs": {},
95
106
  "ui": {
96
107
  "position": {
97
- "x": 580,
98
- "y": 320
108
+ "x": 640,
109
+ "y": 720
99
110
  },
100
111
  "label": "Review"
101
112
  }
@@ -1,8 +1,8 @@
1
1
  import type { RuntimeEdge, RuntimeRunnerEvent } from '@langflower/runtime';
2
2
  import type { DividerPositions, ExecutionFeedSnapshotPayload, RunnerSnapshotPayload, SessionStateSnapshotPayload, ToolConfigSnapshotPayload } from './types/langflower-bootstrap.js';
3
- import type { LangflowerConfigSaveRequestedPayload, LangflowerConfigSnapshotPayload, LangflowerModelsCatalogSnapshotPayload, RunnerPermissionAskPayload, RunnerPermissionReplyPayload } from './types/langflower-config.js';
3
+ import type { LangflowerConfigDraftDiscardRequestedPayload, LangflowerConfigDraftPatchRequestedPayload, LangflowerConfigDraftSnapshotPayload, LangflowerConfigSaveRequestedPayload, LangflowerConfigSnapshotPayload, LangflowerModelsCatalogSnapshotPayload, RunnerPermissionAskPayload, RunnerPermissionReplyPayload } from './types/langflower-config.js';
4
4
  import type { RunnerCheckpointDiscardRequestedPayload, RunnerCheckpointsSnapshotPayload, RunnerResumeFailedPayload, RunnerResumeRequestedPayload, WorkflowCheckpointSummary } from './types/workflow-checkpoint.js';
5
- import type { EditorAddEdgeRequestedPayload, EditorAddNodeRequestedPayload, EditorPasteRequestedPayload, EditorSelectedNodePayload, EditorSelectNodeRequestedPayload, EditorUpdateNodeRequestedPayload } from './types/langflower-editor.js';
5
+ import type { EditorAddEdgeRequestedPayload, EditorAddNodeRequestedPayload, EditorPasteRequestedPayload, EditorSelectedNodePayload, EditorSelectNodeRequestedPayload, EditorSettingsRequestedPayload, EditorSettingsSnapshotPayload, EditorUpdateNodeRequestedPayload } from './types/langflower-editor.js';
6
6
  import type { CustomPaletteSnapshotPayload } from './types/langflower-custom-palette.js';
7
7
  import type { PaletteConfigPayload } from './types/langflower-palette.js';
8
8
  import type { ProjectBootstrapResultPayload } from './types/langflower-project-bootstrap.js';
@@ -31,7 +31,7 @@ import type { CanvasViewport, WorkflowCurrentSnapshotPayload, WorkflowCurrentSta
31
31
  *
32
32
  * | Domain | Model | Reconnect | Live updates |
33
33
  * | ------ | ----- | --------- | -------------- |
34
- * | Session chrome (dividers, selection) + effective `langflowerConfig` | **snapshot only** | slim `session.state.snapshot` | dividers / selection via `editor.dividers.snapshot` / `editor.nodeSelected` |
34
+ * | Session chrome (dividers, selection, settings) + effective `langflowerConfig` | **snapshot only** | slim `session.state.snapshot` | dividers / selection / settings via `editor.dividers.snapshot` / `editor.nodeSelected` / `editor.settings.snapshot` |
35
35
  * | Langflower project config (full layers) | **snapshot only** | `langflower.config.snapshot` after `session.ready`; effective also in slim session | full slice replace |
36
36
  * | Live provider model catalogs | **snapshot only** | `langflower.models.catalog.snapshot` after config (async on connect) + after Save | full map replace |
37
37
  * | Tool config | **snapshot only** | `toolConfig.snapshot` | full slice replace |
@@ -113,11 +113,20 @@ export declare const langflowerWsConfig: {
113
113
  */
114
114
  readonly 'palette.reload.requested': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<Record<string, never>>;
115
115
  /**
116
- * Settings Save — persist one scope (project or global), then
117
- * broadcast {@link LangflowerConfigSnapshotPayload}. No parallel
118
- * `settings.*` bus.
116
+ * Settings Save — persist the **session draft** for one scope (or the
117
+ * payload when no draft is seeded), then broadcast
118
+ * {@link LangflowerConfigSnapshotPayload} and draft snapshot.
119
119
  */
120
120
  readonly 'langflower.config.save.requested': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<LangflowerConfigSaveRequestedPayload>;
121
+ /**
122
+ * Replace the session Settings draft for a scope. Server probes
123
+ * connection when a provider `baseURL` / `apiKey` changes.
124
+ */
125
+ readonly 'langflower.config.draft.patch.requested': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<LangflowerConfigDraftPatchRequestedPayload>;
126
+ /**
127
+ * Reset the session draft from the saved layer for a scope.
128
+ */
129
+ readonly 'langflower.config.draft.discard.requested': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<LangflowerConfigDraftDiscardRequestedPayload>;
121
130
  /**
122
131
  * Settings → Bootstrap — force-reseed skeleton templates into the
123
132
  * open project (workflows / skills / my-nodes / instructions).
@@ -259,8 +268,19 @@ export declare const langflowerWsConfig: {
259
268
  *
260
269
  * Server validates `nodeId` against the active graph, stores it on
261
270
  * the session, and broadcasts `editor.nodeSelected` to all tabs.
271
+ * Selecting a node while Settings is open also closes Settings
272
+ * (`editor.settings.snapshot` with `open: false`).
262
273
  */
263
274
  readonly 'editor.selectNode.requested': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<EditorSelectNodeRequestedPayload>;
275
+ /**
276
+ * Settings aside open/close + scope intent.
277
+ *
278
+ * When `open: true`, `scope` is required (`project` | `global`).
279
+ * When `open: false`, server keeps the prior scope. Session memory
280
+ * only — not written to `langflower.jsonc`. Broadcasts
281
+ * `editor.settings.snapshot` to all tabs.
282
+ */
283
+ readonly 'editor.settings.requested': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<EditorSettingsRequestedPayload>;
264
284
  };
265
285
  readonly fromServerToClient: {
266
286
  /**
@@ -305,6 +325,12 @@ export declare const langflowerWsConfig: {
305
325
  * and after Settings Save. Includes effective + layer slices.
306
326
  */
307
327
  readonly 'langflower.config.snapshot': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<LangflowerConfigSnapshotPayload>;
328
+ /**
329
+ * Authoritative unsaved Settings draft + connection statuses for one
330
+ * scope. Session memory; broadcast after patch / discard / save /
331
+ * scope change / connect.
332
+ */
333
+ readonly 'langflower.config.draft.snapshot': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<LangflowerConfigDraftSnapshotPayload>;
308
334
  /**
309
335
  * Authoritative live model catalogs for all configured providers.
310
336
  * Server-pushed after {@link LangflowerConfigSnapshotPayload} on
@@ -324,10 +350,11 @@ export declare const langflowerWsConfig: {
324
350
  * `session.ready`).
325
351
  *
326
352
  * Payload is only {@link SessionStateSnapshotPayload}: `version`,
327
- * `langflowerConfig`, `dividerPositions`, `selectedNode`. Replace those
328
- * slices from this event; hydrate workflows / runner / feed / viewport /
329
- * tool config from their dedicated snapshot keys (see state-sync table
330
- * on {@link langflowerWsConfig}).
353
+ * `langflowerConfig`, `dividerPositions`, `selectedNode`, `settings`.
354
+ * Replace those slices from this event; hydrate workflows / runner /
355
+ * feed / viewport / tool config from their dedicated snapshot keys
356
+ * (see state-sync table on {@link langflowerWsConfig}). Live Settings
357
+ * updates after connect use `editor.settings.snapshot`.
331
358
  */
332
359
  readonly 'session.state.snapshot': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<SessionStateSnapshotPayload>;
333
360
  /**
@@ -397,6 +424,11 @@ export declare const langflowerWsConfig: {
397
424
  * Stays inside the internal tool loop — not a graph HITL edge.
398
425
  */
399
426
  readonly 'runner.permission.ask': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<RunnerPermissionAskPayload>;
427
+ /**
428
+ * Server accepted one Allow / Deny reply for a still-pending permission
429
+ * ask. Every client removes that ask from its UI only on this fact.
430
+ */
431
+ readonly 'runner.permission.accepted': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<RunnerPermissionReplyPayload>;
400
432
  /**
401
433
  * Resumable checkpoints for the active workflow (bootstrap + after
402
434
  * Stop / discard / complete).
@@ -432,5 +464,13 @@ export declare const langflowerWsConfig: {
432
464
  * lookup against other node lists.
433
465
  */
434
466
  readonly 'editor.nodeSelected': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<EditorSelectedNodePayload>;
467
+ /**
468
+ * Authoritative Settings aside chrome — `open` + `scope`.
469
+ * Broadcast after `editor.settings.requested` and when selecting a
470
+ * node closes Settings. Hydrated on connect via
471
+ * {@link SessionStateSnapshotPayload.settings}. When effective
472
+ * providers are empty, connect forces open Global Settings.
473
+ */
474
+ readonly 'editor.settings.snapshot': import("@langflower/websocket-bridge").WsBridgeMessageDefinition<EditorSettingsSnapshotPayload>;
435
475
  };
436
476
  };
@@ -94,8 +94,19 @@ const editorConfig = {
94
94
  *
95
95
  * Server validates `nodeId` against the active graph, stores it on
96
96
  * the session, and broadcasts `editor.nodeSelected` to all tabs.
97
+ * Selecting a node while Settings is open also closes Settings
98
+ * (`editor.settings.snapshot` with `open: false`).
97
99
  */
98
100
  'editor.selectNode.requested': message(),
101
+ /**
102
+ * Settings aside open/close + scope intent.
103
+ *
104
+ * When `open: true`, `scope` is required (`project` | `global`).
105
+ * When `open: false`, server keeps the prior scope. Session memory
106
+ * only — not written to `langflower.jsonc`. Broadcasts
107
+ * `editor.settings.snapshot` to all tabs.
108
+ */
109
+ 'editor.settings.requested': message(),
99
110
  },
100
111
  fromServerToClient: {
101
112
  /** Added nodes — client applies delta. Empty array = no change applied. */
@@ -124,6 +135,14 @@ const editorConfig = {
124
135
  * lookup against other node lists.
125
136
  */
126
137
  'editor.nodeSelected': message(),
138
+ /**
139
+ * Authoritative Settings aside chrome — `open` + `scope`.
140
+ * Broadcast after `editor.settings.requested` and when selecting a
141
+ * node closes Settings. Hydrated on connect via
142
+ * {@link SessionStateSnapshotPayload.settings}. When effective
143
+ * providers are empty, connect forces open Global Settings.
144
+ */
145
+ 'editor.settings.snapshot': message(),
127
146
  },
128
147
  };
129
148
  /**
@@ -243,6 +262,11 @@ const runnerConfig = {
243
262
  * Stays inside the internal tool loop — not a graph HITL edge.
244
263
  */
245
264
  'runner.permission.ask': message(),
265
+ /**
266
+ * Server accepted one Allow / Deny reply for a still-pending permission
267
+ * ask. Every client removes that ask from its UI only on this fact.
268
+ */
269
+ 'runner.permission.accepted': message(),
246
270
  /**
247
271
  * Resumable checkpoints for the active workflow (bootstrap + after
248
272
  * Stop / discard / complete).
@@ -263,7 +287,7 @@ const runnerConfig = {
263
287
  * slice from its own key — there is no fat all-domains bootstrap payload.
264
288
  *
265
289
  * **Slim `session.state.snapshot` only:** `version`, `langflowerConfig`,
266
- * `dividerPositions`, `selectedNode`.
290
+ * `dividerPositions`, `selectedNode`, `settings`.
267
291
  *
268
292
  * **Do not put here** (dedicated keys, see emit order on
269
293
  * {@link langflowerWsConfig}): workflows, viewport, runner gate, execution
@@ -281,10 +305,11 @@ const bootstrapConfig = {
281
305
  * `session.ready`).
282
306
  *
283
307
  * Payload is only {@link SessionStateSnapshotPayload}: `version`,
284
- * `langflowerConfig`, `dividerPositions`, `selectedNode`. Replace those
285
- * slices from this event; hydrate workflows / runner / feed / viewport /
286
- * tool config from their dedicated snapshot keys (see state-sync table
287
- * on {@link langflowerWsConfig}).
308
+ * `langflowerConfig`, `dividerPositions`, `selectedNode`, `settings`.
309
+ * Replace those slices from this event; hydrate workflows / runner /
310
+ * feed / viewport / tool config from their dedicated snapshot keys
311
+ * (see state-sync table on {@link langflowerWsConfig}). Live Settings
312
+ * updates after connect use `editor.settings.snapshot`.
288
313
  */
289
314
  'session.state.snapshot': message(),
290
315
  /**
@@ -316,8 +341,9 @@ const bootstrapConfig = {
316
341
  /**
317
342
  * **Langflower project config** — project + global `langflower.jsonc` layers.
318
343
  *
319
- * Context: **snapshot-only**. Server pushes on connect after `session.ready`
320
- * and after every `langflower.config.save.requested`. Effective merge is
344
+ * Context: saved layers are **snapshot-only** (`langflower.config.snapshot`).
345
+ * Unsaved Settings form state is a separate **session draft**
346
+ * (`langflower.config.draft.*`) broadcast to all tabs. Effective merge is
321
347
  * project > global; payload also carries redacted layers + `globalPath`.
322
348
  * Slim {@link SessionStateSnapshotPayload} carries effective
323
349
  * `langflowerConfig` on reconnect (not the full layer payload).
@@ -325,11 +351,20 @@ const bootstrapConfig = {
325
351
  const langflowerProjectConfig = {
326
352
  fromClientToServer: {
327
353
  /**
328
- * Settings Save — persist one scope (project or global), then
329
- * broadcast {@link LangflowerConfigSnapshotPayload}. No parallel
330
- * `settings.*` bus.
354
+ * Settings Save — persist the **session draft** for one scope (or the
355
+ * payload when no draft is seeded), then broadcast
356
+ * {@link LangflowerConfigSnapshotPayload} and draft snapshot.
331
357
  */
332
358
  'langflower.config.save.requested': message(),
359
+ /**
360
+ * Replace the session Settings draft for a scope. Server probes
361
+ * connection when a provider `baseURL` / `apiKey` changes.
362
+ */
363
+ 'langflower.config.draft.patch.requested': message(),
364
+ /**
365
+ * Reset the session draft from the saved layer for a scope.
366
+ */
367
+ 'langflower.config.draft.discard.requested': message(),
333
368
  /**
334
369
  * Settings → Bootstrap — force-reseed skeleton templates into the
335
370
  * open project (workflows / skills / my-nodes / instructions).
@@ -345,6 +380,12 @@ const langflowerProjectConfig = {
345
380
  * and after Settings Save. Includes effective + layer slices.
346
381
  */
347
382
  'langflower.config.snapshot': message(),
383
+ /**
384
+ * Authoritative unsaved Settings draft + connection statuses for one
385
+ * scope. Session memory; broadcast after patch / discard / save /
386
+ * scope change / connect.
387
+ */
388
+ 'langflower.config.draft.snapshot': message(),
348
389
  /**
349
390
  * Authoritative live model catalogs for all configured providers.
350
391
  * Server-pushed after {@link LangflowerConfigSnapshotPayload} on
@@ -532,7 +573,7 @@ const workflowManagerConfig = {
532
573
  *
533
574
  * | Domain | Model | Reconnect | Live updates |
534
575
  * | ------ | ----- | --------- | -------------- |
535
- * | Session chrome (dividers, selection) + effective `langflowerConfig` | **snapshot only** | slim `session.state.snapshot` | dividers / selection via `editor.dividers.snapshot` / `editor.nodeSelected` |
576
+ * | Session chrome (dividers, selection, settings) + effective `langflowerConfig` | **snapshot only** | slim `session.state.snapshot` | dividers / selection / settings via `editor.dividers.snapshot` / `editor.nodeSelected` / `editor.settings.snapshot` |
536
577
  * | Langflower project config (full layers) | **snapshot only** | `langflower.config.snapshot` after `session.ready`; effective also in slim session | full slice replace |
537
578
  * | Live provider model catalogs | **snapshot only** | `langflower.models.catalog.snapshot` after config (async on connect) + after Save | full map replace |
538
579
  * | Tool config | **snapshot only** | `toolConfig.snapshot` | full slice replace |
@@ -29,4 +29,8 @@ describe('mergeLangflowerConfigLayers', () => {
29
29
  expect(merged.model).toBe('global/m');
30
30
  expect(merged.provider?.x?.name).toBe('X');
31
31
  });
32
+ it('lets project serverLogs win over global when set', () => {
33
+ expect(mergeLangflowerConfigLayers({ serverLogs: true }, { serverLogs: false }).serverLogs).toBe(false);
34
+ expect(mergeLangflowerConfigLayers({ serverLogs: false }, {}).serverLogs).toBe(false);
35
+ });
32
36
  });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Top-level `LangflowerConfig.model` is OpenCode-style `providerId/modelId`.
3
+ * Settings UI and runtime fallback split/join that composite.
4
+ */
5
+ export type DefaultChatModelParts = {
6
+ readonly providerId: string;
7
+ readonly model: string;
8
+ };
9
+ /** First `/` splits provider vs model; both parts must be non-empty. */
10
+ export declare const parseDefaultChatModel: (model: string | undefined) => DefaultChatModelParts | null;
11
+ /** Compose disk/wire `model` value; omit when either part is empty. */
12
+ export declare const formatDefaultChatModel: (providerId: string, model: string) => string | undefined;
13
+ /** Inspector empty-option title when a default is configured. */
14
+ export declare const defaultChatModelEmptyTitle: (model: string | undefined) => string | null;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Top-level `LangflowerConfig.model` is OpenCode-style `providerId/modelId`.
3
+ * Settings UI and runtime fallback split/join that composite.
4
+ */
5
+ /** First `/` splits provider vs model; both parts must be non-empty. */
6
+ export const parseDefaultChatModel = (model) => {
7
+ if (typeof model !== 'string') {
8
+ return null;
9
+ }
10
+ const trimmed = model.trim();
11
+ const slash = trimmed.indexOf('/');
12
+ if (slash <= 0 || slash >= trimmed.length - 1) {
13
+ return null;
14
+ }
15
+ const providerId = trimmed.slice(0, slash).trim();
16
+ const modelId = trimmed.slice(slash + 1).trim();
17
+ if (providerId.length === 0 || modelId.length === 0) {
18
+ return null;
19
+ }
20
+ return { providerId, model: modelId };
21
+ };
22
+ /** Compose disk/wire `model` value; omit when either part is empty. */
23
+ export const formatDefaultChatModel = (providerId, model) => {
24
+ const provider = providerId.trim();
25
+ const modelId = model.trim();
26
+ if (provider.length === 0 || modelId.length === 0) {
27
+ return undefined;
28
+ }
29
+ return `${provider}/${modelId}`;
30
+ };
31
+ /** Inspector empty-option title when a default is configured. */
32
+ export const defaultChatModelEmptyTitle = (model) => {
33
+ const parts = parseDefaultChatModel(model);
34
+ if (parts === null) {
35
+ return null;
36
+ }
37
+ return `Default (${parts.providerId}/${parts.model})`;
38
+ };
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { defaultChatModelEmptyTitle, formatDefaultChatModel, parseDefaultChatModel, } from './parse-default-chat-model.js';
3
+ describe('parseDefaultChatModel', () => {
4
+ it('splits provider/model on the first slash', () => {
5
+ expect(parseDefaultChatModel('lmstudio/local-model')).toEqual({
6
+ providerId: 'lmstudio',
7
+ model: 'local-model',
8
+ });
9
+ expect(parseDefaultChatModel('openai/org/custom')).toEqual({
10
+ providerId: 'openai',
11
+ model: 'org/custom',
12
+ });
13
+ });
14
+ it('rejects missing slash or empty parts', () => {
15
+ expect(parseDefaultChatModel(undefined)).toBeNull();
16
+ expect(parseDefaultChatModel('')).toBeNull();
17
+ expect(parseDefaultChatModel('noslash')).toBeNull();
18
+ expect(parseDefaultChatModel('/model')).toBeNull();
19
+ expect(parseDefaultChatModel('provider/')).toBeNull();
20
+ expect(parseDefaultChatModel(' / ')).toBeNull();
21
+ });
22
+ });
23
+ describe('formatDefaultChatModel', () => {
24
+ it('joins non-empty parts', () => {
25
+ expect(formatDefaultChatModel('lmstudio', 'local-model')).toBe('lmstudio/local-model');
26
+ });
27
+ it('returns undefined when either part is empty', () => {
28
+ expect(formatDefaultChatModel('', 'm')).toBeUndefined();
29
+ expect(formatDefaultChatModel('p', '')).toBeUndefined();
30
+ expect(formatDefaultChatModel(' ', 'm')).toBeUndefined();
31
+ });
32
+ });
33
+ describe('defaultChatModelEmptyTitle', () => {
34
+ it('formats Default (provider/model)', () => {
35
+ expect(defaultChatModelEmptyTitle('lmstudio/local-model')).toBe('Default (lmstudio/local-model)');
36
+ });
37
+ it('returns null when unset', () => {
38
+ expect(defaultChatModelEmptyTitle(undefined)).toBeNull();
39
+ expect(defaultChatModelEmptyTitle('')).toBeNull();
40
+ });
41
+ });
@@ -0,0 +1,6 @@
1
+ import type { LangflowerConfig } from '../types/langflower-config.js';
2
+ /**
3
+ * Effective bridge diagnostic logging from a merged (project > global) config.
4
+ * Missing `serverLogs` defaults to enabled.
5
+ */
6
+ export declare const resolveServerLogsEnabled: (config: LangflowerConfig) => boolean;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Effective bridge diagnostic logging from a merged (project > global) config.
3
+ * Missing `serverLogs` defaults to enabled.
4
+ */
5
+ export const resolveServerLogsEnabled = (config) => config.serverLogs !== false;
@@ -0,0 +1,11 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { resolveServerLogsEnabled } from './resolve-server-logs-enabled.js';
3
+ describe('resolveServerLogsEnabled', () => {
4
+ it('defaults to enabled when serverLogs is omitted', () => {
5
+ expect(resolveServerLogsEnabled({})).toBe(true);
6
+ });
7
+ it('respects explicit true and false', () => {
8
+ expect(resolveServerLogsEnabled({ serverLogs: true })).toBe(true);
9
+ expect(resolveServerLogsEnabled({ serverLogs: false })).toBe(false);
10
+ });
11
+ });
@@ -0,0 +1,44 @@
1
+ import type { LangflowerConfig, LangflowerConfigSaveRequestedPayload, LangflowerConfigScope, LangflowerProviderConfig } from '../types/langflower-config.js';
2
+ export type ProviderDraft = {
3
+ readonly id: string;
4
+ readonly name: string;
5
+ readonly baseURL: string;
6
+ readonly modelsText: string;
7
+ /** Write-only; snapshots always send empty string. */
8
+ readonly apiKey: string;
9
+ readonly hasApiKey: boolean;
10
+ };
11
+ /** Tri-state for scoped `serverLogs` (Default = key omitted). */
12
+ export type ServerLogsDraft = 'off' | 'default' | 'on';
13
+ export type SettingsDraft = {
14
+ /** Split from disk `model: "provider/model"` for Settings selects. */
15
+ readonly defaultProviderId: string;
16
+ readonly defaultModelId: string;
17
+ readonly providers: readonly ProviderDraft[];
18
+ readonly serverLogs: ServerLogsDraft;
19
+ };
20
+ export declare const configToDraft: (config: LangflowerConfig) => SettingsDraft;
21
+ /** Snapshot-safe draft — never echo secrets. */
22
+ export declare const redactDraftSecrets: (draft: SettingsDraft) => SettingsDraft;
23
+ export declare const draftToSavePayload: (scope: LangflowerConfigScope, draft: SettingsDraft) => LangflowerConfigSaveRequestedPayload & {
24
+ readonly provider: Readonly<Record<string, LangflowerProviderConfig>>;
25
+ readonly providerApiKeys: Readonly<Record<string, string>>;
26
+ readonly serverLogs: boolean | null;
27
+ };
28
+ export declare const sameDraft: (a: SettingsDraft, b: SettingsDraft) => boolean;
29
+ /**
30
+ * Merge an incoming patch draft into the session draft while preserving
31
+ * write-only apiKey values when the patch sends an empty key field.
32
+ */
33
+ export declare const mergeDraftPatch: (previous: SettingsDraft, patch: SettingsDraft) => SettingsDraft;
34
+ /**
35
+ * After a saved-layer baseline refresh:
36
+ * - pristine form → take the layer draft (apiKey always empty);
37
+ * - dirty form → clear write-only `apiKey`, refresh `hasApiKey` from the
38
+ * layer, then fully align when non-secret fields already match (post-Save).
39
+ */
40
+ export declare const draftAfterLayerSnapshot: (previousDraft: SettingsDraft, previousBaseline: SettingsDraft, nextBaseline: SettingsDraft) => SettingsDraft;
41
+ /** Static model ids from the provider row matching `defaultProviderId`. */
42
+ export declare const defaultProviderStaticModelIds: (draft: SettingsDraft) => readonly string[];
43
+ /** Row key for connections map (stable for empty ids). */
44
+ export declare const providerConnectionKey: (index: number) => string;