windmill-components 1.695.1 → 1.698.0

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 (157) hide show
  1. package/dist/sharedUtils/assets/tokens/colorTokensConfig.d.ts +2 -0
  2. package/dist/sharedUtils/base.d.ts +1 -0
  3. package/dist/sharedUtils/cloud.d.ts +1 -0
  4. package/dist/sharedUtils/common.d.ts +111 -0
  5. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/count.d.ts +5 -0
  6. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/delete.d.ts +5 -0
  7. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/insert.d.ts +5 -0
  8. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/select.d.ts +13 -0
  9. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/update.d.ts +11 -0
  10. package/dist/sharedUtils/components/apps/components/display/dbtable/utils.d.ts +95 -0
  11. package/dist/sharedUtils/components/apps/editor/appPolicy.d.ts +6 -0
  12. package/dist/sharedUtils/components/apps/editor/appUtilsCore.d.ts +7 -0
  13. package/dist/sharedUtils/components/apps/editor/appUtilsS3.d.ts +33 -0
  14. package/dist/sharedUtils/components/apps/editor/commonAppUtils.d.ts +10 -0
  15. package/dist/sharedUtils/components/apps/editor/component/components.d.ts +5371 -0
  16. package/dist/sharedUtils/components/apps/editor/component/default-codes.d.ts +3 -0
  17. package/dist/sharedUtils/components/apps/editor/component/index.d.ts +3 -0
  18. package/dist/sharedUtils/components/apps/editor/component/sets.d.ts +7 -0
  19. package/dist/sharedUtils/components/apps/editor/componentsPanel/componentDefaultProps.d.ts +3 -0
  20. package/dist/sharedUtils/components/apps/gridUtils.d.ts +14 -0
  21. package/dist/sharedUtils/components/apps/inputType.d.ts +178 -0
  22. package/dist/sharedUtils/components/apps/rx.d.ts +29 -0
  23. package/dist/sharedUtils/components/apps/sharedTypes.d.ts +21 -0
  24. package/dist/sharedUtils/components/apps/types.d.ts +274 -0
  25. package/dist/sharedUtils/components/assets/lib.d.ts +25 -0
  26. package/dist/sharedUtils/components/common/alert/model.d.ts +2 -0
  27. package/dist/sharedUtils/components/common/badge/model.d.ts +8 -0
  28. package/dist/sharedUtils/components/common/button/model.d.ts +45 -0
  29. package/dist/sharedUtils/components/common/fileInput/model.d.ts +1 -0
  30. package/dist/sharedUtils/components/common/index.d.ts +24 -0
  31. package/dist/sharedUtils/components/common/skeleton/model.d.ts +21 -0
  32. package/dist/sharedUtils/components/dbTypes.d.ts +14 -0
  33. package/dist/sharedUtils/components/diff_drawer.d.ts +26 -0
  34. package/dist/sharedUtils/components/ducklake.d.ts +1 -0
  35. package/dist/sharedUtils/components/flows/scheduleUtils.d.ts +7 -0
  36. package/dist/sharedUtils/components/icons/index.d.ts +101 -0
  37. package/dist/sharedUtils/components/random_positive_adjetive.d.ts +1 -0
  38. package/dist/sharedUtils/components/raw_apps/rawAppPolicy.d.ts +10 -0
  39. package/dist/sharedUtils/components/raw_apps/utils.d.ts +15 -0
  40. package/dist/sharedUtils/components/triggers/email/utils.d.ts +4 -0
  41. package/dist/sharedUtils/components/triggers/gcp/utils.d.ts +2 -0
  42. package/dist/sharedUtils/components/triggers/http/utils.d.ts +11 -0
  43. package/dist/sharedUtils/components/triggers/kafka/utils.d.ts +2 -0
  44. package/dist/sharedUtils/components/triggers/mqtt/utils.d.ts +2 -0
  45. package/dist/sharedUtils/components/triggers/nats/utils.d.ts +2 -0
  46. package/dist/sharedUtils/components/triggers/postgres/utils.d.ts +8 -0
  47. package/dist/sharedUtils/components/triggers/sqs/utils.d.ts +2 -0
  48. package/dist/sharedUtils/components/triggers/triggers.svelte.d.ts +32 -0
  49. package/dist/sharedUtils/components/triggers/utils.d.ts +80 -0
  50. package/dist/sharedUtils/components/triggers/websocket/utils.d.ts +2 -0
  51. package/dist/sharedUtils/components/triggers.d.ts +20 -0
  52. package/dist/sharedUtils/gen/core/ApiError.d.ts +10 -0
  53. package/dist/sharedUtils/gen/core/ApiRequestOptions.d.ts +13 -0
  54. package/dist/sharedUtils/gen/core/ApiResult.d.ts +7 -0
  55. package/dist/sharedUtils/gen/core/CancelablePromise.d.ts +26 -0
  56. package/dist/sharedUtils/gen/core/OpenAPI.d.ts +27 -0
  57. package/dist/sharedUtils/gen/core/request.d.ts +29 -0
  58. package/dist/sharedUtils/gen/index.d.ts +6 -0
  59. package/dist/sharedUtils/gen/schemas.gen.d.ts +7036 -0
  60. package/dist/sharedUtils/gen/services.gen.d.ts +6047 -0
  61. package/dist/sharedUtils/gen/types.gen.d.ts +21881 -0
  62. package/dist/sharedUtils/history.svelte.d.ts +9 -0
  63. package/dist/sharedUtils/hub.d.ts +49 -0
  64. package/dist/sharedUtils/jsr.json +6 -0
  65. package/dist/sharedUtils/lib.d.ts +5 -0
  66. package/dist/sharedUtils/lib.es.js +1588 -0
  67. package/dist/sharedUtils/package.json +12 -0
  68. package/dist/sharedUtils/schema.d.ts +3 -0
  69. package/dist/sharedUtils/stores.d.ts +97 -0
  70. package/dist/sharedUtils/svelte5Utils.svelte.d.ts +80 -0
  71. package/dist/sharedUtils/toast.d.ts +8 -0
  72. package/dist/sharedUtils/utils.d.ts +265 -0
  73. package/package/components/AppConnectInner.svelte +38 -5
  74. package/package/components/CompareWorkspaces.svelte +142 -486
  75. package/package/components/Editor.svelte +5 -4
  76. package/package/components/Editor.svelte.d.ts +1 -0
  77. package/package/components/FilterSearchbar.svelte +3 -1
  78. package/package/components/FilterSearchbar.svelte.d.ts +1 -0
  79. package/package/components/ForkWorkspaceBanner.svelte +16 -0
  80. package/package/components/LogViewer.svelte +51 -60
  81. package/package/components/OnBehalfOfSelector.svelte +10 -7
  82. package/package/components/ResourceEditor.svelte +198 -311
  83. package/package/components/ResourceEditor.svelte.d.ts +3 -3
  84. package/package/components/ResourceEditorDrawer.svelte +17 -6
  85. package/package/components/ResourceForm.svelte +235 -0
  86. package/package/components/ResourceForm.svelte.d.ts +25 -0
  87. package/package/components/RunsPage.svelte +1 -0
  88. package/package/components/ScriptBuilder.svelte +1 -0
  89. package/package/components/ScriptEditor.svelte +10 -3
  90. package/package/components/ScriptEditor.svelte.d.ts +1 -0
  91. package/package/components/TaggedTextInput.svelte +4 -1
  92. package/package/components/TaggedTextInput.svelte.d.ts +2 -0
  93. package/package/components/VariableEditor.svelte +177 -199
  94. package/package/components/VariableEditor.svelte.d.ts +1 -2
  95. package/package/components/VariableForm.svelte +133 -0
  96. package/package/components/VariableForm.svelte.d.ts +22 -0
  97. package/package/components/WsSpecificVersions.svelte +39 -0
  98. package/package/components/WsSpecificVersions.svelte.d.ts +9 -0
  99. package/package/components/apps/components/helpers/RunnableComponent.svelte.d.ts +0 -1
  100. package/package/components/apps/editor/AppEditorHeaderDeploy.svelte.d.ts +1 -1
  101. package/package/components/common/table/AppRow.svelte +2 -1
  102. package/package/components/common/table/AppRow.svelte.d.ts +1 -0
  103. package/package/components/common/table/FlowRow.svelte +2 -1
  104. package/package/components/common/table/FlowRow.svelte.d.ts +1 -0
  105. package/package/components/common/table/RawAppRow.svelte +2 -1
  106. package/package/components/common/table/RawAppRow.svelte.d.ts +1 -0
  107. package/package/components/common/table/Row.svelte +11 -3
  108. package/package/components/common/table/Row.svelte.d.ts +2 -1
  109. package/package/components/common/table/RowIcon.svelte +18 -2
  110. package/package/components/common/table/RowIcon.svelte.d.ts +1 -1
  111. package/package/components/common/table/ScriptRow.svelte +2 -1
  112. package/package/components/common/table/ScriptRow.svelte.d.ts +1 -0
  113. package/package/components/copilot/autocomplete/Autocompletor.d.ts +3 -1
  114. package/package/components/copilot/autocomplete/Autocompletor.js +5 -2
  115. package/package/components/copilot/autocomplete/request.d.ts +1 -0
  116. package/package/components/copilot/autocomplete/request.js +1 -1
  117. package/package/components/copilot/chat/AIChatManager.svelte.js +14 -4
  118. package/package/components/copilot/chat/AiChatLayout.svelte +2 -0
  119. package/package/components/copilot/chat/ContextManager.svelte.d.ts +1 -0
  120. package/package/components/copilot/chat/CreatedResourceActionDrawers.svelte +129 -0
  121. package/package/components/copilot/chat/CreatedResourceActionDrawers.svelte.d.ts +4 -0
  122. package/package/components/copilot/chat/ToolExecutionDisplay.svelte +14 -6
  123. package/package/components/copilot/chat/ToolMessageActions.svelte +73 -0
  124. package/package/components/copilot/chat/ToolMessageActions.svelte.d.ts +7 -0
  125. package/package/components/copilot/chat/createdResourceActions.svelte.d.ts +6 -0
  126. package/package/components/copilot/chat/createdResourceActions.svelte.js +29 -0
  127. package/package/components/copilot/chat/script/core.d.ts +6 -2
  128. package/package/components/copilot/chat/script/core.js +13 -7
  129. package/package/components/copilot/chat/script/wacPrompt.test.d.ts +1 -0
  130. package/package/components/copilot/chat/script/wacPrompt.test.js +25 -0
  131. package/package/components/copilot/chat/shared.d.ts +12 -0
  132. package/package/components/copilot/chat/shared.test.js +23 -2
  133. package/package/components/copilot/chat/workspaceTools.js +34 -4
  134. package/package/components/flows/content/ScriptEditorDrawer.svelte +1 -0
  135. package/package/components/graph/wacToFlow.js +1 -1
  136. package/package/components/graph/wacToFlow.test.d.ts +1 -0
  137. package/package/components/graph/wacToFlow.test.js +17 -0
  138. package/package/components/home/Item.svelte +5 -1
  139. package/package/components/home/Item.svelte.d.ts +1 -0
  140. package/package/components/home/ItemsList.svelte +260 -3
  141. package/package/components/instanceSettings/SecretBackendConfig.svelte +457 -88
  142. package/package/components/runs/useJobsLoader.svelte.js +5 -11
  143. package/package/components/sidebar/WorkspaceMenu.svelte +19 -5
  144. package/package/externalDomain.d.ts +2 -0
  145. package/package/externalDomain.js +16 -0
  146. package/package/gen/core/OpenAPI.js +1 -1
  147. package/package/gen/types.gen.d.ts +0 -112
  148. package/package/hubPaths.json +2 -2
  149. package/package/system_prompts/index.d.ts +1 -1
  150. package/package/system_prompts/index.js +22 -3
  151. package/package/system_prompts/prompts.d.ts +2 -2
  152. package/package/system_prompts/prompts.js +6 -3
  153. package/package/utils_deployable.d.ts +162 -638
  154. package/package/utils_deployable.js +75 -143
  155. package/package/utils_workspace_deploy.d.ts +10 -4
  156. package/package/utils_workspace_deploy.js +167 -42
  157. package/package.json +7 -3
