@flowdrop/flowdrop 1.14.0 → 2.0.0-beta.1

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 (218) hide show
  1. package/CHANGELOG.md +475 -0
  2. package/MIGRATION-2.0.md +472 -0
  3. package/README.md +23 -23
  4. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  5. package/dist/adapters/WorkflowAdapter.js +14 -8
  6. package/dist/adapters/agentspec/AgentSpecAdapter.js +7 -7
  7. package/dist/chat/batchFeedback.d.ts +39 -0
  8. package/dist/chat/batchFeedback.js +51 -0
  9. package/dist/commands/executor.js +15 -1
  10. package/dist/commands/storeIntegration.svelte.d.ts +4 -1
  11. package/dist/commands/storeIntegration.svelte.js +26 -21
  12. package/dist/commands/types.d.ts +2 -0
  13. package/dist/components/App.svelte +162 -192
  14. package/dist/components/App.svelte.d.ts +47 -8
  15. package/dist/components/ConfigForm.svelte +110 -66
  16. package/dist/components/ConfigModal.svelte +7 -2
  17. package/dist/components/ConnectionLine.svelte +4 -2
  18. package/dist/components/Navbar.svelte +61 -1
  19. package/dist/components/NodeSidebar.svelte +27 -45
  20. package/dist/components/NodeStatusOverlay.svelte +94 -6
  21. package/dist/components/NodeSwapPicker.svelte +10 -8
  22. package/dist/components/PipelineStatus.svelte +16 -67
  23. package/dist/components/PortCoordinateTracker.svelte +5 -6
  24. package/dist/components/SchemaForm.stories.svelte +1 -3
  25. package/dist/components/SchemaForm.svelte +45 -40
  26. package/dist/components/SchemaForm.svelte.d.ts +0 -8
  27. package/dist/components/SettingsModal.svelte +8 -3
  28. package/dist/components/SettingsPanel.svelte +20 -4
  29. package/dist/components/SwapMappingEditor.svelte +67 -49
  30. package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
  31. package/dist/components/UniversalNode.svelte +9 -7
  32. package/dist/components/WorkflowEditor.svelte +118 -111
  33. package/dist/components/WorkflowEditor.svelte.d.ts +18 -10
  34. package/dist/components/chat/AIChatPanel.svelte +93 -89
  35. package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
  36. package/dist/components/chat/CommandPreview.svelte +2 -1
  37. package/dist/components/console/CommandConsole.svelte +7 -5
  38. package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
  39. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
  40. package/dist/components/console/ConsoleInput.svelte +15 -6
  41. package/dist/components/console/ConsoleOutput.svelte +2 -1
  42. package/dist/components/form/FormArray.svelte +5 -9
  43. package/dist/components/form/FormArray.svelte.d.ts +2 -1
  44. package/dist/components/form/FormAutocomplete.svelte +29 -13
  45. package/dist/components/form/FormField.svelte +4 -2
  46. package/dist/components/form/FormFieldLight.svelte +4 -2
  47. package/dist/components/form/FormMarkdownEditor.svelte +9 -4
  48. package/dist/components/form/FormRangeField.svelte +1 -0
  49. package/dist/components/form/FormTemplateEditor.svelte +11 -3
  50. package/dist/components/form/FormToggle.svelte +5 -12
  51. package/dist/components/form/FormToggle.svelte.d.ts +4 -2
  52. package/dist/components/form/templateAutocomplete.js +1 -5
  53. package/dist/components/form/types.d.ts +1 -14
  54. package/dist/components/interrupt/FormPrompt.svelte +3 -2
  55. package/dist/components/interrupt/InterruptBubble.svelte +16 -17
  56. package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
  57. package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
  58. package/dist/components/layouts/MainLayout.svelte +20 -13
  59. package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
  60. package/dist/components/nodes/AtomNode.svelte +292 -0
  61. package/dist/components/nodes/AtomNode.svelte.d.ts +26 -0
  62. package/dist/components/nodes/GatewayNode.svelte +19 -10
  63. package/dist/components/nodes/IdeaNode.svelte +7 -0
  64. package/dist/components/nodes/SimpleNode.svelte +11 -6
  65. package/dist/components/nodes/SquareNode.svelte +15 -8
  66. package/dist/components/nodes/TerminalNode.svelte +9 -4
  67. package/dist/components/nodes/ToolNode.svelte +7 -1
  68. package/dist/components/nodes/WorkflowNode.svelte +16 -7
  69. package/dist/components/playground/ChatInput.svelte +11 -14
  70. package/dist/components/playground/ChatPanel.svelte +6 -49
  71. package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
  72. package/dist/components/playground/ControlPanel.svelte +134 -123
  73. package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
  74. package/dist/components/playground/ExecutionLogs.svelte +11 -9
  75. package/dist/components/playground/InputCollector.svelte +11 -9
  76. package/dist/components/playground/MessageStream.svelte +17 -23
  77. package/dist/components/playground/PipelineKanbanView.svelte +65 -6
  78. package/dist/components/playground/PipelinePanel.svelte +11 -5
  79. package/dist/components/playground/PipelineTableView.svelte +186 -44
  80. package/dist/components/playground/Playground.svelte +95 -92
  81. package/dist/components/playground/Playground.svelte.d.ts +2 -0
  82. package/dist/components/playground/PlaygroundApp.svelte +6 -1
  83. package/dist/components/playground/PlaygroundApp.svelte.d.ts +3 -0
  84. package/dist/components/playground/PlaygroundModal.svelte +13 -3
  85. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -0
  86. package/dist/components/playground/PlaygroundStudio.svelte +34 -32
  87. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -0
  88. package/dist/components/playground/SessionManager.svelte +9 -12
  89. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +28 -0
  90. package/dist/components/playground/pipelineViewUtils.svelte.js +38 -1
  91. package/dist/config/endpoints.d.ts +0 -7
  92. package/dist/config/endpoints.js +2 -10
  93. package/dist/core/index.d.ts +4 -4
  94. package/dist/core/index.js +6 -6
  95. package/dist/display/index.d.ts +0 -2
  96. package/dist/display/index.js +0 -6
  97. package/dist/editor/index.d.ts +19 -20
  98. package/dist/editor/index.js +25 -35
  99. package/dist/form/code.d.ts +25 -15
  100. package/dist/form/code.js +44 -41
  101. package/dist/form/fieldRegistry.d.ts +17 -13
  102. package/dist/form/fieldRegistry.js +32 -12
  103. package/dist/form/full.d.ts +17 -13
  104. package/dist/form/full.js +22 -27
  105. package/dist/form/index.d.ts +3 -3
  106. package/dist/form/index.js +3 -3
  107. package/dist/form/markdown.d.ts +13 -8
  108. package/dist/form/markdown.js +22 -23
  109. package/dist/helpers/proximityConnect.d.ts +7 -3
  110. package/dist/helpers/proximityConnect.js +19 -6
  111. package/dist/helpers/workflowEditorHelper.d.ts +12 -5
  112. package/dist/helpers/workflowEditorHelper.js +27 -25
  113. package/dist/index.d.ts +28 -24
  114. package/dist/index.js +27 -50
  115. package/dist/messages/defaults.d.ts +2 -5
  116. package/dist/messages/defaults.js +3 -6
  117. package/dist/messages/index.d.ts +0 -1
  118. package/dist/messages/index.js +0 -1
  119. package/dist/mocks/app-forms.d.ts +6 -2
  120. package/dist/mocks/app-forms.js +11 -4
  121. package/dist/openapi/v1/openapi.yaml +227 -164
  122. package/dist/playground/index.d.ts +2 -3
  123. package/dist/playground/index.js +2 -30
  124. package/dist/playground/mount.d.ts +15 -0
  125. package/dist/playground/mount.js +46 -20
  126. package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
  127. package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
  128. package/dist/registry/builtinFormats.d.ts +9 -18
  129. package/dist/registry/builtinFormats.js +9 -39
  130. package/dist/registry/builtinNodes.d.ts +1 -26
  131. package/dist/registry/builtinNodes.js +14 -50
  132. package/dist/registry/index.d.ts +3 -4
  133. package/dist/registry/index.js +4 -6
  134. package/dist/registry/nodeComponentRegistry.d.ts +182 -15
  135. package/dist/registry/nodeComponentRegistry.js +235 -17
  136. package/dist/registry/workflowFormatRegistry.d.ts +14 -9
  137. package/dist/registry/workflowFormatRegistry.js +24 -8
  138. package/dist/{schema → schemas}/index.d.ts +2 -2
  139. package/dist/{schema → schemas}/index.js +2 -2
  140. package/dist/schemas/v1/workflow.schema.json +53 -6
  141. package/dist/services/agentSpecExecutionService.js +0 -1
  142. package/dist/services/apiVariableService.d.ts +2 -1
  143. package/dist/services/apiVariableService.js +5 -22
  144. package/dist/services/autoSaveService.d.ts +7 -0
  145. package/dist/services/autoSaveService.js +6 -4
  146. package/dist/services/chatService.d.ts +8 -4
  147. package/dist/services/chatService.js +15 -15
  148. package/dist/services/draftStorage.d.ts +129 -13
  149. package/dist/services/draftStorage.js +185 -37
  150. package/dist/services/dynamicSchemaService.d.ts +2 -1
  151. package/dist/services/dynamicSchemaService.js +5 -22
  152. package/dist/services/globalSave.d.ts +13 -12
  153. package/dist/services/globalSave.js +29 -51
  154. package/dist/services/historyService.d.ts +9 -3
  155. package/dist/services/historyService.js +9 -3
  156. package/dist/services/interruptService.d.ts +14 -9
  157. package/dist/services/interruptService.js +27 -27
  158. package/dist/services/nodeExecutionService.d.ts +18 -3
  159. package/dist/services/nodeExecutionService.js +71 -45
  160. package/dist/services/playgroundService.d.ts +14 -9
  161. package/dist/services/playgroundService.js +31 -30
  162. package/dist/services/variableService.d.ts +2 -1
  163. package/dist/services/variableService.js +2 -2
  164. package/dist/services/workflowStorage.js +6 -6
  165. package/dist/stores/apiContext.d.ts +45 -0
  166. package/dist/stores/apiContext.js +65 -0
  167. package/dist/stores/categoriesStore.svelte.d.ts +28 -23
  168. package/dist/stores/categoriesStore.svelte.js +70 -64
  169. package/dist/stores/getInstance.svelte.d.ts +39 -0
  170. package/dist/stores/getInstance.svelte.js +65 -0
  171. package/dist/stores/historyStore.svelte.d.ts +77 -93
  172. package/dist/stores/historyStore.svelte.js +134 -160
  173. package/dist/stores/instanceContainer.svelte.d.ts +111 -0
  174. package/dist/stores/instanceContainer.svelte.js +114 -0
  175. package/dist/stores/interruptStore.svelte.d.ts +112 -82
  176. package/dist/stores/interruptStore.svelte.js +253 -226
  177. package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
  178. package/dist/stores/pipelinePanelStore.svelte.js +61 -14
  179. package/dist/stores/playgroundStore.svelte.d.ts +169 -216
  180. package/dist/stores/playgroundStore.svelte.js +515 -572
  181. package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
  182. package/dist/stores/portCoordinateStore.svelte.js +109 -98
  183. package/dist/stores/settingsStore.svelte.d.ts +4 -1
  184. package/dist/stores/settingsStore.svelte.js +47 -12
  185. package/dist/stores/workflowStore.svelte.d.ts +178 -213
  186. package/dist/stores/workflowStore.svelte.js +449 -501
  187. package/dist/stories/EdgeDecorator.svelte +5 -2
  188. package/dist/stories/NodeDecorator.svelte +5 -3
  189. package/dist/svelte-app.d.ts +60 -10
  190. package/dist/svelte-app.js +157 -53
  191. package/dist/types/events.d.ts +6 -3
  192. package/dist/types/index.d.ts +71 -6
  193. package/dist/types/navbar.d.ts +7 -0
  194. package/dist/types/playground.d.ts +18 -3
  195. package/dist/types/settings.d.ts +13 -0
  196. package/dist/types/settings.js +1 -0
  197. package/dist/utils/colors.d.ts +47 -21
  198. package/dist/utils/colors.js +69 -68
  199. package/dist/utils/connections.d.ts +9 -15
  200. package/dist/utils/connections.js +13 -32
  201. package/dist/utils/duration.d.ts +13 -0
  202. package/dist/utils/duration.js +45 -0
  203. package/dist/utils/formMerge.d.ts +36 -0
  204. package/dist/utils/formMerge.js +70 -0
  205. package/dist/utils/icons.d.ts +5 -2
  206. package/dist/utils/icons.js +6 -5
  207. package/dist/utils/nodeSwap.d.ts +6 -2
  208. package/dist/utils/nodeSwap.js +62 -126
  209. package/dist/utils/nodeTypes.d.ts +17 -8
  210. package/dist/utils/nodeTypes.js +27 -19
  211. package/dist/utils/performanceUtils.js +7 -0
  212. package/package.json +6 -5
  213. package/dist/messages/deprecation.d.ts +0 -20
  214. package/dist/messages/deprecation.js +0 -33
  215. package/dist/registry/plugin.d.ts +0 -215
  216. package/dist/registry/plugin.js +0 -249
  217. package/dist/services/api.d.ts +0 -129
  218. package/dist/services/api.js +0 -217
