Compare commits

..
Author SHA1 Message Date
Ruben Fiszel fb2a7c3ba1 all 2025-08-22 07:44:09 +00:00
Ruben Fiszel 06d078ebfa fix: make relevant sidebar menu items a instead of button 2025-08-21 18:37:32 +00:00
Ruben Fiszel 4373dfbd80 make tag select removable in custom ui 2025-08-21 18:17:52 +00:00
Ruben Fiszel d0c7ac9f95 nit 2 2025-08-21 17:55:29 +00:00
Ruben Fiszel 6e132e8ee9 ctrl drop on apps improvements 2025-08-21 17:51:06 +00:00
Ruben Fiszel 51ea9473ef fix(app): fix ctrl drag for insertion into subgrids 2025-08-21 17:32:51 +00:00
Fred Reimerandpyranota c92bfe6601 feat: bump Go version from 1.22.0 to 1.25.0 #6415
Co-authored-by: pyranota <92104930+pyranota@users.noreply.github.com>
2025-08-21 15:15:09 +00:00
pyranota d152e8e58f add go1_22_compat annotation (#6432)
* bump go to 1.25

Signed-off-by: pyranota <pyra@duck.com>

* Update Dockerfile

* add comms

---------

Signed-off-by: pyranota <pyra@duck.com>
2025-08-21 15:14:29 +00:00
hugocasa 28f1d61164 fix(frontend): graph cache of ai agent step tools (#6431) 2025-08-21 13:59:16 +02:00
Ruben Fiszel 54f36fcce7 remove default db port on docker-compose 2025-08-21 01:35:59 +00:00
Ruben Fiszel 14b0edd8a4 nit fix 2025-08-20 22:55:44 +00:00
hugocasa 958e8af782 feat: ai agent steps (#6393)
* feat: ai agent steps base

* better backend and graph

* feat: anthropic, log viewer

* nit

* fix(frontend): hide tool nodes from timeline

* move ai agent actions from flow status to flow status module

* nits and workspace/hub scripts support

* tmp ref

* fix merge

* feat: display agent tools status in the graph

* fix reactivity

* fix flow status

* nit
2025-08-20 22:40:57 +00:00
Guilhem 4b79e53f0d fix unsafe mutation in input picker getter (#6428)
* fix bad mutation

* remove unnecessary data structure for step args
2025-08-20 22:40:14 +00:00
Ruben Fiszelandrubenfiszel 58bc913dfc chore(main): release 1.530.0 (#6426)
* chore(main): release 1.530.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-08-20 18:20:55 +00:00
Alexander Petric 511ff5e9f7 fix: aggrid newchange to point to correct idx (#6425)
* fix: aggrid newchange to point to correct idx

* fix: aggrid newchange to point to correct idx
2025-08-20 18:16:54 +00:00
Ruben Fiszel aa5a0300f4 better preprocessing fix 2025-08-20 18:06:57 +00:00
Ruben Fiszel 47e49b243d fix: fix preprocessor preview 2025-08-20 17:54:18 +00:00
Ruben Fiszel 05648c7c81 multiple flow editor fixes 2025-08-20 17:28:41 +00:00
Ruben Fiszel bcd05753f4 flow nits 2025-08-20 17:06:11 +00:00
Ruben Fiszel ac066abb98 fix: improve flow editor log streaming for individual tests 2025-08-20 16:33:08 +00:00
ae49737676 feat(mcp): add script preview testing tool (#6417)
* add endpoint to test script

* add same for flow

* better tool spec + remove flow preview from tools

* fix

* fix wrong required fields

* feat(mcp): add warning for missing required fields in schema properties

- Add stderr warning when x-mcp-required-fields contains fields not found in body schema properties
- Prevents silent misconfigurations in MCP tool generation
- Helps debug schema validation issues

Co-authored-by: centdix <centdix@users.noreply.github.com>

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: centdix <centdix@users.noreply.github.com>
2025-08-20 14:50:46 +02:00
Ruben Fiszelandrubenfiszel 26a47ee699 chore(main): release 1.529.0 (#6423)
* chore(main): release 1.529.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-08-20 09:32:32 +00:00
Ruben Fiszel 1734862718 fix In 2025-08-20 09:28:00 +00:00
Ruben Fiszel c1770cd769 improve logic 2025-08-20 09:06:25 +00:00
Ruben Fiszel 18cc474cc7 fix: fix Out output of flow node in frontend be reactive to relevant flowStateStore change 2025-08-20 09:03:05 +00:00
centdix b698ced881 nits(aichat): better ui for tool execution (#6418)
* better conf messages

* nits on tool exec
2025-08-20 10:51:06 +02:00
49ed757424 feat: add prometheus metric queue_running_count (#6413)
* feat: add prometheus metric queue_running_count

Adds a new Prometheus metric queue_running_count that tracks the number
of currently running jobs per tag, similar to the existing queue_count
metric but filtered for running=true instead of running=false.

Changes:
- Added get_queue_running_counts() function in windmill-common/src/queue.rs
- Added QUEUE_RUNNING_COUNT Prometheus metric in monitor.rs
- Added /workers/queue_running_counts API endpoint
- Updated OpenAPI specification
- Added SQLx query cache entry

Requested by @rubenfiszel

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>

* Remove database insertion for queue_running_count metrics

Keep Prometheus metrics and API endpoint functionality while removing
the database INSERT statements as requested.

Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>

* sqlx

* improve logic

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-08-19 22:12:36 +00:00
pyranota 4349a2024d feat(frontend): add relative line numbers toggle (#6416)
* feat(frontend): add relative line numbers toggle

Signed-off-by: pyranota <pyra@duck.com>

* nits

Signed-off-by: pyranota <pyra@duck.com>

---------

Signed-off-by: pyranota <pyra@duck.com>
2025-08-19 22:09:01 +00:00
Diego Imbert 51568eee02 fix: improve computeAssetNodes rendering caching and performance (#6414)
* better caching for computeAssetNodes

* only pass required fields

* unecessary returns

* type not necessary
2025-08-19 21:12:34 +00:00
claude[bot]andwindmill-internal-app[bot] 7f11eb98b5 fix(cli): pass HEADERS environment variable to fetch calls in generate-locks (#6422)
- Import getHeaders function in metadata.ts
- Update updateScriptLock() to include extra headers from HEADERS env var
- Update updateFlow() to include extra headers in both fetch branches
- Fixes issue where wmill flow generate-locks ignored custom headers

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2025-08-19 18:05:33 +00:00
centdix dfb32d2949 feat(aichat): add tool to test specific module in flow mode (#6381)
* draft

* cleaning

* cleaning

* fixes

* fix placeholders

* better fallback

* cleaning
2025-08-19 15:18:30 +02:00
Ruben Fiszel 205618af0a nit module tracker 2025-08-19 07:56:17 +00:00
153 changed files with 3902 additions and 1017 deletions
+29
View File
@@ -1,5 +1,34 @@
# Changelog
## [1.530.0](https://github.com/windmill-labs/windmill/compare/v1.529.0...v1.530.0) (2025-08-20)
### Features
* **mcp:** add script preview testing tool ([#6417](https://github.com/windmill-labs/windmill/issues/6417)) ([ae49737](https://github.com/windmill-labs/windmill/commit/ae497376769f5cd49a41c22cf558a9f052d5b56e))
### Bug Fixes
* aggrid newchange to point to correct idx ([#6425](https://github.com/windmill-labs/windmill/issues/6425)) ([511ff5e](https://github.com/windmill-labs/windmill/commit/511ff5e9f794c29c3c8a5fc0480675a1258fb056))
* fix preprocessor preview ([47e49b2](https://github.com/windmill-labs/windmill/commit/47e49b243d8cf6d29fa5a59918a2168b87111352))
* improve flow editor log streaming for individual tests ([ac066ab](https://github.com/windmill-labs/windmill/commit/ac066abb980501577cc96330a4a5f1309aa35661))
## [1.529.0](https://github.com/windmill-labs/windmill/compare/v1.528.0...v1.529.0) (2025-08-19)
### Features
* add prometheus metric queue_running_count ([#6413](https://github.com/windmill-labs/windmill/issues/6413)) ([49ed757](https://github.com/windmill-labs/windmill/commit/49ed7574245784681f800199b6c7a47df5788e45))
* **aichat:** add tool to test specific module in flow mode ([#6381](https://github.com/windmill-labs/windmill/issues/6381)) ([dfb32d2](https://github.com/windmill-labs/windmill/commit/dfb32d2949541ed149722cff88918d7c6e3dc307))
* **frontend:** add relative line numbers toggle ([#6416](https://github.com/windmill-labs/windmill/issues/6416)) ([4349a20](https://github.com/windmill-labs/windmill/commit/4349a2024da2aa7406b16254fbfc427526718903))
### Bug Fixes
* **cli:** pass HEADERS environment variable to fetch calls in generate-locks ([#6422](https://github.com/windmill-labs/windmill/issues/6422)) ([7f11eb9](https://github.com/windmill-labs/windmill/commit/7f11eb98b5682511e05c270d7ba860e3e0db61e9))
* improve computeAssetNodes rendering caching and performance ([#6414](https://github.com/windmill-labs/windmill/issues/6414)) ([51568ee](https://github.com/windmill-labs/windmill/commit/51568eee025eab6e0069a8e719a562b721fc8c43))
## [1.528.0](https://github.com/windmill-labs/windmill/compare/v1.527.1...v1.528.0) (2025-08-19)
+1 -1
View File
@@ -90,7 +90,7 @@ ARG POWERSHELL_VERSION=7.5.0
ARG POWERSHELL_DEB_VERSION=7.5.0-1
ARG KUBECTL_VERSION=1.28.7
ARG HELM_VERSION=3.14.3
ARG GO_VERSION=1.22.5
ARG GO_VERSION=1.25.0
ARG APP=/usr/src/app
ARG WITH_POWERSHELL=true
ARG WITH_KUBECTL=true
@@ -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 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) \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)",
"describe": {
"columns": [],
"parameters": {
@@ -39,7 +39,8 @@
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
"appscript",
"aiagent"
]
}
}
@@ -121,10 +122,11 @@
]
}
}
}
},
"Bool"
]
},
"nullable": []
},
"hash": "acfe583fe17604ba72ba4800b62a72de0a9de0d58ef8c28dd709adf3be021597"
"hash": "193d292c5ed44bf5266ad52c83704c3a36aa284fab3b7e638dbca12ac846b82b"
}
@@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "SELECT tag AS \"tag!\", count(*) AS \"count!\" FROM v2_job_queue WHERE\n running = true\n GROUP BY tag",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "tag!",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "count!",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
null
]
},
"hash": "1b56a720d99a689e80d12ee1efbfeb71d7cd7bb17e936746749c958062cdff9e"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE v2_job_status SET\n flow_status = jsonb_set(\n flow_status,\n array['modules', $2::TEXT, 'agent_actions_success'],\n COALESCE(\n flow_status->'modules'->$2->'agent_actions_success',\n to_jsonb(ARRAY[]::bool[])\n ) || to_jsonb(ARRAY[$3::bool])\n )\n WHERE id = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Text",
"Bool"
]
},
"nullable": []
},
"hash": "3e3afba04a10f16606e17cea6b31d9578cac41b76092722fd2698afb4cf08834"
}
@@ -28,7 +28,8 @@
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
"appscript",
"aiagent"
]
}
}
@@ -40,7 +40,8 @@
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
"appscript",
"aiagent"
]
}
}
@@ -68,7 +68,8 @@
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
"appscript",
"aiagent"
]
}
}
@@ -98,7 +98,8 @@
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
"appscript",
"aiagent"
]
}
}
@@ -148,7 +149,8 @@
"oracledb",
"nu",
"java",
"duckdb"
"duckdb",
"ruby"
]
}
}
@@ -38,7 +38,8 @@
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
"appscript",
"aiagent"
]
}
}
@@ -0,0 +1,61 @@
{
"db_name": "PostgreSQL",
"query": "SELECT runnable_id as \"runnable_id: ScriptHash\", raw_flow as \"raw_flow: _\", kind as \"kind: _\" FROM v2_job WHERE id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "runnable_id: ScriptHash",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "raw_flow: _",
"type_info": "Jsonb"
},
{
"ordinal": 2,
"name": "kind: _",
"type_info": {
"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"
]
}
}
}
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
true,
true,
false
]
},
"hash": "805d633de90fee335f1726284eda0dbc200d45960fb8dea867492c8c7dd096d5"
}
@@ -28,7 +28,8 @@
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
"appscript",
"aiagent"
]
}
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE v2_job_status SET\n flow_status = jsonb_set(\n flow_status,\n array['modules', $3::TEXT, 'agent_actions'],\n $2\n )\n WHERE id = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Jsonb",
"Text"
]
},
"nullable": []
},
"hash": "9a923c85a015e4149328f650e77872a1c39d5992efd6abd2d3b8a558d7b884a1"
}
@@ -98,7 +98,8 @@
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
"appscript",
"aiagent"
]
}
}
@@ -35,7 +35,8 @@
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
"appscript",
"aiagent"
]
}
}
@@ -33,7 +33,8 @@
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
"appscript",
"aiagent"
]
}
}
@@ -18,8 +18,8 @@
"Left": []
},
"nullable": [
false,
true
true,
false
]
},
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
@@ -1,248 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n cj.id AS \"id!\",\n cj.workspace_id AS \"workspace_id!\",\n cj.parent_job,\n cj.created_by AS \"created_by!\",\n cj.duration_ms AS \"duration_ms!\",\n cj.success AS \"success!\",\n cj.script_hash AS \"script_hash!: Option<ScriptHash>\",\n cj.script_path,\n cj.args AS \"args: sqlx::types::Json<HashMap<String, Box<RawValue>>>\",\n cj.result AS \"result: sqlx::types::Json<Box<RawValue>>\",\n cj.deleted AS \"deleted!\",\n cj.canceled AS \"canceled!\",\n cj.canceled_by,\n cj.canceled_reason,\n cj.job_kind AS \"job_kind!: JobKind\",\n cj.schedule_path,\n cj.permissioned_as AS \"permissioned_as!\",\n cj.is_flow_step AS \"is_flow_step!\",\n cj.language AS \"language: ScriptLang\",\n cj.is_skipped AS \"is_skipped!\",\n cj.email AS \"email!\",\n cj.visible_to_owner AS \"visible_to_owner!\",\n cj.mem_peak,\n cj.tag AS \"tag!\",\n cj.created_at AS \"created_at!\",\n cj.started_at,\n job_logs.logs,\n job_logs.log_offset AS \"log_offset?\",\n job_logs.log_file_index\n\n FROM v2_as_completed_job AS cj\n LEFT JOIN job_logs ON cj.id = job_logs.job_id\n WHERE cj.created_at < $1\n ORDER BY cj.created_at ASC LIMIT $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "workspace_id!",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "parent_job",
"type_info": "Uuid"
},
{
"ordinal": 3,
"name": "created_by!",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "duration_ms!",
"type_info": "Int8"
},
{
"ordinal": 5,
"name": "success!",
"type_info": "Bool"
},
{
"ordinal": 6,
"name": "script_hash!: Option<ScriptHash>",
"type_info": "Int8"
},
{
"ordinal": 7,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "args: sqlx::types::Json<HashMap<String, Box<RawValue>>>",
"type_info": "Jsonb"
},
{
"ordinal": 9,
"name": "result: sqlx::types::Json<Box<RawValue>>",
"type_info": "Jsonb"
},
{
"ordinal": 10,
"name": "deleted!",
"type_info": "Bool"
},
{
"ordinal": 11,
"name": "canceled!",
"type_info": "Bool"
},
{
"ordinal": 12,
"name": "canceled_by",
"type_info": "Varchar"
},
{
"ordinal": 13,
"name": "canceled_reason",
"type_info": "Text"
},
{
"ordinal": 14,
"name": "job_kind!: JobKind",
"type_info": {
"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"
]
}
}
}
},
{
"ordinal": 15,
"name": "schedule_path",
"type_info": "Varchar"
},
{
"ordinal": 16,
"name": "permissioned_as!",
"type_info": "Varchar"
},
{
"ordinal": 17,
"name": "is_flow_step!",
"type_info": "Bool"
},
{
"ordinal": 18,
"name": "language: ScriptLang",
"type_info": {
"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"
]
}
}
}
},
{
"ordinal": 19,
"name": "is_skipped!",
"type_info": "Bool"
},
{
"ordinal": 20,
"name": "email!",
"type_info": "Varchar"
},
{
"ordinal": 21,
"name": "visible_to_owner!",
"type_info": "Bool"
},
{
"ordinal": 22,
"name": "mem_peak",
"type_info": "Int4"
},
{
"ordinal": 23,
"name": "tag!",
"type_info": "Varchar"
},
{
"ordinal": 24,
"name": "created_at!",
"type_info": "Timestamptz"
},
{
"ordinal": 25,
"name": "started_at",
"type_info": "Timestamptz"
},
{
"ordinal": 26,
"name": "logs",
"type_info": "Text"
},
{
"ordinal": 27,
"name": "log_offset?",
"type_info": "Int4"
},
{
"ordinal": 28,
"name": "log_file_index",
"type_info": "TextArray"
}
],
"parameters": {
"Left": [
"Timestamptz",
"Int8"
]
},
"nullable": [
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
false,
true
]
},
"hash": "f22964772dc2d67aee437bbbd08b64792c00da1d713d7ca8f9904ccce7bfdae7"
}
@@ -35,7 +35,8 @@
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
"appscript",
"aiagent"
]
}
}
+121 -120
View File
@@ -1524,7 +1524,7 @@ dependencies = [
"serde_json",
"serde_repr",
"serde_urlencoded",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-util",
"tower-service",
@@ -1545,9 +1545,9 @@ dependencies = [
[[package]]
name = "bon"
version = "3.7.0"
version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a0c21249ad725ebcadcb1b1885f8e3d56e8e6b8924f560268aab000982d637"
checksum = "537c317ddf588aab15c695bf92cf55dec159b93221c074180ca3e0e5a94da415"
dependencies = [
"bon-macros",
"rustversion",
@@ -1555,9 +1555,9 @@ dependencies = [
[[package]]
name = "bon-macros"
version = "3.7.0"
version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a660ebdea4d4d3ec7788cfc9c035b66efb66028b9b97bf6cde7023ccc8e77e28"
checksum = "ca5abbf2d4a4c6896197c9de13d6d7cb7eff438c63dacde1dde980569cb00248"
dependencies = [
"darling 0.21.2",
"ident_case",
@@ -1921,9 +1921,9 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "1.0.1"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "cfg_aliases"
@@ -3314,7 +3314,7 @@ dependencies = [
"swc_visit",
"swc_visit_macros",
"text_lines",
"thiserror 2.0.15",
"thiserror 2.0.16",
"unicode-width 0.1.14",
"url",
]
@@ -3328,7 +3328,7 @@ dependencies = [
"async-trait",
"deno_core",
"deno_error",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"uuid",
]
@@ -3345,7 +3345,7 @@ dependencies = [
"rusqlite",
"serde",
"sha2 0.10.9",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
]
@@ -3389,7 +3389,7 @@ dependencies = [
"deno_webgpu",
"image",
"serde",
"thiserror 2.0.15",
"thiserror 2.0.16",
]
[[package]]
@@ -3415,7 +3415,7 @@ dependencies = [
"serde",
"serde_json",
"sys_traits",
"thiserror 2.0.15",
"thiserror 2.0.16",
"url",
]
@@ -3460,7 +3460,7 @@ dependencies = [
"smallvec",
"sourcemap 8.0.1",
"static_assertions",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"url",
"v8",
@@ -3485,7 +3485,7 @@ dependencies = [
"deno_core",
"deno_error",
"saffron",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
]
@@ -3523,7 +3523,7 @@ dependencies = [
"sha2 0.10.9",
"signature",
"spki",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"uuid",
"x25519-dalek",
@@ -3583,7 +3583,7 @@ dependencies = [
"rustls-webpki 0.102.8",
"serde",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-rustls 0.26.2",
"tokio-socks",
@@ -3611,7 +3611,7 @@ dependencies = [
"serde",
"serde-value",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"winapi",
]
@@ -3637,7 +3637,7 @@ dependencies = [
"rand 0.8.5",
"rayon",
"serde",
"thiserror 2.0.15",
"thiserror 2.0.16",
"winapi",
"windows-sys 0.59.0",
]
@@ -3676,7 +3676,7 @@ dependencies = [
"scopeguard",
"serde",
"smallvec",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-util",
]
@@ -3736,7 +3736,7 @@ dependencies = [
"rand 0.8.5",
"rusqlite",
"serde",
"thiserror 2.0.15",
"thiserror 2.0.16",
"url",
]
@@ -3749,7 +3749,7 @@ dependencies = [
"deno_semver",
"serde",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
]
[[package]]
@@ -3776,7 +3776,7 @@ dependencies = [
"libloading 0.7.4",
"log",
"napi_sym",
"thiserror 2.0.15",
"thiserror 2.0.16",
"windows-sys 0.59.0",
]
@@ -3810,7 +3810,7 @@ dependencies = [
"rustls-tokio-stream",
"serde",
"socket2 0.5.10",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
]
@@ -3897,7 +3897,7 @@ dependencies = [
"spki",
"stable_deref_trait",
"sys_traits",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-eld",
"url",
@@ -3925,7 +3925,7 @@ dependencies = [
"monch",
"serde",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
"url",
]
@@ -3943,7 +3943,7 @@ dependencies = [
"strum 0.25.0",
"strum_macros 0.25.3",
"syn 2.0.106",
"thiserror 2.0.15",
"thiserror 2.0.16",
]
[[package]]
@@ -3964,7 +3964,7 @@ dependencies = [
"serde",
"signal-hook",
"signal-hook-registry",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"winapi",
]
@@ -3983,7 +3983,7 @@ dependencies = [
"serde",
"serde_json",
"sys_traits",
"thiserror 2.0.15",
"thiserror 2.0.16",
"url",
]
@@ -3996,7 +3996,7 @@ dependencies = [
"deno_error",
"percent-encoding",
"sys_traits",
"thiserror 2.0.15",
"thiserror 2.0.16",
"url",
]
@@ -4017,7 +4017,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"serde",
"thiserror 2.0.15",
"thiserror 2.0.16",
"which 6.0.3",
"winapi",
]
@@ -4044,7 +4044,7 @@ dependencies = [
"serde",
"simd-json",
"tempfile",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"which 6.0.3",
"winapi",
@@ -4078,7 +4078,7 @@ dependencies = [
"once_cell",
"parking_lot 0.12.4",
"sys_traits",
"thiserror 2.0.15",
"thiserror 2.0.16",
"url",
]
@@ -4143,7 +4143,7 @@ dependencies = [
"serde",
"sys_traits",
"tempfile",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-metrics",
"twox-hash 1.6.3",
@@ -4166,7 +4166,7 @@ dependencies = [
"monch",
"once_cell",
"serde",
"thiserror 2.0.15",
"thiserror 2.0.16",
"url",
]
@@ -4193,7 +4193,7 @@ dependencies = [
"opentelemetry_sdk",
"pin-project",
"serde",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
]
@@ -4221,7 +4221,7 @@ dependencies = [
"rustls-tokio-stream",
"rustls-webpki 0.102.8",
"serde",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"webpki-roots 0.26.11",
]
@@ -4245,7 +4245,7 @@ checksum = "d79e743ad841f7826d46c6944580f5ba665fe9ab4c31a68c4eed8b5a78225da3"
dependencies = [
"deno_core",
"deno_error",
"thiserror 2.0.15",
"thiserror 2.0.16",
"urlpattern",
]
@@ -4265,7 +4265,7 @@ dependencies = [
"flate2",
"futures",
"serde",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"uuid",
]
@@ -4280,7 +4280,7 @@ dependencies = [
"deno_error",
"raw-window-handle",
"serde",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"wgpu-core",
"wgpu-types",
@@ -4316,7 +4316,7 @@ dependencies = [
"once_cell",
"rustls-tokio-stream",
"serde",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
]
@@ -4330,7 +4330,7 @@ dependencies = [
"deno_error",
"deno_web",
"rusqlite",
"thiserror 2.0.15",
"thiserror 2.0.16",
]
[[package]]
@@ -4378,7 +4378,7 @@ dependencies = [
"rand 0.8.5",
"serde",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-util",
"url",
@@ -4403,7 +4403,7 @@ dependencies = [
"rand 0.8.5",
"rusqlite",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-stream",
"uuid",
@@ -5230,14 +5230,14 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "filetime"
version = "0.2.25"
version = "0.2.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -6332,7 +6332,7 @@ dependencies = [
"reqwest 0.12.23",
"serde",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"ureq",
"windows-sys 0.60.2",
@@ -6357,7 +6357,7 @@ dependencies = [
"once_cell",
"rand 0.9.0",
"serde",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tinyvec",
"tokio",
"tracing",
@@ -6381,7 +6381,7 @@ dependencies = [
"resolv-conf",
"serde",
"smallvec",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tracing",
]
@@ -6923,7 +6923,7 @@ dependencies = [
"percent-encoding",
"serde",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
"url",
]
@@ -7012,9 +7012,9 @@ checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02"
[[package]]
name = "inventory"
version = "0.3.20"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83"
checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e"
dependencies = [
"rustversion",
]
@@ -7199,7 +7199,7 @@ dependencies = [
"pest_derive",
"regex",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
]
[[package]]
@@ -7389,7 +7389,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-util",
"tower 0.5.2",
@@ -7413,7 +7413,7 @@ dependencies = [
"serde",
"serde-value",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
]
[[package]]
@@ -7451,7 +7451,7 @@ dependencies = [
"pin-project",
"serde",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-util",
"tracing",
@@ -8293,7 +8293,7 @@ dependencies = [
"quote",
"syn 2.0.106",
"termcolor",
"thiserror 2.0.15",
"thiserror 2.0.16",
]
[[package]]
@@ -8318,7 +8318,7 @@ dependencies = [
"serde",
"serde_json",
"socket2 0.5.10",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-native-tls",
"tokio-util",
@@ -8350,7 +8350,7 @@ dependencies = [
"serde_json",
"sha1",
"sha2 0.10.9",
"thiserror 2.0.15",
"thiserror 2.0.16",
"uuid",
]
@@ -8520,7 +8520,7 @@ dependencies = [
"serde",
"serde_json",
"sys_traits",
"thiserror 2.0.15",
"thiserror 2.0.16",
"url",
]
@@ -8687,7 +8687,7 @@ dependencies = [
"num-format",
"serde",
"serde_json",
"thiserror 2.0.15",
"thiserror 2.0.16",
"typetag",
"windows-sys 0.48.0",
]
@@ -8954,7 +8954,7 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tracing",
"url",
@@ -9545,7 +9545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
dependencies = [
"memchr",
"thiserror 2.0.15",
"thiserror 2.0.16",
"ucd-trie",
]
@@ -10081,7 +10081,7 @@ dependencies = [
"lazy_static",
"memchr",
"parking_lot 0.12.4",
"thiserror 2.0.15",
"thiserror 2.0.16",
]
[[package]]
@@ -10265,7 +10265,7 @@ dependencies = [
"rustc-hash 2.1.1",
"rustls 0.23.29",
"socket2 0.5.10",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tracing",
"web-time",
@@ -10286,7 +10286,7 @@ dependencies = [
"rustls 0.23.29",
"rustls-pki-types",
"slab",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tinyvec",
"tracing",
"web-time",
@@ -10582,7 +10582,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.16",
"libredox",
"thiserror 2.0.15",
"thiserror 2.0.16",
]
[[package]]
@@ -10911,7 +10911,7 @@ dependencies = [
"serde",
"serde_json",
"sse-stream",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-stream",
"tokio-util",
@@ -11712,9 +11712,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.142"
version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
dependencies = [
"indexmap 2.10.0",
"itoa",
@@ -11793,7 +11793,7 @@ dependencies = [
"num-bigint",
"serde",
"smallvec",
"thiserror 2.0.15",
"thiserror 2.0.16",
"v8",
]
@@ -12021,7 +12021,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
dependencies = [
"num-bigint",
"num-traits",
"thiserror 2.0.15",
"thiserror 2.0.16",
"time",
]
@@ -12303,7 +12303,7 @@ dependencies = [
"serde_json",
"sha2 0.10.9",
"smallvec",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tokio-stream",
"tracing",
@@ -12389,7 +12389,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tracing",
"uuid",
"whoami",
@@ -12430,7 +12430,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tracing",
"uuid",
"whoami",
@@ -12456,7 +12456,7 @@ dependencies = [
"serde",
"serde_urlencoded",
"sqlx-core",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tracing",
"url",
"uuid",
@@ -13215,7 +13215,7 @@ dependencies = [
"tantivy-stacker",
"tantivy-tokenizer-api",
"tempfile",
"thiserror 2.0.15",
"thiserror 2.0.16",
"time",
"uuid",
"winapi",
@@ -13327,15 +13327,15 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.20.0"
version = "3.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
dependencies = [
"fastrand",
"getrandom 0.3.3",
"once_cell",
"rustix 1.0.8",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -13387,11 +13387,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.15"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl 2.0.15",
"thiserror-impl 2.0.16",
]
[[package]]
@@ -13407,9 +13407,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.15"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
@@ -14913,7 +14913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eeee3bdea6257cc36d756fa745a70f9d393571e47d69e0ed97581676a5369ca"
dependencies = [
"deno_error",
"thiserror 2.0.15",
"thiserror 2.0.16",
]
[[package]]
@@ -15114,11 +15114,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.9"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -15129,7 +15129,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"axum",
@@ -15183,7 +15183,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"argon2",
@@ -15263,7 +15263,7 @@ dependencies = [
"sql-builder",
"sqlx",
"tempfile",
"thiserror 2.0.15",
"thiserror 2.0.16",
"time",
"tinyvector",
"tokenizers",
@@ -15300,7 +15300,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -15315,7 +15315,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"chrono",
"serde",
@@ -15328,7 +15328,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"axum",
@@ -15337,7 +15337,7 @@ dependencies = [
"serde",
"serde_json",
"sqlx",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tokio",
"tracing",
"uuid",
@@ -15347,7 +15347,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15405,7 +15405,7 @@ dependencies = [
"systemstat",
"tar",
"tempfile",
"thiserror 2.0.15",
"thiserror 2.0.16",
"tikv-jemalloc-ctl",
"tokio",
"tokio-stream",
@@ -15427,7 +15427,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"regex",
"serde",
@@ -15442,7 +15442,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"bytes",
@@ -15466,7 +15466,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -15478,7 +15478,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -15487,7 +15487,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15499,7 +15499,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15511,7 +15511,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"gosyn",
@@ -15523,7 +15523,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15535,7 +15535,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15547,7 +15547,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"nu-parser",
@@ -15558,7 +15558,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -15569,7 +15569,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -15581,7 +15581,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15604,7 +15604,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15618,7 +15618,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -15635,7 +15635,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15649,7 +15649,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15667,7 +15667,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"getrandom 0.2.16",
@@ -15692,7 +15692,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15702,7 +15702,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15735,7 +15735,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -15745,7 +15745,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.528.0"
version = "1.530.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15812,6 +15812,7 @@ dependencies = [
"tokio-stream",
"tokio-util",
"tracing",
"ulid",
"url",
"urlencoding",
"uuid",
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.528.0"
version = "1.530.0"
authors.workspace = true
edition.workspace = true
@@ -33,7 +33,7 @@ members = [
]
[workspace.package]
version = "1.528.0"
version = "1.530.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
+1 -1
View File
@@ -1 +1 @@
1b5dd04984e11c32a4235021b8a344630b374651
15a7592ca66b93b9760d49e58b23c090ead06fe2
@@ -41,7 +41,7 @@ def load_openapi_spec(file_path: str) -> Dict[str, Any]:
print(f"Error loading OpenAPI spec: {e}", file=sys.stderr)
sys.exit(1)
def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Optional[Dict[str, Any]], spec: Dict[str, Any]) -> tuple:
def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Optional[Dict[str, Any]], spec: Dict[str, Any], required_fields: Optional[List[str]] = None) -> tuple:
"""Extract separate schemas for path parameters, query parameters, and request body."""
path_params_schema = {
"type": "object",
@@ -92,6 +92,20 @@ def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Opt
# Process request body if present
if request_body:
body_schema = extract_request_body_schema(request_body, spec)
# If we have required fields specified and a body schema, update the required array
if body_schema and required_fields:
if 'required' not in body_schema:
body_schema['required'] = []
# Add each required field if it exists in the schema properties
for field in required_fields:
if 'properties' in body_schema and field in body_schema['properties']:
if field not in body_schema['required']:
body_schema['required'].append(field)
else:
# Log warning when a required field is missing from schema properties
print(f"Warning: Required field '{field}' not found in body schema properties", file=sys.stderr)
# Return None for empty schemas
path_params_schema = path_params_schema if path_params_schema['properties'] else None
@@ -199,6 +213,7 @@ def find_mcp_tools(spec: Dict[str, Any]) -> List[Dict[str, Any]]:
'method': method.upper(),
'parameters': operation.get('parameters', []),
'requestBody': operation.get('requestBody'),
'required_fields': operation.get('x-mcp-required-fields', []),
}
tools.append(tool)
@@ -227,7 +242,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
# Generate separate schemas
path_params_schema, query_params_schema, body_schema = extract_separate_schemas(
tool['parameters'], tool['requestBody'], spec
tool['parameters'], tool['requestBody'], spec, tool['required_fields']
)
path_params_rust = schema_to_rust_value(path_params_schema)
@@ -0,0 +1 @@
-- Add down migration script here
@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TYPE JOB_KIND ADD VALUE IF NOT EXISTS 'aiagent';
+45 -3
View File
@@ -114,6 +114,12 @@ lazy_static::lazy_static! {
&["tag"]
).unwrap();
static ref QUEUE_RUNNING_COUNT: prometheus::IntGaugeVec = prometheus::register_int_gauge_vec!(
"queue_running_count",
"Number of running jobs in the queue",
&["tag"]
).unwrap();
}
lazy_static::lazy_static! {
static ref ZOMBIE_JOB_TIMEOUT: String = std::env::var("ZOMBIE_JOB_TIMEOUT")
@@ -140,6 +146,7 @@ lazy_static::lazy_static! {
pub static ref WORKERS_NAMES: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
static ref QUEUE_COUNT_TAGS: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
static ref QUEUE_RUNNING_COUNT_TAGS: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
static ref DISABLE_CONCURRENCY_LIMIT: bool = std::env::var("DISABLE_CONCURRENCY_LIMIT").is_ok_and(|s| s == "true");
static ref STALE_JOB_TRESHOLD_MINUTES: Option<u64> = std::env::var("STALE_JOB_TRESHOLD_MINUTES")
@@ -1324,7 +1331,10 @@ pub async fn reload_url_list_setting(
match url::Url::parse(url_str) {
Ok(url) => urls.push(url),
Err(e) => {
return Err(error::Error::BadRequest(format!("Invalid URL in FORCE_{}: '{}': {}", std_env_var, url_str, e)));
return Err(error::Error::BadRequest(format!(
"Invalid URL in FORCE_{}: '{}': {}",
std_env_var, url_str, e
)));
}
}
}
@@ -1347,7 +1357,11 @@ pub async fn reload_url_list_setting(
}
}
}
if urls.is_empty() { None } else { Some(urls) }
if urls.is_empty() {
None
} else {
Some(urls)
}
} else {
None
};
@@ -1365,7 +1379,11 @@ pub async fn reload_url_list_setting(
}
}
}
tracing::info!("Loaded setting {} from db config: {} URLs", setting_name, urls.len());
tracing::info!(
"Loaded setting {} from db config: {} URLs",
setting_name,
urls.len()
);
value = if urls.is_empty() { None } else { Some(urls) };
} else {
tracing::error!("Could not parse {} found: {:#?}", setting_name, &q);
@@ -1671,6 +1689,30 @@ pub async fn expose_queue_metrics(db: &Pool<Postgres>) {
let mut w = QUEUE_COUNT_TAGS.write().await;
*w = tags_to_watch;
}
#[cfg(feature = "prometheus")]
if metrics_enabled {
// Handle queue running count metrics
let queue_running_counts = windmill_common::queue::get_queue_running_counts(db).await;
for q in QUEUE_RUNNING_COUNT_TAGS.read().await.iter() {
if queue_running_counts.get(q).is_none() {
(*QUEUE_RUNNING_COUNT).with_label_values(&[q]).set(0);
}
}
let mut running_tags_to_watch = vec![];
for q in queue_running_counts {
let count = q.1;
let tag = q.0;
let metric = (*QUEUE_RUNNING_COUNT).with_label_values(&[&tag]);
metric.set(count as i64);
running_tags_to_watch.push(tag.to_string());
}
let mut w = QUEUE_RUNNING_COUNT_TAGS.write().await;
*w = running_tags_to_watch;
}
}
// clean queue metrics older than 14 days
+1
View File
@@ -959,6 +959,7 @@ impl RunJob {
None,
None,
None,
false,
)
.await
.expect("push has to succeed");
+78 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.528.0
version: 1.530.0
title: Windmill API
contact:
@@ -7193,6 +7193,36 @@ paths:
type: string
format: uuid
/w/{workspace}/jobs/run_wait_result/preview:
post:
summary: run script preview and wait for result
operationId: runScriptPreviewAndWaitResult
x-mcp-tool: true
x-mcp-instructions: Allows testing a script before deploying it. For typescript code, the language to send is either bun or deno. By default, send bun if no deno specific code is detected.
x-mcp-required-fields:
- content
- language
- args
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: preview
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Preview"
responses:
"200":
description: job result
content:
application/json:
schema: {}
/w/{workspace}/jobs/workflow_as_code/{job_id}/{entrypoint}:
post:
summary: run code-workflow task
@@ -7302,6 +7332,30 @@ paths:
type: string
format: uuid
/w/{workspace}/jobs/run_wait_result/preview_flow:
post:
summary: run flow preview and wait for result
operationId: runFlowPreviewAndWaitResult
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: preview
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/FlowPreview"
responses:
"200":
description: job result
content:
application/json:
schema: {}
/w/{workspace}/jobs/queue/list:
get:
summary: list all queued jobs
@@ -11502,6 +11556,22 @@ paths:
additionalProperties:
type: integer
/workers/queue_running_counts:
get:
summary: get counts of currently running jobs per tag
operationId: getCountsOfRunningJobsPerTag
tags:
- worker
responses:
"200":
description: queue running counts
content:
application/json:
schema:
type: object
additionalProperties:
type: integer
/configs/list_worker_groups:
get:
summary: list worker groups
@@ -13945,6 +14015,8 @@ components:
$ref: "../../openflow.openapi.yaml#/components/schemas/BranchOne"
BranchAll:
$ref: "../../openflow.openapi.yaml#/components/schemas/BranchAll"
AiAgent:
$ref: "../../openflow.openapi.yaml#/components/schemas/AiAgent"
Identity:
$ref: "../../openflow.openapi.yaml#/components/schemas/Identity"
FlowStatus:
@@ -14427,6 +14499,7 @@ components:
"flowscript",
"flownode",
"appscript",
"aiagent",
]
schedule_path:
type: string
@@ -14536,6 +14609,7 @@ components:
"flowscript",
"flownode",
"appscript",
"aiagent",
]
schedule_path:
type: string
@@ -15154,10 +15228,13 @@ components:
properties:
content:
type: string
description: The code to run
path:
type: string
description: The path to the script
script_hash:
type: string
description: The hash of the script
args:
$ref: "#/components/schemas/ScriptArgs"
language:
+3
View File
@@ -1092,6 +1092,7 @@ async fn create_app_internal<'a>(
None,
None,
Some(&authed.clone().into()),
false,
)
.await?;
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
@@ -1469,6 +1470,7 @@ async fn update_app_internal<'a>(
None,
None,
Some(&authed.clone().into()),
false,
)
.await?;
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
@@ -1782,6 +1784,7 @@ async fn execute_component(
None,
None,
None,
false,
)
.await?;
tx.commit().await?;
+2
View File
@@ -519,6 +519,7 @@ async fn create_flow(
None,
None,
Some(&authed.clone().into()),
false,
)
.await?;
@@ -985,6 +986,7 @@ async fn update_flow(
None,
None,
Some(&authed.clone().into()),
false,
)
.await?;
sqlx::query!(
+52
View File
@@ -177,12 +177,14 @@ pub fn workspaced_service() -> Router {
.layer(ce_headers.clone()),
)
.route("/run/preview", post(run_preview_script))
.route("/run_wait_result/preview", post(run_wait_result_preview_script))
.route(
"/run/preview_bundle",
post(run_bundle_preview_script).layer(axum::extract::DefaultBodyLimit::disable()),
)
.route("/add_batch_jobs/:n", post(add_batch_jobs))
.route("/run/preview_flow", post(run_preview_flow_job))
.route("/run_wait_result/preview_flow", post(run_wait_result_preview_flow))
.route("/list", get(list_jobs))
.route(
"/list_selected_job_groups",
@@ -3883,6 +3885,7 @@ pub async fn run_flow_by_path_inner(
None,
None,
push_authed.as_ref(),
false
)
.await?;
tx.commit().await?;
@@ -3977,6 +3980,7 @@ pub async fn restart_flow(
None,
completed_job.priority,
Some(&authed.clone().into()),
false
)
.await?;
tx.commit().await?;
@@ -4072,6 +4076,7 @@ pub async fn run_script_by_path_inner(
None,
None,
push_authed.as_ref(),
false,
)
.await?;
tx.commit().await?;
@@ -4220,6 +4225,7 @@ pub async fn run_workflow_as_code(
None,
None,
push_authed.as_ref(),
false,
)
.await?;
@@ -4750,6 +4756,7 @@ pub async fn run_wait_result_job_by_path_get(
None,
None,
push_authed.as_ref(),
false,
)
.await?;
tx.commit().await?;
@@ -4900,6 +4907,7 @@ pub async fn run_wait_result_script_by_path_internal(
None,
None,
push_authed.as_ref(),
false,
)
.await?;
tx.commit().await?;
@@ -5014,6 +5022,7 @@ pub async fn run_wait_result_script_by_hash(
None,
None,
push_authed.as_ref(),
false,
)
.await?;
tx.commit().await?;
@@ -5131,6 +5140,7 @@ pub async fn run_wait_result_flow_by_path_internal(
None,
None,
push_authed.as_ref(),
false,
)
.await?;
tx.commit().await?;
@@ -5200,6 +5210,7 @@ async fn run_preview_script(
None,
None,
Some(&authed.clone().into()),
false,
)
.await?;
tx.commit().await?;
@@ -5207,6 +5218,28 @@ async fn run_preview_script(
Ok((StatusCode::CREATED, uuid.to_string()))
}
async fn run_wait_result_preview_script(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Path(w_id): Path<String>,
Query(run_query): Query<RunJobQuery>,
Json(preview): Json<Preview>,
) -> error::Result<Response> {
let (_status_code, uuid) = run_preview_script(
authed.clone(),
Extension(db.clone()),
Extension(user_db.clone()),
Path(w_id.clone()),
Query(run_query.clone()),
Json(preview)
).await?;
let uuid = uuid.parse::<Uuid>().map_err(|_| Error::BadRequest("Invalid UUID".to_string()))?;
let result = run_wait_result(&db, uuid, w_id, None, &authed.username).await;
return result;
}
async fn run_bundle_preview_script(
authed: ApiAuthed,
Extension(db): Extension<DB>,
@@ -5288,6 +5321,7 @@ async fn run_bundle_preview_script(
None,
None,
Some(&authed.clone().into()),
false,
)
.await?;
job_id = Some(uuid);
@@ -5453,6 +5487,7 @@ async fn run_dependencies_job(
None,
None,
Some(&authed.clone().into()),
false,
)
.await?;
tx.commit().await?;
@@ -5518,6 +5553,7 @@ async fn run_flow_dependencies_job(
None,
None,
Some(&authed.clone().into()),
false,
)
.await?;
tx.commit().await?;
@@ -5858,6 +5894,7 @@ async fn run_preview_flow_job(
None,
None,
Some(&authed.clone().into()),
false,
)
.await?;
tx.commit().await?;
@@ -5865,6 +5902,20 @@ async fn run_preview_flow_job(
Ok((StatusCode::CREATED, uuid.to_string()))
}
async fn run_wait_result_preview_flow(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Path(w_id): Path<String>,
Query(run_query): Query<RunJobQuery>,
Json(raw_flow): Json<PreviewFlow>,
) -> error::Result<Response> {
let (_status_code, uuid) = run_preview_flow_job(authed.clone(), Extension(db.clone()), Extension(user_db.clone()), Path(w_id.clone()), Query(run_query.clone()), Json(raw_flow)).await?;
let uuid = uuid.parse::<Uuid>().map_err(|_| Error::BadRequest("Invalid UUID".to_string()))?;
let result = run_wait_result(&db, uuid, w_id, None, &authed.username).await;
return result;
}
pub async fn run_job_by_hash(
authed: ApiAuthed,
Extension(db): Extension<DB>,
@@ -5983,6 +6034,7 @@ pub async fn run_job_by_hash_inner(
None,
None,
push_authed.as_ref(),
false,
)
.await?;
tx.commit().await?;
@@ -564,6 +564,86 @@ pub fn all_tools() -> Vec<EndpointTool> {
})),
body_schema: None,
},
EndpointTool {
name: Cow::Borrowed("runScriptPreviewAndWaitResult"),
description: Cow::Borrowed("run script preview and wait for result"),
instructions: Cow::Borrowed("Allows testing a script before deploying it. For typescript code, the language to send is either bun or deno. By default, send bun if no deno specific code is detected."),
path: Cow::Borrowed("/w/{workspace}/jobs/run_wait_result/preview"),
method: Cow::Borrowed("POST"),
path_params_schema: None,
query_params_schema: None,
body_schema: Some(serde_json::json!({
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "The code to run"
},
"path": {
"type": "string",
"description": "The path to the script"
},
"script_hash": {
"type": "string",
"description": "The hash of the script"
},
"args": {
"type": "object",
"description": "The arguments to pass to the script or flow",
"additionalProperties": {}
},
"language": {
"type": "string",
"enum": [
"python3",
"deno",
"go",
"bash",
"powershell",
"postgresql",
"mysql",
"bigquery",
"snowflake",
"mssql",
"oracledb",
"graphql",
"nativets",
"bun",
"php",
"rust",
"ansible",
"csharp",
"nu",
"java",
"ruby",
"duckdb"
]
},
"tag": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"code",
"identity",
"http"
]
},
"dedicated_worker": {
"type": "boolean"
},
"lock": {
"type": "string"
}
},
"required": [
"args",
"content",
"language"
]
})),
},
EndpointTool {
name: Cow::Borrowed("listQueue"),
description: Cow::Borrowed("list all queued jobs"),
+11 -2
View File
@@ -1023,6 +1023,7 @@ async fn create_script_internal<'c>(
None,
None,
Some(&authed.clone().into()),
false,
)
.await?;
Ok((hash, new_tx, None))
@@ -1388,13 +1389,19 @@ async fn get_empty_ts_script_by_path() -> String {
return String::new();
}
#[derive(Deserialize)]
struct RawScriptByPathQuery {
cache_key: Option<String>,
}
async fn raw_script_by_path(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
Query(query): Query<RawScriptByPathQuery>,
) -> Result<String> {
raw_script_by_path_internal(path, user_db, db, authed, w_id, false).await
raw_script_by_path_internal(path, user_db, db, authed, w_id, false, query.cache_key).await
}
async fn raw_script_by_path_unpinned(
@@ -1402,8 +1409,9 @@ async fn raw_script_by_path_unpinned(
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
Query(query): Query<RawScriptByPathQuery>,
) -> Result<String> {
raw_script_by_path_internal(path, user_db, db, authed, w_id, true).await
raw_script_by_path_internal(path, user_db, db, authed, w_id, true, query.cache_key).await
}
lazy_static::lazy_static! {
@@ -1418,6 +1426,7 @@ async fn raw_script_by_path_internal(
authed: ApiAuthed,
w_id: String,
unpin: bool,
cache_key: Option<String>,
) -> Result<String> {
let path = path.to_path();
check_scopes(&authed, || format!("scripts:read:{}", path))?;
@@ -798,6 +798,7 @@ async fn trigger_script_with_retry_and_error_handler(
None,
None,
push_authed.as_ref(),
false,
)
.await?;
tx.commit().await?;
+10
View File
@@ -37,6 +37,7 @@ pub fn global_service() -> Router {
.route("/get_default_tags", get(get_default_tags))
.route("/queue_metrics", get(get_queue_metrics))
.route("/queue_counts", get(get_queue_counts))
.route("/queue_running_counts", get(get_queue_running_counts))
}
#[derive(FromRow, Serialize, Deserialize)]
@@ -219,3 +220,12 @@ async fn get_queue_counts(
let queue_counts = windmill_common::queue::get_queue_counts(&db).await;
Ok(Json(queue_counts))
}
async fn get_queue_running_counts(
authed: ApiAuthed,
Extension(db): Extension<DB>,
) -> JsonResult<std::collections::HashMap<String, u32>> {
require_super_admin(&db, &authed.email).await?;
let queue_running_counts = windmill_common::queue::get_queue_running_counts(&db).await;
Ok(Json(queue_running_counts))
}
+30 -12
View File
@@ -8,6 +8,7 @@ use crate::{
error::{Error, Result},
jwt,
users::{SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL, SUPERADMIN_SYNC_EMAIL},
utils::WarnAfterExt,
DB,
};
@@ -259,6 +260,22 @@ pub async fn get_groups_for_user(
Ok(groups)
}
pub async fn get_job_perms<'a, E: sqlx::PgExecutor<'a>>(
db: E,
job_id: &Uuid,
w_id: &str,
) -> sqlx::Result<Option<JobPerms>> {
sqlx::query_as!(
JobPerms,
"SELECT email, username, is_admin, is_operator, groups, folders FROM job_perms WHERE job_id = $1 AND workspace_id = $2",
job_id,
w_id
)
.fetch_optional(db)
.warn_after_seconds(3)
.await
}
#[tracing::instrument(level = "trace", skip_all)]
pub async fn create_token_for_owner(
db: &DB,
@@ -274,14 +291,7 @@ pub async fn create_token_for_owner(
let job_perms = if perms.is_some() {
Ok(perms)
} else {
sqlx::query_as!(
JobPerms,
"SELECT email, username, is_admin, is_operator, groups, folders FROM job_perms WHERE job_id = $1 AND workspace_id = $2",
job_id,
w_id
)
.fetch_optional(db)
.await
get_job_perms(db, job_id, w_id).await
};
let job_authed = match job_perms {
Ok(Some(jp)) => jp.into(),
@@ -297,8 +307,16 @@ pub async fn create_token_for_owner(
}
};
create_jwt_token(job_authed, w_id, expires_in, Some(*job_id), Some(label.to_string()), audit_span, None)
.await
create_jwt_token(
job_authed,
w_id,
expires_in,
Some(*job_id),
Some(label.to_string()),
audit_span,
None,
)
.await
}
pub async fn create_jwt_token(
@@ -319,8 +337,8 @@ pub async fn create_jwt_token(
folders: authed.folders.clone(),
label,
workspace_id: workspace_id.to_string(),
exp: (chrono::Utc::now() + chrono::Duration::seconds(expires_in_seconds as i64))
.timestamp() as usize,
exp: (chrono::Utc::now() + chrono::Duration::seconds(expires_in_seconds as i64)).timestamp()
as usize,
job_id: job_id.map(|id| id.to_string()),
scopes,
audit_span,
+42
View File
@@ -537,6 +537,48 @@ pub mod flow {
}
}
pub mod python_import_by_path {
use super::*;
use crate::DB;
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
pub struct ScriptPathWithCacheKey {
pub path: String,
pub cache_key: String,
}
impl Item for ScriptPathWithCacheKey {
fn path(&self, root: impl AsRef<Path>) -> PathBuf {
root.as_ref()
.join(self.path.clone())
.join(self.cache_key.clone())
}
}
#[derive(Eq, PartialEq, Debug, Hash, Clone, Serialize, Deserialize)]
pub struct ScriptContentOrPrefix {
pub content: String,
pub prefix: bool,
}
make_static! {
static ref CACHE: { ScriptPathWithCacheKey => ScriptContentOrPrefix } in "python_import_by_path" <= 1000;
}
pub async fn fetch(
db: &DB,
path: &str,
w_id: &str,
cache_key: &str,
) -> error::Result<ScriptContentOrPrefix> {
let r =sqlx::query_scalar!(
"SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1",
path,
w_id
).fetch_optional(db).await?;
todo!()
}
}
pub mod script {
use crate::{worker::Connection, DB};
@@ -129,6 +129,15 @@ struct UntaggedFlowStatusModule {
approvers: Option<Vec<Approval>>,
failed_retries: Option<Vec<Uuid>>,
skipped: Option<bool>,
agent_actions: Option<Vec<AgentAction>>,
agent_actions_success: Option<Vec<bool>>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AgentAction {
ToolCall { job_id: uuid::Uuid, function_name: String, module_id: String },
Message {},
}
#[derive(Serialize, Debug, Clone)]
@@ -165,6 +174,10 @@ pub enum FlowStatusModule {
parallel: bool,
#[serde(skip_serializing_if = "std::ops::Not::not")]
while_loop: bool,
#[serde(skip_serializing_if = "Option::is_none")]
agent_actions: Option<Vec<AgentAction>>,
#[serde(skip_serializing_if = "Option::is_none")]
agent_actions_success: Option<Vec<bool>>,
},
Success {
id: String,
@@ -181,6 +194,10 @@ pub enum FlowStatusModule {
#[serde(skip_serializing_if = "Vec::is_empty")]
failed_retries: Vec<Uuid>,
skipped: bool,
#[serde(skip_serializing_if = "Option::is_none")]
agent_actions: Option<Vec<AgentAction>>,
#[serde(skip_serializing_if = "Option::is_none")]
agent_actions_success: Option<Vec<bool>>,
},
Failure {
id: String,
@@ -193,6 +210,10 @@ pub enum FlowStatusModule {
branch_chosen: Option<BranchChosen>,
#[serde(skip_serializing_if = "Vec::is_empty")]
failed_retries: Vec<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
agent_actions: Option<Vec<AgentAction>>,
#[serde(skip_serializing_if = "Option::is_none")]
agent_actions_success: Option<Vec<bool>>,
},
}
@@ -244,6 +265,8 @@ impl<'de> Deserialize<'de> for FlowStatusModule {
parallel: untagged.parallel.unwrap_or(false),
while_loop: untagged.while_loop.unwrap_or(false),
progress: untagged.progress,
agent_actions: untagged.agent_actions,
agent_actions_success: untagged.agent_actions_success,
}),
"Success" => Ok(FlowStatusModule::Success {
id: untagged
@@ -258,6 +281,8 @@ impl<'de> Deserialize<'de> for FlowStatusModule {
approvers: untagged.approvers.unwrap_or_default(),
failed_retries: untagged.failed_retries.unwrap_or_default(),
skipped: untagged.skipped.unwrap_or(false),
agent_actions: untagged.agent_actions,
agent_actions_success: untagged.agent_actions_success,
}),
"Failure" => Ok(FlowStatusModule::Failure {
id: untagged
@@ -270,6 +295,8 @@ impl<'de> Deserialize<'de> for FlowStatusModule {
flow_jobs_success: untagged.flow_jobs_success,
branch_chosen: untagged.branch_chosen,
failed_retries: untagged.failed_retries.unwrap_or_default(),
agent_actions: untagged.agent_actions,
agent_actions_success: untagged.agent_actions_success,
}),
other => Err(serde::de::Error::unknown_variant(
other,
@@ -354,6 +381,30 @@ impl FlowStatusModule {
_ => false,
}
}
pub fn agent_actions(&self) -> Option<Vec<AgentAction>> {
match self {
FlowStatusModule::InProgress { agent_actions, .. } => agent_actions.clone(),
FlowStatusModule::Success { agent_actions, .. } => agent_actions.clone(),
FlowStatusModule::Failure { agent_actions, .. } => agent_actions.clone(),
_ => None,
}
}
pub fn agent_actions_success(&self) -> Option<Vec<bool>> {
match self {
FlowStatusModule::InProgress { agent_actions_success, .. } => {
agent_actions_success.clone()
}
FlowStatusModule::Success { agent_actions_success, .. } => {
agent_actions_success.clone()
}
FlowStatusModule::Failure { agent_actions_success, .. } => {
agent_actions_success.clone()
}
_ => None,
}
}
}
impl FlowStatus {
+12
View File
@@ -529,6 +529,10 @@ pub enum FlowModuleValue {
#[serde(skip_serializing_if = "Option::is_none")]
assets: Option<Vec<AssetWithAltAccessType>>,
},
AIAgent {
input_transforms: HashMap<String, InputTransform>,
tools: Vec<FlowModule>,
},
}
fn is_none_or_empty(expr: &Option<String>) -> bool {
@@ -563,6 +567,7 @@ struct UntaggedFlowModuleValue {
default_node: Option<FlowNodeId>,
modules_node: Option<FlowNodeId>,
assets: Option<Vec<AssetWithAltAccessType>>,
tools: Option<Vec<FlowModule>>,
}
impl<'de> Deserialize<'de> for FlowModuleValue {
@@ -655,6 +660,12 @@ impl<'de> Deserialize<'de> for FlowModuleValue {
assets: untagged.assets,
}),
"identity" => Ok(FlowModuleValue::Identity),
"aiagent" => Ok(FlowModuleValue::AIAgent {
input_transforms: untagged.input_transforms.unwrap_or_default(),
tools: untagged
.tools
.ok_or_else(|| serde::de::Error::missing_field("tools"))?,
}),
other => Err(serde::de::Error::unknown_variant(
other,
&[
@@ -666,6 +677,7 @@ impl<'de> Deserialize<'de> for FlowModuleValue {
"branchall",
"rawscript",
"identity",
"aiagent",
],
)),
}
+4
View File
@@ -48,6 +48,7 @@ pub enum JobKind {
FlowScript,
FlowNode,
AppScript,
AIAgent,
}
impl JobKind {
@@ -375,6 +376,9 @@ pub enum JobPayload {
},
Identity,
Noop,
AIAgent {
path: String,
},
}
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
+3 -3
View File
@@ -627,8 +627,8 @@ pub fn get_latest_flow_version_info_for_path<
}
}
pub async fn get_latest_hash_for_path<'c>(
db: &mut sqlx::Transaction<'c, sqlx::Postgres>,
pub async fn get_latest_hash_for_path<'c, E: sqlx::PgExecutor<'c>>(
db: E,
w_id: &str,
script_path: &str,
) -> error::Result<(
@@ -652,7 +652,7 @@ pub async fn get_latest_hash_for_path<'c>(
script_path,
w_id
)
.fetch_optional(&mut **db)
.fetch_optional(db)
.await?;
let script = utils::not_found_if_none(r_o, "script", script_path)?;
+13
View File
@@ -14,3 +14,16 @@ pub async fn get_queue_counts(db: &Pool<Postgres>) -> HashMap<String, u32> {
.map(|v| v.into_iter().map(|x| (x.tag, x.count as u32)).collect())
.unwrap_or_else(|| HashMap::new())
}
pub async fn get_queue_running_counts(db: &Pool<Postgres>) -> HashMap<String, u32> {
sqlx::query!(
"SELECT tag AS \"tag!\", count(*) AS \"count!\" FROM v2_job_queue WHERE
running = true
GROUP BY tag",
)
.fetch_all(db)
.await
.ok()
.map(|v| v.into_iter().map(|x| (x.tag, x.count as u32)).collect())
.unwrap_or_else(|| HashMap::new())
}
+6
View File
@@ -642,6 +642,12 @@ pub struct PythonAnnotations {
pub py313: bool,
}
#[derive(Copy, Clone)]
#[annotations("//")]
pub struct GoAnnotations {
pub go1_22_compat: bool,
}
#[annotations("//")]
pub struct TypeScriptAnnotations {
pub npm: bool,
+1 -1
View File
@@ -117,7 +117,7 @@ pub async fn update_workflow_as_code_status(
// TODO: merge as a CTE
#[tracing::instrument(level = "trace", skip_all)]
async fn get_step_of_flow_status(db: &DB, id: Uuid) -> error::Result<Step> {
pub async fn get_step_of_flow_status(db: &DB, id: Uuid) -> error::Result<Step> {
let r = sqlx::query!(
"SELECT (flow_status->'step')::integer as step, jsonb_array_length(flow_status->'modules') as len
FROM v2_job_status WHERE id = $1",
+35 -4
View File
@@ -448,6 +448,7 @@ pub async fn push_init_job<'c>(
None,
None,
None,
false,
)
.await?;
inner_tx.commit().await?;
@@ -500,6 +501,7 @@ pub async fn push_periodic_bash_job<'c>(
None,
None,
None,
false,
)
.await?;
inner_tx.commit().await?;
@@ -1274,6 +1276,7 @@ async fn restart_job_if_perpetual_inner(
None,
queued_job.priority,
None,
false,
)
.await?;
tx.commit().await?;
@@ -2036,6 +2039,7 @@ pub async fn push_error_handler<'a, 'c, T: Serialize + Send + Sync>(
None,
priority,
None,
false,
)
.await?;
tx.commit().await?;
@@ -2144,6 +2148,7 @@ async fn handle_recovered_schedule<'a, 'c, T: Serialize + Send + Sync>(
None,
None,
None,
false,
)
.await?;
tracing::info!(
@@ -2233,6 +2238,7 @@ async fn handle_successful_schedule<'a, 'c, T: Serialize + Send + Sync>(
None,
None,
None,
false,
)
.await?;
tracing::info!(
@@ -3618,7 +3624,7 @@ pub async fn push<'c, 'd>(
root_job: Option<Uuid>,
job_id: Option<Uuid>,
_is_flow_step: bool,
mut same_worker: bool,
mut same_worker: bool, // whether the job will be executed on the same worker: if true, the job will be set to running but started_at will not be set.
pre_run_error: Option<&windmill_common::error::Error>,
visible_to_owner: bool,
mut tag: Option<String>,
@@ -3626,6 +3632,7 @@ pub async fn push<'c, 'd>(
flow_step_id: Option<String>,
_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.
) -> Result<(Uuid, Transaction<'c, Postgres>), Error> {
#[cfg(feature = "cloud")]
if *CLOUD_HOSTED {
@@ -4435,6 +4442,21 @@ pub async fn push<'c, 'd>(
None,
None,
),
JobPayload::AIAgent { path } => (
None,
Some(path),
None,
JobKind::AIAgent,
None,
None,
None,
None,
None,
None,
None,
None,
None,
),
};
let final_priority: Option<i16>;
@@ -4462,7 +4484,7 @@ pub async fn push<'c, 'd>(
final_priority
};
let is_running = same_worker;
let is_running = same_worker || running;
if let Some(flow) = raw_flow.as_ref() {
same_worker = same_worker || flow.same_worker;
@@ -4508,7 +4530,10 @@ pub async fn push<'c, 'd>(
let per_workspace = per_workspace_tag(&workspace_id).await;
let default = || {
let ntag = if job_kind.is_flow() || job_kind == JobKind::Identity {
let ntag = if job_kind.is_flow()
|| job_kind == JobKind::Identity
|| job_kind == JobKind::AIAgent
{
"flow".to_string()
} else if job_kind == JobKind::Dependencies
|| job_kind == JobKind::FlowDependencies
@@ -4667,7 +4692,7 @@ pub async fn push<'c, 'd>(
)
INSERT INTO v2_job_queue
(workspace_id, id, running, scheduled_for, started_at, tag, priority)
VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 THEN now() END, $30, $31)",
VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31)",
job_id,
workspace_id,
raw_code,
@@ -4711,6 +4736,7 @@ pub async fn push<'c, 'd>(
job_authed.groups.as_slice(),
root_job.or(parent_job),
trigger_kind as Option<JobTriggerKind>,
running,
)
.execute(&mut *tx)
.warn_after_seconds(1)
@@ -4782,6 +4808,7 @@ pub async fn push<'c, 'd>(
JobKind::FlowScript => "jobs.run.flow_script",
JobKind::FlowNode => "jobs.run.flow_node",
JobKind::AppScript => "jobs.run.app_script",
JobKind::AIAgent => "jobs.run.ai_agent",
};
let audit_author = if format!("u/{user}") != permissioned_as && user != permissioned_as {
@@ -4971,6 +4998,8 @@ async fn restarted_flows_resolution(
parallel,
while_loop: false,
progress: None,
agent_actions: None,
agent_actions_success: None,
});
}
Ok(FlowModuleValue::ForloopFlow { parallel, .. }) => {
@@ -5009,6 +5038,8 @@ async fn restarted_flows_resolution(
parallel,
while_loop: false,
progress: None,
agent_actions: None,
agent_actions_success: None,
});
}
_ => {
+2 -1
View File
@@ -153,7 +153,7 @@ pub async fn push_scheduled_job<'c>(
on_behalf_of_email,
created_by,
) = windmill_common::get_latest_hash_for_path(
&mut tx,
&mut *tx,
&schedule.workspace_id,
&schedule.script_path,
)
@@ -302,6 +302,7 @@ pub async fn push_scheduled_job<'c>(
None,
None,
push_authed,
false,
)
.await?;
+1
View File
@@ -59,6 +59,7 @@ windmill-git-sync.workspace = true
flume.workspace = true
sqlx.workspace = true
uuid.workspace = true
ulid.workspace = true
tracing.workspace = true
tokio.workspace = true
tokio-stream.workspace = true
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -758,7 +758,7 @@ pub async fn prebundle_bun_script(
pub const BUN_BUNDLE_OBJECT_STORE_PREFIX: &str = "bun_bundle/";
async fn get_script_import_updated_at(db: &DB, w_id: &str, script_path: &str) -> Result<String> {
let script_hash = get_latest_hash_for_path(&mut db.begin().await?, w_id, script_path).await?;
let script_hash = get_latest_hash_for_path(db, w_id, script_path).await?;
let last_updated_at = sqlx::query_scalar!(
"SELECT created_at FROM script WHERE workspace_id = $1 AND hash = $2",
w_id,
+3 -2
View File
@@ -549,13 +549,14 @@ pub async fn update_worker_ping_for_failed_init_script(
}
}
pub fn error_to_value(err: Error) -> serde_json::Value {
pub fn error_to_value(err: &Error) -> serde_json::Value {
match err {
Error::JsonErr(err) => err,
Error::JsonErr(err) => err.clone(),
_ => json!({"message": err.to_string(), "name": err.name()}),
}
}
#[derive(Clone)]
pub struct OccupancyMetrics {
pub running_job_started_at: Option<Instant>,
pub total_duration_of_running_jobs: f32,
@@ -433,6 +433,7 @@ async fn spawn_dedicated_workers_for_flow(
}
FlowModuleValue::Flow { .. } => (),
FlowModuleValue::Identity => (),
FlowModuleValue::AIAgent { .. } => (),
}
} else {
tracing::error!("failed to get value for module: {:?}", module);
+12 -2
View File
@@ -12,7 +12,7 @@ use uuid::Uuid;
use windmill_common::{
error::{self, Error},
utils::calculate_hash,
worker::{save_cache, write_file, Connection},
worker::{save_cache, write_file, Connection, GoAnnotations},
};
use windmill_parser_go::{parse_go_imports, REQUIRE_PARSE};
use windmill_queue::{append_logs, CanceledBy, MiniPulledJob};
@@ -464,6 +464,7 @@ pub async fn install_go_dependencies(
w_id: &str,
occupation_metrics: &mut OccupancyMetrics,
) -> error::Result<String> {
let anns = GoAnnotations::parse(code);
if raw_deps {
let go_mod =
if let Some(module) = code.lines().find(|l| l.trim_start().starts_with("module ")) {
@@ -529,7 +530,11 @@ pub async fn install_go_dependencies(
} else {
"".to_string()
};
let hash = format!("go-{}", hash);
let hash = format!(
"go{}-{}",
if anns.go1_22_compat { "1.22" } else { "" },
hash
);
let mut skip_tidy = has_sum;
@@ -579,6 +584,11 @@ pub async fn install_go_dependencies(
.args(vec!["mod", mod_command])
.stdout(Stdio::piped())
.stderr(Stdio::piped());
// If annotation used we want to call tidy with special flag to pin go to 1.22
// The reason for this that at some point we had to jump from go 1.22 to 1.25 and this addds backward compatibility.
if anns.go1_22_compat && mod_command == "tidy" {
child_cmd.args(vec!["-go", "1.22"]);
}
#[cfg(windows)]
set_windows_env_vars(&mut child_cmd);
+1
View File
@@ -16,6 +16,7 @@ mod java_executor;
#[cfg(feature = "ruby")]
mod ruby_executor;
mod ai_executor;
mod bun_executor;
pub mod common;
mod config;
@@ -647,7 +647,7 @@ pub async fn process_completed_job(
return Ok(None);
}
async fn handle_non_flow_job_error(
pub async fn handle_non_flow_job_error(
db: &DB,
job: &MiniPulledJob,
mem_peak: i32,
@@ -692,7 +692,7 @@ pub async fn handle_job_error(
#[cfg(feature = "benchmark")] bench: &mut BenchmarkIter,
) {
let err_string = format!("{}: {}", err.name(), err.to_string());
let err_json = error_to_value(err);
let err_json = error_to_value(&err);
let update_job_future = || async {
handle_non_flow_job_error(
@@ -20,13 +20,13 @@ use windmill_queue::{CanceledBy, MiniPulledJob, HTTP_CLIENT};
use serde::{Deserialize, Serialize};
use crate::common::build_args_values;
use crate::common::{
build_http_client, resolve_job_timeout, s3_mode_args_to_worker_data, OccupancyMetrics,
S3ModeWorkerData,
};
use crate::handle_child::run_future_with_polling_update_job_poller;
use crate::sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args;
use crate::common::build_args_values;
use windmill_common::client::AuthedClient;
#[derive(Serialize)]
+36 -6
View File
@@ -99,6 +99,7 @@ use tokio::{
use rand::Rng;
use crate::ai_executor::handle_ai_agent_job;
use crate::{
agent_workers::{queue_init_job, queue_periodic_job},
bash_executor::{handle_bash_job, handle_powershell_job},
@@ -753,7 +754,7 @@ pub async fn handle_all_job_kind_error(
preprocessed_args: None,
job: job.clone(),
result: Arc::new(windmill_common::worker::to_raw_value(&error_to_value(
err,
&err,
))),
result_columns: None,
mem_peak: 0,
@@ -899,7 +900,7 @@ pub fn start_interactive_worker_shell(
})
}
async fn create_job_dir(worker_directory: &str, job_id: impl Display) -> String {
pub async fn create_job_dir(worker_directory: &str, job_id: impl Display) -> String {
let job_dir_path = format!("{}/{}", worker_directory, job_id);
create_directory_async(&job_dir_path).await;
@@ -2190,11 +2191,13 @@ pub struct SendResult {
pub time: Instant,
}
#[derive(Clone)]
pub enum SendResultPayload {
JobCompleted(JobCompleted),
UpdateFlow(UpdateFlow),
}
#[derive(Clone)]
pub struct UpdateFlow {
pub flow: Uuid,
pub w_id: String,
@@ -2341,9 +2344,10 @@ pub async fn handle_queued_job(
| JobKind::FlowDependencies,
x,
) => match x.map(|x| x.0) {
None | Some(PREVIEW_IS_CODEBASE_HASH) | Some(PREVIEW_IS_TAR_CODEBASE_HASH) => {
Some(cache::job::fetch_preview(conn, &job.id, raw_lock, raw_code, raw_flow).await?)
}
None | Some(PREVIEW_IS_CODEBASE_HASH) | Some(PREVIEW_IS_TAR_CODEBASE_HASH) => Some(
cache::job::fetch_preview(conn, &job.id, raw_lock, raw_code, raw_flow.clone())
.await?,
),
_ => None,
},
_ => None,
@@ -2548,6 +2552,31 @@ pub async fn handle_queued_job(
.flatten()
.map(|x| x.to_owned())
.unwrap_or_else(|| serde_json::from_str("{}").unwrap())),
JobKind::AIAgent => match conn {
Connection::Sql(db) => {
handle_ai_agent_job(
conn,
db,
job.as_ref(),
&client,
&mut canceled_by,
&mut mem_peak,
&mut *occupancy_metrics,
&job_completed_tx,
worker_dir,
base_internal_url,
worker_name,
hostname,
killpill_rx,
)
.await
}
Connection::Http(_) => {
return Err(Error::internal_err(
"Agent worker does not support ai agent jobs".to_string(),
));
}
},
_ => {
let metric_timer = Instant::now();
let preview_data = preview_data.and_then(|data| match data {
@@ -2732,6 +2761,7 @@ async fn try_validate_schema(
JobKind::AppDependencies => 12,
JobKind::Noop => 13,
JobKind::FlowNode => 14,
JobKind::AIAgent => 15,
};
let sv = match job.runnable_id {
@@ -3488,7 +3518,7 @@ mount {{
result
}
fn parse_sig_of_lang(
pub fn parse_sig_of_lang(
code: &str,
language: Option<&ScriptLang>,
main_override: Option<String>,
+48 -21
View File
@@ -29,7 +29,7 @@ use sqlx::types::Json;
use sqlx::{FromRow, Postgres, Transaction};
use tracing::instrument;
use uuid::Uuid;
use windmill_common::auth::JobPerms;
use windmill_common::auth::get_job_perms;
#[cfg(feature = "benchmark")]
use windmill_common::bench::BenchmarkIter;
use windmill_common::cache::{self, RawData};
@@ -626,6 +626,8 @@ pub async fn update_flow_status_after_job_completion_internal(
approvers: vec![],
failed_retries: vec![],
skipped: false,
agent_actions: None,
agent_actions_success: None,
}
} else {
success = false;
@@ -636,6 +638,8 @@ pub async fn update_flow_status_after_job_completion_internal(
flow_jobs_success: flow_jobs_success.clone(),
branch_chosen: None,
failed_retries: vec![],
agent_actions: None,
agent_actions_success: None,
}
};
let r = sqlx::query_scalar!(
@@ -806,6 +810,8 @@ pub async fn update_flow_status_after_job_completion_internal(
approvers: vec![],
failed_retries: old_status.retry.failed_jobs.clone(),
skipped: is_skipped,
agent_actions: module_status.agent_actions(),
agent_actions_success: module_status.agent_actions_success(),
}),
)
} else {
@@ -829,6 +835,8 @@ pub async fn update_flow_status_after_job_completion_internal(
flow_jobs_success,
branch_chosen,
failed_retries: old_status.retry.failed_jobs.clone(),
agent_actions: module_status.agent_actions(),
agent_actions_success: module_status.agent_actions_success(),
}),
)
}
@@ -2517,7 +2525,8 @@ async fn push_next_flow_job(
FlowModuleValue::Script { input_transforms, .. }
| FlowModuleValue::RawScript { input_transforms, .. }
| FlowModuleValue::FlowScript { input_transforms, .. }
| FlowModuleValue::Flow { input_transforms, .. },
| FlowModuleValue::Flow { input_transforms, .. }
| FlowModuleValue::AIAgent { input_transforms, .. },
) => {
let ctx = get_transform_context(&flow_job, &previous_id, &status)
.warn_after_seconds(3)
@@ -2605,6 +2614,8 @@ async fn push_next_flow_job(
approvers: vec![],
failed_retries: vec![],
skipped: false,
agent_actions: None,
agent_actions_success: None,
}),
flow_job.id
)
@@ -2825,16 +2836,9 @@ async fn push_next_flow_job(
.flow_innermost_root_job
.or_else(|| Some(flow_job.id))
{
sqlx::query_as!(
JobPerms,
"SELECT email, username, is_admin, is_operator, groups, folders FROM job_perms WHERE job_id = $1 AND workspace_id = $2",
root_job,
flow_job.workspace_id,
)
.fetch_optional(&mut *tx)
.warn_after_seconds(3)
.await?
.map(|x| x.into())
get_job_perms(&mut *tx, root_job, &flow_job.workspace_id)
.await?
.map(|x| x.into())
} else {
None
}
@@ -2886,6 +2890,7 @@ async fn push_next_flow_job(
Some(module.id.clone()),
new_job_priority_override,
job_perms.as_ref(),
false,
)
.warn_after_seconds(2)
.await?;
@@ -3001,6 +3006,8 @@ async fn push_next_flow_job(
parallel: false,
while_loop,
progress: None,
agent_actions: None,
agent_actions_success: None,
}
}
NextStatus::AllFlowJobs { iterator, branchall, .. } => FlowStatusModule::InProgress {
@@ -3014,6 +3021,8 @@ async fn push_next_flow_job(
parallel: true,
while_loop: false,
progress: None,
agent_actions: None,
agent_actions_success: None,
},
NextStatus::NextBranchStep(NextBranch {
mut flow_jobs,
@@ -3037,6 +3046,8 @@ async fn push_next_flow_job(
parallel: false,
while_loop: false,
progress: None,
agent_actions: None,
agent_actions_success: None,
}
}
@@ -3051,6 +3062,8 @@ async fn push_next_flow_job(
parallel: false,
while_loop: false,
progress: None,
agent_actions: None,
agent_actions_success: None,
},
NextStatus::NextStep => {
FlowStatusModule::WaitingForExecutor { id: status_module.id(), job: one_uuid? }
@@ -3253,12 +3266,12 @@ enum NextStatus {
}
#[derive(Clone)]
struct JobPayloadWithTag {
payload: JobPayload,
tag: Option<String>,
delete_after_use: bool,
timeout: Option<i32>,
on_behalf_of: Option<OnBehalfOf>,
pub struct JobPayloadWithTag {
pub payload: JobPayload,
pub tag: Option<String>,
pub delete_after_use: bool,
pub timeout: Option<i32>,
pub on_behalf_of: Option<OnBehalfOf>,
}
enum ContinuePayload {
SingleJob(JobPayloadWithTag),
@@ -3320,7 +3333,7 @@ fn payload_from_modules<'a>(
})
}
fn get_path(flow_job: &MiniPulledJob, status: &FlowStatus, module: &FlowModule) -> String {
pub fn get_path(flow_job: &MiniPulledJob, status: &FlowStatus, module: &FlowModule) -> String {
if status
.preprocessor_module
.as_ref()
@@ -3390,6 +3403,20 @@ async fn compute_next_flow_transform(
NextStatus::NextStep,
))
}
FlowModuleValue::AIAgent { .. } => {
let path = get_path(flow_job, status, module);
let payload = JobPayload::AIAgent { path };
Ok(NextFlowTransform::Continue(
ContinuePayload::SingleJob(JobPayloadWithTag {
payload,
tag: None,
delete_after_use,
timeout: None,
on_behalf_of: None,
}),
NextStatus::NextStep,
))
}
FlowModuleValue::Script { path: script_path, hash: script_hash, tag_override, .. } => {
let payload = script_to_payload(
script_hash,
@@ -4087,7 +4114,7 @@ async fn payload_from_simple_module(
})
}
fn raw_script_to_payload(
pub fn raw_script_to_payload(
path: String,
content: String,
language: windmill_common::scripts::ScriptLang,
@@ -4137,7 +4164,7 @@ async fn flow_to_payload(
Ok(JobPayloadWithTag { payload, tag, delete_after_use, timeout: None, on_behalf_of })
}
async fn script_to_payload(
pub async fn script_to_payload(
script_hash: Option<windmill_common::scripts::ScriptHash>,
script_path: String,
db: &sqlx::Pool<sqlx::Postgres>,
@@ -627,6 +627,7 @@ async fn trigger_dependents_to_recompute_dependencies(
None,
None,
None,
false,
)
.await?;
tracing::info!(
+1 -1
View File
@@ -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.528.0";
export const VERSION = "v1.530.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({
+1 -1
View File
@@ -68,7 +68,7 @@ export {
// }
// });
export const VERSION = "1.528.0";
export const VERSION = "1.530.0";
const command = new Command()
.name("wmill")
+7 -1
View File
@@ -21,7 +21,7 @@ import {
import { inferContentTypeFromFilePath } from "./script_common.ts";
import { GlobalDeps, exts, findGlobalDeps } from "../commands/script/script.ts";
import { FSFSElement, findCodebase, yamlOptions } from "../commands/sync/sync.ts";
import { generateHash, readInlinePathSync } from "./utils.ts";
import { generateHash, readInlinePathSync, getHeaders } from "./utils.ts";
import { SyncCodebase } from "./codebase.ts";
import { FlowFile } from "../commands/flow/flow.ts";
import { replaceInlineScripts } from "../../windmill-utils-internal/src/inline-scripts/replacer.ts";
@@ -384,6 +384,7 @@ async function updateScriptLock(
}
// generate the script lock running a dependency job in Windmill and update it inplace
// TODO: update this once the client is released
const extraHeaders = getHeaders();
const rawResponse = await fetch(
`${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/dependencies`,
{
@@ -391,6 +392,7 @@ async function updateScriptLock(
headers: {
Cookie: `token=${workspace.token}`,
"Content-Type": "application/json",
...extraHeaders,
},
body: JSON.stringify({
raw_scripts: [
@@ -455,6 +457,7 @@ export async function updateFlow(
log.info(colors.blue("Using raw requirements for flow dependencies"));
// generate the script lock running a dependency job in Windmill and update it inplace
const extraHeaders = getHeaders();
rawResponse = await fetch(
`${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/flow_dependencies`,
{
@@ -462,6 +465,7 @@ export async function updateFlow(
headers: {
Cookie: `token=${workspace.token}`,
"Content-Type": "application/json",
...extraHeaders,
},
body: JSON.stringify({
flow_value,
@@ -473,6 +477,7 @@ export async function updateFlow(
);
} else {
// Standard dependency resolution on the server
const extraHeaders = getHeaders();
rawResponse = await fetch(
`${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/flow_dependencies`,
{
@@ -480,6 +485,7 @@ export async function updateFlow(
headers: {
Cookie: `token=${workspace.token}`,
"Content-Type": "application/json",
...extraHeaders,
},
body: JSON.stringify({
flow_value,
-2
View File
@@ -19,8 +19,6 @@ services:
- db_data:/var/lib/postgresql/data
expose:
- 5432
ports:
- 5432:5432
environment:
POSTGRES_PASSWORD: changeme
POSTGRES_DB: windmill
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.528.0",
"version": "1.530.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.528.0",
"version": "1.530.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.528.0",
"version": "1.530.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -0,0 +1,156 @@
<script lang="ts">
import type { GraphModuleState } from './graph'
import {
JobService,
type CompletedJob,
type FlowModule,
type FlowStatusModule,
type Job
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import FlowLogViewerWrapper from './FlowLogViewerWrapper.svelte'
import { z } from 'zod'
import { onMount } from 'svelte'
type AgentActionWithContent = NonNullable<FlowStatusModule['agent_actions']>[number] & {
content: string
}
const resultSchema = z.object({
messages: z.array(
z.object({
role: z.string(),
content: z.string().optional(),
agent_action: z
.union([
z.object({
type: z.literal('tool_call'),
job_id: z.string(),
module_id: z.string(),
function_name: z.string()
}),
z.object({
type: z.literal('message')
})
])
.optional()
})
)
})
interface Props {
tools: FlowModule[]
agentJob: Partial<CompletedJob> & Pick<CompletedJob, 'id'> & { type: 'CompletedJob' }
workspaceId?: string | undefined
storedToolCallJobs?: Record<number, Job>
onToolJobLoaded?: (job: Job, idx: number) => void
}
let { tools, agentJob, workspaceId, onToolJobLoaded, storedToolCallJobs }: Props = $props()
const fakeModuleStates: Record<string, GraphModuleState> = $state({})
async function loadMissingJobs(agentActions: AgentActionWithContent[]) {
const promises = agentActions.map(async (toolCall, idx) => {
if (toolCall.type === 'tool_call') {
let job: Job | undefined = storedToolCallJobs?.[idx]
if (!job || job.type !== 'CompletedJob') {
job = await JobService.getJob({
id: toolCall.job_id,
workspace: workspaceId ?? $workspaceStore!
})
}
fakeModuleStates[idx.toString()] = {
args: job.args,
type: job['success'] ? 'Success' : 'Failure',
logs: job.logs,
result: job['result'],
job_id: toolCall.job_id
}
onToolJobLoaded?.(job, idx)
} else {
fakeModuleStates[idx.toString()] = {
type: 'Success',
args: {},
logs: '',
result: toolCall.content
}
}
})
await Promise.all(promises)
}
let job: Partial<Job> | undefined = $state(undefined)
async function loadToolCalls() {
let parsedResult = resultSchema.safeParse(agentJob.result)
if (!parsedResult.success) {
console.error('Invalid result', parsedResult.error)
return
}
let agentActions = parsedResult.data.messages
.map(
(m) =>
(m.agent_action?.type === 'message'
? {
type: 'message',
content: m.content
}
: m.agent_action?.type === 'tool_call'
? {
type: 'tool_call',
job_id: m.agent_action.job_id,
module_id: m.agent_action.module_id,
function_name: m.agent_action.function_name
}
: undefined) as AgentActionWithContent | undefined
)
.filter((m) => m !== undefined)
await loadMissingJobs(agentActions)
job = {
...agentJob,
raw_flow: {
modules: agentActions
.map((toolCall, idx) => {
if (toolCall.type === 'message') {
return {
id: idx.toString(),
value: {
type: 'identity' as const
}
}
} else {
const module = tools.find((m) => m.summary === toolCall.function_name)
return module
? {
...module,
id: idx.toString()
}
: undefined
}
})
.filter((m) => m !== undefined)
}
}
}
onMount(() => {
loadToolCalls()
})
</script>
{#if job}
<div class="p-2">
<FlowLogViewerWrapper
{job}
localModuleStates={fakeModuleStates}
{workspaceId}
render={true}
onSelectedIteration={async () => {}}
mode="aiagent"
/>
</div>
{/if}
+8 -2
View File
@@ -100,7 +100,8 @@
codeCompletionSessionEnabled,
lspTokenStore,
formatOnSave,
vimMode
vimMode,
relativeLineNumbers
} from '$lib/stores'
import { editorConfig, updateOptions } from '$lib/editorUtils'
@@ -1234,7 +1235,7 @@
try {
editor = meditor.create(divEl as HTMLDivElement, {
...editorConfig(code ?? '', lang, automaticLayout, fixedOverflowWidgets),
...editorConfig(code ?? '', lang, automaticLayout, fixedOverflowWidgets, $relativeLineNumbers),
model,
fontSize: !small ? 14 : 12,
lineNumbersMinChars,
@@ -1652,6 +1653,11 @@
$effect(() => {
files && model && untrack(() => onFileChanges())
})
$effect(() => {
editor?.updateOptions({
lineNumbers: $relativeLineNumbers ? 'relative' : 'on'
})
})
</script>
<svelte:window onkeydown={onKeyDown} />
@@ -2,6 +2,7 @@
import { Settings } from 'lucide-svelte'
import FormatOnSave from './FormatOnSave.svelte'
import VimMode from './VimMode.svelte'
import RelativeLineNumbers from './RelativeLineNumbers.svelte'
import { Button } from './common'
import CodeCompletionStatus from './copilot/CodeCompletionStatus.svelte'
import type { EditorBarUi } from './custom_ui'
@@ -39,6 +40,11 @@
<VimMode />
</div>
{/if}
{#if customUi?.relativeLineNumbers != false}
<div>
<RelativeLineNumbers />
</div>
{/if}
{#if customUi?.aiCompletion != false}
<div>
<CodeCompletionStatus />
@@ -28,7 +28,7 @@
}
}
$effect(() => {
flowStore.val && flowStateStore && untrack(() => loadSchema())
flowStore.val && flowStateStore.val && untrack(() => loadSchema())
})
function handleClick() {
@@ -1157,7 +1157,7 @@
</div>
</div>
<!-- metadata -->
{#if flowStateStore}
{#if flowStateStore.val}
<FlowEditor
bind:this={flowEditor}
{disabledFlowInputs}
@@ -137,6 +137,8 @@
Inline {stepDetail.value.language} script
{:else if stepDetail.value.type == 'script'}
Workspace script
{:else if stepDetail.value.type == 'aiagent'}
AI Agent
{/if}
</span>
</div>
@@ -217,6 +219,11 @@
{:else}
<FlowModuleScript path={stepDetail.value.path} />
{/if}
{:else if stepDetail.value.type == 'aiagent'}
<div class="text-2xs">
<h3 class="mb-2 font-semibold mt-2">Step Inputs</h3>
<InputTransformsViewer inputTransforms={stepDetail?.value?.input_transforms ?? {}} />
</div>
{:else if stepDetail.value.type == 'forloopflow'}
<div>
<p class="font-medium text-secondary pb-2"> Iterator expression: </p>
@@ -2,6 +2,8 @@
import { Loader2 } from 'lucide-svelte'
import DisplayResult from './DisplayResult.svelte'
import LogViewer from './LogViewer.svelte'
import type { CompletedJob, FlowModule, Job } from '$lib/gen'
import AiAgentLogViewer from './AIAgentLogViewer.svelte'
interface Props {
waitingForExecutor?: boolean
@@ -18,6 +20,12 @@
refreshLog?: boolean
downloadLogs?: boolean
tagLabel?: string | undefined
aiAgentStatus?: {
tools: FlowModule[]
agentJob: Partial<CompletedJob> & Pick<CompletedJob, 'id'> & { type: 'CompletedJob' }
storedToolCallJobs?: Record<number, Job>
onToolJobLoaded?: (job: Job, idx: number) => void
}
}
let {
@@ -33,7 +41,8 @@
tag = undefined,
workspaceId = undefined,
downloadLogs = true,
tagLabel = undefined
tagLabel = undefined,
aiAgentStatus = undefined
}: Props = $props()
</script>
@@ -54,13 +63,17 @@
{/if}
</div>
<div class="overflow-auto {col ? '' : 'max-h-80'} relative">
<LogViewer
{tagLabel}
download={downloadLogs}
content={logs ?? ''}
{jobId}
isLoading={waitingForExecutor}
{tag}
/>
{#if aiAgentStatus}
<AiAgentLogViewer {...aiAgentStatus} {workspaceId} />
{:else}
<LogViewer
{tagLabel}
download={downloadLogs}
content={logs ?? ''}
{jobId}
isLoading={waitingForExecutor}
{tag}
/>
{/if}
</div>
</div>
@@ -45,6 +45,7 @@
) => Promise<void>
getSelectedIteration: (stepId: string) => number
flowSummary?: string
mode?: 'flow' | 'aiagent'
}
let {
@@ -63,7 +64,8 @@
flowId = 'root',
onSelectedIteration,
getSelectedIteration,
flowSummary
flowSummary,
mode = 'flow'
}: Props = $props()
function getJobLink(jobId: string | undefined): string {
@@ -96,16 +98,18 @@
function getStepProgress(job: RootJobData, totalSteps: number): string {
if (totalSteps === 0) return ''
const stepWord = mode === 'aiagent' ? 'action' : 'step'
// If flow is completed, show total steps
if (job.type === 'CompletedJob') {
return ` (${totalSteps} step${totalSteps === 1 ? '' : 's'})`
return ` (${totalSteps} ${stepWord}${totalSteps === 1 ? '' : 's'})`
}
// If flow is running, use flow_status.step if available (like JobStatus.svelte)
if (job.type === 'QueuedJob') {
if (job.flow_status?.step !== undefined) {
const currentStep = (job.flow_status.step ?? 0) + 1
return ` (step ${currentStep} of ${totalSteps})`
return ` (${stepWord} ${currentStep} of ${totalSteps})`
}
return ''
@@ -317,7 +321,7 @@
<div class="flex items-center gap-2">
<span class="text-xs font-mono">
{level == 0 ? 'Flow' : 'Subflow'}
{mode === 'aiagent' ? 'AI Agent' : level == 0 ? 'Flow' : 'Subflow'}
{#if flowInfo.label}
: {flowInfo.label}
{/if}
@@ -450,20 +454,26 @@
<div class="flex items-center gap-2">
<span class="text-xs font-mono">
<b>
{module.id}
{mode === 'aiagent'
? module.summary
? 'Tool call'
: 'Message'
: module.id}
</b>
{#if module.value.type === 'forloopflow'}
For loop
{:else if module.value.type === 'whileloopflow'}
While loop
{:else if module.value.type === 'branchall'}
Branch to all
{:else if module.value.type === 'branchone'}
Branch to one
{:else if module.value.type === 'flow'}
Subflow
{:else}
Step
{#if mode === 'flow'}
{#if module.value.type === 'forloopflow'}
For loop
{:else if module.value.type === 'whileloopflow'}
While loop
{:else if module.value.type === 'branchall'}
Branch to all
{:else if module.value.type === 'branchone'}
Branch to one
{:else if module.value.type === 'flow'}
Subflow
{:else}
Step
{/if}
{/if}
{#if module.summary}
: {module.summary}
@@ -507,14 +517,16 @@
{#if isLeafStep}
{@const jobId = localModuleStates[module.id]?.job_id}
<a
href={getJobLink(jobId ?? '')}
class="text-xs text-primary hover:underline font-mono"
target="_blank"
rel="noopener noreferrer"
>
{truncateRev(jobId ?? '', 6)}
</a>
{#if jobId}
<a
href={getJobLink(jobId ?? '')}
class="text-xs text-primary hover:underline font-mono"
target="_blank"
rel="noopener noreferrer"
>
{truncateRev(jobId ?? '', 6)}
</a>
{/if}
{/if}
</div>
@@ -7,7 +7,7 @@
import { readFieldsRecursively } from '$lib/utils'
interface Props {
job: Job
job: Partial<Job>
localModuleStates: Record<string, GraphModuleState>
workspaceId: string | undefined
render: boolean
@@ -16,9 +16,17 @@
| { id: string; index: number; manuallySet: true; moduleId: string }
| { manuallySet: false; moduleId: string }
) => Promise<void>
mode?: 'flow' | 'aiagent'
}
let { job, localModuleStates, workspaceId, render, onSelectedIteration }: Props = $props()
let {
job,
localModuleStates,
workspaceId,
render,
onSelectedIteration,
mode = 'flow'
}: Props = $props()
// State for tracking expanded rows - using Record to allow explicit control
let expandedRows: Record<string, boolean> = $state({})
@@ -68,5 +76,6 @@
{getSelectedIteration}
flowId="root"
flowStatus={undefined}
{mode}
/>
</div>
@@ -44,6 +44,11 @@
import { createState } from '$lib/svelte5Utils.svelte'
import JobLoader from './JobLoader.svelte'
import { writable } from 'svelte/store'
import {
AI_TOOL_CALL_PREFIX,
AI_TOOL_MESSAGE_PREFIX,
getToolCallId
} from './graph/renderers/nodes/AIToolNode.svelte'
let {
flowStateStore,
@@ -507,6 +512,29 @@
iteration_total: mod.iterator?.itered?.length ?? mod.flow_jobs?.length
})
}
if (mod.agent_actions && mod.id) {
setModuleState(mod.id, {
agent_actions: mod.agent_actions
})
mod.agent_actions.forEach((action, idx) => {
if (mod.id) {
if (action.type == 'tool_call') {
const toolCallId = getToolCallId(idx, mod.id, action.module_id)
const success = mod.agent_actions_success?.[idx]
setModuleState(toolCallId, {
job_id: action.job_id,
type: success != undefined ? (success ? 'Success' : 'Failure') : 'InProgress'
})
} else if (action.type == 'message') {
const toolCallId = getToolCallId(idx, mod.id)
setModuleState(toolCallId, {
type: 'Success'
})
}
}
})
}
})
}
}
@@ -699,7 +727,8 @@
flow_jobs_success: mod.flow_jobs_success,
iteration_total: mod.iterator?.itered?.length,
retries: mod?.failed_retries?.length,
skipped: mod.skipped
skipped: mod.skipped,
agent_actions: mod.agent_actions
// retries: flowStateStore?.raw_flow
},
force
@@ -883,6 +912,11 @@
let stepDetail: FlowModule | string | undefined = $state(undefined)
let storedListJobs: Record<number, Job> = $state({})
let storedToolCallJobs: Record<number, Job> = $state({})
let selectedToolCall: number | undefined = $state(undefined)
let toolCallIndicesToLoad: number[] = $state([])
let wrapperHeight: number = $state(0)
function removeFailureNode(id: string, parent_module: any) {
@@ -903,7 +937,7 @@
modules: FlowModule[],
expandedSubflows: Record<string, FlowModule[]>
): string[] {
const ids = dfs(modules, (x) => x.id)
const ids = dfs(modules, (x) => x.id, { skipToolNodes: true })
function rec(ids: string[], prefix: string | undefined): string[] {
return ids.concat(
@@ -1333,6 +1367,75 @@
isNodeSelected={localModuleStates?.[selectedNode ?? '']?.job_id == mod.job}
{globalIterationBounds}
/>
{#if mod.agent_actions && mod.agent_actions.length > 0}
{#each mod.agent_actions as agentAction, j}
{#if agentAction.type === 'tool_call' && mod.id}
{@const toolCallId = getToolCallId(j, mod.id, agentAction.module_id)}
{@const isSelected = selectedToolCall === j}
<Button
variant={isSelected ? 'contained' : 'border'}
color={mod.agent_actions_success?.[j] === false
? 'red'
: isSelected
? 'dark'
: 'light'}
btnClasses="w-full flex justify-start"
on:click={async () => {
if (selectedToolCall == j) {
selectedToolCall = undefined
} else {
selectedToolCall = j
}
}}
endIcon={{
icon: ChevronDown,
classes: isSelected ? '!rotate-180' : ''
}}
>
<span class="truncate font-mono">
Tool call: {agentAction.function_name}
</span>
</Button>
{#if isSelected || storedToolCallJobs[j] || toolCallIndicesToLoad.includes(j)}
<FlowStatusViewerInner
topModuleStates={getTopModuleStates}
{refreshGlobal}
updateRecursiveRefreshFn={updateRecursiveRefreshInner}
globalModuleStates={[localModuleStates, ...globalModuleStates]}
globalDurationStatuses={[
localDurationStatuses,
...globalDurationStatuses
]}
render={selected == 'sequence' && render && isSelected}
{workspaceId}
{prefix}
{updateGlobalRefresh}
{subflowParentsGlobalModuleStates}
{subflowParentsDurationStatuses}
{isSelectedBranch}
jobId={agentAction.job_id}
job={storedToolCallJobs[j]}
initialJob={storedToolCallJobs[j]}
{reducedPolling}
onJobsLoaded={({ job, force }) => {
storedToolCallJobs[j] = job
onJobsLoadedInner({ id: toolCallId } as FlowStatusModule, job, force)
}}
loadExtraLogs={(logs) => {
setModuleState(toolCallId, {
logs
})
}}
{onResultStreamUpdate}
graphTabOpen={selected == 'graph' && graphTabOpen}
isNodeSelected={localModuleStates?.[toolCallId]?.job_id ==
agentAction.job_id}
{globalIterationBounds}
/>
{/if}
{/if}
{/each}
{/if}
{/if}
{:else}
<ModuleStatus
@@ -1378,7 +1481,6 @@
{/if}
{/each}
</div>
<FlowGraphV2
{selectedId}
triggerNode={true}
@@ -1400,9 +1502,19 @@
selectedNode = 'end'
stepDetail = 'end'
} else {
const mod = dfs(job?.raw_flow?.modules ?? [], (m) => m).find((m) => m?.id === e)
const id = e.startsWith(AI_TOOL_CALL_PREFIX) ? e.split('-').pop() : e
const mod = dfs(job?.raw_flow?.modules ?? [], (m) => m).find(
(m) => m?.id === id
)
stepDetail = mod
selectedNode = e
if (e.startsWith(AI_TOOL_CALL_PREFIX)) {
const [_prefix, _agentModuleId, j, _toolModuleId] = e.split('-')
const jIdx = Number(j)
if (!toolCallIndicesToLoad.includes(jIdx)) {
toolCallIndicesToLoad.push(jIdx)
}
}
}
} else {
stepDetail = e
@@ -1454,9 +1566,12 @@
/>
{:else if rightColumnSelect == 'node_status'}
<div class="pt-2 grow flex flex-col">
{#if selectedNode}
{#if selectedNode?.startsWith(AI_TOOL_MESSAGE_PREFIX)}
<div class="pt-2 px-4 pb-4">
<Alert type="info" title="Message output is available on the AI agent node" />
</div>
{:else if selectedNode}
{@const node = localModuleStates[selectedNode]}
{#if selectedNode == 'end'}
<FlowJobResult
tagLabel={customUi?.tagLabel}
@@ -1484,6 +1599,10 @@
<p class="p-2 text-secondary">No arguments</p>
{/if}
{:else if node}
{@const module =
stepDetail && typeof stepDetail !== 'string' ? stepDetail : undefined}
{@const agentTools =
module && module.value.type === 'aiagent' ? module.value.tools : undefined}
{#if node.flow_jobs_results}
<span class="pl-1 text-tertiary"
>Result of step as collection of all subflows</span
@@ -1547,6 +1666,25 @@
tag={node.tag}
logs={node.logs}
downloadLogs={!hideDownloadLogs}
aiAgentStatus={agentTools &&
node.job_id &&
(node.type === 'Success' || node.type === 'Failure')
? {
tools: agentTools,
agentJob: {
id: node.job_id,
result: node.result,
logs: node.logs,
args: node.args,
success: node.type === 'Success',
type: 'CompletedJob'
},
storedToolCallJobs,
onToolJobLoaded: (job, idx) => {
storedToolCallJobs[idx] = job
}
}
: undefined}
/>
{:else}
<p class="p-2 text-tertiary italic"
+31 -3
View File
@@ -11,7 +11,8 @@
type FlowStatus,
type Preview,
type GetJobUpdatesResponse,
type WorkflowStatus
type WorkflowStatus,
type OpenFlow
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { onDestroy, tick, untrack } from 'svelte'
@@ -44,6 +45,7 @@
jobUpdateLastFetch?: Date | undefined
toastError?: boolean
onlyResult?: boolean
loadPlaceholderJobOnStart?: Job
// If you want to find out progress of subjobs of a flow, check job.flow_status.progress
scriptProgress?: number | undefined
@@ -60,6 +62,7 @@
jobUpdateLastFetch = $bindable(undefined),
toastError = false,
onlyResult = false,
loadPlaceholderJobOnStart = undefined,
scriptProgress = $bindable(undefined),
noLogs = false,
children
@@ -226,6 +229,25 @@
)
}
export async function runFlowPreview(
args: Record<string, any>,
flow: OpenFlow & { tag?: string },
callbacks?: Callbacks
): Promise<string> {
return abstractRun(
() =>
JobService.runFlowPreview({
workspace: $workspaceStore!,
requestBody: {
args,
value: flow.value,
tag: flow.tag
}
}),
callbacks
)
}
function refreshLogOffset() {
if (logOffset == 0) {
logOffset = job?.logs?.length ? job.logs?.length + 1 : 0
@@ -326,7 +348,11 @@
syncIteration = 0
errorIteration = 0
currentId = testId
job = undefined
if (loadPlaceholderJobOnStart) {
job = structuredClone(loadPlaceholderJobOnStart)
} else {
job = undefined
}
startedWatchingJob = Date.now()
// Clean up any existing SSE connection
@@ -564,13 +590,15 @@
if (isCurrentJob(id)) {
try {
// First load the job to get initial state
if (!job && !onlyResult) {
if ((!job || job.id == '') && !onlyResult) {
job = await JobService.getJob({
workspace: workspace!,
id,
noLogs: noLogs,
noCode
})
callbacks?.change?.(job)
}
if (!onlyResult) {
@@ -0,0 +1,40 @@
<script lang="ts">
import { FoldVertical, UnfoldVertical } from 'lucide-svelte'
interface Props {
showResultsInputs: boolean | undefined
toggleExpandAll: () => void
allExpanded: boolean | undefined
}
let { showResultsInputs = $bindable(), toggleExpandAll, allExpanded }: Props = $props()
</script>
<div class="flex justify-end gap-4 items-center p-2 bg-surface-secondary border-b">
<div class="flex items-center gap-2 whitespace-nowrap">
<label
for="showResultsInputs"
class="text-xs text-tertiary hover:text-primary transition-colors">Show inputs/results</label
>
<div class="flex-shrink-0">
<input
type="checkbox"
name="showResultsInputs"
id="showResultsInputs"
bind:checked={showResultsInputs}
class="w-3 h-4 accent-primary -my-1"
/>
</div>
</div>
<button
onclick={toggleExpandAll}
class="text-xs text-tertiary hover:text-primary transition-colors flex items-center gap-2 min-w-24 justify-end"
>
{allExpanded ? 'Collapse All' : 'Expand All'}
{#if allExpanded}
<FoldVertical size={16} />
{:else}
<UnfoldVertical size={16} />
{/if}
</button>
</div>
@@ -1,7 +1,7 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import { allTrue } from '$lib/utils'
import { allTrue, sendUserToast } from '$lib/utils'
import { RefreshCw } from 'lucide-svelte'
import ArgInput from './ArgInput.svelte'
import { Button } from './common'
@@ -98,11 +98,11 @@
loadResourceTypes()
let args = $state(<Record<string, any>>{})
onMount(() => {
testSteps?.updateStepArgs(mod.id, flowStateStore, flowStore?.val, previewArgs?.val)
args = testSteps?.getStepArgs(mod.id) ?? { value: {} }
if (!testSteps) {
sendUserToast('testSteps module not initialized. Preview will not work.', true)
}
testSteps?.updateStepArgs(mod.id, flowStateStore.val, flowStore?.val, previewArgs?.val)
})
</script>
@@ -117,14 +117,17 @@
)}
data-arg={argName}
>
{#if typeof args.value == 'object' && schema?.properties?.[argName]}
{#if schema?.properties?.[argName]}
<ArgInput
{resourceTypes}
minW={false}
autofocus={autofocus && !focusArg && i == 0}
label={argName}
description={schema.properties[argName].description}
bind:value={args.value[argName]}
bind:value={
() => testSteps?.getStepInputArgs(mod.id, argName),
(v) => testSteps?.setStepInputArgs(mod.id, argName, v)
}
type={schema.properties[argName].type}
oneOf={schema.properties[argName].oneOf}
required={schema?.required?.includes(argName)}
@@ -4,12 +4,13 @@
import ScriptFix from './copilot/ScriptFix.svelte'
import type DiffEditor from './DiffEditor.svelte'
import type Editor from './Editor.svelte'
import type { Script, Job, FlowModule } from '$lib/gen'
import { type Script, type Job, type FlowModule } from '$lib/gen'
import OutputPickerInner from '$lib/components/flows/propPicker/OutputPickerInner.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import type { FlowEditorContext } from './flows/types'
import { getContext } from 'svelte'
import { getStringError } from './copilot/chat/utils'
import AiAgentLogViewer from './AIAgentLogViewer.svelte'
interface Props {
lang: Script['language']
@@ -51,6 +52,12 @@
let preview: 'mock' | 'job' | undefined = $state(undefined)
let jobProgressReset: () => void = $state(() => {})
$effect(() => {
if (preview != undefined && testJob) {
preview = undefined
}
})
let forceJson = $state(false)
const logJob = $derived(testJob ?? selectedJob)
@@ -102,6 +109,15 @@
customEmptyMessage="Using pinned data"
{tagLabel}
/>
{:else if mod.value.type === 'aiagent' && logJob?.type === 'CompletedJob'}
<AiAgentLogViewer
tools={mod.value.tools}
agentJob={{
...logJob,
type: 'CompletedJob'
}}
workspaceId={logJob.workspace_id}
/>
{:else}
<LogViewer
small
+51 -4
View File
@@ -1,11 +1,12 @@
<script lang="ts">
import { ScriptService, type FlowModule, type Job } from '$lib/gen'
import { ScriptService, type FlowModule, type JavascriptTransform, type Job } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { getScriptByPath } from '$lib/scripts'
import { getContext } from 'svelte'
import type { FlowEditorContext } from './flows/types'
import JobLoader, { type Callbacks } from './JobLoader.svelte'
import { getStepHistoryLoaderContext } from './stepHistoryLoader.svelte'
import { loadSchemaFromModule } from './flows/flowInfers'
interface Props {
mod: FlowModule
@@ -31,12 +32,12 @@
let stepHistoryLoader = getStepHistoryLoaderContext()
export function runTestWithStepArgs() {
runTest(testSteps.getStepArgs(mod.id)?.value)
runTest(testSteps.getStepArgs(mod.id))
}
export function loadArgsAndRunTest() {
testSteps?.updateStepArgs(mod.id, flowStateStore, flowStore?.val, previewArgs?.val)
runTest(testSteps.getStepArgs(mod.id)?.value)
testSteps?.updateStepArgs(mod.id, flowStateStore.val, flowStore?.val, previewArgs?.val)
runTest(testSteps.getStepArgs(mod.id))
}
export async function runTest(args: any) {
@@ -84,6 +85,39 @@
)
} else if (val.type == 'flow') {
await jobLoader?.runFlowByPath(val.path, args, callbacks)
} else if (val.type == 'aiagent') {
const { schema } = await loadSchemaFromModule(mod)
const inputTransforms: { [key: string]: JavascriptTransform } = Object.fromEntries(
Object.keys(args).map((key) => [
key,
{
expr: `flow_input.${key}`,
type: 'javascript'
}
])
)
await jobLoader?.runFlowPreview(
args,
{
value: {
modules: [
{
...mod,
value: {
type: 'aiagent',
tools: mod.value.type == 'aiagent' ? mod.value.tools : [],
input_transforms: inputTransforms
}
}
]
},
summary: '',
schema
},
callbacks
)
} else {
throw Error('Not supported module type')
}
@@ -145,4 +179,17 @@
}
}
}
bind:job={modulesTestStates.states[mod.id].testJob}
loadPlaceholderJobOnStart={{
type: 'QueuedJob',
id: '',
running: false,
canceled: false,
job_kind: 'preview',
permissioned_as: '',
is_flow_step: false,
email: '',
visible_to_owner: true,
tag: ''
}}
/>
@@ -0,0 +1,18 @@
<script lang="ts">
import { RELATIVE_LINE_NUMBERS_SETTING_NAME, relativeLineNumbers } from '$lib/stores'
import { storeLocalSetting } from '$lib/utils'
import Toggle from './Toggle.svelte'
function storeSetting() {
storeLocalSetting(RELATIVE_LINE_NUMBERS_SETTING_NAME, $relativeLineNumbers.toString())
}
</script>
<Toggle
size="xs"
bind:checked={$relativeLineNumbers}
on:change={() => {
storeSetting()
}}
options={{ right: 'relative line numbers' }}
/>
@@ -1707,16 +1707,18 @@
{/if}
<div class="flex flex-row gap-x-1 lg:gap-x-2">
{#if $workerTags}
{#if $workerTags?.length ?? 0 > 0}
<div class="max-w-[200px] pr-8">
<WorkerTagSelect
inputClass="text-sm text-secondary !placeholder-secondary"
nullTag={script.language}
placeholder={customUi?.tagSelectPlaceholder}
bind:tag={script.tag}
/>
</div>
{#if customUi?.topBar?.tagEdit != false}
{#if $workerTags}
{#if $workerTags?.length ?? 0 > 0}
<div class="max-w-[200px] pr-8">
<WorkerTagSelect
inputClass="text-sm text-secondary !placeholder-secondary"
nullTag={script.language}
placeholder={customUi?.tagSelectPlaceholder}
bind:tag={script.tag}
/>
</div>
{/if}
{/if}
{/if}
{#if customUi?.topBar?.settings != false}
@@ -78,7 +78,7 @@
import domContent from '$lib/dom.d.ts.txt?raw'
import { initializeVscode, keepModelAroundToAvoidDisposalOfWorkers } from './vscode'
import EditorTheme from './EditorTheme.svelte'
import { vimMode } from '$lib/stores'
import { vimMode, relativeLineNumbers } from '$lib/stores'
import { initVim } from './monaco_keybindings'
import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte'
import { editorPositionMap } from '$lib/utils'
@@ -266,6 +266,11 @@
untrack(() => onVimDisable())
}
})
$effect(() => {
editor?.updateOptions({
lineNumbers: $relativeLineNumbers ? 'relative' : 'on'
})
})
function onVimDisable() {
vimDisposable?.dispose()
@@ -374,7 +379,7 @@
try {
console.log('fixedOverflowWidgets', fixedOverflowWidgets)
editor = meditor.create(divEl as HTMLDivElement, {
...editorConfig(code ?? '', lang, automaticLayout, fixedOverflowWidgets),
...editorConfig(code ?? '', lang, automaticLayout, fixedOverflowWidgets, $relativeLineNumbers),
model,
lineDecorationsWidth: 6,
lineNumbersMinChars: 2,
@@ -456,7 +456,7 @@
try {
editor = meditor.create(divEl as HTMLDivElement, {
...editorConfig(code, lang, automaticLayout, fixedOverflowWidgets),
...editorConfig(code, lang, automaticLayout, fixedOverflowWidgets, false),
model,
// overflowWidgetsDomNode: widgets,
// lineNumbers: 'on',
@@ -160,7 +160,7 @@
subGridId={`${id}-0`}
containerHeight={componentContainerHeight -
(30 * accordionInput?.value.length + 40)}
on:focus={() => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
@@ -246,7 +246,7 @@
style={css?.container?.style}
subGridId={`${id}-0`}
containerHeight={componentContainerHeight - 40}
on:focus={() => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
@@ -197,14 +197,14 @@
let idx = Number(event.node.data['__index'].split('-')[0])
uid = prevUid ?? ''
outputs?.newChange?.set({
row: event.node.rowIndex,
row: idx,
column: event.colDef.field,
value: dataCell,
old: result[idx][event.colDef.field]
})
result[idx][event.colDef.field] = dataCell
let data = { ...result[event.node.rowIndex] }
let data = { ...result[idx] }
outputs?.selectedRow?.set(data)
resolvedConfig?.extraConfig?.['defaultColDef']?.['onCellValueChanged']?.(event)
}
@@ -126,7 +126,7 @@
style={css?.container?.style}
subGridId={`${id}-${i}`}
containerHeight={componentContainerHeight}
on:focus={(e) => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
@@ -97,7 +97,7 @@
style={css?.container?.style}
subGridId={`${id}-0`}
containerHeight={componentContainerHeight}
on:focus={() => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
@@ -236,7 +236,7 @@
style={css?.container?.style}
subGridId={`${id}-${i}`}
containerHeight={componentContainerHeight - 40}
on:focus={() => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
@@ -182,7 +182,7 @@
{id}
{containerHeight}
subGridId={`${id}-0`}
on:focus={() => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
$focusedGrid = {
@@ -225,7 +225,7 @@
{id}
subGridId={`${id}-0`}
containerHeight={resolvedConfig.heightPx}
on:focus={() => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
@@ -243,7 +243,7 @@
{id}
{containerHeight}
subGridId={`${id}-0`}
on:focus={() => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
$focusedGrid = {
@@ -36,7 +36,9 @@
getContext<AppViewerContext>('AppViewerContext')
//used so that we can count number of outputs setup for first refresh
initOutput($worldStore, id, {})
initOutput($worldStore, id, {
selectedPaneIndex: 0
})
let everRender = $state(render)
@@ -129,13 +131,14 @@
style={css?.container?.style}
subGridId={`${id}-${index}`}
containerHeight={horizontal ? undefined : componentContainerHeight - 8}
on:focus={() => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: index
}
$worldStore.outputsById[id].selectedPaneIndex.set(index)
}
}}
/>
@@ -220,7 +220,7 @@
class={twMerge(css?.container?.class, 'wm-stepper')}
style={css?.container?.style}
containerHeight={componentContainerHeight - tabHeight - footerHeight}
on:focus={() => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
handleTabSelection()
@@ -265,7 +265,7 @@
class={twMerge(css?.container?.class, 'wm-tabs-container')}
style={css?.container?.style}
containerHeight={componentContainerHeight - (titleBarHeight * tabs.length + 40)}
on:focus={() => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
handleTabSelection()
@@ -289,7 +289,7 @@
containerHeight={resolvedConfig.tabsKind !== 'sidebar' && $mode !== 'preview'
? componentContainerHeight - tabHeight
: componentContainerHeight}
on:focus={() => {
onFocus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
handleTabSelection()
@@ -81,8 +81,7 @@
export function moveComponentBetweenSubgrids(
componentId: string,
parentComponentId: string,
subGridIndex: number,
subgrid: { parentComponentId: string; subGridIndex: number } | undefined,
position?: { x: number; y: number }
) {
// Find the component in the source subgrid
@@ -106,7 +105,7 @@
insertNewGridItem(
$app,
(id) => ({ ...gridItem.data, id }),
{ parentComponentId: parentComponentId, subGridIndex: subGridIndex },
subgrid,
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
component.id,
position,
@@ -119,10 +118,12 @@
// Update the app state
$app = $app
$selectedComponent = [parentComponentId]
$focusedGrid = {
parentComponentId,
subGridIndex
if (subgrid) {
$selectedComponent = [subgrid.parentComponentId]
$focusedGrid = subgrid
} else {
$selectedComponent = [componentId]
$focusedGrid = undefined
}
}
</script>
@@ -200,32 +201,34 @@
allIdsInPath={$allIdsInPath}
selectedIds={$selectedComponent}
items={$app.grid}
on:redraw={(e) => {
onRedraw={(grid) => {
push(history, $app)
$app.grid = e.detail
$app.grid = grid
}}
root
on:dropped={(e) => {
const { id, overlapped, x, y } = e.detail
const overlappedComponent = findGridItem($app, overlapped)
onDropped={({ id, overlapped, x, y }) => {
const overlappedComponent = overlapped ? findGridItem($app, overlapped) : undefined
if (overlappedComponent && !isContainer(overlappedComponent.data.type)) {
return
}
if (!overlapped) {
return
}
if (id === overlapped) {
return
}
moveComponentBetweenSubgrids(
id,
overlapped,
subGridIndexKey(overlappedComponent?.data?.type, overlapped, $worldStore),
overlapped
? {
parentComponentId: overlapped,
subGridIndex: subGridIndexKey(
overlappedComponent?.data?.type,
overlapped,
$worldStore
)
}
: undefined,
{ x, y }
)
}}
@@ -3,7 +3,7 @@
import { push } from '$lib/history.svelte'
import { classNames } from '$lib/utils'
import { createEventDispatcher, getContext, onDestroy } from 'svelte'
import { getContext, onDestroy } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { gridColumns, isFixed, toggleFixed } from '../gridUtils'
import Grid from '../svelte-grid/Grid.svelte'
@@ -34,6 +34,7 @@
visible?: boolean
id: string
shouldHighlight?: boolean
onFocus?: () => void
}
let {
@@ -46,11 +47,10 @@
subGridId,
visible = true,
id,
shouldHighlight = true
shouldHighlight = true,
onFocus
}: Props = $props()
const dispatch = createEventDispatcher()
const {
app,
connectingInput,
@@ -80,7 +80,7 @@
let highlight = $derived(id === $focusedGrid?.parentComponentId && shouldHighlight)
const onpointerdown = (e) => {
dispatch('focus')
onFocus?.()
}
function selectComponent(e: PointerEvent, id: string) {
@@ -218,7 +218,7 @@
{#if $mode !== 'preview'}
<div
class={highlight
? `outline !outline-dashed outline-2 min-h-full ${
? `!outline-dashed outline-2 min-h-full ${
isActive && !$selectedComponent?.includes(id)
? 'outline-orange-600'
: 'outline-gray-400 dark:outline-gray-600'
@@ -229,19 +229,17 @@
<Grid
allIdsInPath={$allIdsInPath}
items={$app.subgrids?.[subGridId] ?? []}
on:redraw={(e) => {
onRedraw={(grid) => {
push(editorContext?.history, $app)
if ($app.subgrids) {
$app.subgrids[subGridId] = e.detail
$app.subgrids[subGridId] = grid
}
}}
selectedIds={$selectedComponent}
scroller={container}
parentWidth={$parentWidth - 17}
{containerWidth}
on:dropped={(e) => {
const { id, overlapped, x, y } = e.detail
onDropped={({ id, overlapped, x, y }) => {
if (!overlapped) {
moveToRoot(id, { x, y })
} else {
@@ -257,7 +255,6 @@
if (id === overlapped) {
return
}
moveComponentBetweenSubgrids(
id,
overlapped,
@@ -39,14 +39,14 @@ import gridHelp from '../svelte-grid/utils/helper'
type GridItemLocation =
| {
type: 'grid'
gridItemIndex: number
}
type: 'grid'
gridItemIndex: number
}
| {
type: 'subgrid'
subgridItemIndex: number
subgridKey: string
}
type: 'subgrid'
subgridItemIndex: number
subgridKey: string
}
interface GridItemWithLocation {
location: GridItemLocation
item: GridItem
@@ -187,7 +187,7 @@ export function selectId(
selectedComponent: Writable<string[] | undefined>,
app: App
) {
;(document?.activeElement as HTMLElement)?.blur()
; (document?.activeElement as HTMLElement)?.blur()
if (e.shiftKey) {
selectedComponent.update((old) => {
if (old && old?.[0]) {
@@ -492,11 +492,11 @@ export function appComponentFromType<T extends keyof typeof components>(
xData:
type === 'plotlycomponentv2' || type === 'chartjscomponentv2'
? {
type: 'evalv2',
fieldType: 'array',
expr: '[1, 2, 3, 4]',
connections: []
}
type: 'evalv2',
fieldType: 'array',
expr: '[1, 2, 3, 4]',
connections: []
}
: undefined,
...(extra ?? {})
}
@@ -845,33 +845,33 @@ export type InitConfig<
| EvalAppInput
| EvalV2AppInput
| {
type: 'oneOf'
selected: string
configuration: Record<
string,
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput>
>
}
type: 'oneOf'
selected: string
configuration: Record<
string,
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput>
>
}
>
> = {
[Property in keyof T]: T[Property] extends StaticAppInput
[Property in keyof T]: T[Property] extends StaticAppInput
? T[Property]['value'] | undefined
: T[Property] extends { type: 'oneOf' }
? {
type: 'oneOf'
selected: keyof T[Property]['configuration']
configuration: {
[Choice in keyof T[Property]['configuration']]: {
[IT in keyof T[Property]['configuration'][Choice]]: T[Property]['configuration'][Choice][IT] extends StaticAppInput
? T[Property]['configuration'][Choice][IT] extends StaticAppInputOnDemand
? () => Promise<T[Property]['configuration'][Choice][IT]['value'] | undefined>
: T[Property]['configuration'][Choice][IT]['value'] | undefined
: undefined
}
}
? {
type: 'oneOf'
selected: keyof T[Property]['configuration']
configuration: {
[Choice in keyof T[Property]['configuration']]: {
[IT in keyof T[Property]['configuration'][Choice]]: T[Property]['configuration'][Choice][IT] extends StaticAppInput
? T[Property]['configuration'][Choice][IT] extends StaticAppInputOnDemand
? () => Promise<T[Property]['configuration'][Choice][IT]['value'] | undefined>
: T[Property]['configuration'][Choice][IT]['value'] | undefined
: undefined
}
: undefined
}
}
}
: undefined
}
export function initConfig<
T extends Record<
@@ -880,13 +880,13 @@ export function initConfig<
| EvalAppInput
| EvalV2AppInput
| {
type: 'oneOf'
selected: string
configuration: Record<
string,
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput>
>
}
type: 'oneOf'
selected: string
configuration: Record<
string,
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput>
>
}
>
>(
r: T,
@@ -894,13 +894,13 @@ export function initConfig<
string,
| StaticAppInput
| {
type: 'oneOf'
selected: string
configuration: Record<
string,
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput | boolean>
>
}
type: 'oneOf'
selected: string
configuration: Record<
string,
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput | boolean>
>
}
| any
>
): InitConfig<T> {
@@ -910,31 +910,31 @@ export function initConfig<
Object.entries(r).map(([key, value]) =>
value.type == 'static'
? [
key,
configuration?.[key]?.type == 'static' ? configuration?.[key]?.['value'] : undefined
]
key,
configuration?.[key]?.type == 'static' ? configuration?.[key]?.['value'] : undefined
]
: value.type == 'oneOf'
? [
key,
{
selected: value.selected,
type: 'oneOf',
configuration: Object.fromEntries(
Object.entries(value.configuration).map(([choice, config]) => {
const conf = initConfig(
config,
configuration?.[key]?.configuration?.[choice]
)
Object.entries(config).forEach(([innerKey, innerValue]) => {
if (innerValue.type === 'static' && !(innerKey in conf)) {
conf[innerKey] = innerValue.value
}
})
return [choice, conf]
key,
{
selected: value.selected,
type: 'oneOf',
configuration: Object.fromEntries(
Object.entries(value.configuration).map(([choice, config]) => {
const conf = initConfig(
config,
configuration?.[key]?.configuration?.[choice]
)
Object.entries(config).forEach(([innerKey, innerValue]) => {
if (innerValue.type === 'static' && !(innerKey in conf)) {
conf[innerKey] = innerValue.value
}
})
)
}
]
return [choice, conf]
})
)
}
]
: [key, undefined]
)
) as any
@@ -1269,10 +1269,11 @@ export function isContainer(type: string): boolean {
export function subGridIndexKey(type: string | undefined, id: string, world: World): number {
switch (type) {
case 'containercomponent':
case 'verticalsplitpanescomponent':
case 'horizontalsplitpanescomponent':
case 'listcomponent':
return 0
case 'verticalsplitpanescomponent':
case 'horizontalsplitpanescomponent':
return (world?.outputsById?.[id]?.selectedPaneIndex?.peak() as number) ?? 0
case 'tabscomponent': {
return (world?.outputsById?.[id]?.selectedTabIndex?.peak() as number) ?? 0
}
@@ -16,7 +16,7 @@
import { getContainerHeight } from './utils/container'
import { moveItem, getItemById, specifyUndefinedColumns } from './utils/item'
import { onMount, createEventDispatcher, getContext } from 'svelte'
import { onMount, getContext } from 'svelte'
import { getColumn, throttle } from './utils/other'
import MoveResize from './MoveResize.svelte'
import type { FilledItem } from './types'
@@ -34,8 +34,6 @@
type GridShadow
} from '../editor/appUtils'
const dispatch = createEventDispatcher()
type T = $$Generic
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
@@ -55,6 +53,20 @@
parentWidth?: number | undefined
disableMove?: boolean
children?: import('svelte').Snippet<[any]>
onDropped?: (e: { id: string; overlapped: string | undefined; x: number; y: number }) => void
onRedraw?: (grid: FilledItem<T>[]) => void
onResize?: (e: {
cols: number
xPerPx: number
yPerPx: number
width: number | undefined
}) => void
onMounted?: (e: {
cols: number
xPerPx: number
yPerPx: number
width: number | undefined
}) => void
}
let {
@@ -71,7 +83,11 @@
root = false,
parentWidth = undefined,
disableMove = false,
children
children,
onDropped,
onRedraw,
onResize,
onMounted
}: Props = $props()
const cols = columnConfiguration
@@ -83,10 +99,10 @@
let xPerPx = $state(0)
let yPerPx = rowHeight
const onResize = throttle(() => {
const onResizeThrottled = throttle(() => {
if (!getComputedCols) return
sortedItems = specifyUndefinedColumns(sortedItems, getComputedCols, cols)
dispatch('resize', {
onResize?.({
cols: getComputedCols,
xPerPx,
yPerPx,
@@ -116,13 +132,14 @@
if (!containerWidth && getComputedCols) {
sortedItems = specifyUndefinedColumns(sortedItems, getComputedCols, cols)
dispatch('mount', {
onMounted?.({
cols: getComputedCols,
xPerPx,
yPerPx // same as rowHeight
yPerPx,
width
})
} else {
onResize()
onResizeThrottled()
}
containerWidth = width
@@ -162,8 +179,7 @@
})
: []
}
const updateMatrix = ({ detail }) => {
let isPointerUp = detail.isPointerUp
const updateMatrix = ({ isPointerUp, id, activate }) => {
let citems: FilledItem<T>[]
if (isPointerUp) {
if (initItems == undefined) {
@@ -179,8 +195,8 @@
citems = smartCopy(initItems)
}
let nselectedIds = selectedIds ?? []
if (detail.id && !selectedIds?.includes(detail.id)) {
nselectedIds = [detail.id, ...(selectedIds ?? [])]
if (id && !selectedIds?.includes(id)) {
nselectedIds = [id, ...(selectedIds ?? [])]
}
for (let id of nselectedIds) {
let activeItem = getItemById(id, citems)
@@ -235,13 +251,13 @@
}
for (let id of nselectedIds ?? []) {
if (detail.activate) {
if (activate) {
moveResizes?.[id]?.inActivate()
}
}
if (isPointerUp && getComputedCols) {
dispatch('redraw', sortGridItemsPosition(smartCopy(sortedItems), getComputedCols))
onRedraw?.(sortGridItemsPosition(smartCopy(sortedItems), getComputedCols))
}
}
@@ -259,11 +275,11 @@
}
| undefined = $state(undefined)
const handleRepaint = ({ detail }) => {
if (!detail.isPointerUp) {
throttleMatrix({ detail })
const handleRepaint = ({ isPointerUp, id, activate }) => {
if (!isPointerUp) {
throttleMatrix({ isPointerUp, id, activate })
} else {
updateMatrix({ detail })
updateMatrix({ isPointerUp, id, activate })
}
/**
@@ -488,19 +504,19 @@
{/if}
<MoveResize
{mounted}
on:initmove={() => handleInitMove(item.id)}
onInitMove={() => handleInitMove(item.id)}
onMove={handleMove}
bind:shadow={shadows[item.id]}
bind:this={moveResizes[item.id]}
on:repaint={handleRepaint}
on:resizeStart={() => (resizing = true)}
on:resizeEnd={() => (resizing = false)}
onRepaint={handleRepaint}
onResizeStart={() => (resizing = true)}
onResizeEnd={() => (resizing = false)}
onTop={Boolean(allIdsInPath?.includes(item.id))}
id={item.id}
{xPerPx}
{yPerPx}
fakeShadow={$fakeShadowStore}
on:dropped={(e) => {
onDropped={({ id, overlapped, x, y }) => {
$componentDraggedIdStore = undefined
$componentDraggedParentIdStore = undefined
$overlappedStore = undefined
@@ -510,8 +526,7 @@
if ($moveMode === 'move') {
return
}
dispatch('dropped', e.detail)
onDropped?.({ id, overlapped, x, y })
}}
width={xPerPx == 0
? 0
@@ -1,5 +1,5 @@
<script lang="ts">
import { createEventDispatcher, getContext, onMount } from 'svelte'
import { getContext, onMount } from 'svelte'
import type { AppEditorContext, AppViewerContext } from '../types'
import { writable } from 'svelte/store'
import { twMerge } from 'tailwind-merge'
@@ -12,8 +12,6 @@
import { throttle } from './utils/other'
import { moveMode } from '../gridUtils'
const dispatch = createEventDispatcher()
interface Props {
sensor: any
width: any
@@ -41,8 +39,13 @@
clientY: number
intersectingElement?: string | undefined
shadow?: GridShadow | undefined
overlapped?: string | undefined
overlapped?: string
}) => void
onDropped?: (e: { id: string; overlapped: string | undefined; x: number; y: number }) => void
onInitMove?: () => void
onResizeStart?: () => void
onResizeEnd?: () => void
onRepaint?: (e: { id: string; isPointerUp: boolean; activate: boolean }) => void
children?: import('svelte').Snippet
}
@@ -69,6 +72,11 @@
disableMove = true,
mounted = false,
onMove,
onDropped,
onInitMove,
onResizeStart,
onResizeEnd,
onRepaint,
children
}: Props = $props()
@@ -112,7 +120,7 @@
window.addEventListener('pointermove', pointermove)
window.addEventListener('pointerup', pointerup)
dispatch('initmove')
onInitMove?.()
const cordDiff = {
x: (moveX / $scale) * 100 - initX,
@@ -165,11 +173,7 @@
}
let repaint = (activate: boolean, isPointerUp: boolean) => {
dispatch('repaint', {
id,
isPointerUp,
activate
})
onRepaint?.({ id, isPointerUp, activate })
}
// Autoscroll
@@ -221,7 +225,7 @@
initX = (clientX / $scale) * 100
initY = (clientY / $scale) * 100
dispatch('initmove')
onInitMove?.()
}
window.addEventListener('pointermove', pointermove)
window.addEventListener('pointerup', pointerup)
@@ -299,7 +303,6 @@
}
throttledComputeShadow(clientX, clientY)
onMove({
cordDiff,
clientY,
@@ -359,7 +362,7 @@
el.getAttribute('data-iscontainer') === 'true'
)
const newOverlapped = intersectingElement ? intersectingElement?.id.split('-')[1] : undefined
const newOverlapped = intersectingElement ? intersectingElement?.id.split('-')[2] : undefined
const container = newOverlapped
? intersectingElement?.querySelector('.svlt-grid-container')
@@ -411,6 +414,10 @@
dragClosure = undefined
}
if (!fakeShadow) {
return
}
if (!moving) {
return
}
@@ -421,7 +428,7 @@
return
}
dispatch('dropped', {
onDropped?.({
id,
overlapped,
x: fakeShadow?.x,
@@ -456,7 +463,7 @@
window.addEventListener('pointermove', resizePointerMove)
window.addEventListener('pointerup', resizePointerUp)
dispatch('resizeStart')
onResizeStart?.()
}
const resizePointerMove = ({ pageX, pageY }) => {
@@ -492,7 +499,7 @@
window.removeEventListener('pointermove', resizePointerMove)
window.removeEventListener('pointerup', resizePointerUp)
dispatch('resizeEnd')
onResizeEnd?.()
}
function shouldDisplayShadow(moveMode: 'insert' | 'move', overlapped: string | undefined) {

Some files were not shown because too many files have changed in this diff Show More