windmill-components 1.700.2 → 1.700.3

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 (75) hide show
  1. package/dist/appPolicy/myFunction.es.js +1337 -0
  2. package/dist/sharedUtils/common.d.ts +2 -5
  3. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/select.d.ts +0 -2
  4. package/dist/sharedUtils/components/apps/components/display/dbtable/utils.d.ts +3 -14
  5. package/dist/sharedUtils/components/apps/editor/appPolicy.d.ts +1 -1
  6. package/dist/sharedUtils/components/apps/editor/appUtilsS3.d.ts +1 -12
  7. package/dist/sharedUtils/components/apps/editor/component/components.d.ts +2 -68
  8. package/dist/sharedUtils/components/apps/inputType.d.ts +2 -4
  9. package/dist/sharedUtils/components/apps/sharedTypes.d.ts +0 -2
  10. package/dist/sharedUtils/components/dbTypes.d.ts +0 -3
  11. package/dist/sharedUtils/components/raw_apps/rawAppPolicy.d.ts +1 -1
  12. package/dist/sharedUtils/components/raw_apps/utils.d.ts +1 -1
  13. package/dist/sharedUtils/components/triggers/utils.d.ts +3 -2
  14. package/dist/sharedUtils/gen/schemas.gen.d.ts +71 -915
  15. package/dist/sharedUtils/gen/services.gen.d.ts +23 -329
  16. package/dist/sharedUtils/gen/types.gen.d.ts +141 -1870
  17. package/dist/sharedUtils/hub.d.ts +0 -1
  18. package/dist/sharedUtils/jsr.json +5 -5
  19. package/dist/sharedUtils/lib.d.ts +1 -1
  20. package/dist/sharedUtils/lib.es.js +79 -241
  21. package/dist/sharedUtils/package.json +11 -11
  22. package/dist/sharedUtils/stores.d.ts +0 -1
  23. package/dist/sharedUtils/svelte5Utils.svelte.d.ts +1 -32
  24. package/dist/sharedUtils/utils.d.ts +4 -19
  25. package/package/components/DisplayResultControlBar.svelte +26 -11
  26. package/package/components/JobArgs.svelte +43 -24
  27. package/package/components/ShareModal.svelte.d.ts +1 -1
  28. package/package/components/apps/components/helpers/RunnableComponent.svelte.d.ts +0 -3
  29. package/package/components/copilot/CustomAIPrompts.svelte +3 -2
  30. package/package/components/copilot/chat/AIChatInput.svelte +2 -0
  31. package/package/components/copilot/chat/AIChatManager.svelte.js +52 -14
  32. package/package/components/copilot/chat/CreatedResourceActionDrawers.svelte +15 -0
  33. package/package/components/copilot/chat/ToolMessageActions.svelte +4 -2
  34. package/package/components/copilot/chat/app/core.js +2 -30
  35. package/package/components/copilot/chat/flow/core.js +13 -351
  36. package/package/components/copilot/chat/flow/editableFlowJson.d.ts +52 -0
  37. package/package/components/copilot/chat/flow/editableFlowJson.js +328 -0
  38. package/package/components/copilot/chat/flow/inlineScriptsUtils.js +2 -2
  39. package/package/components/copilot/chat/global/core.d.ts +5 -0
  40. package/package/components/copilot/chat/global/core.js +1739 -0
  41. package/package/components/copilot/chat/global/core.test.d.ts +1 -0
  42. package/package/components/copilot/chat/global/core.test.js +123 -0
  43. package/package/components/copilot/chat/global/deployRequests.d.ts +7 -0
  44. package/package/components/copilot/chat/global/deployRequests.js +76 -0
  45. package/package/components/copilot/chat/global/deployRequests.test.d.ts +1 -0
  46. package/package/components/copilot/chat/global/deployRequests.test.js +142 -0
  47. package/package/components/copilot/chat/global/draftStore.svelte.d.ts +55 -0
  48. package/package/components/copilot/chat/global/draftStore.svelte.js +78 -0
  49. package/package/components/copilot/chat/global/draftStore.test.d.ts +1 -0
  50. package/package/components/copilot/chat/global/draftStore.test.js +44 -0
  51. package/package/components/copilot/chat/global/gate.d.ts +1 -0
  52. package/package/components/copilot/chat/global/gate.js +27 -0
  53. package/package/components/copilot/chat/shared.d.ts +16 -2
  54. package/package/components/copilot/chat/shared.js +40 -0
  55. package/package/components/copilot/chat/workspaceToolsZod.gen.d.ts +28 -9
  56. package/package/components/copilot/chat/workspaceToolsZod.gen.js +19 -0
  57. package/package/components/raw_apps/templates.d.ts +77 -0
  58. package/package/components/raw_apps/templates.js +618 -0
  59. package/package/components/runs/runsFilter.d.ts +1 -1
  60. package/package/components/settings/AIPromptsModal.svelte +5 -2
  61. package/package/components/triggers/azure/AzureTriggerEditorConfigSection.svelte.d.ts +1 -1
  62. package/package/gen/core/OpenAPI.js +1 -1
  63. package/package/gen/schemas.gen.d.ts +37 -355
  64. package/package/gen/schemas.gen.js +39 -359
  65. package/package/gen/services.gen.d.ts +4 -280
  66. package/package/gen/services.gen.js +7 -565
  67. package/package/gen/types.gen.d.ts +77 -1135
  68. package/package/system_prompts/index.d.ts +2 -0
  69. package/package/system_prompts/index.js +8 -0
  70. package/package/system_prompts/prompts.d.ts +2 -0
  71. package/package/system_prompts/prompts.js +381 -0
  72. package/package/utils_deployable.d.ts +5 -318
  73. package/package.json +1 -1
  74. package/dist/sharedUtils/components/assets/lib.d.ts +0 -25
  75. package/dist/sharedUtils/components/icons/index.d.ts +0 -101