@@ -4,18 +4,23 @@
4
4
  import type { UIAction } from '../../commands/types.js';
5
5
  import type { EndpointConfig } from '../../config/endpoints.js';
6
6
  import { chatService } from '../../services/chatService.js';
7
- import { getWorkflowStore } from '../../stores/workflowStore.svelte.js';
7
+ import { getInstance } from '../../stores/getInstance.svelte.js';
8
8
  import { getBehaviorSettings } from '../../stores/settingsStore.svelte.js';
9
9
  import { extractCommands } from '../../chat/responseParser.js';
10
10
  import { isMutatingCommand } from '../../chat/commandClassifier.js';
11
11
  import { parseCommand } from '../../commands/parser.js';
12
12
  import { executeCommand } from '../../commands/index.js';
13
13
  import { createStoreCommandContext } from '../../commands/storeIntegration.svelte.js';
14
+ import {
15
+ buildRetryFeedback,
16
+ type BatchOutcome,
17
+ type ParseFailure
18
+ } from '../../chat/batchFeedback.js';
14
19
  import CommandPreview from './CommandPreview.svelte';
15
20
  import MarkdownDisplay from '../MarkdownDisplay.svelte';
16
21
  import { tick } from 'svelte';
17
22
  import Icon from '@iconify/svelte';