@@ -6,18 +6,22 @@ import type { ReviewChangesOpts } from '../monaco-adapter';
6
6
  export declare const DIFF_BASED_EDIT_PROVIDERS: AIProvider[];
7
7
  export declare function formatResourceTypes(allResourceTypes: ResourceType[], lang: 'python3' | 'php' | 'bun' | 'deno' | 'nativets' | 'bunnative'): string;
8
8
  export declare const SUPPORTED_CHAT_SCRIPT_LANGUAGES: string[];
9
- export declare function getLangContext(lang: ScriptLang | 'bunnative' | 'jsx' | 'tsx' | 'json', { allowResourcesFetch, isPreprocessor }?: {
9
+ export declare function getLangContext(lang: ScriptLang | 'bunnative' | 'jsx' | 'tsx' | 'json', { allowResourcesFetch, isPreprocessor, workflowAsCode }?: {
10
10
  allowResourcesFetch?: boolean;
11
11
  isPreprocessor?: boolean;
12
12
  isFailure?: boolean;
13
+ workflowAsCode?: boolean;
13
14
  }): string;
14
15
  export declare function getFormattedResourceTypes(lang: ScriptLang | 'bunnative', prompt: string, workspace: string): Promise<string>;
15
16
  export declare const INLINE_CHAT_SYSTEM_PROMPT = "\n# Windmill Inline Coding Assistant\n\nYou are a coding assistant for the Windmill platform. You provide precise code modifications based on user instructions.\n\n## Input Format\n\nYou will receive:\n- **INSTRUCTIONS**: User's modification request\n- **CODE**: Current code content with modification boundaries\n- **DATABASES** *(optional)*: Available workspace databases\n\n### Code Boundaries\n\nThe code contains `[#START]` and `[#END]` markers indicating the modification scope:\n- **MUST** only modify code between these markers\n- **MUST** remove the markers in your response\n- **MUST** preserve all other code exactly as provided\n\n## Task Requirements\n\nReturn the modified CODE that fulfills the user's request. Assume all user queries are valid and actionable.\n\n### Critical Rules\n\n- \u2705 **ALWAYS** include a single code block with the entire updated CODE\n- \u2705 **ALWAYS** use the structured XML output format below\n- \u274C **NEVER** include only modified sections\n- \u274C **NEVER** add explanatory text or comments outside the format\n- \u274C **NEVER** include ``` code fences in your response\n- \u274C **NEVER** modify the code outside the boundaries\n\n## Output Format\n\n```xml\n<changes_made>\nBrief description of what was changed\n</changes_made>\n<new_code>\n[complete modified code without markers]\n</new_code>\n```\n\n## Example\n\n### Input:\n```xml\n<user_request>\nINSTRUCTIONS:\nReturn 2 instead of 1\n\nCODE:\nimport * as wmill from \"windmill-client\"\n\nfunction test() {\n\treturn \"hello\"\n}\n\n[#START]\nexport async function main() {\n\treturn 1;\n}\n[#END]\n</user_request>\n```\n\n### Expected Output:\n```xml\n<changes_made>\nChanged return value from 1 to 2 in main function\n</changes_made>\n<new_code>\nimport * as wmill from \"windmill-client\"\n\nfunction test() {\n\treturn \"hello\"\n}\n\nexport async function main() {\n\treturn 2;\n}\n</new_code>\n```\n";
16
- export declare function prepareInlineChatSystemPrompt(lang: ScriptLang | 'bunnative'): string;
17
+ export declare function prepareInlineChatSystemPrompt(lang: ScriptLang | 'bunnative', options?: {
18
+ workflowAsCode?: boolean;
19
+ }): string;
17
20
  export declare const CHAT_USER_PROMPT = "\nINSTRUCTIONS:\n{instructions}\n\n";
18
21
  export declare function prepareScriptSystemMessage(currentModel: AIProviderModel, language: ScriptLang | 'bunnative', options?: {
19
22
  isPreprocessor?: boolean;
20
23
  allowResourcesFetch?: boolean;
24
+ workflowAsCode?: boolean;
21
25
  }, customPrompt?: string): ChatCompletionSystemMessageParam;
22
26
  export declare function prepareScriptTools(currentModel: AIProviderModel, language: ScriptLang | 'bunnative', context: ContextElement[]): Tool<ScriptChatHelpers>[];
23
27
  export declare function prepareScriptUserMessage(instructions: string, selectedContext: ContextElement[]): ChatCompletionUserMessageParam;
@@ -9,7 +9,7 @@ import { setupTypeAcquisition } from '../../../../ata';
9
9
  import { getModelContextWindow } from '../../lib';
10
10
  import { getCurrentModel } from '../../../../aiStore';
11
11
  import { getDbSchemas } from '../../../apps/components/display/dbtable/metadata';
12
- import { getScriptPrompt } from '../../../../system_prompts';
12
+ import { getScriptPrompt, getWorkflowAsCodePrompt } from '../../../../system_prompts';
13
13
  // Score threshold for npm packages search filtering
14
14
  const SCORE_THRESHOLD = 1000;
15
15
  // percentage of the context window for documentation of npm packages
@@ -69,17 +69,21 @@ export const SUPPORTED_CHAT_SCRIPT_LANGUAGES = [
69
69
  'java',
70
70
  'duckdb'
71
71
  ];
72
- export function getLangContext(lang, { allowResourcesFetch = false, isPreprocessor = false } = {}) {
72
+ export function getLangContext(lang, { allowResourcesFetch = false, isPreprocessor = false, workflowAsCode = false } = {}) {
73
73
  // Get base language context from centralized prompts
74
- let context = getScriptPrompt(lang);
74
+ let context = workflowAsCode ? getWorkflowAsCodePrompt(lang) : getScriptPrompt(lang);
75
+ // Fallback to the regular script prompt if WAC context is requested for an unsupported language.
76
+ if (!context) {
77
+ context = getScriptPrompt(lang);
78
+ }
75
79
  // Add tool usage instructions for applicable languages
76
80
  if (['python3', 'php', 'bun', 'deno', 'nativets', 'bunnative'].includes(lang)) {
77
81
  if (allowResourcesFetch) {
78
82
  context += '\n\nTo query available resource types, use the `search_resource_types` tool.';
79
83
  }
80
84
  }
81
- // Note preprocessor function naming if applicable
82
- if (isPreprocessor) {
85
+ // Note preprocessor function naming if applicable. WAC scripts are not preprocessors.
86
+ if (isPreprocessor && !workflowAsCode) {
83
87
  context +=
84
88
  '\n\nThe main function for this script should be named `preprocessor` instead of `main`.';
85
89
  }
@@ -225,8 +229,10 @@ export async function main() {
225
229
  </new_code>
226
230
  \`\`\`
227
231
  `;
228
- export function prepareInlineChatSystemPrompt(lang) {
229
- return INLINE_CHAT_SYSTEM_PROMPT + getLangContext(lang, { allowResourcesFetch: true });
232
+ export function prepareInlineChatSystemPrompt(lang, options = {}) {
233
+ return (INLINE_CHAT_SYSTEM_PROMPT +
234
+ '\n\n' +
235
+ getLangContext(lang, { allowResourcesFetch: true, workflowAsCode: options.workflowAsCode }));
230
236
  }
231
237
  export const CHAT_USER_PROMPT = `
232
238
  INSTRUCTIONS:
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getScriptPrompt, getWorkflowAsCodePrompt } from '../../../../system_prompts';
3
+ describe('Workflow-as-Code prompt helpers', () => {
4
+ it('injects only the TypeScript WAC SDK for TypeScript runtimes', () => {
5
+ const prompt = getWorkflowAsCodePrompt('bun');
6
+ expect(prompt).toContain('Windmill Workflow-as-Code Writing Guide');
7
+ expect(prompt).toContain('## TypeScript Workflow-as-Code API');
8
+ expect(prompt).not.toContain('## Python Workflow-as-Code API');
9
+ });
10
+ it('injects only the Python WAC SDK for Python runtimes', () => {
11
+ const prompt = getWorkflowAsCodePrompt('python3');
12
+ expect(prompt).toContain('Windmill Workflow-as-Code Writing Guide');
13
+ expect(prompt).toContain('## Python Workflow-as-Code API');
14
+ expect(prompt).not.toContain('## TypeScript Workflow-as-Code API');
15
+ });
16
+ it('does not support non-Bun TypeScript runtimes as WAC targets', () => {
17
+ expect(getWorkflowAsCodePrompt('deno')).toBe('');
18
+ expect(getWorkflowAsCodePrompt('nativets')).toBe('');
19
+ expect(getWorkflowAsCodePrompt('bunnative')).toBe('');
20
+ });
21
+ it('does not change normal script prompts', () => {
22
+ expect(getWorkflowAsCodePrompt('go')).toBe('');
23
+ expect(getScriptPrompt('bun')).not.toContain('Windmill Workflow-as-Code Writing Guide');
24
+ });
25
+ });
@@ -44,6 +44,17 @@ export type UserDisplayMessage = BaseDisplayMessage & {
44
44
  index: number;
45
45
  error?: boolean;
46
46
  };
47
+ export type CreatedResourceTriggerKind = 'http' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'mqtt' | 'sqs' | 'gcp' | 'azure';
48
+ export type CreatedResourceAction = {
49
+ id: string;
50
+ type: 'open_created_resource';
51
+ label: string;
52
+ resource: 'schedule' | 'trigger';
53
+ path: string;
54
+ targetKind: 'script' | 'flow';
55
+ triggerKind?: CreatedResourceTriggerKind;
56
+ };
57
+ export type ToolDisplayAction = CreatedResourceAction;
47
58
  export type ToolDisplayMessage = {
48
59
  role: 'tool';
49
60
  tool_call_id: string;
@@ -58,6 +69,7 @@ export type ToolDisplayMessage = {
58
69
  isStreamingArguments?: boolean;
59
70
  toolName?: string;
60
71
  showFade?: boolean;
72
+ actions?: ToolDisplayAction[];
61
73
  };
62
74
  export type AssistantDisplayMessage = BaseDisplayMessage & {
63
75
  role: 'assistant';
@@ -244,7 +244,17 @@ describe('processToolCall', () => {
244
244
  target_path: 'f/scripts/current',
245
245
  target_kind: 'script',
246
246
  backend_result: 'schedule-created'
247
- })
247
+ }),
248
+ actions: [
249
+ expect.objectContaining({
250
+ id: 'open-created-schedule:f/schedules/current',
251
+ type: 'open_created_resource',
252
+ label: 'Open schedule',
253
+ resource: 'schedule',
254
+ path: 'f/schedules/current',
255
+ targetKind: 'script'
256
+ })
257
+ ]
248
258
  }));
249
259
  expect(JSON.parse(scheduleResult.content)).toEqual(expect.objectContaining({
250
260
  success: true,
@@ -302,7 +312,18 @@ describe('processToolCall', () => {
302
312
  target_path: 'f/flows/current',
303
313
  target_kind: 'flow',
304
314
  backend_result: 'trigger-created'
305
- })
315
+ }),
316
+ actions: [
317
+ expect.objectContaining({
318
+ id: 'open-created-trigger:http:f/triggers/current',
319
+ type: 'open_created_resource',
320
+ label: 'Open HTTP trigger',
321
+ resource: 'trigger',
322
+ triggerKind: 'http',
323
+ path: 'f/triggers/current',
324
+ targetKind: 'flow'
325
+ })
326
+ ]
306
327
  }));
307
328
  expect(JSON.parse(triggerResult.content)).toEqual(expect.objectContaining({
308
329
  success: true,
@@ -83,6 +83,30 @@ const triggerConfigs = {
83
83
  create: (data) => AzureTriggerService.createAzureTrigger(data)
84
84
  }
85
85
  };
86
+ function getActionTargetKind(isFlow) {
87
+ return isFlow ? 'flow' : 'script';
88
+ }
89
+ function createOpenScheduleAction(path, targetKind) {
90
+ return {
91
+ id: `open-created-schedule:${path}`,
92
+ type: 'open_created_resource',
93
+ label: 'Open schedule',
94
+ resource: 'schedule',
95
+ path,
96
+ targetKind
97
+ };
98
+ }
99
+ function createOpenTriggerAction(kind, path, targetKind, label) {
100
+ return {
101
+ id: `open-created-trigger:${kind}:${path}`,
102
+ type: 'open_created_resource',
103
+ label: `Open ${label}`,
104
+ resource: 'trigger',
105
+ triggerKind: kind,
106
+ path,
107
+ targetKind
108
+ };
109
+ }
86
110
  function formatPath(path) {
87
111
  if (path.length === 0) {
88
112
  return 'value';
@@ -168,16 +192,18 @@ const createScheduleTool = {
168
192
  });
169
193
  try {
170
194
  const result = await ScheduleService.createSchedule({ workspace, requestBody });
195
+ const targetKind = getActionTargetKind(requestBody.is_flow);
171
196
  const toolResult = {
172
197
  success: true,
173
198
  path: requestBody.path,
174
199
  target_path: requestBody.script_path,
175
- target_kind: requestBody.is_flow ? 'flow' : 'script',
200
+ target_kind: targetKind,
176
201
  backend_result: result
177
202
  };
178
203
  toolCallbacks.setToolStatus(toolId, {
179
204
  content: `Created schedule "${requestBody.path}"`,
180
- result: toolResult
205
+ result: toolResult,
206
+ actions: [createOpenScheduleAction(requestBody.path, targetKind)]
181
207
  });
182
208
  return JSON.stringify(toolResult);
183
209
  }
@@ -210,17 +236,21 @@ const createTriggerTool = {
210
236
  });
211
237
  try {
212
238
  const result = await triggerConfig.create({ workspace, requestBody });
239
+ const targetKind = getActionTargetKind(requestBody.is_flow);
213
240
  const toolResult = {
214
241
  success: true,
215
242
  kind: parsedArgs.kind,
216
243
  path: requestBody.path,
217
244
  target_path: requestBody.script_path,
218
- target_kind: requestBody.is_flow ? 'flow' : 'script',
245
+ target_kind: targetKind,
219
246
  backend_result: result
220
247
  };
221
248
  toolCallbacks.setToolStatus(toolId, {
222
249
  content: `Created ${triggerConfig.label} "${requestBody.path}"`,
223
- result: toolResult
250
+ result: toolResult,
251
+ actions: [
252
+ createOpenTriggerAction(parsedArgs.kind, requestBody.path, targetKind, triggerConfig.label)
253
+ ]
224
254
  });
225
255
  return JSON.stringify(toolResult);
226
256
  }
@@ -157,6 +157,7 @@ let displayEditor = $state(true);
157
157
  noSyncFromGithub
158
158
  lang={script.language}
159
159
  path={script.path}
160
+ autoKind={script.auto_kind}
160
161
  tag={script.tag}
161
162
  fixedOverflowWidgets={false}
162
163
  bind:code={script.content}
@@ -5,7 +5,7 @@ export function isWorkflowAsCode(code, language) {
5
5
  if (language === 'python3') {
6
6
  return /^\s*@workflow\s*$/m.test(code) || /from\s+wmill\s+import.*workflow/.test(code);
7
7
  }
8
- if (language === 'bun' || language === 'deno') {
8
+ if (language === 'bun') {
9
9
  return (/workflow\s*\(/.test(code) &&
10
10
  /task\s*\(/.test(code) &&
11
11
  /['"]windmill-client(?:@[^'"]*)?['"]/.test(code));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { isWorkflowAsCode } from './wacToFlow';
3
+ const tsWac = `
4
+ import { workflow, task } from "windmill-client"
5
+
6
+ const process = task(async () => "ok")
7
+
8
+ export const main = workflow(async () => await process())
9
+ `;
10
+ describe('isWorkflowAsCode', () => {
11
+ it('detects WAC only for Bun TypeScript and Python', () => {
12
+ expect(isWorkflowAsCode(tsWac, 'bun')).toBe(true);
13
+ expect(isWorkflowAsCode(tsWac, 'deno')).toBe(false);
14
+ expect(isWorkflowAsCode(tsWac, 'nativets')).toBe(false);
15
+ expect(isWorkflowAsCode('@workflow\nasync def main():\n return None\n', 'python3')).toBe(true);
16
+ });
17
+ });
@@ -15,7 +15,7 @@ let shareModal = $state();
15
15
  let moveDrawer = $state();
16
16
  let deploymentDrawer = $state();
17
17
  let menuOpen = $state(false);
18
- let { item, depth = 0, showCode, showEditButton = true } = $props();
18
+ let { item, depth = 0, showCode, showEditButton = true, keyboardSelected = false } = $props();
19
19
  </script>
20
20
 
21
21
  {#if item.type == 'script'}
@@ -35,6 +35,7 @@ let { item, depth = 0, showCode, showEditButton = true } = $props();
35
35
  bind:menuOpen
36
36
  {showCode}
37
37
  {showEditButton}
38
+ {keyboardSelected}
38
39
  />
39
40
  {:else if item.type == 'flow'}
40
41
  <FlowRow
@@ -52,6 +53,7 @@ let { item, depth = 0, showCode, showEditButton = true } = $props();
52
53
  {depth}
53
54
  bind:menuOpen
54
55
  {showEditButton}
56
+ {keyboardSelected}
55
57
  />
56
58
  {:else if item.type == 'app'}
57
59
  <AppRow
@@ -65,6 +67,7 @@ let { item, depth = 0, showCode, showEditButton = true } = $props();
65
67
  {depth}
66
68
  bind:menuOpen
67
69
  {showEditButton}
70
+ {keyboardSelected}
68
71
  />
69
72
  {:else if item.type == 'raw_app'}
70
73
  <RawAppRow
@@ -74,6 +77,7 @@ let { item, depth = 0, showCode, showEditButton = true } = $props();
74
77
  {deploymentDrawer}
75
78
  {depth}
76
79
  bind:menuOpen
80
+ {keyboardSelected}
77
81
  />
78
82
  {/if}
79
83
 
@@ -3,6 +3,7 @@ interface Props {
3
3
  depth?: number;
4
4
  showCode: (path: string, summary: string) => void;
5
5
  showEditButton?: boolean;
6
+ keyboardSelected?: boolean;
6
7
  }
7
8
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
8
9
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
@@ -20,7 +20,7 @@ import DrawerContent from '../common/drawer/DrawerContent.svelte';
20
20
  import Item from './Item.svelte';
21
21
  import TreeViewRoot from './TreeViewRoot.svelte';
22
22
  import Popover from '../meltComponents/Popover.svelte';
23
- import { getContext, untrack } from 'svelte';
23
+ import { getContext, tick, untrack } from 'svelte';
24
24
  import { triggerableByAI } from '../../actions/triggerableByAI.svelte';
25
25
  import TextInput from '../text_input/TextInput.svelte';
26
26
  let { filter = $bindable(''), subtab = $bindable('script'), showEditButtons = true } = $props();
@@ -215,9 +215,261 @@ let preFilteredItems = $derived(ownerFilter != undefined
215
215
  filterItemsPathsBaseOnUserFilters(x, filterUserFolders, filterUserFoldersType) &&
216
216
  (labelFilter == undefined || ('labels' in x && x.labels?.includes(labelFilter)))));
217
217
  let items = $derived(filter !== '' ? filteredItems : preFilteredItems);
218
+ let displayedItems = $derived((items ?? []).slice(0, nbDisplayed));
218
219
  $effect(() => {
219
220
  items && resetScroll();
220
221
  });
222
+ let selectedIndex = $state(-1);
223
+ let hasMore = $derived(items != undefined && items.length > nbDisplayed);
224
+ let loadMoreIndex = $derived(displayedItems.length);
225
+ let loadMoreEl = $state();
226
+ let pendingAutoSelect = $state(true);
227
+ let firstWorkspaceRun = true;
228
+ $effect(() => {
229
+ $workspaceStore;
230
+ pendingAutoSelect = true;
231
+ if (firstWorkspaceRun) {
232
+ firstWorkspaceRun = false;
233
+ return;
234
+ }
235
+ // On workspace switch, melt-ui restores focus to the workspace-picker trigger
236
+ // button asynchronously after the menu closes. Without overriding it, pressing
237
+ // an arrow key would re-open / re-highlight the workspace picker instead of
238
+ // moving the items-list selection. Run several times to win the focus race.
239
+ const focusSearch = () => {
240
+ const el = document.getElementById('home-search-input');
241
+ el?.focus();
242
+ };
243
+ focusSearch();
244
+ const raf1 = requestAnimationFrame(() => {
245
+ focusSearch();
246
+ requestAnimationFrame(focusSearch);
247
+ });
248
+ const timeoutId = setTimeout(focusSearch, 100);
249
+ return () => {
250
+ cancelAnimationFrame(raf1);
251
+ clearTimeout(timeoutId);
252
+ };
253
+ });
254
+ $effect(() => {
255
+ filter;
256
+ itemKind;
257
+ ownerFilter;
258
+ labelFilter;
259
+ // Skip while pendingAutoSelect is true (initial load / workspace switch);
260
+ // the auto-select effect below will set the index once items appear.
261
+ if (!pendingAutoSelect) {
262
+ selectedIndex = -1;
263
+ }
264
+ });
265
+ $effect(() => {
266
+ if (pendingAutoSelect && displayedItems.length > 0) {
267
+ selectedIndex = 0;
268
+ pendingAutoSelect = false;
269
+ }
270
+ });
271
+ $effect(() => {
272
+ const max = hasMore ? displayedItems.length : displayedItems.length - 1;
273
+ if (selectedIndex > max) {
274
+ selectedIndex = max;
275
+ }
276
+ });
277
+ $effect(() => {
278
+ if (hasMore && selectedIndex === loadMoreIndex) {
279
+ loadMoreEl?.scrollIntoView({ block: 'nearest' });
280
+ }
281
+ });
282
+ // Capture-phase listener so we run before melt-ui's button keydown handlers
283
+ // (e.g. ArrowDown on the dropdown trigger would otherwise open the menu).
284
+ $effect(() => {
285
+ window.addEventListener('keydown', handleGlobalKeydown, true);
286
+ return () => window.removeEventListener('keydown', handleGlobalKeydown, true);
287
+ });
288
+ function loadMoreAndPreselectFirstNew() {
289
+ const previousNbDisplayed = nbDisplayed;
290
+ nbDisplayed += 30;
291
+ selectedIndex = previousNbDisplayed;
292
+ }
293
+ function getSelectedRowActionButtons() {
294
+ const anchor = document.querySelector('a[data-row-keyboard-selected="true"]');
295
+ const actions = anchor?.parentElement?.querySelector('[data-row-actions]');
296
+ return actions ? Array.from(actions.querySelectorAll('button, a[href]')) : [];
297
+ }
298
+ function handleGlobalKeydown(e) {
299
+ if (treeView)
300
+ return;
301
+ const target = e.target;
302
+ // When focus is inside a row's action buttons, handle arrow keys ourselves:
303
+ // - Left/Right cycle between buttons (Left from the first returns to search).
304
+ // - Up/Down move to the same-position button on the previous/next row.
305
+ // All other keys pass through so Enter/Space activate the focused button normally.
306
+ // This must run BEFORE the skipSelector check, since the dropdown ellipsis
307
+ // trigger carries [data-menu] (which would otherwise filter the event out).
308
+ // Up/Down also need stopImmediatePropagation so melt-ui's dropdown trigger
309
+ // doesn't open the menu (its default ArrowDown behavior).
310
+ const actionsContainer = target?.closest('[data-row-actions]');
311
+ if (actionsContainer) {
312
+ if (e.key !== 'ArrowRight' &&
313
+ e.key !== 'ArrowLeft' &&
314
+ e.key !== 'ArrowUp' &&
315
+ e.key !== 'ArrowDown')
316
+ return;
317
+ const buttons = Array.from(actionsContainer.querySelectorAll('button, a[href]'));
318
+ const currentIdx = buttons.indexOf(target);
319
+ if (currentIdx < 0)
320
+ return;
321
+ if (e.key === 'ArrowRight') {
322
+ if (currentIdx < buttons.length - 1) {
323
+ e.preventDefault();
324
+ buttons[currentIdx + 1].focus();
325
+ }
326
+ }
327
+ else if (e.key === 'ArrowLeft') {
328
+ e.preventDefault();
329
+ if (currentIdx > 0) {
330
+ buttons[currentIdx - 1].focus();
331
+ }
332
+ else {
333
+ ;
334
+ document.getElementById('home-search-input')?.focus();
335
+ }
336
+ }
337
+ else {
338
+ // ArrowUp / ArrowDown: move to same-position button on prev/next row.
339
+ e.preventDefault();
340
+ e.stopImmediatePropagation();
341
+ if (selectedIndex < 0 || selectedIndex >= displayedItems.length)
342
+ return;
343
+ const newIndex = e.key === 'ArrowDown'
344
+ ? Math.min(selectedIndex + 1, displayedItems.length - 1)
345
+ : Math.max(selectedIndex - 1, 0);
346
+ if (newIndex === selectedIndex)
347
+ return;
348
+ selectedIndex = newIndex;
349
+ tick().then(() => {
350
+ const newButtons = getSelectedRowActionButtons();
351
+ if (newButtons.length === 0)
352
+ return;
353
+ const targetIdx = Math.min(currentIdx, newButtons.length - 1);
354
+ newButtons[targetIdx]?.focus();
355
+ });
356
+ }
357
+ return;
358
+ }
359
+ // Inside an open dropdown menu: ArrowUp on first item / ArrowDown on last item
360
+ // closes the menu (so users can leave with arrows instead of needing Escape).
361
+ // Other arrow keys fall through to melt-ui's default cycle.
362
+ const menuItem = target?.closest('[role="menuitem"]');
363
+ if (menuItem) {
364
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
365
+ const menu = menuItem.closest('[role="menu"]');
366
+ if (menu) {
367
+ const items = Array.from(menu.querySelectorAll('[role="menuitem"]'));
368
+ const idx = items.indexOf(menuItem);
369
+ const isFirst = idx === 0;
370
+ const isLast = idx === items.length - 1;
371
+ if ((e.key === 'ArrowUp' && isFirst) || (e.key === 'ArrowDown' && isLast)) {
372
+ e.preventDefault();
373
+ e.stopImmediatePropagation();
374
+ menuItem.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }));
375
+ }
376
+ }
377
+ }
378
+ return;
379
+ }
380
+ const skipSelector = '[role="menu"], [role="menuitem"], [role="dialog"], [role="listbox"], [role="combobox"], [aria-expanded="true"], [data-menu]';
381
+ if (target) {
382
+ const tag = target.tagName;
383
+ const isEditable = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable;
384
+ const isOurSearch = target.id === 'home-search-input';
385
+ if (isEditable && !isOurSearch)
386
+ return;
387
+ if (target.closest(skipSelector))
388
+ return;
389
+ }
390
+ const active = document.activeElement;
391
+ if (active?.closest(skipSelector))
392
+ return;
393
+ // ArrowRight from search input / body → focus first action button of selected row.
394
+ // Guard: if cursor is in the middle of typed search text, let the cursor move.
395
+ if (e.key === 'ArrowRight') {
396
+ if (target?.id === 'home-search-input') {
397
+ const inp = target;
398
+ if (inp.value.length > 0 && inp.selectionEnd !== inp.value.length)
399
+ return;
400
+ }
401
+ if (selectedIndex < 0 || selectedIndex >= displayedItems.length)
402
+ return;
403
+ const buttons = getSelectedRowActionButtons();
404
+ if (buttons.length > 0) {
405
+ e.preventDefault();
406
+ buttons[0].focus();
407
+ }
408
+ return;
409
+ }
410
+ // ArrowLeft from search input with cursor at start: no-op (let default handle).
411
+ if (e.key === 'ArrowLeft') {
412
+ if (target?.id === 'home-search-input') {
413
+ const inp = target;
414
+ if (inp.value.length > 0 && inp.selectionStart !== 0)
415
+ return;
416
+ }
417
+ return;
418
+ }
419
+ if (e.key === 'ArrowDown') {
420
+ if (displayedItems.length === 0)
421
+ return;
422
+ e.preventDefault();
423
+ if (selectedIndex === -1) {
424
+ selectedIndex = 0;
425
+ }
426
+ else if (selectedIndex === loadMoreIndex && hasMore) {
427
+ selectedIndex = 0;
428
+ }
429
+ else if (selectedIndex === displayedItems.length - 1) {
430
+ selectedIndex = hasMore ? loadMoreIndex : 0;
431
+ }
432
+ else {
433
+ selectedIndex = selectedIndex + 1;
434
+ }
435
+ }
436
+ else if (e.key === 'ArrowUp') {
437
+ if (displayedItems.length === 0)
438
+ return;
439
+ e.preventDefault();
440
+ if (selectedIndex === -1) {
441
+ selectedIndex = displayedItems.length - 1;
442
+ }
443
+ else if (selectedIndex === loadMoreIndex && hasMore) {
444
+ selectedIndex = displayedItems.length - 1;
445
+ }
446
+ else if (selectedIndex === 0) {
447
+ selectedIndex = hasMore ? loadMoreIndex : displayedItems.length - 1;
448
+ }
449
+ else {
450
+ selectedIndex = selectedIndex - 1;
451
+ }
452
+ }
453
+ else if (e.key === 'Enter') {
454
+ if (selectedIndex === loadMoreIndex && hasMore) {
455
+ e.preventDefault();
456
+ loadMoreAndPreselectFirstNew();
457
+ }
458
+ else if (selectedIndex >= 0 && selectedIndex < displayedItems.length) {
459
+ const anchor = document.querySelector('a[data-row-keyboard-selected="true"]');
460
+ if (anchor) {
461
+ e.preventDefault();
462
+ anchor.click();
463
+ }
464
+ }
465
+ }
466
+ else if (e.key === 'Escape') {
467
+ if (selectedIndex !== -1) {
468
+ e.preventDefault();
469
+ selectedIndex = -1;
470
+ }
471
+ }
472
+ }
221
473
  $effect(() => {
222
474
  storeLocalSetting(TREE_VIEW_SETTING_NAME, treeView ? 'true' : undefined);
223
475
  });
@@ -461,7 +713,7 @@ $effect(() => {
461
713
  />
462
714
  {:else}
463
715
  <div class="border rounded-md bg-surface-tertiary">
464
- {#each (items ?? []).slice(0, nbDisplayed) as item (item.type + '/' + item.path + (item.hash ? '/' + item.hash : ''))}
716
+ {#each displayedItems as item, i (item.type + '/' + item.path + (item.hash ? '/' + item.hash : ''))}
465
717
  <Item
466
718
  {item}
467
719
  on:scriptChanged={() => loadScripts(includeWithoutMain)}
@@ -476,6 +728,7 @@ $effect(() => {
476
728
  }}
477
729
  {showCode}
478
730
  showEditButton={showEditButtons}
731
+ keyboardSelected={selectedIndex === i}
479
732
  />
480
733
  {/each}
481
734
  </div>
@@ -483,7 +736,11 @@ $effect(() => {
483
736
  <span class="text-xs font-normal text-secondary"
484
737
  >{nbDisplayed} items out of {items.length}
485
738
  <button
486
- class="ml-4 text-xs font-normal text-primary hover:text-emphasis"
739
+ bind:this={loadMoreEl}
740
+ class="ml-4 text-xs font-normal text-primary hover:text-emphasis rounded px-1 {selectedIndex ===
741
+ loadMoreIndex
742
+ ? 'bg-gray-200 dark:bg-gray-700 underline'
743
+ : ''}"
487
744
  onclick={() => (nbDisplayed += 30)}>load 30 more</button
488
745
  ></span
489
746
  >