windmill-components 1.687.0 → 1.695.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 (264) hide show
  1. package/package/components/ArgInput.svelte +2 -0
  2. package/package/components/AutoscalingConfigEditor.svelte +18 -4
  3. package/package/components/CompareWorkspaces.svelte +206 -157
  4. package/package/components/DatatableSchemaDiff.svelte +2 -2
  5. package/package/components/Dev.svelte +401 -85
  6. package/package/components/EditableSchemaForm.svelte +4 -0
  7. package/package/components/ErrorOrRecoveryHandler.svelte +2 -2
  8. package/package/components/FlowPreviewContent.svelte +32 -30
  9. package/package/components/FlowRestartButton.svelte +143 -61
  10. package/package/components/FlowRestartButton.svelte.d.ts +37 -0
  11. package/package/components/FlowStatusViewer.svelte +15 -1
  12. package/package/components/FlowStatusViewer.svelte.d.ts +10 -2
  13. package/package/components/FlowStatusViewerInner.svelte +1 -2
  14. package/package/components/FlowStatusViewerInner.svelte.d.ts +6 -2
  15. package/package/components/ForkConflictModal.svelte +57 -0
  16. package/package/components/ForkConflictModal.svelte.d.ts +3 -0
  17. package/package/components/GitRepoViewer.svelte +251 -97
  18. package/package/components/InputTransformSchemaForm.svelte +1 -1
  19. package/package/components/InstanceSettings.svelte +36 -16
  20. package/package/components/Login.svelte +113 -28
  21. package/package/components/Login.svelte.d.ts +1 -0
  22. package/package/components/Path.svelte +7 -1
  23. package/package/components/Path.svelte.d.ts +1 -1
  24. package/package/components/RunsPage.svelte +2 -1
  25. package/package/components/S3FilePickerInner.svelte +89 -89
  26. package/package/components/ScriptEditor.svelte +18 -5
  27. package/package/components/ShareModal.svelte.d.ts +1 -1
  28. package/package/components/apps/components/helpers/RunnableComponent.svelte.d.ts +3 -0
  29. package/package/components/apps/components/helpers/executeRunnable.js +2 -1
  30. package/package/components/apps/editor/AppReportsDrawerInner.svelte +1 -1
  31. package/package/components/apps/editor/appPolicy.js +2 -1
  32. package/package/components/apps/editor/commonAppUtils.d.ts +3 -0
  33. package/package/components/apps/editor/inlineScriptsPanel/CacheTtlPopup.svelte +1 -1
  34. package/package/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte +7 -0
  35. package/package/components/apps/editor/inlineScriptsPanel/TagPopup.svelte +49 -0
  36. package/package/components/apps/editor/inlineScriptsPanel/TagPopup.svelte.d.ts +9 -0
  37. package/package/components/apps/inputType.d.ts +1 -0
  38. package/package/components/apps/sharedTypes.d.ts +1 -0
  39. package/package/components/auditLogs/AuditLogsFilters.svelte +8 -3
  40. package/package/components/common/fileUpload/S3ArgInput.svelte +12 -10
  41. package/package/components/common/fileUpload/S3ArgInput.svelte.d.ts +2 -0
  42. package/package/components/copilot/chat/AIChatDisplay.svelte +5 -36
  43. package/package/components/copilot/chat/AIChatInput.svelte +56 -47
  44. package/package/components/copilot/chat/AIChatManager.svelte.js +48 -46
  45. package/package/components/copilot/chat/ContextElementBadge.svelte +6 -4
  46. package/package/components/copilot/chat/app/core.d.ts +12 -20
  47. package/package/components/copilot/chat/app/core.js +103 -160
  48. package/package/components/copilot/chat/app/core.test.js +234 -9
  49. package/package/components/copilot/chat/context.js +44 -0
  50. package/package/components/copilot/chat/flow/FlowAIChat.svelte +5 -3
  51. package/package/components/copilot/chat/flow/core.d.ts +2 -1
  52. package/package/components/copilot/chat/flow/core.js +48 -21
  53. package/package/components/copilot/chat/flow/helperUtils.d.ts +5 -2
  54. package/package/components/copilot/chat/flow/helperUtils.js +33 -1
  55. package/package/components/copilot/chat/flow/helperUtils.test.js +116 -1
  56. package/package/components/copilot/chat/flow/openFlow.json +1 -1
  57. package/package/components/copilot/chat/flow/openFlowZod.gen.js +24 -0
  58. package/package/components/copilot/chat/script/core.js +3 -0
  59. package/package/components/copilot/chat/shared.d.ts +6 -0
  60. package/package/components/copilot/chat/shared.js +22 -1
  61. package/package/components/copilot/chat/shared.test.d.ts +1 -0
  62. package/package/components/copilot/chat/shared.test.js +412 -0
  63. package/package/components/copilot/chat/workspaceTools.d.ts +7 -0
  64. package/package/components/copilot/chat/workspaceTools.js +239 -0
  65. package/package/components/copilot/chat/workspaceToolsZod.gen.d.ts +1295 -0
  66. package/package/components/copilot/chat/workspaceToolsZod.gen.js +424 -0
  67. package/package/components/copilot/lib.js +3 -1
  68. package/package/components/copilot/lib.test.d.ts +1 -0
  69. package/package/components/copilot/lib.test.js +19 -0
  70. package/package/components/copilot/modelConfig.d.ts +3 -0
  71. package/package/components/copilot/modelConfig.js +10 -0
  72. package/package/components/flows/FlowProgressBar.svelte +5 -2
  73. package/package/components/flows/content/FlowModuleComponent.svelte +636 -599
  74. package/package/components/flows/conversations/FlowChatManager.svelte.js +21 -10
  75. package/package/components/flows/flowStateUtils.svelte.js +5 -1
  76. package/package/components/flows/map/FlowModuleSchemaMap.svelte +3 -2
  77. package/package/components/flows/map/FlowModuleSchemaMap.svelte.d.ts +1 -0
  78. package/package/components/git_sync/GitSyncContext.svelte.js +0 -2
  79. package/package/components/graph/FlowGraphV2.svelte +7 -3
  80. package/package/components/graph/FlowGraphV2.svelte.d.ts +1 -0
  81. package/package/components/graph/renderers/triggers/TriggersBadge.svelte +3 -0
  82. package/package/components/home/deploy_ui.js +1 -1
  83. package/package/components/icons/AzureIcon.svelte +12 -25
  84. package/package/components/icons/AzureIcon.svelte.d.ts +3 -2
  85. package/package/components/instanceSettings.js +24 -0
  86. package/package/components/mcp/McpScopeSelector.svelte +119 -9
  87. package/package/components/mcp/McpScopeSelector.svelte.d.ts +1 -0
  88. package/package/components/offboarding-utils.js +2 -0
  89. package/package/components/progressBar/ProgressBar.svelte +9 -5
  90. package/package/components/progressBar/ProgressBar.svelte.d.ts +1 -0
  91. package/package/components/raw_apps/DeleteAfterUsePopup.svelte +52 -0
  92. package/package/components/raw_apps/DeleteAfterUsePopup.svelte.d.ts +9 -0
  93. package/package/components/raw_apps/RawAppBackgroundRunner.svelte +5 -1
  94. package/package/components/raw_apps/RawAppEditor.svelte +159 -102
  95. package/package/components/raw_apps/RawAppInlineScriptEditor.svelte +9 -3
  96. package/package/components/raw_apps/RawAppInlineScriptEditor.svelte.d.ts +2 -1
  97. package/package/components/raw_apps/RawAppInlineScriptRunnable.svelte +1 -0
  98. package/package/components/raw_apps/RawAppInlineScriptRunnable.svelte.d.ts +1 -0
  99. package/package/components/raw_apps/RawAppInputsSpecEditor.svelte +48 -5
  100. package/package/components/raw_apps/RawAppSharedUiDrawer.svelte +129 -0
  101. package/package/components/raw_apps/RawAppSharedUiDrawer.svelte.d.ts +5 -0
  102. package/package/components/raw_apps/RawAppSidebar.svelte +12 -0
  103. package/package/components/raw_apps/dataTableRefUtils.d.ts +7 -0
  104. package/package/components/raw_apps/dataTableRefUtils.js +34 -0
  105. package/package/components/raw_apps/dataTableRefUtils.test.d.ts +1 -0
  106. package/package/components/raw_apps/dataTableRefUtils.test.js +29 -0
  107. package/package/components/raw_apps/rawAppPolicy.d.ts +1 -0
  108. package/package/components/raw_apps/rawAppPolicy.js +17 -2
  109. package/package/components/resources/resourceTypesFilter.d.ts +19 -0
  110. package/package/components/resources/resourceTypesFilter.js +21 -0
  111. package/package/components/restartFromStepPath.d.ts +39 -0
  112. package/package/components/restartFromStepPath.js +89 -0
  113. package/package/components/runs/JobDetailFieldConfig.d.ts +1 -0
  114. package/package/components/runs/JobDetailFieldConfig.js +57 -10
  115. package/package/components/runs/JobDetailHeader.svelte +24 -3
  116. package/package/components/runs/runsFilter.d.ts +1 -1
  117. package/package/components/schema/FlowPropertyEditor.svelte +30 -1
  118. package/package/components/schema/FlowPropertyEditor.svelte.d.ts +5 -2
  119. package/package/components/search/GlobalSearchModal.svelte +8 -1
  120. package/package/components/select/Select.svelte +1 -1
  121. package/package/components/settings/CreateToken.svelte +48 -77
  122. package/package/components/settings/EditTokenScopesModal.svelte +57 -0
  123. package/package/components/settings/EditTokenScopesModal.svelte.d.ts +10 -0
  124. package/package/components/settings/ScopesPicker.svelte +43 -0
  125. package/package/components/settings/ScopesPicker.svelte.d.ts +11 -0
  126. package/package/components/settings/TokensTable.svelte +51 -15
  127. package/package/components/sidebar/OperatorMenu.svelte +6 -0
  128. package/package/components/sidebar/SidebarContent.svelte +11 -1
  129. package/package/components/triggers/AddTriggersButton.svelte +6 -0
  130. package/package/components/triggers/CaptureWrapper.svelte +19 -1
  131. package/package/components/triggers/TriggerEditorToolbar.svelte.d.ts +1 -1
  132. package/package/components/triggers/TriggerModeToggle.svelte +36 -7
  133. package/package/components/triggers/TriggerModeToggle.svelte.d.ts +1 -1
  134. package/package/components/triggers/TriggerSuspendedJobsModal.svelte.d.ts +1 -1
  135. package/package/components/triggers/TriggersEditor.svelte +5 -1
  136. package/package/components/triggers/TriggersWrapper.svelte +10 -0
  137. package/package/components/triggers/azure/AzureCapture.svelte +41 -0
  138. package/package/components/triggers/azure/AzureCapture.svelte.d.ts +44 -0
  139. package/package/components/triggers/azure/AzureTriggerEditor.svelte +20 -0
  140. package/package/components/triggers/azure/AzureTriggerEditor.svelte.d.ts +9 -0
  141. package/package/components/triggers/azure/AzureTriggerEditorConfigSection.svelte +301 -0
  142. package/package/components/triggers/azure/AzureTriggerEditorConfigSection.svelte.d.ts +16 -0
  143. package/package/components/triggers/azure/AzureTriggerEditorInner.svelte +422 -0
  144. package/package/components/triggers/azure/AzureTriggerEditorInner.svelte.d.ts +25 -0
  145. package/package/components/triggers/azure/AzureTriggerPanel.svelte +55 -0
  146. package/package/components/triggers/azure/AzureTriggerPanel.svelte.d.ts +10 -0
  147. package/{dist/sharedUtils/components/triggers/kafka → package/components/triggers/azure}/utils.d.ts +1 -1
  148. package/package/components/triggers/azure/utils.js +56 -0
  149. package/package/components/triggers/email/EmailTriggerEditorInner.svelte +2 -0
  150. package/package/components/triggers/gcp/GcpTriggerEditorInner.svelte +9 -3
  151. package/package/components/triggers/http/RouteEditorInner.svelte +2 -0
  152. package/package/components/triggers/kafka/KafkaTriggerEditorInner.svelte +9 -3
  153. package/package/components/triggers/mqtt/MqttTriggerEditorInner.svelte +9 -3
  154. package/package/components/triggers/nats/NatsTriggerEditorInner.svelte +9 -3
  155. package/package/components/triggers/postgres/PostgresTriggerEditorInner.svelte +9 -3
  156. package/package/components/triggers/schedules/ScheduleEditorInner.svelte +9 -3
  157. package/package/components/triggers/sqs/SqsTriggerEditorInner.svelte +9 -3
  158. package/package/components/triggers/triggers.svelte.d.ts +1 -0
  159. package/package/components/triggers/triggers.svelte.js +23 -1
  160. package/package/components/triggers/utils.js +20 -0
  161. package/package/components/triggers/websocket/WebsocketTriggerEditorInner.svelte +9 -3
  162. package/package/components/triggers.d.ts +1 -1
  163. package/package/components/useNestedRestartState.svelte.d.ts +56 -0
  164. package/package/components/useNestedRestartState.svelte.js +320 -0
  165. package/package/components/workspaceSettings/SharedUiSettings.svelte +175 -0
  166. package/package/components/workspaceSettings/SharedUiSettings.svelte.d.ts +3 -0
  167. package/package/gen/core/OpenAPI.js +1 -1
  168. package/package/gen/schemas.gen.d.ts +294 -24
  169. package/package/gen/schemas.gen.js +297 -25
  170. package/package/gen/services.gen.d.ts +247 -4
  171. package/package/gen/services.gen.js +498 -7
  172. package/package/gen/types.gen.d.ts +990 -37
  173. package/package/hubPaths.json +2 -5
  174. package/package/infer.d.ts +1 -1
  175. package/package/infer.js +37 -51
  176. package/package/mcpEndpointTools.js +60 -4
  177. package/package/script_helpers.js +17 -0
  178. package/package/stores.d.ts +7 -0
  179. package/package/stores.js +6 -1
  180. package/package/system_prompts/index.d.ts +1 -0
  181. package/package/system_prompts/index.js +8 -0
  182. package/package/system_prompts/prompts.d.ts +16 -13
  183. package/package/system_prompts/prompts.js +653 -43
  184. package/package/templates/ci_test_bun.ts.template +8 -0
  185. package/package/templates/ci_test_python.py.template +8 -0
  186. package/package/utils/forkConflict.d.ts +26 -0
  187. package/package/utils/forkConflict.js +56 -0
  188. package/package/utils_deployable.d.ts +164 -121
  189. package/package/utils_deployable.js +61 -11
  190. package/package/utils_workspace_deploy.js +3 -1
  191. package/package.json +29 -5
  192. package/dist/sharedUtils/assets/tokens/colorTokensConfig.d.ts +0 -2
  193. package/dist/sharedUtils/base.d.ts +0 -1
  194. package/dist/sharedUtils/cloud.d.ts +0 -1
  195. package/dist/sharedUtils/common.d.ts +0 -111
  196. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/count.d.ts +0 -5
  197. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/delete.d.ts +0 -5
  198. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/insert.d.ts +0 -5
  199. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/select.d.ts +0 -13
  200. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/update.d.ts +0 -11
  201. package/dist/sharedUtils/components/apps/components/display/dbtable/utils.d.ts +0 -95
  202. package/dist/sharedUtils/components/apps/editor/appPolicy.d.ts +0 -6
  203. package/dist/sharedUtils/components/apps/editor/appUtilsCore.d.ts +0 -7
  204. package/dist/sharedUtils/components/apps/editor/appUtilsS3.d.ts +0 -33
  205. package/dist/sharedUtils/components/apps/editor/commonAppUtils.d.ts +0 -10
  206. package/dist/sharedUtils/components/apps/editor/component/components.d.ts +0 -5371
  207. package/dist/sharedUtils/components/apps/editor/component/default-codes.d.ts +0 -3
  208. package/dist/sharedUtils/components/apps/editor/component/index.d.ts +0 -3
  209. package/dist/sharedUtils/components/apps/editor/component/sets.d.ts +0 -7
  210. package/dist/sharedUtils/components/apps/editor/componentsPanel/componentDefaultProps.d.ts +0 -3
  211. package/dist/sharedUtils/components/apps/gridUtils.d.ts +0 -14
  212. package/dist/sharedUtils/components/apps/inputType.d.ts +0 -178
  213. package/dist/sharedUtils/components/apps/rx.d.ts +0 -29
  214. package/dist/sharedUtils/components/apps/sharedTypes.d.ts +0 -21
  215. package/dist/sharedUtils/components/apps/types.d.ts +0 -274
  216. package/dist/sharedUtils/components/assets/lib.d.ts +0 -25
  217. package/dist/sharedUtils/components/common/alert/model.d.ts +0 -2
  218. package/dist/sharedUtils/components/common/badge/model.d.ts +0 -8
  219. package/dist/sharedUtils/components/common/button/model.d.ts +0 -45
  220. package/dist/sharedUtils/components/common/fileInput/model.d.ts +0 -1
  221. package/dist/sharedUtils/components/common/index.d.ts +0 -24
  222. package/dist/sharedUtils/components/common/skeleton/model.d.ts +0 -21
  223. package/dist/sharedUtils/components/dbTypes.d.ts +0 -14
  224. package/dist/sharedUtils/components/diff_drawer.d.ts +0 -26
  225. package/dist/sharedUtils/components/ducklake.d.ts +0 -1
  226. package/dist/sharedUtils/components/flows/scheduleUtils.d.ts +0 -7
  227. package/dist/sharedUtils/components/icons/index.d.ts +0 -101
  228. package/dist/sharedUtils/components/random_positive_adjetive.d.ts +0 -1
  229. package/dist/sharedUtils/components/raw_apps/rawAppPolicy.d.ts +0 -10
  230. package/dist/sharedUtils/components/raw_apps/utils.d.ts +0 -15
  231. package/dist/sharedUtils/components/triggers/email/utils.d.ts +0 -4
  232. package/dist/sharedUtils/components/triggers/gcp/utils.d.ts +0 -2
  233. package/dist/sharedUtils/components/triggers/http/utils.d.ts +0 -11
  234. package/dist/sharedUtils/components/triggers/mqtt/utils.d.ts +0 -2
  235. package/dist/sharedUtils/components/triggers/nats/utils.d.ts +0 -2
  236. package/dist/sharedUtils/components/triggers/postgres/utils.d.ts +0 -8
  237. package/dist/sharedUtils/components/triggers/sqs/utils.d.ts +0 -2
  238. package/dist/sharedUtils/components/triggers/triggers.svelte.d.ts +0 -32
  239. package/dist/sharedUtils/components/triggers/utils.d.ts +0 -80
  240. package/dist/sharedUtils/components/triggers/websocket/utils.d.ts +0 -2
  241. package/dist/sharedUtils/components/triggers.d.ts +0 -20
  242. package/dist/sharedUtils/gen/core/ApiError.d.ts +0 -10
  243. package/dist/sharedUtils/gen/core/ApiRequestOptions.d.ts +0 -13
  244. package/dist/sharedUtils/gen/core/ApiResult.d.ts +0 -7
  245. package/dist/sharedUtils/gen/core/CancelablePromise.d.ts +0 -26
  246. package/dist/sharedUtils/gen/core/OpenAPI.d.ts +0 -27
  247. package/dist/sharedUtils/gen/core/request.d.ts +0 -29
  248. package/dist/sharedUtils/gen/index.d.ts +0 -6
  249. package/dist/sharedUtils/gen/schemas.gen.d.ts +0 -7036
  250. package/dist/sharedUtils/gen/services.gen.d.ts +0 -6047
  251. package/dist/sharedUtils/gen/types.gen.d.ts +0 -21881
  252. package/dist/sharedUtils/history.svelte.d.ts +0 -9
  253. package/dist/sharedUtils/hub.d.ts +0 -49
  254. package/dist/sharedUtils/jsr.json +0 -6
  255. package/dist/sharedUtils/lib.d.ts +0 -5
  256. package/dist/sharedUtils/lib.es.js +0 -1588
  257. package/dist/sharedUtils/package.json +0 -12
  258. package/dist/sharedUtils/schema.d.ts +0 -3
  259. package/dist/sharedUtils/stores.d.ts +0 -97
  260. package/dist/sharedUtils/svelte5Utils.svelte.d.ts +0 -80
  261. package/dist/sharedUtils/toast.d.ts +0 -8
  262. package/dist/sharedUtils/utils.d.ts +0 -265
  263. package/package/components/copilot/chat/flow/openFlowZod.js +0 -24
  264. /package/package/components/copilot/chat/flow/{openFlowZod.d.ts → openFlowZod.gen.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
- import { getAppTools } from './core';
2
+ import { getAppTools, prepareAppUserMessage } from './core';
3
3
  vi.mock('../shared', () => ({
4
4
  createToolDef: (_schema, name, description) => ({
5
5
  type: 'function',
@@ -52,9 +52,6 @@ vi.mock('../AIChatManager.svelte', () => ({
52
52
  }
53
53
  }
54
54
  }));
55
- vi.mock('$system_prompts', () => ({
56
- getDatatableSdkReference: () => 'Datatable SDK reference'
57
- }));
58
55
  const EMPTY_LINT_RESULT = {
59
56
  errorCount: 0,
60
57
  warningCount: 0,
@@ -80,11 +77,12 @@ function createHelpers(overrides = {}) {
80
77
  setBackendRunnable: async () => EMPTY_LINT_RESULT,
81
78
  deleteBackendRunnable: () => undefined,
82
79
  getFiles: () => ({ frontend: {}, backend: {} }),
83
- getSelectedContext: () => ({ type: 'none' }),
80
+ getSelectedContext: () => ({}),
84
81
  snapshot: () => 1,
85
82
  revertToSnapshot: () => undefined,
86
83
  lint: () => EMPTY_LINT_RESULT,
87
- getDatatables: async () => [],
84
+ listDatatableTables: async () => [],
85
+ getDatatableTableSchema: async () => ({}),
88
86
  getAvailableDatatableNames: () => [],
89
87
  execDatatableSql: async () => ({ success: true }),
90
88
  addTableToWhitelist: () => undefined,
@@ -97,13 +95,139 @@ function createToolCallbacks() {
97
95
  removeToolStatus: vi.fn()
98
96
  };
99
97
  }
100
- function getPatchFileTool() {
101
- const tool = getAppTools().find((entry) => entry.def.function.name === 'patch_file');
98
+ function getTool(name) {
99
+ const tool = getAppTools().find((entry) => entry.def.function.name === name);
102
100
  if (!tool) {
103
- throw new Error('patch_file tool not found');
101
+ throw new Error(`${name} tool not found`);
104
102
  }
105
103
  return tool;
106
104
  }
105
+ function getListFilesTool() {
106
+ return getTool('list_files');
107
+ }
108
+ function getPatchFileTool() {
109
+ return getTool('patch_file');
110
+ }
111
+ describe('app list_files tool', () => {
112
+ it('returns lightweight metadata without file or runnable contents', async () => {
113
+ const tool = getListFilesTool();
114
+ const result = await tool.fn({
115
+ args: {},
116
+ workspace: 'test-workspace',
117
+ helpers: createHelpers({
118
+ getFiles: () => ({
119
+ frontend: {
120
+ '/index.tsx': 'const secretFrontendContent = true',
121
+ '/styles.css': '.secret-class { color: red; }'
122
+ },
123
+ backend: {
124
+ loadUsers: {
125
+ name: 'Load users',
126
+ type: 'inline',
127
+ staticInputs: { admin: true },
128
+ inlineScript: {
129
+ language: 'bun',
130
+ content: 'export async function main() { return "secretBackendContent" }'
131
+ }
132
+ },
133
+ workspaceFlow: {
134
+ name: 'Workspace flow',
135
+ type: 'flow',
136
+ path: 'f/flows/workspace_flow'
137
+ }
138
+ }
139
+ })
140
+ }),
141
+ toolCallbacks: createToolCallbacks(),
142
+ toolId: 'tool-list-files'
143
+ });
144
+ const parsed = JSON.parse(result);
145
+ expect(parsed).toEqual({
146
+ frontend: [
147
+ { path: '/index.tsx', size: 34, kind: 'tsx' },
148
+ { path: '/styles.css', size: 29, kind: 'css' }
149
+ ],
150
+ backend: [
151
+ {
152
+ key: 'loadUsers',
153
+ name: 'Load users',
154
+ type: 'inline',
155
+ language: 'bun',
156
+ contentSize: 62,
157
+ staticInputKeys: ['admin']
158
+ },
159
+ {
160
+ key: 'workspaceFlow',
161
+ name: 'Workspace flow',
162
+ type: 'flow',
163
+ path: 'f/flows/workspace_flow'
164
+ }
165
+ ]
166
+ });
167
+ expect(result).not.toContain('secretFrontendContent');
168
+ expect(result).not.toContain('secretBackendContent');
169
+ });
170
+ });
171
+ describe('app datatable tools', () => {
172
+ it('lists datatable metadata without column definitions', async () => {
173
+ const tool = getTool('list_datatables');
174
+ const result = await tool.fn({
175
+ args: {},
176
+ workspace: 'test-workspace',
177
+ helpers: createHelpers({
178
+ listDatatableTables: async () => [
179
+ {
180
+ datatable_name: 'main',
181
+ schemas: {
182
+ public: ['users', 'orders'],
183
+ analytics: ['events']
184
+ },
185
+ tableCount: 3
186
+ }
187
+ ]
188
+ }),
189
+ toolCallbacks: createToolCallbacks(),
190
+ toolId: 'tool-list-datatables'
191
+ });
192
+ const parsed = JSON.parse(result);
193
+ expect(parsed).toEqual([
194
+ {
195
+ datatable_name: 'main',
196
+ schemas: {
197
+ public: ['users', 'orders'],
198
+ analytics: ['events']
199
+ },
200
+ tableCount: 3
201
+ }
202
+ ]);
203
+ expect(result).not.toContain('email');
204
+ expect(result).not.toContain('jsonb');
205
+ });
206
+ it('gets one datatable table schema', async () => {
207
+ const tool = getTool('get_datatable_table_schema');
208
+ const result = await tool.fn({
209
+ args: {
210
+ datatable_name: 'main',
211
+ schema_name: 'public',
212
+ table_name: 'users'
213
+ },
214
+ workspace: 'test-workspace',
215
+ helpers: createHelpers({
216
+ getDatatableTableSchema: async () => ({ id: 'int4', email: 'text' })
217
+ }),
218
+ toolCallbacks: createToolCallbacks(),
219
+ toolId: 'tool-get-table-schema'
220
+ });
221
+ const parsed = JSON.parse(result);
222
+ expect(parsed).toEqual({
223
+ datatable_name: 'main',
224
+ schema_name: 'public',
225
+ table_name: 'users',
226
+ columns: { id: 'int4', email: 'text' }
227
+ });
228
+ expect(result).not.toContain('total');
229
+ });
230
+ });
107
231
  describe('app patch_file tool', () => {
108
232
  it('patches frontend files with an exact replacement', async () => {
109
233
  const setFrontendFile = vi.fn(() => EMPTY_LINT_RESULT);
@@ -141,6 +265,26 @@ describe('app patch_file tool', () => {
141
265
  toolId: 'tool-2'
142
266
  })).rejects.toThrow('old_string matched 2 locations');
143
267
  });
268
+ it('treats leading /backend paths as frontend files', async () => {
269
+ const setFrontendFile = vi.fn(() => EMPTY_LINT_RESULT);
270
+ const tool = getPatchFileTool();
271
+ const result = await tool.fn({
272
+ args: {
273
+ path: '/backend/deleteRecipe/main.ts',
274
+ old_string: 'frontend route',
275
+ new_string: 'frontend page'
276
+ },
277
+ workspace: 'test-workspace',
278
+ helpers: createHelpers({
279
+ getFrontendFile: () => 'export const title = "frontend route"\n',
280
+ setFrontendFile
281
+ }),
282
+ toolCallbacks: createToolCallbacks(),
283
+ toolId: 'tool-leading-backend-path'
284
+ });
285
+ expect(setFrontendFile).toHaveBeenCalledWith('/backend/deleteRecipe/main.ts', 'export const title = "frontend page"\n');
286
+ expect(result).toContain("Patched '/backend/deleteRecipe/main.ts' successfully.");
287
+ });
144
288
  it('patches inline backend runnables through backend/<key>/main.ts paths', async () => {
145
289
  const runnable = {
146
290
  name: 'Delete recipe',
@@ -190,3 +334,84 @@ describe('app patch_file tool', () => {
190
334
  })).rejects.toThrow('generated automatically');
191
335
  });
192
336
  });
337
+ describe('prepareAppUserMessage app context', () => {
338
+ it('still serializes inspector and code selections', () => {
339
+ const selectedContext = {
340
+ type: 'frontend',
341
+ frontendPath: '/index.tsx',
342
+ inspectorElement: {
343
+ path: 'body > button.primary',
344
+ tagName: 'button',
345
+ id: 'save',
346
+ className: 'primary action',
347
+ rect: { top: 10, left: 20, width: 120, height: 40 },
348
+ html: '<button id="save" class="primary action">Save</button>',
349
+ textContent: 'Save',
350
+ styles: {}
351
+ },
352
+ codeSelection: {
353
+ type: 'app_code_selection',
354
+ source: '/index.tsx',
355
+ sourceType: 'frontend',
356
+ title: '/index.tsx:3-4',
357
+ content: 'const selectedCode = true',
358
+ startLine: 3,
359
+ endLine: 4,
360
+ startColumn: 1,
361
+ endColumn: 25
362
+ }
363
+ };
364
+ const message = prepareAppUserMessage('Change this selected area', selectedContext);
365
+ const content = message.content;
366
+ expect(content).toContain('The user has selected an element in the app preview');
367
+ expect(content).toContain('body > button.primary');
368
+ expect(content).toContain('### CODE SELECTION:');
369
+ expect(content).toContain('const selectedCode = true');
370
+ });
371
+ it('serializes explicit mentions with lightweight file context', () => {
372
+ const additionalContext = [
373
+ {
374
+ type: 'app_frontend_file',
375
+ path: '/index.tsx',
376
+ title: '/index.tsx',
377
+ content: 'const fullFrontendContent = true'
378
+ },
379
+ {
380
+ type: 'app_backend_runnable',
381
+ key: 'loadUsers',
382
+ title: 'loadUsers',
383
+ runnable: {
384
+ name: 'Load users',
385
+ type: 'inline',
386
+ staticInputs: { admin: true },
387
+ inlineScript: {
388
+ language: 'bun',
389
+ content: 'export async function main() { return "secret" }'
390
+ }
391
+ }
392
+ },
393
+ {
394
+ type: 'app_datatable',
395
+ datatableName: 'main',
396
+ schemaName: 'public',
397
+ tableName: 'users',
398
+ title: 'main/users',
399
+ columns: {
400
+ id: 'int4',
401
+ email: 'text'
402
+ }
403
+ }
404
+ ];
405
+ const message = prepareAppUserMessage('Wire these together', undefined, additionalContext);
406
+ const content = message.content;
407
+ expect(content).toContain('- Frontend file: /index.tsx');
408
+ expect(content).toContain('- Backend runnable: loadUsers');
409
+ expect(content).not.toContain('fullFrontendContent');
410
+ expect(content).not.toContain('export async function main');
411
+ expect(content).not.toContain('Static inputs');
412
+ expect(content).not.toContain('Load users');
413
+ expect(content).toContain('**Table: main/users**');
414
+ expect(content).toContain('"id": "int4"');
415
+ expect(content).toContain('"email": "text"');
416
+ });
417
+ });
@@ -16,3 +16,47 @@ export const ContextIconMap = {
16
16
  workspace_flow: BarsStaggered
17
17
  // flow_module type is handled with FlowModuleIcon
18
18
  };
19
+ export function createAppSelectedContext(options = {}) {
20
+ return {
21
+ ...options
22
+ };
23
+ }
24
+ export function createAppFrontendFileContextElement(path, content) {
25
+ return {
26
+ type: 'app_frontend_file',
27
+ path,
28
+ title: path,
29
+ content
30
+ };
31
+ }
32
+ export function createAppBackendRunnableContextElement(key, runnable) {
33
+ return {
34
+ type: 'app_backend_runnable',
35
+ key,
36
+ title: key,
37
+ runnable
38
+ };
39
+ }
40
+ export function formatAppDatatableContextTitle(datatableName, schemaName, tableName) {
41
+ return schemaName === 'public'
42
+ ? `${datatableName}/${tableName}`
43
+ : `${datatableName}/${schemaName}:${tableName}`;
44
+ }
45
+ export function createAppDatatableContextElement(datatableName, schemaName, tableName, columns) {
46
+ return {
47
+ type: 'app_datatable',
48
+ datatableName,
49
+ schemaName,
50
+ tableName,
51
+ title: formatAppDatatableContextTitle(datatableName, schemaName, tableName),
52
+ columns
53
+ };
54
+ }
55
+ export function flattenDatatablesToAppContextElements(datatables) {
56
+ return datatables.flatMap((datatable) => {
57
+ if (datatable.error) {
58
+ return [];
59
+ }
60
+ return Object.entries(datatable.schemas).flatMap(([schemaName, tables]) => tables.map((tableName) => createAppDatatableContextElement(datatable.datatable_name, schemaName, tableName)));
61
+ });
62
+ }
@@ -142,12 +142,13 @@ const flowHelpers = {
142
142
  }
143
143
  return { errorCount: 0, warningCount: 0, errors: [], warnings: [] };
144
144
  },
145
- setFlowJson: async ({ modules, schema, preprocessorModule, failureModule }) => {
145
+ setFlowJson: async ({ modules, schema, preprocessorModule, failureModule, groups }) => {
146
146
  try {
147
147
  if (modules !== undefined ||
148
148
  schema !== undefined ||
149
149
  preprocessorModule !== undefined ||
150
- failureModule !== undefined) {
150
+ failureModule !== undefined ||
151
+ groups !== undefined) {
151
152
  // Take snapshot of current flowStore and set as beforeFlow
152
153
  if (!diffManager?.hasPendingChanges) {
153
154
  const snapshot = $state.snapshot(flowStore).val;
@@ -159,7 +160,8 @@ const flowHelpers = {
159
160
  modules,
160
161
  schema,
161
162
  preprocessorModule,
162
- failureModule
163
+ failureModule,
164
+ groups
163
165
  });
164
166
  // Refresh the state store to update UI
165
167
  refreshStateStore(flowStore);
@@ -4,12 +4,13 @@ import { type Tool, type ScriptLintResult } from '../shared';
4
4
  import type { ContextElement } from '../context';
5
5
  import type { ExtendedOpenFlow } from '../../../flows/types';
6
6
  import { type InlineScriptSession } from './inlineScriptsUtils';
7
- import type { FlowJsonUpdateResult } from './helperUtils';
7
+ import { type FlowGroup, type FlowJsonUpdateResult } from './helperUtils';
8
8
  type FlowJsonUpdate = {
9
9
  modules?: FlowModule[];
10
10
  schema?: Record<string, any> | null;
11
11
  preprocessorModule?: FlowModule | null;
12
12
  failureModule?: FlowModule | null;
13
+ groups?: FlowGroup[] | null;
13
14
  };
14
15
  /**
15
16
  * Helper interface for AI chat flow operations
@@ -2,9 +2,11 @@ import { ScriptService, JobService } from '../../../../gen';
2
2
  import { z } from 'zod';
3
3
  import { createDbSchemaTool, getFormattedResourceTypes, getLangContext, SUPPORTED_CHAT_SCRIPT_LANGUAGES } from '../script/core';
4
4
  import { createSearchHubScriptsTool, createToolDef, executeTestRun, buildSchemaForTool, buildTestRunArgs, buildContextString, applyCodePiecesToFlowModules, SPECIAL_MODULE_IDS, formatScriptLintResult, createSearchWorkspaceTool, createGetRunnableDetailsTool } from '../shared';
5
+ import { createWorkspaceMutationTools } from '../workspaceTools';
5
6
  import { findModuleInFlow, findModuleInModules } from '../../../flows/flowTree';
6
7
  import { createInlineScriptSession } from './inlineScriptsUtils';
7
- import { flowModuleSchema, flowModulesSchema } from './openFlowZod';
8
+ import { validateFlowGroups } from './helperUtils';
9
+ import { flowModuleSchema, flowModulesSchema } from './openFlowZod.gen';
8
10
  import { collectAllFlowModuleIdsFromModules } from '../../../flows/flowTree';
9
11
  import { FLOW_CHAT_SPECIAL_MODULES, getFlowPrompt } from '../../../../system_prompts';
10
12
  /**
@@ -280,11 +282,14 @@ function validateEditableFlowJson(rawFlow) {
280
282
  const schema = validateFlowSchema(flow.schema);
281
283
  const preprocessorModule = validateOptionalFlowModule(flow.preprocessor_module, 'preprocessor_module');
282
284
  const failureModule = validateOptionalFlowModule(flow.failure_module, 'failure_module');
285
+ const groupModuleIds = new Set(collectAllFlowModuleIdsFromModules(modules));
286
+ const groups = validateFlowGroups(flow.groups, groupModuleIds);
283
287
  if (preprocessorModule) {
284
288
  if (preprocessorModule.id !== SPECIAL_MODULE_IDS.PREPROCESSOR) {
285
289
  throw new Error(`Invalid preprocessor_module: id must be "${SPECIAL_MODULE_IDS.PREPROCESSOR}"`);
286
290
  }
287
- if (preprocessorModule.value.type !== 'rawscript' && preprocessorModule.value.type !== 'script') {
291
+ if (preprocessorModule.value.type !== 'rawscript' &&
292
+ preprocessorModule.value.type !== 'script') {
288
293
  throw new Error('Invalid preprocessor_module: only "rawscript" and "script" modules are supported');
289
294
  }
290
295
  }
@@ -310,7 +315,8 @@ function validateEditableFlowJson(rawFlow) {
310
315
  modules,
311
316
  schema,
312
317
  preprocessor_module: preprocessorModule,
313
- failure_module: failureModule
318
+ failure_module: failureModule,
319
+ groups
314
320
  };
315
321
  }
316
322
  function buildEditableFlowJson(flow, inlineScriptSession, selectedContext = []) {
@@ -333,7 +339,9 @@ function buildEditableFlowJson(flow, inlineScriptSession, selectedContext = [])
333
339
  };
334
340
  }
335
341
  let failureModule = flow.value.failure_module;
336
- if (failureModule?.value?.type === 'rawscript' && failureModule.value.content && inlineScriptSession) {
342
+ if (failureModule?.value?.type === 'rawscript' &&
343
+ failureModule.value.content &&
344
+ inlineScriptSession) {
337
345
  inlineScriptSession.set(failureModule.id, failureModule.value.content);
338
346
  failureModule = {
339
347
  ...failureModule,
@@ -347,7 +355,8 @@ function buildEditableFlowJson(flow, inlineScriptSession, selectedContext = [])
347
355
  modules,
348
356
  schema: flow.schema ?? null,
349
357
  preprocessor_module: preprocessorModule ?? null,
350
- failure_module: failureModule ?? null
358
+ failure_module: failureModule ?? null,
359
+ groups: flow.value.groups ?? null
351
360
  };
352
361
  }
353
362
  const langSchema = z.enum(SUPPORTED_CHAT_SCRIPT_LANGUAGES);
@@ -377,9 +386,14 @@ const setFlowJsonToolSchema = z.object({
377
386
  .string()
378
387
  .optional()
379
388
  .nullable()
380
- .describe('JSON string containing the optional failure module')
389
+ .describe('JSON string containing the optional failure module'),
390
+ groups: z
391
+ .string()
392
+ .optional()
393
+ .nullable()
394
+ .describe('JSON string containing the optional array of semantic flow groups (summary, note, autocollapse, start_id, end_id, color). Pass null to clear groups.')
381
395
  });
382
- const setFlowJsonToolDef = createToolDef(setFlowJsonToolSchema, 'set_flow_json', 'Set the complete flow modules array and optionally the flow input schema, preprocessor module, and failure module.', { strict: false });
396
+ const setFlowJsonToolDef = createToolDef(setFlowJsonToolSchema, 'set_flow_json', 'Set the complete flow modules array and optionally the flow input schema, preprocessor module, failure module, and semantic groups.', { strict: false });
383
397
  const setPreprocessorModuleToolSchema = z.object({
384
398
  module: specialModuleToolArgSchema
385
399
  });
@@ -427,10 +441,7 @@ function validateSpecialFlowModule(module, field) {
427
441
  return parsedModule;
428
442
  }
429
443
  const patchFlowJsonSchema = z.object({
430
- old_string: z
431
- .string()
432
- .min(1)
433
- .describe('Exact text to find in the current compact flow JSON'),
444
+ old_string: z.string().min(1).describe('Exact text to find in the current compact flow JSON'),
434
445
  new_string: z.string().describe('Replacement JSON text'),
435
446
  replace_all: z
436
447
  .boolean()
@@ -475,6 +486,7 @@ export const flowTools = [
475
486
  createDbSchemaTool(),
476
487
  createSearchWorkspaceTool(),
477
488
  createGetRunnableDetailsTool(),
489
+ ...createWorkspaceMutationTools(),
478
490
  {
479
491
  def: resourceTypeToolDef,
480
492
  fn: async ({ args, toolId, workspace, toolCallbacks }) => {
@@ -726,7 +738,8 @@ export const flowTools = [
726
738
  modules: parsedFlow.modules,
727
739
  schema: parsedFlow.schema,
728
740
  preprocessorModule: parsedFlow.preprocessor_module,
729
- failureModule: parsedFlow.failure_module
741
+ failureModule: parsedFlow.failure_module,
742
+ groups: parsedFlow.groups
730
743
  });
731
744
  const warning = formatEmptyInlineScriptWarning(updateResult);
732
745
  const selectedModule = findModuleInFlow(parsedFlow, selectedId) ?? undefined;
@@ -751,7 +764,9 @@ export const flowTools = [
751
764
  const parsedArgs = setPreprocessorModuleToolSchema.parse(args);
752
765
  const parsedModule = validateSpecialFlowModule(parseOptionalJsonArg(parsedArgs.module, 'module'), 'preprocessor_module');
753
766
  toolCallbacks.setToolStatus(toolId, {
754
- content: parsedModule === null ? 'Removing preprocessor module...' : 'Setting preprocessor module...'
767
+ content: parsedModule === null
768
+ ? 'Removing preprocessor module...'
769
+ : 'Setting preprocessor module...'
755
770
  });
756
771
  const updateResult = await helpers.setFlowJson({ preprocessorModule: parsedModule });
757
772
  const warning = formatEmptyInlineScriptWarning(updateResult);
@@ -804,16 +819,18 @@ export const flowTools = [
804
819
  showDetails: true,
805
820
  showFade: true,
806
821
  fn: async ({ args, helpers, toolId, toolCallbacks }) => {
807
- const { modules, schema, preprocessor_module, failure_module } = args;
822
+ const { modules, schema, preprocessor_module, failure_module, groups } = args;
808
823
  let parsedModules;
809
824
  let parsedSchema;
810
825
  let parsedPreprocessorModule;
811
826
  let parsedFailureModule;
827
+ let parsedGroups;
812
828
  // Parse JSON strings
813
829
  parsedModules = parseOptionalJsonArg(modules, 'modules');
814
830
  parsedSchema = parseOptionalJsonArg(schema, 'schema');
815
831
  parsedPreprocessorModule = parseOptionalJsonArg(preprocessor_module, 'preprocessor_module');
816
832
  parsedFailureModule = parseOptionalJsonArg(failure_module, 'failure_module');
833
+ parsedGroups = parseOptionalJsonArg(groups, 'groups');
817
834
  if (parsedModules === null) {
818
835
  parsedModules = undefined;
819
836
  }
@@ -832,10 +849,16 @@ export const flowTools = [
832
849
  }
833
850
  parsedPreprocessorModule = validateSpecialFlowModule(parsedPreprocessorModule, 'preprocessor_module');
834
851
  parsedFailureModule = validateSpecialFlowModule(parsedFailureModule, 'failure_module');
852
+ if (parsedGroups !== undefined) {
853
+ const effectiveModules = parsedModules ?? helpers.getFlowAndSelectedId().flow.value.modules ?? [];
854
+ const moduleIdsForGroups = new Set(collectAllFlowModuleIdsFromModules(effectiveModules));
855
+ parsedGroups = validateFlowGroups(parsedGroups, moduleIdsForGroups);
856
+ }
835
857
  const ids = [
836
858
  ...(parsedModules ? collectAllFlowModuleIdsFromModules(parsedModules) : []),
837
- ...([parsedPreprocessorModule, parsedFailureModule].filter((module) => module !== undefined && module !== null)
838
- .map((module) => module.id))
859
+ ...[parsedPreprocessorModule, parsedFailureModule]
860
+ .filter((module) => module !== undefined && module !== null)
861
+ .map((module) => module.id)
839
862
  ];
840
863
  if (ids.length !== new Set(ids).size) {
841
864
  throw new Error('Duplicate module IDs found in flow');
@@ -849,7 +872,8 @@ export const flowTools = [
849
872
  ...(parsedPreprocessorModule !== undefined
850
873
  ? { preprocessorModule: parsedPreprocessorModule }
851
874
  : {}),
852
- ...(parsedFailureModule !== undefined ? { failureModule: parsedFailureModule } : {})
875
+ ...(parsedFailureModule !== undefined ? { failureModule: parsedFailureModule } : {}),
876
+ ...(parsedGroups !== undefined ? { groups: parsedGroups } : {})
853
877
  });
854
878
  const warning = formatEmptyInlineScriptWarning(updateResult);
855
879
  // Update exprsToSet if the selected module has input_transforms
@@ -858,9 +882,9 @@ export const flowTools = [
858
882
  parsedFailureModule !== undefined) {
859
883
  const { selectedId } = helpers.getFlowAndSelectedId();
860
884
  const selectedModule = selectedId === SPECIAL_MODULE_IDS.PREPROCESSOR
861
- ? parsedPreprocessorModule ?? undefined
885
+ ? (parsedPreprocessorModule ?? undefined)
862
886
  : selectedId === SPECIAL_MODULE_IDS.FAILURE
863
- ? parsedFailureModule ?? undefined
887
+ ? (parsedFailureModule ?? undefined)
864
888
  : parsedModules
865
889
  ? findModuleInModules(parsedModules, selectedId)
866
890
  : undefined;
@@ -928,13 +952,15 @@ export function prepareFlowSystemMessage(customPrompt) {
928
952
  **Resources & Schema:**
929
953
  - **Search resource types** → \`resource_type\`
930
954
  - **Get database schema** → \`get_db_schema\`
955
+ - **Create a schedule for the current flow** → \`create_schedule\`
956
+ - **Create a trigger for the current flow** → \`create_trigger\`
931
957
 
932
958
  ## Quick Edits with patch_flow_json
933
959
 
934
960
  Use \`patch_flow_json\` for small, localized changes when you can target an exact snippet from the \`CURRENT FLOW JSON COMPACT\` block below.
935
961
 
936
962
  Always copy the exact search text from the \`CURRENT FLOW JSON COMPACT\` block below.
937
- The compact JSON is a single object with \`modules\`, \`schema\`, \`preprocessor_module\`, and \`failure_module\` keys.
963
+ The compact JSON is a single object with \`modules\`, \`schema\`, \`preprocessor_module\`, \`failure_module\`, and \`groups\` keys.
938
964
 
939
965
  **Parameters:**
940
966
  - \`old_string\`: Exact JSON text to find
@@ -955,13 +981,14 @@ ${FLOW_CHAT_SPECIAL_MODULES}
955
981
 
956
982
  ## Flow Modification with set_flow_json
957
983
 
958
- Use the \`set_flow_json\` tool to set the entire flow structure at once. Provide the complete modules array and optionally the flow input schema, \`preprocessor_module\`, and \`failure_module\`.
984
+ Use the \`set_flow_json\` tool to set the entire flow structure at once. Provide the complete modules array and optionally the flow input schema, \`preprocessor_module\`, \`failure_module\`, and \`groups\`.
959
985
 
960
986
  **Parameters:**
961
987
  - \`modules\`: Array of flow modules (required)
962
988
  - \`schema\`: Flow input schema in JSON Schema format (optional)
963
989
  - \`preprocessor_module\`: Special module that runs before \`modules\` (optional, separate from \`modules\`)
964
990
  - \`failure_module\`: Special module that runs on failure (optional, separate from \`modules\`)
991
+ - \`groups\`: Array of semantic groups for organizing modules in the editor (optional). Each group has \`summary\` (display name), \`note\` (markdown description shown below the group header — attached directly to the group, not a separate sticky note), \`autocollapse\`, \`start_id\`, \`end_id\`, and \`color\`. \`start_id\` and \`end_id\` must reference existing module IDs in the flow (not \`preprocessor\` or \`failure\`). Groups do not affect execution — they provide naming and collapsibility in the editor. Pass \`null\` to clear existing groups.
965
992
 
966
993
  **Example - Simple flow:**
967
994
  \`\`\`javascript
@@ -1,13 +1,15 @@
1
- import type { FlowModule, OpenFlow, RawScript } from '../../../../gen';
1
+ import type { FlowModule, FlowValue, OpenFlow, RawScript } from '../../../../gen';
2
2
  import type { InlineScriptSession } from './inlineScriptsUtils';
3
3
  type FlowLike = Pick<OpenFlow, 'value'> & {
4
4
  schema?: Record<string, any>;
5
5
  };
6
+ export type FlowGroup = NonNullable<FlowValue['groups']>[number];
6
7
  export interface FlowJsonUpdate {
7
8
  modules?: FlowModule[];
8
9
  schema?: Record<string, any> | null;
9
10
  preprocessorModule?: FlowModule | null;
10
11
  failureModule?: FlowModule | null;
12
+ groups?: FlowGroup[] | null;
11
13
  }
12
14
  export interface FlowJsonUpdateResult {
13
15
  emptyInlineScriptModuleIds: string[];
@@ -15,5 +17,6 @@ export interface FlowJsonUpdateResult {
15
17
  export declare function updateRawScriptModuleContent(flow: FlowLike, id: string, code: string): (FlowModule & {
16
18
  value: RawScript;
17
19
  }) | undefined;
18
- export declare function applyFlowJsonUpdate(flow: FlowLike, inlineScriptSession: InlineScriptSession, { modules, schema, preprocessorModule, failureModule }: FlowJsonUpdate): FlowJsonUpdateResult;
20
+ export declare function validateFlowGroups(rawGroups: unknown, moduleIds?: Set<string>): FlowGroup[] | null;
21
+ export declare function applyFlowJsonUpdate(flow: FlowLike, inlineScriptSession: InlineScriptSession, { modules, schema, preprocessorModule, failureModule, groups }: FlowJsonUpdate): FlowJsonUpdateResult;
19
22
  export {};
@@ -9,7 +9,36 @@ export function updateRawScriptModuleContent(flow, id, code) {
9
9
  rawScriptModule.value.content = code;
10
10
  return rawScriptModule;
11
11
  }
12
- export function applyFlowJsonUpdate(flow, inlineScriptSession, { modules, schema, preprocessorModule, failureModule }) {
12
+ export function validateFlowGroups(rawGroups, moduleIds) {
13
+ if (rawGroups == null) {
14
+ return null;
15
+ }
16
+ if (!Array.isArray(rawGroups)) {
17
+ throw new Error('Flow groups must be an array');
18
+ }
19
+ return rawGroups.map((group, index) => {
20
+ if (!group || typeof group !== 'object' || Array.isArray(group)) {
21
+ throw new Error(`Invalid group at index ${index}: must be an object`);
22
+ }
23
+ const g = group;
24
+ if (typeof g.start_id !== 'string' || !g.start_id) {
25
+ throw new Error(`Invalid group at index ${index}: start_id must be a non-empty string`);
26
+ }
27
+ if (typeof g.end_id !== 'string' || !g.end_id) {
28
+ throw new Error(`Invalid group at index ${index}: end_id must be a non-empty string`);
29
+ }
30
+ if (moduleIds) {
31
+ if (!moduleIds.has(g.start_id)) {
32
+ throw new Error(`Invalid group at index ${index}: start_id "${g.start_id}" does not match any flow module`);
33
+ }
34
+ if (!moduleIds.has(g.end_id)) {
35
+ throw new Error(`Invalid group at index ${index}: end_id "${g.end_id}" does not match any flow module`);
36
+ }
37
+ }
38
+ return g;
39
+ });
40
+ }
41
+ export function applyFlowJsonUpdate(flow, inlineScriptSession, { modules, schema, preprocessorModule, failureModule, groups }) {
13
42
  const emptyInlineScriptModuleIds = new Set();
14
43
  if (modules !== undefined) {
15
44
  flow.value.modules = restoreFlowModules(modules, inlineScriptSession, emptyInlineScriptModuleIds);
@@ -29,6 +58,9 @@ export function applyFlowJsonUpdate(flow, inlineScriptSession, { modules, schema
29
58
  ? undefined
30
59
  : restoreFlowModule(failureModule, inlineScriptSession, emptyInlineScriptModuleIds);
31
60
  }
61
+ if (groups !== undefined) {
62
+ flow.value.groups = groups == null || groups.length === 0 ? undefined : groups;
63
+ }
32
64
  return {
33
65
  emptyInlineScriptModuleIds: Array.from(emptyInlineScriptModuleIds)
34
66
  };