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,123 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ vi.mock('monaco-editor', () => ({
3
+ editor: {},
4
+ languages: {},
5
+ KeyCode: {},
6
+ Uri: {
7
+ parse: (value) => ({ toString: () => value })
8
+ },
9
+ MarkerSeverity: {
10
+ Error: 8,
11
+ Warning: 4,
12
+ Info: 2,
13
+ Hint: 1
14
+ }
15
+ }));
16
+ vi.mock('@codingame/monaco-vscode-standalone-typescript-language-features', () => ({
17
+ getTypeScriptWorker: async () => async () => ({}),
18
+ typescriptVersion: 'test'
19
+ }));
20
+ vi.mock('@codingame/monaco-vscode-languages-service-override', () => ({
21
+ default: () => ({})
22
+ }));
23
+ vi.mock('$lib/components/vscode', () => ({}));
24
+ vi.mock('$lib/gen', async () => {
25
+ const actual = await vi.importActual('$lib/gen');
26
+ function wrapService(target, overrides) {
27
+ return new Proxy(target, {
28
+ get(source, property, receiver) {
29
+ if (typeof property === 'string' && property in overrides) {
30
+ return overrides[property];
31
+ }
32
+ return Reflect.get(source, property, receiver);
33
+ }
34
+ });
35
+ }
36
+ return {
37
+ ...actual,
38
+ FlowService: wrapService(actual.FlowService, {
39
+ existsFlowByPath: vi.fn(async () => false)
40
+ }),
41
+ VariableService: wrapService(actual.VariableService, {
42
+ existsVariable: vi.fn(async () => false)
43
+ })
44
+ };
45
+ });
46
+ import { globalTools } from './core';
47
+ import { globalDraftStore } from './draftStore.svelte';
48
+ const WORKSPACE = 'global-core-test';
49
+ const toolCallbacks = {
50
+ setToolStatus: vi.fn(),
51
+ removeToolStatus: vi.fn()
52
+ };
53
+ function getGlobalTool(name) {
54
+ const tool = globalTools.find((candidate) => candidate.def.function.name === name);
55
+ if (!tool) {
56
+ throw new Error(`Missing global tool "${name}"`);
57
+ }
58
+ return tool;
59
+ }
60
+ async function callGlobalTool(name, args) {
61
+ return getGlobalTool(name).fn({
62
+ args,
63
+ workspace: WORKSPACE,
64
+ helpers: {},
65
+ toolCallbacks,
66
+ toolId: `test-${name}`
67
+ });
68
+ }
69
+ describe('global AI tools', () => {
70
+ beforeEach(() => {
71
+ globalDraftStore.clearDrafts(WORKSPACE);
72
+ vi.clearAllMocks();
73
+ });
74
+ it('redacts variable draft values when reading workspace items', async () => {
75
+ await callGlobalTool('write_variable', {
76
+ path: 'f/secrets/api_key',
77
+ value: 'super-secret-token',
78
+ is_secret: true,
79
+ description: 'API key'
80
+ });
81
+ const raw = await callGlobalTool('read_workspace_item', {
82
+ type: 'variable',
83
+ path: 'f/secrets/api_key'
84
+ });
85
+ const item = JSON.parse(raw);
86
+ expect(raw).not.toContain('super-secret-token');
87
+ expect(item).toEqual({
88
+ type: 'variable',
89
+ path: 'f/secrets/api_key',
90
+ summary: 'API key',
91
+ isDraft: true
92
+ });
93
+ });
94
+ it('fills an empty rawscript module through set_flow_module_code', async () => {
95
+ await callGlobalTool('write_flow', {
96
+ path: 'f/flows/empty-module',
97
+ summary: 'Flow with empty module',
98
+ value: JSON.stringify({
99
+ modules: [
100
+ {
101
+ id: 'empty_step',
102
+ value: {
103
+ type: 'rawscript',
104
+ language: 'bun',
105
+ content: '',
106
+ input_transforms: {}
107
+ }
108
+ }
109
+ ]
110
+ })
111
+ });
112
+ const code = 'export async function main() {\n\treturn 42\n}';
113
+ await expect(callGlobalTool('set_flow_module_code', {
114
+ path: 'f/flows/empty-module',
115
+ module_id: 'empty_step',
116
+ code
117
+ })).resolves.toContain('Updated AI draft flow');
118
+ await expect(callGlobalTool('read_flow_module_code', {
119
+ path: 'f/flows/empty-module',
120
+ module_id: 'empty_step'
121
+ })).resolves.toBe(code);
122
+ });
123
+ });
@@ -0,0 +1,7 @@
1
+ import type { Flow, NewScript, OpenFlowWPath, Script } from '../../../../gen/types.gen';
2
+ import type { FlowDraftValue, WorkspaceItem } from './draftStore.svelte';
3
+ export type FlowDeployRequestBody = OpenFlowWPath & {
4
+ deployment_message?: string;
5
+ };
6
+ export declare function buildScriptDeployRequestBody(path: string, draft: WorkspaceItem, existing: Script | undefined, deploymentMessage: string | undefined): NewScript;
7
+ export declare function buildFlowDeployRequestBody(path: string, draftSummary: string | undefined, flowDraft: FlowDraftValue, existing: Flow | undefined, deploymentMessage: string | undefined): FlowDeployRequestBody;
@@ -0,0 +1,76 @@
1
+ function preserveOnBehalfOf(email) {
2
+ return email ? true : undefined;
3
+ }
4
+ export function buildScriptDeployRequestBody(path, draft, existing, deploymentMessage) {
5
+ if (typeof draft.value !== 'string' || !draft.language) {
6
+ throw new Error(`Draft script "${path}" is missing content or language.`);
7
+ }
8
+ const existingWithMetadata = existing;
9
+ return {
10
+ path,
11
+ summary: draft.summary ?? existing?.summary ?? '',
12
+ description: existing?.description ?? '',
13
+ content: draft.value,
14
+ parent_hash: existing?.hash,
15
+ schema: existing?.schema,
16
+ is_template: existing?.is_template,
17
+ language: draft.language,
18
+ kind: existing?.kind,
19
+ tag: existing?.tag,
20
+ envs: existing?.envs,
21
+ concurrent_limit: existing?.concurrent_limit,
22
+ concurrency_time_window_s: existing?.concurrency_time_window_s,
23
+ debounce_key: existing?.debounce_key,
24
+ debounce_delay_s: existing?.debounce_delay_s,
25
+ debounce_args_to_accumulate: existing?.debounce_args_to_accumulate,
26
+ max_total_debouncing_time: existing?.max_total_debouncing_time,
27
+ max_total_debounces_amount: existing?.max_total_debounces_amount,
28
+ cache_ttl: existing?.cache_ttl,
29
+ cache_ignore_s3_path: existingWithMetadata?.cache_ignore_s3_path,
30
+ dedicated_worker: existing?.dedicated_worker,
31
+ ws_error_handler_muted: existing?.ws_error_handler_muted,
32
+ priority: existing?.priority,
33
+ restart_unless_cancelled: existing?.restart_unless_cancelled,
34
+ timeout: existing?.timeout,
35
+ delete_after_secs: existing?.delete_after_secs,
36
+ deployment_message: deploymentMessage,
37
+ concurrency_key: existing?.concurrency_key,
38
+ visible_to_runner_only: existing?.visible_to_runner_only,
39
+ auto_kind: existing?.auto_kind,
40
+ codebase: existing?.codebase,
41
+ has_preprocessor: existing?.has_preprocessor,
42
+ on_behalf_of_email: existing?.on_behalf_of_email,
43
+ preserve_on_behalf_of: preserveOnBehalfOf(existing?.on_behalf_of_email),
44
+ assets: existingWithMetadata?.assets,
45
+ modules: existing?.modules,
46
+ labels: existing?.labels
47
+ };
48
+ }
49
+ function flowValueWithDraftGroups(flowDraft) {
50
+ if (flowDraft.groups === undefined) {
51
+ return flowDraft.value;
52
+ }
53
+ return {
54
+ ...flowDraft.value,
55
+ groups: flowDraft.groups ?? undefined
56
+ };
57
+ }
58
+ export function buildFlowDeployRequestBody(path, draftSummary, flowDraft, existing, deploymentMessage) {
59
+ return {
60
+ path,
61
+ summary: draftSummary ?? existing?.summary ?? '',
62
+ description: existing?.description ?? '',
63
+ value: flowValueWithDraftGroups(flowDraft),
64
+ schema: flowDraft.schema ?? existing?.schema ?? {},
65
+ tag: existing?.tag,
66
+ ws_error_handler_muted: existing?.ws_error_handler_muted,
67
+ priority: existing?.priority,
68
+ dedicated_worker: existing?.dedicated_worker,
69
+ timeout: existing?.timeout,
70
+ visible_to_runner_only: existing?.visible_to_runner_only,
71
+ on_behalf_of_email: existing?.on_behalf_of_email,
72
+ preserve_on_behalf_of: preserveOnBehalfOf(existing?.on_behalf_of_email),
73
+ labels: existing?.labels,
74
+ deployment_message: deploymentMessage
75
+ };
76
+ }
@@ -0,0 +1,142 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildFlowDeployRequestBody, buildScriptDeployRequestBody } from './deployRequests';
3
+ describe('global AI deploy request builders', () => {
4
+ it('preserves existing script metadata while replacing draft-controlled fields', () => {
5
+ const existing = {
6
+ hash: 'parent-hash',
7
+ path: 'f/demo/script',
8
+ summary: 'existing summary',
9
+ description: 'existing description',
10
+ content: 'old content',
11
+ schema: { properties: { name: { type: 'string' } } },
12
+ is_template: true,
13
+ language: 'bun',
14
+ kind: 'script',
15
+ tag: 'node',
16
+ envs: ['ENV_A'],
17
+ concurrent_limit: 3,
18
+ concurrency_time_window_s: 60,
19
+ concurrency_key: 'key',
20
+ debounce_key: 'debounce',
21
+ debounce_delay_s: 5,
22
+ debounce_args_to_accumulate: ['ids'],
23
+ max_total_debouncing_time: 120,
24
+ max_total_debounces_amount: 4,
25
+ cache_ttl: 30,
26
+ cache_ignore_s3_path: true,
27
+ dedicated_worker: true,
28
+ ws_error_handler_muted: true,
29
+ priority: 9,
30
+ restart_unless_cancelled: true,
31
+ timeout: 300,
32
+ delete_after_secs: 600,
33
+ visible_to_runner_only: true,
34
+ auto_kind: 'script',
35
+ codebase: 'repo',
36
+ has_preprocessor: true,
37
+ on_behalf_of_email: 'deployer@example.com',
38
+ assets: [{ path: 's3://bucket/key', kind: 's3object' }],
39
+ modules: { 'helper.ts': { content: 'export const helper = 1', language: 'bun' } },
40
+ labels: ['prod'],
41
+ lock: 'stale lock'
42
+ };
43
+ const draft = {
44
+ type: 'script',
45
+ path: 'f/demo/script',
46
+ summary: 'draft summary',
47
+ language: 'bun',
48
+ value: 'new content',
49
+ isDraft: true
50
+ };
51
+ const requestBody = buildScriptDeployRequestBody('f/demo/script', draft, existing, 'ai deploy');
52
+ expect(requestBody).toMatchObject({
53
+ path: 'f/demo/script',
54
+ parent_hash: 'parent-hash',
55
+ summary: 'draft summary',
56
+ description: 'existing description',
57
+ content: 'new content',
58
+ schema: existing.schema,
59
+ tag: 'node',
60
+ envs: ['ENV_A'],
61
+ concurrent_limit: 3,
62
+ concurrency_time_window_s: 60,
63
+ concurrency_key: 'key',
64
+ debounce_key: 'debounce',
65
+ debounce_delay_s: 5,
66
+ debounce_args_to_accumulate: ['ids'],
67
+ max_total_debouncing_time: 120,
68
+ max_total_debounces_amount: 4,
69
+ cache_ttl: 30,
70
+ cache_ignore_s3_path: true,
71
+ dedicated_worker: true,
72
+ ws_error_handler_muted: true,
73
+ priority: 9,
74
+ restart_unless_cancelled: true,
75
+ timeout: 300,
76
+ delete_after_secs: 600,
77
+ visible_to_runner_only: true,
78
+ auto_kind: 'script',
79
+ codebase: 'repo',
80
+ has_preprocessor: true,
81
+ on_behalf_of_email: 'deployer@example.com',
82
+ preserve_on_behalf_of: true,
83
+ assets: existing.assets,
84
+ modules: existing.modules,
85
+ labels: ['prod'],
86
+ deployment_message: 'ai deploy'
87
+ });
88
+ expect(requestBody.lock).toBeUndefined();
89
+ });
90
+ it('preserves existing flow metadata and uses draft value/schema overrides', () => {
91
+ const existing = {
92
+ path: 'f/demo/flow',
93
+ summary: 'existing summary',
94
+ description: 'existing description',
95
+ value: { modules: [] },
96
+ schema: { required: ['name'] },
97
+ tag: 'python',
98
+ ws_error_handler_muted: true,
99
+ priority: 7,
100
+ dedicated_worker: true,
101
+ timeout: 60,
102
+ visible_to_runner_only: true,
103
+ on_behalf_of_email: 'deployer@example.com',
104
+ labels: ['critical']
105
+ };
106
+ const draftValue = {
107
+ value: { modules: [{ id: 'step', value: { type: 'identity' } }] },
108
+ schema: { properties: { name: { type: 'string' } } },
109
+ groups: [{ start_id: 'step', end_id: 'step', summary: 'Group' }]
110
+ };
111
+ const requestBody = buildFlowDeployRequestBody('f/demo/flow', undefined, draftValue, existing, 'ai deploy');
112
+ expect(requestBody).toMatchObject({
113
+ path: 'f/demo/flow',
114
+ summary: 'existing summary',
115
+ description: 'existing description',
116
+ schema: draftValue.schema,
117
+ tag: 'python',
118
+ ws_error_handler_muted: true,
119
+ priority: 7,
120
+ dedicated_worker: true,
121
+ timeout: 60,
122
+ visible_to_runner_only: true,
123
+ on_behalf_of_email: 'deployer@example.com',
124
+ preserve_on_behalf_of: true,
125
+ labels: ['critical'],
126
+ deployment_message: 'ai deploy'
127
+ });
128
+ expect(requestBody.value.modules).toHaveLength(1);
129
+ expect(requestBody.value.groups).toEqual(draftValue.groups);
130
+ });
131
+ it('falls back to existing flow schema when the draft has no schema', () => {
132
+ const existing = {
133
+ path: 'f/demo/flow',
134
+ summary: 'existing summary',
135
+ value: { modules: [] },
136
+ schema: { properties: { existing: { type: 'boolean' } } }
137
+ };
138
+ const requestBody = buildFlowDeployRequestBody('f/demo/flow', 'draft summary', { value: { modules: [] }, schema: null, groups: null }, existing, undefined);
139
+ expect(requestBody.summary).toBe('draft summary');
140
+ expect(requestBody.schema).toBe(existing.schema);
141
+ });
142
+ });
@@ -0,0 +1,55 @@
1
+ import type { AzureTriggerData, CreateResource, CreateVariable, FlowValue, GcpTriggerData, NewHttpTrigger, NewKafkaTrigger, NewMqttTrigger, NewNatsTrigger, NewPostgresTrigger, NewSchedule, NewSqsTrigger, NewWebsocketTrigger, Policy, ScriptLang } from '../../../../gen/types.gen';
2
+ /**
3
+ * Flow draft value. Mirrors what the backend's create/update flow API expects
4
+ * — the OpenFlow value, plus the inputs schema and (optional) groups.
5
+ *
6
+ * Schema and groups are split out from FlowValue intentionally so that
7
+ * deploy_workspace_item can preserve them through the draft → workspace
8
+ * round-trip; an earlier version dropped them on every deploy.
9
+ */
10
+ export type FlowDraftValue = {
11
+ value: FlowValue;
12
+ schema?: Record<string, any> | null;
13
+ groups?: NonNullable<FlowValue['groups']> | null;
14
+ };
15
+ export declare const TRIGGER_KINDS: readonly ["http", "websocket", "kafka", "nats", "postgres", "mqtt", "sqs", "gcp", "azure"];
16
+ export type TriggerKind = (typeof TRIGGER_KINDS)[number];
17
+ export type TriggerRequestBody = NewHttpTrigger | NewWebsocketTrigger | NewKafkaTrigger | NewNatsTrigger | NewPostgresTrigger | NewMqttTrigger | NewSqsTrigger | GcpTriggerData | AzureTriggerData;
18
+ export type WorkspaceItemType = 'script' | 'flow' | 'schedule' | 'trigger' | 'resource' | 'variable' | 'app';
19
+ export type AppDraftValue = {
20
+ summary?: string;
21
+ files: Record<string, string>;
22
+ runnables: Record<string, any>;
23
+ data?: any;
24
+ policy?: Policy;
25
+ custom_path?: string;
26
+ };
27
+ export type WorkspaceItem = {
28
+ type: WorkspaceItemType;
29
+ path: string;
30
+ summary?: string;
31
+ language?: ScriptLang;
32
+ triggerKind?: TriggerKind;
33
+ value?: string | FlowDraftValue | NewSchedule | TriggerRequestBody | CreateResource | CreateVariable | AppDraftValue;
34
+ isDraft: boolean;
35
+ };
36
+ export declare function getWorkspaceItemKey(type: WorkspaceItemType, path: string, triggerKind?: TriggerKind): string;
37
+ declare class GlobalDraftStore {
38
+ private drafts;
39
+ private getWorkspaceDrafts;
40
+ private ensureWorkspaceDrafts;
41
+ listDrafts(workspace: string): WorkspaceItem[];
42
+ getDraft(workspace: string, type: WorkspaceItemType, path: string, triggerKind?: TriggerKind): WorkspaceItem | undefined;
43
+ setDraft(workspace: string, item: WorkspaceItem): WorkspaceItem;
44
+ deleteDraft(workspace: string, type: WorkspaceItemType, path: string, triggerKind?: TriggerKind): void;
45
+ clearDrafts(workspace: string): void;
46
+ getScriptDraft(workspace: string, path: string): WorkspaceItem | undefined;
47
+ getFlowDraft(workspace: string, path: string): WorkspaceItem | undefined;
48
+ getScheduleDraft(workspace: string, path: string): WorkspaceItem | undefined;
49
+ getTriggerDraft(workspace: string, kind: TriggerKind, path: string): WorkspaceItem | undefined;
50
+ getResourceDraft(workspace: string, path: string): WorkspaceItem | undefined;
51
+ getVariableDraft(workspace: string, path: string): WorkspaceItem | undefined;
52
+ getAppDraft(workspace: string, path: string): WorkspaceItem | undefined;
53
+ }
54
+ export declare const globalDraftStore: GlobalDraftStore;
55
+ export {};
@@ -0,0 +1,78 @@
1
+ export const TRIGGER_KINDS = [
2
+ 'http',
3
+ 'websocket',
4
+ 'kafka',
5
+ 'nats',
6
+ 'postgres',
7
+ 'mqtt',
8
+ 'sqs',
9
+ 'gcp',
10
+ 'azure'
11
+ ];
12
+ export function getWorkspaceItemKey(type, path, triggerKind) {
13
+ if (type === 'trigger') {
14
+ return `trigger:${triggerKind ?? ''}:${path}`;
15
+ }
16
+ return `${type}:${path}`;
17
+ }
18
+ function clone(value) {
19
+ return structuredClone($state.snapshot(value));
20
+ }
21
+ class GlobalDraftStore {
22
+ drafts = $state({});
23
+ getWorkspaceDrafts(workspace) {
24
+ return this.drafts[workspace] ?? {};
25
+ }
26
+ ensureWorkspaceDrafts(workspace) {
27
+ if (!this.drafts[workspace]) {
28
+ this.drafts[workspace] = {};
29
+ }
30
+ return this.drafts[workspace];
31
+ }
32
+ listDrafts(workspace) {
33
+ return Object.values(this.getWorkspaceDrafts(workspace)).map(clone);
34
+ }
35
+ getDraft(workspace, type, path, triggerKind) {
36
+ const draft = this.getWorkspaceDrafts(workspace)[getWorkspaceItemKey(type, path, triggerKind)];
37
+ return draft ? clone(draft) : undefined;
38
+ }
39
+ setDraft(workspace, item) {
40
+ const stored = { ...clone(item), isDraft: true };
41
+ this.ensureWorkspaceDrafts(workspace)[getWorkspaceItemKey(item.type, item.path, item.triggerKind)] = stored;
42
+ return clone(stored);
43
+ }
44
+ deleteDraft(workspace, type, path, triggerKind) {
45
+ const drafts = this.drafts[workspace];
46
+ if (!drafts)
47
+ return;
48
+ delete drafts[getWorkspaceItemKey(type, path, triggerKind)];
49
+ if (Object.keys(drafts).length === 0) {
50
+ delete this.drafts[workspace];
51
+ }
52
+ }
53
+ clearDrafts(workspace) {
54
+ delete this.drafts[workspace];
55
+ }
56
+ getScriptDraft(workspace, path) {
57
+ return this.getDraft(workspace, 'script', path);
58
+ }
59
+ getFlowDraft(workspace, path) {
60
+ return this.getDraft(workspace, 'flow', path);
61
+ }
62
+ getScheduleDraft(workspace, path) {
63
+ return this.getDraft(workspace, 'schedule', path);
64
+ }
65
+ getTriggerDraft(workspace, kind, path) {
66
+ return this.getDraft(workspace, 'trigger', path, kind);
67
+ }
68
+ getResourceDraft(workspace, path) {
69
+ return this.getDraft(workspace, 'resource', path);
70
+ }
71
+ getVariableDraft(workspace, path) {
72
+ return this.getDraft(workspace, 'variable', path);
73
+ }
74
+ getAppDraft(workspace, path) {
75
+ return this.getDraft(workspace, 'app', path);
76
+ }
77
+ }
78
+ export const globalDraftStore = new GlobalDraftStore();
@@ -0,0 +1,44 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { globalDraftStore } from './draftStore.svelte';
3
+ const WORKSPACE_A = 'draft-store-test-a';
4
+ const WORKSPACE_B = 'draft-store-test-b';
5
+ function clearTestDrafts() {
6
+ globalDraftStore.clearDrafts(WORKSPACE_A);
7
+ globalDraftStore.clearDrafts(WORKSPACE_B);
8
+ }
9
+ describe('globalDraftStore', () => {
10
+ beforeEach(clearTestDrafts);
11
+ it('lists and reads drafts only from the requested workspace', () => {
12
+ globalDraftStore.setDraft(WORKSPACE_A, {
13
+ type: 'script',
14
+ path: 'f/shared/path',
15
+ language: 'bun',
16
+ value: 'export async function main() {}',
17
+ isDraft: true
18
+ });
19
+ expect(globalDraftStore.getDraft(WORKSPACE_A, 'script', 'f/shared/path')?.value).toBe('export async function main() {}');
20
+ expect(globalDraftStore.getDraft(WORKSPACE_B, 'script', 'f/shared/path')).toBeUndefined();
21
+ expect(globalDraftStore.listDrafts(WORKSPACE_A)).toHaveLength(1);
22
+ expect(globalDraftStore.listDrafts(WORKSPACE_B)).toHaveLength(0);
23
+ });
24
+ it('deletes and clears drafts only from the requested workspace', () => {
25
+ globalDraftStore.setDraft(WORKSPACE_A, {
26
+ type: 'flow',
27
+ path: 'f/shared/path',
28
+ value: { value: { modules: [] }, schema: null, groups: null },
29
+ isDraft: true
30
+ });
31
+ globalDraftStore.setDraft(WORKSPACE_B, {
32
+ type: 'flow',
33
+ path: 'f/shared/path',
34
+ value: { value: { modules: [] }, schema: { workspace: WORKSPACE_B }, groups: null },
35
+ isDraft: true
36
+ });
37
+ globalDraftStore.deleteDraft(WORKSPACE_A, 'flow', 'f/shared/path');
38
+ expect(globalDraftStore.getDraft(WORKSPACE_A, 'flow', 'f/shared/path')).toBeUndefined();
39
+ expect(globalDraftStore.getDraft(WORKSPACE_B, 'flow', 'f/shared/path')).toBeDefined();
40
+ globalDraftStore.clearDrafts(WORKSPACE_B);
41
+ expect(globalDraftStore.listDrafts(WORKSPACE_A)).toHaveLength(0);
42
+ expect(globalDraftStore.listDrafts(WORKSPACE_B)).toHaveLength(0);
43
+ });
44
+ });
@@ -0,0 +1 @@
1
+ export declare function isGlobalAiEnabled(): boolean;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Dev-only gate for the Global AI chat mode.
3
+ *
4
+ * While the mode is being iterated on, it should not be visible to regular
5
+ * users. Developers/QA enable it in their browser with:
6
+ *
7
+ * localStorage.setItem('wm_dev_global_ai', '1')
8
+ *
9
+ * and reload the page. To disable, remove the key or set it to anything else.
10
+ *
11
+ * When the mode is ready to ship to everyone, replace every call to
12
+ * `isGlobalAiEnabled()` with `true` and delete this file. The references are
13
+ * intentionally narrow (chat mode visibility, custom prompt settings, the
14
+ * `change_mode` tool enum, and the `/global_drafts` dev route) so the rip-out
15
+ * is a small grep.
16
+ */
17
+ const STORAGE_KEY = 'wm_dev_global_ai';
18
+ export function isGlobalAiEnabled() {
19
+ if (typeof localStorage === 'undefined')
20
+ return false;
21
+ try {
22
+ return localStorage.getItem(STORAGE_KEY) === '1';
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
@@ -25,6 +25,20 @@ export interface ContextStringResult {
25
25
  hasDiff: boolean;
26
26
  hasFlowModule: boolean;
27
27
  }
28
+ /** Count exact occurrences of `search` in `content`. */
29
+ export declare function countExactMatches(content: string, search: string): number;
30
+ /**
31
+ * Replace exact occurrences of `oldString` with `newString` in `content`.
32
+ * When `replaceAll` is false, only the first match is replaced. Returns the
33
+ * original string unchanged when no match is found.
34
+ */
35
+ export declare function applyExactReplace(content: string, oldString: string, newString: string, replaceAll: boolean): string;
36
+ /**
37
+ * Match-count-validated exact text replacement. Throws when `oldString` is
38
+ * missing, and (unless `replaceAll`) when it appears more than once.
39
+ * `contextLabel` flows into the error message ("not found in the <label>.").
40
+ */
41
+ export declare function findAndReplace(content: string, oldString: string, newString: string, replaceAll: boolean, contextLabel: string): string;
28
42
  export declare const extractAllModules: (modules: FlowModule[]) => FlowModule[];
29
43
  export declare function applyCodePiecesToFlowModules(codePieces: FlowModuleCodePieceElement[], flowModules: FlowModule[]): FlowModule[];
30
44
  export declare function buildContextString(selectedContext: ContextElement[]): string;
@@ -49,9 +63,9 @@ export type CreatedResourceAction = {
49
63
  id: string;
50
64
  type: 'open_created_resource';
51
65
  label: string;
52
- resource: 'schedule' | 'trigger';
66
+ resource: 'schedule' | 'trigger' | 'resource' | 'variable';
53
67
  path: string;
54
- targetKind: 'script' | 'flow';
68
+ targetKind?: 'script' | 'flow';
55
69
  triggerKind?: CreatedResourceTriggerKind;
56
70
  };
57
71
  export type ToolDisplayAction = CreatedResourceAction;
@@ -197,6 +197,46 @@ export const TOOL_PRETTIFY_MAP = {
197
197
  add_module: prettifyModuleValue,
198
198
  modify_module: prettifyModuleValue
199
199
  };
200
+ /** Count exact occurrences of `search` in `content`. */
201
+ export function countExactMatches(content, search) {
202
+ if (search.length === 0)
203
+ return 0;
204
+ let count = 0;
205
+ let index = 0;
206
+ while ((index = content.indexOf(search, index)) !== -1) {
207
+ count++;
208
+ index += search.length;
209
+ }
210
+ return count;
211
+ }
212
+ /**
213
+ * Replace exact occurrences of `oldString` with `newString` in `content`.
214
+ * When `replaceAll` is false, only the first match is replaced. Returns the
215
+ * original string unchanged when no match is found.
216
+ */
217
+ export function applyExactReplace(content, oldString, newString, replaceAll) {
218
+ if (replaceAll)
219
+ return content.split(oldString).join(newString);
220
+ const index = content.indexOf(oldString);
221
+ if (index === -1)
222
+ return content;
223
+ return content.slice(0, index) + newString + content.slice(index + oldString.length);
224
+ }
225
+ /**
226
+ * Match-count-validated exact text replacement. Throws when `oldString` is
227
+ * missing, and (unless `replaceAll`) when it appears more than once.
228
+ * `contextLabel` flows into the error message ("not found in the <label>.").
229
+ */
230
+ export function findAndReplace(content, oldString, newString, replaceAll, contextLabel) {
231
+ const matchCount = countExactMatches(content, oldString);
232
+ if (matchCount === 0) {
233
+ throw new Error(`old_string was not found in the ${contextLabel}.`);
234
+ }
235
+ if (!replaceAll && matchCount !== 1) {
236
+ throw new Error(`old_string matched ${matchCount} locations. Make it more specific or set replace_all to true.`);
237
+ }
238
+ return applyExactReplace(content, oldString, newString, replaceAll);
239
+ }
200
240
  export const extractAllModules = (modules) => {
201
241
  return modules.flatMap((m) => {
202
242
  if (m.value.type === 'forloopflow' || m.value.type === 'whileloopflow') {