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.
- package/dist/appPolicy/myFunction.es.js +1337 -0
- package/dist/sharedUtils/common.d.ts +2 -5
- package/dist/sharedUtils/components/apps/components/display/dbtable/queries/select.d.ts +0 -2
- package/dist/sharedUtils/components/apps/components/display/dbtable/utils.d.ts +3 -14
- package/dist/sharedUtils/components/apps/editor/appPolicy.d.ts +1 -1
- package/dist/sharedUtils/components/apps/editor/appUtilsS3.d.ts +1 -12
- package/dist/sharedUtils/components/apps/editor/component/components.d.ts +2 -68
- package/dist/sharedUtils/components/apps/inputType.d.ts +2 -4
- package/dist/sharedUtils/components/apps/sharedTypes.d.ts +0 -2
- package/dist/sharedUtils/components/dbTypes.d.ts +0 -3
- package/dist/sharedUtils/components/raw_apps/rawAppPolicy.d.ts +1 -1
- package/dist/sharedUtils/components/raw_apps/utils.d.ts +1 -1
- package/dist/sharedUtils/components/triggers/utils.d.ts +3 -2
- package/dist/sharedUtils/gen/schemas.gen.d.ts +71 -915
- package/dist/sharedUtils/gen/services.gen.d.ts +23 -329
- package/dist/sharedUtils/gen/types.gen.d.ts +141 -1870
- package/dist/sharedUtils/hub.d.ts +0 -1
- package/dist/sharedUtils/jsr.json +5 -5
- package/dist/sharedUtils/lib.d.ts +1 -1
- package/dist/sharedUtils/lib.es.js +79 -241
- package/dist/sharedUtils/package.json +11 -11
- package/dist/sharedUtils/stores.d.ts +0 -1
- package/dist/sharedUtils/svelte5Utils.svelte.d.ts +1 -32
- package/dist/sharedUtils/utils.d.ts +4 -19
- package/package/components/DisplayResultControlBar.svelte +26 -11
- package/package/components/JobArgs.svelte +43 -24
- package/package/components/ShareModal.svelte.d.ts +1 -1
- package/package/components/apps/components/helpers/RunnableComponent.svelte.d.ts +0 -3
- package/package/components/copilot/CustomAIPrompts.svelte +3 -2
- package/package/components/copilot/chat/AIChatInput.svelte +2 -0
- package/package/components/copilot/chat/AIChatManager.svelte.js +52 -14
- package/package/components/copilot/chat/CreatedResourceActionDrawers.svelte +15 -0
- package/package/components/copilot/chat/ToolMessageActions.svelte +4 -2
- package/package/components/copilot/chat/app/core.js +2 -30
- package/package/components/copilot/chat/flow/core.js +13 -351
- package/package/components/copilot/chat/flow/editableFlowJson.d.ts +52 -0
- package/package/components/copilot/chat/flow/editableFlowJson.js +328 -0
- package/package/components/copilot/chat/flow/inlineScriptsUtils.js +2 -2
- package/package/components/copilot/chat/global/core.d.ts +5 -0
- package/package/components/copilot/chat/global/core.js +1739 -0
- package/package/components/copilot/chat/global/core.test.d.ts +1 -0
- package/package/components/copilot/chat/global/core.test.js +123 -0
- package/package/components/copilot/chat/global/deployRequests.d.ts +7 -0
- package/package/components/copilot/chat/global/deployRequests.js +76 -0
- package/package/components/copilot/chat/global/deployRequests.test.d.ts +1 -0
- package/package/components/copilot/chat/global/deployRequests.test.js +142 -0
- package/package/components/copilot/chat/global/draftStore.svelte.d.ts +55 -0
- package/package/components/copilot/chat/global/draftStore.svelte.js +78 -0
- package/package/components/copilot/chat/global/draftStore.test.d.ts +1 -0
- package/package/components/copilot/chat/global/draftStore.test.js +44 -0
- package/package/components/copilot/chat/global/gate.d.ts +1 -0
- package/package/components/copilot/chat/global/gate.js +27 -0
- package/package/components/copilot/chat/shared.d.ts +16 -2
- package/package/components/copilot/chat/shared.js +40 -0
- package/package/components/copilot/chat/workspaceToolsZod.gen.d.ts +28 -9
- package/package/components/copilot/chat/workspaceToolsZod.gen.js +19 -0
- package/package/components/raw_apps/templates.d.ts +77 -0
- package/package/components/raw_apps/templates.js +618 -0
- package/package/components/runs/runsFilter.d.ts +1 -1
- package/package/components/settings/AIPromptsModal.svelte +5 -2
- package/package/components/triggers/azure/AzureTriggerEditorConfigSection.svelte.d.ts +1 -1
- package/package/gen/core/OpenAPI.js +1 -1
- package/package/gen/schemas.gen.d.ts +37 -355
- package/package/gen/schemas.gen.js +39 -359
- package/package/gen/services.gen.d.ts +4 -280
- package/package/gen/services.gen.js +7 -565
- package/package/gen/types.gen.d.ts +77 -1135
- package/package/system_prompts/index.d.ts +2 -0
- package/package/system_prompts/index.js +8 -0
- package/package/system_prompts/prompts.d.ts +2 -0
- package/package/system_prompts/prompts.js +381 -0
- package/package/utils_deployable.d.ts +5 -318
- package/package.json +1 -1
- package/dist/sharedUtils/components/assets/lib.d.ts +0 -25
- package/dist/sharedUtils/components/icons/index.d.ts +0 -101
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
|
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') {
|