18
- import { m, warnDeprecatedProp } from '../../messages/index.js';
23
+ import { m } from '../../messages/index.js';
19
24
 
20
25
  // =========================================================================
21
26
  // Internal Display Message Type
@@ -36,26 +41,17 @@
36
41
  nodeTypes: NodeMetadata[];
37
42
  workflowId?: string;
38
43
  onUIAction?: (action: UIAction) => void;
39
- /**
40
- * @deprecated since v1.8 — use `messages.chat.placeholder`. Removed in v2.0.
41
- */
42
- placeholder?: string;
43
44
  endpointConfig?: EndpointConfig | null;
44
45
  }
45
46
 
46
- let { nodeTypes, workflowId, onUIAction, placeholder, endpointConfig }: Props = $props();
47
+ let { nodeTypes, workflowId, onUIAction, endpointConfig }: Props = $props();
47
48
 
48
- // svelte-ignore state_referenced_locally — deprecation warns once per mount; later prop rebinds aren't relevant
49
- if (placeholder !== undefined) {
50
- warnDeprecatedProp('AIChatPanel', 'placeholder', 'messages.chat.placeholder');
51
- }
49
+ const fd = getInstance();
52
50
 
53
51
  // Hoist the chat branch — read in placeholder, header, three welcome states,
54
52
  // auto-retry banner, and the send button aria-label.