@@ -0,0 +1,1739 @@
1
+ import { AppService, AzureTriggerService, FlowService, GcpTriggerService, HttpTriggerService, KafkaTriggerService, MqttTriggerService, NatsTriggerService, PostgresTriggerService, ResourceService, ScheduleService, ScriptService, SqsTriggerService, VariableService, WebsocketTriggerService } from '../../../../gen';
2
+ import { $ScriptLang } from '../../../../gen/schemas.gen';
3
+ import { updateRawAppPolicy } from '../../../raw_apps/rawAppPolicy';
4
+ import { FRAMEWORK_TEMPLATES, STARTER_RUNNABLE, STARTER_RUNNABLE_KEY } from '../../../raw_apps/templates';
5
+ import { applyEditableFlowJsonToFlow, buildEditableFlowJson, validateEditableFlowJson } from '../flow/editableFlowJson';
6
+ import { createInlineScriptSession } from '../flow/inlineScriptsUtils';
7
+ import { getFlowPrompt, getRawAppPrompt, getResourcePrompt, getScriptPrompt } from '../../../../system_prompts';
8
+ import { z } from 'zod';
9
+ import { createToolDef, findAndReplace } from '../shared';
10
+ import { flowModuleSchema, flowModulesSchema } from '../flow/openFlowZod.gen';
11
+ import { resourceRequestSchema, scheduleRequestSchema, triggerRequestSchemas, variableRequestSchema } from '../workspaceToolsZod.gen';
12
+ import { getWorkspaceItemKey, globalDraftStore, TRIGGER_KINDS } from './draftStore.svelte';
13
+ import { buildFlowDeployRequestBody, buildScriptDeployRequestBody } from './deployRequests';
14
+ const ITEM_TYPES = [
15
+ 'script',
16
+ 'flow',
17
+ 'schedule',
18
+ 'trigger',
19
+ 'resource',
20
+ 'variable',
21
+ 'app'
22
+ ];
23
+ const INSTRUCTION_SUBJECTS = [
24
+ 'script',
25
+ 'flow',
26
+ 'resource',
27
+ 'app'
28
+ ];
29
+ const MAX_LIST_LIMIT = 100;
30
+ const itemTypeSchema = z.enum(ITEM_TYPES);
31
+ const instructionSubjectSchema = z.enum(INSTRUCTION_SUBJECTS);
32
+ const triggerKindSchema = z.enum(TRIGGER_KINDS);
33
+ const scriptLangSchema = z.enum($ScriptLang.enum);
34
+ const getInstructionsSchema = z.object({
35
+ subject: instructionSubjectSchema.describe("The workspace item type to get authoring instructions for (script, flow, resource, app). Schedules, triggers, and variables don't need instructions — their tool schemas describe everything."),
36
+ language: scriptLangSchema
37
+ .optional()
38
+ .describe('Required when subject is script. Use the existing script language when modifying, or the requested target language when creating.')
39
+ });
40
+ const listWorkspaceItemsSchema = z.object({
41
+ types: z
42
+ .array(itemTypeSchema)
43
+ .optional()
44
+ .describe('Optional item types to list. Defaults to scripts and flows. Pass schedule or trigger explicitly when needed (listing triggers spans 9 kinds and is heavier).'),
45
+ query: z.string().optional().describe('Optional case-insensitive path or summary search string.'),
46
+ path_prefix: z
47
+ .string()
48
+ .optional()
49
+ .describe('Optional path prefix filter, such as f/ or u/user/.'),
50
+ limit: z
51
+ .number()
52
+ .int()
53
+ .min(1)
54
+ .max(MAX_LIST_LIMIT)
55
+ .optional()
56
+ .describe('Maximum number of items to return. Defaults to 50 and is capped at 100.')
57
+ });
58
+ const readWorkspaceItemSchema = z.object({
59
+ type: itemTypeSchema,
60
+ path: z.string().describe('Workspace path of the item to read.'),
61
+ trigger_kind: triggerKindSchema
62
+ .optional()
63
+ .describe('Required when type is trigger. Identifies which trigger service to call.')
64
+ });
65
+ const writeScriptSchema = z.object({
66
+ path: z
67
+ .string()
68
+ .describe('Workspace path of the script, e.g. f/folder/name or u/user/name.'),
69
+ summary: z.string().optional().describe('Short human-readable summary.'),
70
+ language: scriptLangSchema.describe('Script language.'),
71
+ content: z.string().describe('Full script source code.')
72
+ });
73
+ const flowValueSchema = z
74
+ .looseObject({
75
+ modules: flowModulesSchema.describe('Sequential flow modules.'),
76
+ preprocessor_module: flowModuleSchema
77
+ .nullable()
78
+ .optional()
79
+ .describe("Optional preprocessor module with id 'preprocessor'. Runs before normal modules; cannot reference results.*."),
80
+ failure_module: flowModuleSchema
81
+ .nullable()
82
+ .optional()
83
+ .describe("Optional failure handler module with id 'failure'.")
84
+ })
85
+ .describe('OpenFlow value: modules plus optional preprocessor_module and failure_module.');
86
+ const readFlowModuleCodeSchema = z.object({
87
+ path: z.string().describe('Workspace path of the flow.'),
88
+ module_id: z
89
+ .string()
90
+ .describe('Module id whose inline rawscript content to read. Must reference a module whose value.type is "rawscript".')
91
+ });
92
+ const setFlowModuleCodeSchema = z.object({
93
+ path: z.string().describe('Workspace path of the flow.'),
94
+ module_id: z
95
+ .string()
96
+ .describe('Module id whose inline rawscript content to overwrite. Must reference a module whose value.type is "rawscript". Use patch_flow_json for structural changes.'),
97
+ code: z.string().describe('New script source. Replaces the module\'s value.content entirely.')
98
+ });
99
+ // `value` is taken as a JSON string rather than a typed object because the
100
+ // underlying flowValueSchema is recursive (modules can contain modules), which
101
+ // makes z.toJSONSchema emit $defs/$ref. Gemini's tools API rejects those
102
+ // keywords ("Unknown name $ref/$defs"). The string is parsed and validated
103
+ // against flowValueSchema inside the handler. Same trick as set_flow_json in
104
+ // chat/flow/core.ts (see comment on its schema).
105
+ const writeFlowSchema = z.object({
106
+ path: z
107
+ .string()
108
+ .describe('Workspace path of the flow, e.g. f/folder/name or u/user/name.'),
109
+ summary: z.string().optional().describe('Short human-readable summary.'),
110
+ value: z
111
+ .string()
112
+ .describe('JSON string of the OpenFlow value object: { modules, preprocessor_module?, failure_module? }. Pass it as a JSON-encoded string, not a nested object.')
113
+ });
114
+ const writeScheduleSchema = scheduleRequestSchema;
115
+ const writeTriggerSchema = z.object({
116
+ kind: triggerKindSchema.describe('Trigger kind. Determines which fields are valid in config.'),
117
+ config: z
118
+ .union([
119
+ triggerRequestSchemas.http,
120
+ triggerRequestSchemas.websocket,
121
+ triggerRequestSchemas.kafka,
122
+ triggerRequestSchemas.nats,
123
+ triggerRequestSchemas.postgres,
124
+ triggerRequestSchemas.mqtt,
125
+ triggerRequestSchemas.sqs,
126
+ triggerRequestSchemas.gcp,
127
+ triggerRequestSchemas.azure
128
+ ])
129
+ .describe('Full trigger configuration. Must include path, script_path, is_flow plus the kind-specific fields.')
130
+ });
131
+ const writeResourceSchema = resourceRequestSchema;
132
+ const writeVariableSchema = variableRequestSchema;
133
+ const searchResourceTypesSchema = z.object({
134
+ query: z.string().describe('Substring to match against resource type names.'),
135
+ limit: z
136
+ .number()
137
+ .int()
138
+ .min(1)
139
+ .max(20)
140
+ .optional()
141
+ .describe('Max number of resource types to return. Defaults to 5.')
142
+ });
143
+ const deleteWorkspaceItemSchema = z.object({
144
+ type: itemTypeSchema,
145
+ path: z.string().describe('Workspace path of the item to delete.'),
146
+ trigger_kind: triggerKindSchema
147
+ .optional()
148
+ .describe('Required when type is trigger. Identifies which trigger service to call.')
149
+ });
150
+ const deployWorkspaceItemSchema = z.object({
151
+ type: itemTypeSchema,
152
+ path: z.string().describe('Workspace path of the draft to deploy.'),
153
+ trigger_kind: triggerKindSchema
154
+ .optional()
155
+ .describe('Required when type is trigger. Must match the draft trigger kind.'),
156
+ deployment_message: z
157
+ .string()
158
+ .optional()
159
+ .describe('Optional deployment message recorded with the change.')
160
+ });
161
+ const editScriptSchema = z.object({
162
+ path: z.string().describe('Workspace path of the script to edit.'),
163
+ old_string: z.string().min(1).describe('Exact text to find in the script source.'),
164
+ new_string: z.string().describe('Replacement text.'),
165
+ replace_all: z
166
+ .boolean()
167
+ .optional()
168
+ .default(false)
169
+ .describe('When true, replace every exact match. When false, old_string must match exactly once.')
170
+ });
171
+ const patchFlowJsonSchema = z.object({
172
+ path: z.string().describe('Workspace path of the flow to edit.'),
173
+ old_string: z
174
+ .string()
175
+ .min(1)
176
+ .describe('Exact text to find in the flow value, serialized as compact JSON (no indent).'),
177
+ new_string: z.string().describe('Replacement JSON text.'),
178
+ replace_all: z
179
+ .boolean()
180
+ .optional()
181
+ .default(false)
182
+ .describe('When true, replace every exact match. When false, old_string must match exactly once.')
183
+ });
184
+ // ============= App tools (raw apps) =============
185
+ const backendRunnableSchema = z
186
+ .object({
187
+ name: z.string().describe('Short summary/description of what the runnable does.'),
188
+ type: z
189
+ .enum(['inline', 'script', 'flow', 'hubscript'])
190
+ .describe('Runnable kind: "inline" for custom code stored on the app; "script"/"flow" for a workspace runnable; "hubscript" for a hub script.'),
191
+ staticInputs: z
192
+ .record(z.string(), z.any())
193
+ .optional()
194
+ .describe('Static inputs that are not overridable by the frontend caller. Useful for workspace/hub scripts to pre-fill arguments.'),
195
+ inlineScript: z
196
+ .object({
197
+ language: z.enum(['bun', 'python3']).describe('Inline script language.'),
198
+ content: z.string().describe('Inline script source. Must export a main function.')
199
+ })
200
+ .optional()
201
+ .describe('Required when type is "inline".'),
202
+ path: z
203
+ .string()
204
+ .optional()
205
+ .describe('Required when type is "script", "flow", or "hubscript". Workspace path of the runnable.')
206
+ })
207
+ .describe('Backend runnable shape: same as in app mode. Inline runnables carry their script content; path runnables reference an existing workspace/hub item.');
208
+ const readAppFileSchema = z.object({
209
+ path: z.string().describe('Workspace path of the app, e.g. f/folder/name.'),
210
+ file_path: z
211
+ .string()
212
+ .describe('Frontend file path like /index.tsx, or backend inline runnable path like backend/<key>/main.ts (or main.py).')
213
+ });
214
+ const writeAppFileSchema = z.object({
215
+ path: z.string().describe('Workspace path of the app.'),
216
+ file_path: z
217
+ .string()
218
+ .describe('Frontend file path (must start with /). Use write_app_runnable to set inline backend script content.'),
219
+ content: z.string().describe('Full file content.')
220
+ });
221
+ const deleteAppFileSchema = z.object({
222
+ path: z.string().describe('Workspace path of the app.'),
223
+ file_path: z
224
+ .string()
225
+ .describe('Frontend file path to remove from the draft. Use delete_app_runnable for backend runnables.')
226
+ });
227
+ const patchAppFileSchema = z.object({
228
+ path: z.string().describe('Workspace path of the app.'),
229
+ file_path: z
230
+ .string()
231
+ .describe('Frontend file path like /index.tsx, or backend inline runnable path like backend/<key>/main.ts.'),
232
+ old_string: z.string().min(1).describe('Exact text to find.'),
233
+ new_string: z.string().describe('Replacement text.'),
234
+ replace_all: z
235
+ .boolean()
236
+ .optional()
237
+ .default(false)
238
+ .describe('When true, replace every exact match. When false, old_string must match exactly once.')
239
+ });
240
+ const writeAppRunnableSchema = z.object({
241
+ path: z.string().describe('Workspace path of the app.'),
242
+ key: z
243
+ .string()
244
+ .describe('Unique key for the backend runnable (called from frontend as backend.<key>()). Becomes the file id at backend/<key>/main.{ts|py}.'),
245
+ runnable: backendRunnableSchema
246
+ });
247
+ const deleteAppRunnableSchema = z.object({
248
+ path: z.string().describe('Workspace path of the app.'),
249
+ key: z.string().describe('Key of the backend runnable to remove.')
250
+ });
251
+ const FRAMEWORK_KEYS = ['react19', 'react18', 'svelte5', 'vue'];
252
+ const initAppSchema = z.object({
253
+ path: z
254
+ .string()
255
+ .describe('Workspace path for the new app, e.g. f/folder/my_app or u/username/my_app. Errors if an app already exists at this path or a draft is already in flight.'),
256
+ summary: z.string().optional().describe('Short human-readable summary of the app.'),
257
+ framework: z
258
+ .enum(FRAMEWORK_KEYS)
259
+ .describe('Frontend framework template. Confirm with the user before calling — never default silently. react19 is recommended for new apps.'),
260
+ data: z
261
+ .object({
262
+ datatable: z.string().optional().describe('Default datatable name (e.g. "main").'),
263
+ schema: z.string().optional().describe('Default schema (PostgreSQL schema, optional).'),
264
+ tables: z
265
+ .array(z.string())
266
+ .optional()
267
+ .describe('Initially-whitelisted tables, in the format "<datatable>/<table>" or "<datatable>/<schema>:<table>".')
268
+ })
269
+ .optional()
270
+ .describe('Optional datatable configuration. Omit unless the user asked to wire one up.')
271
+ });
272
+ const GLOBAL_SYSTEM_PROMPT = `You are Windmill's global workspace assistant.
273
+
274
+ You can inspect workspace scripts, flows, schedules, triggers, resources, variables, and apps, then create draft changes in the frontend AI draft store.
275
+
276
+ Important rules:
277
+ - write_{script,flow,schedule,trigger,resource,variable} create or overwrite drafts. They do not save, deploy, or mutate workspace items.
278
+ - edit_script and patch_flow_json apply small exact-text edits and save the result as a draft. Prefer them for localized changes; use write_* for large rewrites.
279
+ - For flows specifically: read_workspace_item and patch_flow_json work on a COMPACT view where rawscript module bodies are replaced with the placeholder "inline_script.<moduleId>". Use read_flow_module_code / set_flow_module_code to inspect or overwrite an inline script body; use patch_flow_json for structural edits.
280
+ - deploy_workspace_item persists a draft to the workspace via the real backend create/update API and removes the draft. Requires user confirmation. Only call after the user has reviewed the draft and explicitly asked to deploy.
281
+ - delete_workspace_item permanently removes a workspace item (and any matching draft). Irreversible. Requires user confirmation. Only call when the user has explicitly asked to delete.
282
+ - Use list_workspace_items before broad reads.
283
+ - Use read_workspace_item before overwriting an existing item, unless the user already provided the complete current item. For triggers, pass trigger_kind.
284
+ - Variable values are NEVER returned by read_workspace_item or list_workspace_items — only metadata (path, description, is_secret). The model cannot read secret values, by design.
285
+ - For resources that need secrets, write a Variable first (with is_secret: true), then in the resource value reference it as "$var:path/to/variable". When deploying both, deploy the variable before the resource.
286
+ - Use search_resource_types before write_resource to discover the resource_type name and the JSON Schema its value must match.
287
+ - Use get_instructions before writing a script, flow, resource, or app. For scripts, pass the target language; when modifying, use the language from the item you read.
288
+ - Schedules, triggers, and variables do not need get_instructions — their tool schemas describe every field.
289
+ - A workspace item is { type, path, summary?, language?, triggerKind?, value, isDraft }. For scripts, value is the source code string. For flows, value is { value: <OpenFlow value>, schema, groups } so the inputs schema and groups round-trip through deploy. For schedules/triggers/resources/variables, value is the full request body for that type. For apps, value is { files, runnables, data?, policy?, custom_path? } with frontend file contents and backend runnable definitions.
290
+ - Apps (raw apps): use list_workspace_items with types: ['app'] to find them, read_workspace_item with type 'app' for a metadata summary (file paths + runnable list, no contents), then read_app_file to read individual files. Edit with write_app_file / patch_app_file / delete_app_file for frontend files and write_app_runnable / delete_app_runnable for backend runnables. Frontend file paths start with "/" (e.g. /index.tsx). Backend inline runnables are addressed as "backend/<key>/main.{ts|py}". /wmill.d.ts is generated and cannot be written.
291
+ - To create a new raw app, use init_app. Before calling it, confirm framework (react19 / react18 / svelte5 / vue), path, and summary with the user — do not silently default to react19, even though it is the recommended choice.
292
+ - Apps cannot be deployed from chat. The app editor bundles JS/CSS before save; tell the user to open the app editor to deploy app drafts.
293
+ - Keep context targeted. Do not read unrelated items.
294
+ - Be explicit with the user when you create or update a draft.`;
295
+ const DEFAULT_LIST_TYPES = ['script', 'flow'];
296
+ function getRequestedTypes(types) {
297
+ return types && types.length > 0 ? types : [...DEFAULT_LIST_TYPES];
298
+ }
299
+ function itemMatches(item, query) {
300
+ const normalized = query?.trim().toLowerCase();
301
+ if (!normalized)
302
+ return true;
303
+ return (item.path.toLowerCase().includes(normalized) ||
304
+ (item.summary?.toLowerCase().includes(normalized) ?? false));
305
+ }
306
+ function scriptToItem(script, includeValue) {
307
+ return {
308
+ type: 'script',
309
+ path: script.path,
310
+ summary: script.summary,
311
+ language: script.language,
312
+ value: includeValue ? script.content : undefined,
313
+ isDraft: false
314
+ };
315
+ }
316
+ function flowToItem(flow, includeValue) {
317
+ return {
318
+ type: 'flow',
319
+ path: flow.path,
320
+ summary: flow.summary,
321
+ value: includeValue
322
+ ? { value: flow.value, schema: flow.schema, groups: flow.value.groups ?? null }
323
+ : undefined,
324
+ isDraft: false
325
+ };
326
+ }
327
+ /**
328
+ * Turn a flow workspace item into the compact response we send to the model:
329
+ * rawscript content is replaced with `inline_script.<moduleId>` placeholders.
330
+ * The model retrieves real content via `read_flow_module_code` and edits it via
331
+ * `set_flow_module_code`. `patch_flow_json` operates on this compact view too,
332
+ * so structural edits never have to traverse inline script bodies.
333
+ */
334
+ function serializeWorkspaceItemForRead(item) {
335
+ if (item.type === 'variable') {
336
+ return {
337
+ type: 'variable',
338
+ path: item.path,
339
+ summary: item.summary,
340
+ isDraft: item.isDraft
341
+ };
342
+ }
343
+ if (item.type !== 'flow' || !item.value)
344
+ return item;
345
+ const flowDraft = item.value;
346
+ const session = createInlineScriptSession();
347
+ const editable = buildEditableFlowJson(flowDraft, session);
348
+ return {
349
+ type: 'flow',
350
+ path: item.path,
351
+ summary: item.summary,
352
+ value: editable,
353
+ isDraft: item.isDraft
354
+ };
355
+ }
356
+ function scheduleToItem(schedule, includeValue) {
357
+ return {
358
+ type: 'schedule',
359
+ path: schedule.path,
360
+ summary: schedule.summary ?? undefined,
361
+ value: includeValue ? schedule : undefined,
362
+ isDraft: false
363
+ };
364
+ }
365
+ function resourceToItem(resource, includeValue) {
366
+ return {
367
+ type: 'resource',
368
+ path: resource.path,
369
+ summary: resource.description,
370
+ value: includeValue ? resource : undefined,
371
+ isDraft: false
372
+ };
373
+ }
374
+ function variableToItem(variable) {
375
+ // Variables NEVER expose value (secret risk). Returns metadata only.
376
+ return {
377
+ type: 'variable',
378
+ path: variable.path,
379
+ summary: variable.description,
380
+ isDraft: false
381
+ };
382
+ }
383
+ function convertPersistedToBackendRunnable(persisted, key) {
384
+ if (!persisted)
385
+ return undefined;
386
+ const out = {
387
+ name: persisted.name ?? key,
388
+ type: 'inline'
389
+ };
390
+ if (persisted.type === 'inline' || persisted.type === 'runnableByName') {
391
+ out.type = 'inline';
392
+ if (persisted.inlineScript) {
393
+ let language = persisted.inlineScript.language;
394
+ if (language === 'nativets' || language === 'deno')
395
+ language = 'bun';
396
+ out.inlineScript = {
397
+ language: language,
398
+ content: persisted.inlineScript.content ?? ''
399
+ };
400
+ }
401
+ }
402
+ else if (persisted.type === 'path' || persisted.type === 'runnableByPath') {
403
+ if (persisted.runType === 'flow')
404
+ out.type = 'flow';
405
+ else if (persisted.runType === 'hubscript')
406
+ out.type = 'hubscript';
407
+ else
408
+ out.type = 'script';
409
+ out.path = persisted.path;
410
+ }
411
+ if (persisted.fields) {
412
+ const staticInputs = {};
413
+ for (const [k, v] of Object.entries(persisted.fields)) {
414
+ if (v && typeof v === 'object' && v.type === 'static') {
415
+ staticInputs[k] = v.value;
416
+ }
417
+ }
418
+ if (Object.keys(staticInputs).length > 0)
419
+ out.staticInputs = staticInputs;
420
+ }
421
+ return out;
422
+ }
423
+ function buildPersistedRunnable(input, existing) {
424
+ const fields = input.staticInputs
425
+ ? Object.fromEntries(Object.entries(input.staticInputs).map(([k, v]) => [
426
+ k,
427
+ { type: 'static', value: v, fieldType: 'object' }
428
+ ]))
429
+ : existing?.fields ?? {};
430
+ if (input.type === 'inline') {
431
+ if (!input.inlineScript) {
432
+ throw new Error('inlineScript is required when runnable type is "inline".');
433
+ }
434
+ return {
435
+ ...(existing ?? {}),
436
+ name: input.name,
437
+ type: 'inline',
438
+ inlineScript: {
439
+ content: input.inlineScript.content,
440
+ language: input.inlineScript.language
441
+ },
442
+ fields
443
+ };
444
+ }
445
+ if (!input.path) {
446
+ throw new Error('path is required when runnable type is "script", "flow", or "hubscript".');
447
+ }
448
+ return {
449
+ ...(existing ?? {}),
450
+ name: input.name,
451
+ type: 'path',
452
+ runType: input.type,
453
+ path: input.path,
454
+ fields,
455
+ schema: existing?.schema ?? {}
456
+ };
457
+ }
458
+ function summarizeAppValue(value) {
459
+ const frontend = Object.entries(value.files).map(([path, content]) => ({
460
+ path,
461
+ size: typeof content === 'string' ? content.length : 0
462
+ }));
463
+ const backend = Object.entries(value.runnables).map(([key, runnable]) => {
464
+ const converted = convertPersistedToBackendRunnable(runnable, key);
465
+ const staticInputKeys = converted?.staticInputs
466
+ ? Object.keys(converted.staticInputs)
467
+ : undefined;
468
+ return {
469
+ key,
470
+ name: converted?.name ?? key,
471
+ type: converted?.type ?? 'inline',
472
+ ...(converted?.path && { path: converted.path }),
473
+ ...(converted?.inlineScript && {
474
+ language: converted.inlineScript.language,
475
+ contentSize: converted.inlineScript.content.length
476
+ }),
477
+ ...(staticInputKeys && staticInputKeys.length > 0 && { staticInputKeys })
478
+ };
479
+ });
480
+ return {
481
+ frontend,
482
+ backend,
483
+ ...(value.data && { data: value.data })
484
+ };
485
+ }
486
+ function appToItem(app, includeValue) {
487
+ return {
488
+ type: 'app',
489
+ path: app.path,
490
+ summary: app.summary,
491
+ value: includeValue ? app.value : undefined,
492
+ isDraft: false
493
+ };
494
+ }
495
+ const GENERATED_APP_FILE_PATHS = new Set(['/wmill.d.ts']);
496
+ function assertNotGeneratedAppFile(filePath) {
497
+ if (GENERATED_APP_FILE_PATHS.has(filePath)) {
498
+ throw new Error(`"${filePath}" is generated automatically from backend runnables and cannot be modified directly.`);
499
+ }
500
+ }
501
+ function resolveAppFileTarget(rawPath) {
502
+ const trimmed = rawPath.trim();
503
+ const backendMatch = trimmed.match(/^backend\/([^/]+)\/main\.(ts|py)$/);
504
+ if (backendMatch) {
505
+ return {
506
+ kind: 'backend',
507
+ filePath: trimmed,
508
+ key: backendMatch[1],
509
+ extension: backendMatch[2]
510
+ };
511
+ }
512
+ return {
513
+ kind: 'frontend',
514
+ filePath: trimmed.startsWith('/') ? trimmed : `/${trimmed}`
515
+ };
516
+ }
517
+ function getInlineScriptExtension(runnable) {
518
+ return runnable?.inlineScript?.language === 'python3' ? 'py' : 'ts';
519
+ }
520
+ /**
521
+ * Resolve a backend file target to its inline script body, validating that the
522
+ * runnable exists, is inline, and matches the requested file extension. Throws
523
+ * with a clear message otherwise.
524
+ */
525
+ function getInlineRunnableContent(value, target, appPath) {
526
+ const runnable = value.runnables[target.key];
527
+ if (!runnable) {
528
+ throw new Error(`Backend runnable "${target.key}" not found in app "${appPath}".`);
529
+ }
530
+ if (runnable.type !== 'inline' && runnable.type !== 'runnableByName') {
531
+ throw new Error(`Runnable "${target.key}" is not inline. Use read_workspace_item on the referenced ${runnable.runType ?? 'item'} instead.`);
532
+ }
533
+ const expected = getInlineScriptExtension(runnable);
534
+ if (target.extension !== expected) {
535
+ throw new Error(`Runnable "${target.key}" language is ${expected}. Use backend/${target.key}/main.${expected}.`);
536
+ }
537
+ return { content: runnable.inlineScript?.content ?? '', runnable };
538
+ }
539
+ async function loadAppDraftValue(path, workspace) {
540
+ const draft = globalDraftStore.getDraft(workspace, 'app', path);
541
+ if (draft && draft.value && typeof draft.value === 'object' && 'files' in draft.value) {
542
+ return draft.value;
543
+ }
544
+ const app = await AppService.getAppByPath({ workspace, path });
545
+ const value = (app.value ?? {});
546
+ return {
547
+ summary: app.summary,
548
+ files: { ...(value.files ?? {}) },
549
+ runnables: { ...(value.runnables ?? {}) },
550
+ data: value.data,
551
+ policy: app.policy,
552
+ custom_path: app.custom_path
553
+ };
554
+ }
555
+ function saveAppDraft(workspace, path, value) {
556
+ return globalDraftStore.setDraft(workspace, {
557
+ type: 'app',
558
+ path,
559
+ summary: value.summary,
560
+ value,
561
+ isDraft: true
562
+ });
563
+ }
564
+ function triggerToItem(kind, trigger, includeValue) {
565
+ return {
566
+ type: 'trigger',
567
+ triggerKind: kind,
568
+ path: trigger.path,
569
+ summary: trigger.summary ?? undefined,
570
+ value: includeValue ? trigger : undefined,
571
+ isDraft: false
572
+ };
573
+ }
574
+ const triggerServices = {
575
+ http: {
576
+ exists: (a) => HttpTriggerService.existsHttpTrigger(a),
577
+ get: (a) => HttpTriggerService.getHttpTrigger(a),
578
+ list: (a) => HttpTriggerService.listHttpTriggers(a),
579
+ create: (a) => HttpTriggerService.createHttpTrigger(a),
580
+ update: (a) => HttpTriggerService.updateHttpTrigger(a),
581
+ delete: (a) => HttpTriggerService.deleteHttpTrigger(a)
582
+ },
583
+ websocket: {
584
+ exists: (a) => WebsocketTriggerService.existsWebsocketTrigger(a),
585
+ get: (a) => WebsocketTriggerService.getWebsocketTrigger(a),
586
+ list: (a) => WebsocketTriggerService.listWebsocketTriggers(a),
587
+ create: (a) => WebsocketTriggerService.createWebsocketTrigger(a),
588
+ update: (a) => WebsocketTriggerService.updateWebsocketTrigger(a),
589
+ delete: (a) => WebsocketTriggerService.deleteWebsocketTrigger(a)
590
+ },
591
+ kafka: {
592
+ exists: (a) => KafkaTriggerService.existsKafkaTrigger(a),
593
+ get: (a) => KafkaTriggerService.getKafkaTrigger(a),
594
+ list: (a) => KafkaTriggerService.listKafkaTriggers(a),
595
+ create: (a) => KafkaTriggerService.createKafkaTrigger(a),
596
+ update: (a) => KafkaTriggerService.updateKafkaTrigger(a),
597
+ delete: (a) => KafkaTriggerService.deleteKafkaTrigger(a)
598
+ },
599
+ nats: {
600
+ exists: (a) => NatsTriggerService.existsNatsTrigger(a),
601
+ get: (a) => NatsTriggerService.getNatsTrigger(a),
602
+ list: (a) => NatsTriggerService.listNatsTriggers(a),
603
+ create: (a) => NatsTriggerService.createNatsTrigger(a),
604
+ update: (a) => NatsTriggerService.updateNatsTrigger(a),
605
+ delete: (a) => NatsTriggerService.deleteNatsTrigger(a)
606
+ },
607
+ postgres: {
608
+ exists: (a) => PostgresTriggerService.existsPostgresTrigger(a),
609
+ get: (a) => PostgresTriggerService.getPostgresTrigger(a),
610
+ list: (a) => PostgresTriggerService.listPostgresTriggers(a),
611
+ create: (a) => PostgresTriggerService.createPostgresTrigger(a),
612
+ update: (a) => PostgresTriggerService.updatePostgresTrigger(a),
613
+ delete: (a) => PostgresTriggerService.deletePostgresTrigger(a)
614
+ },
615
+ mqtt: {
616
+ exists: (a) => MqttTriggerService.existsMqttTrigger(a),
617
+ get: (a) => MqttTriggerService.getMqttTrigger(a),
618
+ list: (a) => MqttTriggerService.listMqttTriggers(a),
619
+ create: (a) => MqttTriggerService.createMqttTrigger(a),
620
+ update: (a) => MqttTriggerService.updateMqttTrigger(a),
621
+ delete: (a) => MqttTriggerService.deleteMqttTrigger(a)
622
+ },
623
+ sqs: {
624
+ exists: (a) => SqsTriggerService.existsSqsTrigger(a),
625
+ get: (a) => SqsTriggerService.getSqsTrigger(a),
626
+ list: (a) => SqsTriggerService.listSqsTriggers(a),
627
+ create: (a) => SqsTriggerService.createSqsTrigger(a),
628
+ update: (a) => SqsTriggerService.updateSqsTrigger(a),
629
+ delete: (a) => SqsTriggerService.deleteSqsTrigger(a)
630
+ },
631
+ gcp: {
632
+ exists: (a) => GcpTriggerService.existsGcpTrigger(a),
633
+ get: (a) => GcpTriggerService.getGcpTrigger(a),
634
+ list: (a) => GcpTriggerService.listGcpTriggers(a),
635
+ create: (a) => GcpTriggerService.createGcpTrigger(a),
636
+ update: (a) => GcpTriggerService.updateGcpTrigger(a),
637
+ delete: (a) => GcpTriggerService.deleteGcpTrigger(a)
638
+ },
639
+ azure: {
640
+ exists: (a) => AzureTriggerService.existsAzureTrigger(a),
641
+ get: (a) => AzureTriggerService.getAzureTrigger(a),
642
+ list: (a) => AzureTriggerService.listAzureTriggers(a),
643
+ create: (a) => AzureTriggerService.createAzureTrigger(a),
644
+ update: (a) => AzureTriggerService.updateAzureTrigger(a),
645
+ delete: (a) => AzureTriggerService.deleteAzureTrigger(a)
646
+ }
647
+ };
648
+ async function workspaceItemExists(type, path, workspace, triggerKind) {
649
+ switch (type) {
650
+ case 'script':
651
+ return ScriptService.existsScriptByPath({ workspace, path });
652
+ case 'flow':
653
+ return FlowService.existsFlowByPath({ workspace, path });
654
+ case 'schedule':
655
+ return ScheduleService.existsSchedule({ workspace, path });
656
+ case 'trigger':
657
+ if (!triggerKind)
658
+ return false;
659
+ return triggerServices[triggerKind].exists({ workspace, path });
660
+ case 'resource':
661
+ return ResourceService.existsResource({ workspace, path });
662
+ case 'variable':
663
+ return VariableService.existsVariable({ workspace, path });
664
+ case 'app':
665
+ return AppService.existsApp({ workspace, path });
666
+ }
667
+ }
668
+ async function readWorkspaceItem(type, path, workspace, triggerKind) {
669
+ switch (type) {
670
+ case 'script':
671
+ return scriptToItem(await ScriptService.getScriptByPath({ workspace, path }), true);
672
+ case 'flow':
673
+ return flowToItem(await FlowService.getFlowByPath({ workspace, path }), true);
674
+ case 'schedule':
675
+ return scheduleToItem(await ScheduleService.getSchedule({ workspace, path }), true);
676
+ case 'trigger':
677
+ if (!triggerKind) {
678
+ throw new Error('trigger_kind is required when type is trigger.');
679
+ }
680
+ return triggerToItem(triggerKind, await triggerServices[triggerKind].get({ workspace, path }), true);
681
+ case 'resource':
682
+ return resourceToItem(await ResourceService.getResource({ workspace, path }), true);
683
+ case 'variable':
684
+ // Never expose the value, even when read directly. Pass decryptSecret=false
685
+ // to avoid materializing secret values server-side.
686
+ return variableToItem(await VariableService.getVariable({ workspace, path, decryptSecret: false }));
687
+ case 'app': {
688
+ // Returns lightweight metadata only — file/runnable contents come via read_app_file.
689
+ const app = await AppService.getAppByPath({ workspace, path });
690
+ const value = (app.value ?? {});
691
+ const metadata = summarizeAppValue({
692
+ summary: app.summary,
693
+ files: value.files ?? {},
694
+ runnables: value.runnables ?? {},
695
+ data: value.data
696
+ });
697
+ return {
698
+ type: 'app',
699
+ path: app.path,
700
+ summary: app.summary,
701
+ value: metadata,
702
+ isDraft: false
703
+ };
704
+ }
705
+ }
706
+ }
707
+ async function listWorkspaceItems(types, workspace, pathPrefix, perPage) {
708
+ const items = [];
709
+ if (types.includes('script')) {
710
+ const scripts = await ScriptService.listScripts({
711
+ workspace,
712
+ pathStart: pathPrefix,
713
+ perPage,
714
+ includeDraftOnly: true,
715
+ withoutDescription: true
716
+ });
717
+ for (const script of scripts)
718
+ items.push(scriptToItem(script, false));
719
+ }
720
+ if (types.includes('flow')) {
721
+ const flows = await FlowService.listFlows({
722
+ workspace,
723
+ pathStart: pathPrefix,
724
+ perPage,
725
+ includeDraftOnly: true,
726
+ withoutDescription: true
727
+ });
728
+ for (const flow of flows)
729
+ items.push(flowToItem(flow, false));
730
+ }
731
+ if (types.includes('schedule')) {
732
+ const schedules = await ScheduleService.listSchedules({
733
+ workspace,
734
+ pathStart: pathPrefix,
735
+ perPage
736
+ });
737
+ for (const schedule of schedules)
738
+ items.push(scheduleToItem(schedule, false));
739
+ }
740
+ if (types.includes('trigger')) {
741
+ for (const kind of TRIGGER_KINDS) {
742
+ const triggers = await triggerServices[kind].list({
743
+ workspace,
744
+ pathStart: pathPrefix,
745
+ perPage
746
+ });
747
+ for (const trigger of triggers)
748
+ items.push(triggerToItem(kind, trigger, false));
749
+ }
750
+ }
751
+ if (types.includes('resource')) {
752
+ const resources = await ResourceService.listResource({
753
+ workspace,
754
+ pathStart: pathPrefix,
755
+ perPage
756
+ });
757
+ for (const resource of resources)
758
+ items.push(resourceToItem(resource, false));
759
+ }
760
+ if (types.includes('variable')) {
761
+ const variables = await VariableService.listVariable({
762
+ workspace,
763
+ pathStart: pathPrefix,
764
+ perPage
765
+ });
766
+ for (const variable of variables)
767
+ items.push(variableToItem(variable));
768
+ }
769
+ if (types.includes('app')) {
770
+ const apps = await AppService.listApps({
771
+ workspace,
772
+ pathStart: pathPrefix,
773
+ perPage
774
+ });
775
+ for (const app of apps)
776
+ items.push(appToItem(app, false));
777
+ }
778
+ return items;
779
+ }
780
+ function getScriptInstructions(language) {
781
+ const selected = language ?? 'bun';
782
+ const note = language
783
+ ? ''
784
+ : `\n- No script language was provided. Default to \`bun\` only for new TypeScript scripts; if the user requested another language or you read an existing script, call get_instructions again with that language.`;
785
+ return `# Global draft script instructions
786
+
787
+ - Global mode writes complete draft payloads only; it does not save, deploy, run, or generate metadata.
788
+ - A script draft is a workspace item: \`{ type: 'script', path, summary?, language, value, isDraft }\` where \`value\` is the source code string.
789
+ - Use workspace paths such as \`f/folder/name\` or \`u/username/name\`. Preserve the current path/language when modifying unless the user asked to change them.
790
+ - Use \`edit_script\` for small localized changes (provide \`old_string\`/\`new_string\`); use \`write_script\` for full rewrites.${note}
791
+
792
+ # Windmill script authoring reference (${selected})
793
+
794
+ ${getScriptPrompt(selected)}`;
795
+ }
796
+ function getFlowInstructions() {
797
+ return `# Global draft flow instructions
798
+
799
+ - Global mode writes complete draft payloads only; it does not save, deploy, run, scaffold local files, or generate metadata.
800
+ - A flow draft is a workspace item: \`{ type: 'flow', path, summary?, value, isDraft }\` where \`value\` is \`{ value: <OpenFlow value object>, schema, groups }\`. The inputs schema and groups are kept alongside the OpenFlow value so deploy round-trips them.
801
+ - \`value.modules\` contains normal sequential modules. Use top-level \`value.preprocessor_module\` and \`value.failure_module\` for special modules; do not put \`preprocessor\` or \`failure\` in \`value.modules\`.
802
+ - Every module needs a stable unique \`id\` and a useful \`summary\` when the schema supports it.
803
+ - Prefer path/script/flow modules when composing existing workspace logic. Use rawscript modules only when new inline code is needed.
804
+ - When writing rawscript module code, call \`get_instructions\` with \`subject: "script"\` and the rawscript language first.
805
+
806
+ ## Compact view: how rawscript bodies surface in tool I/O
807
+
808
+ - \`read_workspace_item\` and \`patch_flow_json\` operate on a **compact view** of the flow: every rawscript module's \`value.content\` is replaced with the placeholder \`"inline_script.<moduleId>"\` so inline script bodies don't bloat tool I/O. Schema, groups, preprocessor_module and failure_module are all shown in this view.
809
+ - Inline rawscript content is **not** part of the JSON \`patch_flow_json\` sees. Edits to inline bodies happen via dedicated tools:
810
+ - \`read_flow_module_code(path, module_id)\` — returns the raw inline script content for one module.
811
+ - \`set_flow_module_code(path, module_id, code)\` — overwrites that module's inline script content; saves to the AI draft.
812
+ - Use \`patch_flow_json\` for *structural* edits: module ids, paths, input_transforms, branch arrangement, summaries, preprocessor/failure swaps, schema/groups. Use \`set_flow_module_code\` for changes inside a specific rawscript body.
813
+ - \`write_flow\` is for full overwrites / create-from-scratch. Its \`value\` argument is the **non-compact** OpenFlow value (rawscript content is the actual code, not a placeholder).
814
+
815
+ # Windmill flow authoring reference
816
+
817
+ ${getFlowPrompt()}`;
818
+ }
819
+ function getAppInstructions() {
820
+ return `# Global draft app instructions
821
+
822
+ - Global mode edits raw app drafts only; it does not save, deploy, or bundle.
823
+ - App drafts are addressed by workspace path (e.g. \`f/folder/my_app\`). The first write tool snapshots the workspace app onto the draft, and subsequent writes accumulate.
824
+ - To create a new app, use \`init_app\` with a path, optional summary, and a framework (\`react19\` / \`react18\` / \`svelte5\` / \`vue\`). Confirm framework + path + summary with the user before calling — do not silently default to \`react19\` even though it is the recommended choice. \`init_app\` errors if an app already exists at the path or a draft is already in flight; in that case, edit the existing one rather than re-initializing.
825
+ - \`init_app\` seeds a starter inline runnable named \`a\` (bun, \`main(x: string) => string\`) so the React/Svelte demo button works on first render. Replace or remove it once you start building real backend runnables.
826
+ - Frontend file paths start with \`/\` (e.g. \`/index.tsx\`, \`/App.tsx\`, \`/styles.css\`). Use \`write_app_file\` / \`patch_app_file\` / \`delete_app_file\`.
827
+ - Backend inline runnables are addressed as \`backend/<key>/main.{ts|py}\` from the file tools, but you create or update them via \`write_app_runnable\` / \`delete_app_runnable\` (which take the runnable shape directly: \`{ name, type, inlineScript?, path?, staticInputs? }\`).
828
+ - \`/wmill.d.ts\` (or \`wmill.ts\`) is generated automatically from the backend runnables — never write it directly.
829
+ - Inline runnables only support \`bun\` or \`python3\` in chat. Path runnables (\`script\`/\`flow\`/\`hubscript\`) reference an existing item.
830
+ - Apps cannot be deployed from chat. The app editor bundles JS/CSS before save; tell the user to open the app editor to deploy app drafts.
831
+ - Use \`read_workspace_item\` with \`type: 'app'\` for a metadata summary (file paths and runnable list, no contents). Use \`read_app_file\` to read an individual file.
832
+ - Note: the authoring reference below mentions the CLI on-disk layout (\`backend/<id>.<ext>\`, \`raw_app.yaml\`, \`sql_to_apply/\`). That layout is only relevant for the terminal workflow — in chat, apps are addressed via the tool surface above.
833
+
834
+ # Windmill raw app authoring reference
835
+
836
+ ${getRawAppPrompt()}`;
837
+ }
838
+ function getResourceInstructions() {
839
+ return `# Global draft resource & variable instructions
840
+
841
+ - Global mode writes complete draft payloads only; it does not save, deploy, run, scaffold local files, or generate metadata.
842
+ - A resource draft is a workspace item: \`{ type: 'resource', path, summary?, value, isDraft }\`. \`value\` is a CreateResource body: \`{ path, value, description?, resource_type, labels? }\` where the inner \`value\` is the resource type's data shape.
843
+ - A variable draft is a workspace item: \`{ type: 'variable', path, summary?, value, isDraft }\`. \`value\` is a CreateVariable body: \`{ path, value, is_secret, description, account?, is_oauth?, expires_at?, labels? }\`.
844
+ - For secret fields in a resource value, do NOT inline the raw secret. Create a Variable first with \`is_secret: true\`, then in the resource value reference it as \`"$var:path/to/variable"\`.
845
+ - Reference formats inside resource values: \`$var:g/all/name\` (global), \`$var:u/user/name\` (user), \`$var:f/folder/name\` (folder). Reference another resource with \`$res:path/to/resource\`.
846
+ - When deploying drafts that depend on each other (e.g., a resource and the variables it references), deploy the variables first.
847
+ - Use \`search_resource_types\` to discover valid \`resource_type\` names and their JSON Schemas. Match the resource value to that schema.
848
+ - For OAuth resources, the \`is_oauth: true\` flag is managed by Windmill's OAuth flow; global mode generally creates manual resources, not OAuth ones.
849
+
850
+ # Windmill resource & variable reference
851
+
852
+ ${getResourcePrompt()}`;
853
+ }
854
+ function getInstructions(subject, language) {
855
+ switch (subject) {
856
+ case 'script':
857
+ return getScriptInstructions(language);
858
+ case 'flow':
859
+ return getFlowInstructions();
860
+ case 'resource':
861
+ return getResourceInstructions();
862
+ case 'app':
863
+ return getAppInstructions();
864
+ }
865
+ }
866
+ export const globalTools = [
867
+ {
868
+ def: createToolDef(getInstructionsSchema, 'get_instructions', 'Get Windmill authoring instructions for scripts, flows, resources, or apps. For scripts, pass the target language.'),
869
+ fn: async ({ args, toolId, toolCallbacks }) => {
870
+ const parsed = getInstructionsSchema.parse(args);
871
+ const label = parsed.subject === 'script' && parsed.language
872
+ ? `${parsed.subject} (${parsed.language})`
873
+ : parsed.subject;
874
+ toolCallbacks.setToolStatus(toolId, { content: `Loaded ${label} instructions` });
875
+ return getInstructions(parsed.subject, parsed.language);
876
+ }
877
+ },
878
+ {
879
+ def: createToolDef(listWorkspaceItemsSchema, 'list_workspace_items', 'List workspace items (scripts, flows, schedules, triggers, resources, variables, apps) and AI drafts. Returns metadata only (no value). Defaults to scripts and flows.'),
880
+ fn: async ({ args, workspace, toolId, toolCallbacks }) => {
881
+ const parsed = listWorkspaceItemsSchema.parse(args);
882
+ const types = getRequestedTypes(parsed.types);
883
+ const limit = parsed.limit ?? 50;
884
+ toolCallbacks.setToolStatus(toolId, { content: 'Listing workspace items...' });
885
+ const byKey = new Map();
886
+ const workspaceItems = await listWorkspaceItems(types, workspace, parsed.path_prefix, Math.min(limit, MAX_LIST_LIMIT));
887
+ for (const item of workspaceItems) {
888
+ byKey.set(getWorkspaceItemKey(item.type, item.path, item.triggerKind), item);
889
+ }
890
+ for (const draft of globalDraftStore.listDrafts(workspace)) {
891
+ if (!types.includes(draft.type))
892
+ continue;
893
+ byKey.set(getWorkspaceItemKey(draft.type, draft.path, draft.triggerKind), {
894
+ ...draft,
895
+ value: undefined
896
+ });
897
+ }
898
+ const results = Array.from(byKey.values())
899
+ .filter((item) => itemMatches(item, parsed.query))
900
+ .slice(0, limit);
901
+ toolCallbacks.setToolStatus(toolId, {
902
+ content: `Listed ${results.length} workspace item(s)`
903
+ });
904
+ return JSON.stringify(results, null, 2);
905
+ }
906
+ },
907
+ {
908
+ def: createToolDef(readWorkspaceItemSchema, 'read_workspace_item', 'Read one workspace item or AI draft by type and path. Returns the full workspace item including value.'),
909
+ fn: async ({ args, workspace, toolId, toolCallbacks }) => {
910
+ const parsed = readWorkspaceItemSchema.parse(args);
911
+ if (parsed.type === 'trigger' && !parsed.trigger_kind) {
912
+ const message = 'trigger_kind is required when type is trigger.';
913
+ toolCallbacks.setToolStatus(toolId, { content: message, error: message });
914
+ return JSON.stringify({ success: false, error: message });
915
+ }
916
+ const draft = globalDraftStore.getDraft(workspace, parsed.type, parsed.path, parsed.trigger_kind);
917
+ if (draft) {
918
+ toolCallbacks.setToolStatus(toolId, {
919
+ content: `Read AI draft ${parsed.type} "${parsed.path}"`
920
+ });
921
+ return JSON.stringify(serializeWorkspaceItemForRead(draft), null, 2);
922
+ }
923
+ toolCallbacks.setToolStatus(toolId, {
924
+ content: `Reading ${parsed.type} "${parsed.path}"...`
925
+ });
926
+ const item = await readWorkspaceItem(parsed.type, parsed.path, workspace, parsed.trigger_kind);
927
+ toolCallbacks.setToolStatus(toolId, { content: `Read ${parsed.type} "${parsed.path}"` });
928
+ return JSON.stringify(serializeWorkspaceItemForRead(item), null, 2);
929
+ }
930
+ },
931
+ {
932
+ def: createToolDef(writeScriptSchema, 'write_script', 'Create or overwrite an AI draft script. Does not save or deploy. Read the existing script first when overwriting.'),
933
+ showDetails: true,
934
+ streamArguments: true,
935
+ showFade: true,
936
+ fn: async (ctx) => {
937
+ const parsed = writeScriptSchema.parse(ctx.args);
938
+ return writeDraft({
939
+ type: 'script',
940
+ path: parsed.path,
941
+ summary: parsed.summary,
942
+ language: parsed.language,
943
+ value: parsed.content,
944
+ isDraft: true
945
+ }, ctx);
946
+ }
947
+ },
948
+ {
949
+ def: createToolDef(writeFlowSchema, 'write_flow', 'Create or overwrite an AI draft flow. Does not save or deploy. Read the existing flow first when overwriting. value must be a JSON-encoded string of the OpenFlow value object.'),
950
+ showDetails: true,
951
+ streamArguments: true,
952
+ showFade: true,
953
+ fn: async (ctx) => {
954
+ const parsed = writeFlowSchema.parse(ctx.args);
955
+ let value;
956
+ try {
957
+ value = JSON.parse(parsed.value);
958
+ }
959
+ catch (error) {
960
+ const message = error instanceof Error ? error.message : String(error);
961
+ throw new Error(`Invalid JSON for value: ${message}`);
962
+ }
963
+ const validated = flowValueSchema.safeParse(value);
964
+ if (!validated.success) {
965
+ throw new Error(`Invalid flow value: ${validated.error.issues
966
+ .slice(0, 5)
967
+ .map((i) => `${i.path.join('.')}: ${i.message}`)
968
+ .join('; ')}`);
969
+ }
970
+ return writeDraft({
971
+ type: 'flow',
972
+ path: parsed.path,
973
+ summary: parsed.summary,
974
+ value: { value: validated.data, schema: null, groups: null },
975
+ isDraft: true
976
+ }, ctx);
977
+ }
978
+ },
979
+ {
980
+ def: createToolDef(writeScheduleSchema, 'write_schedule', 'Create or overwrite an AI draft schedule. Does not save or deploy. Provide script_path and is_flow to point to the runnable.', { strict: false }),
981
+ showDetails: true,
982
+ streamArguments: true,
983
+ showFade: true,
984
+ fn: async (ctx) => {
985
+ const parsed = writeScheduleSchema.parse(ctx.args);
986
+ return writeDraft({
987
+ type: 'schedule',
988
+ path: parsed.path,
989
+ summary: parsed.summary ?? undefined,
990
+ value: parsed,
991
+ isDraft: true
992
+ }, ctx);
993
+ }
994
+ },
995
+ {
996
+ def: createToolDef(writeTriggerSchema, 'write_trigger', 'Create or overwrite an AI draft trigger. Does not save or deploy. Provide kind plus the kind-specific config (including path, script_path, is_flow).', { strict: false }),
997
+ showDetails: true,
998
+ streamArguments: true,
999
+ showFade: true,
1000
+ fn: async (ctx) => {
1001
+ const parsed = writeTriggerSchema.parse(ctx.args);
1002
+ const config = parsed.config;
1003
+ return writeDraft({
1004
+ type: 'trigger',
1005
+ triggerKind: parsed.kind,
1006
+ path: config.path,
1007
+ summary: config.summary ?? undefined,
1008
+ value: parsed.config,
1009
+ isDraft: true
1010
+ }, ctx);
1011
+ }
1012
+ },
1013
+ {
1014
+ def: createToolDef(editScriptSchema, 'edit_script', 'Find/replace exact text in a script. Edits the existing draft if one exists, otherwise reads the workspace script and saves the result as a new draft.'),
1015
+ showDetails: true,
1016
+ streamArguments: true,
1017
+ showFade: true,
1018
+ fn: async (ctx) => {
1019
+ const parsed = editScriptSchema.parse(ctx.args);
1020
+ return editScript(parsed, ctx);
1021
+ }
1022
+ },
1023
+ {
1024
+ def: createToolDef(patchFlowJsonSchema, 'patch_flow_json', 'Find/replace exact text in a flow value (compact JSON). Edits the existing draft if one exists, otherwise reads the workspace flow and saves the result as a new draft. Use write_flow for larger structural rewrites.'),
1025
+ showDetails: true,
1026
+ streamArguments: true,
1027
+ showFade: true,
1028
+ fn: async (ctx) => {
1029
+ const parsed = patchFlowJsonSchema.parse(ctx.args);
1030
+ return patchFlowJson(parsed, ctx);
1031
+ }
1032
+ },
1033
+ {
1034
+ def: createToolDef(deployWorkspaceItemSchema, 'deploy_workspace_item', 'Persist an AI draft to the workspace by calling the real backend create/update API. This MUTATES the workspace. Requires user confirmation.', { strict: false }),
1035
+ showDetails: true,
1036
+ showFade: true,
1037
+ requiresConfirmation: true,
1038
+ confirmationMessage: 'Deploy AI draft to workspace',
1039
+ fn: async (ctx) => {
1040
+ const parsed = deployWorkspaceItemSchema.parse(ctx.args);
1041
+ return deployDraft(parsed, ctx);
1042
+ }
1043
+ },
1044
+ {
1045
+ def: createToolDef(deleteWorkspaceItemSchema, 'delete_workspace_item', 'Permanently delete a workspace item by path. This MUTATES the workspace and is irreversible. Also clears any matching AI draft. Requires user confirmation.'),
1046
+ showDetails: true,
1047
+ showFade: true,
1048
+ requiresConfirmation: true,
1049
+ confirmationMessage: 'Delete workspace item',
1050
+ fn: async (ctx) => {
1051
+ const parsed = deleteWorkspaceItemSchema.parse(ctx.args);
1052
+ return deleteWorkspaceItem(parsed, ctx);
1053
+ }
1054
+ },
1055
+ {
1056
+ def: createToolDef(writeResourceSchema, 'write_resource', 'Create or overwrite an AI draft resource. Does not save or deploy. Reference secret values via $var:path/to/variable; create the variable separately with write_variable.', { strict: false }),
1057
+ showDetails: true,
1058
+ streamArguments: true,
1059
+ showFade: true,
1060
+ fn: async (ctx) => {
1061
+ const parsed = writeResourceSchema.parse(ctx.args);
1062
+ return writeDraft({
1063
+ type: 'resource',
1064
+ path: parsed.path,
1065
+ summary: parsed.description,
1066
+ value: parsed,
1067
+ isDraft: true
1068
+ }, ctx);
1069
+ }
1070
+ },
1071
+ {
1072
+ def: createToolDef(writeVariableSchema, 'write_variable', 'Create or overwrite an AI draft variable. Does not save or deploy. Use is_secret: true for secret values. After deploy, reference from a resource as $var:path/to/variable.', { strict: false }),
1073
+ showDetails: true,
1074
+ streamArguments: true,
1075
+ showFade: true,
1076
+ fn: async (ctx) => {
1077
+ const parsed = writeVariableSchema.parse(ctx.args);
1078
+ return writeDraft({
1079
+ type: 'variable',
1080
+ path: parsed.path,
1081
+ summary: parsed.description,
1082
+ value: parsed,
1083
+ isDraft: true
1084
+ }, ctx);
1085
+ }
1086
+ },
1087
+ {
1088
+ def: createToolDef(searchResourceTypesSchema, 'search_resource_types', 'Search for resource types in the workspace by substring. Returns names, descriptions, and JSON Schemas — use this before write_resource to know what shape value should have.'),
1089
+ fn: async ({ args, workspace, toolId, toolCallbacks }) => {
1090
+ const parsed = searchResourceTypesSchema.parse(args);
1091
+ toolCallbacks.setToolStatus(toolId, {
1092
+ content: `Searching resource types for "${parsed.query}"...`
1093
+ });
1094
+ const results = await ResourceService.queryResourceTypes({
1095
+ workspace,
1096
+ text: parsed.query,
1097
+ limit: parsed.limit ?? 5
1098
+ });
1099
+ toolCallbacks.setToolStatus(toolId, {
1100
+ content: `Found ${results.length} resource type(s) for "${parsed.query}"`
1101
+ });
1102
+ return JSON.stringify(results.map((rt) => ({
1103
+ name: rt.name,
1104
+ schema: rt.schema
1105
+ })), null, 2);
1106
+ }
1107
+ },
1108
+ {
1109
+ def: createToolDef(readFlowModuleCodeSchema, 'read_flow_module_code', 'Read the inline rawscript content of one flow module by id. Reads from the AI draft when one exists, otherwise from the workspace flow. Use this instead of patch_flow_json when you only need to inspect an inline script body.'),
1110
+ fn: async (ctx) => {
1111
+ const parsed = readFlowModuleCodeSchema.parse(ctx.args);
1112
+ return readFlowModuleCode(parsed, ctx);
1113
+ }
1114
+ },
1115
+ {
1116
+ def: createToolDef(setFlowModuleCodeSchema, 'set_flow_module_code', 'Overwrite the inline rawscript content of one flow module by id. Saves to the AI draft only — does not deploy. Use this for inline script body changes; structural changes (module ids, paths, input_transforms, branches) go through patch_flow_json.'),
1117
+ showDetails: true,
1118
+ streamArguments: true,
1119
+ showFade: true,
1120
+ fn: async (ctx) => {
1121
+ const parsed = setFlowModuleCodeSchema.parse(ctx.args);
1122
+ return setFlowModuleCode(parsed, ctx);
1123
+ }
1124
+ },
1125
+ {
1126
+ def: createToolDef(initAppSchema, 'init_app', 'Initialize a new raw app draft from a framework template. Errors if an app already exists at the path or a draft is already in flight. Confirm framework, path, and summary with the user before calling — do not silently default to react19.', { strict: false }),
1127
+ showDetails: true,
1128
+ showFade: true,
1129
+ fn: async (ctx) => {
1130
+ const parsed = initAppSchema.parse(ctx.args);
1131
+ return initApp(parsed, ctx);
1132
+ }
1133
+ },
1134
+ {
1135
+ def: createToolDef(readAppFileSchema, 'read_app_file', 'Read one frontend file or inline backend runnable script from a raw app. Use file_path "/foo.tsx" for frontend files and "backend/<key>/main.{ts|py}" for inline runnables. Prefers the AI draft when one exists.'),
1136
+ fn: async (ctx) => {
1137
+ const parsed = readAppFileSchema.parse(ctx.args);
1138
+ return readAppFile(parsed, ctx);
1139
+ }
1140
+ },
1141
+ {
1142
+ def: createToolDef(writeAppFileSchema, 'write_app_file', 'Create or overwrite a frontend file in an app draft. Saves to the AI draft only — does not deploy. First write snapshots the workspace app onto the draft.'),
1143
+ showDetails: true,
1144
+ streamArguments: true,
1145
+ showFade: true,
1146
+ fn: async (ctx) => {
1147
+ const parsed = writeAppFileSchema.parse(ctx.args);
1148
+ return writeAppFile(parsed, ctx);
1149
+ }
1150
+ },
1151
+ {
1152
+ def: createToolDef(deleteAppFileSchema, 'delete_app_file', 'Remove a frontend file from an app draft. Saves to the AI draft only — does not deploy.'),
1153
+ fn: async (ctx) => {
1154
+ const parsed = deleteAppFileSchema.parse(ctx.args);
1155
+ return deleteAppFile(parsed, ctx);
1156
+ }
1157
+ },
1158
+ {
1159
+ def: createToolDef(patchAppFileSchema, 'patch_app_file', 'Find/replace exact text in a frontend file or inline backend runnable script. Saves the result to the AI draft.'),
1160
+ showDetails: true,
1161
+ streamArguments: true,
1162
+ showFade: true,
1163
+ fn: async (ctx) => {
1164
+ const parsed = patchAppFileSchema.parse(ctx.args);
1165
+ return patchAppFile(parsed, ctx);
1166
+ }
1167
+ },
1168
+ {
1169
+ def: createToolDef(writeAppRunnableSchema, 'write_app_runnable', 'Create or overwrite a backend runnable in an app draft. Saves to the AI draft only — does not deploy. Re-derives the app policy after the change.', { strict: false }),
1170
+ showDetails: true,
1171
+ streamArguments: true,
1172
+ showFade: true,
1173
+ fn: async (ctx) => {
1174
+ const parsed = writeAppRunnableSchema.parse(ctx.args);
1175
+ return writeAppRunnable(parsed, ctx);
1176
+ }
1177
+ },
1178
+ {
1179
+ def: createToolDef(deleteAppRunnableSchema, 'delete_app_runnable', 'Remove a backend runnable from an app draft. Saves to the AI draft only — does not deploy. Re-derives the app policy after the change.'),
1180
+ fn: async (ctx) => {
1181
+ const parsed = deleteAppRunnableSchema.parse(ctx.args);
1182
+ return deleteAppRunnable(parsed, ctx);
1183
+ }
1184
+ }
1185
+ ];
1186
+ async function loadScriptForEdit(path, workspace) {
1187
+ const draft = globalDraftStore.getDraft(workspace, 'script', path);
1188
+ if (draft) {
1189
+ if (typeof draft.value !== 'string' || !draft.language) {
1190
+ throw new Error(`Draft script "${path}" is missing content or language.`);
1191
+ }
1192
+ return { content: draft.value, language: draft.language, summary: draft.summary };
1193
+ }
1194
+ const script = await ScriptService.getScriptByPath({ workspace, path });
1195
+ return { content: script.content, language: script.language, summary: script.summary };
1196
+ }
1197
+ async function editScript(args, ctx) {
1198
+ const { path, old_string: oldString, new_string: newString, replace_all: replaceAll } = args;
1199
+ ctx.toolCallbacks.setToolStatus(ctx.toolId, { content: `Editing script "${path}"...` });
1200
+ const base = await loadScriptForEdit(path, ctx.workspace);
1201
+ const updated = findAndReplace(base.content, oldString, newString, replaceAll, 'script source');
1202
+ return writeDraft({
1203
+ type: 'script',
1204
+ path,
1205
+ summary: base.summary,
1206
+ language: base.language,
1207
+ value: updated,
1208
+ isDraft: true
1209
+ }, ctx);
1210
+ }
1211
+ async function loadFlowDraftValue(path, workspace) {
1212
+ const draft = globalDraftStore.getDraft(workspace, 'flow', path);
1213
+ if (draft) {
1214
+ if (draft.value === undefined || typeof draft.value === 'string') {
1215
+ throw new Error(`Draft flow "${path}" has no value.`);
1216
+ }
1217
+ return { flow: draft.value, summary: draft.summary };
1218
+ }
1219
+ const flow = await FlowService.getFlowByPath({ workspace, path });
1220
+ return {
1221
+ flow: { value: flow.value, schema: flow.schema, groups: flow.value.groups ?? null },
1222
+ summary: flow.summary
1223
+ };
1224
+ }
1225
+ async function patchFlowJson(args, ctx) {
1226
+ const { path, old_string: oldString, new_string: newString, replace_all: replaceAll } = args;
1227
+ ctx.toolCallbacks.setToolStatus(ctx.toolId, { content: `Patching flow "${path}"...` });
1228
+ // Operate on the compact (placeholders-for-rawscript) view so inline script
1229
+ // bodies don't appear in the JSON the model sees or patches. Real script
1230
+ // content is preserved through the patch via the InlineScriptSession; the
1231
+ // model uses set_flow_module_code to change inline script bodies.
1232
+ const base = await loadFlowDraftValue(path, ctx.workspace);
1233
+ const session = createInlineScriptSession();
1234
+ const editable = buildEditableFlowJson(base.flow, session);
1235
+ const currentJson = JSON.stringify(editable);
1236
+ const updatedJson = findAndReplace(currentJson, oldString, newString, replaceAll, 'compact flow JSON');
1237
+ let parsedValue;
1238
+ try {
1239
+ parsedValue = JSON.parse(updatedJson);
1240
+ }
1241
+ catch (error) {
1242
+ const message = error instanceof Error ? error.message : String(error);
1243
+ throw new Error(`Invalid JSON after replacement: ${message}`);
1244
+ }
1245
+ const patchedEditable = validateEditableFlowJson(parsedValue);
1246
+ const newFlowValue = applyEditableFlowJsonToFlow(base.flow.value, patchedEditable, session);
1247
+ return writeDraft({
1248
+ type: 'flow',
1249
+ path,
1250
+ summary: base.summary,
1251
+ value: {
1252
+ ...base.flow,
1253
+ value: newFlowValue,
1254
+ schema: patchedEditable.schema,
1255
+ groups: patchedEditable.groups
1256
+ },
1257
+ isDraft: true
1258
+ }, ctx);
1259
+ }
1260
+ async function readFlowModuleCode(args, ctx) {
1261
+ const { workspace, toolId, toolCallbacks } = ctx;
1262
+ toolCallbacks.setToolStatus(toolId, {
1263
+ content: `Reading inline script for module "${args.module_id}" from flow "${args.path}"...`
1264
+ });
1265
+ const base = await loadFlowDraftValue(args.path, workspace);
1266
+ const session = createInlineScriptSession();
1267
+ buildEditableFlowJson(base.flow, session);
1268
+ const content = session.get(args.module_id);
1269
+ if (content === undefined) {
1270
+ throw new Error(`Module "${args.module_id}" is not an inline rawscript in flow "${args.path}". (Path runnables, branches, and loops have no inline script content; use patch_flow_json to inspect them.)`);
1271
+ }
1272
+ toolCallbacks.setToolStatus(toolId, {
1273
+ content: `Read inline script for "${args.module_id}"`
1274
+ });
1275
+ return content;
1276
+ }
1277
+ async function setFlowModuleCode(args, ctx) {
1278
+ const { workspace, toolId, toolCallbacks } = ctx;
1279
+ toolCallbacks.setToolStatus(toolId, {
1280
+ content: `Updating inline script for module "${args.module_id}" in flow "${args.path}"...`
1281
+ });
1282
+ const base = await loadFlowDraftValue(args.path, workspace);
1283
+ const session = createInlineScriptSession();
1284
+ const editable = buildEditableFlowJson(base.flow, session);
1285
+ if (!session.has(args.module_id)) {
1286
+ throw new Error(`Module "${args.module_id}" is not an inline rawscript in flow "${args.path}". Use patch_flow_json or write_flow for structural changes.`);
1287
+ }
1288
+ session.set(args.module_id, args.code);
1289
+ const newFlowValue = applyEditableFlowJsonToFlow(base.flow.value, editable, session);
1290
+ return writeDraft({
1291
+ type: 'flow',
1292
+ path: args.path,
1293
+ summary: base.summary,
1294
+ value: { ...base.flow, value: newFlowValue },
1295
+ isDraft: true
1296
+ }, ctx);
1297
+ }
1298
+ async function initApp(args, ctx) {
1299
+ const { workspace, toolId, toolCallbacks } = ctx;
1300
+ const { path, summary, framework, data } = args;
1301
+ if (globalDraftStore.getDraft(workspace, 'app', path)) {
1302
+ throw new Error(`An AI draft for app "${path}" already exists. Use write_app_file / write_app_runnable to modify it, or delete the existing draft first.`);
1303
+ }
1304
+ if (await AppService.existsApp({ workspace, path })) {
1305
+ throw new Error(`An app already exists at "${path}". Use read_workspace_item + write_app_file / write_app_runnable to modify it.`);
1306
+ }
1307
+ toolCallbacks.setToolStatus(toolId, {
1308
+ content: `Initializing app draft "${path}" with ${framework} template...`
1309
+ });
1310
+ const template = FRAMEWORK_TEMPLATES[framework];
1311
+ const value = {
1312
+ summary,
1313
+ files: { ...template },
1314
+ runnables: { [STARTER_RUNNABLE_KEY]: { ...STARTER_RUNNABLE } },
1315
+ ...(data && {
1316
+ data: {
1317
+ tables: data.tables ?? [],
1318
+ datatable: data.datatable,
1319
+ schema: data.schema
1320
+ }
1321
+ })
1322
+ };
1323
+ await recomputeAppPolicy(value);
1324
+ const stored = saveAppDraft(workspace, path, value);
1325
+ toolCallbacks.setToolStatus(toolId, {
1326
+ content: `Initialized app draft "${path}" (${framework})`,
1327
+ result: 'Draft initialized'
1328
+ });
1329
+ return JSON.stringify({
1330
+ success: true,
1331
+ message: `Initialized AI draft app "${path}" from the ${framework} template with a starter runnable "${STARTER_RUNNABLE_KEY}". Use write_app_file / write_app_runnable to evolve the draft.`,
1332
+ item: stored
1333
+ }, null, 2);
1334
+ }
1335
+ async function readAppFile(args, ctx) {
1336
+ const { workspace, toolId, toolCallbacks } = ctx;
1337
+ const target = resolveAppFileTarget(args.file_path);
1338
+ toolCallbacks.setToolStatus(toolId, {
1339
+ content: `Reading ${target.filePath} from app "${args.path}"...`
1340
+ });
1341
+ const value = await loadAppDraftValue(args.path, workspace);
1342
+ if (target.kind === 'frontend') {
1343
+ const content = value.files[target.filePath];
1344
+ if (content === undefined) {
1345
+ throw new Error(`Frontend file "${target.filePath}" not found in app "${args.path}".`);
1346
+ }
1347
+ toolCallbacks.setToolStatus(toolId, { content: `Read ${target.filePath}` });
1348
+ return content;
1349
+ }
1350
+ const { content } = getInlineRunnableContent(value, target, args.path);
1351
+ toolCallbacks.setToolStatus(toolId, { content: `Read ${target.filePath}` });
1352
+ return content;
1353
+ }
1354
+ async function writeAppFile(args, ctx) {
1355
+ const { workspace, toolId, toolCallbacks } = ctx;
1356
+ const target = resolveAppFileTarget(args.file_path);
1357
+ if (target.kind !== 'frontend') {
1358
+ throw new Error(`write_app_file only writes frontend files. Use write_app_runnable to set inline backend script content.`);
1359
+ }
1360
+ assertNotGeneratedAppFile(target.filePath);
1361
+ toolCallbacks.setToolStatus(toolId, {
1362
+ content: `Writing ${target.filePath} to app "${args.path}"...`
1363
+ });
1364
+ const value = await loadAppDraftValue(args.path, workspace);
1365
+ value.files = { ...value.files, [target.filePath]: args.content };
1366
+ const stored = saveAppDraft(workspace, args.path, value);
1367
+ toolCallbacks.setToolStatus(toolId, {
1368
+ content: `Updated ${target.filePath} in app "${args.path}"`,
1369
+ result: 'Draft updated'
1370
+ });
1371
+ return JSON.stringify({
1372
+ success: true,
1373
+ message: `Updated AI draft app "${args.path}" with frontend file "${target.filePath}".`,
1374
+ item: stored
1375
+ }, null, 2);
1376
+ }
1377
+ async function deleteAppFile(args, ctx) {
1378
+ const { workspace, toolId, toolCallbacks } = ctx;
1379
+ const target = resolveAppFileTarget(args.file_path);
1380
+ if (target.kind !== 'frontend') {
1381
+ throw new Error(`delete_app_file only deletes frontend files. Use delete_app_runnable for backend runnables.`);
1382
+ }
1383
+ assertNotGeneratedAppFile(target.filePath);
1384
+ toolCallbacks.setToolStatus(toolId, {
1385
+ content: `Deleting ${target.filePath} from app "${args.path}"...`
1386
+ });
1387
+ const value = await loadAppDraftValue(args.path, workspace);
1388
+ if (!(target.filePath in value.files)) {
1389
+ throw new Error(`Frontend file "${target.filePath}" not found in app "${args.path}".`);
1390
+ }
1391
+ const { [target.filePath]: _removed, ...remaining } = value.files;
1392
+ value.files = remaining;
1393
+ const stored = saveAppDraft(workspace, args.path, value);
1394
+ toolCallbacks.setToolStatus(toolId, {
1395
+ content: `Removed ${target.filePath} from app "${args.path}"`,
1396
+ result: 'Draft updated'
1397
+ });
1398
+ return JSON.stringify({
1399
+ success: true,
1400
+ message: `Removed "${target.filePath}" from AI draft app "${args.path}".`,
1401
+ item: stored
1402
+ }, null, 2);
1403
+ }
1404
+ async function patchAppFile(args, ctx) {
1405
+ const { workspace, toolId, toolCallbacks } = ctx;
1406
+ const { path, file_path: filePath, old_string: oldString, new_string: newString, replace_all: replaceAll } = args;
1407
+ const target = resolveAppFileTarget(filePath);
1408
+ if (target.kind === 'frontend') {
1409
+ assertNotGeneratedAppFile(target.filePath);
1410
+ }
1411
+ toolCallbacks.setToolStatus(toolId, { content: `Patching ${target.filePath} in app "${path}"...` });
1412
+ const value = await loadAppDraftValue(path, workspace);
1413
+ let currentContent;
1414
+ let runnable;
1415
+ if (target.kind === 'frontend') {
1416
+ const existing = value.files[target.filePath];
1417
+ if (existing === undefined) {
1418
+ throw new Error(`Frontend file "${target.filePath}" not found in app "${path}".`);
1419
+ }
1420
+ currentContent = existing;
1421
+ }
1422
+ else {
1423
+ const resolved = getInlineRunnableContent(value, target, path);
1424
+ currentContent = resolved.content;
1425
+ runnable = resolved.runnable;
1426
+ }
1427
+ const updated = findAndReplace(currentContent, oldString, newString, replaceAll, `${target.filePath} content`);
1428
+ if (target.kind === 'frontend') {
1429
+ value.files = { ...value.files, [target.filePath]: updated };
1430
+ }
1431
+ else {
1432
+ value.runnables = {
1433
+ ...value.runnables,
1434
+ [target.key]: {
1435
+ ...runnable,
1436
+ inlineScript: {
1437
+ language: runnable.inlineScript?.language ?? (target.extension === 'py' ? 'python3' : 'bun'),
1438
+ content: updated
1439
+ }
1440
+ }
1441
+ };
1442
+ }
1443
+ const stored = saveAppDraft(workspace, path, value);
1444
+ toolCallbacks.setToolStatus(toolId, {
1445
+ content: `Patched ${target.filePath} in app "${path}"`,
1446
+ result: 'Draft updated'
1447
+ });
1448
+ return JSON.stringify({
1449
+ success: true,
1450
+ message: `Patched "${target.filePath}" in AI draft app "${path}".`,
1451
+ item: stored
1452
+ }, null, 2);
1453
+ }
1454
+ async function recomputeAppPolicy(value) {
1455
+ value.policy = (await updateRawAppPolicy(value.runnables, value.policy));
1456
+ }
1457
+ async function writeAppRunnable(args, ctx) {
1458
+ const { workspace, toolId, toolCallbacks } = ctx;
1459
+ const { path, key, runnable: input } = args;
1460
+ toolCallbacks.setToolStatus(toolId, {
1461
+ content: `Writing runnable "${key}" to app "${path}"...`
1462
+ });
1463
+ const value = await loadAppDraftValue(path, workspace);
1464
+ const existing = value.runnables[key];
1465
+ const persisted = buildPersistedRunnable(input, existing);
1466
+ value.runnables = { ...value.runnables, [key]: persisted };
1467
+ await recomputeAppPolicy(value);
1468
+ const stored = saveAppDraft(workspace, path, value);
1469
+ toolCallbacks.setToolStatus(toolId, {
1470
+ content: `Updated runnable "${key}" in app "${path}"`,
1471
+ result: 'Draft updated'
1472
+ });
1473
+ return JSON.stringify({
1474
+ success: true,
1475
+ message: `Updated AI draft app "${path}" with runnable "${key}".`,
1476
+ item: stored
1477
+ }, null, 2);
1478
+ }
1479
+ async function deleteAppRunnable(args, ctx) {
1480
+ const { workspace, toolId, toolCallbacks } = ctx;
1481
+ const { path, key } = args;
1482
+ toolCallbacks.setToolStatus(toolId, {
1483
+ content: `Removing runnable "${key}" from app "${path}"...`
1484
+ });
1485
+ const value = await loadAppDraftValue(path, workspace);
1486
+ if (!(key in value.runnables)) {
1487
+ throw new Error(`Backend runnable "${key}" not found in app "${path}".`);
1488
+ }
1489
+ const { [key]: _removed, ...remaining } = value.runnables;
1490
+ value.runnables = remaining;
1491
+ await recomputeAppPolicy(value);
1492
+ const stored = saveAppDraft(workspace, path, value);
1493
+ toolCallbacks.setToolStatus(toolId, {
1494
+ content: `Removed runnable "${key}" from app "${path}"`,
1495
+ result: 'Draft updated'
1496
+ });
1497
+ return JSON.stringify({
1498
+ success: true,
1499
+ message: `Removed runnable "${key}" from AI draft app "${path}".`,
1500
+ item: stored
1501
+ }, null, 2);
1502
+ }
1503
+ const triggerLabels = {
1504
+ http: 'HTTP trigger',
1505
+ websocket: 'WebSocket trigger',
1506
+ kafka: 'Kafka trigger',
1507
+ nats: 'NATS trigger',
1508
+ postgres: 'Postgres trigger',
1509
+ mqtt: 'MQTT trigger',
1510
+ sqs: 'SQS trigger',
1511
+ gcp: 'GCP Pub/Sub trigger',
1512
+ azure: 'Azure Event Grid trigger'
1513
+ };
1514
+ function createOpenScheduleAction(path, targetKind) {
1515
+ return {
1516
+ id: `open-deployed-schedule:${path}`,
1517
+ type: 'open_created_resource',
1518
+ label: 'Open schedule',
1519
+ resource: 'schedule',
1520
+ path,
1521
+ targetKind
1522
+ };
1523
+ }
1524
+ function createOpenTriggerAction(kind, path, targetKind) {
1525
+ return {
1526
+ id: `open-deployed-trigger:${kind}:${path}`,
1527
+ type: 'open_created_resource',
1528
+ label: `Open ${triggerLabels[kind]}`,
1529
+ resource: 'trigger',
1530
+ triggerKind: kind,
1531
+ path,
1532
+ targetKind
1533
+ };
1534
+ }
1535
+ function createOpenResourceAction(path) {
1536
+ return {
1537
+ id: `open-deployed-resource:${path}`,
1538
+ type: 'open_created_resource',
1539
+ label: 'Open resource',
1540
+ resource: 'resource',
1541
+ path
1542
+ };
1543
+ }
1544
+ function createOpenVariableAction(path) {
1545
+ return {
1546
+ id: `open-deployed-variable:${path}`,
1547
+ type: 'open_created_resource',
1548
+ label: 'Open variable',
1549
+ resource: 'variable',
1550
+ path
1551
+ };
1552
+ }
1553
+ async function deployDraft(args, ctx) {
1554
+ const { workspace, toolId, toolCallbacks } = ctx;
1555
+ const { type, path, trigger_kind: triggerKind, deployment_message: deploymentMessage } = args;
1556
+ if (type === 'app') {
1557
+ throw new Error('Apps cannot be deployed from chat. Open the app editor to deploy (the editor bundles JS/CSS before save).');
1558
+ }
1559
+ if (type === 'trigger' && !triggerKind) {
1560
+ throw new Error('trigger_kind is required when deploying a trigger.');
1561
+ }
1562
+ const draft = globalDraftStore.getDraft(workspace, type, path, triggerKind);
1563
+ if (!draft) {
1564
+ throw new Error(`No AI draft found for ${type} "${path}".`);
1565
+ }
1566
+ if (draft.value === undefined) {
1567
+ throw new Error(`Draft ${type} "${path}" has no value to deploy.`);
1568
+ }
1569
+ toolCallbacks.setToolStatus(toolId, {
1570
+ content: `Deploying ${type} "${path}"...`
1571
+ });
1572
+ let actions;
1573
+ switch (type) {
1574
+ case 'script': {
1575
+ const existing = (await ScriptService.existsScriptByPath({ workspace, path }))
1576
+ ? await ScriptService.getScriptByPath({ workspace, path })
1577
+ : undefined;
1578
+ await ScriptService.createScript({
1579
+ workspace,
1580
+ requestBody: buildScriptDeployRequestBody(path, draft, existing, deploymentMessage)
1581
+ });
1582
+ break;
1583
+ }
1584
+ case 'flow': {
1585
+ const flowDraft = draft.value;
1586
+ const existing = (await FlowService.existsFlowByPath({ workspace, path }))
1587
+ ? await FlowService.getFlowByPath({ workspace, path })
1588
+ : undefined;
1589
+ const requestBody = buildFlowDeployRequestBody(path, draft.summary, flowDraft, existing, deploymentMessage);
1590
+ if (existing) {
1591
+ await FlowService.updateFlow({ workspace, path, requestBody });
1592
+ }
1593
+ else {
1594
+ await FlowService.createFlow({ workspace, requestBody });
1595
+ }
1596
+ break;
1597
+ }
1598
+ case 'schedule': {
1599
+ const requestBody = draft.value;
1600
+ if (await ScheduleService.existsSchedule({ workspace, path })) {
1601
+ await ScheduleService.updateSchedule({ workspace, path, requestBody });
1602
+ }
1603
+ else {
1604
+ await ScheduleService.createSchedule({ workspace, requestBody });
1605
+ }
1606
+ actions = [createOpenScheduleAction(path, requestBody.is_flow ? 'flow' : 'script')];
1607
+ break;
1608
+ }
1609
+ case 'trigger': {
1610
+ const service = triggerServices[triggerKind];
1611
+ const requestBody = draft.value;
1612
+ if (await service.exists({ workspace, path })) {
1613
+ await service.update({ workspace, path, requestBody });
1614
+ }
1615
+ else {
1616
+ await service.create({ workspace, requestBody });
1617
+ }
1618
+ actions = [
1619
+ createOpenTriggerAction(triggerKind, path, requestBody.is_flow ? 'flow' : 'script')
1620
+ ];
1621
+ break;
1622
+ }
1623
+ case 'resource': {
1624
+ const requestBody = draft.value;
1625
+ if (await ResourceService.existsResource({ workspace, path })) {
1626
+ await ResourceService.updateResource({ workspace, path, requestBody });
1627
+ }
1628
+ else {
1629
+ await ResourceService.createResource({ workspace, requestBody });
1630
+ }
1631
+ actions = [createOpenResourceAction(path)];
1632
+ break;
1633
+ }
1634
+ case 'variable': {
1635
+ const requestBody = draft.value;
1636
+ if (await VariableService.existsVariable({ workspace, path })) {
1637
+ await VariableService.updateVariable({ workspace, path, requestBody });
1638
+ }
1639
+ else {
1640
+ await VariableService.createVariable({ workspace, requestBody });
1641
+ }
1642
+ actions = [createOpenVariableAction(path)];
1643
+ break;
1644
+ }
1645
+ }
1646
+ globalDraftStore.deleteDraft(workspace, type, path, triggerKind);
1647
+ toolCallbacks.setToolStatus(toolId, {
1648
+ content: `Deployed ${type} "${path}"`,
1649
+ result: 'Deployed',
1650
+ actions
1651
+ });
1652
+ return JSON.stringify({
1653
+ success: true,
1654
+ message: `Deployed AI draft ${type} "${path}" to the workspace. Draft removed from the AI draft store.`,
1655
+ type,
1656
+ path,
1657
+ triggerKind
1658
+ }, null, 2);
1659
+ }
1660
+ async function deleteWorkspaceItem(args, ctx) {
1661
+ const { workspace, toolId, toolCallbacks } = ctx;
1662
+ const { type, path, trigger_kind: triggerKind } = args;
1663
+ if (type === 'trigger' && !triggerKind) {
1664
+ throw new Error('trigger_kind is required when deleting a trigger.');
1665
+ }
1666
+ toolCallbacks.setToolStatus(toolId, {
1667
+ content: `Deleting ${type} "${path}"...`
1668
+ });
1669
+ switch (type) {
1670
+ case 'script':
1671
+ await ScriptService.deleteScriptByPath({ workspace, path });
1672
+ break;
1673
+ case 'flow':
1674
+ await FlowService.deleteFlowByPath({ workspace, path });
1675
+ break;
1676
+ case 'schedule':
1677
+ await ScheduleService.deleteSchedule({ workspace, path });
1678
+ break;
1679
+ case 'trigger':
1680
+ await triggerServices[triggerKind].delete({ workspace, path });
1681
+ break;
1682
+ case 'resource':
1683
+ await ResourceService.deleteResource({ workspace, path });
1684
+ break;
1685
+ case 'variable':
1686
+ await VariableService.deleteVariable({ workspace, path });
1687
+ break;
1688
+ case 'app':
1689
+ await AppService.deleteApp({ workspace, path });
1690
+ break;
1691
+ }
1692
+ globalDraftStore.deleteDraft(workspace, type, path, triggerKind);
1693
+ toolCallbacks.setToolStatus(toolId, {
1694
+ content: `Deleted ${type} "${path}"`,
1695
+ result: 'Deleted'
1696
+ });
1697
+ return JSON.stringify({
1698
+ success: true,
1699
+ message: `Deleted ${type} "${path}" from the workspace. Any matching AI draft was also cleared.`,
1700
+ type,
1701
+ path,
1702
+ triggerKind
1703
+ }, null, 2);
1704
+ }
1705
+ async function writeDraft(item, ctx) {
1706
+ const { workspace, toolId, toolCallbacks } = ctx;
1707
+ toolCallbacks.setToolStatus(toolId, {
1708
+ content: `Writing draft ${item.type} "${item.path}"...`
1709
+ });
1710
+ const exists = globalDraftStore.getDraft(workspace, item.type, item.path, item.triggerKind) !== undefined ||
1711
+ (await workspaceItemExists(item.type, item.path, workspace, item.triggerKind));
1712
+ const stored = globalDraftStore.setDraft(workspace, item);
1713
+ const verb = exists ? 'Updated' : 'Created';
1714
+ toolCallbacks.setToolStatus(toolId, {
1715
+ content: `${verb} AI draft ${item.type} "${item.path}"`,
1716
+ result: `Draft ${verb.toLowerCase()}`
1717
+ });
1718
+ return JSON.stringify({
1719
+ success: true,
1720
+ message: `${verb} AI draft ${item.type} "${item.path}". The workspace was not saved or deployed.`,
1721
+ item: stored.type === 'variable' ? serializeWorkspaceItemForRead(stored) : stored
1722
+ }, null, 2);
1723
+ }
1724
+ export function prepareGlobalSystemMessage(customPrompt) {
1725
+ let content = GLOBAL_SYSTEM_PROMPT;
1726
+ if (customPrompt?.trim()) {
1727
+ content = `${content}\n\nUSER GIVEN INSTRUCTIONS:\n${customPrompt.trim()}`;
1728
+ }
1729
+ return {
1730
+ role: 'system',
1731
+ content
1732
+ };
1733
+ }
1734
+ export function prepareGlobalUserMessage(instructions) {
1735
+ return {
1736
+ role: 'user',
1737
+ content: instructions
1738
+ };
1739
+ }