mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 16:00:38 +00:00
Merge branch 'main' into dieri/sharding-v0
This commit is contained in:
@@ -54,7 +54,7 @@ jobs:
|
||||
toolchain: 1.90.0
|
||||
- name: Read EE repo commit hash
|
||||
run: |
|
||||
echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_ENV"
|
||||
echo "ee_repo_ref=$(cat ./ee-repo-ref.txt)" >> "$GITHUB_ENV"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# Changelog
|
||||
|
||||
## [1.555.1](https://github.com/windmill-labs/windmill/compare/v1.555.0...v1.555.1) (2025-10-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* app button tooltip also when disabled + audit log filters ([#6751](https://github.com/windmill-labs/windmill/issues/6751)) ([605c552](https://github.com/windmill-labs/windmill/commit/605c5526f83d3b985f04570039bb4671cc5912f4))
|
||||
* init git repo preview save ([#6753](https://github.com/windmill-labs/windmill/issues/6753)) ([d9c01e0](https://github.com/windmill-labs/windmill/commit/d9c01e0c0392f13e52ad92f87c4216e64e89a3bc))
|
||||
|
||||
## [1.555.0](https://github.com/windmill-labs/windmill/compare/v1.554.1...v1.555.0) (2025-10-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* end user email env var ([#6750](https://github.com/windmill-labs/windmill/issues/6750)) ([3907c9f](https://github.com/windmill-labs/windmill/commit/3907c9f9512ebd73daf0a2f3ee2e8db6fb9f4df6))
|
||||
* **flow:** add option to turn flow into chat ([#6658](https://github.com/windmill-labs/windmill/issues/6658)) ([047420e](https://github.com/windmill-labs/windmill/commit/047420e5ad7b6178291bc7ed75d029794760d18b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** prevent s3 file upload infinite loop ([#6742](https://github.com/windmill-labs/windmill/issues/6742)) ([6d436d7](https://github.com/windmill-labs/windmill/commit/6d436d745994f954a37f33ca2cc2e9f0801b16b9))
|
||||
* show that user is disabled in workspacelist ([#6748](https://github.com/windmill-labs/windmill/issues/6748)) ([c658f32](https://github.com/windmill-labs/windmill/commit/c658f321d68e2d72622d9d167b20cac67364651c))
|
||||
* top level assigment doesn't propagate to setContext ([#6745](https://github.com/windmill-labs/windmill/issues/6745)) ([06b152b](https://github.com/windmill-labs/windmill/commit/06b152b295cd4892d7309651d382a05cdcf7d378))
|
||||
|
||||
## [1.554.1](https://github.com/windmill-labs/windmill/compare/v1.554.0...v1.554.1) (2025-10-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** concurrency limits preprocessor ([#6727](https://github.com/windmill-labs/windmill/issues/6727)) ([cdb7524](https://github.com/windmill-labs/windmill/commit/cdb75241188ee0a6d7bc62ca6dd639606eef426a))
|
||||
|
||||
## [1.554.0](https://github.com/windmill-labs/windmill/compare/v1.553.0...v1.554.0) (2025-10-01)
|
||||
|
||||
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT data FROM app_bundles WHERE app_version_id = $1 AND file_type = $2 AND w_id = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "data",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "01050e7057f3d1971ad9e47ac83bf6a3c3c9f41689c3607f0b264437ae6b3324"
|
||||
}
|
||||
+5
-5
@@ -46,11 +46,11 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE flow_conversation_message\n SET content = $1\n WHERE job_id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "20888e946a5597989488e2a54e354e4ce2872f2bc3af7b26fcaafe8703ba70bf"
|
||||
}
|
||||
+9
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n v2_job_queue.workspace_id,\n v2_job_queue.id,\n v2_job.args as \"args: sqlx::types::Json<HashMap<String, Box<RawValue>>>\",\n v2_job.parent_job,\n v2_job.created_by,\n v2_job_queue.started_at,\n scheduled_for,\n runnable_path,\n kind as \"kind: JobKind\",\n runnable_id as \"runnable_id: ScriptHash\",\n canceled_reason,\n canceled_by,\n permissioned_as,\n permissioned_as_email,\n flow_status as \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n v2_job.tag,\n script_lang as \"script_lang: ScriptLang\",\n same_worker,\n pre_run_error,\n concurrent_limit,\n concurrency_time_window_s,\n flow_innermost_root_job,\n root_job,\n timeout,\n flow_step_id,\n cache_ttl,\n v2_job_queue.priority,\n preprocessed,\n script_entrypoint_override,\n trigger,\n trigger_kind as \"trigger_kind: JobTriggerKind\",\n visible_to_owner\n FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id WHERE v2_job_queue.id = $1",
|
||||
"query": "SELECT \n v2_job_queue.workspace_id,\n v2_job_queue.id,\n v2_job.args as \"args: sqlx::types::Json<HashMap<String, Box<RawValue>>>\",\n v2_job.parent_job,\n v2_job.created_by,\n v2_job_queue.started_at,\n scheduled_for,\n runnable_path,\n kind as \"kind: JobKind\",\n runnable_id as \"runnable_id: ScriptHash\",\n canceled_reason,\n canceled_by,\n permissioned_as,\n permissioned_as_email,\n flow_status as \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n v2_job.tag,\n script_lang as \"script_lang: ScriptLang\",\n same_worker,\n pre_run_error,\n concurrent_limit,\n concurrency_time_window_s,\n flow_innermost_root_job,\n root_job,\n timeout,\n flow_step_id,\n cache_ttl,\n v2_job_queue.priority,\n preprocessed,\n script_entrypoint_override,\n trigger,\n trigger_kind as \"trigger_kind: JobTriggerKind\",\n visible_to_owner,\n NULL as permissioned_as_end_user_email\n FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id WHERE v2_job_queue.id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -241,6 +241,11 @@
|
||||
"ordinal": 31,
|
||||
"name": "visible_to_owner",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"name": "permissioned_as_end_user_email",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -280,8 +285,9 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "f3f96e066716e61042519a645d487b578bc63792cdb0f7ddaeb82e9771287c22"
|
||||
"hash": "3162ec92bb32af47a71cc41172cc740b5dea1304ce4dfdb4d3d0efa4266f38c5"
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, conversation_id, message_type as \"message_type: MessageType\", content, job_id, created_at\n FROM (\n SELECT id, conversation_id, message_type, content, job_id, created_at\n FROM flow_conversation_message\n WHERE conversation_id = $1\n ORDER BY created_at DESC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END\n LIMIT $2 OFFSET $3\n ) AS messages\n ORDER BY created_at ASC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "conversation_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "message_type: MessageType",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "message_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"user",
|
||||
"assistant"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "content",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "job_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5f57ec3330a6576f4017fd469848a1c214896dc5abbf2b898ac9a68810fd89dd"
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO flow_conversation_message (conversation_id, message_type, content, job_id)\n VALUES ($1, $2, $3, $4)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "message_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"user",
|
||||
"assistant"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Text",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "69606859fe08d24f0306b866f9f50ad766d56a0d5aa8f0784f79aa5e211b00e4"
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO flow_conversation (id, workspace_id, flow_path, created_by, title)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING id, workspace_id, flow_path, title, created_at, updated_at, created_by",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "flow_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "title",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "updated_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6bd23a98838e3eec309e6b696edc776bd56fc9dae1238b3272557d1562400dbe"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT (flow_status->>'memory_id')::uuid as memory_id \n FROM v2_job_status \n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "memory_id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "7628e7690aaf6da3dba3cf78dc9e78fe32c3a001c08920e02fb850695afdb7ec"
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO script (summary, description, dedicated_worker, content, workspace_id, path, hash, language, tag, created_by, lock) VALUES ('', '', true, $1, $2, $3, $4, $5, $6, $7, '') ON CONFLICT (workspace_id, hash) DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "script_lang",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
"snowflake",
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative",
|
||||
"rust",
|
||||
"ansible",
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM flow_conversation WHERE id = $1 AND workspace_id = $2)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "81e997576319ae1d6d9a91f76465f6fa53892ff223f3b9d9ad3f4a1d2e720cc8"
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "85705fc3d7f8ba5f1b12d5fb222c38fc64deb1226aab9dc3bc4465324fce37d1"
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id) \n values ($1, $32, $33, $34, $35, $36, $37, $2) \n ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31)",
|
||||
"query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email) \n values ($1, $32, $33, $34, $35, $36, $37, $2, $41) \n ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -123,10 +123,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Bool"
|
||||
"Bool",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "193d292c5ed44bf5266ad52c83704c3a36aa284fab3b7e638dbca12ac846b82b"
|
||||
"hash": "8d67ed8e1271a27b072a8e8ae9973e372949f5ec7d71b80d1ba3eaddf851adce"
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a0b3e10e077d30c1da135dff9feca3761d400391f1f46a8294da3e6c9af63887"
|
||||
}
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, 1)) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Varchar",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "job_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies",
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies",
|
||||
"deploymentcallback",
|
||||
"singlescriptflow",
|
||||
"flowscript",
|
||||
"flownode",
|
||||
"appscript",
|
||||
"aiagent"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Custom": {
|
||||
"name": "script_lang",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
"snowflake",
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative",
|
||||
"rust",
|
||||
"ansible",
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338"
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO app_bundles (app_version_id, w_id, file_type, data) VALUES ($1, $2, $3, $4)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "abaae3dde751a41b2dbb7856ece1c840d0ea8d59346ed9e88f6f609edb543d7e"
|
||||
}
|
||||
+2
-2
@@ -18,8 +18,8 @@
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE flow_conversation SET updated_at = NOW() WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "bdb1ece5c233f242cf341c089a2f2b785dfa5cc14d9be224c0707e10247ed8b7"
|
||||
}
|
||||
+9
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color, workspace.parent_workspace_id,\n CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false",
|
||||
"query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color, workspace.parent_workspace_id,\n CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings,\n usr.disabled\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -32,6 +32,11 @@
|
||||
"ordinal": 5,
|
||||
"name": "operator_settings",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "disabled",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -45,8 +50,9 @@
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
null
|
||||
null,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d0037961e8e787c4277afc3eb79f3b72e3323f878387de8b6fa31493f1215a77"
|
||||
"hash": "c095a9658c542efc9f0255a1b536d2fd8a25fe4cd57c223db7d744493f8470c6"
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, workspace_id, flow_path, title, created_at, updated_at, created_by\n FROM flow_conversation\n WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "flow_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "title",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "updated_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c383cc023714b361d10c10e8fef1fc148ab1da942951ee9ffdddaecee76a6be9"
|
||||
}
|
||||
+10
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, on_behalf_of_email, edited_by, flow_version.id AS version\n FROM flow\n INNER JOIN flow_version\n ON flow_version.id = $3\n WHERE flow.path = $1 and flow.workspace_id = $2",
|
||||
"query": "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, on_behalf_of_email, edited_by, flow_version.id AS version\n FROM flow\n INNER JOIN flow_version\n ON flow_version.id = $3\n WHERE flow.path = $1 and flow.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,16 +25,21 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "chat_input_enabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "on_behalf_of_email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 6,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 7,
|
||||
"name": "version",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
@@ -51,10 +56,11 @@
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9b60fa8a1003015bc5a7cdbee9a4486b313d45347dfd9d4793d60e2760763ca3"
|
||||
"hash": "e1409c67b93881cf68f6ac9c8bae0856cf426c7e7860c8b5b799972baa8e6945"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM flow_conversation_message WHERE job_id = $1 AND message_type = 'assistant')",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "e2f4eeb896c22e3215370ed30ed5cc890503e4197b0fdb3a906504eba166aa97"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM flow_conversation WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e6c4454e552dc82db1af2d6b887ebb4b78eb58600cc21e3ffb12dd9e5a0a6f08"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE v2_job_status \n SET flow_status = jsonb_set(\n flow_status,\n '{memory_id}',\n to_jsonb($2::uuid)\n )\n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f5568a691ec5931634cf986f806f5eae7bb8ed0f5c6e54ca3f49a991c53ed50d"
|
||||
}
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT hash FROM script WHERE path = $1 AND workspace_id = $2",
|
||||
"query": "SELECT app.versions[array_upper(app.versions, 1)] FROM app\n WHERE app.path = $1 AND app.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "hash",
|
||||
"name": "versions",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
@@ -16,8 +16,8 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "16e4b1bead9fc77fd98658b8cb8cc6d6bf1df758b30e99bd661da866062ef14f"
|
||||
"hash": "fad966db585b91c9ce143c9aa26a826aec1ddb193a7f4988c5f12b1a2d8ce071"
|
||||
}
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Varchar",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "job_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies",
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies",
|
||||
"deploymentcallback",
|
||||
"singlescriptflow",
|
||||
"flowscript",
|
||||
"flownode",
|
||||
"appscript",
|
||||
"aiagent"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Custom": {
|
||||
"name": "script_lang",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
"snowflake",
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative",
|
||||
"rust",
|
||||
"ansible",
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238"
|
||||
}
|
||||
Generated
+115
-110
@@ -169,9 +169,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.20"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -779,9 +779,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-credential-types"
|
||||
version = "1.2.6"
|
||||
version = "1.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d025db5d9f52cbc413b167136afb3d8aeea708c0d8884783cf6253be5e22f6f2"
|
||||
checksum = "799a1290207254984cb7c05245111bc77958b92a3c9bb449598044b36341cce6"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-runtime-api",
|
||||
@@ -1763,18 +1763,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.23.2"
|
||||
version = "1.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677"
|
||||
checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.10.1"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29"
|
||||
checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1935,9 +1935,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.39"
|
||||
version = "1.2.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f"
|
||||
checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -2674,7 +2674,7 @@ dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core 0.9.11",
|
||||
"parking_lot_core 0.9.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2688,7 +2688,7 @@ dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core 0.9.11",
|
||||
"parking_lot_core 0.9.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2745,7 +2745,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"object_store",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"parquet",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
@@ -2780,7 +2780,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"object_store",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -2953,7 +2953,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"object_store",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"parquet",
|
||||
"rand 0.8.5",
|
||||
"tokio",
|
||||
@@ -2978,7 +2978,7 @@ dependencies = [
|
||||
"futures",
|
||||
"log",
|
||||
"object_store",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"rand 0.8.5",
|
||||
"tempfile",
|
||||
"url",
|
||||
@@ -3114,7 +3114,7 @@ dependencies = [
|
||||
"datafusion-common",
|
||||
"datafusion-expr",
|
||||
"datafusion-physical-plan",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"paste",
|
||||
]
|
||||
|
||||
@@ -3255,7 +3255,7 @@ dependencies = [
|
||||
"indexmap 2.11.1",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
@@ -3280,7 +3280,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"object_store",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -3409,7 +3409,7 @@ dependencies = [
|
||||
"indexmap 2.11.1",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
@@ -3491,7 +3491,7 @@ dependencies = [
|
||||
"indexmap 2.11.1",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"serde",
|
||||
@@ -3736,7 +3736,7 @@ dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"os_pipe",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"pin-project",
|
||||
"rand 0.8.5",
|
||||
"tokio",
|
||||
@@ -4116,7 +4116,7 @@ dependencies = [
|
||||
"log",
|
||||
"node_resolver",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"sys_traits",
|
||||
"thiserror 2.0.17",
|
||||
"url",
|
||||
@@ -4273,7 +4273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6742a724e8becb372a74c650a1aefb8924a5b8107f7d75b3848763ea24b27a87"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -4821,13 +4821,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dyn-stack"
|
||||
version = "0.13.0"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490bd48eb68fffcfed519b4edbfd82c69cbe741d175b84f0e0cbe8c57cbe0bdd"
|
||||
checksum = "1c4713e43e2886ba72b8271aa66c93d722116acf7a75555cce11dcde84388fe8"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"dyn-stack-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-stack-macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05dbec7076f432bb132db738df90d87a4f5789e99f59e7b1219a6b8ef61eaa68"
|
||||
|
||||
[[package]]
|
||||
name = "dynasm"
|
||||
version = "1.2.3"
|
||||
@@ -5264,9 +5271,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
|
||||
checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
@@ -5499,7 +5506,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"lock_api",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5613,7 +5620,7 @@ version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab96b703d31950f1aeddded248bc95543c9efc7ac9c4a21fda8703a83ee35451"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"dyn-stack 0.13.2",
|
||||
"gemm-c32 0.18.2",
|
||||
"gemm-c64 0.18.2",
|
||||
"gemm-common 0.18.2",
|
||||
@@ -5648,7 +5655,7 @@ version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6db9fd9f40421d00eea9dd0770045a5603b8d684654816637732463f4073847"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"dyn-stack 0.13.2",
|
||||
"gemm-common 0.18.2",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
@@ -5678,7 +5685,7 @@ version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfcad8a3d35a43758330b635d02edad980c1e143dc2f21e6fd25f9e4eada8edf"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"dyn-stack 0.13.2",
|
||||
"gemm-common 0.18.2",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
@@ -5714,7 +5721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a352d4a69cbe938b9e2a9cb7a3a63b7e72f9349174a2752a558a8a563510d0f3"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"dyn-stack 0.13.0",
|
||||
"dyn-stack 0.13.2",
|
||||
"half",
|
||||
"libm",
|
||||
"num-complex",
|
||||
@@ -5752,7 +5759,7 @@ version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff95ae3259432f3c3410eaa919033cd03791d81cebd18018393dc147952e109"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"dyn-stack 0.13.2",
|
||||
"gemm-common 0.18.2",
|
||||
"gemm-f32 0.18.2",
|
||||
"half",
|
||||
@@ -5785,7 +5792,7 @@ version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc8d3d4385393304f407392f754cd2dc4b315d05063f62cf09f47b58de276864"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"dyn-stack 0.13.2",
|
||||
"gemm-common 0.18.2",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
@@ -5815,7 +5822,7 @@ version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35b2a4f76ce4b8b16eadc11ccf2e083252d8237c1b589558a49b0183545015bd"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"dyn-stack 0.13.2",
|
||||
"gemm-common 0.18.2",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
@@ -6390,7 +6397,7 @@ dependencies = [
|
||||
"ipconfig",
|
||||
"moka",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"rand 0.9.0",
|
||||
"resolv-conf",
|
||||
"serde",
|
||||
@@ -7461,7 +7468,7 @@ dependencies = [
|
||||
"json-patch",
|
||||
"k8s-openapi",
|
||||
"kube-client",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"pin-project",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -7631,11 +7638,11 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "libproc"
|
||||
version = "0.14.10"
|
||||
version = "0.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78a09b56be5adbcad5aa1197371688dc6bb249a26da3bca2011ee2fb987ebfb"
|
||||
checksum = "a54ad7278b8bc5301d5ffd2a94251c004feb971feba96c971ea4063645990757"
|
||||
dependencies = [
|
||||
"bindgen 0.70.1",
|
||||
"bindgen 0.72.1",
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
@@ -7648,7 +7655,7 @@ checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"libc",
|
||||
"redox_syscall 0.5.17",
|
||||
"redox_syscall 0.5.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7736,11 +7743,10 @@ checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.13"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
@@ -8192,7 +8198,7 @@ dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"equivalent",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"portable-atomic",
|
||||
"rustc_version 0.4.1",
|
||||
"smallvec",
|
||||
@@ -8912,7 +8918,7 @@ dependencies = [
|
||||
"hyper 1.7.0",
|
||||
"itertools 0.14.0",
|
||||
"md-5 0.10.6",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"percent-encoding",
|
||||
"quick-xml 0.37.5",
|
||||
"rand 0.9.0",
|
||||
@@ -9344,12 +9350,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.4"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core 0.9.11",
|
||||
"parking_lot_core 0.9.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9368,15 +9374,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.11"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.5.17",
|
||||
"redox_syscall 0.5.18",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9502,20 +9508,19 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.2"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8"
|
||||
checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror 2.0.17",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.8.2"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663"
|
||||
checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
@@ -9523,9 +9528,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.8.2"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f"
|
||||
checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
@@ -9536,9 +9541,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.8.2"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420"
|
||||
checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"sha2 0.10.9",
|
||||
@@ -10042,7 +10047,7 @@ dependencies = [
|
||||
"fnv",
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
@@ -10100,9 +10105,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.26"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f"
|
||||
checksum = "e66fcd288453b748497d8fb18bccc83a16b0518e3906d4b8df0a8d42d93dbb1c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@@ -10210,7 +10215,7 @@ dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10518,9 +10523,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.17"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
]
|
||||
@@ -11117,7 +11122,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"ring 0.17.14",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.103.6",
|
||||
"rustls-webpki 0.103.7",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -11222,9 +11227,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.6"
|
||||
version = "0.103.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb"
|
||||
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring 0.17.14",
|
||||
@@ -12444,9 +12449,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.21"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b"
|
||||
checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
@@ -13592,7 +13597,7 @@ dependencies = [
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio 1.0.4",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
@@ -13657,7 +13662,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"log",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"percent-encoding",
|
||||
"phf 0.11.3",
|
||||
"pin-project-lite",
|
||||
@@ -13683,7 +13688,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"log",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"percent-encoding",
|
||||
"phf 0.11.3",
|
||||
"pin-project-lite",
|
||||
@@ -13974,7 +13979,7 @@ dependencies = [
|
||||
"cookie 0.18.1",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
@@ -14278,9 +14283,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "typetag"
|
||||
@@ -14978,7 +14983,7 @@ dependencies = [
|
||||
"log",
|
||||
"naga",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"ron",
|
||||
@@ -15020,7 +15025,7 @@ dependencies = [
|
||||
"ndk-sys",
|
||||
"objc",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.4",
|
||||
"parking_lot 0.12.5",
|
||||
"profiling",
|
||||
"range-alloc",
|
||||
"raw-window-handle",
|
||||
@@ -15119,7 +15124,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"aws-sdk-config",
|
||||
@@ -15180,7 +15185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -15300,7 +15305,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
@@ -15315,7 +15320,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -15328,7 +15333,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -15347,7 +15352,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15432,7 +15437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -15447,7 +15452,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -15471,7 +15476,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -15483,7 +15488,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -15492,7 +15497,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15504,7 +15509,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15516,7 +15521,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -15528,7 +15533,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15540,7 +15545,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15552,7 +15557,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -15563,7 +15568,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -15574,7 +15579,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -15586,7 +15591,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15609,7 +15614,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15623,7 +15628,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -15640,7 +15645,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15654,7 +15659,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15672,7 +15677,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.16",
|
||||
@@ -15697,7 +15702,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15707,7 +15712,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15740,7 +15745,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
@@ -15750,7 +15755,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-once-cell",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -34,7 +34,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.554.0"
|
||||
version = "1.555.1"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
fc153796d006d08777b5b778d4f370a60495c3af
|
||||
33bdef405c678616b12084cf779a68a62d1f477e
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Add down migration script here
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_conversation_message_conversation_time;
|
||||
DROP INDEX IF EXISTS idx_flow_conversation_workspace_path;
|
||||
|
||||
-- Drop tables (order matters due to foreign keys)
|
||||
DROP TABLE IF EXISTS flow_conversation_message;
|
||||
DROP TABLE IF EXISTS flow_conversation;
|
||||
|
||||
-- Drop enum
|
||||
DROP TYPE IF EXISTS MESSAGE_TYPE;
|
||||
@@ -0,0 +1,57 @@
|
||||
-- Add up migration script here
|
||||
|
||||
-- Create message_type enum
|
||||
CREATE TYPE MESSAGE_TYPE AS ENUM ('user', 'assistant');
|
||||
|
||||
-- Create flow_conversation table
|
||||
CREATE TABLE flow_conversation (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
|
||||
flow_path VARCHAR(255) NOT NULL,
|
||||
title VARCHAR(255),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
created_by VARCHAR(50) NOT NULL
|
||||
);
|
||||
|
||||
-- Create flow_conversation_message table
|
||||
CREATE TABLE flow_conversation_message (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
conversation_id UUID NOT NULL REFERENCES flow_conversation(id) ON DELETE CASCADE,
|
||||
message_type MESSAGE_TYPE NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
job_id UUID REFERENCES v2_job(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Basic indexes for performance
|
||||
CREATE INDEX idx_flow_conversation_workspace_path ON flow_conversation(workspace_id, flow_path, updated_at DESC);
|
||||
CREATE INDEX idx_conversation_message_conversation_time ON flow_conversation_message(conversation_id, created_at DESC);
|
||||
|
||||
-- Grant permissions
|
||||
GRANT ALL ON flow_conversation TO windmill_admin;
|
||||
GRANT ALL ON flow_conversation TO windmill_user;
|
||||
GRANT ALL ON flow_conversation_message TO windmill_admin;
|
||||
GRANT ALL ON flow_conversation_message TO windmill_user;
|
||||
|
||||
-- RLS policies
|
||||
ALTER TABLE flow_conversation ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE flow_conversation_message ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Admin policies - admins can access all conversations
|
||||
CREATE POLICY admin_policy ON flow_conversation FOR ALL TO windmill_admin USING (true);
|
||||
CREATE POLICY admin_policy ON flow_conversation_message FOR ALL TO windmill_admin USING (true);
|
||||
|
||||
-- User policies - users can only access their own conversations
|
||||
CREATE POLICY see_own ON flow_conversation FOR ALL TO windmill_user
|
||||
USING (flow_conversation.created_by = current_setting('session.user'));
|
||||
|
||||
-- Users can see messages of conversations they own
|
||||
CREATE POLICY see_own ON flow_conversation_message FOR ALL TO windmill_user
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM flow_conversation
|
||||
WHERE flow_conversation.id = flow_conversation_message.conversation_id
|
||||
AND flow_conversation.created_by = current_setting('session.user')
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
DROP TABLE app_bundles;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Add up migration script here
|
||||
CREATE TABLE app_bundles (
|
||||
app_version_id BIGINT NOT NULL,
|
||||
w_id VARCHAR(255) NOT NULL,
|
||||
file_type VARCHAR(10) NOT NULL,
|
||||
data BYTEA NOT NULL,
|
||||
PRIMARY KEY (app_version_id, file_type)
|
||||
);
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE job_perms DROP COLUMN end_user_email;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE job_perms ADD COLUMN end_user_email VARCHAR(255);
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use anyhow::anyhow;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use regex::Regex;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -491,13 +492,15 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let mut args = vec![];
|
||||
let mut hm: HashMap<i32, String> = HashMap::new();
|
||||
for cap in RE_CODE_PGSQL.captures_iter(code) {
|
||||
let typ = cap
|
||||
.get(2)
|
||||
.map(|cap| transform_types_with_spaces(&cap, &code))
|
||||
.unwrap_or("text");
|
||||
hm.insert(
|
||||
cap.get(1)
|
||||
.and_then(|x| x.as_str().parse::<i32>().ok())
|
||||
.ok_or_else(|| anyhow!("Impossible to parse arg digit"))?,
|
||||
cap.get(2)
|
||||
.map(|x| x.as_str().to_string())
|
||||
.unwrap_or_else(|| "text".to_string()),
|
||||
typ.to_string(),
|
||||
);
|
||||
}
|
||||
for (i, v) in hm.iter() {
|
||||
@@ -543,6 +546,37 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
Ok(Some(args))
|
||||
}
|
||||
|
||||
// The regex doesn't parse types with space such as "character varying"
|
||||
// So we look for them manually and replace them with their shorter counterpart
|
||||
fn transform_types_with_spaces<'a>(cap: ®ex::Match<'a>, code: &str) -> &'a str {
|
||||
lazy_static! {
|
||||
static ref TYPES: [(&'static str, &'static str); 6] = [
|
||||
("character varying", "varchar"),
|
||||
("double precision", "double"),
|
||||
("time with time zone", "timetz"),
|
||||
("time without time zone", "time"),
|
||||
("timestamp with time zone", "timestamptz"),
|
||||
("timestamp without time zone", "timestamp"),
|
||||
];
|
||||
}
|
||||
let typ = &code[cap.start()..];
|
||||
for (long_type, alias) in TYPES.iter() {
|
||||
let mut typ = typ;
|
||||
let mut found_mismatch = false;
|
||||
for token in long_type.split(' ') {
|
||||
if typ.len() < token.len() || !typ[..token.len()].eq_ignore_ascii_case(token) {
|
||||
found_mismatch = true;
|
||||
break;
|
||||
}
|
||||
typ = typ[token.len()..].trim_start();
|
||||
}
|
||||
if !found_mismatch {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
cap.as_str()
|
||||
}
|
||||
|
||||
pub fn parse_sql_statement_named_params(code: &str, prefix: char) -> HashSet<String> {
|
||||
let mut arg_names = HashSet::new();
|
||||
run_on_sql_statement_matches(
|
||||
|
||||
@@ -156,6 +156,7 @@ impl RunJob {
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("push has to succeed");
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Default directory
|
||||
EE_DIR="../windmill-ee-private"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.554.0
|
||||
version: 1.555.1
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -5727,6 +5727,12 @@ paths:
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
requestBody:
|
||||
description: script args
|
||||
@@ -5743,6 +5749,230 @@ paths:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/f/{path}:
|
||||
post:
|
||||
summary: run flow by path and stream updates via SSE
|
||||
operationId: runAndStreamFlowByPath
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
requestBody:
|
||||
description: flow args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
get:
|
||||
summary: run flow by path with GET and stream updates via SSE
|
||||
operationId: runAndStreamFlowByPathGet
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/p/{path}:
|
||||
post:
|
||||
summary: run script by path and stream updates via SSE
|
||||
operationId: runAndStreamScriptByPath
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/CacheTtl"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
requestBody:
|
||||
description: script args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
get:
|
||||
summary: run script by path with GET and stream updates via SSE
|
||||
operationId: runAndStreamScriptByPathGet
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/CacheTtl"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/h/{hash}:
|
||||
post:
|
||||
summary: run script by hash and stream updates via SSE
|
||||
operationId: runAndStreamScriptByHash
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: hash
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/CacheTtl"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
requestBody:
|
||||
description: script args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
get:
|
||||
summary: run script by hash with GET and stream updates via SSE
|
||||
operationId: runAndStreamScriptByHashGet
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: hash
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/CacheTtl"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs/result_by_id/{flow_job_id}/{node_id}:
|
||||
get:
|
||||
summary: get job result by id
|
||||
@@ -6250,6 +6480,81 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/flow_conversations/list:
|
||||
get:
|
||||
summary: list flow conversations
|
||||
operationId: listFlowConversations
|
||||
tags:
|
||||
- flow_conversation
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
- name: flow_path
|
||||
description: filter conversations by flow path
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: flow conversations list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/FlowConversation"
|
||||
|
||||
/w/{workspace}/flow_conversations/delete/{conversation_id}:
|
||||
delete:
|
||||
summary: delete flow conversation
|
||||
operationId: deleteFlowConversation
|
||||
tags:
|
||||
- flow_conversation
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: conversation_id
|
||||
description: conversation id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
"200":
|
||||
description: flow conversation deleted
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/flow_conversations/{conversation_id}/messages:
|
||||
get:
|
||||
summary: list conversation messages
|
||||
operationId: listConversationMessages
|
||||
tags:
|
||||
- flow_conversation
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: conversation_id
|
||||
description: conversation id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
responses:
|
||||
"200":
|
||||
description: conversation messages
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/FlowConversationMessage"
|
||||
|
||||
/w/{workspace}/raw_apps/list:
|
||||
get:
|
||||
summary: list all raw apps
|
||||
@@ -6698,6 +7003,23 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps/secret_of_latest_version/{path}:
|
||||
get:
|
||||
summary: get public secret of latest version of an app bundle
|
||||
operationId: getPublicSecretOfLatestVersionOfApp
|
||||
tags:
|
||||
- app
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: app secret
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps/get/v/{id}:
|
||||
get:
|
||||
summary: get app by version
|
||||
@@ -7122,6 +7444,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: flow args
|
||||
required: true
|
||||
@@ -14454,6 +14782,66 @@ components:
|
||||
# -- INLINE END --
|
||||
# Do not change line above
|
||||
|
||||
FlowConversation:
|
||||
type: object
|
||||
required:
|
||||
[id, workspace_id, flow_path, created_at, updated_at, created_by]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Unique identifier for the conversation
|
||||
workspace_id:
|
||||
type: string
|
||||
description: The workspace ID where the conversation belongs
|
||||
flow_path:
|
||||
type: string
|
||||
description: Path of the flow this conversation is for
|
||||
title:
|
||||
type: string
|
||||
description: Optional title for the conversation
|
||||
nullable: true
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: When the conversation was created
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: When the conversation was last updated
|
||||
created_by:
|
||||
type: string
|
||||
description: Username who created the conversation
|
||||
|
||||
FlowConversationMessage:
|
||||
type: object
|
||||
required: [id, conversation_id, message_type, content, created_at]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Unique identifier for the message
|
||||
conversation_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: The conversation this message belongs to
|
||||
message_type:
|
||||
type: string
|
||||
enum: [user, assistant, system]
|
||||
description: Type of the message
|
||||
content:
|
||||
type: string
|
||||
description: The message content
|
||||
job_id:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Associated job ID if this message came from a flow run
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: When the message was created
|
||||
|
||||
EndpointTool:
|
||||
type: object
|
||||
required: [name, description, instructions, path, method]
|
||||
@@ -17654,11 +18042,14 @@ components:
|
||||
created_by:
|
||||
type: string
|
||||
nullable: true
|
||||
disabled:
|
||||
type: boolean
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- username
|
||||
- color
|
||||
- disabled
|
||||
required:
|
||||
- email
|
||||
- workspaces
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::db::DB;
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
pub fn global_service(_job_completed_tx: windmill_worker::JobCompletedSender) -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,10 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/get/lite/*path", get(get_app_lite))
|
||||
.route("/get/draft/*path", get(get_app_w_draft))
|
||||
.route("/secret_of/*path", get(get_secret_id))
|
||||
.route(
|
||||
"/secret_of_latest_version/*path",
|
||||
get(get_latest_version_secret_id),
|
||||
)
|
||||
.route("/get/v/*id", get(get_app_by_id))
|
||||
.route("/get_data/v/*id", get(get_raw_app_data))
|
||||
.route("/exists/*path", get(exists_app))
|
||||
@@ -389,19 +393,83 @@ async fn list_apps(
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
async fn get_raw_app_data(Path((w_id, version_id)): Path<(String, String)>) -> Result<Response> {
|
||||
let file_path = format!("/tmp/wmill/{}/{}", w_id, version_id);
|
||||
let file = tokio::fs::File::open(file_path).await?;
|
||||
let stream = tokio_util::io::ReaderStream::new(file);
|
||||
let res = Response::builder().header(
|
||||
http::header::CONTENT_TYPE,
|
||||
if version_id.ends_with(".css") {
|
||||
"text/css"
|
||||
} else {
|
||||
"text/javascript"
|
||||
},
|
||||
);
|
||||
Ok(res.body(Body::from_stream(stream)).unwrap())
|
||||
async fn get_raw_app_data(
|
||||
Path((w_id, secret_with_ext)): Path<(String, String)>,
|
||||
Extension(db): Extension<DB>,
|
||||
) -> Result<Response> {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
let object_store = windmill_common::s3_helpers::get_object_store().await;
|
||||
|
||||
// tracing::info!("secret_with_ext: {}", secret_with_ext);
|
||||
let mut splitted = secret_with_ext.split('.');
|
||||
let secret_id = splitted.next().unwrap_or("");
|
||||
|
||||
if secret_id.is_empty() {
|
||||
return Err(Error::BadRequest("Invalid secret".to_string()));
|
||||
}
|
||||
|
||||
let id = get_id_from_secret(
|
||||
&db,
|
||||
&w_id,
|
||||
secret_id.to_string(),
|
||||
Some(BUNDLE_SECRET_PREFIX),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let file_type = splitted.next().unwrap_or("");
|
||||
let file_type = if file_type == "css" {
|
||||
"css"
|
||||
} else if file_type == "js" {
|
||||
"js"
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
"Invalid file type, only .css and .js are supported".to_string(),
|
||||
));
|
||||
};
|
||||
// tracing::info!("file_type: {}", file_type);
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
let mut body: Option<Body> = None;
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = object_store {
|
||||
let path = format!("/app_bundles/{}/{}.{}", w_id, id, file_type);
|
||||
let stream = os
|
||||
.get(&object_store::path::Path::from(path))
|
||||
.await?
|
||||
.bytes()
|
||||
.await?;
|
||||
tracing::info!("stream: {}", stream.len());
|
||||
body = Some(Body::from(stream));
|
||||
}
|
||||
|
||||
if body.is_none() {
|
||||
let get_raw_app_file = sqlx::query_scalar!(
|
||||
"SELECT data FROM app_bundles WHERE app_version_id = $1 AND file_type = $2 AND w_id = $3",
|
||||
id,
|
||||
file_type,
|
||||
&w_id,
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
if let Some(file) = get_raw_app_file {
|
||||
body = Some(Body::from(file));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(body) = body {
|
||||
// let stream = tokio_util::io::ReaderStream::new(file);
|
||||
let res = Response::builder().header(
|
||||
http::header::CONTENT_TYPE,
|
||||
if file_type == "css" {
|
||||
"text/css"
|
||||
} else {
|
||||
"text/javascript"
|
||||
},
|
||||
);
|
||||
Ok(res.body(body).unwrap())
|
||||
} else {
|
||||
return Err(Error::NotFound("File not found".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// async fn get_app_version(
|
||||
@@ -692,14 +760,7 @@ async fn get_public_app_by_secret(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, secret)): Path<(String, String)>,
|
||||
) -> JsonResult<AppWithLastVersion> {
|
||||
let mc = build_crypt(&db, &w_id).await?;
|
||||
|
||||
let decrypted = mc
|
||||
.decrypt_bytes_to_bytes(&(hex::decode(secret)?))
|
||||
.map_err(|e| Error::internal_err(e.to_string()))?;
|
||||
let bytes = str::from_utf8(&decrypted).map_err(to_anyhow)?;
|
||||
|
||||
let id: i64 = bytes.parse().map_err(to_anyhow)?;
|
||||
let id = get_id_from_secret(&db, &w_id, secret, None).await?;
|
||||
|
||||
let app_o = sqlx::query_as::<_, AppWithLastVersion>(
|
||||
"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path,
|
||||
@@ -747,6 +808,27 @@ async fn get_public_app_by_secret(
|
||||
Ok(Json(app))
|
||||
}
|
||||
|
||||
async fn get_id_from_secret(
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
secret: String,
|
||||
prefix: Option<&str>,
|
||||
) -> Result<i64> {
|
||||
let mc = build_crypt(db, w_id).await?;
|
||||
let decrypted = mc
|
||||
.decrypt_bytes_to_bytes(&(hex::decode(secret)?))
|
||||
.map_err(|e| Error::internal_err(e.to_string()))?;
|
||||
let mut bytes = str::from_utf8(&decrypted).map_err(to_anyhow)?;
|
||||
if let Some(prefix) = prefix {
|
||||
if !bytes.starts_with(prefix) {
|
||||
return Err(Error::BadRequest("Invalid secret".to_string()));
|
||||
}
|
||||
bytes = bytes.strip_prefix(prefix).unwrap_or("");
|
||||
}
|
||||
let id: i64 = bytes.parse().map_err(to_anyhow)?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn get_public_resource(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
@@ -803,16 +885,85 @@ async fn get_secret_id(
|
||||
Ok(hx)
|
||||
}
|
||||
|
||||
const BUNDLE_SECRET_PREFIX: &str = "bundle_";
|
||||
|
||||
async fn get_latest_version_secret_id(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> Result<String> {
|
||||
let path = path.to_path();
|
||||
check_scopes(&authed, || format!("apps:read:{}", path))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let id_o = sqlx::query_scalar!(
|
||||
"SELECT app.versions[array_upper(app.versions, 1)] FROM app
|
||||
WHERE app.path = $1 AND app.workspace_id = $2",
|
||||
path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
let id = not_found_if_none(id_o, "App", path.to_string())?;
|
||||
|
||||
let mc = build_crypt(&db, &w_id).await?;
|
||||
|
||||
let hx = hex::encode(mc.encrypt_str_to_bytes(format!("{}{}", BUNDLE_SECRET_PREFIX, id)));
|
||||
|
||||
Ok(hx)
|
||||
}
|
||||
|
||||
async fn store_raw_app_file<'a>(
|
||||
w_id: &str,
|
||||
id: &i64,
|
||||
file_type: &str,
|
||||
data: bytes::Bytes,
|
||||
tx: &mut sqlx::Transaction<'a, sqlx::Postgres>,
|
||||
) -> Result<()> {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
{
|
||||
let object_store = windmill_common::s3_helpers::get_object_store().await;
|
||||
|
||||
let path: String = format!("/app_bundles/{}/{}.{}", w_id, id, file_type);
|
||||
|
||||
if let Some(os) = object_store {
|
||||
if let Err(e) = os
|
||||
.put(&object_store::path::Path::from(path.clone()), data.into())
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to put snapshot to s3 at {path}: {:?}", e);
|
||||
return Err(windmill_common::error::Error::ExecutionErr(format!(
|
||||
"Failed to put {path} to s3"
|
||||
)));
|
||||
}
|
||||
tracing::info!("Successfully put snapshot to s3 at {path}");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO app_bundles (app_version_id, w_id, file_type, data) VALUES ($1, $2, $3, $4)",
|
||||
id,
|
||||
w_id,
|
||||
file_type,
|
||||
data.to_vec()
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
macro_rules! process_app_multipart {
|
||||
($authed:expr, $user_db:expr, $db:expr, $w_id:expr, $path:expr, $multipart:expr, $internal_fn:expr) => {
|
||||
async {
|
||||
let mut saved_app = None;
|
||||
let mut uploaded_js = false;
|
||||
|
||||
//todo: use s3 instead
|
||||
let file_path = format!("/tmp/wmill/{}", $w_id);
|
||||
std::fs::create_dir_all(&file_path).unwrap();
|
||||
|
||||
let mut multipart = $multipart;
|
||||
while let Some(field) = multipart.next_field().await.unwrap() {
|
||||
let name = field.name().unwrap().to_string();
|
||||
@@ -831,9 +982,8 @@ macro_rules! process_app_multipart {
|
||||
.await?;
|
||||
saved_app = Some((npath, nid, ntx));
|
||||
} else if name == "js" {
|
||||
if let Some((_npath, id, _tx)) = saved_app.as_ref() {
|
||||
let file_path = format!("{}/{}.js", file_path, id);
|
||||
std::fs::write(file_path, data).unwrap();
|
||||
if let Some((_npath, id, tx)) = saved_app.as_mut() {
|
||||
store_raw_app_file($w_id, &id, "js", data, tx).await?;
|
||||
uploaded_js = true;
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
@@ -841,9 +991,8 @@ macro_rules! process_app_multipart {
|
||||
));
|
||||
}
|
||||
} else if name == "css" {
|
||||
if let Some((_npath, id, _tx)) = saved_app.as_ref() {
|
||||
let file_path = format!("{}/{}.css", file_path, id);
|
||||
std::fs::write(file_path, data).unwrap();
|
||||
if let Some((_npath, id, tx)) = saved_app.as_mut() {
|
||||
store_raw_app_file($w_id, &id, "css", data, tx).await?;
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
"App payload need to be created first".to_string(),
|
||||
@@ -1090,6 +1239,7 @@ async fn create_app_internal<'a>(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
@@ -1469,6 +1619,7 @@ async fn update_app_internal<'a>(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
@@ -1755,6 +1906,8 @@ async fn execute_component(
|
||||
(email.as_str(), permissioned_as)
|
||||
};
|
||||
|
||||
let end_user_email = opt_authed.as_ref().map(|a| a.email.clone());
|
||||
|
||||
let (uuid, tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
@@ -1784,6 +1937,7 @@ async fn execute_component(
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
end_user_email,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
routing::{delete, get},
|
||||
Extension, Json, Router,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sql_builder::prelude::*;
|
||||
use sqlx::{FromRow, Postgres};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{JsonResult, Result},
|
||||
utils::{not_found_if_none, paginate, Pagination},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
|
||||
#[sqlx(type_name = "MESSAGE_TYPE", rename_all = "lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum MessageType {
|
||||
User,
|
||||
Assistant,
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
.route("/list", get(list_conversations))
|
||||
.route("/delete/:conversation_id", delete(delete_conversation))
|
||||
.route("/:conversation_id/messages", get(list_messages))
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow, Debug)]
|
||||
pub struct FlowConversation {
|
||||
pub id: Uuid,
|
||||
pub workspace_id: String,
|
||||
pub flow_path: String,
|
||||
pub title: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub created_by: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow, Debug)]
|
||||
pub struct FlowConversationMessage {
|
||||
pub id: Uuid,
|
||||
pub conversation_id: Uuid,
|
||||
pub message_type: MessageType,
|
||||
pub content: String,
|
||||
pub job_id: Option<Uuid>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListConversationsQuery {
|
||||
pub flow_path: Option<String>,
|
||||
}
|
||||
|
||||
async fn list_conversations(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Query(pagination): Query<Pagination>,
|
||||
Query(query): Query<ListConversationsQuery>,
|
||||
) -> JsonResult<Vec<FlowConversation>> {
|
||||
let (per_page, offset) = paginate(pagination);
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
let mut sqlb = SqlBuilder::select_from("flow_conversation");
|
||||
sqlb.fields(&[
|
||||
"id",
|
||||
"workspace_id",
|
||||
"flow_path",
|
||||
"title",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
])
|
||||
.and_where_eq("workspace_id", "?".bind(&w_id));
|
||||
|
||||
if let Some(flow_path) = &query.flow_path {
|
||||
sqlb.and_where_eq("flow_path", "?".bind(flow_path));
|
||||
}
|
||||
|
||||
sqlb.order_by("updated_at", true)
|
||||
.limit(per_page as i64)
|
||||
.offset(offset as i64);
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| {
|
||||
windmill_common::error::Error::InternalErr(format!("Failed to build SQL: {}", e))
|
||||
})?;
|
||||
|
||||
let conversations = sqlx::query_as::<Postgres, FlowConversation>(&sql)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(Json(conversations))
|
||||
}
|
||||
|
||||
pub async fn get_or_create_conversation_with_id(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
w_id: &str,
|
||||
flow_path: &str,
|
||||
username: &str,
|
||||
title: &str,
|
||||
conversation_id: Uuid,
|
||||
) -> Result<FlowConversation> {
|
||||
// Check if conversation already exists
|
||||
let existing_conversation = sqlx::query_as!(
|
||||
FlowConversation,
|
||||
"SELECT id, workspace_id, flow_path, title, created_at, updated_at, created_by
|
||||
FROM flow_conversation
|
||||
WHERE id = $1 AND workspace_id = $2",
|
||||
conversation_id,
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(&mut **tx)
|
||||
.await?;
|
||||
|
||||
if let Some(existing) = existing_conversation {
|
||||
return Ok(existing);
|
||||
}
|
||||
|
||||
// Create new conversation with provided ID
|
||||
let conversation = sqlx::query_as!(
|
||||
FlowConversation,
|
||||
"INSERT INTO flow_conversation (id, workspace_id, flow_path, created_by, title)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, workspace_id, flow_path, title, created_at, updated_at, created_by",
|
||||
conversation_id,
|
||||
w_id,
|
||||
flow_path,
|
||||
username,
|
||||
title
|
||||
)
|
||||
.fetch_one(&mut **tx)
|
||||
.await?;
|
||||
Ok(conversation)
|
||||
}
|
||||
|
||||
async fn delete_conversation(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, conversation_id)): Path<(String, Uuid)>,
|
||||
) -> Result<String> {
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
// Verify the conversation exists and belongs to the user
|
||||
let conversation = sqlx::query_as!(
|
||||
FlowConversation,
|
||||
"SELECT id, workspace_id, flow_path, title, created_at, updated_at, created_by
|
||||
FROM flow_conversation
|
||||
WHERE id = $1 AND workspace_id = $2",
|
||||
conversation_id,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
not_found_if_none(conversation, "Conversation", conversation_id.to_string())?;
|
||||
|
||||
// Delete the conversation (messages will be cascade deleted)
|
||||
sqlx::query!(
|
||||
"DELETE FROM flow_conversation WHERE id = $1 AND workspace_id = $2",
|
||||
conversation_id,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
// Delete associated memory in background (non-blocking cleanup)
|
||||
let w_id_clone = w_id.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) =
|
||||
windmill_worker::memory_oss::delete_conversation_memory(&w_id_clone, conversation_id)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to delete memory for conversation {} in workspace {}: {:?}",
|
||||
conversation_id,
|
||||
w_id_clone,
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(format!("Conversation {} deleted", conversation_id))
|
||||
}
|
||||
|
||||
async fn list_messages(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, conversation_id)): Path<(String, Uuid)>,
|
||||
Query(pagination): Query<Pagination>,
|
||||
) -> JsonResult<Vec<FlowConversationMessage>> {
|
||||
let (per_page, offset) = paginate(pagination);
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
// Verify the conversation exists and belongs to the user
|
||||
let conversation_exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM flow_conversation WHERE id = $1 AND workspace_id = $2)",
|
||||
conversation_id,
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if !conversation_exists {
|
||||
return Err(windmill_common::error::Error::NotFound(format!(
|
||||
"Conversation not found or access denied: {}",
|
||||
conversation_id
|
||||
)));
|
||||
}
|
||||
|
||||
// Fetch messages for this conversation, oldest first, but reverse the order of the messages for easy rendering on the frontend
|
||||
let messages = sqlx::query_as!(
|
||||
FlowConversationMessage,
|
||||
r#"SELECT id, conversation_id, message_type as "message_type: MessageType", content, job_id, created_at
|
||||
FROM (
|
||||
SELECT id, conversation_id, message_type, content, job_id, created_at
|
||||
FROM flow_conversation_message
|
||||
WHERE conversation_id = $1
|
||||
ORDER BY created_at DESC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END
|
||||
LIMIT $2 OFFSET $3
|
||||
) AS messages
|
||||
ORDER BY created_at ASC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END
|
||||
"#,
|
||||
conversation_id,
|
||||
per_page as i64,
|
||||
offset as i64
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(Json(messages))
|
||||
}
|
||||
|
||||
// Helper function to create a message using an existing transaction
|
||||
pub async fn create_message(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
conversation_id: Uuid,
|
||||
message_type: MessageType,
|
||||
content: &str,
|
||||
job_id: Option<Uuid>,
|
||||
workspace_id: &str,
|
||||
) -> windmill_common::error::Result<()> {
|
||||
// Verify the conversation exists and belongs to the user
|
||||
let conversation_exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM flow_conversation WHERE id = $1 AND workspace_id = $2)",
|
||||
conversation_id,
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(&mut **tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if !conversation_exists {
|
||||
return Err(windmill_common::error::Error::NotFound(format!(
|
||||
"Conversation not found or access denied: {}",
|
||||
conversation_id
|
||||
)));
|
||||
}
|
||||
|
||||
// Insert the message
|
||||
sqlx::query!(
|
||||
"INSERT INTO flow_conversation_message (conversation_id, message_type, content, job_id)
|
||||
VALUES ($1, $2, $3, $4)",
|
||||
conversation_id,
|
||||
message_type as MessageType,
|
||||
content,
|
||||
job_id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
// Update conversation updated_at timestamp
|
||||
sqlx::query!(
|
||||
"UPDATE flow_conversation SET updated_at = NOW() WHERE id = $1",
|
||||
conversation_id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -545,6 +545,7 @@ async fn create_flow(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1012,6 +1013,7 @@ async fn update_flow(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
@@ -1593,6 +1595,7 @@ mod tests {
|
||||
priority: None,
|
||||
early_return: None,
|
||||
concurrency_key: None,
|
||||
chat_input_enabled: None,
|
||||
};
|
||||
let expect = serde_json::json!({
|
||||
"modules": [
|
||||
|
||||
@@ -19,7 +19,6 @@ use quick_cache::sync::Cache;
|
||||
use serde_json::value::RawValue;
|
||||
use serde_json::Value;
|
||||
use sqlx::Pool;
|
||||
use windmill_common::s3_helpers::{upload_artifact_to_store, BundleFormat};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
@@ -37,6 +36,7 @@ use windmill_common::jobs::{
|
||||
check_tag_available_for_workspace_internal, format_completed_job_result, format_result,
|
||||
DynamicInput, ENTRYPOINT_OVERRIDE,
|
||||
};
|
||||
use windmill_common::s3_helpers::{upload_artifact_to_store, BundleFormat};
|
||||
use windmill_common::utils::{RunnableKind, WarnAfterExt};
|
||||
use windmill_common::worker::{Connection, CLOUD_HOSTED, TMP_DIR};
|
||||
use windmill_common::DYNAMIC_INPUT_CACHE;
|
||||
@@ -104,6 +104,8 @@ use windmill_queue::{
|
||||
PushArgsOwned, PushIsolationLevel,
|
||||
};
|
||||
|
||||
use crate::flow_conversations::{self, MessageType};
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_methods([http::Method::GET, http::Method::POST])
|
||||
@@ -1757,6 +1759,7 @@ pub struct RunJobQuery {
|
||||
pub cache_ttl: Option<i32>,
|
||||
pub skip_preprocessor: Option<bool>,
|
||||
pub poll_delay_ms: Option<u64>,
|
||||
pub memory_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl RunJobQuery {
|
||||
@@ -3612,7 +3615,7 @@ struct Preview {
|
||||
tag: Option<String>,
|
||||
dedicated_worker: Option<bool>,
|
||||
lock: Option<String>,
|
||||
format: Option<String>
|
||||
format: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -3997,6 +4000,87 @@ async fn batch_rerun_handle_job(
|
||||
))
|
||||
}
|
||||
|
||||
/// Set the memory_id in flow_status for agent memory persistence
|
||||
async fn set_flow_memory_id(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
job_id: Uuid,
|
||||
memory_id: Uuid,
|
||||
) -> error::Result<()> {
|
||||
sqlx::query!(
|
||||
"UPDATE v2_job_status
|
||||
SET flow_status = jsonb_set(
|
||||
flow_status,
|
||||
'{memory_id}',
|
||||
to_jsonb($2::uuid)
|
||||
)
|
||||
WHERE id = $1",
|
||||
job_id,
|
||||
memory_id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_chat_conversation_messages(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
authed: &ApiAuthed,
|
||||
w_id: &str,
|
||||
flow_path: &str,
|
||||
run_query: &RunJobQuery,
|
||||
args: &PushArgsOwned,
|
||||
uuid: Uuid,
|
||||
) -> error::Result<()> {
|
||||
let memory_id = run_query.memory_id.ok_or_else(|| {
|
||||
windmill_common::error::Error::BadRequest(
|
||||
"memory_id is required for chat-enabled flows".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let user_msg_raw = args.args.get("user_message").ok_or_else(|| {
|
||||
windmill_common::error::Error::BadRequest(
|
||||
"user_message argument is required for chat-enabled flows".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let user_msg = serde_json::from_str::<String>(user_msg_raw.get())?;
|
||||
|
||||
// Create conversation with provided ID (or get existing one)
|
||||
flow_conversations::get_or_create_conversation_with_id(
|
||||
tx,
|
||||
w_id,
|
||||
flow_path,
|
||||
&authed.username,
|
||||
&user_msg,
|
||||
memory_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Create user message
|
||||
flow_conversations::create_message(
|
||||
tx,
|
||||
memory_id,
|
||||
MessageType::User,
|
||||
&user_msg,
|
||||
None, // No job_id for user message
|
||||
w_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Create placeholder assistant message in the same transaction as the job
|
||||
flow_conversations::create_message(
|
||||
tx,
|
||||
memory_id,
|
||||
MessageType::Assistant,
|
||||
"", // Empty content, will be updated when job completes
|
||||
Some(uuid), // Associate with the job
|
||||
w_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_flow_by_path(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
@@ -4043,6 +4127,7 @@ pub async fn run_flow_by_path_inner(
|
||||
tag,
|
||||
dedicated_worker,
|
||||
has_preprocessor,
|
||||
chat_input_enabled,
|
||||
on_behalf_of_email,
|
||||
edited_by,
|
||||
early_return,
|
||||
@@ -4068,11 +4153,11 @@ pub async fn run_flow_by_path_inner(
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
Some(authed.clone().into()),
|
||||
PushIsolationLevel::Isolated(user_db, authed.clone().into()),
|
||||
PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into()),
|
||||
)
|
||||
};
|
||||
|
||||
let (uuid, tx) = push(
|
||||
let (uuid, mut tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
@@ -4083,7 +4168,7 @@ pub async fn run_flow_by_path_inner(
|
||||
apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false)
|
||||
&& has_preprocessor.unwrap_or(false),
|
||||
},
|
||||
PushArgs { args: &args.args, extra: args.extra },
|
||||
PushArgs { args: &args.args, extra: args.extra.clone() },
|
||||
authed.display_username(),
|
||||
email,
|
||||
permissioned_as,
|
||||
@@ -4104,8 +4189,29 @@ pub async fn run_flow_by_path_inner(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Set memory_id if provided (for agent memory)
|
||||
if let Some(memory_id) = run_query.memory_id {
|
||||
set_flow_memory_id(&mut tx, uuid, memory_id).await?;
|
||||
}
|
||||
|
||||
// Handle conversation messages for chat-enabled flows
|
||||
if chat_input_enabled.unwrap_or(false) {
|
||||
handle_chat_conversation_messages(
|
||||
&mut tx,
|
||||
&authed,
|
||||
&w_id,
|
||||
&flow_path.to_string(),
|
||||
&run_query,
|
||||
&args,
|
||||
uuid,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
Ok((uuid, early_return))
|
||||
}
|
||||
@@ -4200,6 +4306,7 @@ pub async fn restart_flow(
|
||||
completed_job.priority,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -4302,6 +4409,7 @@ pub async fn run_script_by_path_inner(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -4454,6 +4562,7 @@ pub async fn run_workflow_as_code(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -4997,6 +5106,7 @@ pub async fn run_wait_result_job_by_path_get(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -5149,6 +5259,7 @@ pub async fn run_wait_result_script_by_path_internal(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -5265,6 +5376,7 @@ pub async fn run_wait_result_script_by_hash(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -5517,6 +5629,7 @@ pub async fn run_wait_result_flow_by_path_internal(
|
||||
dedicated_worker,
|
||||
early_return,
|
||||
has_preprocessor,
|
||||
chat_input_enabled,
|
||||
on_behalf_of_email,
|
||||
edited_by,
|
||||
version,
|
||||
@@ -5539,11 +5652,11 @@ pub async fn run_wait_result_flow_by_path_internal(
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
Some(authed.clone().into()),
|
||||
PushIsolationLevel::Isolated(user_db, authed.clone().into()),
|
||||
PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into()),
|
||||
)
|
||||
};
|
||||
|
||||
let (uuid, tx) = push(
|
||||
let (uuid, mut tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
@@ -5554,7 +5667,7 @@ pub async fn run_wait_result_flow_by_path_internal(
|
||||
apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false)
|
||||
&& has_preprocessor.unwrap_or(false),
|
||||
},
|
||||
PushArgs { args: &args.args, extra: args.extra },
|
||||
PushArgs { args: &args.args, extra: args.extra.clone() },
|
||||
authed.display_username(),
|
||||
email,
|
||||
permissioned_as,
|
||||
@@ -5575,8 +5688,29 @@ pub async fn run_wait_result_flow_by_path_internal(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Set conversation_id if provided (for agent memory)
|
||||
if let Some(memory_id) = run_query.memory_id {
|
||||
set_flow_memory_id(&mut tx, uuid, memory_id).await?;
|
||||
}
|
||||
|
||||
// Handle conversation messages for chat-enabled flows
|
||||
if chat_input_enabled.unwrap_or(false) {
|
||||
handle_chat_conversation_messages(
|
||||
&mut tx,
|
||||
&authed,
|
||||
&w_id,
|
||||
&flow_path.to_string(),
|
||||
&run_query,
|
||||
&args,
|
||||
uuid,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
run_wait_result(&db, uuid, w_id, early_return, &authed.username).await
|
||||
@@ -5646,6 +5780,7 @@ async fn run_preview_script(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -5677,7 +5812,6 @@ async fn run_wait_result_preview_script(
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
async fn run_bundle_preview_script(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
@@ -5686,7 +5820,6 @@ async fn run_bundle_preview_script(
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
mut multipart: axum::extract::Multipart,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
|
||||
if authed.is_operator {
|
||||
return Err(error::Error::NotAuthorized(
|
||||
"Operators cannot run preview jobs for security reasons".to_string(),
|
||||
@@ -5705,7 +5838,10 @@ async fn run_bundle_preview_script(
|
||||
let data = data.map_err(to_anyhow)?;
|
||||
if name == "preview" {
|
||||
let preview: Preview = serde_json::from_slice(&data).map_err(to_anyhow)?;
|
||||
format = preview.format.and_then(|s| BundleFormat::from_string(&s)).unwrap_or(BundleFormat::Cjs);
|
||||
format = preview
|
||||
.format
|
||||
.and_then(|s| BundleFormat::from_string(&s))
|
||||
.unwrap_or(BundleFormat::Cjs);
|
||||
|
||||
let scheduled_for = run_query.get_scheduled_for(&db).await?;
|
||||
let tag = run_query.tag.clone().or(preview.tag.clone());
|
||||
@@ -5726,7 +5862,10 @@ async fn run_bundle_preview_script(
|
||||
ltx,
|
||||
&w_id,
|
||||
JobPayload::Code(RawCode {
|
||||
hash: Some(windmill_common::scripts::codebase_to_hash(is_tar, format == BundleFormat::Esm)),
|
||||
hash: Some(windmill_common::scripts::codebase_to_hash(
|
||||
is_tar,
|
||||
format == BundleFormat::Esm,
|
||||
)),
|
||||
content: preview.content.unwrap_or_default(),
|
||||
path: preview.path,
|
||||
language: preview.language.unwrap_or(ScriptLang::Deno),
|
||||
@@ -5758,6 +5897,7 @@ async fn run_bundle_preview_script(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
job_id = Some(uuid);
|
||||
@@ -5785,7 +5925,12 @@ async fn run_bundle_preview_script(
|
||||
uploaded = true;
|
||||
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &id);
|
||||
upload_artifact_to_store(&path, data, &windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR).await?;
|
||||
upload_artifact_to_store(
|
||||
&path,
|
||||
data,
|
||||
&windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
// println!("Length of `{}` is {} bytes", name, data.len());
|
||||
}
|
||||
@@ -5890,6 +6035,7 @@ async fn run_dependencies_job(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -5957,6 +6103,7 @@ async fn run_flow_dependencies_job(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -6444,6 +6591,7 @@ async fn run_preview_flow_job(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -6617,6 +6765,7 @@ async fn run_dynamic_select(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -6744,6 +6893,7 @@ pub async fn run_job_by_hash_inner(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -87,6 +87,7 @@ pub mod ee;
|
||||
pub mod ee_oss;
|
||||
pub mod embeddings;
|
||||
mod favorite;
|
||||
mod flow_conversations;
|
||||
pub mod flows;
|
||||
mod folders;
|
||||
mod granular_acls;
|
||||
@@ -411,7 +412,7 @@ pub async fn run_server(
|
||||
};
|
||||
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
let (agent_workers_router, agent_workers_bg_processor, agent_workers_killpill_tx) =
|
||||
let (agent_workers_router, agent_workers_bg_processor, agent_workers_job_completed_tx) =
|
||||
if server_mode {
|
||||
agent_workers_oss::workspaced_service(db.clone(), _base_internal_url.clone())
|
||||
} else {
|
||||
@@ -443,6 +444,10 @@ pub async fn run_server(
|
||||
.nest("/drafts", drafts::workspaced_service())
|
||||
.nest("/favorites", favorite::workspaced_service())
|
||||
.nest("/flows", flows::workspaced_service())
|
||||
.nest(
|
||||
"/flow_conversations",
|
||||
flow_conversations::workspaced_service(),
|
||||
)
|
||||
.nest("/folders", folders::workspaced_service())
|
||||
.nest("/groups", groups::workspaced_service())
|
||||
.nest("/inputs", inputs::workspaced_service())
|
||||
@@ -535,7 +540,14 @@ pub async fn run_server(
|
||||
.nest("/agent_workers", {
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
{
|
||||
agent_workers_oss::global_service().layer(Extension(agent_cache.clone()))
|
||||
if let Some(agent_workers_job_completed_tx) =
|
||||
agent_workers_job_completed_tx.clone()
|
||||
{
|
||||
agent_workers_oss::global_service(agent_workers_job_completed_tx)
|
||||
.layer(Extension(agent_cache.clone()))
|
||||
} else {
|
||||
Router::new()
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "agent_worker_server"))]
|
||||
{
|
||||
@@ -705,8 +717,8 @@ pub async fn run_server(
|
||||
let server = server.with_graceful_shutdown(async move {
|
||||
killpill_rx.recv().await.ok();
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
if let Some(agent_workers_killpill_tx) = agent_workers_killpill_tx {
|
||||
if let Err(e) = agent_workers_killpill_tx.kill().await {
|
||||
if let Some(agent_workers_job_completed_tx) = agent_workers_job_completed_tx {
|
||||
if let Err(e) = agent_workers_job_completed_tx.kill().await {
|
||||
tracing::error!("Error killing agent workers: {e:#}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,7 +515,8 @@ pub async fn transform_json_value<'c>(
|
||||
match v {
|
||||
Value::String(y) if y.starts_with("$var:") => {
|
||||
let path = y.strip_prefix("$var:").unwrap();
|
||||
let userdb_authed = UserDbWithOptAuthed { authed: authed, user_db: user_db.clone(), db: db.clone() };
|
||||
let userdb_authed =
|
||||
UserDbWithOptAuthed { authed: authed, user_db: user_db.clone(), db: db.clone() };
|
||||
|
||||
let v = crate::variables::get_value_internal(
|
||||
&userdb_authed,
|
||||
@@ -616,6 +617,7 @@ pub async fn transform_json_value<'c>(
|
||||
job.root_job.map(|x| x.to_string()),
|
||||
Some(job.scheduled_for.clone()),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -41,7 +41,12 @@ use windmill_audit::ActionKind;
|
||||
use windmill_worker::process_relative_imports;
|
||||
|
||||
use windmill_common::{
|
||||
assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind, AssetWithAltAccessType}, error::to_anyhow, s3_helpers::upload_artifact_to_store, scripts::hash_script, utils::WarnAfterExt, worker::CLOUD_HOSTED
|
||||
assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind, AssetWithAltAccessType},
|
||||
error::to_anyhow,
|
||||
s3_helpers::upload_artifact_to_store,
|
||||
scripts::hash_script,
|
||||
utils::WarnAfterExt,
|
||||
worker::CLOUD_HOSTED,
|
||||
};
|
||||
|
||||
use windmill_common::{
|
||||
@@ -418,7 +423,12 @@ async fn create_snapshot_script(
|
||||
uploaded = true;
|
||||
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &hash);
|
||||
upload_artifact_to_store(&path, data, &windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR).await?;
|
||||
upload_artifact_to_store(
|
||||
&path,
|
||||
data,
|
||||
&windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
// println!("Length of `{}` is {} bytes", name, data.len());
|
||||
}
|
||||
@@ -438,7 +448,6 @@ async fn create_snapshot_script(
|
||||
return Ok((StatusCode::CREATED, format!("{}", script_hash.unwrap())));
|
||||
}
|
||||
|
||||
|
||||
async fn list_paths_from_workspace_runnable(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -980,6 +989,7 @@ async fn create_script_internal<'c>(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok((hash, new_tx, None))
|
||||
|
||||
@@ -42,7 +42,6 @@ use windmill_common::{
|
||||
},
|
||||
parse_postgres_url,
|
||||
server::Smtp,
|
||||
utils::build_arg_str,
|
||||
};
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
@@ -617,19 +616,21 @@ async fn create_ducklake_database(
|
||||
|
||||
// We have to connect to the newly created database as admin to grant permissions
|
||||
let pg_creds = parse_postgres_url(&get_database_url().await?)?;
|
||||
let Some(wm_pg_pwd) = pg_creds.password else {
|
||||
return Err(error::Error::BadRequest("Password not found".to_string()));
|
||||
|
||||
let ssl_mode = match pg_creds.ssl_mode.as_deref() {
|
||||
Some("allow") => "prefer".to_string(),
|
||||
Some("verify-ca") | Some("verify-full") => "require".to_string(),
|
||||
Some(s) => s.to_string(),
|
||||
None => "prefer".to_string(),
|
||||
};
|
||||
let conn_str: String = build_arg_str(
|
||||
&[
|
||||
("host", Some(&pg_creds.host)),
|
||||
("port", pg_creds.port.map(|p| p.to_string()).as_deref()),
|
||||
("password", Some(&wm_pg_pwd)),
|
||||
("user", pg_creds.username.as_deref()),
|
||||
("dbname", Some(&dbname)),
|
||||
],
|
||||
" ",
|
||||
"=",
|
||||
let conn_str = format!(
|
||||
"postgres://{user}:{password}@{host}:{port}/{dbname}?sslmode={sslmode}",
|
||||
user = urlencoding::encode(&pg_creds.username.unwrap_or_else(|| "postgres".to_string())),
|
||||
password = urlencoding::encode(&pg_creds.password.as_deref().unwrap_or("")),
|
||||
host = urlencoding::encode(&pg_creds.host),
|
||||
port = pg_creds.port.unwrap_or(5432),
|
||||
dbname = dbname,
|
||||
sslmode = ssl_mode
|
||||
);
|
||||
let (client, connection) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(20),
|
||||
|
||||
@@ -684,7 +684,8 @@ pub async fn trigger_runnable_and_wait_for_raw_result_with_error_ctx(
|
||||
error_handler_path,
|
||||
error_handler_args,
|
||||
trigger_path,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !success {
|
||||
Err(windmill_common::error::Error::internal_err(format!(
|
||||
@@ -857,6 +858,7 @@ async fn trigger_script_with_retry_and_error_handler(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -83,6 +83,7 @@ async fn list_contextual_variables(
|
||||
Some("017e0ad5-f499-73b6-5488-92a61c5196dd".to_string()),
|
||||
Some(chrono::offset::Utc::now()),
|
||||
Some(ScriptHash(1234567890)),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.to_vec(),
|
||||
@@ -843,11 +844,11 @@ pub async fn get_value_internal<'a, 'e, A: sqlx::Acquire<'e, Database = Postgres
|
||||
} else if !value.is_empty() {
|
||||
let mc = build_crypt(&db, &w_id).await?;
|
||||
decrypt(&mc, value).map_err(|e| {
|
||||
Error::internal_err(format!(
|
||||
"Error decrypting variable {}: {}",
|
||||
variable.path, e
|
||||
))
|
||||
})?
|
||||
Error::internal_err(format!(
|
||||
"Error decrypting variable {}: {}",
|
||||
variable.path, e
|
||||
))
|
||||
})?
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
@@ -884,11 +885,8 @@ pub async fn get_variable_or_self(path: String, db: &DB, w_id: &str) -> Result<S
|
||||
if record.is_secret {
|
||||
let mc = build_crypt(db, w_id).await?;
|
||||
value = decrypt(&mc, value).map_err(|e| {
|
||||
Error::internal_err(format!(
|
||||
"Error decrypting variable {}: {}",
|
||||
path, e
|
||||
))
|
||||
})?;
|
||||
Error::internal_err(format!("Error decrypting variable {}: {}", path, e))
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
|
||||
@@ -366,6 +366,7 @@ struct UserWorkspace {
|
||||
pub color: Option<String>,
|
||||
pub operator_settings: Option<Option<serde_json::Value>>,
|
||||
pub parent_workspace_id: Option<String>,
|
||||
pub disabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -2109,7 +2110,8 @@ async fn user_workspaces(
|
||||
let workspaces = sqlx::query_as!(
|
||||
UserWorkspace,
|
||||
"SELECT workspace.id, workspace.name, usr.username, workspace_settings.color, workspace.parent_workspace_id,
|
||||
CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings
|
||||
CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings,
|
||||
usr.disabled
|
||||
FROM workspace
|
||||
JOIN usr ON usr.workspace_id = workspace.id
|
||||
JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id
|
||||
|
||||
@@ -45,6 +45,10 @@ pub struct FlowStatus {
|
||||
pub restarted_from: Option<RestartedFrom>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stream_job: Option<Uuid>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub chat_input_enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub memory_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
@@ -508,6 +512,8 @@ impl FlowStatus {
|
||||
restarted_from: None,
|
||||
user_states: HashMap::new(),
|
||||
stream_job: None,
|
||||
chat_input_enabled: f.chat_input_enabled,
|
||||
memory_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,8 @@ pub struct FlowValue {
|
||||
pub priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub concurrency_key: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub chat_input_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
impl FlowValue {
|
||||
|
||||
@@ -682,6 +682,7 @@ pub struct FlowVersionInfo {
|
||||
pub tag: Option<String>,
|
||||
pub early_return: Option<String>,
|
||||
pub has_preprocessor: Option<bool>,
|
||||
pub chat_input_enabled: Option<bool>,
|
||||
pub on_behalf_of_email: Option<String>,
|
||||
pub edited_by: String,
|
||||
pub dedicated_worker: Option<bool>,
|
||||
@@ -803,7 +804,7 @@ pub fn get_latest_flow_version_info_for_path_from_version<
|
||||
let mut conn = db.acquire().await?;
|
||||
let info = sqlx::query_as!(
|
||||
FlowVersionInfo,
|
||||
"SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, on_behalf_of_email, edited_by, flow_version.id AS version
|
||||
"SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, on_behalf_of_email, edited_by, flow_version.id AS version
|
||||
FROM flow
|
||||
INNER JOIN flow_version
|
||||
ON flow_version.id = $3
|
||||
|
||||
@@ -14,6 +14,7 @@ use object_store::gcp::GoogleCloudStorageBuilder;
|
||||
use object_store::ObjectStore;
|
||||
#[cfg(feature = "parquet")]
|
||||
use object_store::{aws::AmazonS3Builder, ClientOptions};
|
||||
use quick_cache::sync::Cache;
|
||||
#[cfg(feature = "parquet")]
|
||||
use reqwest::header::HeaderMap;
|
||||
use serde::de::Visitor;
|
||||
@@ -414,18 +415,6 @@ pub struct AzureBlobResource {
|
||||
pub federated_token_file: Option<String>,
|
||||
}
|
||||
|
||||
impl AzureBlobResource {
|
||||
pub fn get_endpoint_url(&self) -> error::Result<String> {
|
||||
Ok(render_endpoint(
|
||||
self.endpoint.clone().unwrap_or_else(|| "".to_string()),
|
||||
self.use_ssl.unwrap_or(false),
|
||||
None,
|
||||
None,
|
||||
"".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn as_string<'de, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
@@ -526,7 +515,6 @@ pub async fn build_object_store_client(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum BundleFormat {
|
||||
Esm,
|
||||
@@ -534,7 +522,7 @@ pub enum BundleFormat {
|
||||
}
|
||||
|
||||
impl BundleFormat {
|
||||
pub fn from_string(s: &str) -> Option<Self> {
|
||||
pub fn from_string(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"esm" => Some(Self::Esm),
|
||||
"cjs" => Some(Self::Cjs),
|
||||
@@ -543,49 +531,51 @@ impl BundleFormat {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upload_artifact_to_store(path: &str, data: bytes::Bytes, standalone_dir: &str) -> error::Result<()> {
|
||||
pub async fn upload_artifact_to_store(
|
||||
path: &str,
|
||||
data: bytes::Bytes,
|
||||
standalone_dir: &str,
|
||||
) -> error::Result<()> {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
let object_store = crate::s3_helpers::get_object_store().await;
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
let object_store: Option<()> = None;
|
||||
Ok(if &crate::utils::MODE_AND_ADDONS.mode
|
||||
== &crate::utils::Mode::Standalone
|
||||
&& object_store.is_none()
|
||||
{
|
||||
let path = format!("{}/{}", standalone_dir, path);
|
||||
tracing::info!("Writing file to path {path}");
|
||||
|
||||
let split_path = path.split("/").collect::<Vec<&str>>();
|
||||
std::fs::create_dir_all(
|
||||
split_path[..split_path.len() - 1].join("/"),
|
||||
)?;
|
||||
|
||||
crate::worker::write_file_bytes(
|
||||
&path,
|
||||
&data,
|
||||
)?;
|
||||
} else {
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
Ok(
|
||||
if &crate::utils::MODE_AND_ADDONS.mode == &crate::utils::Mode::Standalone
|
||||
&& object_store.is_none()
|
||||
{
|
||||
return Err(error::Error::ExecutionErr("codebase is an EE feature".to_string()));
|
||||
}
|
||||
let path = format!("{}/{}", standalone_dir, path);
|
||||
tracing::info!("Writing file to path {path}");
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = object_store {
|
||||
let split_path = path.split("/").collect::<Vec<&str>>();
|
||||
std::fs::create_dir_all(split_path[..split_path.len() - 1].join("/"))?;
|
||||
|
||||
if let Err(e) = os
|
||||
.put(&object_store::path::Path::from(path), data.into())
|
||||
.await
|
||||
{
|
||||
tracing::info!("Failed to put snapshot to s3 at {path}: {:?}", e);
|
||||
return Err(error::Error::ExecutionErr(format!("Failed to put {path} to s3")));
|
||||
}
|
||||
crate::worker::write_file_bytes(&path, &data)?;
|
||||
} else {
|
||||
return Err(error::Error::BadConfig("Object store is required for snapshot script and is not configured for servers".to_string()));
|
||||
}
|
||||
})
|
||||
}
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
{
|
||||
return Err(error::Error::ExecutionErr(
|
||||
"codebase is an EE feature".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = object_store {
|
||||
if let Err(e) = os
|
||||
.put(&object_store::path::Path::from(path), data.into())
|
||||
.await
|
||||
{
|
||||
tracing::info!("Failed to put snapshot to s3 at {path}: {:?}", e);
|
||||
return Err(error::Error::ExecutionErr(format!(
|
||||
"Failed to put {path} to s3"
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(error::Error::BadConfig("Object store is required for snapshot script and is not configured for servers".to_string()));
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn attempt_fetch_bytes(
|
||||
@@ -1246,21 +1236,11 @@ pub fn duckdb_connection_settings_internal(
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
impl ObjectStoreResource {
|
||||
pub fn get_endpoint_url(&self) -> error::Result<String> {
|
||||
match self {
|
||||
ObjectStoreResource::S3(s3_resource) => Ok(render_endpoint(
|
||||
s3_resource.endpoint.clone(),
|
||||
s3_resource.use_ssl,
|
||||
s3_resource.port,
|
||||
s3_resource.path_style,
|
||||
s3_resource.bucket.clone(),
|
||||
)),
|
||||
ObjectStoreResource::Gcs(gcs_resource) => Ok(format!(
|
||||
"https://storage.googleapis.com/{}",
|
||||
gcs_resource.bucket
|
||||
)),
|
||||
ObjectStoreResource::Azure(az_resource) => az_resource.get_endpoint_url(),
|
||||
}
|
||||
}
|
||||
// DuckDB does not parse anything in case of S3 errors and just returns a generic error message.
|
||||
// To display better error messages, we cache the errors in a Map<Token, ErrorMessage>
|
||||
//
|
||||
// We leverage the fact that workers have an internal server to insert the error message
|
||||
// from the S3 Proxy, and read it directly in memory from the worker.
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref S3_PROXY_LAST_ERRORS_CACHE: Cache<String, String> = Cache::new(4);
|
||||
}
|
||||
|
||||
@@ -246,6 +246,7 @@ pub async fn now_from_db<'c, E: sqlx::PgExecutor<'c>>(
|
||||
) -> Result<chrono::DateTime<chrono::Utc>> {
|
||||
Ok(sqlx::query_scalar!("SELECT now()")
|
||||
.fetch_one(db)
|
||||
.warn_after_seconds_with_sql(1, "now_from_db".to_string())
|
||||
.await?
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
@@ -165,11 +165,11 @@ pub async fn get_secret_value_as_admin(
|
||||
if !value.is_empty() {
|
||||
let mc = build_crypt(db, w_id).await?;
|
||||
decrypt(&mc, value).map_err(|e| {
|
||||
crate::error::Error::internal_err(format!(
|
||||
"Error decrypting variable {}: {}",
|
||||
variable.path, e
|
||||
))
|
||||
})?
|
||||
crate::error::Error::internal_err(format!(
|
||||
"Error decrypting variable {}: {}",
|
||||
variable.path, e
|
||||
))
|
||||
})?
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
@@ -219,6 +219,7 @@ pub async fn get_reserved_variables(
|
||||
root_job_id: Option<String>,
|
||||
scheduled_for: Option<chrono::DateTime<Utc>>,
|
||||
runnable_id: Option<ScriptHash>,
|
||||
end_user_email: Option<String>,
|
||||
) -> Vec<ContextualVariable> {
|
||||
let state_path = {
|
||||
let trigger = if schedule_path.is_some() {
|
||||
@@ -389,6 +390,12 @@ pub async fn get_reserved_variables(
|
||||
description: "Hash of the script. Useful as cache key for cache that should be runnable specific.".to_string(),
|
||||
is_custom: false,
|
||||
},
|
||||
ContextualVariable {
|
||||
name: "WM_END_USER_EMAIL".to_string(),
|
||||
value: end_user_email.unwrap_or_else(|| "".to_string()),
|
||||
description: "Email of the end user that executed the current script. Only available when triggered from an app.".to_string(),
|
||||
is_custom: false,
|
||||
},
|
||||
].into_iter().chain(custom_envs.into_iter().map(|(name, value)| ContextualVariable {
|
||||
name,
|
||||
value,
|
||||
|
||||
@@ -395,7 +395,7 @@ fn format_pull_query(peek: String) -> String {
|
||||
j.timeout, j.flow_step_id, j.cache_ttl, j.priority, j.raw_code, j.raw_lock, j.raw_flow,
|
||||
j.script_entrypoint_override, j.preprocessed, pj.runnable_path as parent_runnable_path,
|
||||
COALESCE(p.email, j.permissioned_as_email) as permissioned_as_email, p.username as permissioned_as_username, p.is_admin as permissioned_as_is_admin,
|
||||
p.is_operator as permissioned_as_is_operator, p.groups as permissioned_as_groups, p.folders as permissioned_as_folders
|
||||
p.is_operator as permissioned_as_is_operator, p.groups as permissioned_as_groups, p.folders as permissioned_as_folders, p.end_user_email as permissioned_as_end_user_email
|
||||
FROM q, j
|
||||
LEFT JOIN v2_job_status f USING (id)
|
||||
LEFT JOIN job_perms p ON p.job_id = j.id
|
||||
|
||||
@@ -466,6 +466,7 @@ pub async fn push_init_job<'c>(
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
inner_tx.commit().await?;
|
||||
@@ -520,6 +521,7 @@ pub async fn push_periodic_bash_job<'c>(
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
inner_tx.commit().await?;
|
||||
@@ -766,6 +768,11 @@ lazy_static::lazy_static! {
|
||||
pub static ref MAX_RESULT_SIZE_MB: usize = std::env::var("MAX_RESULT_SIZE_MB").unwrap_or("500".to_string()).parse().unwrap_or(500);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct OutputWrapper {
|
||||
output: String,
|
||||
}
|
||||
|
||||
pub async fn add_completed_job<T: Serialize + Send + Sync + ValidableJson>(
|
||||
db: &DB,
|
||||
job_queue_db: &DB,
|
||||
@@ -845,6 +852,59 @@ pub async fn add_completed_job<T: Serialize + Send + Sync + ValidableJson>(
|
||||
|
||||
restart_job_if_perpetual(db, queued_job, &canceled_by).await?;
|
||||
|
||||
// Update conversation message if it's a flow and it's done (both success and error cases)
|
||||
if !skipped && flow_is_done {
|
||||
let chat_input_enabled = queued_job.parse_chat_input_enabled();
|
||||
if chat_input_enabled.unwrap_or(false) {
|
||||
let content = if let Ok(wrapper) = serde_json::from_value::<OutputWrapper>(
|
||||
serde_json::to_value(result.0).unwrap_or(serde_json::Value::Null),
|
||||
) {
|
||||
// Successfully deserialized to OutputWrapper, use the output field
|
||||
wrapper.output
|
||||
} else {
|
||||
// No string output field, use the whole result
|
||||
serde_json::to_value(result.0)
|
||||
.ok()
|
||||
.and_then(|v| {
|
||||
if let serde_json::Value::String(s) = v {
|
||||
Some(s)
|
||||
} else {
|
||||
serde_json::to_string_pretty(&v).ok()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
if success {
|
||||
"Job completed successfully".to_string()
|
||||
} else {
|
||||
"Job failed".to_string()
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// check if flow_conversation_message exists
|
||||
let flow_conversation_message_exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM flow_conversation_message WHERE job_id = $1 AND message_type = 'assistant')",
|
||||
queued_job.id
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
|
||||
if flow_conversation_message_exists.unwrap_or(false) {
|
||||
// Update the assistant message using direct DB access
|
||||
let _ = sqlx::query!(
|
||||
"UPDATE flow_conversation_message
|
||||
SET content = $1
|
||||
WHERE job_id = $2
|
||||
",
|
||||
content,
|
||||
queued_job.id,
|
||||
)
|
||||
.execute(db)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tracing::error!("4 {:?}", start.elapsed());
|
||||
|
||||
Ok((queued_job.id, duration))
|
||||
@@ -1363,6 +1423,7 @@ async fn restart_job_if_perpetual_inner(
|
||||
queued_job.priority,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -1850,6 +1911,7 @@ pub async fn push_error_handler<'a, 'c, T: Serialize + Send + Sync>(
|
||||
priority,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -1905,6 +1967,12 @@ pub struct MiniPulledJob {
|
||||
pub trigger: Option<String>,
|
||||
pub trigger_kind: Option<JobTriggerKind>,
|
||||
pub visible_to_owner: bool,
|
||||
pub permissioned_as_end_user_email: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct FlowStatusChatInputEnabled {
|
||||
chat_input_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
impl MiniPulledJob {
|
||||
@@ -1931,6 +1999,13 @@ impl MiniPulledJob {
|
||||
.and_then(|v| serde_json::from_str::<FlowStatus>((**v).get()).ok())
|
||||
}
|
||||
|
||||
pub fn parse_chat_input_enabled(&self) -> Option<bool> {
|
||||
self.flow_status
|
||||
.as_ref()
|
||||
.and_then(|v| serde_json::from_str::<FlowStatusChatInputEnabled>((**v).get()).ok())
|
||||
.and_then(|f| f.chat_input_enabled)
|
||||
}
|
||||
|
||||
pub fn from(job: &QueuedJob) -> MiniPulledJob {
|
||||
MiniPulledJob {
|
||||
workspace_id: job.workspace_id.clone(),
|
||||
@@ -1969,6 +2044,7 @@ impl MiniPulledJob {
|
||||
None
|
||||
},
|
||||
visible_to_owner: job.visible_to_owner.clone(),
|
||||
permissioned_as_end_user_email: None,
|
||||
}
|
||||
}
|
||||
pub fn is_flow(&self) -> bool {
|
||||
@@ -2169,7 +2245,8 @@ pub async fn get_mini_pulled_job<'c>(
|
||||
script_entrypoint_override,
|
||||
trigger,
|
||||
trigger_kind as \"trigger_kind: JobTriggerKind\",
|
||||
visible_to_owner
|
||||
visible_to_owner,
|
||||
NULL as permissioned_as_end_user_email
|
||||
FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id WHERE v2_job_queue.id = $1",
|
||||
job_id,
|
||||
)
|
||||
@@ -2182,6 +2259,37 @@ pub async fn get_mini_pulled_job<'c>(
|
||||
pub struct PulledJobResult {
|
||||
pub job: Option<PulledJob>,
|
||||
pub suspended: bool,
|
||||
pub missing_concurrency_key: bool,
|
||||
}
|
||||
|
||||
pub enum PulledJobResultToJobErr {
|
||||
MissingConcurrencyKey(JobCompleted),
|
||||
}
|
||||
|
||||
impl PulledJobResult {
|
||||
pub fn to_pulled_job(self) -> Result<Option<PulledJob>, PulledJobResultToJobErr> {
|
||||
match self {
|
||||
PulledJobResult { job: Some(job), missing_concurrency_key: true, .. } => Err(
|
||||
PulledJobResultToJobErr::MissingConcurrencyKey(JobCompleted {
|
||||
preprocessed_args: None,
|
||||
job: Arc::new(job.job),
|
||||
success: false,
|
||||
result: Arc::new(windmill_common::worker::to_raw_value(&json!({
|
||||
"name": "InternalErr",
|
||||
"message": "The job has a concurrency limit but concurrency key couldn't be found. This is an unexpected behavior that should never happen. Please report this to support."}
|
||||
))),
|
||||
result_columns: None,
|
||||
mem_peak: 0,
|
||||
cached_res_path: None,
|
||||
token: "".to_string(),
|
||||
canceled_by: None,
|
||||
duration: None,
|
||||
has_stream: Some(false),
|
||||
}),
|
||||
),
|
||||
PulledJobResult { job, .. } => Ok(job),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn pull(
|
||||
@@ -2201,7 +2309,11 @@ pub async fn pull(
|
||||
}
|
||||
if pull_loop_count > 1000 {
|
||||
tracing::error!("Pull job loop count exceeded 1000, breaking");
|
||||
return Ok(PulledJobResult { job: None, suspended: false });
|
||||
return Ok(PulledJobResult {
|
||||
job: None,
|
||||
suspended: false,
|
||||
missing_concurrency_key: false,
|
||||
});
|
||||
}
|
||||
if let Some((query_suspended, query_no_suspend)) = query_o {
|
||||
let njob = {
|
||||
@@ -2213,16 +2325,43 @@ pub async fn pull(
|
||||
.fetch_optional(job_queue_db)
|
||||
.await?
|
||||
};
|
||||
if let Some(job) = job {
|
||||
PulledJobResult { job: Some(job), suspended: true }
|
||||
|
||||
let (job, suspended) = if let Some(job) = job {
|
||||
(Some(job), true)
|
||||
} else {
|
||||
let job = sqlx::query_as::<_, PulledJob>(query_no_suspend)
|
||||
.bind(worker_name)
|
||||
.fetch_optional(job_queue_db)
|
||||
.await?;
|
||||
PulledJobResult { job, suspended: false }
|
||||
}
|
||||
};
|
||||
(job, false)
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
let pulled_job_result = match job {
|
||||
Some(job) if job.concurrent_limit.is_some() => {
|
||||
let job = crate::jobs_ee::apply_concurrency_limit(
|
||||
db,
|
||||
pull_loop_count,
|
||||
suspended,
|
||||
job,
|
||||
)
|
||||
.await?;
|
||||
job.unwrap_or(PulledJobResult {
|
||||
job: None,
|
||||
suspended,
|
||||
missing_concurrency_key: false,
|
||||
})
|
||||
}
|
||||
_ => PulledJobResult { job, suspended, missing_concurrency_key: false },
|
||||
};
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "private")))]
|
||||
let pulled_job_result =
|
||||
PulledJobResult { job, suspended, missing_concurrency_key: false };
|
||||
|
||||
Ok::<_, Error>(pulled_job_result)
|
||||
}?;
|
||||
|
||||
if let Some(job) = njob.job.as_ref() {
|
||||
if job.is_flow() || job.is_dependency() {
|
||||
let per_workspace = per_workspace_tag(&job.workspace_id).await;
|
||||
@@ -2258,7 +2397,7 @@ pub async fn pull(
|
||||
.await?;
|
||||
|
||||
let Some(job) = job else {
|
||||
return Ok(PulledJobResult { job: None, suspended });
|
||||
return Ok(PulledJobResult { job: None, suspended, missing_concurrency_key: false });
|
||||
};
|
||||
|
||||
let has_concurent_limit = job.concurrent_limit.is_some();
|
||||
@@ -2281,7 +2420,11 @@ pub async fn pull(
|
||||
if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
QUEUE_PULL_COUNT.inc();
|
||||
}
|
||||
return Ok(PulledJobResult { job: Some(pulled_job), suspended });
|
||||
return Ok(PulledJobResult {
|
||||
job: Some(pulled_job),
|
||||
suspended,
|
||||
missing_concurrency_key: false,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
@@ -3062,6 +3205,7 @@ pub async fn push<'c, 'd>(
|
||||
_priority_override: Option<i16>,
|
||||
authed: Option<&Authed>,
|
||||
running: bool, // whether the job is already running: only set this to true if you don't want the job to be picked up by a worker from the queue. It will also set started_at to now.
|
||||
end_user_email: Option<String>,
|
||||
) -> Result<(Uuid, Transaction<'c, Postgres>), Error> {
|
||||
#[cfg(feature = "cloud")]
|
||||
if *CLOUD_HOSTED {
|
||||
@@ -3558,6 +3702,8 @@ pub async fn push<'c, 'd>(
|
||||
user_states,
|
||||
preprocessor_module: None,
|
||||
stream_job: None,
|
||||
chat_input_enabled: None,
|
||||
memory_id: None,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -3688,6 +3834,7 @@ pub async fn push<'c, 'd>(
|
||||
early_return: None,
|
||||
skip_expr: None,
|
||||
preprocessor_module: None,
|
||||
chat_input_enabled: None,
|
||||
};
|
||||
// this is a new flow being pushed, flow_status is set to flow_value:
|
||||
let flow_status: FlowStatus = FlowStatus::new(&flow_value);
|
||||
@@ -3813,6 +3960,8 @@ pub async fn push<'c, 'd>(
|
||||
user_states,
|
||||
preprocessor_module: None,
|
||||
stream_job: None,
|
||||
chat_input_enabled: None,
|
||||
memory_id: None,
|
||||
};
|
||||
let value = flow_data.value();
|
||||
let priority = value.priority;
|
||||
@@ -4164,8 +4313,8 @@ pub async fn push<'c, 'd>(
|
||||
INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)
|
||||
),
|
||||
inserted_job_perms AS (
|
||||
INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id)
|
||||
values ($1, $32, $33, $34, $35, $36, $37, $2)
|
||||
INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email)
|
||||
values ($1, $32, $33, $34, $35, $36, $37, $2, $41)
|
||||
ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2
|
||||
)
|
||||
INSERT INTO v2_job_queue
|
||||
@@ -4215,6 +4364,7 @@ pub async fn push<'c, 'd>(
|
||||
root_job,
|
||||
trigger_kind as Option<JobTriggerKind>,
|
||||
running,
|
||||
end_user_email,
|
||||
)
|
||||
.execute(if !shard_mode {&mut *tx} else {
|
||||
println!("Push");
|
||||
@@ -4681,7 +4831,7 @@ pub async fn get_same_worker_job(
|
||||
v2_job.raw_flow,
|
||||
pj.runnable_path as parent_runnable_path,
|
||||
p.email as permissioned_as_email, p.username as permissioned_as_username, p.is_admin as permissioned_as_is_admin,
|
||||
p.is_operator as permissioned_as_is_operator, p.groups as permissioned_as_groups, p.folders as permissioned_as_folders
|
||||
p.is_operator as permissioned_as_is_operator, p.groups as permissioned_as_groups, p.folders as permissioned_as_folders, p.end_user_email as permissioned_as_end_user_email
|
||||
FROM v2_job_queue
|
||||
INNER JOIN v2_job ON v2_job.id = v2_job_queue.id
|
||||
LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id
|
||||
|
||||
@@ -22,6 +22,7 @@ use windmill_common::get_latest_flow_version_info_for_path_from_version;
|
||||
use windmill_common::jobs::check_tag_available_for_workspace_internal;
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::schedule::schedule_to_user;
|
||||
use windmill_common::utils::WarnAfterExt;
|
||||
use windmill_common::FlowVersionInfo;
|
||||
use windmill_common::DB;
|
||||
use windmill_common::{
|
||||
@@ -76,6 +77,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
&schedule.path
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.warn_after_seconds_with_sql(1, "update_schedule_paused_until".to_string())
|
||||
.await
|
||||
.context("Failed to clear paused_until for schedule")?;
|
||||
}
|
||||
@@ -107,6 +109,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
&schedule.script_path
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.warn_after_seconds_with_sql(1, "already_exists_job".to_string())
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
@@ -141,6 +144,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
&schedule.script_path,
|
||||
false,
|
||||
)
|
||||
.warn_after_seconds_with_sql(1, "get_latest_flow_version_id_for_path".to_string())
|
||||
.await?;
|
||||
|
||||
let FlowVersionInfo {
|
||||
@@ -151,6 +155,10 @@ pub async fn push_scheduled_job<'c>(
|
||||
&schedule.workspace_id,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.warn_after_seconds_with_sql(
|
||||
1,
|
||||
"get_latest_flow_version_info_for_path_from_version".to_string(),
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
JobPayload::Flow {
|
||||
@@ -184,6 +192,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
&schedule.script_path,
|
||||
false,
|
||||
)
|
||||
.warn_after_seconds_with_sql(1, "get_latest_hash_for_path".to_string())
|
||||
.await?;
|
||||
|
||||
if schedule.retry.is_some() {
|
||||
@@ -257,6 +266,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
&schedule.path
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.warn_after_seconds_with_sql(1, "clear_schedule_error".to_string())
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
@@ -272,10 +282,12 @@ pub async fn push_scheduled_job<'c>(
|
||||
let is_windmill_user =
|
||||
sqlx::query_scalar!("SELECT CURRENT_USER = 'windmill_user' as \"is_windmill_user!\"")
|
||||
.fetch_one(&mut *tx)
|
||||
.warn_after_seconds_with_sql(1, "is_windmill_user".to_string())
|
||||
.await?;
|
||||
if is_windmill_user {
|
||||
sqlx::query!("SET LOCAL ROLE NONE")
|
||||
.execute(&mut *tx)
|
||||
.warn_after_seconds_with_sql(1, "set_local_role_none".to_string())
|
||||
.await?;
|
||||
}
|
||||
(
|
||||
@@ -301,6 +313,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
email,
|
||||
None, // no token for schedules so no scopes so no scope_tags
|
||||
)
|
||||
.warn_after_seconds_with_sql(1, "check_tag_available_for_workspace_internal".to_string())
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -331,12 +344,15 @@ pub async fn push_scheduled_job<'c>(
|
||||
None,
|
||||
push_authed,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.warn_after_seconds_with_sql(1, "push in push_scheduled_job".to_string())
|
||||
.await?;
|
||||
|
||||
if revert_to_windmill_user {
|
||||
sqlx::query!("SET LOCAL ROLE windmill_user")
|
||||
.execute(&mut *tx)
|
||||
.warn_after_seconds_with_sql(1, "set_local_role_windmill_user".to_string())
|
||||
.await?;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ pub struct AIAgentArgs {
|
||||
pub output_type: Option<OutputType>,
|
||||
pub user_images: Option<Vec<S3Object>>,
|
||||
pub streaming: Option<bool>,
|
||||
pub messages_context_length: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::memory_oss::{read_from_memory, write_to_memory};
|
||||
use anyhow::Context;
|
||||
use async_recursion::async_recursion;
|
||||
use regex::Regex;
|
||||
@@ -98,6 +99,20 @@ pub async fn get_flow_job_runnable_and_raw_flow(
|
||||
Ok(job)
|
||||
}
|
||||
|
||||
/// Get memory_id from parent flow's flow_status
|
||||
async fn get_memory_id_from_flow_status(db: &DB, parent_job: &Uuid) -> Result<Option<Uuid>, Error> {
|
||||
let result = sqlx::query_scalar!(
|
||||
"SELECT (flow_status->>'memory_id')::uuid as memory_id
|
||||
FROM v2_job_status
|
||||
WHERE id = $1",
|
||||
parent_job
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
|
||||
Ok(result.flatten())
|
||||
}
|
||||
|
||||
pub async fn handle_ai_agent_job(
|
||||
// connection
|
||||
conn: &Connection,
|
||||
@@ -310,7 +325,7 @@ pub async fn handle_ai_agent_job(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Find a unique tool name to avoid collisions with user-provided tools
|
||||
/// Find a unique tool name for structured output tool to avoid collisions with user-provided tools
|
||||
fn find_unique_tool_name(base_name: &str, existing_tools: Option<&[ToolDef]>) -> String {
|
||||
let Some(tools) = existing_tools else {
|
||||
return base_name.to_string();
|
||||
@@ -445,6 +460,39 @@ pub async fn run_agent(
|
||||
vec![]
|
||||
};
|
||||
|
||||
// Load previous messages from memory for text output mode (only if context length is set)
|
||||
if matches!(output_type, OutputType::Text) {
|
||||
if let Some(context_length) = args.messages_context_length.filter(|&n| n > 0) {
|
||||
if let Some(step_id) = job.flow_step_id.as_deref() {
|
||||
// Get memory_id from flow_status
|
||||
if let Ok(Some(memory_id)) = get_memory_id_from_flow_status(db, parent_job).await {
|
||||
// Read messages from memory
|
||||
match read_from_memory(&job.workspace_id, memory_id, step_id).await {
|
||||
Ok(Some(loaded_messages)) => {
|
||||
// Take the last n messages
|
||||
let start_idx = loaded_messages.len().saturating_sub(context_length);
|
||||
let mut messages_to_load = loaded_messages[start_idx..].to_vec();
|
||||
|
||||
// Remove the first message if its role is "tool" to avoid OpenAI API error
|
||||
// "messages with role 'tool' must be a response to a preceeding message with 'tool_calls'"
|
||||
if let Some(first_msg) = messages_to_load.first() {
|
||||
if first_msg.role == "tool" {
|
||||
messages_to_load.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
messages.extend(messages_to_load);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to read memory for step {}: {}", step_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create user message with optional images
|
||||
let mut parts = vec![ContentPart::Text { text: args.user_message.clone() }];
|
||||
if let Some(images) = &args.user_images {
|
||||
@@ -808,6 +856,7 @@ pub async fn run_agent(
|
||||
job_priority,
|
||||
job_perms.as_ref(),
|
||||
true,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -835,30 +884,69 @@ pub async fn run_agent(
|
||||
#[cfg(feature = "benchmark")]
|
||||
let mut bench = windmill_common::bench::BenchmarkIter::new();
|
||||
|
||||
match handle_queued_job(
|
||||
tool_job.clone(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
conn,
|
||||
Some(job_queue_db),
|
||||
client,
|
||||
hostname,
|
||||
worker_name,
|
||||
worker_dir,
|
||||
&job_dir,
|
||||
None,
|
||||
base_internal_url,
|
||||
inner_job_completed_tx,
|
||||
occupancy_metrics,
|
||||
killpill_rx,
|
||||
None,
|
||||
// Spawn handle_queued_job on separate task to prevent tokio stack overflow
|
||||
// Clone everything needed for the spawned task
|
||||
let tool_job_spawn = tool_job.clone();
|
||||
let conn_spawn = conn.clone();
|
||||
let client_spawn = client.clone();
|
||||
let hostname_spawn = hostname.to_string();
|
||||
let worker_name_spawn = worker_name.to_string();
|
||||
let worker_dir_spawn = worker_dir.to_string();
|
||||
let job_dir_spawn = job_dir.clone();
|
||||
let base_internal_url_spawn = base_internal_url.to_string();
|
||||
let inner_job_completed_tx_spawn = inner_job_completed_tx.clone();
|
||||
let mut occupancy_metrics_spawn = occupancy_metrics.clone();
|
||||
let mut killpill_rx_spawn = killpill_rx.resubscribe();
|
||||
|
||||
// Spawn on separate tokio task with fresh stack
|
||||
let join_handle = tokio::task::spawn(async move {
|
||||
#[cfg(feature = "benchmark")]
|
||||
&mut bench,
|
||||
)
|
||||
.await
|
||||
{
|
||||
let mut bench_spawn =
|
||||
windmill_common::bench::BenchmarkIter::new();
|
||||
|
||||
let result = handle_queued_job(
|
||||
tool_job_spawn,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&conn_spawn,
|
||||
Some(job_queue_db),
|
||||
&client_spawn,
|
||||
&hostname_spawn,
|
||||
&worker_name_spawn,
|
||||
&worker_dir_spawn,
|
||||
&job_dir_spawn,
|
||||
None,
|
||||
&base_internal_url_spawn,
|
||||
inner_job_completed_tx_spawn,
|
||||
&mut occupancy_metrics_spawn,
|
||||
&mut killpill_rx_spawn,
|
||||
None,
|
||||
#[cfg(feature = "benchmark")]
|
||||
&mut bench_spawn,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Return both result and updated metrics
|
||||
(result, occupancy_metrics_spawn)
|
||||
});
|
||||
|
||||
// Await the spawned task
|
||||
let (handle_result, updated_occupancy) =
|
||||
join_handle.await.map_err(|e| {
|
||||
Error::internal_err(format!(
|
||||
"Tool execution task panicked: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
// Merge occupancy metrics back
|
||||
occupancy_metrics.total_duration_of_running_jobs =
|
||||
updated_occupancy.total_duration_of_running_jobs;
|
||||
|
||||
// Continue with match on handle_result
|
||||
match handle_result {
|
||||
Err(err) => {
|
||||
let err_string =
|
||||
format!("{}: {}", err.name(), err.to_string());
|
||||
@@ -1040,6 +1128,45 @@ pub async fn run_agent(
|
||||
}
|
||||
}
|
||||
|
||||
// Persist complete conversation to memory at the end (only if context length is set)
|
||||
// final_messages contains the complete history (old messages + new ones)
|
||||
if matches!(output_type, OutputType::Text) {
|
||||
if let Some(context_length) = args.messages_context_length.filter(|&n| n > 0) {
|
||||
if let Some(step_id) = job.flow_step_id.as_deref() {
|
||||
// Extract OpenAIMessages from final_messages
|
||||
let all_messages: Vec<OpenAIMessage> =
|
||||
final_messages.iter().map(|m| m.message.clone()).collect();
|
||||
|
||||
if !all_messages.is_empty() {
|
||||
// Keep only the last n messages
|
||||
let start_idx = all_messages.len().saturating_sub(context_length);
|
||||
let messages_to_persist = all_messages[start_idx..].to_vec();
|
||||
|
||||
// Get memory_id from flow_status
|
||||
if let Ok(Some(memory_id)) =
|
||||
get_memory_id_from_flow_status(db, parent_job).await
|
||||
{
|
||||
if let Err(e) = write_to_memory(
|
||||
&job.workspace_id,
|
||||
memory_id,
|
||||
step_id,
|
||||
&messages_to_persist,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to persist {} messages to memory for step {}: {}",
|
||||
messages_to_persist.len(),
|
||||
step_id,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(to_raw_value(&AIAgentResult {
|
||||
output: output_value,
|
||||
messages: final_messages,
|
||||
|
||||
@@ -25,7 +25,11 @@ use crate::{
|
||||
DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, NPM_CONFIG_REGISTRY,
|
||||
NPM_PATH, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TZ_ENV,
|
||||
};
|
||||
use windmill_common::{client::AuthedClient, s3_helpers::BundleFormat, scripts::{id_to_codebase_info, CodebaseInfo}};
|
||||
use windmill_common::{
|
||||
client::AuthedClient,
|
||||
s3_helpers::BundleFormat,
|
||||
scripts::{id_to_codebase_info, CodebaseInfo},
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::SYSTEM_ROOT;
|
||||
@@ -910,7 +914,6 @@ pub async fn handle_bun_job(
|
||||
let common_bun_proc_envs: HashMap<String, String> =
|
||||
get_common_bun_proc_envs(Some(&base_internal_url)).await;
|
||||
|
||||
|
||||
let main_override = job.script_entrypoint_override.as_deref();
|
||||
let apply_preprocessor =
|
||||
job.flow_step_id.as_deref() != Some("preprocessor") && job.preprocessed == Some(false);
|
||||
@@ -1621,11 +1624,11 @@ pub async fn start_worker(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let context_envs = build_envs_map(context.to_vec()).await;
|
||||
|
||||
|
||||
let mut format = BundleFormat::Cjs;
|
||||
if let Some(codebase) = codebase.as_ref() {
|
||||
let pulled_codebase = pull_codebase(w_id, codebase, job_dir).await?;
|
||||
|
||||
@@ -452,6 +452,7 @@ pub async fn get_reserved_variables(
|
||||
Some(get_root_job_id(job).to_string()),
|
||||
Some(job.scheduled_for.clone()),
|
||||
job.runnable_id,
|
||||
job.permissioned_as_end_user_email.clone(),
|
||||
)
|
||||
.await
|
||||
.to_vec();
|
||||
|
||||
@@ -556,6 +556,7 @@ pub async fn start_worker(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let context_envs = build_envs_map(context.to_vec()).await;
|
||||
|
||||
@@ -10,7 +10,7 @@ use serde_json::value::RawValue;
|
||||
use serde_json::{json, Value};
|
||||
use uuid::Uuid;
|
||||
use windmill_common::error::{to_anyhow, Error, Result};
|
||||
use windmill_common::s3_helpers::S3Object;
|
||||
use windmill_common::s3_helpers::{S3Object, S3_PROXY_LAST_ERRORS_CACHE};
|
||||
use windmill_common::utils::sanitize_string_from_password;
|
||||
use windmill_common::worker::Connection;
|
||||
use windmill_common::workspaces::{get_ducklake_from_db_unchecked, DucklakeCatalogResourceType};
|
||||
@@ -128,7 +128,7 @@ pub async fn do_duckdb(
|
||||
let base_internal_url = client.base_internal_url.clone();
|
||||
let w_id = job.workspace_id.clone();
|
||||
|
||||
let (result, column_order) = tokio::task::spawn_blocking(move || {
|
||||
let result = tokio::task::spawn_blocking(move || {
|
||||
run_duckdb_ffi_safe(
|
||||
query_block_list.iter().map(String::as_str),
|
||||
query_block_list.len(),
|
||||
@@ -139,7 +139,21 @@ pub async fn do_duckdb(
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map_err(to_anyhow)??;
|
||||
.map_err(|e| Error::from(to_anyhow(e)))
|
||||
.and_then(|r| r);
|
||||
let (result, column_order) = match result {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
if let Some(s3_proxy_err) = S3_PROXY_LAST_ERRORS_CACHE.get(&client.token) {
|
||||
return Err(Error::ExecutionErr(format!(
|
||||
"{}\n\nS3 Related Error: {}",
|
||||
e.to_string(),
|
||||
s3_proxy_err,
|
||||
)));
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
drop(bigquery_credentials);
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ pub mod job_logger;
|
||||
pub mod job_logger_ee;
|
||||
mod job_logger_oss;
|
||||
mod js_eval;
|
||||
pub mod memory_common;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod memory_ee;
|
||||
pub mod memory_oss;
|
||||
#[cfg(feature = "mysql")]
|
||||
mod mysql_executor;
|
||||
#[cfg(feature = "nu")]
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
use crate::ai::types::OpenAIMessage;
|
||||
use std::path::PathBuf;
|
||||
use tokio::{fs, io::AsyncWriteExt};
|
||||
use uuid::Uuid;
|
||||
use windmill_common::worker::TMP_LOGS_DIR;
|
||||
|
||||
/// Get the file path for storing memory for a specific AI agent step
|
||||
pub fn path_for(workspace_id: &str, conversation_id: Uuid, step_id: &str) -> PathBuf {
|
||||
PathBuf::from(TMP_LOGS_DIR)
|
||||
.join("memory")
|
||||
.join(workspace_id)
|
||||
.join(conversation_id.to_string())
|
||||
.join(format!("{step_id}.json"))
|
||||
}
|
||||
|
||||
/// Read messages from disk storage
|
||||
pub async fn read_from_disk(
|
||||
workspace_id: &str,
|
||||
conversation_id: Uuid,
|
||||
step_id: &str,
|
||||
) -> anyhow::Result<Option<Vec<OpenAIMessage>>> {
|
||||
let path = path_for(workspace_id, conversation_id, step_id);
|
||||
if !fs::try_exists(&path).await? {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let bytes = fs::read(&path).await?;
|
||||
let messages: Vec<OpenAIMessage> = serde_json::from_slice(&bytes)?;
|
||||
Ok(Some(messages))
|
||||
}
|
||||
|
||||
/// Write messages to disk storage
|
||||
pub async fn write_to_disk(
|
||||
workspace_id: &str,
|
||||
conversation_id: Uuid,
|
||||
step_id: &str,
|
||||
messages: &[OpenAIMessage],
|
||||
) -> anyhow::Result<()> {
|
||||
let path = path_for(workspace_id, conversation_id, step_id);
|
||||
|
||||
// Ensure parent directories exist
|
||||
if let Some(dir) = path.parent() {
|
||||
fs::create_dir_all(dir).await?;
|
||||
}
|
||||
|
||||
// Write atomically using a temporary file
|
||||
let tmp = path.with_extension("json.tmp");
|
||||
let mut f = fs::File::create(&tmp).await?;
|
||||
f.write_all(&serde_json::to_vec(messages)?).await?;
|
||||
f.flush().await?;
|
||||
drop(f);
|
||||
|
||||
// Atomic rename
|
||||
fs::rename(tmp, &path).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete all memory for a conversation from disk storage
|
||||
pub async fn delete_conversation_from_disk(
|
||||
workspace_id: &str,
|
||||
conversation_id: Uuid,
|
||||
) -> anyhow::Result<()> {
|
||||
let conversation_path = PathBuf::from(TMP_LOGS_DIR)
|
||||
.join("memory")
|
||||
.join(workspace_id)
|
||||
.join(conversation_id.to_string());
|
||||
|
||||
if fs::try_exists(&conversation_path).await? {
|
||||
fs::remove_dir_all(&conversation_path).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#[cfg(all(feature = "private", feature = "enterprise"))]
|
||||
#[allow(unused)]
|
||||
pub use crate::memory_ee::*;
|
||||
|
||||
#[cfg(not(all(feature = "private", feature = "enterprise")))]
|
||||
use {crate::ai::types::OpenAIMessage, crate::memory_common, uuid::Uuid};
|
||||
|
||||
/// Read AI agent memory from storage
|
||||
/// In OSS: always reads from disk
|
||||
#[cfg(not(all(feature = "private", feature = "enterprise")))]
|
||||
pub async fn read_from_memory(
|
||||
workspace_id: &str,
|
||||
conversation_id: Uuid,
|
||||
step_id: &str,
|
||||
) -> anyhow::Result<Option<Vec<OpenAIMessage>>> {
|
||||
memory_common::read_from_disk(workspace_id, conversation_id, step_id).await
|
||||
}
|
||||
|
||||
/// Write AI agent memory to storage
|
||||
/// In OSS: always writes to disk
|
||||
#[cfg(not(all(feature = "private", feature = "enterprise")))]
|
||||
pub async fn write_to_memory(
|
||||
workspace_id: &str,
|
||||
conversation_id: Uuid,
|
||||
step_id: &str,
|
||||
messages: &[OpenAIMessage],
|
||||
) -> anyhow::Result<()> {
|
||||
if messages.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
memory_common::write_to_disk(workspace_id, conversation_id, step_id, messages).await
|
||||
}
|
||||
|
||||
/// Delete all memory for a conversation from storage
|
||||
/// In OSS: always deletes from disk
|
||||
#[cfg(not(all(feature = "private", feature = "enterprise")))]
|
||||
pub async fn delete_conversation_memory(
|
||||
workspace_id: &str,
|
||||
conversation_id: Uuid,
|
||||
) -> anyhow::Result<()> {
|
||||
memory_common::delete_conversation_from_disk(workspace_id, conversation_id).await
|
||||
}
|
||||
@@ -88,7 +88,7 @@ fn do_postgresql_inner<'a>(
|
||||
let arg_t = arg
|
||||
.otyp
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing otzyp for pg arg"))?;
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing otyp for pg arg"))?;
|
||||
let typ = &arg.typ;
|
||||
let param = convert_val(value, arg_t, typ)?;
|
||||
query_params.push(param);
|
||||
|
||||
@@ -2173,6 +2173,7 @@ pub async fn start_worker(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.to_vec();
|
||||
@@ -2294,6 +2295,7 @@ for line in sys.stdin:
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use windmill_parser::MainArgSignature;
|
||||
use windmill_queue::PulledJobResultToJobErr;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -161,7 +162,7 @@ use crate::mysql_executor::do_mysql;
|
||||
#[cfg(feature = "duckdb")]
|
||||
use crate::duckdb_executor::do_duckdb;
|
||||
|
||||
#[cfg(feature = "oracledb")]
|
||||
#[cfg(all(feature = "enterprise", feature = "oracledb"))]
|
||||
use crate::oracledb_executor::do_oracledb;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
@@ -827,7 +828,18 @@ pub fn start_interactive_worker_shell(
|
||||
)
|
||||
.await;
|
||||
|
||||
job.map(|x| x.job.map(NextJob::Sql))
|
||||
match job {
|
||||
Ok(j) => match j.to_pulled_job() {
|
||||
Ok(j) => Ok(j.map(NextJob::Sql)),
|
||||
Err(PulledJobResultToJobErr::MissingConcurrencyKey(jc)) => {
|
||||
if let Err(err) = job_completed_tx.send_job(jc, true).await {
|
||||
tracing::error!("An error occurred while sending job completed (missing concurrency key): {:#?}", err)
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
Connection::Http(client) => {
|
||||
crate::agent_workers::pull_job(&client, None, Some(true))
|
||||
@@ -1646,7 +1658,18 @@ pub async fn run_worker(
|
||||
}
|
||||
}
|
||||
}
|
||||
job.map(|x| x.job.map(NextJob::Sql))
|
||||
match job {
|
||||
Ok(pulled_job_result) => match pulled_job_result.to_pulled_job() {
|
||||
Ok(j) => Ok(j.map(NextJob::Sql)),
|
||||
Err(PulledJobResultToJobErr::MissingConcurrencyKey(jc)) => {
|
||||
if let Err(err) = job_completed_tx.send_job(jc, true).await {
|
||||
tracing::error!("An error occurred while sending job completed (missing concurrency key): {:#?}", err)
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
Connection::Http(client) => crate::agent_workers::pull_job(&client, None, None)
|
||||
.await
|
||||
@@ -2388,14 +2411,16 @@ pub async fn handle_queued_job(
|
||||
| JobKind::Flow
|
||||
| JobKind::FlowDependencies,
|
||||
x,
|
||||
) => if x.map(|x| x.0).is_none_or(|x| is_special_codebase_hash(x)) {
|
||||
Some(
|
||||
cache::job::fetch_preview(conn, &job.id, raw_lock, raw_code, raw_flow.clone())
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
) => {
|
||||
if x.map(|x| x.0).is_none_or(|x| is_special_codebase_hash(x)) {
|
||||
Some(
|
||||
cache::job::fetch_preview(conn, &job.id, raw_lock, raw_code, raw_flow.clone())
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -2889,7 +2914,9 @@ async fn handle_code_execution_job(
|
||||
ScriptMetadata { language, envs, codebase, schema_validator, schema },
|
||||
) = match job.kind {
|
||||
JobKind::Preview => {
|
||||
let codebase = job.runnable_id.and_then(|x| hash_to_codebase_id(&job.id.to_string(), x.0));
|
||||
let codebase = job
|
||||
.runnable_id
|
||||
.and_then(|x| hash_to_codebase_id(&job.id.to_string(), x.0));
|
||||
if codebase.is_none() && job.runnable_id.is_some() {
|
||||
(arc_data, arc_metadata) =
|
||||
cache::script::fetch(conn, job.runnable_id.unwrap()).await?;
|
||||
|
||||
@@ -1246,14 +1246,14 @@ pub async fn update_flow_status_after_job_completion_internal(
|
||||
if let Some(t) = tag {
|
||||
tag = Some(interpolate_args(t, &args, &flow_job.workspace_id));
|
||||
}
|
||||
} else if let Some(ck) = concurrency_key {
|
||||
} else if concurrent_limit.is_some() {
|
||||
let mut tx = db.begin().await?;
|
||||
insert_concurrency_key(
|
||||
&flow_job.workspace_id,
|
||||
&PushArgs::from(&HashMap::new()),
|
||||
&flow_job.runnable_path,
|
||||
JobKind::Flow,
|
||||
Some(ck),
|
||||
concurrency_key,
|
||||
&mut tx,
|
||||
flow,
|
||||
)
|
||||
@@ -3170,6 +3170,7 @@ async fn push_next_flow_job(
|
||||
new_job_priority_override,
|
||||
job_perms.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.warn_after_seconds(2)
|
||||
.await?;
|
||||
|
||||
@@ -833,6 +833,7 @@ pub async fn trigger_dependents_to_recompute_dependencies(
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!(
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
|
||||
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
|
||||
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
|
||||
|
||||
export const VERSION = "v1.554.0";
|
||||
export const VERSION = "v1.555.1";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
+5
-1
@@ -16,4 +16,8 @@ set -e
|
||||
echo "Running dnt..."
|
||||
deno run -A dnt.ts
|
||||
|
||||
echo "Build complete!"
|
||||
# Remove .ts extensions after building to go back to the original state
|
||||
./windmill-utils-internal/remove-ts-ext.sh
|
||||
|
||||
echo "Build complete!"
|
||||
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.554.0";
|
||||
export const VERSION = "1.555.1";
|
||||
|
||||
export const WM_FORK_PREFIX = "wm-fork";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { newPathAssigner } from "../path-utils/path-assigner";
|
||||
import { newPathAssigner, PathAssigner } from "../path-utils/path-assigner";
|
||||
import { FlowModule } from "../gen/types.gen";
|
||||
|
||||
/**
|
||||
@@ -17,20 +17,25 @@ interface InlineScript {
|
||||
*
|
||||
* @param modules - Array of flow modules to process
|
||||
* @param mapping - Optional mapping of module IDs to custom file paths
|
||||
* @param separator - Path separator to use
|
||||
* @param defaultTs - Default TypeScript runtime to use ("bun" or "deno")
|
||||
* @param pathAssigner - Optional path assigner to reuse (for nested calls)
|
||||
* @returns Array of inline scripts with their paths and content
|
||||
*/
|
||||
export function extractInlineScripts(
|
||||
modules: FlowModule[],
|
||||
mapping: Record<string, string> = {},
|
||||
separator: string = "/",
|
||||
defaultTs?: "bun" | "deno"
|
||||
defaultTs?: "bun" | "deno",
|
||||
pathAssigner?: PathAssigner
|
||||
): InlineScript[] {
|
||||
const pathAssigner = newPathAssigner(defaultTs ?? "bun");
|
||||
// Create pathAssigner only if not provided (top-level call), but reuse it for nested calls
|
||||
const assigner = pathAssigner ?? newPathAssigner(defaultTs ?? "bun");
|
||||
|
||||
return modules.flatMap((m) => {
|
||||
if (m.value.type == "rawscript") {
|
||||
let basePath, ext;
|
||||
[basePath, ext] = pathAssigner.assignPath(m.summary, m.value.language);
|
||||
[basePath, ext] = assigner.assignPath(m.summary, m.value.language);
|
||||
const path = mapping[m.id] ?? basePath + ext;
|
||||
const content = m.value.content;
|
||||
const r = [{ path: path, content: content }];
|
||||
@@ -47,25 +52,39 @@ export function extractInlineScripts(
|
||||
m.value.modules,
|
||||
mapping,
|
||||
separator,
|
||||
defaultTs
|
||||
defaultTs,
|
||||
assigner
|
||||
);
|
||||
} else if (m.value.type == "branchall") {
|
||||
return m.value.branches.flatMap((b) =>
|
||||
extractInlineScripts(b.modules, mapping, separator, defaultTs)
|
||||
extractInlineScripts(b.modules, mapping, separator, defaultTs, assigner)
|
||||
);
|
||||
} else if (m.value.type == "whileloopflow") {
|
||||
return extractInlineScripts(
|
||||
m.value.modules,
|
||||
mapping,
|
||||
separator,
|
||||
defaultTs
|
||||
defaultTs,
|
||||
assigner
|
||||
);
|
||||
} else if (m.value.type == "branchone") {
|
||||
return [
|
||||
...m.value.branches.flatMap((b) =>
|
||||
extractInlineScripts(b.modules, mapping, separator, defaultTs)
|
||||
extractInlineScripts(
|
||||
b.modules,
|
||||
mapping,
|
||||
separator,
|
||||
defaultTs,
|
||||
assigner
|
||||
)
|
||||
),
|
||||
...extractInlineScripts(
|
||||
m.value.default,
|
||||
mapping,
|
||||
separator,
|
||||
defaultTs,
|
||||
assigner
|
||||
),
|
||||
...extractInlineScripts(m.value.default, mapping, separator, defaultTs),
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.554.0",
|
||||
"version": "1.555.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.554.0",
|
||||
"version": "1.555.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.554.0",
|
||||
"version": "1.555.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -349,8 +349,8 @@
|
||||
"default": "./package/gen/index.js"
|
||||
},
|
||||
"./components/flows/flowStore": {
|
||||
"types": "./package/components/flows/flowStore.d.ts",
|
||||
"default": "./package/components/flows/flowStore.js"
|
||||
"types": "./package/components/flows/flowStore.svelte.d.ts",
|
||||
"default": "./package/components/flows/flowStore.svelte.js"
|
||||
},
|
||||
"./components/icons": {
|
||||
"types": "./package/components/icons/index.d.ts",
|
||||
@@ -501,7 +501,7 @@
|
||||
"./package/gen/index.d.ts"
|
||||
],
|
||||
"components/flows/flowStore": [
|
||||
"./package/components/flows/flowStore.d.ts"
|
||||
"./package/components/flows/flowStore.svelte.d.ts"
|
||||
],
|
||||
"components/icons": [
|
||||
"./package/components/icons/index.d.ts"
|
||||
@@ -542,4 +542,4 @@
|
||||
"@rollup/rollup-linux-x64-gnu": "^4.35.0",
|
||||
"fsevents": "^2.3.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ console.log('Running postinstall for root project');
|
||||
|
||||
import { x } from 'tar'
|
||||
|
||||
const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-d44b577.tar.gz'
|
||||
const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-a3f259c.tar.gz'
|
||||
const outputTarPath = path.join(process.cwd(), 'ui_builder.tar.gz')
|
||||
const extractTo = path.join(process.cwd(), 'static/ui_builder/')
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { writable, get } from 'svelte/store'
|
||||
import { workspaceAIClients } from './components/copilot/lib'
|
||||
import { type AIProviderModel, type AIProvider, WorkspaceService, type AIConfig } from './gen'
|
||||
import { COPILOT_SESSION_MODEL_SETTING_NAME, COPILOT_SESSION_PROVIDER_SETTING_NAME } from './stores'
|
||||
import { getLocalSetting } from './utils'
|
||||
|
||||
const sessionModel = getLocalSetting(COPILOT_SESSION_MODEL_SETTING_NAME)
|
||||
const sessionProvider = getLocalSetting(COPILOT_SESSION_PROVIDER_SETTING_NAME)
|
||||
export const copilotSessionModel = writable<AIProviderModel | undefined>(
|
||||
sessionModel && sessionProvider
|
||||
? {
|
||||
model: sessionModel,
|
||||
provider: sessionProvider as AIProvider
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
|
||||
|
||||
export const copilotInfo = writable<{
|
||||
enabled: boolean
|
||||
codeCompletionModel?: AIProviderModel
|
||||
defaultModel?: AIProviderModel
|
||||
aiModels: AIProviderModel[]
|
||||
customPrompts?: Record<string, string>
|
||||
maxTokensPerModel?: Record<string, number>
|
||||
}>({
|
||||
enabled: false,
|
||||
codeCompletionModel: undefined,
|
||||
defaultModel: undefined,
|
||||
aiModels: [],
|
||||
customPrompts: {},
|
||||
maxTokensPerModel: {}
|
||||
})
|
||||
|
||||
export async function loadCopilot(workspace: string) {
|
||||
workspaceAIClients.init(workspace)
|
||||
try {
|
||||
const info = await WorkspaceService.getCopilotInfo({ workspace })
|
||||
setCopilotInfo(info)
|
||||
} catch (err) {
|
||||
setCopilotInfo({})
|
||||
console.error('Could not get copilot info', err)
|
||||
}
|
||||
}
|
||||
|
||||
export function setCopilotInfo(aiConfig: AIConfig) {
|
||||
if (Object.keys(aiConfig.providers ?? {}).length > 0) {
|
||||
const aiModels = Object.entries(aiConfig.providers ?? {}).flatMap(
|
||||
([provider, providerConfig]) =>
|
||||
providerConfig.models.map((m) => ({ model: m, provider: provider as AIProvider }))
|
||||
)
|
||||
|
||||
copilotSessionModel.update((model) => {
|
||||
if (
|
||||
model &&
|
||||
!aiModels.some((m) => m.model === model.model && m.provider === model.provider)
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return model
|
||||
})
|
||||
|
||||
copilotInfo.set({
|
||||
enabled: true,
|
||||
codeCompletionModel: aiConfig.code_completion_model,
|
||||
defaultModel: aiConfig.default_model,
|
||||
aiModels: aiModels,
|
||||
customPrompts: aiConfig.custom_prompts ?? {},
|
||||
maxTokensPerModel: aiConfig.max_tokens_per_model ?? {}
|
||||
})
|
||||
} else {
|
||||
copilotSessionModel.set(undefined)
|
||||
|
||||
copilotInfo.set({
|
||||
enabled: false,
|
||||
codeCompletionModel: undefined,
|
||||
defaultModel: undefined,
|
||||
aiModels: [],
|
||||
customPrompts: {},
|
||||
maxTokensPerModel: {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function getCurrentModel() {
|
||||
const model =
|
||||
get(copilotSessionModel) ?? get(copilotInfo).defaultModel ?? get(copilotInfo).aiModels[0]
|
||||
if (!model) {
|
||||
throw new Error('No model selected')
|
||||
}
|
||||
return model
|
||||
}
|
||||
@@ -49,6 +49,7 @@ export interface SchemaProperty {
|
||||
placeholder?: string
|
||||
oneOf?: SchemaProperty[]
|
||||
originalType?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export interface ModalSchemaProperty {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
type TriggersCount
|
||||
} from '$lib/gen'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { setCopilotInfo, userStore, workspaceStore } from '$lib/stores'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { emptySchema, readFieldsRecursively, sendUserToast, type StateStore } from '$lib/utils'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import { onDestroy, onMount, setContext, untrack } from 'svelte'
|
||||
@@ -51,6 +51,7 @@
|
||||
import { StepsInputArgs } from './flows/stepsInputArgs.svelte'
|
||||
import { ModulesTestStates } from './modulesTest.svelte'
|
||||
import type { GraphModuleState } from './graph'
|
||||
import { setCopilotInfo } from '$lib/aiStore'
|
||||
|
||||
let {
|
||||
initial = undefined
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
import {
|
||||
dbSchemas,
|
||||
type DBSchema,
|
||||
copilotInfo,
|
||||
codeCompletionSessionEnabled,
|
||||
lspTokenStore,
|
||||
formatOnSave,
|
||||
@@ -91,6 +90,7 @@
|
||||
import { getDbSchemas } from './apps/components/display/dbtable/utils'
|
||||
import { PYTHON_PREPROCESSOR_MODULE_CODE, TS_PREPROCESSOR_MODULE_CODE } from '$lib/script_helpers'
|
||||
import { setMonacoTypescriptOptions } from './monacoLanguagesOptions'
|
||||
import { copilotInfo } from '$lib/aiStore'
|
||||
// import EditorTheme from './EditorTheme.svelte'
|
||||
|
||||
let divEl: HTMLDivElement | null = $state(null)
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
import { getStepHistoryLoaderContext } from './stepHistoryLoader.svelte'
|
||||
import { aiChatManager } from './copilot/chat/AIChatManager.svelte'
|
||||
import { stateSnapshot } from '$lib/svelte5Utils.svelte'
|
||||
import FlowChatInterface from './flows/FlowChatInterface.svelte'
|
||||
|
||||
interface Props {
|
||||
previewMode: 'upTo' | 'whole'
|
||||
@@ -93,9 +94,9 @@
|
||||
let suspendStatus: StateStore<Record<string, { job: Job; nb: number }>> = $state({ val: {} })
|
||||
let isOwner: boolean = $state(false)
|
||||
|
||||
export function test() {
|
||||
export async function test(): Promise<string | undefined> {
|
||||
renderCount++
|
||||
runPreview(previewArgs.val, undefined)
|
||||
return await runPreview(previewArgs.val, undefined)
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -139,6 +140,7 @@
|
||||
args: Record<string, any>,
|
||||
restartedFrom: RestartedFrom | undefined
|
||||
) {
|
||||
let newJobId: string | undefined = undefined
|
||||
if (stepHistoryLoader?.flowJobInitial !== false) {
|
||||
stepHistoryLoader?.setFlowJobInitial(false)
|
||||
}
|
||||
@@ -146,7 +148,8 @@
|
||||
lastPreviewFlow = JSON.stringify(flowStore.val)
|
||||
flowProgressBar?.reset()
|
||||
const newFlow = extractFlow(previewMode)
|
||||
jobId = await runFlowPreview(args, newFlow, $pathStore, restartedFrom)
|
||||
newJobId = await runFlowPreview(args, newFlow, $pathStore, restartedFrom)
|
||||
jobId = newJobId
|
||||
isRunning = true
|
||||
if (inputSelected) {
|
||||
savedArgs = $state.snapshot(previewArgs.val)
|
||||
@@ -159,6 +162,7 @@
|
||||
jobId = undefined
|
||||
}
|
||||
schemaFormWithArgPicker?.refreshHistory()
|
||||
return newJobId
|
||||
}
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
@@ -414,25 +418,27 @@
|
||||
</Popover>
|
||||
{/if}
|
||||
{/if}
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={{ icon: isRunning ? RefreshCw : Play }}
|
||||
color="dark"
|
||||
size="sm"
|
||||
btnClasses="w-full max-w-lg"
|
||||
on:click={() => runPreview(previewArgs.val, undefined)}
|
||||
id="flow-editor-test-flow-drawer"
|
||||
shortCut={{ Icon: CornerDownLeft }}
|
||||
>
|
||||
{#if previewMode == 'upTo'}
|
||||
Test up to
|
||||
<Badge baseClass="ml-1" color="indigo">
|
||||
{$selectedId}
|
||||
</Badge>
|
||||
{:else}
|
||||
Test flow
|
||||
{/if}
|
||||
</Button>
|
||||
{#if !flowStore.val.value?.chat_input_enabled}
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={{ icon: isRunning ? RefreshCw : Play }}
|
||||
color="dark"
|
||||
size="sm"
|
||||
btnClasses="w-full max-w-lg"
|
||||
on:click={() => runPreview(previewArgs.val, undefined)}
|
||||
id="flow-editor-test-flow-drawer"
|
||||
shortCut={{ Icon: CornerDownLeft }}
|
||||
>
|
||||
{#if previewMode == 'upTo'}
|
||||
Test up to
|
||||
<Badge baseClass="ml-1" color="indigo">
|
||||
{$selectedId}
|
||||
</Badge>
|
||||
{:else}
|
||||
Test flow
|
||||
{/if}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -457,81 +463,96 @@
|
||||
onscroll={(e) => handleScroll()}
|
||||
>
|
||||
{#if render}
|
||||
<div class="border-b">
|
||||
<SchemaFormWithArgPicker
|
||||
bind:this={schemaFormWithArgPicker}
|
||||
runnableId={$initialPathStore}
|
||||
stablePathForCaptures={$initialPathStore || fakeInitialPath}
|
||||
runnableType={'FlowPath'}
|
||||
previewArgs={previewArgs.val}
|
||||
on:openTriggers
|
||||
on:select={(e) => {
|
||||
selectInput(e.detail.payload, e.detail?.type)
|
||||
}}
|
||||
{isValid}
|
||||
{jsonView}
|
||||
>
|
||||
<div class="w-full flex flex-row justify-between">
|
||||
<InputSelectedBadge
|
||||
onReject={() => schemaFormWithArgPicker?.resetSelected()}
|
||||
{inputSelected}
|
||||
/>
|
||||
<div class="flex flex-row gap-2">
|
||||
<Toggle
|
||||
bind:checked={jsonView}
|
||||
label="JSON View"
|
||||
size="xs"
|
||||
options={{
|
||||
right: 'JSON',
|
||||
rightTooltip: 'Fill args from JSON'
|
||||
}}
|
||||
lightMode
|
||||
on:change={(e) => {
|
||||
jsonEditor?.setCode(JSON.stringify(previewArgs.val ?? {}, null, '\t'))
|
||||
refresh()
|
||||
}}
|
||||
{#if flowStore.val.value?.chat_input_enabled}
|
||||
<div class="flex flex-row justify-center w-full">
|
||||
<FlowChatInterface
|
||||
onRunFlow={async (userMessage, _conversationId) => {
|
||||
await runPreview({ user_message: userMessage }, undefined)
|
||||
return jobId ?? ''
|
||||
}}
|
||||
createConversation={async () => {
|
||||
const newConversationId = crypto.randomUUID()
|
||||
return newConversationId
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="border-b">
|
||||
<SchemaFormWithArgPicker
|
||||
bind:this={schemaFormWithArgPicker}
|
||||
runnableId={$initialPathStore}
|
||||
stablePathForCaptures={$initialPathStore || fakeInitialPath}
|
||||
runnableType={'FlowPath'}
|
||||
previewArgs={previewArgs.val}
|
||||
on:openTriggers
|
||||
on:select={(e) => {
|
||||
selectInput(e.detail.payload, e.detail?.type)
|
||||
}}
|
||||
{isValid}
|
||||
{jsonView}
|
||||
>
|
||||
<div class="w-full flex flex-row justify-between">
|
||||
<InputSelectedBadge
|
||||
onReject={() => schemaFormWithArgPicker?.resetSelected()}
|
||||
{inputSelected}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if jsonView}
|
||||
<div class="py-2" style="height: {Math.max(schemaHeight, 100)}px" data-schema-picker>
|
||||
<JsonInputs
|
||||
bind:this={jsonEditor}
|
||||
on:select={(e) => {
|
||||
if (e.detail) {
|
||||
previewArgs.val = e.detail
|
||||
}
|
||||
}}
|
||||
updateOnBlur={false}
|
||||
placeholder={`Write args as JSON.<br/><br/>Example:<br/><br/>{<br/> "foo": "12"<br/>}`}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
{#key renderCount}
|
||||
<div bind:clientHeight={schemaHeight} class="min-h-[40vh]">
|
||||
<SchemaForm
|
||||
noVariablePicker
|
||||
compact
|
||||
schema={flowStore.val.schema}
|
||||
bind:args={previewArgs.val}
|
||||
on:change={() => {
|
||||
savedArgs = $state.snapshot(previewArgs.val)
|
||||
<div class="flex flex-row gap-2">
|
||||
<Toggle
|
||||
bind:checked={jsonView}
|
||||
label="JSON View"
|
||||
size="xs"
|
||||
options={{
|
||||
right: 'JSON',
|
||||
rightTooltip: 'Fill args from JSON'
|
||||
}}
|
||||
lightMode
|
||||
on:change={(e) => {
|
||||
jsonEditor?.setCode(JSON.stringify(previewArgs.val ?? {}, null, '\t'))
|
||||
refresh()
|
||||
}}
|
||||
bind:isValid
|
||||
helperScript={flowStore.val.schema?.['x-windmill-dyn-select-code'] &&
|
||||
flowStore.val.schema?.['x-windmill-dyn-select-lang']
|
||||
? {
|
||||
source: 'inline',
|
||||
code: flowStore.val.schema['x-windmill-dyn-select-code'] as string,
|
||||
lang: flowStore.val.schema['x-windmill-dyn-select-lang'] as ScriptLang
|
||||
}
|
||||
: undefined}
|
||||
/>
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
</SchemaFormWithArgPicker>
|
||||
</div>
|
||||
</div>
|
||||
{#if jsonView}
|
||||
<div class="py-2" style="height: {Math.max(schemaHeight, 100)}px" data-schema-picker>
|
||||
<JsonInputs
|
||||
bind:this={jsonEditor}
|
||||
on:select={(e) => {
|
||||
if (e.detail) {
|
||||
previewArgs.val = e.detail
|
||||
}
|
||||
}}
|
||||
updateOnBlur={false}
|
||||
placeholder={`Write args as JSON.<br/><br/>Example:<br/><br/>{<br/> "foo": "12"<br/>}`}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
{#key renderCount}
|
||||
<div bind:clientHeight={schemaHeight} class="min-h-[40vh]">
|
||||
<SchemaForm
|
||||
noVariablePicker
|
||||
compact
|
||||
schema={flowStore.val.schema}
|
||||
bind:args={previewArgs.val}
|
||||
on:change={() => {
|
||||
savedArgs = $state.snapshot(previewArgs.val)
|
||||
}}
|
||||
bind:isValid
|
||||
helperScript={flowStore.val.schema?.['x-windmill-dyn-select-code'] &&
|
||||
flowStore.val.schema?.['x-windmill-dyn-select-lang']
|
||||
? {
|
||||
source: 'inline',
|
||||
code: flowStore.val.schema['x-windmill-dyn-select-code'] as string,
|
||||
lang: flowStore.val.schema['x-windmill-dyn-select-lang'] as ScriptLang
|
||||
}
|
||||
: undefined}
|
||||
/>
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
</SchemaFormWithArgPicker>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<div class="pt-4 flex flex-col grow relative">
|
||||
<div
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
let globalRefreshes: Record<string, ((clear, root) => Promise<void>)[]> = $state({})
|
||||
|
||||
setContext<FlowStatusViewerContext>('FlowStatusViewer', {
|
||||
flowState: flowState,
|
||||
flowState,
|
||||
suspendStatus,
|
||||
retryStatus,
|
||||
hideDownloadInGraph,
|
||||
@@ -88,7 +88,7 @@
|
||||
retryStatus.val = {}
|
||||
suspendStatus.val = {}
|
||||
globalRefreshes = {}
|
||||
flowState = {}
|
||||
for (let key in localModuleStates) delete flowState[key]
|
||||
localDurationStatuses = {}
|
||||
localModuleStates = {}
|
||||
}
|
||||
|
||||
@@ -1671,7 +1671,7 @@
|
||||
</div>
|
||||
{#if selected == 'logs' && render}
|
||||
<div
|
||||
class="mx-auto"
|
||||
class="mx-auto overflow-auto"
|
||||
bind:clientHeight={tabsHeight.logsHeight}
|
||||
style="min-height: {minTabHeight}px"
|
||||
>
|
||||
|
||||
@@ -700,7 +700,9 @@
|
||||
{/if}
|
||||
{#if argName && schema?.properties?.[argName]?.description}
|
||||
<div class="text-xs italic py-1 text-hint">
|
||||
<pre class="font-main">{schema.properties[argName].description}</pre>
|
||||
<pre class="font-main whitespace-normal"
|
||||
>{schema.properties[argName].description}</pre
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -806,6 +808,14 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if argName && schema?.properties?.[argName]?.description}
|
||||
<div class="text-xs italic py-1 text-hint">
|
||||
<pre class="font-main whitespace-normal"
|
||||
>{schema.properties[argName].description}</pre
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !hideHelpButton}
|
||||
<DynamicInputHelpBox />
|
||||
{/if}
|
||||
|
||||
@@ -291,10 +291,9 @@
|
||||
<h5 class="mt-4 inline-flex items-center gap-4 pb-2">
|
||||
File content ({resourceTypeInfo.format_extension})
|
||||
</h5>
|
||||
<div class="h-full w-full border p-1 rounded">
|
||||
<div class="">
|
||||
<SimpleEditor
|
||||
autoHeight
|
||||
class="editor"
|
||||
lang={resourceTypeInfo.format_extension}
|
||||
bind:code={textFileContent}
|
||||
fixedOverflowWidgets={false}
|
||||
@@ -324,14 +323,8 @@
|
||||
{#if !emptyString(jsonError)}<span class="text-red-400 text-xs mb-1 flex flex-row-reverse"
|
||||
>{jsonError}</span
|
||||
>{:else}<div class="py-2"></div>{/if}
|
||||
<div class="h-full w-full border p-1 rounded">
|
||||
<SimpleEditor
|
||||
autoHeight
|
||||
class="editor"
|
||||
lang="json"
|
||||
bind:code={rawCode}
|
||||
fixedOverflowWidgets={false}
|
||||
/>
|
||||
<div class="bg-surface-secondary rounded-md border py-2.5">
|
||||
<SimpleEditor autoHeight lang="json" bind:code={rawCode} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
defaultValues?: Record<string, any> | undefined
|
||||
placeholder?: string | undefined
|
||||
selectInputClass?: string
|
||||
class?: string
|
||||
onClear?: () => void
|
||||
excludedValues?: string[]
|
||||
}
|
||||
@@ -44,6 +45,7 @@
|
||||
defaultValues = undefined,
|
||||
placeholder = undefined,
|
||||
selectInputClass = '',
|
||||
class: className = '',
|
||||
onClear = undefined,
|
||||
excludedValues = undefined
|
||||
}: Props = $props()
|
||||
@@ -180,7 +182,7 @@
|
||||
}}
|
||||
/>
|
||||
<!-- {JSON.stringify({ value, collection })} -->
|
||||
<div class="flex flex-col w-full items-start min-h-10">
|
||||
<div class="flex flex-col w-full items-start min-h-10 {className}">
|
||||
<div class="flex flex-row w-full items-center">
|
||||
<Select
|
||||
{disabled}
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
| undefined)
|
||||
| undefined
|
||||
workspace?: string | undefined
|
||||
actions?: import('svelte').Snippet
|
||||
actions?: import('svelte').Snippet<[{ item: { id: string; value: string } }]> | undefined
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -414,7 +414,7 @@
|
||||
{displayType}
|
||||
>
|
||||
{#snippet actions()}
|
||||
{@render actions_render?.()}
|
||||
{@render actions_render?.({ item })}
|
||||
{#if linkedSecretCandidates?.includes(argName)}
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
let loading = false
|
||||
let captureTable: CaptureTable | undefined = undefined
|
||||
let historicInputs: HistoricInputs | undefined = undefined
|
||||
$: selectedTab, (dropdownItems = getDropdownItems())
|
||||
$: (selectedTab, (dropdownItems = getDropdownItems()))
|
||||
|
||||
let inputPanelSize = 70
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import type { Schema, SupportedLanguage } from '$lib/common'
|
||||
import { type CompletedJob, type Job, JobService, type Preview, type ScriptLang } from '$lib/gen'
|
||||
import { copilotInfo, enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
|
||||
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
|
||||
import { copyToClipboard, emptySchema, sendUserToast } from '$lib/utils'
|
||||
import Editor from './Editor.svelte'
|
||||
import { inferArgs, inferAssets } from '$lib/infer'
|
||||
@@ -50,6 +50,7 @@
|
||||
import { assetEq, type AssetWithAltAccessType } from './assets/lib'
|
||||
import { editor as meditor } from 'monaco-editor'
|
||||
import type { ReviewChangesOpts } from './copilot/chat/monaco-adapter'
|
||||
import { copilotInfo } from '$lib/aiStore'
|
||||
|
||||
interface Props {
|
||||
// Exported
|
||||
|
||||
@@ -110,7 +110,11 @@
|
||||
<span
|
||||
class={twMerge(
|
||||
'ml-2 font-normal duration-50 select-none',
|
||||
bothOptions || textDisabled ? (checked ? 'text-primary' : 'text-disabled') : 'text-primary',
|
||||
bothOptions || textDisabled
|
||||
? checked
|
||||
? 'text-secondary'
|
||||
: 'text-disabled'
|
||||
: 'text-secondary',
|
||||
size === 'xs' || size === '2sm' ? 'text-xs' : size === '2xs' ? 'text-xs' : 'text-sm',
|
||||
textClass
|
||||
)}
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
<div class="max-w-min">
|
||||
<ToggleButtonGroup id="flow-editor-flow-providers" bind:selected>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton value="all" label="All" light small {item} />
|
||||
<ToggleButton value="hub" icon={WindmillIcon2} label="Hub" light small {item} />
|
||||
<ToggleButton value="workspace" icon={Building} label="Workspace" light small {item} />
|
||||
<ToggleButton value="all" label="All" light {item} />
|
||||
<ToggleButton value="hub" icon={WindmillIcon2} label="Hub" light {item} />
|
||||
<ToggleButton value="workspace" icon={Building} label="Workspace" light {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
@@ -267,60 +267,64 @@
|
||||
<div class="text-red-500 text-xs">{errorsMessage}</div>
|
||||
{/if}
|
||||
{#key css}
|
||||
<Button
|
||||
on:pointerdown={(e) => e.stopPropagation()}
|
||||
btnClasses={twMerge(
|
||||
css?.button?.class ?? '',
|
||||
isMenuItem ? 'flex items-center justify-start' : '',
|
||||
isMenuItem ? '!border-0' : '',
|
||||
'wm-button',
|
||||
`wm-button-${resolvedConfig.color}`
|
||||
)}
|
||||
variant={isMenuItem ? 'border' : 'contained'}
|
||||
style={css?.button?.style}
|
||||
wrapperClasses={twMerge(
|
||||
css?.container?.class ?? '',
|
||||
resolvedConfig.fillContainer ? 'w-full h-full' : '',
|
||||
isMenuItem ? 'w-full' : '',
|
||||
'wm-button-container',
|
||||
`wm-button-container-${resolvedConfig.color}`
|
||||
)}
|
||||
wrapperStyle={css?.container?.style}
|
||||
disabled={resolvedConfig.disabled}
|
||||
on:click={handleClick}
|
||||
size={resolvedConfig.size}
|
||||
color={resolvedConfig.color}
|
||||
<div
|
||||
class="inline-flex"
|
||||
title={resolvedConfig.tooltip && String(resolvedConfig.tooltip).length > 0
|
||||
? String(resolvedConfig.tooltip)
|
||||
: undefined}
|
||||
loading={resolvedConfig.runInBackground ? backgroundClickFeedback : loading}
|
||||
>
|
||||
{#if resolvedConfig.beforeIcon}
|
||||
{#key resolvedConfig.beforeIcon}
|
||||
<div
|
||||
class={resolvedConfig.label?.toString() &&
|
||||
resolvedConfig.label?.toString()?.length > 0
|
||||
? 'min-w-4'
|
||||
: ''}
|
||||
bind:this={beforeIconComponent}
|
||||
></div>
|
||||
{/key}
|
||||
{/if}
|
||||
{#if resolvedConfig.label?.toString() && resolvedConfig.label?.toString()?.length > 0}
|
||||
<div>{resolvedConfig.label.toString()}</div>
|
||||
{/if}
|
||||
{#if resolvedConfig.afterIcon}
|
||||
{#key resolvedConfig.afterIcon}
|
||||
<div
|
||||
class={resolvedConfig.label?.toString() &&
|
||||
resolvedConfig.label?.toString()?.length > 0
|
||||
? 'min-w-4'
|
||||
: ''}
|
||||
bind:this={afterIconComponent}
|
||||
></div>
|
||||
{/key}
|
||||
{/if}
|
||||
</Button>
|
||||
<Button
|
||||
on:pointerdown={(e) => e.stopPropagation()}
|
||||
btnClasses={twMerge(
|
||||
css?.button?.class ?? '',
|
||||
isMenuItem ? 'flex items-center justify-start' : '',
|
||||
isMenuItem ? '!border-0' : '',
|
||||
'wm-button',
|
||||
`wm-button-${resolvedConfig.color}`
|
||||
)}
|
||||
variant={isMenuItem ? 'border' : 'contained'}
|
||||
style={css?.button?.style}
|
||||
wrapperClasses={twMerge(
|
||||
css?.container?.class ?? '',
|
||||
resolvedConfig.fillContainer ? 'w-full h-full' : '',
|
||||
isMenuItem ? 'w-full' : '',
|
||||
'wm-button-container',
|
||||
`wm-button-container-${resolvedConfig.color}`
|
||||
)}
|
||||
wrapperStyle={css?.container?.style}
|
||||
disabled={resolvedConfig.disabled}
|
||||
on:click={handleClick}
|
||||
size={resolvedConfig.size}
|
||||
color={resolvedConfig.color}
|
||||
loading={resolvedConfig.runInBackground ? backgroundClickFeedback : loading}
|
||||
>
|
||||
{#if resolvedConfig.beforeIcon}
|
||||
{#key resolvedConfig.beforeIcon}
|
||||
<div
|
||||
class={resolvedConfig.label?.toString() &&
|
||||
resolvedConfig.label?.toString()?.length > 0
|
||||
? 'min-w-4'
|
||||
: ''}
|
||||
bind:this={beforeIconComponent}
|
||||
></div>
|
||||
{/key}
|
||||
{/if}
|
||||
{#if resolvedConfig.label?.toString() && resolvedConfig.label?.toString()?.length > 0}
|
||||
<div>{resolvedConfig.label.toString()}</div>
|
||||
{/if}
|
||||
{#if resolvedConfig.afterIcon}
|
||||
{#key resolvedConfig.afterIcon}
|
||||
<div
|
||||
class={resolvedConfig.label?.toString() &&
|
||||
resolvedConfig.label?.toString()?.length > 0
|
||||
? 'min-w-4'
|
||||
: ''}
|
||||
bind:this={afterIconComponent}
|
||||
></div>
|
||||
{/key}
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
{/key}
|
||||
</AlignWrapper>
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { argSigToJsonSchemaType } from 'windmill-utils-internal'
|
||||
import SchemaForm from '$lib/components/SchemaForm.svelte'
|
||||
import { untrack } from 'svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
|
||||
let schema: Schema | undefined = $state(undefined)
|
||||
|
||||
@@ -134,8 +135,10 @@
|
||||
let fields = $derived(
|
||||
columnDefs
|
||||
?.filter((t) => {
|
||||
const shouldFilter = t.isidentity === ColumnIdentity.Always || t?.hideInsert === true
|
||||
|
||||
const shouldFilter =
|
||||
t.isidentity === ColumnIdentity.Always ||
|
||||
t?.hideInsert === true ||
|
||||
t.defaultvalue?.startsWith('nextval(') // exclude postgres serial/auto increment fields
|
||||
return !shouldFilter
|
||||
})
|
||||
.map((column) => {
|
||||
@@ -171,5 +174,32 @@
|
||||
</script>
|
||||
|
||||
{#if schema}
|
||||
<SchemaForm onlyMaskPassword {schema} bind:args />
|
||||
<SchemaForm onlyMaskPassword {schema} bind:args>
|
||||
{#snippet actions({ item })}
|
||||
{@const disabled = fields?.[fields?.findIndex((f) => f.name === item.id)]?.nullable != 'YES'}
|
||||
{#if !disabled}
|
||||
<Toggle
|
||||
options={{ right: 'NULL' }}
|
||||
class="pl-2"
|
||||
textClass="text-tertiary"
|
||||
size="2sm"
|
||||
bind:checked={
|
||||
() => args[item.id] === null,
|
||||
(v) => {
|
||||
if (!schema?.properties[item.id]) return
|
||||
if (v) {
|
||||
schema.properties[item.id].nullable = true
|
||||
schema.properties[item.id].disabled = true
|
||||
args[item.id] = null
|
||||
} else {
|
||||
delete schema.properties[item.id].disabled
|
||||
delete schema.properties[item.id].nullable
|
||||
args[item.id] = schema.properties[item.id].default ?? ''
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</SchemaForm>
|
||||
{/if}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user