55
53
  const t = $derived(m().chat);
56
54
 
57
- const resolvedPlaceholder = $derived(placeholder ?? t.placeholder);
58
-
59
55
  // =========================================================================
60
56
  // State
61
57
  // =========================================================================
@@ -84,8 +80,8 @@
84
80
  // =========================================================================
85
81
 
86
82
  $effect(() => {
87
- displayMessages.length;
88
- isLoading;
83
+ // Read scroll-relevant state so the effect re-runs when messages change
84
+ const _deps = [displayMessages.length, isLoading];
89
85
  tick().then(() => {
90
86
  if (messagesElement) {
91
87
  messagesElement.scrollTop = messagesElement.scrollHeight;
@@ -103,7 +99,7 @@
103
99
  }
104
100
 
105
101
  function getWorkflowState(): unknown {
106
- const workflow = getWorkflowStore();
102
+ const workflow = fd.workflow.current;
107
103
  if (!workflow) return null;
108
104
  return {
109
105
  nodes: workflow.nodes.map((n) => ({
@@ -123,7 +119,7 @@
123
119
  }
124
120
 
125
121
  function getCommandContext() {
126
- return createStoreCommandContext(nodeTypes, onUIAction);
122
+ return createStoreCommandContext(nodeTypes, onUIAction, fd);
127
123
  }
128
124
 
129
125
  // =========================================================================
@@ -186,28 +182,21 @@
186
182
  return msg;
187
183
  }
188
184
 
189
- /** Execute all pending mutating commands one by one with progressive UI feedback */
185
+ /**
186
+ * Execute the parseable commands in a batch one by one with progressive UI
187
+ * feedback.
188
+ *
189
+ * Commands that failed to parse are *isolated* — they stay marked as errors
190
+ * and are skipped, but they no longer abort the whole batch, so the healthy
191
+ * subset still applies (matching the long-standing "applied to a certain
192
+ * extent" expectation). After execution, if anything failed to parse or
193
+ * execute and auto-retry is enabled, specific feedback is sent back so the
194
+ * assistant can resend corrected commands.
195
+ */
190
196
  async function handleApproveCommands(messageIndex: number) {
191
197
  const msg = displayMessages[messageIndex];
192
198
  if (!msg?.commandPreview) return;
193
199
 
194
- // Refuse to run the batch if any command failed to parse. A corrupted batch
195
- // (e.g. multiline set without """) causes partial execution and can hang the
196
- // app — rejecting the whole batch is safer than executing the healthy subset.
197
- const parseErrorCount = msg.commandPreview.filter((c) => c.status === 'error').length;
198
- if (parseErrorCount > 0) {
199
- for (const cmd of msg.commandPreview) {
200
- if (cmd.status === 'pending') {
201
- cmd.status = 'error';
202
- cmd.result = 'Batch refused: fix parse errors before executing';
203
- }
204
- }
205
- appendErrorToHistory(
206
- `Batch was not executed: ${parseErrorCount} command${parseErrorCount > 1 ? 's have' : ' has'} parse errors. Dismiss this batch and ask the AI to provide corrected commands.`
207
- );
208
- return;
209
- }
210
-
211
200
  const context = getCommandContext();
212
201
  if (!context) {
213
202
  for (const cmd of msg.commandPreview) {
@@ -220,7 +209,13 @@
220
209
  return;
221
210
  }
222
211
 
223
- // Parse all pending commands first, before touching any status
212
+ // Commands that failed to parse (in processResponse) are isolated, not run.
213
+ // Capture their raw text + reason so we can feed specific corrections back.
214
+ const parseErrors: ParseFailure[] = msg.commandPreview
215
+ .filter((c) => c.status === 'error')
216
+ .map((c) => ({ raw: c.raw, error: c.result ?? 'Parse error' }));
217
+
218
+ // Re-parse the pending (parseable) commands, preserving order.
224
219
  const pendingItems = msg.commandPreview.filter((c) => c.status === 'pending');
225
220
  const parsedCommands: {
226
221
  item: CommandPreviewItem;
@@ -230,23 +225,37 @@
230
225
  for (const item of pendingItems) {
231
226
  const parsed = parseCommand(item.raw);
232
227
  if (!parsed.ok) {
228
+ // Re-parse disagreed with processResponse — isolate it like a parse error.
233
229
  item.status = 'error';
234
230
  item.result = parsed.error;
235
- appendErrorToHistory(`Command parse error for "${item.raw}": ${parsed.error}`);
236
- return;
231
+ parseErrors.push({ raw: item.raw, error: parsed.error });
232
+ continue;
237
233
  }
238
234
  parsedCommands.push({ item, command: parsed.command });
239
235
  }
240
236
 
241
- // Execute commands one by one inside a single transaction.
242
- // A 100ms pause between commands lets the canvas visibly update at each step.
243
237
  const totalCount = parsedCommands.length;
238
+
239
+ // Nothing parseable to run — feed the parse errors straight back so the
240
+ // assistant can resend a corrected batch.
241
+ if (totalCount === 0) {
242
+ await resolveBatchOutcome({
243
+ completedCount: 0,
244
+ executionError: undefined,
245
+ rolledBack: false,
246
+ parseErrors
247
+ });
248
+ return;
249
+ }
250
+
251
+ // Execute the healthy subset one by one inside a single transaction.
252
+ // A 100ms pause between commands lets the canvas visibly update at each step.
244
253
  context.dispatch.startTransaction(
245
254
  totalCount === 1 ? 'batch: 1 command' : `batch: ${totalCount} commands`
246
255
  );
247
256
 
248
257
  let completedCount = 0;
249
- let batchError: string | undefined;
258
+ let executionError: string | undefined;
250
259
 
251
260
  try {
252
261
  for (let i = 0; i < parsedCommands.length; i++) {
@@ -258,7 +267,7 @@
258
267
  if (!result.ok) {
259
268
  item.status = 'error';
260
269
  item.result = result.error;
261
- batchError = result.error;
270
+ executionError = result.error;
262
271
  context.dispatch.cancelTransaction();
263
272
  break;
264
273
  }
@@ -283,24 +292,48 @@
283
292
  return;
284
293
  }
285
294
 
286
- if (!batchError) {
287
- context.dispatch.commitTransaction();
295
+ if (executionError !== undefined) {
296
+ // An executed command failed — the whole batch is rolled back (atomic,
297
+ // long-standing behaviour). Retry feedback covers both the execution
298
+ // failure and any parse-skipped commands.
299
+ await resolveBatchOutcome({
300
+ completedCount,
301
+ executionError,
302
+ rolledBack: true,
303
+ parseErrors
304
+ });
288
305
  return;
289
306
  }
290
307
 
291
- if (getBehaviorSettings().chatAutoRetry && workflowId && autoRetryCount < MAX_AUTO_RETRIES) {
292
- autoRetryCount++;
293
- const errorText = buildBatchErrorMessage(
308
+ // Every parseable command applied commit so the subset persists.
309
+ context.dispatch.commitTransaction();
310
+
311
+ // If some commands couldn't be parsed, ask the assistant to resend them.
312
+ if (parseErrors.length > 0) {
313
+ await resolveBatchOutcome({
294
314
  completedCount,
295
- totalCount,
296
- batchError,
297
- pendingItems
298
- );
299
- await sendMessageInternal(errorText, autoRetryCount);
315
+ executionError: undefined,
316
+ rolledBack: false,
317
+ parseErrors
318
+ });
319
+ }
320
+ }
321
+
322
+ /**
323
+ * After a batch finishes with failures (parse and/or execution), either
324
+ * auto-retry with specific feedback so the assistant can self-correct, or —
325
+ * when auto-retry is off/exhausted — surface the same summary in the log.
326
+ */
327
+ async function resolveBatchOutcome(outcome: BatchOutcome) {
328
+ const feedback = buildRetryFeedback(outcome);
329
+ const canRetry =
330
+ getBehaviorSettings().chatAutoRetry && !!workflowId && autoRetryCount < MAX_AUTO_RETRIES;
331
+
332
+ if (canRetry) {
333
+ autoRetryCount++;
334
+ await sendMessageInternal(feedback, autoRetryCount);
300
335
  } else {
301
- appendErrorToHistory(
302
- `Command execution failed at command ${completedCount + 1}/${totalCount}: ${batchError}`
303
- );
336
+ appendErrorToHistory(feedback);
304
337
  }
305
338
  }
306
339
 
@@ -317,36 +350,6 @@
317
350
  });
318
351
  }
319
352
 
320
- /** Build a structured error report from a failed batch for the LLM */
321
- function buildBatchErrorMessage(
322
- completedCount: number,
323
- totalCount: number,
324
- error: string,
325
- items: CommandPreviewItem[]
326
- ): string {
327
- const lines: string[] = [
328
- `Batch execution failed at command ${completedCount + 1}/${totalCount}: ${error}`
329
- ];
330
-
331
- if (completedCount > 0) {
332
- lines.push('\nCommands that succeeded (rolled back):');
333
- for (let i = 0; i < completedCount; i++) {
334
- lines.push(` ${i + 1}. ${items[i].raw}`);
335
- }
336
- }
337
-
338
- lines.push('\nFailed command:');
339
- lines.push(` ${items[completedCount]?.raw ?? '(unknown)'}`);
340
-
341
- const remaining = totalCount - completedCount - 1;
342
- if (remaining > 0) {
343
- lines.push(`\n${remaining} command(s) were skipped.`);
344
- }
345
-
346
- lines.push('\nPlease provide corrected commands to achieve the same goal.');
347
- return lines.join('\n');
348
- }
349
-
350
353
  // =========================================================================
351
354
  // Message Handling
352
355
  // =========================================================================
@@ -366,7 +369,7 @@
366
369
  history: history.slice(0, -1) // all except current message
367
370
  };
368
371
 
369
- const response = await chatService.sendMessage(workflowId, request);
372
+ const response = await chatService.sendMessage(fd.api.config, workflowId, request);
370
373
  const displayMsg = processResponse(response.content);
371
374
  displayMessages.push(displayMsg);
372
375
  } catch (err) {
@@ -419,7 +422,8 @@
419
422
  <span>{t.helpBuild}</span>
420
423
  </div>
421
424
  {/if}
422
- {#each displayMessages as message, msgIndex}
425
+ <!-- Append-only chat log without stable IDs — index is the identity -->
426
+ {#each displayMessages as message, msgIndex (msgIndex)}
423
427
  {#if message.retryAttempt !== undefined}
424
428
  <div
425
429
  class="ai-chat-panel__retry-notice"
@@ -440,7 +444,7 @@
440
444
  {/if}
441
445
  {#if message.readOnlyResults && message.readOnlyResults.length > 0}
442
446
  <div class="ai-chat-panel__readonly-results">
443
- {#each message.readOnlyResults as result}
447
+ {#each message.readOnlyResults as result, i (i)}
444
448
  <pre class="ai-chat-panel__readonly-result">{result}</pre>
445
449
  {/each}
446
450
  </div>
@@ -475,7 +479,7 @@
475
479
  bind:value={inputValue}
476
480
  onkeydown={handleKeydown}
477
481
  class="ai-chat-panel__input"
478
- placeholder={resolvedPlaceholder}
482
+ placeholder={t.placeholder}
479
483
  rows="1"
480
484
  disabled={isLoading}
481
485
  ></textarea>
@@ -5,10 +5,6 @@ interface Props {
5
5
  nodeTypes: NodeMetadata[];
6
6
  workflowId?: string;
7
7
  onUIAction?: (action: UIAction) => void;
8
- /**
9
- * @deprecated since v1.8 — use `messages.chat.placeholder`. Removed in v2.0.
10
- */
11
- placeholder?: string;
12
8
  endpointConfig?: EndpointConfig | null;
13
9
  }
14
10
  declare const AIChatPanel: import("svelte").Component<Props, {}, "">;
@@ -32,7 +32,8 @@
32
32
 
33
33
  <div class="command-preview" role="region" aria-label={t.ariaLabel}>
34
34
  <div class="command-preview__list">
35
- {#each commands as command, i}
35
+ <!-- Fixed positional batch (raw strings may repeat) — index is the identity -->
36
+ {#each commands as command, i (i)}
36
37
  <div class="command-preview__item command-preview__item--{command.status}">
37
38
  <span class="command-preview__status">
38
39
  {#if command.status === 'pending'}
@@ -20,6 +20,7 @@
20
20
  type HelpResultData
21
21
  } from '../../commands/index.js';
22
22
  import { createStoreCommandContext } from '../../commands/storeIntegration.svelte.js';
23
+ import { getInstance } from '../../stores/getInstance.svelte.js';
23
24
  import { updateSettings, getUiSettings } from '../../stores/settingsStore.svelte.js';
24
25
  import ConsoleInput from './ConsoleInput.svelte';
25
26
  import ConsoleOutput, { type ConsoleEntry } from './ConsoleOutput.svelte';
@@ -41,13 +42,14 @@
41
42
 
42
43
  let { nodeTypes, onUIAction }: Props = $props();
43
44
 
45
+ const fd = getInstance();
46
+
44
47
  let outputEntries: ConsoleEntry[] = $state([]);
45
- let commandContext: CommandContext | null = $state(null);
46
48
 
47
- // Recreate context when nodeTypes changes
48
- $effect(() => {
49
- commandContext = createStoreCommandContext(nodeTypes, onUIAction);
50
- });
49
+ // Recreated whenever nodeTypes changes; null while no workflow is loaded
50
+ const commandContext: CommandContext | null = $derived(
51
+ createStoreCommandContext(nodeTypes, onUIAction, fd)
52
+ );
51
53
 
52
54
  /**
53
55
  * Attempts to format CommandResult data into a rich display string.
@@ -6,8 +6,6 @@
6
6
  -->
7
7
 
8
8
  <script lang="ts">
9
- import { tick } from 'svelte';
10
-
11
9
  export interface Suggestion {
12
10
  /** The text to insert into the input */
13
11
  value: string;
@@ -26,9 +24,15 @@
26
24
  selectedIndex: number;
27
25
  /** Called when a suggestion is accepted */
28
26
  onAccept: (suggestion: Suggestion) => void;
27
+ /**
28
+ * Listbox element id — supplied by the owning combobox (ConsoleInput) so
29
+ * its aria-controls/aria-activedescendant match, and unique per instance
30
+ * so two FlowDrop editors on one page don't render colliding DOM ids.
31
+ */
32
+ listboxId: string;
29
33
  }
30
34
 
31
- let { suggestions, visible, selectedIndex, onAccept }: Props = $props();
35
+ let { suggestions, visible, selectedIndex, onAccept, listboxId }: Props = $props();
32
36
 
33
37
  let listElement: HTMLDivElement | undefined = $state();
34
38
 
@@ -43,18 +47,13 @@
43
47
  </script>
44
48
 
45
49
  {#if visible && suggestions.length > 0}
46
- <div
47
- class="console-autocomplete"
48
- role="listbox"
49
- id="console-autocomplete-listbox"
50
- bind:this={listElement}
51
- >
52
- {#each suggestions as suggestion, i}
50
+ <div class="console-autocomplete" role="listbox" id={listboxId} bind:this={listElement}>
51
+ {#each suggestions as suggestion, i (suggestion.value)}
53
52
  <div
54
53
  class="console-autocomplete__item"
55
54
  class:console-autocomplete__item--selected={i === selectedIndex}
56
55
  role="option"
57
- id="console-autocomplete-option-{i}"
56
+ id="{listboxId}-option-{i}"
58
57
  tabindex="-1"
59
58
  aria-selected={i === selectedIndex}
60
59
  onmousedown={(e: MouseEvent) => {
@@ -15,6 +15,12 @@ interface Props {
15
15
  selectedIndex: number;
16
16
  /** Called when a suggestion is accepted */
17
17
  onAccept: (suggestion: Suggestion) => void;
18
+ /**
19
+ * Listbox element id — supplied by the owning combobox (ConsoleInput) so
20
+ * its aria-controls/aria-activedescendant match, and unique per instance
21
+ * so two FlowDrop editors on one page don't render colliding DOM ids.
22
+ */
23
+ listboxId: string;
18
24
  }
19
25
  declare const ConsoleAutocomplete: import("svelte").Component<Props, {}, "">;
20
26
  type ConsoleAutocomplete = ReturnType<typeof ConsoleAutocomplete>;
@@ -7,7 +7,7 @@
7
7
 
8
8
  <script lang="ts">
9
9
  import type { NodeMetadata } from '../../types/index.js';
10
- import { getWorkflowStore } from '../../stores/workflowStore.svelte.js';
10
+ import { getInstance } from '../../stores/getInstance.svelte.js';
11
11
  import { toShortId, resolveNode } from '../../commands/index.js';
12
12
  import ConsoleAutocomplete, { type Suggestion } from './ConsoleAutocomplete.svelte';
13
13
 
@@ -26,6 +26,14 @@
26
26
 
27
27
  let { open, nodeTypes = [], onSubmit, onBatchSubmit, onClose }: Props = $props();
28
28
 
29
+ const fd = getInstance();
30
+
31
+ // Unique per component instance so two FlowDrop editors on one page
32
+ // don't render colliding DOM ids; shared with the autocomplete listbox so
33
+ // aria-controls/aria-activedescendant stay consistent.
34
+ const uid = $props.id();
35
+ const listboxId = `${uid}-console-autocomplete-listbox`;
36
+
29
37
  let inputValue = $state('');
30
38
  let inputElement: HTMLInputElement | undefined = $state();
31
39
 
@@ -94,7 +102,7 @@
94
102
  * Returns suggestions with short IDs and labels.
95
103
  */
96
104
  function getWorkflowNodeSuggestions(prefix: string): Suggestion[] {
97
- const workflow = getWorkflowStore();
105
+ const workflow = fd.workflow.current;
98
106
  if (!workflow) return [];
99
107
 
100
108
  const lowerPrefix = prefix.toLowerCase();
@@ -207,7 +215,7 @@
207
215
  partial: string,
208
216
  filter: 'input' | 'output' | 'all'
209
217
  ): Suggestion[] {
210
- const workflow = getWorkflowStore();
218
+ const workflow = fd.workflow.current;
211
219
  if (!workflow) return [];
212
220
 
213
221
  const node = resolveNode(nodeId, workflow.nodes);
@@ -256,7 +264,7 @@
256
264
  * Get config key suggestions for a resolved node, filtered by prefix.
257
265
  */
258
266
  function getConfigKeySuggestions(nodeId: string, partial: string): Suggestion[] {
259
- const workflow = getWorkflowStore();
267
+ const workflow = fd.workflow.current;
260
268
  if (!workflow) return [];
261
269
 
262
270
  const node = resolveNode(nodeId, workflow.nodes);
@@ -657,6 +665,7 @@
657
665
  visible={acVisible}
658
666
  selectedIndex={acSelectedIndex}
659
667
  onAccept={acceptSuggestion}
668
+ {listboxId}
660
669
  />
661
670
  <input
662
671
  bind:this={inputElement}
@@ -668,9 +677,9 @@
668
677
  autocomplete="off"
669
678
  role="combobox"
670
679
  aria-expanded={acVisible}
671
- aria-controls="console-autocomplete-listbox"
680
+ aria-controls={listboxId}
672
681
  aria-activedescendant={acVisible && acSuggestions.length > 0
673
- ? `console-autocomplete-option-${acSelectedIndex}`
682
+ ? `${listboxId}-option-${acSelectedIndex}`
674
683
  : undefined}
675
684
  onkeydown={handleKeydown}
676
685
  oninput={handleInput}
@@ -33,7 +33,8 @@
33
33
  </script>
34
34
 
35
35
  <div class="console-output" bind:this={outputElement} role="log" aria-live="polite">
36
- {#each entries as entry}
36
+ <!-- Append-only log without stable IDs — index is the identity -->
37
+ {#each entries as entry, i (i)}
37
38
  <div class="console-output__entry console-output__entry--{entry.type}">
38
39
  {#if entry.type === 'input'}
39
40
  <span class="console-output__prefix">&gt;</span>
@@ -20,7 +20,7 @@
20
20
  <script lang="ts">
21
21
  import Icon from '@iconify/svelte';
22
22
  import type { FieldSchema } from './types.js';
23
- import { m, warnDeprecatedProp } from '../../messages/index.js';
23
+ import { m } from '../../messages/index.js';
24
24
 
25
25
  interface Props {
26
26
  /** Field identifier */
@@ -34,7 +34,8 @@
34
34
  /** Maximum number of items allowed */
35
35
  maxItems?: number;
36
36
  /**
37
- * @deprecated since v1.8 use `messages.form.array.add`. Removed in v2.0.
37
+ * Per-instance label for the add button (e.g. "Add Header" derived from
38
+ * the item schema title). Falls back to the global `messages.form.array.add`.
38
39
  */
39
40
  addLabel?: string;
40
41
  /** Whether the field is disabled */
@@ -54,11 +55,6 @@
54
55
  onChange
55
56
  }: Props = $props();
56
57
 
57
- // svelte-ignore state_referenced_locally — deprecation warns once per mount; later prop rebinds aren't relevant
58
- if (addLabel !== undefined) {
59
- warnDeprecatedProp('FormArray', 'addLabel', 'messages.form.array.add');
60
- }
61
-
62
58
  // Hoist the array branch — every {#each} iteration would otherwise re-walk
63
59
  // `m().form.array.*` for ~6 keys per item. One getter call instead of N×6.
64
60
  const t = $derived(m().form.array);
@@ -390,7 +386,7 @@
390
386
  onchange={(e) => updateItem(index, e.currentTarget.value)}
391
387
  {disabled}
392
388
  >
393
- {#each itemSchema.enum as option}
389
+ {#each itemSchema.enum as option (option)}
394
390
  <option value={String(option)}>{String(option)}</option>
395
391
  {/each}
396
392
  </select>
@@ -437,7 +433,7 @@
437
433
  updateObjectProperty(index, propKey, e.currentTarget.value)}
438
434
  {disabled}
439
435
  >
440
- {#each propFieldSchema.enum as option}
436
+ {#each propFieldSchema.enum as option (option)}
441
437
  <option value={String(option)}>{String(option)}</option>
442
438
  {/each}
443
439
  </select>
@@ -11,7 +11,8 @@ interface Props {
11
11
  /** Maximum number of items allowed */
12
12
  maxItems?: number;
13
13
  /**
14
- * @deprecated since v1.8 use `messages.form.array.add`. Removed in v2.0.
14
+ * Per-instance label for the add button (e.g. "Add Header" derived from
15
+ * the item schema title). Falls back to the global `messages.form.array.add`.
15
16
  */
16
17
  addLabel?: string;
17
18
  /** Whether the field is disabled */