Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Petric
2eb7360210 fix: add leaf_job to openapi spec of QueuedJob 2025-05-01 17:56:35 -04:00
35 changed files with 387 additions and 971 deletions

View File

@@ -1,28 +1,5 @@
# Changelog
## [1.486.1](https://github.com/windmill-labs/windmill/compare/v1.486.0...v1.486.1) (2025-05-04)
### Bug Fixes
* improve MultiSelectWrapper behavior ([36da8ae](https://github.com/windmill-labs/windmill/commit/36da8aec080742e13f23e1dee12b3954947f53dd))
## [1.486.0](https://github.com/windmill-labs/windmill/compare/v1.485.3...v1.486.0) (2025-05-01)
### Features
* add run now directly on schedule drawer and duplicate schedule option ([#5674](https://github.com/windmill-labs/windmill/issues/5674)) ([dfb947f](https://github.com/windmill-labs/windmill/commit/dfb947ff37c688f54a32de5aa3c5c3d142cb80f4))
* Database Manager ([#5586](https://github.com/windmill-labs/windmill/issues/5586)) ([41c15fc](https://github.com/windmill-labs/windmill/commit/41c15fc78aaf844c559d3d6c772e04ecce436e9d))
* Integrate MCP with hub ([#5685](https://github.com/windmill-labs/windmill/issues/5685)) ([ec701a9](https://github.com/windmill-labs/windmill/commit/ec701a9ee74c9d890b54234362392deca63a77c7))
### Bug Fixes
* Ai Chat: do not send tools if empty + respond even if tool fails ([#5692](https://github.com/windmill-labs/windmill/issues/5692)) ([9c55040](https://github.com/windmill-labs/windmill/commit/9c55040e47e76af8b7e2864b82fa30505545dcb5))
* do not track relative deps for scripts with raw defined deps from CLI ([#5696](https://github.com/windmill-labs/windmill/issues/5696)) ([7eb9d7d](https://github.com/windmill-labs/windmill/commit/7eb9d7d46cb48ae69a3fd3ff852a57abae450a3b))
* improve CLI file scanning performances ([0916978](https://github.com/windmill-labs/windmill/commit/09169784bd2d0ab7acf5f40dc86f36f1cae967b7))
## [1.485.3](https://github.com/windmill-labs/windmill/compare/v1.485.2...v1.485.3) (2025-04-29)

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt)\n VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)\n ON CONFLICT (hostname, log_ts) DO UPDATE SET ok_lines = log_file.ok_lines + $6, err_lines = log_file.err_lines + $7",
"query": "INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)",
"describe": {
"columns": [],
"parameters": {
@@ -17,5 +17,5 @@
},
"nullable": []
},
"hash": "92faee8714a45a403b623e04d789f7f99067a05e9dfe270223164db8a1df2e4b"
"hash": "33c1793e55b1127d88d2509aadd0eb04e042463200f237b4c2cb176612fa16fe"
}

212
backend/Cargo.lock generated
View File

@@ -634,7 +634,7 @@ dependencies = [
"serde",
"serde-aux",
"serde_json",
"sha2 0.10.9",
"sha2 0.10.8",
"thiserror 1.0.69",
"url",
]
@@ -938,7 +938,7 @@ dependencies = [
"http 0.2.12",
"http 1.3.1",
"percent-encoding",
"sha2 0.10.9",
"sha2 0.10.8",
"time",
"tracing",
]
@@ -1828,9 +1828,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.21"
version = "1.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0"
checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a"
dependencies = [
"jobserver",
"libc",
@@ -2727,7 +2727,7 @@ dependencies = [
"md-5 0.10.6",
"rand 0.8.5",
"regex",
"sha2 0.10.9",
"sha2 0.10.8",
"unicode-segmentation",
"uuid",
]
@@ -2966,7 +2966,7 @@ dependencies = [
"deno_error",
"rusqlite",
"serde",
"sha2 0.10.9",
"sha2 0.10.8",
"thiserror 2.0.12",
"tokio",
]
@@ -2994,7 +2994,7 @@ dependencies = [
"parking_lot 0.12.3",
"serde",
"serde_json",
"sha2 0.10.9",
"sha2 0.10.8",
"sys_traits",
"thiserror 1.0.69",
"url",
@@ -3142,7 +3142,7 @@ dependencies = [
"serde",
"serde_bytes",
"sha1",
"sha2 0.10.9",
"sha2 0.10.8",
"signature",
"spki",
"thiserror 2.0.12",
@@ -3511,7 +3511,7 @@ dependencies = [
"sec1",
"serde",
"sha1",
"sha2 0.10.9",
"sha2 0.10.8",
"sha3",
"signature",
"simd-json",
@@ -4322,7 +4322,7 @@ dependencies = [
"num-traits",
"pkcs8",
"rfc6979",
"sha2 0.10.9",
"sha2 0.10.8",
"signature",
"zeroize",
]
@@ -4433,7 +4433,7 @@ dependencies = [
"ed25519",
"rand_core 0.6.4",
"serde",
"sha2 0.10.9",
"sha2 0.10.8",
"signature",
"subtle",
"zeroize",
@@ -4689,7 +4689,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
dependencies = [
"cfg-if",
"rustix 1.0.7",
"rustix 1.0.5",
"windows-sys 0.59.0",
]
@@ -5474,7 +5474,7 @@ checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca"
dependencies = [
"bitflags 2.9.0",
"gpu-descriptor-types",
"hashbrown 0.15.3",
"hashbrown 0.15.2",
]
[[package]]
@@ -5589,9 +5589,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.3"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"allocator-api2",
"equivalent",
@@ -5624,7 +5624,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.15.3",
"hashbrown 0.15.2",
]
[[package]]
@@ -5774,6 +5774,17 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "hostname"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65"
dependencies = [
"cfg-if",
"libc",
"windows-link",
]
[[package]]
name = "hstr"
version = "0.2.17"
@@ -6279,7 +6290,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown 0.15.3",
"hashbrown 0.15.2",
"serde",
]
@@ -6539,7 +6550,7 @@ dependencies = [
"ecdsa",
"elliptic-curve",
"once_cell",
"sha2 0.10.9",
"sha2 0.10.8",
"signature",
]
@@ -6797,7 +6808,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.9.0",
"libc",
"redox_syscall 0.5.12",
"redox_syscall 0.5.11",
]
[[package]]
@@ -6909,7 +6920,7 @@ version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
dependencies = [
"hashbrown 0.15.3",
"hashbrown 0.15.2",
]
[[package]]
@@ -6918,7 +6929,7 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f8cc7106155f10bdf99a6f379688f543ad6596a415375b36a59a054ceda1198"
dependencies = [
"hashbrown 0.15.3",
"hashbrown 0.15.2",
]
[[package]]
@@ -7466,7 +7477,7 @@ dependencies = [
"serde",
"serde_json",
"sha1",
"sha2 0.10.9",
"sha2 0.10.8",
"thiserror 2.0.12",
"uuid",
]
@@ -7998,7 +8009,7 @@ dependencies = [
"serde",
"serde_json",
"serde_path_to_error",
"sha2 0.10.9",
"sha2 0.10.8",
"thiserror 1.0.69",
"url",
]
@@ -8151,7 +8162,7 @@ dependencies = [
"serde_path_to_error",
"serde_plain",
"serde_with",
"sha2 0.10.9",
"sha2 0.10.8",
"subtle",
"thiserror 1.0.69",
"url",
@@ -8200,9 +8211,9 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.108"
version = "0.9.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847"
checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
dependencies = [
"cc",
"libc",
@@ -8405,7 +8416,7 @@ dependencies = [
"ecdsa",
"elliptic-curve",
"primeorder",
"sha2 0.10.9",
"sha2 0.10.8",
]
[[package]]
@@ -8417,7 +8428,7 @@ dependencies = [
"ecdsa",
"elliptic-curve",
"primeorder",
"sha2 0.10.9",
"sha2 0.10.8",
]
[[package]]
@@ -8429,7 +8440,7 @@ dependencies = [
"ecdsa",
"elliptic-curve",
"primeorder",
"sha2 0.10.9",
"sha2 0.10.8",
]
[[package]]
@@ -8443,7 +8454,7 @@ dependencies = [
"elliptic-curve",
"primeorder",
"rand_core 0.6.4",
"sha2 0.10.9",
"sha2 0.10.8",
]
[[package]]
@@ -8495,7 +8506,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.12",
"redox_syscall 0.5.11",
"smallvec",
"windows-targets 0.52.6",
]
@@ -8765,7 +8776,7 @@ dependencies = [
"der",
"pbkdf2",
"scrypt",
"sha2 0.10.9",
"sha2 0.10.8",
"spki",
]
@@ -8854,7 +8865,7 @@ dependencies = [
"md-5 0.10.6",
"memchr",
"rand 0.8.5",
"sha2 0.10.9",
"sha2 0.10.8",
"stringprep",
]
@@ -8872,7 +8883,7 @@ dependencies = [
"md-5 0.10.6",
"memchr",
"rand 0.9.0",
"sha2 0.10.9",
"sha2 0.10.8",
"stringprep",
]
@@ -9238,7 +9249,7 @@ checksum = "287e56aac5a2b4fb25a6fb050961d157635924c8696305a5c937a76f29841a0f"
dependencies = [
"ahash 0.8.11",
"equivalent",
"hashbrown 0.15.3",
"hashbrown 0.15.2",
"parking_lot 0.12.3",
]
@@ -9284,9 +9295,9 @@ dependencies = [
[[package]]
name = "quinn-udp"
version = "0.5.12"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842"
checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5"
dependencies = [
"cfg_aliases 0.2.1",
"libc",
@@ -9507,9 +9518,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.12"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
dependencies = [
"bitflags 2.9.0",
]
@@ -9744,9 +9755,12 @@ dependencies = [
[[package]]
name = "resolv-conf"
version = "0.7.3"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7c8f7f733062b66dc1c63f9db168ac0b97a9210e247fa90fdc9ad08f51b302"
checksum = "48375394603e3dd4b2d64371f7148fd8c7baa2680e28741f2cb8d23b59e3d4c4"
dependencies = [
"hostname",
]
[[package]]
name = "retry-policies"
@@ -9971,7 +9985,7 @@ version = "7.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74"
dependencies = [
"sha2 0.10.9",
"sha2 0.10.8",
"walkdir",
]
@@ -10062,9 +10076,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.0.7"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags 2.9.0",
"errno",
@@ -10446,7 +10460,7 @@ dependencies = [
"password-hash",
"pbkdf2",
"salsa20",
"sha2 0.10.9",
"sha2 0.10.8",
]
[[package]]
@@ -10769,9 +10783,9 @@ dependencies = [
[[package]]
name = "sha2"
version = "0.10.9"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -11164,7 +11178,7 @@ dependencies = [
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.15.3",
"hashbrown 0.15.2",
"hashlink 0.10.0",
"indexmap 2.9.0",
"log",
@@ -11174,7 +11188,7 @@ dependencies = [
"rustls 0.23.26",
"serde",
"serde_json",
"sha2 0.10.9",
"sha2 0.10.8",
"smallvec",
"thiserror 2.0.12",
"tokio",
@@ -11213,7 +11227,7 @@ dependencies = [
"quote",
"serde",
"serde_json",
"sha2 0.10.9",
"sha2 0.10.8",
"sqlx-core",
"sqlx-mysql",
"sqlx-postgres",
@@ -11259,7 +11273,7 @@ dependencies = [
"rsa",
"serde",
"sha1",
"sha2 0.10.9",
"sha2 0.10.8",
"smallvec",
"sqlx-core",
"stringprep",
@@ -11300,7 +11314,7 @@ dependencies = [
"rand 0.8.5",
"serde",
"serde_json",
"sha2 0.10.9",
"sha2 0.10.8",
"smallvec",
"sqlx-core",
"stringprep",
@@ -11900,9 +11914,9 @@ dependencies = [
[[package]]
name = "synstructure"
version = "0.13.2"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
@@ -12171,7 +12185,7 @@ dependencies = [
"fastrand",
"getrandom 0.3.2",
"once_cell",
"rustix 1.0.7",
"rustix 1.0.5",
"windows-sys 0.59.0",
]
@@ -12190,7 +12204,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
dependencies = [
"rustix 1.0.7",
"rustix 1.0.5",
"windows-sys 0.59.0",
]
@@ -12675,7 +12689,7 @@ dependencies = [
"futures-io",
"futures-sink",
"futures-util",
"hashbrown 0.15.3",
"hashbrown 0.15.2",
"pin-project-lite",
"slab",
"tokio",
@@ -12744,7 +12758,7 @@ checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
dependencies = [
"indexmap 2.9.0",
"toml_datetime",
"winnow 0.7.9",
"winnow 0.7.7",
]
[[package]]
@@ -13732,18 +13746,18 @@ dependencies = [
[[package]]
name = "webpki-root-certs"
version = "0.26.10"
version = "0.26.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c99403924bc5f23afefc319b8ac67ed0e50669f6e52a413314cccb1fdbc93ba0"
checksum = "180d2741b6115c3d906577e6533ad89472d48d96df00270fccb78233073d77f7"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webpki-roots"
version = "0.26.10"
version = "0.26.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37493cadf42a2a939ed404698ded7fb378bf301b5011f973361779a3a74f8c93"
checksum = "29aad86cec885cafd03e8305fd727c418e970a521322c91688414d5b8efba16b"
dependencies = [
"rustls-pki-types",
]
@@ -13861,7 +13875,7 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7"
dependencies = [
"redox_syscall 0.5.12",
"redox_syscall 0.5.11",
"wasite",
"web-sys",
]
@@ -13905,7 +13919,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"axum",
@@ -13929,7 +13943,7 @@ dependencies = [
"serde",
"serde_json",
"sha1",
"sha2 0.10.9",
"sha2 0.10.8",
"sqlx",
"tikv-jemalloc-ctl",
"tikv-jemalloc-sys",
@@ -13952,7 +13966,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"argon2",
@@ -14023,7 +14037,7 @@ dependencies = [
"serde_json",
"serde_urlencoded",
"sha1",
"sha2 0.10.9",
"sha2 0.10.8",
"sql-builder",
"sqlx",
"tempfile",
@@ -14062,7 +14076,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -14077,7 +14091,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"chrono",
"serde",
@@ -14090,7 +14104,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"serde",
@@ -14104,7 +14118,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"async-stream",
@@ -14149,7 +14163,7 @@ dependencies = [
"semver 1.0.26",
"serde",
"serde_json",
"sha2 0.10.9",
"sha2 0.10.8",
"sqlx",
"tar",
"tempfile",
@@ -14168,7 +14182,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"regex",
"serde",
@@ -14182,7 +14196,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"bytes",
@@ -14205,7 +14219,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -14217,7 +14231,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -14226,7 +14240,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"lazy_static",
@@ -14238,7 +14252,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"serde_json",
@@ -14250,7 +14264,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"gosyn",
@@ -14262,7 +14276,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"lazy_static",
@@ -14274,7 +14288,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"serde_json",
@@ -14286,7 +14300,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"nu-parser",
@@ -14297,7 +14311,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -14308,7 +14322,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -14319,7 +14333,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"async-recursion",
@@ -14339,7 +14353,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -14356,7 +14370,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"lazy_static",
@@ -14368,7 +14382,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"lazy_static",
@@ -14386,7 +14400,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"getrandom 0.2.16",
@@ -14410,7 +14424,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"serde_json",
@@ -14420,7 +14434,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"async-recursion",
@@ -14453,7 +14467,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -14463,7 +14477,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.486.1"
version = "1.485.3"
dependencies = [
"anyhow",
"async-recursion",
@@ -14517,7 +14531,7 @@ dependencies = [
"rust_decimal",
"serde",
"serde_json",
"sha2 0.10.9",
"sha2 0.10.8",
"sqlx",
"tar",
"tiberius",
@@ -15023,9 +15037,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "0.7.9"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3"
checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5"
dependencies = [
"memchr",
]
@@ -15118,7 +15132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
dependencies = [
"libc",
"rustix 1.0.7",
"rustix 1.0.5",
]
[[package]]
@@ -15178,7 +15192,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"synstructure 0.13.2",
"synstructure 0.13.1",
]
[[package]]
@@ -15239,7 +15253,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"synstructure 0.13.2",
"synstructure 0.13.1",
]
[[package]]

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.486.1"
version = "1.485.3"
authors.workspace = true
edition.workspace = true
@@ -32,7 +32,7 @@ members = [
]
[workspace.package]
version = "1.486.1"
version = "1.485.3"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"

View File

@@ -142,8 +142,6 @@ pub static FULL_IMPORTS_MAP: PyMap = phf_map! {
"google.cloud.dns" => "google-cloud-dns",
"google.cloud.runtimeconfig" => "google-cloud-runtimeconfig",
"google.cloud.iot" => "google-cloud-iot",
"google.generativeai" => "google-generativeai",
"google.genai" => "google-genai",
// Azure
"azure.mgmt.hybridkubernetes" => "azure-mgmt-hybridkubernetes",
"azure.mgmt.sql" => "azure-mgmt-sql",

View File

@@ -638,9 +638,7 @@ async fn send_log_file_to_object_store(
let (ok_lines, err_lines) = read_log_counters(ts_str);
if let Some(db) = conn.as_sql() {
if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt)
VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)
ON CONFLICT (hostname, log_ts) DO UPDATE SET ok_lines = log_file.ok_lines + $6, err_lines = log_file.err_lines + $7",
if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)",
hostname, mode.to_string(), worker_group.clone(), ts, highest_file, ok_lines as i64, err_lines as i64, *JSON_FMT)
.execute(db)
.await {

View File

@@ -16,7 +16,6 @@ use tokio::sync::RwLock;
#[cfg(feature = "enterprise")]
use tokio::time::{timeout, Duration};
#[cfg(feature = "python")]
use windmill_api_client::types::{CreateFlowBody, RawScript};
#[cfg(feature = "enterprise")]
use windmill_api_client::types::{EditSchedule, NewSchedule, ScriptArgs};
@@ -24,12 +23,9 @@ use windmill_api_client::types::{NewScript, ScriptLang as NewScriptLanguage};
use serde::Serialize;
use windmill_common::worker::WORKER_CONFIG;
#[cfg(feature = "deno_core")]
use windmill_common::flows::InputTransform;
use windmill_common::{
flow_status::{FlowStatus, FlowStatusModule, RestartedFrom},
flows::{FlowModule, FlowModuleValue, FlowValue},
flows::{FlowModule, FlowModuleValue, FlowValue, InputTransform},
jobs::{JobKind, JobPayload, RawCode},
jwt::JWT_SECRET,
scripts::{ScriptHash, ScriptLang},
@@ -171,7 +167,6 @@ impl ApiServer {
// Ok(())
// }
#[cfg(feature="python")]
fn get_module(cjob: &CompletedJob, id: &str) -> Option<FlowStatusModule> {
cjob.flow_status.clone().and_then(|fs| {
find_module_in_vec(
@@ -181,7 +176,6 @@ fn get_module(cjob: &CompletedJob, id: &str) -> Option<FlowStatusModule> {
})
}
#[cfg(feature="python")]
fn find_module_in_vec(modules: Vec<FlowStatusModule>, id: &str) -> Option<FlowStatusModule> {
modules.into_iter().find(|s| s.id() == id)
}
@@ -290,7 +284,6 @@ mod suspend_resume {
.unwrap()
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -373,7 +366,6 @@ mod suspend_resume {
);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn cancel_from_job(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -399,7 +391,6 @@ mod suspend_resume {
);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn cancel_after_suspend(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -573,7 +564,6 @@ def main(last, port):
.unwrap()
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_pass(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -619,7 +609,6 @@ def main(last, port):
assert_eq!(json!([3, 5, 7, 9]), result);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_fail_step_zero(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -663,7 +652,6 @@ def main(last, port):
);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_fail_step_one(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -705,7 +693,6 @@ def main(last, port):
.contains("index out of range"));
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_with_failure_module(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -782,7 +769,6 @@ def main(error, port):
}
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_iteration(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -841,7 +827,6 @@ async fn test_iteration(db: Pool<Postgres>) {
.contains("2"));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_iteration_parallel(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1120,7 +1105,6 @@ trait StreamFind: futures::Stream + Unpin + Sized {
impl<T: futures::Stream + Unpin + Sized> StreamFind for T {}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_deno_flow(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1239,7 +1223,6 @@ async fn test_deno_flow(db: Pool<Postgres>) {
}
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_identity(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1277,7 +1260,6 @@ async fn test_identity(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!(42));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1553,7 +1535,6 @@ async fn test_flow_result_by_id(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!([[42]]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_stop_after_if(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1607,7 +1588,6 @@ async fn test_stop_after_if(db: Pool<Postgres>) {
assert_eq!(json!(-123), result);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_stop_after_if_nested(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1666,7 +1646,6 @@ async fn test_stop_after_if_nested(db: Pool<Postgres>) {
assert_eq!(json!([-123]), result);
}
#[cfg(all(feature = "deno_core", feature = "python"))]
#[sqlx::test(fixtures("base"))]
async fn test_python_flow(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1724,7 +1703,6 @@ async fn test_python_flow(db: Pool<Postgres>) {
}
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_python_flow_2(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1799,7 +1777,6 @@ func main(derp string) (string, error) {
assert_eq!(result, serde_json::json!("hello world"));
}
#[cfg(feature = "rust")]
#[sqlx::test(fixtures("base"))]
async fn test_rust_job(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2055,7 +2032,6 @@ public class Main {
assert_eq!(job.json_result(), Some(json!("hello world")));
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_python_job(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2089,7 +2065,6 @@ def main():
assert_eq!(result, serde_json::json!("hello world"));
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_python_job_heavy_dep(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2126,7 +2101,6 @@ def main():
assert_eq!(result, serde_json::json!(3));
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_python_job_with_imports(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2230,7 +2204,6 @@ export async function main(a: Date) {
assert_eq!(result, serde_json::json!("object"));
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_python_job_datetime_and_bytes(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2266,7 +2239,6 @@ def main(a: datetime, b: bytes):
assert_eq!(result, serde_json::json!([true, true]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_empty_loop_1(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2323,7 +2295,6 @@ async fn test_empty_loop_1(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!(0));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_invalid_first_step(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2404,7 +2375,6 @@ async fn test_empty_loop_2(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!([]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_step_after_loop(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2528,7 +2498,6 @@ async fn test_branchone_simple(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!([1, 2]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_branchone_with_cond(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2565,7 +2534,6 @@ async fn test_branchone_with_cond(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!([1, 3]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_branchall_sequential(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2604,7 +2572,6 @@ async fn test_branchall_sequential(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!([[1, 2], [1, 3]]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_branchall_simple(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2732,7 +2699,6 @@ async fn test_branchall_skip_failure(db: Pool<Postgres>) {
);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_branchone_nested(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2852,7 +2818,6 @@ async fn test_branchall_nested(db: Pool<Postgres>) {
);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_failure_module(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2965,7 +2930,6 @@ async fn test_failure_module(db: Pool<Postgres>) {
assert_eq!(json!({ "l": [0, 1, 2] }), result);
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_flow_lock_all(db: Pool<Postgres>) {
use futures::StreamExt;
@@ -3104,7 +3068,6 @@ async fn test_flow_lock_all(db: Pool<Postgres>) {
});
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_complex_flow_restart(db: Pool<Postgres>) {
@@ -3789,7 +3752,6 @@ export async function main() {
run_preview_relative_imports(&db, content, ScriptLang::Bun).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "relative_bun"))]
async fn test_nested_imports_bun(db: Pool<Postgres>) {
let content = r#"
@@ -3838,7 +3800,6 @@ export async function main() {
run_preview_relative_imports(&db, content, ScriptLang::Deno).await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "relative_python"))]
async fn test_relative_imports_python(db: Pool<Postgres>) {
let content = r#"
@@ -3856,7 +3817,6 @@ def main():
run_preview_relative_imports(&db, content, ScriptLang::Python3).await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "relative_python"))]
async fn test_nested_imports_python(db: Pool<Postgres>) {
let content = r#"
@@ -3872,7 +3832,6 @@ def main():
run_preview_relative_imports(&db, content, ScriptLang::Python3).await;
}
#[cfg(feature = "python")]
async fn assert_lockfile(
db: &Pool<Postgres>,
script_content: String,
@@ -3965,8 +3924,6 @@ async fn assert_lockfile(
)
.await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_requirements_python(db: Pool<Postgres>) {
let content = r#"
@@ -3992,8 +3949,6 @@ def main():
)
.await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_extra_requirements_python(db: Pool<Postgres>) {
{
@@ -4021,8 +3976,6 @@ def main():
.await;
}
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_extra_requirements_python2(db: Pool<Postgres>) {
let content = r#"
@@ -4045,7 +3998,6 @@ def main():
.await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_pins_python(db: Pool<Postgres>) {
let content = r#"
@@ -4329,7 +4281,6 @@ mod job_payload {
];
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_script_hash_payload(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4490,7 +4441,6 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_flow_node_payload(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4675,7 +4625,6 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_flow_payload(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4718,7 +4667,6 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_flow_payload_with_preprocessor(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4786,7 +4734,6 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_restarted_flow_payload(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4839,7 +4786,6 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_raw_flow_payload(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4886,7 +4832,6 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_raw_flow_payload_with_restarted_from(db: Pool<Postgres>) {
initialize_tracing().await;

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.486.1
version: 1.485.3
title: Windmill API
contact:
@@ -13430,6 +13430,8 @@ components:
type: string
raw_code:
type: string
leaf_job:
$ref: "#/components/schemas/LeafJob"
canceled:
type: boolean
canceled_by:
@@ -16729,4 +16731,30 @@ components:
channel_name:
type: string
description: Microsoft Teams channel name
minLength: 1
minLength: 1
LeafJob:
type: object
additionalProperties:
oneOf:
- type: object
properties:
SingleJob:
type: string
format: uuid
description: UUID of an individual job
required:
- SingleJob
additionalProperties: false
- type: object
properties:
ListJob:
type: array
items:
type: string
format: uuid
description: Array of UUIDs for multiple related jobs
required:
- ListJob
additionalProperties: false
description: Map of keys to JobResult, which can be either SingleJob (individual UUID) or ListJob (array of UUIDs)

View File

@@ -12,168 +12,27 @@ use rmcp::{
service::{RequestContext, RoleServer},
Error,
};
use serde::{Deserialize, Serialize};
use serde::Serialize;
use serde_json::Value;
use sql_builder::prelude::*;
use sqlx::FromRow;
use tokio::try_join;
use tokio_util::sync::CancellationToken;
use windmill_common::db::UserDB;
use windmill_common::scripts::Schema;
use windmill_common::worker::to_raw_value;
use windmill_common::{DB, HUB_BASE_URL};
use windmill_common::scripts::{get_full_hub_script_by_path, Schema};
use windmill_common::DB;
use crate::db::ApiAuthed;
use crate::jobs::{
run_wait_result_flow_by_path_internal, run_wait_result_script_by_path_internal, RunJobQuery,
};
use crate::HTTP_CLIENT;
use windmill_common::utils::{query_elems_from_hub, StripPath};
/// Transforms the path for workspace scripts/flows.
///
/// This function takes a path and a type string.
/// It then formats the transformed path with the type prefix.
/// This is used when listing, because we can't have names with slashes.
/// Because we replace slashes with underscores, we also need to escape underscores.
///
/// # Parameters
/// - `path`: The path to transform.
/// - `type_str`: The type of the item (script or flow).
///
/// # Returns
/// - `String`: The transformed path.
fn transform_path(path: &str, type_str: &str) -> String {
// Only apply special underscore escaping for paths starting with "f/"
let transformed = if path.starts_with("f/") {
let escaped_path = path.replace('_', "__");
escaped_path.replace('/', "_")
} else {
path.replace('/', "_")
};
// first letter of type_str is used as prefix, only one letter to avoid reaching 60 char name limit
format!("{}-{}", &type_str[..1], transformed)
}
fn convert_schema_to_schema_type(schema: Option<Schema>) -> SchemaType {
let schema_obj = if let Some(ref s) = schema {
match serde_json::from_str::<SchemaType>(s.0.get()) {
Ok(val) => val,
Err(_) => SchemaType::default(),
}
} else {
SchemaType::default()
};
schema_obj
}
trait ToolableItem {
fn get_path_or_id(&self) -> String;
fn get_summary(&self) -> &str;
fn get_description(&self) -> &str;
fn get_schema(&self) -> SchemaType;
fn is_hub(&self) -> bool;
fn item_type(&self) -> &'static str;
fn get_integration_type(&self) -> Option<String>;
}
impl ToolableItem for ScriptInfo {
fn get_path_or_id(&self) -> String {
transform_path(&self.path, "script")
}
fn get_summary(&self) -> &str {
self.summary.as_deref().unwrap_or("No summary")
}
fn get_description(&self) -> &str {
self.description.as_deref().unwrap_or("No description")
}
fn get_schema(&self) -> SchemaType {
convert_schema_to_schema_type(self.schema.clone())
}
fn is_hub(&self) -> bool {
false
}
fn item_type(&self) -> &'static str {
"script"
}
fn get_integration_type(&self) -> Option<String> {
None
}
}
impl ToolableItem for FlowInfo {
fn get_path_or_id(&self) -> String {
transform_path(&self.path, "flow")
}
fn get_summary(&self) -> &str {
self.summary.as_deref().unwrap_or("No summary")
}
fn get_description(&self) -> &str {
self.description.as_deref().unwrap_or("No description")
}
fn get_schema(&self) -> SchemaType {
convert_schema_to_schema_type(self.schema.clone())
}
fn is_hub(&self) -> bool {
false
}
fn item_type(&self) -> &'static str {
"flow"
}
fn get_integration_type(&self) -> Option<String> {
None
}
}
impl ToolableItem for HubScriptInfo {
fn get_path_or_id(&self) -> String {
let id = self.version_id;
let summary = self.summary.as_deref().unwrap_or("No summary");
format!("hs-{}-{}", id, summary.replace(" ", "_"))
}
fn get_summary(&self) -> &str {
self.summary.as_deref().unwrap_or("No summary")
}
fn get_description(&self) -> &str {
self.description.as_deref().unwrap_or("No description")
}
fn get_schema(&self) -> SchemaType {
match serde_json::from_value::<SchemaType>(self.schema.clone().unwrap_or_default()) {
Ok(schema_type) => schema_type,
Err(_) => SchemaType::default(),
}
}
fn is_hub(&self) -> bool {
true
}
fn item_type(&self) -> &'static str {
"script"
}
fn get_integration_type(&self) -> Option<String> {
self.app.clone()
}
}
use windmill_common::utils::StripPath;
#[derive(Clone)]
pub struct Runner {}
#[derive(Serialize, Deserialize, Debug)]
struct HubResponse {
asks: Vec<HubScriptInfo>,
}
#[derive(Serialize, Deserialize, Debug)]
struct HubScriptInfo {
version_id: u64,
summary: Option<String>,
description: Option<String>,
schema: Option<Value>,
app: Option<String>,
}
#[derive(Serialize, FromRow, Deserialize, Debug, Clone)]
#[derive(serde::Deserialize, serde::Serialize)]
struct SchemaType {
r#type: String,
properties: std::collections::HashMap<String, serde_json::Value>,
@@ -235,7 +94,7 @@ impl Runner {
authed: &ApiAuthed,
workspace_id: &str,
item_type: &str,
) -> Result<Option<Schema>, Error> {
) -> Result<ItemSchema, Error> {
let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type));
sqlb.fields(&["o.schema"]);
sqlb.and_where("o.path = ?".bind(&path));
@@ -251,7 +110,7 @@ impl Runner {
.begin(authed)
.await
.map_err(|_e| Error::internal_error("failed to begin transaction", None))?;
let item = sqlx::query_as::<_, ItemSchema>(&sql)
let rows = sqlx::query_as::<_, ItemSchema>(&sql)
.fetch_one(&mut *tx)
.await
.map_err(|_e| {
@@ -261,29 +120,27 @@ impl Runner {
tx.commit()
.await
.map_err(|_e| Error::internal_error("failed to commit transaction", None))?;
Ok(item.schema)
Ok(rows)
}
/// Reverses the transformation of a path.
///
/// This function takes a transformed path and reverses the transformation applied by `transform_path`.
/// It checks if the path starts with "h" (indicating a Hub script) and removes the prefix if present.
/// It then determines the type of the item (script or flow) based on the prefix.
/// This is used in call_tool to get the original path, and the type of the item.
///
/// # Parameters
/// - `transformed_path`: The transformed path to reverse.
///
/// # Returns
/// - `Result<(&str, String, bool), String>`: A tuple containing the original path, the type of the item, and a boolean indicating if it's a Hub script.
/// - `Err(String)`: If the path is invalid.
fn reverse_transform(transformed_path: &str) -> Result<(&str, String, bool), String> {
let is_hub = transformed_path.starts_with("h");
let transformed_path = if is_hub {
transformed_path[1..].to_string()
fn transform_path(path: &str, type_str: &str) -> Result<String, String> {
if type_str != "script" && type_str != "flow" {
return Err(format!("Invalid type: {}", type_str));
}
// Only apply special underscore escaping for paths starting with "f/"
let transformed = if path.starts_with("f/") {
let escaped_path = path.replace('_', "__");
escaped_path.replace('/', "_")
} else {
transformed_path.to_string()
path.replace('/', "_")
};
// first letter of type_str is used as prefix, only one letter to avoid reaching 60 char name limit
Ok(format!("{}-{}", &type_str[..1], transformed))
}
fn reverse_transform(transformed_path: &str) -> Result<(&str, String), String> {
let type_str = if transformed_path.starts_with("s-") {
"script"
} else if transformed_path.starts_with("f-") {
@@ -300,10 +157,7 @@ impl Runner {
// Check if this path was previously transformed with special underscore handling
let is_special_path = mangled_path.starts_with("f_");
let original_path = if is_hub {
let parts = mangled_path.split("-").collect::<Vec<&str>>();
parts[0].to_string()
} else if is_special_path {
let original_path = if is_special_path {
const TEMP_PLACEHOLDER: &str = "@@UNDERSCORE@@";
let path_with_placeholder = mangled_path.replace("__", TEMP_PLACEHOLDER);
let path_with_slashes = path_with_placeholder.replace('_', "/");
@@ -312,7 +166,7 @@ impl Runner {
mangled_path.replacen('_', "/", 2)
};
Ok((type_str, original_path, is_hub))
Ok((type_str, original_path))
}
async fn inner_get_resources_types(
@@ -426,52 +280,6 @@ impl Runner {
Ok(rows)
}
async fn inner_get_scripts_from_hub(
db: &DB,
scope_integrations: Option<&str>,
) -> Result<Vec<HubScriptInfo>, Error> {
let query_params = Some(vec![
("limit", "100".to_string()),
("with_schema", "true".to_string()),
("apps", scope_integrations.unwrap_or("").to_string()),
]);
let url = format!("{}/scripts/top", *HUB_BASE_URL.read().await);
let (_status_code, _headers, response) =
query_elems_from_hub(&HTTP_CLIENT, &url, query_params, &db)
.await
.map_err(|e| {
tracing::error!("Failed to get items from hub: {}", e);
Error::internal_error(format!("Failed to get items from hub: {}", e), None)
})?;
let body_bytes = to_bytes(response, usize::MAX).await.map_err(|e| {
tracing::error!("Failed to read response body: {}", e);
Error::internal_error(format!("Failed to read response body: {}", e), None)
})?;
let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| {
tracing::error!("Failed to decode response body: {}", e);
Error::internal_error(format!("Failed to decode response body: {}", e), None)
})?;
let hub_response: HubResponse = serde_json::from_str(&body_str).map_err(|e| {
tracing::error!("Failed to parse hub response: {}", e);
Error::internal_error(format!("Failed to parse hub response: {}", e), None)
})?;
Ok(hub_response.asks)
}
/// Transforms a value if it's an object.
///
/// This function takes a key and a value, and a schema object.
/// If the value is a string that starts with "$res:", it returns the value as is.
/// Otherwise, it checks if the key is defined in the schema and if it's an object type.
/// If it is, it transforms the value to a string. This is because some clients do not support object types.
/// # Parameters
/// - `key`: The key of the value to transform.
/// - `value`: The value to transform.
/// - `schema_obj`: The schema object.
///
/// # Returns
/// - `Value`: The transformed value.
fn transform_value_if_object(
key: &str,
value: &Value,
@@ -507,16 +315,6 @@ impl Runner {
value.clone()
}
/// Reverses the transformation of a key.
///
/// This function takes a transformed key and a schema object.
/// It then reverses the transformation applied by `apply_key_transformation`. This can be subject to collisions, but it's unlikely and is ok for our use case.
/// # Parameters
/// - `transformed_key`: The transformed key to reverse.
/// - `schema_obj`: The schema object.
///
/// # Returns
/// - `String`: The original key.
fn reverse_transform_key(transformed_key: &str, schema_obj: &Option<SchemaType>) -> String {
let schema_obj = match schema_obj {
Some(s) => s,
@@ -540,16 +338,6 @@ impl Runner {
transformed_key.to_string()
}
/// Applies a key transformation to a key.
///
/// This function takes a key and replaces spaces with underscores.
/// It also removes any characters that are not alphanumeric or underscores.
/// This is used when listing, because we can't have names with spaces or special characters in the schema properties.
/// # Parameters
/// - `key`: The key to transform.
///
/// # Returns
/// - `String`: The transformed key.
fn apply_key_transformation(key: &str) -> String {
key.replace(' ', "_")
.chars()
@@ -557,32 +345,18 @@ impl Runner {
.collect::<String>()
}
/// Transforms the schema for resources.
///
/// This function takes a schema and a database connection, and attempts to transform the schema for resources.
/// It replaces invalid characters in property keys with underscores and converts object properties to strings.
/// It also fetches resource type information and adds it to the description of resource properties.
///
/// # Parameters
/// - `schema`: The schema to transform.
/// - `user_db`: The database connection.
/// - `authed`: The authenticated user.
/// - `w_id`: The workspace ID.
/// - `resources_cache`: A mutable reference to the resources cache.
/// - `resources_types`: A reference to the resource types.
///
/// # Returns
/// - `Result<SchemaType, Error>`: The transformed schema.
/// - `Err(Error)`: If the transformation fails.
async fn transform_schema_for_resources(
schema: &SchemaType,
schema: &Schema,
user_db: &UserDB,
authed: &ApiAuthed,
w_id: &str,
resources_cache: &mut HashMap<String, Vec<ResourceInfo>>,
resources_types: &Vec<ResourceType>,
) -> Result<SchemaType, Error> {
let mut schema_obj: SchemaType = schema.clone();
let mut schema_obj: SchemaType = match serde_json::from_str(schema.0.get()) {
Ok(val) => val,
Err(_) => SchemaType::default(),
};
// replace invalid char in property key with underscore
let replacements: Vec<(String, String, serde_json::Value)> = schema_obj
@@ -715,133 +489,9 @@ impl Runner {
Ok(schema_obj)
}
/// Fetches the schema for a Hub script.
///
/// This function takes a script path and a database connection, and attempts to fetch the schema for the script.
/// It strips the path to remove any leading slashes, and then attempts to retrieve the full script using `get_full_hub_script_by_path`.
/// If successful, it converts the schema string to a `Schema` object.
/// If the schema cannot be converted, it logs a warning and returns `None`.
///
/// # Parameters
/// - `path`: The path of the script to fetch the schema for.
/// - `db`: The database connection.
///
/// # Returns
/// - `Ok(Option<Schema>)`: The schema if found, otherwise `None`.
/// - `Err(Error)`: If the request fails.
async fn get_hub_script_schema(path: &str, db: &DB) -> Result<Option<Schema>, Error> {
let strip_path = StripPath(path.to_string());
let res = get_full_hub_script_by_path(strip_path, &HTTP_CLIENT, Some(db))
.await
.map_err(|e| {
tracing::error!("Failed to get hub script: {}", e);
Error::internal_error(format!("Failed to get hub script: {}", e), None)
})?;
match serde_json::from_str::<Schema>(res.schema.get()) {
Ok(schema) => Ok(Some(schema)),
Err(e) => {
tracing::warn!("Failed to convert schema: {}", e);
Ok(None)
}
}
}
/// Creates a `Tool` from a `ToolableItem`.
///
/// This function takes an item that implements the `ToolableItem` trait and converts it into an RMCP `Tool`.
/// It handles both workspace scripts/flows and Hub scripts differently, depending on the item type.
///
/// # Parameters
/// - `item`: The item to convert to a `Tool`.
/// - `user_db`: The database connection.
/// - `authed`: The authenticated user.
/// - `workspace_id`: The workspace ID.
/// - `resources_cache`: A mutable reference to the resources cache.
/// - `resources_types`: A reference to the resource types.
///
/// # Returns
/// - `Ok(Tool)`: The created `Tool`.
async fn create_tool_from_item<T: ToolableItem>(
item: &T,
user_db: &UserDB,
authed: &ApiAuthed,
workspace_id: &str,
resources_cache: &mut HashMap<String, Vec<ResourceInfo>>,
resources_types: &Vec<ResourceType>,
) -> Result<Tool, Error> {
let is_hub = item.is_hub();
let path = item.get_path_or_id();
let item_type = item.item_type();
let description = format!(
"This is a {} named `{}` with the following description: `{}`.{}",
item_type,
item.get_summary(),
item.get_description(),
if is_hub {
format!(
" It is a tool used for the following app: {}",
item.get_integration_type()
.unwrap_or("No integration type".to_string())
)
} else {
"".to_string()
}
);
let schema_obj = Runner::transform_schema_for_resources(
&item.get_schema(),
user_db,
authed,
&workspace_id,
resources_cache,
&resources_types,
)
.await?;
let input_schema_map = match serde_json::to_value(schema_obj) {
Ok(Value::Object(map)) => map,
Ok(_) => {
tracing::warn!("Schema object for tool '{}' did not serialize to a JSON object, using empty schema.", path);
serde_json::Map::new()
}
Err(e) => {
tracing::error!(
"Failed to serialize schema object for tool '{}': {}. Using empty schema.",
path,
e
);
serde_json::Map::new()
}
};
Ok(Tool {
name: Cow::Owned(path),
description: Some(Cow::Owned(description)),
input_schema: Arc::new(input_schema_map),
annotations: None,
})
}
}
impl ServerHandler for Runner {
/// Handles the `CallTool` request from the MCP client.
///
/// This involves:
/// 1. Parsing arguments and extracting context (DB, Auth).
/// 2. Reversing the tool name (`request.name`) to get the original path and type using `reverse_transform`.
/// 3. Handling Hub scripts: If identified as a Hub script, searches the Hub for the actual script ID.
/// 4. Fetching the schema for the item (needed for argument transformation).
/// 5. Transforming incoming arguments:
/// - Reversing key transformations (e.g., `user_input` back to `user input`).
/// - Parsing stringified JSON objects back into JSON values based on schema type.
/// 6. Executing the corresponding script or flow using internal Windmill runners.
/// 7. Formatting the execution result into an RMCP `CallToolResult`.
///
/// # Parameters
/// - `request`: The `CallToolRequestParam` containing the tool name and arguments.
/// - `context`: The `RequestContext` providing access to workspace ID, DB connections, auth info.
///
/// # Returns
/// - `Ok(CallToolResult)`: On successful execution, containing the output.
/// - `Err(Error)`: If any step fails (parsing, DB access, execution, reversing transform, hub search).
async fn call_tool(
&self,
request: CallToolRequestParam,
@@ -870,17 +520,14 @@ impl ServerHandler for Runner {
.ok_or_else(|| Error::internal_error("UserDB not found", None))?;
let args = parse_args(request.arguments)?;
let (tool_type, path, is_hub) =
Runner::reverse_transform(&request.name).unwrap_or_default();
let (tool_type, path) = Runner::reverse_transform(&request.name).unwrap_or_default();
let item_schema = if is_hub {
Runner::get_hub_script_schema(&format!("hub/{}", path), db).await?
} else {
let item_info =
Runner::get_item_schema(&path, user_db, authed, &context.workspace_id, &tool_type)
.await?
};
.await?;
let schema_obj = if let Some(ref s) = item_schema {
let schema = item_info.schema;
let schema_obj = if let Some(ref s) = schema {
match serde_json::from_str::<SchemaType>(s.0.get()) {
Ok(val) => Some(val),
Err(e) => {
@@ -895,7 +542,7 @@ impl ServerHandler for Runner {
let push_args = if let Value::Object(map) = args.clone() {
let mut args_hash = HashMap::new();
for (k, v) in map {
// need to transform back the key without invalid characters to the original key
// need to transform back the key to the original key
let original_key = Runner::reverse_transform_key(&k, &schema_obj);
// object properties are transformed to string because some client does not support object, might change in the future
@@ -908,11 +555,7 @@ impl ServerHandler for Runner {
};
let w_id = context.workspace_id.clone();
let script_or_flow_path = if is_hub {
StripPath(format!("hub/{}", path))
} else {
StripPath(path)
};
let script_or_flow_path = StripPath(path);
let run_query = RunJobQuery::default();
let result = if tool_type == "script" {
@@ -960,31 +603,12 @@ impl ServerHandler for Runner {
}
}
/// Fetches available tools (scripts, flows, hub scripts) based on the user's scope.
///
/// - Determines scope (all, favorites, hub-specific) from auth token.
/// - Fetches relevant items (workspace scripts/flows, hub scripts) concurrently.
/// - Fetches resource type information needed for schema enrichment.
/// - Transforms each item into an RMCP `Tool` definition, including schema adjustments
/// (like resource description enrichment and object->string conversion).
///
/// # Parameters
/// - `_request`: Optional pagination parameters (currently ignored).
/// - `_context`: The `RequestContext` providing workspace ID, DB, auth.
///
/// # Returns
/// - `Ok(ListToolsResult)`: A list of `Tool` definitions. Pagination is not yet implemented.
/// - `Err(Error)`: If fetching data from DB or Hub fails.
async fn list_tools(
&self,
_request: Option<PaginatedRequestParam>,
mut _context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, Error> {
let workspace_id = _context.workspace_id.clone();
let db = _context
.req_extensions
.get::<DB>()
.ok_or_else(|| Error::internal_error("DB not found", None))?;
let user_db = _context
.req_extensions
.get::<UserDB>()
@@ -993,27 +617,11 @@ impl ServerHandler for Runner {
.req_extensions
.get::<ApiAuthed>()
.ok_or_else(|| Error::internal_error("ApiAuthed not found", None))?;
let owned_scope = authed.scopes.as_ref().and_then(|scopes| {
scopes
.iter()
.find(|scope| scope.starts_with("mcp:") && !scope.contains("hub"))
});
let hub_scope = authed
let scope = authed
.scopes
.as_ref()
.and_then(|scopes| scopes.iter().find(|scope| scope.starts_with("mcp:hub")));
let scope_type = owned_scope.map_or("all", |scope| {
let parts = scope.split(":").collect::<Vec<&str>>();
parts[1]
});
let scope_integrations = hub_scope.and_then(|scope| {
let parts = scope.split(":").collect::<Vec<&str>>();
if parts.len() == 3 {
Some(parts[2])
} else {
None
}
});
.and_then(|scopes| scopes.iter().find(|scope| scope.starts_with("mcp:")));
let scope_type = scope.map_or("all", |scope| scope.split(":").last().unwrap_or("all"));
let scripts_fn = Runner::inner_get_items::<ScriptInfo>(
user_db,
@@ -1025,62 +633,84 @@ impl ServerHandler for Runner {
let flows_fn =
Runner::inner_get_items::<FlowInfo>(user_db, authed, &workspace_id, scope_type, "flow");
let resources_types_fn = Runner::inner_get_resources_types(user_db, authed, &workspace_id);
let hub_scripts_fn = Runner::inner_get_scripts_from_hub(db, scope_integrations.as_deref());
let (scripts, flows, resources_types, hub_scripts) = if scope_integrations.is_some() {
let (scripts, flows, resources_types, hub_scripts) =
try_join!(scripts_fn, flows_fn, resources_types_fn, hub_scripts_fn)?;
(scripts, flows, resources_types, hub_scripts)
} else {
let (scripts, flows, resources_types) =
try_join!(scripts_fn, flows_fn, resources_types_fn)?;
(scripts, flows, resources_types, vec![])
};
let (scripts, flows, resources_types) =
try_join!(scripts_fn, flows_fn, resources_types_fn)?;
let mut resources_cache: HashMap<String, Vec<ResourceInfo>> = HashMap::new();
let mut tools: Vec<Tool> = Vec::new();
let mut script_tools: Vec<Tool> = Vec::with_capacity(scripts.len());
for script in scripts {
tools.push(
Runner::create_tool_from_item(
&script,
let name = Runner::transform_path(&script.path, "script").unwrap_or_default();
let description = format!(
"This is a script named `{}` with the following description: `{}`.",
script.summary.as_deref().unwrap_or("No summary"),
script.description.as_deref().unwrap_or("No description")
);
let schema_obj = if let Some(schema) = script.schema {
Runner::transform_schema_for_resources(
&schema,
user_db,
authed,
&workspace_id,
&mut resources_cache,
&resources_types,
)
.await?,
);
.await?
} else {
SchemaType::default()
};
script_tools.push(Tool {
name: Cow::Owned(name),
description: Some(Cow::Owned(description)),
input_schema: {
let value = serde_json::to_value(schema_obj).unwrap_or_default();
if let serde_json::Value::Object(map) = value {
Arc::new(map)
} else {
Arc::new(serde_json::Map::new())
}
},
annotations: None,
});
}
let mut flow_tools: Vec<Tool> = Vec::with_capacity(flows.len());
for flow in flows {
tools.push(
Runner::create_tool_from_item(
&flow,
let name = Runner::transform_path(&flow.path, "flow").unwrap_or_default();
let description = format!(
"This is a flow named `{}` with the following description: `{}`.",
flow.summary.as_deref().unwrap_or("No summary"),
flow.description.as_deref().unwrap_or("No description")
);
let schema_obj = if let Some(schema) = flow.schema {
Runner::transform_schema_for_resources(
&schema,
user_db,
authed,
&workspace_id,
&mut resources_cache,
&resources_types,
)
.await?,
);
}
for hub_script in hub_scripts {
tools.push(
Runner::create_tool_from_item(
&hub_script,
user_db,
authed,
&workspace_id,
&mut resources_cache,
&resources_types,
)
.await?,
);
.await?
} else {
SchemaType::default()
};
flow_tools.push(Tool {
name: Cow::Owned(name),
description: Some(Cow::Owned(description)),
input_schema: {
let value = serde_json::to_value(schema_obj).unwrap_or_default();
if let serde_json::Value::Object(map) = value {
Arc::new(map)
} else {
Arc::new(serde_json::Map::new())
}
},
annotations: None,
});
}
let tools = [script_tools, flow_tools].concat();
Ok(ListToolsResult { tools, next_cursor: None })
}

View File

@@ -650,13 +650,8 @@ async fn create_script_internal<'c>(
) {
Some(String::new())
} else {
ns.lock.as_ref().and_then(|e| {
if e.is_empty() {
None
} else {
Some(e.to_string())
}
})
ns.lock
.and_then(|e| if e.is_empty() { None } else { Some(e) })
};
let needs_lock_gen = lock.is_none() && codebase.is_none();
@@ -906,7 +901,6 @@ async fn create_script_internal<'c>(
let permissioned_as2 = permissioned_as.clone();
let script_path2 = script_path.clone();
let parent_path = p_path_opt.clone();
let lock = ns.lock.clone();
let deployment_message = ns.deployment_message.clone();
let content = ns.content.clone();
let language = ns.language.clone();
@@ -926,7 +920,6 @@ async fn create_script_internal<'c>(
&authed2.email,
&authed2.username,
&permissioned_as2,
lock,
)
.await
{

View File

@@ -33,8 +33,6 @@ impl<B> OnResponse<B> for MyOnResponse {
let status = response.status().as_u16();
if response.status().is_success() || response.status().is_redirection() {
tracing::info!(latency = latency, status = status, "response")
} else if response.status().as_u16() == 404 {
tracing::warn!(latency = latency, status = status, "response")
} else {
tracing::error!(latency = latency, status = status, "response")
}

View File

@@ -265,7 +265,7 @@ pub struct ScriptHistoryUpdate {
pub deployment_msg: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, sqlx::Type, Clone)]
#[derive(Serialize, Deserialize, Debug, sqlx::Type)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct Schema(pub sqlx::types::Json<Box<serde_json::value::RawValue>>);

View File

@@ -42,7 +42,7 @@ use windmill_common::{
error::{self, Result},
get_latest_hash_for_path,
scripts::ScriptLang,
worker::{exists_in_cache, save_cache, to_raw_value, write_file, Connection, DISABLE_BUNDLING},
worker::{exists_in_cache, save_cache, write_file, Connection, DISABLE_BUNDLING},
DB,
};
@@ -111,7 +111,7 @@ pub async fn gen_bun_lockfile(
let mut empty_deps = false;
if let Some(raw_deps) = raw_deps.as_ref() {
if let Some(raw_deps) = raw_deps {
gen_bunfig(job_dir).await?;
write_file(job_dir, "package.json", raw_deps.as_str())?;
} else {
@@ -201,21 +201,10 @@ pub async fn gen_bun_lockfile(
}
if export_pkg {
let mut content;
let mut content = "".to_string();
{
let mut file = File::open(format!("{job_dir}/package.json")).await?;
let mut buf = String::default();
file.read_to_string(&mut buf).await?;
if raw_deps.is_some() {
let mut json_map: HashMap<String, Box<RawValue>> = serde_json::from_str(&buf)?;
json_map.insert(
"generatedFromPackageJson".to_string(),
to_raw_value(&"true".to_string()),
);
content = serde_json::to_string_pretty(&json_map)?;
} else {
content = buf;
}
file.read_to_string(&mut content).await?;
}
if !npm_mode {
#[cfg(any(target_os = "linux", target_os = "macos"))]

View File

@@ -77,7 +77,6 @@ use crate::{
start_child_process, OccupancyMetrics,
},
handle_child::handle_child,
worker_lockfiles::LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT,
worker_utils::ping_job_status,
AuthedClient, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, INSTANCE_PYTHON_VERSION, NSJAIL_PATH,
PATH_ENV, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, PROXY_ENVS, PY_INSTALL_DIR, TZ_ENV, UV_CACHE_DIR,
@@ -2304,16 +2303,8 @@ fn split_requirements(requirements: &str) -> Vec<&str> {
/// Check requirements/lockfile to figure out python version assigned to it.
fn get_pyv_from_requirements_lines(requirements_lines: &[&str]) -> PyVersion {
// If script is deployed we can try to parse first line to get assigned version
let index = if requirements_lines.get(0).map_or(false, |line| {
line.starts_with(LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT)
}) {
1
} else {
0
};
if let Some(v) = requirements_lines
.get(index)
.get(0)
.and_then(|line| PyVersion::parse_version(*line))
{
// We have valid assigned version, we use it

View File

@@ -65,17 +65,17 @@ pub async fn update_script_dependency_map(
relative_imports: Vec<String>,
) -> error::Result<()> {
let importer_kind = "script";
let mut tx = db.begin().await?;
tx = clear_dependency_parent_path(parent_path, script_path, w_id, importer_kind, tx).await?;
tx = clear_dependency_map_for_item(script_path, w_id, importer_kind, tx, &None).await?;
if !relative_imports.is_empty() {
let mut logs = "".to_string();
logs.push_str("\n--- RELATIVE IMPORTS ---\n\n");
logs.push_str(&relative_imports.join("\n"));
let mut tx = db.begin().await?;
tx =
clear_dependency_parent_path(parent_path, script_path, w_id, importer_kind, tx).await?;
tx = clear_dependency_map_for_item(script_path, w_id, importer_kind, tx, &None).await?;
tx = add_relative_imports_to_dependency_map(
script_path,
w_id,
@@ -86,10 +86,9 @@ pub async fn update_script_dependency_map(
None,
)
.await?;
tx.commit().await?;
append_logs(job_id, w_id, logs, &db.into()).await;
}
tx.commit().await?;
Ok(())
}
@@ -382,7 +381,6 @@ pub async fn handle_dependency_job(
&job.permissioned_as_email,
&job.created_by,
&job.permissioned_as,
None,
)
.await?;
@@ -435,42 +433,18 @@ pub async fn process_relative_imports(
permissioned_as_email: &str,
created_by: &str,
permissioned_as: &str,
lock: Option<String>,
) -> error::Result<()> {
let relative_imports = extract_relative_imports(&code, script_path, script_lang);
if let Some(relative_imports) = relative_imports {
if (script_lang.is_some_and(|v| v == ScriptLang::Bun)
&& lock
.as_ref()
.is_some_and(|v| v.contains("generatedFromPackageJson")))
|| (script_lang.is_some_and(|v| v == ScriptLang::Python3)
&& lock
.as_ref()
.is_some_and(|v| v.starts_with(LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT)))
{
// if the lock file is generated from a package.json/requirements.txt, we need to clear the dependency map
// because we do not want to have dependencies be recomputed automatically. Empty relative imports passed
// to update_script_dependency_map will clear the dependency map.
update_script_dependency_map(
&job_id.unwrap_or_else(|| Uuid::nil()),
db,
w_id,
&parent_path,
script_path,
vec![],
)
.await?;
} else {
update_script_dependency_map(
&job_id.unwrap_or_else(|| Uuid::nil()),
db,
w_id,
&parent_path,
script_path,
relative_imports,
)
.await?;
}
update_script_dependency_map(
&job_id.unwrap_or_else(|| Uuid::nil()),
db,
w_id,
&parent_path,
script_path,
relative_imports,
)
.await?;
let already_visited = args
.map(|x| {
x.get("already_visited")
@@ -2076,8 +2050,6 @@ async fn ansible_dep(
serde_json::to_string(&ansible_lockfile).map_err(|e| e.into())
}
pub const LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT: &str = "# from requirements.txt";
async fn capture_dependency_job(
job_id: &Uuid,
job_language: &ScriptLang,
@@ -2146,13 +2118,6 @@ async fn capture_dependency_job(
anns,
)
.await
.map(|res| {
if raw_deps {
format!("{}\n{}", LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT, res)
} else {
res
}
})
}
}
ScriptLang::Ansible => {

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.486.1";
export const VERSION = "v1.485.3";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({

View File

@@ -63,7 +63,7 @@ export {
// }
// });
export const VERSION = "1.486.1";
export const VERSION = "1.485.3";
const command = new Command()
.name("wmill")

View File

@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.486.1",
"version": "1.485.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.486.1",
"version": "1.485.3",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.486.1",
"version": "1.485.3",
"scripts": {
"dev": "vite dev",
"build": "vite build",

View File

@@ -103,9 +103,7 @@
}}
disabled={!$enterpriseLicense || !$superadmin}
>
Save {#if !$superadmin}
<span class="text-2xs text-tertiary">superadmin only</span>
{/if}
Save {#if !$superadmin} <span class="text-2xs text-tertiary">superadmin only</span> {/if}
</Button>
<span class="text-2xs text-tertiary"

View File

@@ -293,7 +293,7 @@
tooltip="This event is triggered when the script runs successfully."
items={Object.keys($runnableComponents).filter((_id) => _id !== id)}
bind:value={
() => hiddenInlineScript.script.recomputeIds ?? [],
() => hiddenInlineScript.script.recomputeIds,
(v) => {
if ($app.hiddenInlineScripts[hiddenInlineScript.index]) {
$app.hiddenInlineScripts[hiddenInlineScript.index].recomputeIds = v

View File

@@ -4,15 +4,12 @@
import MultiSelect from '$lib/components/multiselect/MultiSelectWrapper.svelte'
import { twMerge } from 'tailwind-merge'
export let items: string[]
export let value: string[] | undefined = undefined
export let title: string
export let tooltip: string
let { items, value = $bindable(), title, tooltip } = $props<{
items: string[]
value: string[] | undefined
title: string
tooltip: string
}>()
let width = $state(0)
let width = 0
const inputWidth = 280
</script>

View File

@@ -1,41 +1,30 @@
<script lang="ts">
// @ts-ignore
import Portal from '$lib/components/Portal.svelte'
import { createFloatingActions } from 'svelte-floating-ui'
import { tick } from 'svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import MultiSelect from '$lib/components/multiselect/MultiSelect.svelte'
import DarkModeObserver from '../DarkModeObserver.svelte'
import { deepEqual } from 'fast-equals'
let {
items,
value = $bindable(),
placeholder = undefined,
target = undefined,
topPlacement = false,
allowUserOptions = undefined
} = $props<{
items: any[]
value?: string[]
placeholder?: string
target?: string | HTMLElement
topPlacement?: boolean
allowUserOptions?: boolean | 'append'
}>()
$effect.pre(() => { if (value === undefined) value = [] })
export let items: any[]
let propValue: string[] | undefined = []
export { propValue as value }
$: value = structuredClone(propValue)
export let placeholder: string | undefined = undefined
export let target: string | HTMLElement | undefined = undefined
export let topPlacement = false
export let allowUserOptions: boolean | 'append' | undefined = undefined
const [floatingRef, floatingContent] = createFloatingActions({
strategy: 'absolute',
placement: topPlacement ? 'top-start' : 'bottom-start',
middleware: [offset(5), flip(), shift()]
})
let outerDiv = $state<HTMLDivElement | undefined>(undefined)
let portalRef = $state<HTMLDivElement | undefined>(undefined)
let darkMode = $state(false)
let w = $state(0)
let open = $state(false)
let outerDiv: HTMLDivElement | undefined = undefined
let portalRef: HTMLDivElement | undefined = undefined
function moveOptionsToPortal() {
// Find ul element with class 'options' within the outerDiv
const ul = outerDiv?.querySelector('.options')
@@ -45,13 +34,17 @@
}
}
$effect(() => {
if (portalRef && outerDiv && (allowUserOptions || items?.length > 0)) {
tick().then(() => {
moveOptionsToPortal()
})
}
})
$: if (portalRef && outerDiv && (allowUserOptions || items?.length > 0)) {
tick().then(() => {
moveOptionsToPortal()
})
}
// bg-indigo-100 text-indigo-800 dark:bg-indigo-200 dark:text-indigo-900
let darkMode: boolean = false
let w = 0
let open: boolean = false
</script>
<DarkModeObserver bind:darkMode />
@@ -69,14 +62,10 @@
--sms-focus-border={'none'}
--sms-selected-bg={darkMode ? '#c7d2fe' : '#e0e7ff'}
--sms-selected-text-color={darkMode ? '#312e81' : '#3730a3'}
bind:selected={
() => [...value],
(newVal) => {
if (!deepEqual(value, newVal)) {
value = newVal
}
}
}
bind:selected={value}
on:change={() => {
propValue = value
}}
{placeholder}
options={items}
on:close={() => {
@@ -88,19 +77,15 @@
let:option
disableRemoveAll
>
<!-- needed because portal doesn't work for mouseup event en mobile -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="w-full text-sm"
role="option"
tabindex="0"
onmouseup={(e) => {
e.stopPropagation()
}}
onpointerdown={(e) => {
e.stopPropagation()
on:mouseup|stopPropagation
on:pointerdown|stopPropagation={(e) => {
let newe = new MouseEvent('mouseup')
e.target?.['parentElement']?.dispatchEvent(newe)
}}
aria-selected={value?.includes(option)}
>
{option}
</div>
@@ -108,17 +93,13 @@
</div>
<Portal {target} name="multi-select">
<div use:floatingContent class="z5000" hidden={!open}>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
bind:this={portalRef}
class="multiselect"
style={`min-width: ${w}px;`}
onclick={(e) => {
e.stopPropagation()
}}
role="listbox"
tabindex="0"
on:click|stopPropagation
></div>
</div>
</Portal>

View File

@@ -2,7 +2,7 @@
import TableCustom from '$lib/components/TableCustom.svelte'
import { displayDate, copyToClipboard } from '$lib/utils'
import type { TruncatedToken, NewToken } from '$lib/gen'
import { IntegrationService, UserService } from '$lib/gen'
import { UserService } from '$lib/gen'
import { Button } from '$lib/components/common'
import { Clipboard, Plus } from 'lucide-svelte'
import { workspaceStore, userWorkspaces, type UserWorkspace } from '$lib/stores'
@@ -12,7 +12,6 @@
import Toggle from '../Toggle.svelte'
import ClipboardPanel from '../details/ClipboardPanel.svelte'
import { sendUserToast } from '$lib/toast'
import MultiSelectWrapper from '../multiselect/MultiSelectWrapper.svelte'
// --- Props ---
interface Props {
@@ -38,14 +37,10 @@
let newToken = $state<string | undefined>(undefined)
let newTokenExpiration = $state<number | undefined>(undefined)
let newTokenWorkspace = $state<string | undefined>(defaultNewTokenWorkspace)
let newMcpApps = $state<string[]>([])
let displayCreateToken = $state(scopes != undefined)
let mcpCreationMode = $state(false)
let newMcpScope = $state('favorites')
let newMcpToken = $state<string | undefined>(undefined)
let loadingApps = $state(false)
let errorFetchApps = $state(false)
let allApps = $state<string[]>([])
function ensureCurrentWorkspaceIncluded(
workspacesList: UserWorkspace[],
@@ -72,14 +67,6 @@
listTokens()
})
$effect(() => {
if (mcpCreationMode) {
getAllApps()
} else {
newMcpApps = []
}
})
// --- Functions ---
async function createToken(mcpMode: boolean = false): Promise<void> {
try {
@@ -88,13 +75,7 @@
date = new Date(new Date().getTime() + newTokenExpiration * 1000)
}
let tokenScopes = scopes
if (mcpMode) {
tokenScopes = [`mcp:${newMcpScope}`]
if (newMcpApps.length > 0) {
tokenScopes.push(`mcp:hub:${newMcpApps.join(',')}`)
}
}
let tokenScopes = mcpMode ? [`mcp:${newMcpScope}`] : scopes
const createdToken = await UserService.createToken({
requestBody: {
@@ -126,7 +107,6 @@
newMcpToken = undefined
newToken = undefined
newTokenExpiration = undefined
newMcpApps = []
newTokenLabel = type === 'mcpUrl' ? 'MCP token' : undefined
}
@@ -161,26 +141,6 @@
tokenPage -= 1
listTokens()
}
async function getAllApps() {
if (allApps.length > 0) {
return
}
try {
loadingApps = true
allApps = (
await IntegrationService.listHubIntegrations({
kind: 'script'
})
).map((x) => x.name)
} catch (err) {
console.error('Hub is not available')
allApps = []
errorFetchApps = true
} finally {
loadingApps = false
}
}
</script>
<div class="grid grid-cols-2 pt-8 pb-1" class:pt-8={scopes == undefined}>
@@ -232,103 +192,66 @@
{#if displayCreateToken}
<div class="py-3 px-3 border rounded-md mb-6 bg-surface-secondary min-w-min">
<h3 class="pb-3 font-semibold">Add a new token</h3>
{#if showMcpMode}
<div class="mb-4 flex flex-row flex-shrink-0">
<Toggle
on:change={(e) => {
mcpCreationMode = e.detail
if (e.detail) {
newTokenLabel = 'MCP token'
newTokenExpiration = undefined
newTokenWorkspace = $workspaceStore
} else {
newTokenLabel = undefined
newTokenExpiration = undefined
newTokenWorkspace = defaultNewTokenWorkspace
}
}}
checked={mcpCreationMode}
options={{
right: 'Generate MCP URL',
rightTooltip:
'Generate a new MCP URL to make your scripts and flows available as tools through your LLM clients.',
rightDocumentationLink: 'https://www.windmill.dev/docs/core_concepts/mcp'
}}
size="xs"
/>
</div>
{/if}
{#if scopes != undefined}
<div class="mb-4">
<span class="block mb-1">Scope</span>
{#each scopes as scope}
<input disabled type="text" value={scope} class="mb-2 w-full" />
{/each}
</div>
{#each scopes as scope}
<div class="flex flex-col mb-4">
<label for="label">Scope</label>
<input disabled type="text" value={scope} />
</div>
{/each}
{/if}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{#if showMcpMode}
<Toggle
on:change={(e) => {
mcpCreationMode = e.detail
if (e.detail) {
newTokenLabel = 'MCP token'
newTokenExpiration = undefined
newTokenWorkspace = $workspaceStore
} else {
newTokenLabel = undefined
newTokenExpiration = undefined
newTokenWorkspace = defaultNewTokenWorkspace
}
}}
checked={mcpCreationMode}
options={{
right: 'Generate MCP URL',
rightTooltip:
'Generate a new MCP URL to make your scripts and flows available as tools through your LLM clients.',
rightDocumentationLink: 'https://www.windmill.dev/docs/core_concepts/mcp'
}}
class="mb-4"
size="xs"
/>
{/if}
<div class="flex flex-row flex-wrap gap-2 w-full justify-between">
{#if mcpCreationMode}
<div>
<span class="block mb-1">Scope</span>
<div class="flex flex-col">
<label for="label">Scope</label>
<ToggleButtonGroup bind:selected={newMcpScope} allowEmpty={false} let:item>
<ToggleButton
{item}
value="favorites"
label="Favorites only"
tooltip="Make only your favorite scripts and flows available as tools"
/>
<ToggleButton
{item}
value="all"
label="All scripts/flows"
tooltip="Make all your scripts and flows available as tools"
/>
<ToggleButton {item} value="favorites" label="Favorites Only" />
<ToggleButton {item} value="all" label="All Resources" />
</ToggleButtonGroup>
</div>
<div>
<span class="block mb-1">Hub scripts (optional)</span>
{#if loadingApps}
<div>Loading...</div>
{:else if errorFetchApps}
<div>Error fetching apps</div>
{:else}
<MultiSelectWrapper
items={allApps}
placeholder="Select apps"
bind:value={newMcpApps}
/>
{/if}
</div>
<div>
<span class="block mb-1">Workspace</span>
<select
bind:value={newTokenWorkspace}
disabled={workspaces.length === 1}
class="w-full"
>
<div class="flex flex-col">
<label for="label">Workspace</label>
<select bind:value={newTokenWorkspace} disabled={workspaces.length === 1}>
{#each workspaces as workspace}
<option value={workspace.id}>{workspace.name}</option>
{/each}
</select>
</div>
{/if}
<div>
<span class="block mb-1">Label <span class="text-xs text-tertiary">(optional)</span></span
>
<input type="text" bind:value={newTokenLabel} class="w-full" />
<div class="flex flex-col">
<label for="label">Label <span class="text-xs text-tertiary">(optional)</span></label>
<input type="text" bind:value={newTokenLabel} />
</div>
<div>
<span class="block mb-1"
>Expires In <span class="text-xs text-tertiary">(optional)</span></span
>
<select bind:value={newTokenExpiration} disabled={mcpCreationMode} class="w-full">
<div class="flex flex-col">
<label for="expires"
>Expires In &nbsp;<span class="text-xs text-tertiary">(optional)</span>
</label>
<select bind:value={newTokenExpiration} disabled={mcpCreationMode}>
<option value={undefined}>No expiration</option>
<option value={15 * 60}>15m</option>
<option value={30 * 60}>30m</option>
@@ -339,23 +262,15 @@
<option value={90 * 24 * 60 * 60}>90d</option>
</select>
</div>
</div>
<div class="mt-4 flex justify-end gap-2 flex-row">
<Button
on:click={() => {
mcpCreationMode = false
displayCreateToken = false
}}
>
Cancel
</Button>
<Button
on:click={() => createToken(mcpCreationMode)}
disabled={mcpCreationMode && newTokenWorkspace == undefined}
>
New token
</Button>
<div class="flex items-end">
<Button
btnClasses="!mt-2"
on:click={() => createToken(mcpCreationMode)}
disabled={mcpCreationMode && newTokenWorkspace === undefined}
>
New token
</Button>
</div>
</div>
</div>
{/if}

View File

@@ -133,7 +133,7 @@
} else {
await goto(rd ?? '/')
}
console.log('Workspace selected going to ' + (rd ? `rd: ${rd}` : 'home'))
console.log('Workspace selected, going to', rd)
} catch (e) {
console.error('Error going to', rd, e)
window.location.reload()

View File

@@ -163,7 +163,6 @@ class TestAgentWorkers(unittest.TestCase):
def test_create_agent_token(self):
token = self._agent_token
print(f"Agent token tests for token: {token}")
self.assertIsNotNone(token)
# JWT tokens have the format: jwt_agent_<prefix>_<token>

View File

@@ -415,5 +415,5 @@ class WindmillClient:
raise Exception(response.content.decode())
token = response.content.decode().strip('"')
print(f"Created agent token: {token}")
print(f"Created agent token: {token[:15]}...{token[-15:]}")
return token

View File

@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.486.1"
wmill_pg = ">=1.486.1"
wmill = ">=1.485.3"
wmill_pg = ">=1.485.3"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.486.1
version: 1.485.3
title: OpenFlow Spec
contact:
name: Ruben Fiszel

View File

@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.486.1'
ModuleVersion = '1.485.3'
# Supported PSEditions
# CompatiblePSEditions = @()

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.486.1"
version = "1.485.3"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.486.1"
version = "1.485.3"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"

View File

@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.486.1",
"version": "1.485.3",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./client.ts"]

View File

@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.486.1",
"version": "1.485.3",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {

View File

@@ -1 +1 @@
1.486.1
1.485.3