Compare commits

...

153 Commits

Author SHA1 Message Date
Ruben Fiszel
48152bc5c6 rds 2025-11-19 18:23:50 +00:00
Ruben Fiszel
4ef47d4bdf irsa 4 2025-11-19 15:26:14 +00:00
Ruben Fiszel
22cb3b0114 irsa III 2025-11-19 14:23:44 +00:00
Ruben Fiszel
95b168d665 irsa 2nd attempt 2025-11-19 13:01:24 +00:00
Ruben Fiszel
350a1e7e70 fix test 2025-11-19 11:15:31 +00:00
Ruben Fiszel
88ab5659e7 disable duckdb temporarily 2025-11-19 10:18:02 +00:00
Ruben Fiszel
4dfc8f5f91 make s3 proxy work with IRSA 2025-11-19 09:56:10 +00:00
Ruben Fiszel
83c0c54613 chore(main): release 1.580.0 (#7169)
* chore(main): release 1.580.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-19 00:12:46 +00:00
Ruben Fiszel
04ddda1b56 remove cli dead code 2025-11-18 23:14:21 +00:00
centdix
5c79a35306 feat(aichat): use responses api for openai models (#7163)
* use responses api

* way cleaner

* fix non streaming

* fix metadatagen

* fix for azure

* remove deprecated

* fix listing

* fix
2025-11-18 23:12:42 +00:00
Ruben Fiszel
58b9b9e330 ee ref 2025-11-18 23:04:50 +00:00
Alexander Petric
5aa251a2d2 feat(security): unshare pid of worker job process (#7106)
* feat(security): unbind pid for worker jobs to create extra process isolation

* review

* simplify

* cleanup + compose files

* re-add removed comments from nu executor

* simplify

* fail immediately

* updates

* update ping backend

* nsjail / unshare in workers page

* migrations

* frontend + sqlx

* frontend

* frontend

* fix error message

* undo example changes
2025-11-18 23:04:31 +00:00
hugocasa
8ae266b6a9 feat: disabling/enabling email triggers (#7171)
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-11-18 23:04:12 +00:00
hugocasa
f2dbf6d20d count external jwts for telemetry
* feat: count external jwts

* nits
2025-11-18 23:03:35 +00:00
Alexander Petric
5548221092 feat: support secondary promotion repos in git sync settings (#7173) 2025-11-18 23:02:04 +00:00
Alexander Petric
4acd5e526f fix rhel9 add rhel8 ci (#7172) 2025-11-18 15:44:23 +00:00
Ruben Fiszel
a3cf674cd0 test fix 2025-11-18 12:59:00 +00:00
Ruben Fiszel
f3e62a2e0f test fix 2025-11-18 12:29:24 +00:00
Ruben Fiszel
64a9c4f7d2 sqlx 2025-11-18 12:12:24 +00:00
Ruben Fiszel
c3e59fe064 fix: change uv tool dir from /root to /usr/local/uv 2025-11-18 09:45:45 +00:00
Ruben Fiszel
478e19379f buffer cloud hosted usage 2025-11-18 09:40:57 +00:00
Ruben Fiszel
b112c218db nit test 2025-11-18 08:44:32 +00:00
Ruben Fiszel
f371fbeb9b fix: improve delete to handle ai chat 2025-11-18 08:12:54 +00:00
Ruben Fiszel
2058f27e03 fix: support IRSA for duckdb s3 proxy 2025-11-18 01:37:18 +00:00
Ruben Fiszel
423ed04cb9 irsa attempts 2025-11-18 01:34:32 +00:00
Ruben Fiszel
aad43768d4 chore(main): release 1.579.2 (#7168)
* chore(main): release 1.579.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-18 00:58:17 +00:00
Ruben Fiszel
d08c0916f7 fix: ducklake manager table explorer issue 2025-11-18 00:48:56 +00:00
Alexander Petric
4e9c22a0bd rhel duckdb lib (#7167)
* feat: rhel8 + fix rhel9

* duckdb lib
2025-11-18 00:37:52 +00:00
Ruben Fiszel
610f90b19e chore(main): release 1.579.1 (#7166)
* chore(main): release 1.579.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-18 00:19:08 +00:00
Ruben Fiszel
09a6e1feaa fix: fix s3 object download frontend freezes 2025-11-18 00:09:54 +00:00
Ruben Fiszel
25c9223ba0 chore(main): release 1.579.0 (#7161)
* chore(main): release 1.579.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-17 23:26:59 +00:00
Alexander Petric
499d7d4098 feat: rhel8 + fix rhel9 (#7165) 2025-11-17 23:12:13 +00:00
Ruben Fiszel
9b7527c379 add tracing on email receiving 2025-11-17 23:07:02 +00:00
Ruben Fiszel
13216bc2a3 add tracing on email receiving 2025-11-17 23:00:21 +00:00
centdix
88d04b9cbe feat(mcp): granular token scopes for scripts, flows, and endpoints (#7130)
* feat(mcp): add granular scope parsing and filtering

- Add scope_matcher.rs with McpScopeConfig and parsing logic
- Support new scope format: mcp:scripts:{paths}, mcp:flows:{paths}, mcp:endpoints:{names}
- Update check_scopes() to accept any mcp:* scope
- Implement resource matching with wildcard support (f/folder/*)
- Full backward compatibility with legacy scopes (mcp:all, mcp:favorites, mcp:hub:{app})
- Add comprehensive unit tests for scope parsing and matching

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

* feat(mcp): add badge-based UI for granular scope selection

- Add 'Custom' toggle option to MCP token creation UI
- Implement clickable badge interface for selecting scripts/flows/endpoints
- Selected badges show in blue, unselected in gray with opacity
- Automatically load all workspace scripts/flows/endpoints in custom mode
- Generate granular scopes: mcp:scripts:{paths}, mcp:flows:{paths}, mcp:endpoints:{names}
- Validate at least one resource is selected before token creation
- Display selection count for user feedback

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

* docs(openapi): document granular MCP scope format

- Add comprehensive documentation for MCP scopes in NewToken schema
- Document new granular format: mcp:scripts:{paths}, mcp:flows:{paths}, mcp:endpoints:{names}
- Provide usage examples for common scenarios
- Mark legacy scopes (mcp:all, mcp:favorites) as deprecated but supported
- Include wildcard pattern examples (f/folder/*)

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

* fix: add type annotation for None in scope_path tuple

Fix compilation error where type parameter T could not be inferred for Option.
Changed None to None::<&str> to explicitly specify the type.

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

* better ui

* cleaning

* fix back comp

* fix

* fix

* cleaning

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: centdix <centdix@users.noreply.github.com>
2025-11-17 22:33:16 +00:00
hugocasa
b87d2cc64c fix(backend): worker count in latest worker usage (#7160) 2025-11-17 22:33:02 +00:00
Ruben Fiszel
90b5569c91 fix: fix custom email triggers enabled (#7164) 2025-11-17 22:26:47 +00:00
centdix
abcfb2b3b8 fix breaking error (#7162) 2025-11-17 21:51:29 +01:00
centdix
79ac6312e8 feat(ai): handle aws bedrock as provider (#7155)
* backend draft

* fix for tool and streaming

* do frontend side

* working

* working tools

* rm

* handle list endpoint

* handle for ai agents

* fix for models requiring inference id

* cleaning

* fix desc issue

* fix tool usage

* fix structured output

* cleaning

* fix for api

* rm

* fix input images

* cleaning

* chore: use aws sdk (#7156)

* feat(ai): Add AWS SDK dependencies for Bedrock integration

- Add aws-sdk-bedrockruntime v1.113.0
- Add aws-credential-types for bearer token authentication
- Update rustls to v0.23.35 for compatibility
- Dependencies added to windmill-common for AI features

* feat(ai): Add bearer token provider for Bedrock authentication

- Implement BearerTokenProvider using aws_credential_types
- Simple token-based auth using API keys from Windmill resources
- Add basic unit tests for provider creation
- Export bedrock_auth module in lib.rs

* feat(ai): Add Bedrock client wrapper with region extraction

- Implement BedrockClient wrapper around AWS SDK client
- Bearer token authentication integration
- Extract AWS region from Bedrock base URL automatically
- Comprehensive unit tests for region extraction
- Make aws-config non-optional dependency for AI features
- Update feature flags to reflect new dependency structure

* cargo

* feat(ai): Implement non-streaming Bedrock via AWS SDK

Use official AWS SDK instead of manual HTTP requests for better type safety and maintainability. Implements the Bedrock converse() API for non-streaming requests with proper bearer token authentication and message format conversion between OpenAI and Bedrock formats.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(ai): Eliminate Simple* conversion types for Bedrock SDK

- Move AI types to windmill-common/src/ai_types.rs for shared access
- Update bedrock_converters to work directly with OpenAI types
- Remove ~200 lines of conversion boilerplate from ai_executor.rs and bedrock.rs
- Remove unused imports to clean compilation warnings
- Benefits: 50% fewer conversion steps, no information loss, easier maintenance

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

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(ai): Add streaming support for AWS Bedrock SDK

- Implement converse_stream() for Bedrock streaming responses
- Use EventReceiver.recv() to process stream events
- Extract text deltas using bedrock_stream_event_to_text()
- Send TokenDelta events to StreamEventProcessor for real-time updates
- Refactor request building to eliminate duplication between streaming and non-streaming
- Clean, minimal implementation following AWS SDK patterns

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

Co-Authored-By: Claude <noreply@anthropic.com>

* revert flake change

* fix

* feat(ai): Add tool calls and image support for Bedrock streaming

**Phase 1: Streaming Tool Call Support**
- Add stream event processing functions in bedrock_converters.rs:
  - bedrock_stream_event_to_tool_start() - Extract tool use start from ContentBlockStart
  - bedrock_stream_event_to_tool_delta() - Extract tool input deltas from ContentBlockDelta
  - bedrock_stream_event_is_block_stop() - Detect ContentBlockStop events
  - streaming_tool_calls_to_openai() - Convert accumulated tool calls to OpenAI format
- Update ai_executor.rs streaming loop with tool call accumulator (HashMap)
- Track current tool use ID during streaming
- Send ToolCallArguments events to StreamEventProcessor
- Return accumulated tool calls instead of empty vector

**Phase 2: Image Input Support**
- Add parse_image_data_url() to extract format and base64 data from data URLs
- Add content_part_to_block() to convert ContentPart to Bedrock ContentBlock
- Refactor convert_message() to handle multi-part content with images
- Support ImageUrl conversion to Bedrock ImageBlock with proper format (png/jpeg/gif/webp)
- Import AWS SDK image types: ImageBlock, ImageSource, ImageFormat
- Keep content_to_text() helper for system message text extraction

**Benefits**:
-  Tool calling now works in both streaming and non-streaming modes
-  Images are properly converted instead of being silently dropped
-  Structured output works in streaming (uses tool calling)
-  Full feature parity with manual HTTP implementation

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

Co-Authored-By: Claude <noreply@anthropic.com>

* cleaning

* fix(ai): Add S3 image support and structured output for Bedrock

**Fixes:**
1. **S3 Image Support**: Call prepare_messages_for_api() before Bedrock SDK path to convert S3Objects to ImageUrls
   - Downloads images from S3 and encodes as base64 data URLs
   - Ensures images are properly handled in both streaming and non-streaming modes

2. **Structured Output**: Add ToolChoice::Any when structured output tool is present
   - Forces Bedrock to call the structured_output tool
   - Ensures JSON schema compliance for structured output
   - Works in both streaming and non-streaming modes

**Changes:**
- ai_executor.rs: Call prepare_messages_for_api() for Bedrock SDK path
- ai_executor.rs: Set tool_choice to Any when structured_output_tool_name is present
- aws_bedrock.rs: Remove unused ToolChoice imports (used via full path in worker)

**Testing:**
-  S3 images are now downloaded and converted before API call
-  Structured output now forces tool usage with ToolChoice::Any
-  Both work in streaming and non-streaming modes

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

Co-Authored-By: Claude <noreply@anthropic.com>

* cleaning

* cleaning

* cleaning

* better error

* cleaning

* cleaning

* rm

* rename

* apply region

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix default

* no panic

* no print

* use utils file

* cleaning

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-17 18:57:59 +00:00
Ruben Fiszel
da4f57ae59 chore(main): release 1.578.0 (#7159)
* chore(main): release 1.578.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-17 11:54:32 +00:00
Alexander Petric
ceeff5f76c feat: support to run windows binary as service (#7153)
* feat: support to run windows binary as service

* ee ref

* ee ref

* flags

* Update backend/src/main.rs

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* fix ai commit

* fix

* ee ref

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-11-17 11:48:31 +00:00
Ruben Fiszel
9add877047 chore(main): release 1.577.0 (#7152)
* chore(main): release 1.577.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-17 11:34:02 +00:00
hugocasa
b66e038a0f feat: add support for validateset in pwsh (#7158) 2025-11-17 11:29:58 +00:00
Diego Imbert
2cae72c9db fix: fix parse_postgres_uri not decoding password (#7157)
* nit overscroll auto

* decode password in parse_postgres_url

* don't encode host in pg_executor

* nit
2025-11-17 11:29:44 +00:00
Ruben Fiszel
767940c6fa avoid utf8 migration encoding 2025-11-17 10:47:31 +00:00
dieriba
09082de539 feat: allow http trigger to be disabled (#6976)
* done

* update sqlx

* fix

* fix openapi spec

* fix

* Update backend/windmill-api/openapi.yaml

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* nits

* nits

* fix

* fix

* update sqlx

* unneeded

* ok

* update sqlx

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-11-17 08:51:35 +00:00
Ruben Fiszel
65a40b6df1 sqlx 2025-11-16 12:19:52 +00:00
Ruben Fiszel
cf5d58ea43 fix: create app_themes/groups/components only when needed 2025-11-16 12:09:33 +00:00
Ruben Fiszel
e1d659b7d2 chore(main): release 1.576.3 (#7150)
* chore(main): release 1.576.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-15 21:07:56 +00:00
Ruben Fiszel
d062d5e390 duckdb nits 2025-11-15 21:06:19 +00:00
Ruben Fiszel
2c04e04bf0 fix: handle better alias types in duckdb 2025-11-15 20:42:05 +00:00
Ruben Fiszel
f1029d0f14 s3 endpoints improvements 2025-11-15 19:07:13 +00:00
Ruben Fiszel
7215aa9712 chore(main): release 1.576.2 (#7149)
* chore(main): release 1.576.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-15 18:17:08 +00:00
Diego Imbert
6426ebf8cb fix: temporary fix for duckdb type_aliases causing issues (#7148) 2025-11-15 18:10:05 +00:00
Diego Imbert
fa1bc3c711 DuckDB test to ensure FFI doesn't crash on simple query (#7147)
* test_duckdb_ffi

* build dev duckdb lib

* cache
2025-11-15 11:56:45 +00:00
Ruben Fiszel
5c893becfd chore(main): release 1.576.1 (#7146)
* chore(main): release 1.576.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-14 22:19:58 +00:00
Diego Imbert
d3fc459b40 fix: DuckDB FFI crash fix (#7145) 2025-11-14 22:14:28 +00:00
Ruben Fiszel
9925932a28 chore(main): release 1.576.0 (#7140)
* chore(main): release 1.576.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-14 18:02:30 +00:00
hugocasa
c16bef8f29 feat: add support for switch and attributes in pwsh params (#7143) 2025-11-14 17:28:54 +00:00
Ruben Fiszel
d50b67f1c8 whitelist apps_u 2025-11-14 14:51:50 +00:00
dieriba
09cdfb4556 feat: webhook by flow version (#7062)
* ok

* done

* update sqlx and repo ref

* fix
2025-11-14 14:36:24 +00:00
centdix
3dd293e450 Revert "feat(ai): handle aws bedrock as provider (#7131)" (#7141)
This reverts commit 30eb9aae25.
2025-11-14 12:31:16 +00:00
centdix
30eb9aae25 feat(ai): handle aws bedrock as provider (#7131)
* backend draft

* fix for tool and streaming

* do frontend side

* working

* working tools

* rm

* handle list endpoint

* handle for ai agents

* fix for models requiring inference id

* cleaning

* fix desc issue

* fix tool usage

* fix structured output

* cleaning

* fix for api

* rm

* fix input images
2025-11-14 12:22:51 +00:00
Diego Imbert
cf36fe3bb1 fix: use proper TLS connector for DuckLake instance catalog setup (#7138)
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-11-14 13:01:43 +01:00
Ruben Fiszel
d578a0ca80 nit 2025-11-14 11:50:29 +00:00
Ruben Fiszel
137adbf813 nit 2025-11-14 11:32:38 +00:00
Ruben Fiszel
0c9d268e2f nit 2025-11-14 11:28:17 +00:00
Ruben Fiszel
18468e593b nit 2025-11-14 11:22:45 +00:00
Ruben Fiszel
01e99467e4 nit 2025-11-14 11:19:46 +00:00
Ruben Fiszel
05c875c613 nit 2025-11-14 11:07:42 +00:00
Ruben Fiszel
ec4998d866 nits license ee check 2025-11-14 10:41:03 +00:00
Ruben Fiszel
03f9702690 nits 2025-11-14 10:16:24 +00:00
Ruben Fiszel
0423eeedbe handle better public domain for apps (#7136)
* cookelogin

* cookelogin

* all

* all

* fix

* all

* all

* update back

* all

* all

* cookelogin

* cookelogin

* Update frontend/src/lib/components/apps/editor/PublicApp.svelte

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* Update frontend/src/lib/components/apps/editor/PublicApp.svelte

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* all

* all

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-11-14 10:14:32 +00:00
Ruben Fiszel
c86a080cd6 chore(main): release 1.575.4 (#7135)
* chore(main): release 1.575.4

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-13 16:43:32 +00:00
Ruben Fiszel
d4340b83ac fix: fix cancel job selection 2025-11-13 16:39:08 +00:00
Ruben Fiszel
f6c77cf4b3 chore(main): release 1.575.3 (#7134)
* chore(main): release 1.575.3

* update versions
2025-11-13 15:02:20 +00:00
Ruben Fiszel
fbab86cc43 fix: fix cancel job selection 2025-11-13 14:49:52 +00:00
Ruben Fiszel
10fe8883d2 chore(main): release 1.575.2 (#7132)
* chore(main): release 1.575.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-13 14:04:01 +00:00
Ruben Fiszel
1c0f80dc55 fix: fix cancel job selection 2025-11-13 13:59:26 +00:00
Ruben Fiszel
850e87184e chore(main): release 1.575.1 (#7127)
* chore(main): release 1.575.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-13 12:35:19 +00:00
Ruben Fiszel
839e092aba nit check 2025-11-13 12:34:53 +00:00
Ruben Fiszel
2ee17aea8b fix: handle string in input transform strings 2025-11-13 12:26:50 +00:00
Diego Imbert
0639623af6 Trigger kind filter (#7123)
* Pass JobTriggerKind to push() to store it in v2_job

* fix compiltion

* frontend job trigger kind filter

* SQL trigger_kind filtering

* fix animated button

* ee repo ref

* Fix "Show schedule" + trigger kind = schedule

* duplicate email

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-11-13 10:50:04 +00:00
Diego Imbert
6fac896390 fix: send alternatives on timeout toast (#6920)
* Fix broken batch reruns InputTransformForm

* send alternatives on timeout toast

* Toast when runs page query is slow + throttle toasts spamming

* configurable perPage in runs page

* store perPage in query params

* subtle border

* nit fix

* reduce to 25 actions on runs page

* Fix annoying scrollbar due to AIChatLayout

* nit animated pane

* don't kill AI Chat Manager on pane close

* button shrink 0

* nist

* Cancelable Promise Utils

* migrate to CancelablePromiseUtils

* CancelablePromise onTimeout + update usage

* CancelablePromise onTimeout + update usage

* Loading spinner + fix per page bug in audit logs

* Fix .cancel() not behaving as expected

* fix nits

* audit logs nits

* auditlog filter fix selects

* fix wrong number of jobs when switching perPage

* default 1000

* Fix breaking merge conflict

* Fix missing computeCompletedJobs

* change audit logs default perPage to 100
2025-11-13 09:51:15 +00:00
Diego Imbert
77316cbd0e fix: Fix flow buttons clipped in Safari (#7126) 2025-11-12 21:04:55 +01:00
Ruben Fiszel
5e7a94cab3 chore(main): release 1.575.0 (#7121)
* chore(main): release 1.575.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-12 17:46:28 +00:00
Ruben Fiszel
14e41babfe feat: SQL result_collection flag (#7041) 2025-11-12 17:36:01 +00:00
centdix
9791eb4568 fix(flow chat): fix chat in edit mode + cleaner code (#7118)
* handle conversation for preview endpoints

* rm

* way better chat logic

* remove old logic

* no streaming in flow input

* pass conv id to preview func

* max width on input

* add info

* cleaning

* nits

* nits

* use streaming in preview
2025-11-12 16:39:48 +00:00
Alexander Petric
1280617a28 fix: allow --yes in fork command (#7122) 2025-11-12 16:32:39 +00:00
Diego Imbert
c079155e3e SQL result_collection flag (#7041)
* Return array of results duckdb

* Migration script to add result_collection=legacy

* migration script fixes

* app_version_lite not necessary

* New annotations macro that supports custom types

* pass unit tests

* nit style

* result_collection almost works for postgres

* fix last_statement

* frontend suggesitons

* fix column_order making columns disappear

* added version check for duckdb FFI lib to avoid crashes when changing FFI interface on Windows agent workers

* result_collection for duckdb

* Correct legacy behavior in DuckDB

* mysql result_collection

* mssql collection_strategy

* result_collection for oracle

* snowflake result_collection

* fix errors

* mistake, .clone() causing deadlock

* fix frontend errors on oracle / mssql

* fix legacy behavior on mssql

* add exception catch in migration

* missing app_script update

* change cache layout to avoid worker pulling old scripts
2025-11-12 15:32:07 +00:00
hugocasa
7a8fd865e2 fix(backend): scim email filter (#7120) 2025-11-12 15:22:46 +00:00
Ruben Fiszel
1001643874 nit free quotas 2025-11-12 14:36:18 +00:00
Diego Imbert
9e5a69c9e9 nit tw classes (#7119) 2025-11-12 13:39:32 +00:00
wendrul
806a168e18 Add link to job + update git sync script tip if applicable on fork fail (#7117)
* Add link to job + update git sync script tip if applicable on fork fail

* Format
2025-11-12 12:32:36 +00:00
Diego Imbert
d31b1800be Fix animated button opacity nit (#7114) 2025-11-12 10:20:39 +00:00
Ruben Fiszel
d965ccdbc1 use cookie domain for csrf 2025-11-11 23:13:02 +00:00
Ruben Fiszel
5b26be6ab4 nit public app 2025-11-11 22:25:59 +00:00
Ruben Fiszel
e77574b243 more endpoints for public apps 2025-11-11 21:50:12 +00:00
Ruben Fiszel
ee0e0a214e allow custom paths on public domain 2025-11-11 21:01:58 +00:00
Ruben Fiszel
83fec5fd4f chore(main): release 1.574.3 (#7111)
* chore(main): release 1.574.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-11 20:56:14 +00:00
Ruben Fiszel
957a18318a public domain nit 2025-11-11 20:52:42 +00:00
Ruben Fiszel
ed7878c186 public domain nit 2025-11-11 20:01:52 +00:00
Ruben Fiszel
c5251a8c33 implement PUBLIC_APP_DOMAIN v1 2025-11-11 19:28:00 +00:00
Ruben Fiszel
931e3d3c90 implement PUBLIC_APP_DOMAIN v1 2025-11-11 19:27:57 +00:00
Alexander Petric
25c36c1e0a fix: use teams internal_id rather than id (#7113)
* fix: use teams internal_id rather than id

* npm check

* ee ref
2025-11-11 19:08:10 +00:00
Stephan Fitzpatrick
ea5b3e6b92 fix(backend): add sslmode parameter to DuckLake PostgreSQL connections 2025-11-11 08:56:53 +00:00
Ruben Fiszel
4e4c9e31fd chore(main): release 1.574.2 (#7097)
* chore(main): release 1.574.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-10 15:40:22 +00:00
Pyra
a76713d556 nit: use /usr/bin/env bash for better compat (#7099)
Signed-off-by: pyranota <pyra@duck.com>
2025-11-10 15:40:07 +00:00
Ruben Fiszel
d18c48ac99 ee-repo-ref 2025-11-10 15:39:25 +00:00
hugocasa
51cba95d39 fix(backend): improve pwsh param block parsing (#7096)
* fix(backend): replace regex with one-pass algorithm for PowerShell param parsing

The previous regex-based approach used  which would
fail when encountering nested parentheses inside the param block, such as
 or .

This commit replaces the regex with a proper one-pass parser that:
- Tracks parenthesis depth to correctly handle nesting
- Respects string quotes (both single and double quotes)
- Handles PowerShell's backtick escape character
- Works correctly with complex default values and function calls

Changes:
- Removed RE_POWERSHELL_PARAM regex constant
- Added extract_powershell_param_block() function for extracting param contents
- Added extract_powershell_param_block_full() function for extracting full param block
- Updated parse_powershell_file() in windmill-parser-bash
- Updated pwsh_executor.rs to use the new extractor
- Added comprehensive test cases for nested parens and quoted strings

Fixes #7079

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

* refactor(backend): combine PowerShell param extraction functions

Merged extract_powershell_param_block() and extract_powershell_param_block_full()
into a single function with a boolean parameter to control output format:
- include_keyword=false returns just contents between parentheses
- include_keyword=true returns full param(...) block

This eliminates code duplication while maintaining all existing functionality.

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

* fix(backend): improve pwsh param block parsing

* chore: publish parser

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: hugocasa <hugocasa@users.noreply.github.com>
2025-11-10 15:08:20 +00:00
hugocasa
ce8d5660fe continue streaming on malformed sse payload (#7102)
* fix(backend): continue streaming on malformed sse payload

* nit
2025-11-10 15:08:01 +00:00
Ruben Fiszel
26d17141d1 ee-repo-ref 2025-11-10 14:32:39 +00:00
hugocasa
2e245accc5 fix(backend): improve sse parsing (#7100) 2025-11-10 14:32:16 +00:00
Pyra
84992cd8ff fix: proper error handling in pulled job preprocessor (#7098)
* fix: proper error handling in pulled job preprocessor

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

* follow up for merge

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

* make it safe

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

* clippy

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

* remove unused import

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

* use String instead of Value

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

* update ee ref

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

* implement Error for PulledJobResultToErr

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

* updatesqlx

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

---------

Signed-off-by: pyranota <pyra@duck.com>
2025-11-10 13:43:50 +00:00
dieriba
fc5034e94d fix: preproccessor ui and expanding preprocessor support (#6872)
* preproccessor php

* fix

* ok

* remove folder

* chore: publish parser

---------

Co-authored-by: HugoCasa <hugo@casademont.ch>
2025-11-10 11:22:31 +00:00
Ruben Fiszel
e047c3b2b1 add require non-empty array 2025-11-10 09:51:30 +00:00
Ruben Fiszel
3dcad57481 add debug_sse_stream 2025-11-09 10:53:37 +00:00
Ruben Fiszel
41a6f89bdb not require crypto for ai chat 2025-11-08 20:14:07 +00:00
Ruben Fiszel
2d54dfbf05 fix: make ai chat works with unicode messages 2025-11-08 19:49:47 +00:00
Ruben Fiszel
9e4882c0a9 fix: fix multiselect in list for apps 2025-11-08 19:05:23 +00:00
Ruben Fiszel
8b2291b0f9 chore(main): release 1.574.1 (#7092)
* chore(main): release 1.574.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-08 10:28:55 +00:00
Ruben Fiszel
f12be4eb19 fix direct access 2025-11-08 10:26:48 +00:00
Ruben Fiszel
d6421c2ea7 fix: make get_logs work even for partial flow jobs 2025-11-08 00:20:49 +00:00
Ruben Fiszel
b5c21cfe56 chore(main): release 1.574.0 (#7088)
* chore(main): release 1.574.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-07 23:27:45 +00:00
Ruben Fiszel
62ffe9ffce fix(cli): add automatic handler of .node files for codebase bundler 2025-11-07 23:10:26 +00:00
Alexander Petric
408911dbf6 camelcase (#7091) 2025-11-07 18:51:33 +00:00
dieriba
3a657b10e7 nit flow env (#7090) 2025-11-07 18:51:22 +00:00
dieriba
c59183f5c3 feat: env var in flow (#6852) 2025-11-07 18:38:55 +00:00
Alexander Petric
6045f0c406 fix: teams selector svelte5 (#7087) 2025-11-07 18:21:55 +00:00
Ruben Fiszel
180b1523f3 chore(main): release 1.573.5 (#7082)
* chore(main): release 1.573.5

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-07 17:55:07 +00:00
centdix
e1fc963990 remove breaking prop (#7086)
* remove breaking prop

* cleaning

* fix weirdbroken multiselect

---------

Co-authored-by: Diego Imbert <diego@windmill.dev>
2025-11-07 17:49:42 +00:00
Diego Imbert
b07c35fe1c fix: modal action in App AgGrid (#7085)
* Modal action in App AgGrid

* nit
2025-11-07 17:27:06 +00:00
Alexander Petric
9063945161 trim whitespaces from license key input field (#7084)
- Add onBlur prop support to Password component
- Trim license key on blur in InstanceSetting component
- Trim license key before saving in InstanceSettings component

This ensures leading and trailing whitespace is always removed
from the license key input field, both when the user leaves the
field and when settings are saved.

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2025-11-07 16:22:13 +00:00
centdix
49524d5b28 chore(aiagent): store memory in db (#7053)
* store to db

* no warning in context

* warning in frontend

* remove log

* 100kb + alert

* update sqlx

* update eeref

* Update ee-repo-ref.txt

* cleaning

---------

Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2025-11-07 15:27:00 +00:00
Ruben Fiszel
ad43680bb9 fix: dep jobs improvements (#7081)
* dep jobs improvements

* update
2025-11-07 11:04:53 +00:00
centdix
10e621cffe nits (#7080) 2025-11-07 11:01:20 +00:00
Ruben Fiszel
5ef901152f chore(main): release 1.573.4 (#7075)
* chore(main): release 1.573.4

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-07 08:09:26 +00:00
Ruben Fiszel
ad861f5312 formalize ai input transform (#7078)
* ai input transform

* fix
2025-11-07 08:03:44 +00:00
Ruben Fiszel
5f7e0b7244 fix: improve priority for child jobs ran from api 2025-11-06 19:08:59 +00:00
Ruben Fiszel
3bda6da168 fix: make it impossible to have non linear script history even in edge condition 2025-11-06 18:52:43 +00:00
Ruben Fiszel
9e3ea76575 fix(bun): support relative imports without .ts but starting with /u/ or /f/ 2025-11-06 18:42:57 +00:00
wendrul
db045e7d29 fix git sync related build error (#7076) 2025-11-06 18:06:05 +00:00
wendrul
192fecc86f fix: create git branch right before creating the workspace fork to catch errors and have a coherent fork point (#7073)
* Workspace forks: add endpoint to create a branch before creating a fork

* Update hubPaths + create branch before creating fork on frontend

* Update tmp ee-repo-ref

* Remove debug hubPath

* Prepare sqlx

* Fix ee imports

* Update ee-ref

* Update ee-repo-ref final

* Prepare sqlx
2025-11-06 16:45:34 +00:00
Ruben Fiszel
9d1f9bd39b chore(main): release 1.573.3 (#7071)
* chore(main): release 1.573.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-06 13:56:59 +00:00
Ruben Fiszel
f16231d7c9 fix: job streaming improvement after compaction 2025-11-06 13:50:52 +00:00
Ruben Fiszel
b0a20a4c00 chore(main): release 1.573.2 (#7066)
* chore(main): release 1.573.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-11-06 13:13:37 +00:00
Diego Imbert
08607509d6 Frontend nit fixes (#7069)
* fix misaligned "Create from template" button

* fix Drawer actions shrink / overflow
2025-11-06 13:08:53 +00:00
Ruben Fiszel
75e056bbce fix: oidc token issue 2025-11-06 13:08:19 +00:00
Ruben Fiszel
4c13819434 fix oidc token issue 2025-11-06 13:05:55 +00:00
Ruben Fiszel
b83eeefd66 logs improvements 2025-11-06 13:04:00 +00:00
Ruben Fiszel
af6e919765 avoid writing all oneOf options for apps in apps to lighten them 2025-11-05 23:02:42 +00:00
Ruben Fiszel
3f0a4e0e64 nits 2025-11-05 22:23:47 +00:00
Alexander Petric
8cb8650460 fix: authentik frontend baseurl field empty when loaded from db (#7065) 2025-11-05 17:53:46 +00:00
351 changed files with 13752 additions and 5766 deletions

View File

@@ -67,13 +67,23 @@ jobs:
- name: Substitute EE code (EE logic is behind feature flag)
run: |
./substitute_ee_code.sh --copy --dir ./windmill-ee-private
- name: Cache DuckDB FFI module build
uses: actions/cache@v3
with:
path: ./backend/windmill-duckdb-ffi-internal/target
key: ${{ runner.os }}-duckdb-ffi-${{ hashFiles('./backend/windmill-duckdb-ffi-internal/src/**/*.rs', './backend/windmill-duckdb-ffi-internal/Cargo.toml', './backend/windmill-duckdb-ffi-internal/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-duckdb-ffi-
- name: cargo test
timeout-minutes: 16
run: deno --version && bun -v && go version && python3 --version &&
SQLX_OFFLINE=true
DATABASE_URL=postgres://postgres:changeme@localhost:5432/windmill
DISABLE_EMBEDDING=true RUST_LOG=info RUST_LOG_STYLE=never
DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go)
UV_PATH=$(which uv) cargo test --features
enterprise,deno_core,license,python,rust,scoped_cache,private --all --
--nocapture
env:
SQLX_OFFLINE: true
DATABASE_URL: postgres://postgres:changeme@localhost:5432/windmill
DISABLE_EMBEDDING: true
RUST_LOG: info
RUST_LOG_STYLE: never
CARGO_NET_GIT_FETCH_WITH_CLI: true
run: |
deno --version && bun -v && go version && python3 --version
cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd ..
DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go) UV_PATH=$(which uv) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,private --all -- --nocapture

View File

@@ -0,0 +1,140 @@
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
name: Build and publish windmill for RHEL8
on: workflow_dispatch
permissions: write-all
jobs:
build_ee:
runs-on: ubicloud
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Read EE repo commit hash
run: |
echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_ENV"
- uses: actions/checkout@v4
with:
repository: windmill-labs/windmill-ee-private
path: ./windmill-ee-private
ref: ${{ env.ee_repo_ref }}
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
fetch-depth: 0
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v2
- uses: depot/setup-action@v1
- name: Docker meta
id: meta-ee-public
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-rhel8
flavor: |
latest=false
tags: |
type=sha
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Substitute EE code
run: |
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
- name: Copy RHEL8 Dockerfile
run: |
cp ./docker/RHEL8/Dockerfile ./Dockerfile
- name: Build and push publicly ee amd64
uses: depot/build-push-action@v1
with:
context: .
platforms: linux/amd64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
tags: |
${{ steps.meta-ee-public.outputs.tags }}-amd64
labels: |
${{ steps.meta-ee-public.outputs.labels }}-amd64
org.opencontainers.image.licenses=Windmill-Enterprise-License
- name: Build and push publicly ee arm64
uses: depot/build-push-action@v1
with:
context: .
platforms: linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
tags: |
${{ steps.meta-ee-public.outputs.tags }}-arm64
labels: |
${{ steps.meta-ee-public.outputs.labels }}-arm64
org.opencontainers.image.licenses=Windmill-Enterprise-License
- uses: shrink/actions-docker-extract@v3
id: extract-ee-amd64
with:
image: ${{ steps.meta-ee-public.outputs.tags}}-amd64
path: "/windmill/target/release/windmill"
- uses: shrink/actions-docker-extract@v3
id: extract-duckdb-ffi-internal
with:
image: ${{ steps.meta-ee-public.outputs.tags}}-amd64
path: "/usr/src/app/libwindmill_duckdb_ffi_internal.so"
# - uses: shrink/actions-docker-extract@v3
# id: extract-ee-arm64
# with:
# image: ${{ steps.meta-ee-public.outputs.tags}}-arm64
# path: "/windmill/target/release/windmill"
- name: Rename binary with corresponding architecture
run: |
mv "${{ steps.extract-ee-amd64.outputs.destination }}/windmill" "${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel8"
# mv "${{ steps.extract-ee-arm64.outputs.destination }}/windmill" "${{ steps.extract-ee-arm64.outputs.destination }}/windmill-ee-arm64-rhel8"
- uses: actions/upload-artifact@v4
with:
name: RHEL8-amd64 build
path: ${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel8
- uses: actions/upload-artifact@v4
with:
name: RHEL8-amd64 dynamic libraries build
path: ${{ steps.extract-duckdb-ffi-internal.outputs.destination }}/libwindmill_duckdb_ffi_internal.so
# - uses: actions/upload-artifact@v4
# with:
# name: RHEL8-arm64 build
# path:
# ${{ steps.extract-ee-arm64.outputs.destination
# }}/windmill-ee-arm64-rhel8
# - name: Attach binary to release
# uses: softprops/action-gh-release@v2
# if: startsWith(github.ref, 'refs/tags/')
# with:
# files: |
# ${{ steps.extract-ee-arm64.outputs.destination }}/windmill-ee-arm64-rhel8
# ${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel8

View File

@@ -40,11 +40,12 @@ jobs:
with:
github-token: ${{ steps.app.outputs.token }}
script: |
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'Starting sqlx update...'
body: `Starting sqlx update...\n\n[View workflow run](${runUrl})`
})
- name: Checkout repository

View File

@@ -49,14 +49,14 @@ jobs:
- **Bug Fixes**: PRs with titles starting with "fix:", "bug:", or containing "fix", "resolve", "patch"
- **Other**: All remaining PRs (improvements, refactors, docs, chores, etc.)
4. **Gather Details**: For each merged PR, include:
4. **Gather Details**: For each feature and bug fix merged PR, include:
- Full PR title (NO truncation, NO links)
- Author (extract login from author.login in JSON)
- Brief summary: Use `gh pr view <number> --json body` to get PR description, then extract first paragraph or key points (1-2 sentences max)
5. **Character Limit Enforcement**:
- The final summary MUST be under 6000 characters
- If the summary exceeds 6000 characters, truncate PR descriptions (NOT titles) and add at the end: "_and X more PRs_" where X is the count of omitted PRs
- The final summary MUST be under 5000 characters
- If the summary exceeds 5000 characters, truncate PR descriptions (NOT titles) and add at the end: "_and X more PRs_" where X is the count of omitted PRs
6. **Save Summary to Markdown File**: Write the summary to a file for webhook delivery:
- Save the complete formatted markdown to: `summary.md`
@@ -65,23 +65,19 @@ jobs:
## Output Format:
```markdown
#### 📈 Weekly overview
### 📈 Weekly overview
- **Total merged**: X
- **Features**: Y
- **Bug Fixes**: Z
- **Other**: W
#### ✨ Features (Y)
**[Full PR Title]** by @username - [brief impact description]
**[Full PR Title]** by @username - [brief impact description]
### ✨ Features (Y)
- **[Full PR Title]** by @username - [brief impact description]
- **[Full PR Title]** by @username - [brief impact description]
#### 🐛 Bug Fixes (Z)
**[Full PR Title]** by @username - [brief impact description]
**[Full PR Title]** by @username - [brief impact description]
#### 🔧 Other (W)
• **[Full PR Title]** by @username - [brief impact description]
• **[Full PR Title]** by @username - [brief impact description]
### 🐛 Bug Fixes (Z)
- **[Full PR Title]** by @username - [brief impact description]
- **[Full PR Title]** by @username - [brief impact description]
_and X more PRs_
```
@@ -89,9 +85,9 @@ jobs:
## Important Notes:
- **CRITICAL**: ONLY include PRs with state "merged" from the last 7 days
- **CRITICAL**: EXCLUDE all PRs with titles starting with "chore: release" or "chore(release)"
- **CRITICAL**: Total character count MUST be under 6000 characters
- Only use #### markdown headers for major sections and emoji indicators
- Use bullet points (•) for individual PR entries - more compact than paragraphs
- **CRITICAL**: Total character count MUST be under 5000 characters
- Count the number of "Other" PRs but do not include a section for them in the output
- Only use ### markdown headers for major sections and emoji indicators
- NO links to PRs
- NO merged date in output
- NEVER truncate PR titles - show full titles
@@ -100,7 +96,7 @@ jobs:
- If a PR has no description, write "(No description provided)"
- Extract meaningful summary from PR body - look for the first paragraph or key bullet points
- Parse JSON responses carefully using `jq` or similar tools
- If summary exceeds 6000 chars, shorten PR descriptions and add "_and X more PRs_" at the end
- If summary exceeds 5000 chars, shorten PR descriptions and add "_and X more PRs_" at the end
- Count PRs in each category and display in both overview and section headers
## Saving the Markdown Output:

View File

@@ -1,5 +1,224 @@
# Changelog
## [1.580.0](https://github.com/windmill-labs/windmill/compare/v1.579.2...v1.580.0) (2025-11-18)
### Features
* **aichat:** use responses api for openai models ([#7163](https://github.com/windmill-labs/windmill/issues/7163)) ([5c79a35](https://github.com/windmill-labs/windmill/commit/5c79a35306855143428d0725519578aea0a746fd))
* disabling/enabling email triggers ([#7171](https://github.com/windmill-labs/windmill/issues/7171)) ([8ae266b](https://github.com/windmill-labs/windmill/commit/8ae266b6a9ced16e1b7416cfc8bea5fe7a7af042))
* **security:** unshare pid of worker job process ([#7106](https://github.com/windmill-labs/windmill/issues/7106)) ([5aa251a](https://github.com/windmill-labs/windmill/commit/5aa251a2d276cc9d27bf104f8e4f724ea6a28231))
* support secondary promotion repos in git sync settings ([#7173](https://github.com/windmill-labs/windmill/issues/7173)) ([5548221](https://github.com/windmill-labs/windmill/commit/55482210921fe2eb0fd158abd4f7369495f2dfd7))
### Bug Fixes
* change uv tool dir from /root to /usr/local/uv ([c3e59fe](https://github.com/windmill-labs/windmill/commit/c3e59fe064fc3b9d4c05958eea54601ff3410899))
* improve delete to handle ai chat ([f371fbe](https://github.com/windmill-labs/windmill/commit/f371fbeb9bb0946bd29a6413ee7ede75dedda5d9))
* support IRSA for duckdb s3 proxy ([2058f27](https://github.com/windmill-labs/windmill/commit/2058f27e03468d45813f340b8563f935ca2142f4))
## [1.579.2](https://github.com/windmill-labs/windmill/compare/v1.579.1...v1.579.2) (2025-11-18)
### Bug Fixes
* ducklake manager table explorer issue ([d08c091](https://github.com/windmill-labs/windmill/commit/d08c0916f72a67f01e0c4475f03f9d1d33c10905))
## [1.579.1](https://github.com/windmill-labs/windmill/compare/v1.579.0...v1.579.1) (2025-11-18)
### Bug Fixes
* fix s3 object download frontend freezes ([09a6e1f](https://github.com/windmill-labs/windmill/commit/09a6e1feaa79ce3f8548f8090fddbf46abb08b18))
## [1.579.0](https://github.com/windmill-labs/windmill/compare/v1.578.0...v1.579.0) (2025-11-17)
### Features
* **ai:** handle aws bedrock as provider ([#7155](https://github.com/windmill-labs/windmill/issues/7155)) ([79ac631](https://github.com/windmill-labs/windmill/commit/79ac6312e87afa3646bddc0f7e66fc4367dbff7c))
* **mcp:** granular token scopes for scripts, flows, and endpoints ([#7130](https://github.com/windmill-labs/windmill/issues/7130)) ([88d04b9](https://github.com/windmill-labs/windmill/commit/88d04b9cbeee98f3256b78e9d34beb930cd729ec))
* rhel8 + fix rhel9 ([#7165](https://github.com/windmill-labs/windmill/issues/7165)) ([499d7d4](https://github.com/windmill-labs/windmill/commit/499d7d4098758726a8cb2bf3e4837927b8fd70a4))
### Bug Fixes
* **backend:** worker count in latest worker usage ([#7160](https://github.com/windmill-labs/windmill/issues/7160)) ([b87d2cc](https://github.com/windmill-labs/windmill/commit/b87d2cc64cb54b602ee599fcde7f0fd3c8931550))
* fix custom email triggers enabled ([#7164](https://github.com/windmill-labs/windmill/issues/7164)) ([90b5569](https://github.com/windmill-labs/windmill/commit/90b5569c911f9025b0e6b5318f57705efbd9bd17))
## [1.578.0](https://github.com/windmill-labs/windmill/compare/v1.577.0...v1.578.0) (2025-11-17)
### Features
* support to run windows binary as service ([#7153](https://github.com/windmill-labs/windmill/issues/7153)) ([ceeff5f](https://github.com/windmill-labs/windmill/commit/ceeff5f76c69d98319bb3fb7f7779b6046478d6b))
## [1.577.0](https://github.com/windmill-labs/windmill/compare/v1.576.3...v1.577.0) (2025-11-17)
### Features
* add support for validateset in pwsh ([#7158](https://github.com/windmill-labs/windmill/issues/7158)) ([b66e038](https://github.com/windmill-labs/windmill/commit/b66e038a0f8b6bffe157a83671c8e692c1441f23))
* allow http trigger to be disabled ([#6976](https://github.com/windmill-labs/windmill/issues/6976)) ([09082de](https://github.com/windmill-labs/windmill/commit/09082de53971d0d2f2a6308bc8ee573458a3b913))
### Bug Fixes
* create app_themes/groups/components only when needed ([cf5d58e](https://github.com/windmill-labs/windmill/commit/cf5d58ea43cef6add3da2aa1e24efc83be6df3b9))
* fix parse_postgres_uri not decoding password ([#7157](https://github.com/windmill-labs/windmill/issues/7157)) ([2cae72c](https://github.com/windmill-labs/windmill/commit/2cae72c9db6bd08689e1672be6dda32f6af831fb))
## [1.576.3](https://github.com/windmill-labs/windmill/compare/v1.576.2...v1.576.3) (2025-11-15)
### Bug Fixes
* handle better alias types in duckdb ([2c04e04](https://github.com/windmill-labs/windmill/commit/2c04e04bf0e3272c89f321392158888d02a1191b))
## [1.576.2](https://github.com/windmill-labs/windmill/compare/v1.576.1...v1.576.2) (2025-11-15)
### Bug Fixes
* temporary fix for duckdb type_aliases causing issues ([#7148](https://github.com/windmill-labs/windmill/issues/7148)) ([6426ebf](https://github.com/windmill-labs/windmill/commit/6426ebf8cb713443904065064b6a07eb1db0761a))
## [1.576.1](https://github.com/windmill-labs/windmill/compare/v1.576.0...v1.576.1) (2025-11-14)
### Bug Fixes
* DuckDB FFI crash fix ([#7145](https://github.com/windmill-labs/windmill/issues/7145)) ([d3fc459](https://github.com/windmill-labs/windmill/commit/d3fc459b407682bf588236236916363d94f3e1ff))
## [1.576.0](https://github.com/windmill-labs/windmill/compare/v1.575.4...v1.576.0) (2025-11-14)
### Features
* add support for switch and attributes in pwsh params ([#7143](https://github.com/windmill-labs/windmill/issues/7143)) ([c16bef8](https://github.com/windmill-labs/windmill/commit/c16bef8f296645ff873f9d8d28e3dcb50a65e304))
* **ai:** handle aws bedrock as provider ([#7131](https://github.com/windmill-labs/windmill/issues/7131)) ([30eb9aa](https://github.com/windmill-labs/windmill/commit/30eb9aae25eeb563ad119ef93f3ff1ab17c66d75))
* webhook by flow version ([#7062](https://github.com/windmill-labs/windmill/issues/7062)) ([09cdfb4](https://github.com/windmill-labs/windmill/commit/09cdfb4556748903dc5bbf53ef3356ac97c57d90))
### Bug Fixes
* use proper TLS connector for DuckLake instance catalog setup ([#7138](https://github.com/windmill-labs/windmill/issues/7138)) ([cf36fe3](https://github.com/windmill-labs/windmill/commit/cf36fe3bb1beec80fa84dc342a8a38cc7369bc4d))
## [1.575.4](https://github.com/windmill-labs/windmill/compare/v1.575.3...v1.575.4) (2025-11-13)
### Bug Fixes
* fix cancel job selection ([d4340b8](https://github.com/windmill-labs/windmill/commit/d4340b83ac94a9dd9febdcfa11288f35c0a57799))
## [1.575.3](https://github.com/windmill-labs/windmill/compare/v1.575.2...v1.575.3) (2025-11-13)
### Bug Fixes
* fix cancel job selection ([fbab86c](https://github.com/windmill-labs/windmill/commit/fbab86cc43dae17727a3aa99abc43a57c6b59b57))
## [1.575.2](https://github.com/windmill-labs/windmill/compare/v1.575.1...v1.575.2) (2025-11-13)
### Bug Fixes
* fix cancel job selection ([1c0f80d](https://github.com/windmill-labs/windmill/commit/1c0f80dc5594146b0ec18caf3b49c036695ce759))
## [1.575.1](https://github.com/windmill-labs/windmill/compare/v1.575.0...v1.575.1) (2025-11-13)
### Bug Fixes
* Fix flow buttons clipped in Safari ([#7126](https://github.com/windmill-labs/windmill/issues/7126)) ([77316cb](https://github.com/windmill-labs/windmill/commit/77316cbd0e3ee81fccee9680541f9b2346c8a209))
* handle string in input transform strings ([2ee17ae](https://github.com/windmill-labs/windmill/commit/2ee17aea8b29d99c5d2bb609b2becad354835de7))
* send alternatives on timeout toast ([#6920](https://github.com/windmill-labs/windmill/issues/6920)) ([6fac896](https://github.com/windmill-labs/windmill/commit/6fac8963909fb40726d85f5e897398d2a691edb0))
## [1.575.0](https://github.com/windmill-labs/windmill/compare/v1.574.3...v1.575.0) (2025-11-12)
### Features
* SQL result_collection flag ([#7041](https://github.com/windmill-labs/windmill/issues/7041)) ([14e41ba](https://github.com/windmill-labs/windmill/commit/14e41babfe6321abb22081b1b3601e9009451257))
### Bug Fixes
* allow --yes in fork command ([#7122](https://github.com/windmill-labs/windmill/issues/7122)) ([1280617](https://github.com/windmill-labs/windmill/commit/1280617a283ef7bcf53c17d833f1413ffe69dbf8))
* **backend:** scim email filter ([#7120](https://github.com/windmill-labs/windmill/issues/7120)) ([7a8fd86](https://github.com/windmill-labs/windmill/commit/7a8fd865e289522fd2d5eaaa9af471f01193a03d))
* **flow chat:** fix chat in edit mode + cleaner code ([#7118](https://github.com/windmill-labs/windmill/issues/7118)) ([9791eb4](https://github.com/windmill-labs/windmill/commit/9791eb4568aa9548174941f1bbfb6a338c3bef00))
## [1.574.3](https://github.com/windmill-labs/windmill/compare/v1.574.2...v1.574.3) (2025-11-11)
### Bug Fixes
* **backend:** add sslmode parameter to DuckLake PostgreSQL connections ([ea5b3e6](https://github.com/windmill-labs/windmill/commit/ea5b3e6b9269a0c631c7601313ab97c4bbdc2137))
* use teams internal_id rather than id ([#7113](https://github.com/windmill-labs/windmill/issues/7113)) ([25c36c1](https://github.com/windmill-labs/windmill/commit/25c36c1e0a6cfc56e079ff5a18980ea67403d4f9))
## [1.574.2](https://github.com/windmill-labs/windmill/compare/v1.574.1...v1.574.2) (2025-11-10)
### Bug Fixes
* **backend:** improve pwsh param block parsing ([#7096](https://github.com/windmill-labs/windmill/issues/7096)) ([51cba95](https://github.com/windmill-labs/windmill/commit/51cba95d3961ec637fd5060556f0e7a8a8bdbb88))
* **backend:** improve sse parsing ([#7100](https://github.com/windmill-labs/windmill/issues/7100)) ([2e245ac](https://github.com/windmill-labs/windmill/commit/2e245accc5f641f64a2cd28fc181e6a806c407e2))
* fix multiselect in list for apps ([9e4882c](https://github.com/windmill-labs/windmill/commit/9e4882c0a919303480f5a431db3c05b1d5855d37))
* make ai chat works with unicode messages ([2d54dfb](https://github.com/windmill-labs/windmill/commit/2d54dfbf05f21faa14655b7124daa291a9d8d66d))
* preproccessor ui and expanding preprocessor support ([#6872](https://github.com/windmill-labs/windmill/issues/6872)) ([fc5034e](https://github.com/windmill-labs/windmill/commit/fc5034e94d513bab2feb0ebb2aff822bbb42e4aa))
* proper error handling in pulled job preprocessor ([#7098](https://github.com/windmill-labs/windmill/issues/7098)) ([84992cd](https://github.com/windmill-labs/windmill/commit/84992cd8ffa94e009aeb6b25db224a3001581e15))
## [1.574.1](https://github.com/windmill-labs/windmill/compare/v1.574.0...v1.574.1) (2025-11-08)
### Bug Fixes
* make get_logs work even for partial flow jobs ([d6421c2](https://github.com/windmill-labs/windmill/commit/d6421c2ea79993ef7815c50cf035d3b3425e271e))
## [1.574.0](https://github.com/windmill-labs/windmill/compare/v1.573.5...v1.574.0) (2025-11-07)
### Features
* env var in flow ([#6852](https://github.com/windmill-labs/windmill/issues/6852)) ([c59183f](https://github.com/windmill-labs/windmill/commit/c59183f5c39f853d9679c00dd5aa755ef171d735))
### Bug Fixes
* **cli:** add automatic handler of .node files for codebase bundler ([62ffe9f](https://github.com/windmill-labs/windmill/commit/62ffe9ffce6d070c81794e8483ab83b431aee9ea))
* teams selector svelte5 ([#7087](https://github.com/windmill-labs/windmill/issues/7087)) ([6045f0c](https://github.com/windmill-labs/windmill/commit/6045f0c40654a88e93be688bcdfab874cfc0b267))
## [1.573.5](https://github.com/windmill-labs/windmill/compare/v1.573.4...v1.573.5) (2025-11-07)
### Bug Fixes
* dep jobs improvements ([#7081](https://github.com/windmill-labs/windmill/issues/7081)) ([ad43680](https://github.com/windmill-labs/windmill/commit/ad43680bb979f80db3bdb182ccb5bc61ca385ceb))
* modal action in App AgGrid ([#7085](https://github.com/windmill-labs/windmill/issues/7085)) ([b07c35f](https://github.com/windmill-labs/windmill/commit/b07c35fe1c9dbc4955ce47766ef92bd5cfdac4c8))
## [1.573.4](https://github.com/windmill-labs/windmill/compare/v1.573.3...v1.573.4) (2025-11-07)
### Bug Fixes
* **bun:** support relative imports without .ts but starting with /u/ or /f/ ([9e3ea76](https://github.com/windmill-labs/windmill/commit/9e3ea7657568d6561abcdfd23cc8a210432e4050))
* create git branch right before creating the workspace fork to catch errors and have a coherent fork point ([#7073](https://github.com/windmill-labs/windmill/issues/7073)) ([192fecc](https://github.com/windmill-labs/windmill/commit/192fecc86f54cf2a69ec7dddc466139b40933ac9))
* improve priority for child jobs ran from api ([5f7e0b7](https://github.com/windmill-labs/windmill/commit/5f7e0b7244e27533d9a8d1551e8affeb17eb94c0))
* make it impossible to have non linear script history even in edge condition ([3bda6da](https://github.com/windmill-labs/windmill/commit/3bda6da1683589180cad0780dd87b391a04851f8))
## [1.573.3](https://github.com/windmill-labs/windmill/compare/v1.573.2...v1.573.3) (2025-11-06)
### Bug Fixes
* job streaming improvement after compaction ([f16231d](https://github.com/windmill-labs/windmill/commit/f16231d7c9837efa3873034498abc9bfa87575d5))
## [1.573.2](https://github.com/windmill-labs/windmill/compare/v1.573.1...v1.573.2) (2025-11-06)
### Bug Fixes
* authentik frontend baseurl field empty when loaded from db ([#7065](https://github.com/windmill-labs/windmill/issues/7065)) ([8cb8650](https://github.com/windmill-labs/windmill/commit/8cb8650460856a1975c6eee429a2771f784a1555))
* oidc token issue ([75e056b](https://github.com/windmill-labs/windmill/commit/75e056bbce7e00334a36175eed00eb8eba332fbc))
## [1.573.1](https://github.com/windmill-labs/windmill/compare/v1.573.0...v1.573.1) (2025-11-05)

View File

@@ -114,7 +114,10 @@ ARG WITH_GIT=true
ARG LATEST_STABLE_PY=3.11.10
ENV UV_PYTHON_INSTALL_DIR=/tmp/windmill/cache/py_runtime
ENV UV_PYTHON_PREFERENCE=only-managed
RUN mkdir -p /usr/local/uv
ENV UV_TOOL_BIN_DIR=/usr/local/bin
ENV UV_TOOL_DIR=/usr/local/uv
ENV PATH /usr/local/bin:/root/.local/bin:$PATH

View File

@@ -13,4 +13,7 @@ rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
"-C", "link-args=-Wl,-rpath,$ORIGIN/"
]
]
[net]
git-fetch-with-cli = true

View File

@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": []
},
"hash": "045b26db0cefe6eaac0e572661d984ff5ce7086ac511e8647e2024d9dbe0af56"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM flow_conversation WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "08574e8e5dc165041750880fb02e7ffea83ae94a670b598b6dada0b3d0914629"
}

View File

@@ -0,0 +1,25 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n CASE \n WHEN flow_version.id IS NOT NULL THEN\n (flow_version.value -> 'flow_env' -> $3) #> $4\n ELSE\n (root_job.raw_flow -> 'flow_env' -> $3) #> $4\n END AS \"flow_env: sqlx::types::Json<Box<RawValue>>\"\n FROM \n v2_job current_job\n JOIN \n v2_job root_job ON root_job.id = COALESCE(current_job.root_job, current_job.flow_innermost_root_job, current_job.parent_job, current_job.id)\n AND root_job.workspace_id = current_job.workspace_id\n LEFT JOIN\n flow_version ON flow_version.id = root_job.runnable_id\n AND flow_version.path = root_job.runnable_path\n AND flow_version.workspace_id = root_job.workspace_id\n WHERE \n current_job.id = $1 AND \n current_job.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "flow_env: sqlx::types::Json<Box<RawValue>>",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Uuid",
"Text",
"Text",
"TextArray"
]
},
"nullable": [
null
]
},
"hash": "0c0f3909b80c35210fc64c685905308621f9135c2c45a2fa0531ea750387da1f"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT 1 FROM script WHERE hash = $1 AND workspace_id = $2 FOR UPDATE",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": [
null
]
},
"hash": "1fc43faf3a7ea15be82b1666273836901cc6ba17c7d5e194b50b9204f264de27"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT j.id AS \"id!\" FROM v2_job j WHERE j.id = ANY($1) AND j.trigger_kind != 'schedule'::job_trigger_kind AND ($2::text[] IS NULL OR j.tag = ANY($2))",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"UuidArray",
"TextArray"
]
},
"nullable": [
false
]
},
"hash": "24d302b8215d49a289bedd14a5791e9366d1f6d3d2aa485e0f50c6f2d85693dd"
}

View File

@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO ai_agent_memory (workspace_id, conversation_id, step_id, messages, created_at, updated_at)\n VALUES ($1, $2, $3, $4, NOW(), NOW())\n ON CONFLICT (workspace_id, conversation_id, step_id)\n DO UPDATE SET\n messages = EXCLUDED.messages,\n updated_at = NOW()",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Uuid",
"Varchar",
"Jsonb"
]
},
"nullable": []
},
"hash": "38baa6cf7d1c2532d38486a01e6d27ac1a58540a51e97dec5613e9bc0791e890"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO app_version\n (app_id, value, created_by, raw_app)\n SELECT app_id, value, created_by, raw_app\n FROM app_version WHERE id = $1\n RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
false
]
},
"hash": "3d38720e807b379645d8f3ab61c6a968143d42c3014152608f7d1b252cd8085c"
}

View File

@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": []
},
"hash": "42baae3c69367bbb21771a9711c311afe67d339ca022ba61c2767c004b038ef0"
}

View File

@@ -1,18 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE job_logs SET logs = $1, log_offset = $2, \n log_file_index = array_append(coalesce(log_file_index, array[]::text[]), $3) \n WHERE workspace_id = $4 AND job_id = $5",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Int4",
"Text",
"Text",
"Uuid"
]
},
"nullable": []
},
"hash": "528cdbb75f1c5135170a58fce3fda464be138272487639d0ffbbbe6961ec5c37"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM ai_agent_memory WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "56d3dccce81c652d6ab3d4ba69de89c4a5cb1fbc9a8b73e6b8ba4d6e74088352"
}

View File

@@ -1,11 +1,11 @@
{
"db_name": "PostgreSQL",
"query": "SELECT j.created_by AS \"created_by!\", CONCAT(coalesce(job_logs.logs, '')) as logs, job_logs.log_offset, job_logs.log_file_index\n FROM v2_job j\n LEFT JOIN job_logs ON job_logs.job_id = j.id\n WHERE j.id = $1 AND j.workspace_id = $2 AND ($3::text[] IS NULL OR j.tag = ANY($3))",
"query": "SELECT j.created_by AS \"created_by\", coalesce(job_logs.logs, '') as logs, COALESCE(job_logs.log_offset, 0) as log_offset, job_logs.log_file_index\n FROM v2_job j\n LEFT JOIN job_logs ON job_logs.job_id = j.id\n WHERE j.id = $1 AND j.workspace_id = $2 AND ($3::text[] IS NULL OR j.tag = ANY($3))",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "created_by!",
"name": "created_by",
"type_info": "Varchar"
},
{
@@ -34,9 +34,9 @@
"nullable": [
false,
null,
false,
null,
true
]
},
"hash": "35061719d01929a7146c80de4b637abdad3198d3340ec7c04ed671baff0a4d0b"
"hash": "5e7cadffbee74b11e224b60322b102b9899b4a97b8e557692c0085a9b472b8a7"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE local_part = $1 \n AND workspaced_local_part = FALSE\n ",
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE local_part = $1 \n AND workspaced_local_part = FALSE\n AND enabled IS TRUE\n ",
"describe": {
"columns": [
{
@@ -66,5 +66,5 @@
true
]
},
"hash": "668edc2f84eccf5db6b8daa3ed97e25e46a37c50a9d1a6eca5a277fb58a2e7b4"
"hash": "628d303956d55d22e5ac64496b66010d4476d5c222184f09e5a189975a37b941"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": []
},
"hash": "6525e65ffe66643fed4db83aa81b525fc3ec54fdbca5d12647c1da88c9983b10"
}

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT messages FROM ai_agent_memory\n WHERE workspace_id = $1 AND conversation_id = $2 AND step_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "messages",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text",
"Uuid",
"Text"
]
},
"nullable": [
false
]
},
"hash": "6a1005b8ae5326c5d5955534e9228b6bd524253971ae09cbe100c0d6d42e6cdc"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO email_trigger (\n workspace_id,\n path,\n script_path,\n is_flow,\n local_part,\n workspaced_local_part,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, now(), $9, $10, $11\n )\n ",
"query": "\n INSERT INTO email_trigger (\n workspace_id,\n path,\n script_path,\n is_flow,\n local_part,\n workspaced_local_part,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry,\n enabled\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, now(), $9, $10, $11, $12\n )\n ",
"describe": {
"columns": [],
"parameters": {
@@ -15,10 +15,11 @@
"Varchar",
"Varchar",
"Jsonb",
"Jsonb"
"Jsonb",
"Bool"
]
},
"nullable": []
},
"hash": "d328d00e5393b0e8d2c4b3674221fabbae580fc404e0d4481fd8d7fb51272c94"
"hash": "6c568509908c1833d9e6f58f739b8a186fc91f94d4fcc40eeb9914084e4ef60c"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT \n j.id, j.workspace_id, j.runnable_id AS \"runnable_id!: ScriptHash\", q.scheduled_for, q.started_at, j.parent_job, j.flow_innermost_root_job, j.runnable_path, j.kind as \"kind!: JobKind\", j.permissioned_as, \n j.created_by, j.script_lang AS \"script_lang!: ScriptLang\", j.permissioned_as_email, j.flow_step_id, j.trigger_kind AS \"trigger_kind!: JobTriggerKind\", j.trigger, j.priority, j.concurrent_limit, j.tag, j.cache_ttl\n FROM v2_job j LEFT JOIN v2_job_queue q ON j.id = q.id\n WHERE j.id = $1 AND j.workspace_id = $2",
"query": "SELECT \n j.id, j.workspace_id, j.runnable_id AS \"runnable_id: ScriptHash\", q.scheduled_for, q.started_at, j.parent_job, j.flow_innermost_root_job, j.runnable_path, j.kind as \"kind!: JobKind\", j.permissioned_as, \n j.created_by, j.script_lang AS \"script_lang: ScriptLang\", j.permissioned_as_email, j.flow_step_id, j.trigger_kind AS \"trigger_kind: JobTriggerKind\", j.trigger, j.priority, j.concurrent_limit, j.tag, j.cache_ttl\n FROM v2_job j LEFT JOIN v2_job_queue q ON j.id = q.id\n WHERE j.id = $1 AND j.workspace_id = $2",
"describe": {
"columns": [
{
@@ -15,7 +15,7 @@
},
{
"ordinal": 2,
"name": "runnable_id!: ScriptHash",
"name": "runnable_id: ScriptHash",
"type_info": "Int8"
},
{
@@ -87,7 +87,7 @@
},
{
"ordinal": 11,
"name": "script_lang!: ScriptLang",
"name": "script_lang: ScriptLang",
"type_info": {
"Custom": {
"name": "script_lang",
@@ -133,7 +133,7 @@
},
{
"ordinal": 14,
"name": "trigger_kind!: JobTriggerKind",
"name": "trigger_kind: JobTriggerKind",
"type_info": {
"Custom": {
"name": "job_trigger_kind",
@@ -212,5 +212,5 @@
true
]
},
"hash": "a001b4254e0f1ba8a87776e32f9506dce6eb4af3fb32e6a4c6e76084873a539a"
"hash": "6cc922a5bbd348c938a9d1431aaa0f24f078ea814b429d44403aca1e5002e750"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE job_logs SET logs = $1 WHERE workspace_id = $2 AND job_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Uuid"
]
},
"nullable": []
},
"hash": "6ec1e81e1698c754ca8a660cf6a3ae66e75b487901befd647839dd2bc2233bf4"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
"describe": {
"columns": [],
"parameters": {
@@ -14,5 +14,5 @@
},
"nullable": []
},
"hash": "e8e33f599eae064011232f9f715e676d0c8ae31982865cb2c8103ed735c42c69"
"hash": "6f49a47b0b77720b0e8a2ddbd46e45e414dd646c912387ee63609d9f5611690f"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE worker_ping SET ping_at = now(), current_job_id = $1, current_job_workspace_id = $2, memory_usage = $3, wm_memory_usage = $4,\n occupancy_rate = $6, occupancy_rate_15s = $7, occupancy_rate_5m = $8, occupancy_rate_30m = $9 WHERE worker = $5",
"query": "UPDATE worker_ping SET ping_at = now(), current_job_id = $1, current_job_workspace_id = $2, memory_usage = $3, wm_memory_usage = $4,\n occupancy_rate = $6, occupancy_rate_15s = $7, occupancy_rate_5m = $8, occupancy_rate_30m = $9, job_isolation = $10 WHERE worker = $5",
"describe": {
"columns": [],
"parameters": {
@@ -13,10 +13,11 @@
"Float4",
"Float4",
"Float4",
"Float4"
"Float4",
"Text"
]
},
"nullable": []
},
"hash": "506066203c49424e9944eb3948dc1657d3d796e6233e9f0ec925879c705d4773"
"hash": "76a7ad0588afcb4e9f0b876c50e203c36be56b2b2b08ca787417a9822ef56f64"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed,\n CASE WHEN $4 IS TRUE THEN current_job_id ELSE NULL END as last_job_id, CASE WHEN $4 IS TRUE THEN current_job_workspace_id ELSE NULL END as last_job_workspace_id, \n custom_tags, worker_group, wm_version, occupancy_rate, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m, memory, vcpus, memory_usage, wm_memory_usage\n FROM worker_ping\n WHERE ($1::integer IS NULL AND ping_at > now() - interval '5 minute') OR (ping_at > now() - ($1 || ' seconds')::interval)\n ORDER BY ping_at desc LIMIT $2 OFFSET $3",
"query": "SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed,\n CASE WHEN $4 IS TRUE THEN current_job_id ELSE NULL END as last_job_id, CASE WHEN $4 IS TRUE THEN current_job_workspace_id ELSE NULL END as last_job_workspace_id,\n custom_tags, worker_group, wm_version, occupancy_rate, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m, memory, vcpus, memory_usage, wm_memory_usage, job_isolation\n FROM worker_ping\n WHERE ($1::integer IS NULL AND ping_at > now() - interval '5 minute') OR (ping_at > now() - ($1 || ' seconds')::interval)\n ORDER BY ping_at desc LIMIT $2 OFFSET $3",
"describe": {
"columns": [
{
@@ -97,6 +97,11 @@
"ordinal": 18,
"name": "wm_memory_usage",
"type_info": "Int8"
},
{
"ordinal": 19,
"name": "job_isolation",
"type_info": "Text"
}
],
"parameters": {
@@ -126,8 +131,9 @@
true,
true,
true,
true,
true
]
},
"hash": "6a497334c98bfaf70be44fced572a1cc0dde4141aa4c5002765a95432d0101ab"
"hash": "771a858a4b7ca41b6787e61f5a4a5c9c4d48fd213852e2f997cd4b2420580d30"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO unique_ext_jwt_token (jwt_hash, last_used_at)\n VALUES ($1, NOW())\n ON CONFLICT (jwt_hash)\n DO UPDATE SET last_used_at = NOW()",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT j.id AS \"id!\" FROM v2_job j LEFT JOIN v2_job_queue q USING (id) WHERE j.id = ANY($1) AND j.trigger_kind IS DISTINCT FROM 'schedule'::job_trigger_kind AND ($2::text[] IS NULL OR j.tag = ANY($2))",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"UuidArray",
"TextArray"
]
},
"nullable": [
false
]
},
"hash": "78f5684086a8c28a602c583389e974fffd7f74b10618b1467426623973c10131"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(id) FROM v2_job WHERE permissioned_as_email = $1",
"query": "SELECT COUNT(j.id) FROM v2_job_queue q JOIN v2_job j USING (id) WHERE j.permissioned_as_email = $1",
"describe": {
"columns": [
{
@@ -18,5 +18,5 @@
null
]
},
"hash": "1368ccd2c15a75690041a6c87d4a2849fe6bc668654ffcbfbc22a02027280739"
"hash": "7a4e79bcae7c7d69e4bb71c8540b2adb9ca8bd15098038e3ccf8b16164944951"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n path \n FROM \n flow_version \n WHERE \n id = $1 AND \n workspace_id = $2\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": [
false
]
},
"hash": "7cab8475996f0abd7ca7888b460bd3faf3bef6e34200c258e3704981817c241e"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO app_version\n (app_id, value, created_by, raw_app)\n SELECT app_id, value, created_by, raw_app\n FROM app_version WHERE id = $1\n RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
false
]
},
"hash": "83232f2db5eb1b6fef744998e60420ef920d472286cf4c1f78452446a4bcb604"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": []
},
"hash": "88d529fd26d6ecba7640c74daccb5cb0c7146c11adae8c287c0c9e71ac68f7a7"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
"describe": {
"columns": [],
"parameters": {
@@ -14,5 +14,5 @@
},
"nullable": []
},
"hash": "2e131a019051bdac7c9c65f7c504cfba31cfdd64a0f68001e123694ed5cde5ed"
"hash": "8e750d4b3af9b5844c11b1b92741f2ee9d2d3412d4a2c96c6ccc87ec1c382384"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO http_trigger (\n workspace_id,\n path,\n route_path,\n route_path_key,\n workspaced_route,\n authentication_resource_path,\n wrap_body,\n raw_string,\n script_path,\n summary,\n description,\n is_flow,\n request_type,\n authentication_method,\n http_method,\n static_asset_config,\n edited_by,\n email,\n edited_at,\n is_static_website,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, now(), $19, $20, $21, $22\n )\n ",
"query": "\n INSERT INTO http_trigger (\n workspace_id,\n path,\n route_path,\n route_path_key,\n workspaced_route,\n authentication_resource_path,\n wrap_body,\n raw_string,\n script_path,\n summary,\n description,\n is_flow,\n enabled,\n request_type,\n authentication_method,\n http_method,\n static_asset_config,\n edited_by,\n email,\n edited_at,\n is_static_website,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, now(), $20, $21, $22, $23\n )\n ",
"describe": {
"columns": [],
"parameters": {
@@ -17,6 +17,7 @@
"Varchar",
"Text",
"Bool",
"Bool",
{
"Custom": {
"name": "request_type",
@@ -69,5 +70,5 @@
},
"nullable": []
},
"hash": "1f6b773ce34fe51d03d6f9a2345481629c62453eebbb08f82dd2da23389bc117"
"hash": "94e2e899fc0a5134c29217feda1f0530d03548cc5c884c83d7b85d068e738b8e"
}

View File

@@ -0,0 +1,18 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE job_logs SET logs = $1, log_offset = $2,\n log_file_index = array_append(coalesce(log_file_index, array[]::text[]), $3)\n WHERE workspace_id = $4 AND job_id = $5",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Int4",
"Text",
"Text",
"Uuid"
]
},
"nullable": []
},
"hash": "a0af7dc507778ab23d5b1615e2a03278de05f4cd90fb1c31d7281f8681c7f85d"
}

View File

@@ -1,24 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO flow_version\n (workspace_id, path, value, schema, created_by)\n\n SELECT workspace_id, path, value, schema, created_by\n FROM flow_version WHERE path = $1 AND workspace_id = $2 AND id = $3\n\n RETURNING id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Int8"
]
},
"nullable": [
false
]
},
"hash": "a6a973dcd92d2e40fd9a1c1be42052fcd350bd47ee4f63832448b6e6f0f472f0"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n edited_by = $4,\n email = $5,\n edited_at = now(),\n error_handler_path = $6,\n error_handler_args = $7,\n retry = $8\n WHERE \n workspace_id = $9 AND path = $10\n ",
"query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n edited_by = $4,\n email = $5,\n edited_at = now(),\n error_handler_path = $6,\n error_handler_args = $7,\n retry = $8,\n enabled = $9\n WHERE \n workspace_id = $10 AND path = $11\n ",
"describe": {
"columns": [],
"parameters": {
@@ -13,11 +13,12 @@
"Varchar",
"Jsonb",
"Jsonb",
"Bool",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "acb094aef60bba9083087264d65034fce38417099f15e8312be72a386f10bc1f"
"hash": "a704283ff62ac1cd6db489ca3f84d6994d75c6b9e5f61daf54d298a2fdcfd9af"
}

View File

@@ -0,0 +1,66 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n flow_version.id AS version,\n flow_version.value->>'early_return' as early_return, \n flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, \n (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, \n flow.tag, \n flow.dedicated_worker, \n flow.on_behalf_of_email, \n flow.edited_by\n FROM \n flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE \n flow_version.workspace_id = $1 AND\n flow_version.path = $2 AND\n flow_version.id = $3\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "version",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "early_return",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "has_preprocessor",
"type_info": "Bool"
},
{
"ordinal": 3,
"name": "chat_input_enabled",
"type_info": "Bool"
},
{
"ordinal": 4,
"name": "tag",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "dedicated_worker",
"type_info": "Bool"
},
{
"ordinal": 6,
"name": "on_behalf_of_email",
"type_info": "Text"
},
{
"ordinal": 7,
"name": "edited_by",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Int8"
]
},
"nullable": [
false,
null,
null,
null,
true,
true,
true,
false
]
},
"hash": "a7468e9054beed88636786c5495ac3b9d9a6086ae6212ad9237b39a0346d7d26"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE\n http_trigger\n SET\n wrap_body = $1,\n raw_string = $2,\n authentication_resource_path = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n http_method = $7,\n static_asset_config = $8,\n edited_by = $9,\n email = $10,\n request_type = $11,\n authentication_method = $12,\n summary = $13,\n description = $14,\n edited_at = now(),\n is_static_website = $15,\n error_handler_path = $16,\n error_handler_args = $17,\n retry = $18\n WHERE\n workspace_id = $19 AND\n path = $20\n ",
"query": "\n UPDATE\n http_trigger\n SET\n wrap_body = $1,\n raw_string = $2,\n authentication_resource_path = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n enabled = $7,\n http_method = $8,\n static_asset_config = $9,\n edited_by = $10,\n email = $11,\n request_type = $12,\n authentication_method = $13,\n summary = $14,\n description = $15,\n edited_at = now(),\n is_static_website = $16,\n error_handler_path = $17,\n error_handler_args = $18,\n retry = $19\n WHERE\n workspace_id = $20 AND\n path = $21\n ",
"describe": {
"columns": [],
"parameters": {
@@ -11,6 +11,7 @@
"Varchar",
"Varchar",
"Bool",
"Bool",
{
"Custom": {
"name": "http_method",
@@ -67,5 +68,5 @@
},
"nullable": []
},
"hash": "4c64bd0e364f536597db83161b5a27ff58b5ec7148bc94807423593bcfa27b90"
"hash": "ada1a14b4f25e41742df153b07e8bdf5b5feac9c6fa9de38bd24104bac8539d2"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n path \n FROM \n flow_version \n WHERE \n id = $1 AND \n workspace_id = $2\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": [
false
]
},
"hash": "b82f97f2a858e20b1fb6d9b0b3eab5bab2bafa91689eac5073c1678c338afb27"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE\n http_trigger\n SET\n route_path = $1,\n route_path_key = $2,\n workspaced_route = $3,\n wrap_body = $4,\n raw_string = $5,\n authentication_resource_path = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n http_method = $10,\n static_asset_config = $11,\n edited_by = $12,\n email = $13,\n request_type = $14,\n authentication_method = $15,\n summary = $16,\n description = $17,\n edited_at = now(),\n is_static_website = $18,\n error_handler_path = $19,\n error_handler_args = $20,\n retry = $21\n WHERE\n workspace_id = $22 AND\n path = $23\n ",
"query": "\n UPDATE\n http_trigger\n SET\n route_path = $1,\n route_path_key = $2,\n workspaced_route = $3,\n wrap_body = $4,\n raw_string = $5,\n authentication_resource_path = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n enabled = $10,\n http_method = $11,\n static_asset_config = $12,\n edited_by = $13,\n email = $14,\n request_type = $15,\n authentication_method = $16,\n summary = $17,\n description = $18,\n edited_at = now(),\n is_static_website = $19,\n error_handler_path = $20,\n error_handler_args = $21,\n retry = $22\n WHERE\n workspace_id = $23 AND\n path = $24\n ",
"describe": {
"columns": [],
"parameters": {
@@ -14,6 +14,7 @@
"Varchar",
"Varchar",
"Bool",
"Bool",
{
"Custom": {
"name": "http_method",
@@ -70,5 +71,5 @@
},
"nullable": []
},
"hash": "465144ea7e2930203618d9814a3e20c77b4363cf9e7c655d395f3fe40c247f61"
"hash": "b8bcf1fef244395dad802174dfc9bff45ff6f9cd1298eae5de40218cbab2eb96"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n path,\n script_path,\n is_flow,\n route_path,\n authentication_resource_path,\n workspace_id,\n request_type AS \"request_type: _\",\n authentication_method AS \"authentication_method: _\",\n edited_by,\n email,\n static_asset_config AS \"static_asset_config: _\",\n wrap_body,\n raw_string,\n workspaced_route,\n is_static_website,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM\n http_trigger\n WHERE\n http_method = $1\n ",
"query": "\n SELECT\n path,\n script_path,\n is_flow,\n route_path,\n authentication_resource_path,\n workspace_id,\n request_type AS \"request_type: _\",\n authentication_method AS \"authentication_method: _\",\n edited_by,\n email,\n static_asset_config AS \"static_asset_config: _\",\n wrap_body,\n raw_string,\n workspaced_route,\n is_static_website,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM\n http_trigger\n WHERE\n http_method = $1 AND\n enabled is TRUE\n ",
"describe": {
"columns": [
{
@@ -158,5 +158,5 @@
true
]
},
"hash": "1301f873a829db137573b8b39449f6160f2adf44f864f26a99b8eab5818fbd50"
"hash": "bbc96ae911d4ca0330582340e65c10e008e507610f095ff3936865101c9ba346"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n (elem->>'installation_id')::bigint as installation_id\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE workspace_id = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "installation_id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "be00ac55e8668a0ed3befda7d8595c7cda0cba0b119d4fdb8a0dea1b28a1d560"
}

View File

@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, $2, $3, $4)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $4",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Bool",
"Int4",
"Int4"
]
},
"nullable": []
},
"hash": "cabc79429825b5f4861e4ba2001a5f235f39558dee88cbb486d7ac988d0a44d1"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource (workspace_id, path, value, description, resource_type, created_by, edited_at) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme', $2, now()) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "cccf9e216d84dfbb801b92d697496f44d93b344190a26c8393867b3a268b9aba"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM ai_agent_memory\n WHERE workspace_id = $1 AND conversation_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid"
]
},
"nullable": []
},
"hash": "d1d9cb0a7043760564171f481dd73f04eaa2c7d5c1ddc0501bc70c3fb34a07be"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM unique_ext_jwt_token WHERE last_used_at > NOW() - INTERVAL '30 days'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE workspace_id = $1 \n AND local_part = $2 \n AND (workspaced_local_part = TRUE OR $3 IS TRUE)\n ",
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE workspace_id = $1 \n AND local_part = $2 \n AND (workspaced_local_part = TRUE OR $3 IS TRUE)\n AND enabled IS TRUE\n ",
"describe": {
"columns": [
{
@@ -68,5 +68,5 @@
true
]
},
"hash": "bbd51f4f0a8bb2db5d6b634f2d32f5f7f7f57390a0e4b6e7cad45c9147fd6356"
"hash": "df6972fb16a2364f10379fe37e125ae228697602e3769c479582470dc0b9488b"
}

View File

@@ -1,66 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, on_behalf_of_email, edited_by, flow_version.id AS version\n FROM flow\n INNER JOIN flow_version\n ON flow_version.id = $3\n WHERE flow.path = $1 and flow.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "tag",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "dedicated_worker",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "early_return",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "has_preprocessor",
"type_info": "Bool"
},
{
"ordinal": 4,
"name": "chat_input_enabled",
"type_info": "Bool"
},
{
"ordinal": 5,
"name": "on_behalf_of_email",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 7,
"name": "version",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Int8"
]
},
"nullable": [
true,
true,
null,
null,
null,
true,
false,
false
]
},
"hash": "e1409c67b93881cf68f6ac9c8bae0856cf426c7e7860c8b5b799972baa8e6945"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, wm_version, vcpus, memory) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (worker) \n DO UPDATE set ip = EXCLUDED.ip, custom_tags = EXCLUDED.custom_tags, worker_group = EXCLUDED.worker_group",
"query": "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, wm_version, vcpus, memory, job_isolation) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (worker)\n DO UPDATE set ip = EXCLUDED.ip, custom_tags = EXCLUDED.custom_tags, worker_group = EXCLUDED.worker_group",
"describe": {
"columns": [],
"parameters": {
@@ -13,10 +13,11 @@
"Varchar",
"Varchar",
"Int8",
"Int8"
"Int8",
"Text"
]
},
"nullable": []
},
"hash": "e5f8830450e90f678494ae2b0f86b0692a02dec4c8415fd7d364b0cc088905c2"
"hash": "ed90b9fcc57f530bab2d2d7426795bb358c91ebdd9dab50d7e3d2fdce63b947c"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n value->'preprocessor_module'->'value' as \"preprocessor_module: _\",\n schema as \"schema: _\"\n FROM flow_version\n WHERE\n path = $1\n AND workspace_id = $2\n ORDER BY created_at DESC\n LIMIT 1",
"query": "SELECT\n value->'preprocessor_module'->'value' as \"preprocessor_module: _\",\n schema as \"schema: _\"\n FROM flow_version\n WHERE\n id = $1\n AND workspace_id = $2",
"describe": {
"columns": [
{
@@ -16,7 +16,7 @@
],
"parameters": {
"Left": [
"Text",
"Int8",
"Text"
]
},
@@ -25,5 +25,5 @@
true
]
},
"hash": "e2474b7855c8b08f927f2b987421e773e537a8eb0a113477764bbab14e1f3a3d"
"hash": "f0c9670c27d9a3b8e8f316fd28f7d3ede2ecc3225ff2879ff5dfd59c4ef236d5"
}

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO flow_version\n (workspace_id, path, value, schema, created_by)\n\n SELECT workspace_id, path, value, schema, created_by\n FROM flow_version WHERE path = $1 AND workspace_id = $2 AND id = $3\n\n RETURNING id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Int8"
]
},
"nullable": [
false
]
},
"hash": "f0efa383f2025158de160577ad839ae72faf0c8fe097e6ad6d309aee9a8aede2"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
"describe": {
"columns": [],
"parameters": {
@@ -14,5 +14,5 @@
},
"nullable": []
},
"hash": "e572fa64eec9188368d7c271ac7ecd6b45dc161423abab22cddb2b13f6fb9833"
"hash": "f844026597d2e1c59a80dc8e1ddd069c0ad82b27b41171825928a938cfa8316f"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT path FROM flow_version WHERE id = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": [
false
]
},
"hash": "fc9753f501974c4b570d710c7621ff1f6787a852bb27acf49593af963a6aacca"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n local_part = $4,\n workspaced_local_part = $5,\n edited_by = $6,\n email = $7,\n edited_at = now(),\n error_handler_path = $8,\n error_handler_args = $9,\n retry = $10\n WHERE \n workspace_id = $11 AND path = $12\n ",
"query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n local_part = $4,\n workspaced_local_part = $5,\n edited_by = $6,\n email = $7,\n edited_at = now(),\n error_handler_path = $8,\n error_handler_args = $9,\n retry = $10,\n enabled = $11\n WHERE \n workspace_id = $12 AND path = $13\n ",
"describe": {
"columns": [],
"parameters": {
@@ -15,11 +15,12 @@
"Varchar",
"Jsonb",
"Jsonb",
"Bool",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "6fafc23924eded970689040bd4a94d4d23ebee4f2b7d37bb54c47edb7720be00"
"hash": "fe12006498b9e7aece6b104fc5d9a80eeac12c6ca07a0e8950941ba796b0ba79"
}

View File

@@ -10,3 +10,16 @@
1. Update database schema with migration if necessary
2. Update backend/windmill-api/openapi.yaml after modifying API endpoints
## Querying the Database
To query the database directly, use psql with the following connection string:
```bash
psql postgres://postgres:changeme@localhost:5432/windmill
```
This can be helpful for:
- Inspecting database state during development
- Testing queries before implementing them in Rust
- Debugging data-related issues

572
backend/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.573.1"
version = "1.580.0"
authors.workspace = true
edition.workspace = true
@@ -33,7 +33,7 @@ members = [
exclude = ["./windmill-duckdb-ffi-internal"]
[workspace.package]
version = "1.573.1"
version = "1.580.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -161,6 +161,10 @@ nom.workspace = true
globset.workspace = true
[target.'cfg(windows)'.dependencies]
windows-service = "0.7"
windows-sys = { version = "0.52", features = ["Win32_System_Services", "Win32_System_Console", "Win32_Foundation"] }
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = { optional = true, workspace = true }
tikv-jemalloc-sys = { optional = true, workspace = true }
@@ -252,17 +256,19 @@ urlencoding = "^2"
url = { version = "^2" , features = ["serde"]}
async-oauth2 = "0.5.1"
reqwest = { version = "^0.12", features = ["json", "stream", "gzip", "multipart"] }
eventsource-stream = "0.2.3"
time = "^0"
serde_urlencoded = "^0"
tokio-tar = "^0"
tempfile = "^3"
tokio-util = { version = "^0", features = ["io"] }
json-pointer = "^0"
itertools = "^0"
itertools = "^0.14.0"
regex = "^1"
semver = "^1"
aws-sigv4 = "^1.3.4"
aws-sdk-config = "=1.68.0"
aws-sdk-rds = "^1"
async-trait = "0.1.88"
@@ -299,7 +305,7 @@ swc_ecma_visit = "=0.104.8"
async-recursion = "^1"
base64 = "^0"
base64 = "^0.22.1"
base32 = "^0"
hmac = "0.12.1"
sha2 = "0.10.6"
@@ -327,6 +333,7 @@ dyn-iter = "0.2.0"
rsa = "^0"
async_zip = { version = "0.0.17", features = ["tokio", "tokio-fs", "deflate", "chrono"] }
once_cell = "1.17.1"
dashmap = "6.1.0"
gosyn = "0.2.6"
bytes = "1.4.0"
gethostname = "0.4.3"
@@ -379,11 +386,14 @@ datafusion = "47.0.0"
object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7", features = ["aws", "azure", "gcp"] }
openidconnect = { version = "4.0.0-rc.1" }
aws-config = "^1"
aws-sdk-bedrockruntime = "=1.113.0"
aws-credential-types = "^1"
aws-smithy-types = "^1"
aws-sdk-sqs = "=1.77.0"
aws-sdk-sts = "=1.79.0"
aws-sdk-sso = "=1.77.0"
aws-sdk-ssooidc = "=1.78.0"
rustls = "=0.23.29"
rustls = "=0.23.35"
async-once-cell = "0.5.4"

View File

@@ -1 +1 @@
8ab9b8d3cb7486fcb472d34f3a0b49f52548fe8f
5dd7ab7ce9e78e1b96ea78fd297da95d9b69e7a3

View File

@@ -0,0 +1,2 @@
-- Add down migration script here
ALTER TABLE http_trigger DROP COLUMN enabled;

View File

@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TABLE http_trigger ADD COLUMN enabled BOOLEAN DEFAULT TRUE NOT NULL;

View File

@@ -0,0 +1,4 @@
-- Add down migration script here
-- Drop table
DROP TABLE IF EXISTS ai_agent_memory;

View File

@@ -0,0 +1,16 @@
-- Add up migration script here
-- Create ai_agent_memory table for storing AI agent step memory when S3 is unavailable
CREATE TABLE ai_agent_memory (
workspace_id VARCHAR(50) NOT NULL,
conversation_id UUID NOT NULL,
step_id VARCHAR(255) NOT NULL,
messages JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
PRIMARY KEY (workspace_id, conversation_id, step_id)
);
-- Grant permissions
GRANT ALL ON ai_agent_memory TO windmill_admin;
GRANT ALL ON ai_agent_memory TO windmill_user;

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,99 @@
CREATE OR REPLACE FUNCTION update_string(s text)
RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
prefix TEXT := '-- https://www.windmill.dev/docs/getting_started/scripts_quickstart/sql#result-collection
-- result_collection=legacy
';
BEGIN
RETURN prefix || s;
END;
$$;
CREATE OR REPLACE FUNCTION update_all_modules(obj jsonb)
RETURNS jsonb
LANGUAGE plpgsql
AS $$
DECLARE
result jsonb;
k text;
v jsonb;
BEGIN
IF jsonb_typeof(obj) = 'object' THEN
result := '{}'::jsonb;
FOR k, v IN SELECT * FROM jsonb_each(obj)
LOOP
IF k = 'content' and jsonb_typeof(v) = 'string' AND obj->>'language' IN ('bigquery', 'postgresql', 'duckdb', 'mssql', 'oracledb', 'snowflake', 'mysql') THEN
result := result || jsonb_build_object('content', update_string(obj->>'content'));
ELSE
result := result || jsonb_build_object(k, update_all_modules(v));
END IF;
END LOOP;
RETURN result;
ELSIF jsonb_typeof(obj) = 'array' AND jsonb_array_length(obj) > 0 THEN
SELECT jsonb_agg(update_all_modules(elem))
INTO result
FROM jsonb_array_elements(obj) elem;
RETURN result;
ELSE
RETURN obj;
END IF;
END;
$$;
-- Run on a flow_version_lite jsonb value. Returns an array of flow_node ids whose languages are SQL.
CREATE OR REPLACE FUNCTION find_sql_flow_nodes_ids(obj jsonb)
RETURNS BIGINT[]
LANGUAGE plpgsql
AS $$
DECLARE
result BIGINT[] := '{}';
k text;
v jsonb;
BEGIN
IF jsonb_typeof(obj) = 'object' THEN
IF obj->>'language' IN ('bigquery', 'postgresql', 'duckdb', 'mssql', 'oracledb', 'snowflake', 'mysql') AND jsonb_typeof(obj->'id') = 'number' THEN
result := result || (obj->>'id')::BIGINT;
END IF;
FOR k, v IN SELECT * FROM jsonb_each(obj)
LOOP
result := result || find_sql_flow_nodes_ids(v);
END LOOP;
ELSIF jsonb_typeof(obj) = 'array' AND jsonb_array_length(obj) > 0 THEN
SELECT array_agg(result_ids)
INTO result
FROM jsonb_array_elements(obj) elem, unnest(find_sql_flow_nodes_ids(elem)) as result_ids;
END IF;
RETURN result;
END;
$$;
DO $$
BEGIN
UPDATE app_version SET value = update_all_modules(value::jsonb)::json;
UPDATE draft SET value = update_all_modules(value::jsonb)::json;
UPDATE flow SET value = update_all_modules(value);
UPDATE flow_version SET value = update_all_modules(value);
UPDATE flow_node SET code = update_string(code) WHERE id IN (
SELECT v FROM flow_version_lite, unnest(find_sql_flow_nodes_ids(value)) as v
);
UPDATE app_script SET code = update_string(code) WHERE id IN (
SELECT v FROM app_version_lite, unnest(find_sql_flow_nodes_ids(value)) as v
);
UPDATE script SET content = update_string(content) WHERE language IN ('bigquery', 'postgresql', 'duckdb', 'mssql', 'oracledb', 'snowflake', 'mysql');
EXCEPTION WHEN OTHERS THEN
-- ✅ LOG ERROR WITHOUT STOPPING THE MIGRATION
RAISE WARNING 'Migration failed: %', SQLERRM;
END;
$$;
DROP FUNCTION IF EXISTS update_all_modules(jsonb);
DROP FUNCTION IF EXISTS update_string(text);
DROP FUNCTION IF EXISTS find_sql_flow_nodes_ids(jsonb);

View File

@@ -0,0 +1,2 @@
-- Rollback: Remove job_isolation column from worker_ping table
ALTER TABLE worker_ping DROP COLUMN IF EXISTS job_isolation;

View File

@@ -0,0 +1,4 @@
-- Add job_isolation column to worker_ping table
-- This tracks which job isolation method the worker is using: 'nsjail', 'unshare', or 'none'
-- Nullable for backwards compatibility - old workers will report NULL
ALTER TABLE worker_ping ADD COLUMN job_isolation TEXT;

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
ALTER TABLE email_trigger ADD COLUMN enabled BOOLEAN DEFAULT TRUE NOT NULL;

View File

@@ -0,0 +1,3 @@
-- Add down migration script here
DROP TABLE IF EXISTS unique_ext_jwt_token;

View File

@@ -0,0 +1,9 @@
-- Add up migration script here
CREATE TABLE IF NOT EXISTS unique_ext_jwt_token (
jwt_hash BIGINT PRIMARY KEY NOT NULL,
last_used_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_unique_ext_jwt_token_last_used_at ON unique_ext_jwt_token(last_used_at);

File diff suppressed because it is too large Load Diff

View File

@@ -44,23 +44,41 @@ fn parse_default_expr(e: Expression) -> Option<Value> {
pub fn parse_php_signature(
code: &str,
override_main: Option<String>,
override_entrypoint: Option<String>,
) -> anyhow::Result<MainArgSignature> {
let main_name = override_main.unwrap_or("main".to_string());
let entrypoint_fn_name = override_entrypoint.unwrap_or("main".to_string());
let ast = parser::parse(code)
.map_err(|e| anyhow::anyhow!("Error parsing code: {}", e.to_string()))?;
let params = ast.into_iter().find_map(|x| match x {
Statement::Function(FunctionStatement {
name,
parameters: FunctionParameterList { parameters, .. },
..
}) if name.to_string() == main_name => Some(parameters),
_ => None,
});
let mut entrypoint_params = None;
let mut has_preprocessor = None;
for node in ast.into_iter() {
match node {
Statement::Function(FunctionStatement {
name,
parameters: FunctionParameterList { parameters, .. },
..
}) => {
let fn_name = name.to_string();
if let Some(params) = params {
if has_preprocessor.is_none() && fn_name == "preprocessor" {
has_preprocessor = Some(true);
}
if entrypoint_params.is_none() && fn_name == entrypoint_fn_name {
entrypoint_params = Some(parameters);
}
if has_preprocessor.is_some() && entrypoint_params.is_some() {
break;
}
}
_ => {}
};
}
if let Some(params) = entrypoint_params {
let args = params
.into_iter()
.map(|x| {
@@ -82,7 +100,7 @@ pub fn parse_php_signature(
star_kwargs: false,
args,
no_main_func: Some(false),
has_preprocessor: None,
has_preprocessor,
})
} else {
Ok(MainArgSignature {
@@ -90,7 +108,7 @@ pub fn parse_php_signature(
star_kwargs: false,
args: vec![],
no_main_func: Some(true),
has_preprocessor: None,
has_preprocessor,
})
}
}

View File

@@ -134,8 +134,11 @@ pub fn parse_graphql(code: &str) -> String {
#[cfg(feature = "php-parser")]
#[wasm_bindgen]
pub fn parse_php(code: &str) -> String {
wrap_sig(windmill_parser_php::parse_php_signature(code, None))
pub fn parse_php(code: &str, main_override: Option<String>) -> String {
wrap_sig(windmill_parser_php::parse_php_signature(
code,
main_override,
))
}
#[cfg(feature = "rust-parser")]

View File

@@ -1,6 +1,7 @@
#!/bin/bash
# Run the command repeatedly until it fails (exits with non-zero code)
cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd ..
while true; do
DISABLE_EMBEDDING=true \
RUST_LOG=info \
@@ -9,7 +10,7 @@ while true; do
GO_PATH=$(which go) \
UV_PATH=$(which uv) \
CARGO_PATH=$(which cargo) \
cargo test --features enterprise,deno_core,license,python,rust,scoped_cache \
cargo test --features enterprise,deno_core,license,python,duckdb,rust,scoped_cache \
-- --nocapture --test-threads=8 | tee /tmp/test.log
# Capture the exit code of the cargo test command (not tee)

View File

@@ -3,7 +3,7 @@
pub use crate::ee::*;
#[cfg(not(feature = "private"))]
pub async fn set_license_key(_license_key: String) -> () {
pub async fn set_license_key(_license_key: String, _db: Option<&windmill_common::db::DB>) -> () {
// Implementation is not open source
}

View File

@@ -113,6 +113,10 @@ pub mod ee;
mod ee_oss;
mod monitor;
// Windows service support - EE feature
#[cfg(all(windows, feature = "enterprise", feature = "private"))]
mod windows_service_ee;
pub fn setup_deno_runtime() -> anyhow::Result<()> {
// https://github.com/denoland/deno/blob/main/cli/main.rs#L477
#[cfg(feature = "deno_core")]
@@ -207,6 +211,17 @@ lazy_static::lazy_static! {
}
pub fn main() -> anyhow::Result<()> {
// On Windows with enterprise feature, check if running as a service
#[cfg(all(windows, feature = "enterprise", feature = "private"))]
{
if windows_service_ee::is_running_as_service() {
// Run as Windows service with SCM handlers
return windows_service_ee::run_as_windows_service()
.map_err(|e| anyhow::anyhow!("Failed to run as Windows service: {}", e));
}
}
// Normal execution (console/foreground mode)
setup_deno_runtime()?;
create_and_run_current_thread_inner(windmill_main())
}

View File

@@ -79,7 +79,7 @@ use windmill_common::{
OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED, SERVICE_LOG_RETENTION_SECS,
};
use windmill_common::{client::AuthedClient, global_settings::APP_WORKSPACED_ROUTE_SETTING};
use windmill_queue::{SameWorkerPayload, cancel_job, get_queued_job_v2};
use windmill_queue::{cancel_job, get_queued_job_v2, SameWorkerPayload};
use windmill_worker::{
handle_job_error, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES,
INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN,
@@ -1271,7 +1271,7 @@ pub async fn reload_license_key(conn: &Connection) -> anyhow::Result<()> {
tracing::error!("Could not parse LICENSE_KEY found: {:#?}", &q);
}
};
set_license_key(value).await;
set_license_key(value, conn.as_sql()).await;
Ok(())
}
@@ -2219,7 +2219,6 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, node_n
);
}
timeouts
};
@@ -2253,7 +2252,6 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, node_n
}
}
let timeouts = non_restartable_jobs
.into_iter()
.map(|x| (x, ErrorMessage::RestartDisabled))
@@ -2289,59 +2287,59 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, node_n
continue;
}
if let Some(job) = job.unwrap() {
let label = if job.permissioned_as != format!("u/{}", job.created_by)
&& job.permissioned_as != job.created_by
{
format!("ephemeral-script-end-user-{}", job.created_by)
} else {
"ephemeral-script".to_string()
};
let token = create_token_for_owner(
&db,
&job.workspace_id,
&job.permissioned_as,
&label,
*SCRIPT_TOKEN_EXPIRY,
&job.permissioned_as_email,
&job.id,
None,
Some(format!("handle_zombie_jobs")),
)
.await
.expect("could not create job token");
let label = if job.permissioned_as != format!("u/{}", job.created_by)
&& job.permissioned_as != job.created_by
{
format!("ephemeral-script-end-user-{}", job.created_by)
} else {
"ephemeral-script".to_string()
};
let token = create_token_for_owner(
&db,
&job.workspace_id,
&job.permissioned_as,
&label,
*SCRIPT_TOKEN_EXPIRY,
&job.permissioned_as_email,
&job.id,
None,
Some(format!("handle_zombie_jobs")),
)
.await
.expect("could not create job token");
let client = AuthedClient::new(
base_internal_url.to_string(),
job.workspace_id.to_string(),
token,
None,
);
let client = AuthedClient::new(
base_internal_url.to_string(),
job.workspace_id.to_string(),
token,
None,
);
let error_message = format!(
let error_message = format!(
"Job timed out after no ping from job since {} (ZOMBIE_JOB_TIMEOUT: {}, reason: {:?}).\nThis likely means that the job died on worker {}, OOM are a common reason for worker crashes.\nCheck the workers around the time of the last ping and the exit code if any.",
job.last_ping.unwrap_or_default(),
*ZOMBIE_JOB_TIMEOUT,
error_kind.to_string(),
job.worker.clone().unwrap_or_default(),
);
let memory_peak = job.memory_peak.unwrap_or(0);
let _ = handle_job_error(
db,
&client,
&windmill_queue::MiniCompletedJob::from(job),
memory_peak,
None,
error::Error::ExecutionErr(error_message),
true,
Some(&same_worker_tx_never_used),
"",
node_name,
send_result_never_used,
#[cfg(feature = "benchmark")]
&mut windmill_common::bench::BenchmarkIter::new(),
)
.await;
}
let memory_peak = job.memory_peak.unwrap_or(0);
let _ = handle_job_error(
db,
&client,
&windmill_queue::MiniCompletedJob::from(job),
memory_peak,
None,
error::Error::ExecutionErr(error_message),
true,
Some(&same_worker_tx_never_used),
"",
node_name,
send_result_never_used,
#[cfg(feature = "benchmark")]
&mut windmill_common::bench::BenchmarkIter::new(),
)
.await;
}
}
}

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -euo pipefail
script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root_dirpath="$(cd "${script_dirpath}/.." && pwd)"

View File

@@ -208,6 +208,7 @@ impl RunJob {
false,
None,
debounce_job_id_o,
None,
)
.await
.expect("push has to succeed");

View File

@@ -421,14 +421,16 @@ def main():
path: "f/rel/root_flow_renamed".into(),
summary: "".into(),
description: None,
value: to_raw_value(&serde_json::from_str::<serde_json::Value>(
&serde_json::to_string(flow.value())
.unwrap()
.replace("nstep1", "Foxes")
.replace("nstep2_2", "like")
.replace("nstep_4_1", "Emeralds"),
)
.unwrap()),
value: to_raw_value(
&serde_json::from_str::<serde_json::Value>(
&serde_json::to_string(flow.value())
.unwrap()
.replace("nstep1", "Foxes")
.replace("nstep2_2", "like")
.replace("nstep_4_1", "Emeralds"),
)
.unwrap(),
),
schema: None,
draft_only: None,
tag: None,
@@ -437,7 +439,7 @@ def main():
deployment_message: None,
visible_to_runner_only: None,
on_behalf_of_email: None,
ws_error_handler_muted: None
ws_error_handler_muted: None,
})
.send()
.await
@@ -862,6 +864,7 @@ def main():
false,
None,
None,
None,
)
.await
.unwrap();
@@ -1021,6 +1024,7 @@ def main():
false,
None,
debounce_job_id_o,
None,
)
.await
.unwrap();
@@ -1198,6 +1202,7 @@ def main():
false,
None,
debounce_job_id_o,
None,
)
.await
.unwrap();
@@ -1705,6 +1710,7 @@ WHERE
false,
None,
None,
None,
)
.await
.unwrap();
@@ -1844,6 +1850,7 @@ WHERE
false,
None,
None,
None,
)
.await
.unwrap();
@@ -2274,6 +2281,7 @@ WHERE
false,
None,
None,
None,
)
.await
.unwrap();
@@ -2399,6 +2407,7 @@ WHERE
// false,
// None,
// None,
// None,
// )
// .await
// .unwrap();

View File

@@ -2769,7 +2769,7 @@ async fn test_result_format(db: Pool<Postgres>) -> anyhow::Result<()> {
let response = windmill_api::jobs::run_wait_result(
&db,
Uuid::parse_str(ordered_result_job_id).unwrap(),
"test-workspace".to_string(),
"test-workspace",
None,
"test-user",
)
@@ -2944,3 +2944,33 @@ async fn test_workflow_as_code(db: Pool<Postgres>) -> anyhow::Result<()> {
.await;
Ok(())
}
#[cfg(feature = "duckdb")]
#[sqlx::test(fixtures("base"))]
async fn test_duckdb_ffi(db: Pool<Postgres>) -> anyhow::Result<()> {
initialize_tracing().await;
let server = ApiServer::start(db.clone()).await?;
let content = "-- result_collection=last_statement_first_row_scalar\nSELECT 'Hello world!';";
let flow: FlowValue = serde_json::from_value(serde_json::json!({
"modules": [{
"value": {
"type": "rawscript",
"language": "duckdb",
"content": content,
},
}],
}))
.unwrap();
let result =
RunJob::from(JobPayload::RawFlow { value: flow.clone(), path: None, restarted_from: None })
.run_until_complete(&db, false, server.addr.port())
.await
.json_result()
.unwrap();
assert_eq!(result, serde_json::json!("Hello world!"));
Ok(())
}

View File

@@ -94,6 +94,7 @@ tempfile.workspace = true
tokio-util.workspace = true
tokio-tar.workspace = true
tokio-postgres.workspace = true
postgres-native-tls.workspace = true
hmac.workspace = true
cookie.workspace = true
sha2.workspace = true

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.573.1
version: 1.580.0
title: Windmill API
contact:
@@ -572,12 +572,12 @@ paths:
use_case:
type: string
responses:
'200':
"200":
description: Onboarding data submitted successfully
content:
application/json:
schema:
type: string
type: string
/w/{workspace}/users/delete/{username}:
delete:
@@ -728,6 +728,32 @@ paths:
schema:
type: string
/w/{workspace}/workspaces/create_workspace_fork_branch:
post:
summary: create forked workspace branch with git sync
operationId: createWorkspaceForkGitBranch
tags:
- workspace
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: new forked workspace
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateWorkspaceFork"
responses:
"201":
description: forked workspace branch created
content:
application/json:
schema:
type: array
items:
type: string
format: uuid
/w/{workspace}/workspaces/create_fork:
post:
summary: create forked workspace
@@ -5930,6 +5956,80 @@ paths:
application/json:
schema: {}
/w/{workspace}/jobs/run_wait_result/fv/{version}:
post:
summary: run flow by version and wait until completion
operationId: runWaitResultFlowByVersion
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: version
description: flow version ID
in: path
required: true
schema:
type: integer
format: int64
- $ref: "#/components/parameters/IncludeHeader"
- $ref: "#/components/parameters/QueueLimit"
- $ref: "#/components/parameters/NewJobId"
- $ref: "#/components/parameters/SkipPreprocessor"
- name: memory_id
description: memory ID for chat-enabled flows
in: query
schema:
type: string
format: uuid
requestBody:
description: script args
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ScriptArgs"
responses:
"200":
description: job result
content:
application/json:
schema: {}
get:
summary: run flow by version with GET and wait until completion
operationId: runWaitResultFlowByVersionGet
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: version
description: flow version ID
in: path
required: true
schema:
type: integer
format: int64
- $ref: "#/components/parameters/IncludeHeader"
- $ref: "#/components/parameters/QueueLimit"
- $ref: "#/components/parameters/Payload"
- $ref: "#/components/parameters/NewJobId"
- $ref: "#/components/parameters/SkipPreprocessor"
- name: memory_id
description: memory ID for chat-enabled flows
in: query
schema:
type: string
format: uuid
responses:
"200":
description: job result
content:
application/json:
schema: {}
/w/{workspace}/jobs/run_and_stream/f/{path}:
post:
summary: run flow by path and stream updates via SSE
@@ -6006,6 +6106,94 @@ paths:
schema:
type: string
/w/{workspace}/jobs/run_and_stream/fv/{version}:
post:
summary: run flow by version and stream updates via SSE
operationId: runAndStreamFlowByVersion
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: version
description: flow version ID
in: path
required: true
schema:
type: integer
format: int64
- $ref: "#/components/parameters/IncludeHeader"
- $ref: "#/components/parameters/QueueLimit"
- $ref: "#/components/parameters/NewJobId"
- $ref: "#/components/parameters/SkipPreprocessor"
- name: memory_id
description: memory ID for chat-enabled flows
in: query
schema:
type: string
format: uuid
- name: poll_delay_ms
description: delay between polling for job updates in milliseconds
in: query
schema:
type: integer
format: int64
requestBody:
description: flow args
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ScriptArgs"
responses:
"200":
description: server-sent events stream of job updates
content:
text/event-stream:
schema:
type: string
get:
summary: run flow by version with GET and stream updates via SSE
operationId: runAndStreamFlowByVersionGet
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: version
description: flow version ID
in: path
required: true
schema:
type: integer
format: int64
- $ref: "#/components/parameters/IncludeHeader"
- $ref: "#/components/parameters/QueueLimit"
- $ref: "#/components/parameters/Payload"
- $ref: "#/components/parameters/NewJobId"
- $ref: "#/components/parameters/SkipPreprocessor"
- name: memory_id
description: memory ID for chat-enabled flows
in: query
schema:
type: string
format: uuid
- name: poll_delay_ms
description: delay between polling for job updates in milliseconds
in: query
schema:
type: integer
format: int64
responses:
"200":
description: server-sent events stream of job updates
content:
text/event-stream:
schema:
type: string
/w/{workspace}/jobs/run_and_stream/p/{path}:
post:
summary: run script by path and stream updates via SSE
@@ -7654,6 +7842,64 @@ paths:
type: string
format: uuid
/w/{workspace}/jobs/run/fv/{version}:
post:
summary: run flow by version
operationId: runFlowByVersion
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: version
description: flow version ID
in: path
required: true
schema:
type: integer
format: int64
- name: scheduled_for
description: when to schedule this job (leave empty for immediate run)
in: query
schema:
type: string
format: date-time
- name: scheduled_in_secs
description: schedule the script to execute in the number of seconds starting now
in: query
schema:
type: integer
- $ref: "#/components/parameters/SkipPreprocessor"
- $ref: "#/components/parameters/ParentJob"
- $ref: "#/components/parameters/WorkerTag"
- $ref: "#/components/parameters/NewJobId"
- $ref: "#/components/parameters/IncludeHeader"
- name: invisible_to_owner
description: make the run invisible to the the flow owner (default false)
in: query
schema:
type: boolean
- name: memory_id
description: memory ID for chat-enabled flows
in: query
schema:
type: string
format: uuid
requestBody:
description: flow args
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ScriptArgs"
responses:
"201":
description: job created
content:
text/plain:
schema:
type: string
format: uuid
/w/{workspace}/jobs/run/batch_rerun_jobs:
post:
summary: re-run multiple jobs
@@ -7968,6 +8214,12 @@ paths:
schema:
type: boolean
- $ref: "#/components/parameters/NewJobId"
- name: memory_id
description: memory ID for chat-enabled flows
in: query
schema:
type: string
format: uuid
requestBody:
description: preview
@@ -7994,6 +8246,12 @@ paths:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: memory_id
description: memory ID for chat-enabled flows
in: query
schema:
type: string
format: uuid
requestBody:
description: preview
@@ -8413,6 +8671,7 @@ paths:
- $ref: "#/components/parameters/ResultFilter"
- $ref: "#/components/parameters/AllowWildcards"
- $ref: "#/components/parameters/PerPage"
- $ref: "#/components/parameters/JobTriggerKind"
- name: is_skipped
description: is the job skipped
in: query
@@ -9904,6 +10163,34 @@ paths:
schema:
type: boolean
/w/{workspace}/http_triggers/setenabled/{path}:
post:
summary: enable/disable http trigger
operationId: setHttpTriggerEnabled
tags:
- http_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
required:
- enabled
responses:
"200":
description: http trigger enable/disable
content:
text/plain:
schema:
type: string
/w/{workspace}/websocket_triggers/create:
post:
summary: create websocket trigger
@@ -11711,6 +11998,33 @@ paths:
application/json:
schema:
type: boolean
/w/{workspace}/email_triggers/setenabled/{path}:
post:
summary: enable/disable email trigger
operationId: setEmailTriggerEnabled
tags:
- email_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
required:
- enabled
responses:
"200":
description: email trigger enable/disable
content:
text/plain:
schema:
type: string
/groups/list:
get:
@@ -14386,6 +14700,7 @@ paths:
- $ref: "#/components/parameters/AllowWildcards"
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PerPage"
- $ref: "#/components/parameters/JobTriggerKind"
- name: is_skipped
description: is the job skipped
in: query
@@ -14824,6 +15139,27 @@ components:
in: query
schema:
type: integer
JobTriggerKind:
name: trigger_kind
description: trigger kind (schedule, http, websocket...)
in: query
schema:
type: string
enum:
- webhook
- default_email
- email
- schedule
- http
- websocket
- postgres
- kafka
- nats
- mqtt
- sqs
- gcp
- poll
- cli
OrderDesc:
name: order_desc
description: order by desc order (default true)
@@ -14985,8 +15321,7 @@ components:
CreatedAfterQueue:
name: created_after_queue
description:
filter on jobs created after X for jobs in the queue only
description: filter on jobs created after X for jobs in the queue only
in: query
schema:
type: string
@@ -14994,8 +15329,7 @@ components:
CreatedBeforeQueue:
name: created_before_queue
description:
filter on jobs created before X for jobs in the queue only
description: filter on jobs created before X for jobs in the queue only
in: query
schema:
type: string
@@ -15290,6 +15624,7 @@ components:
groq,
openrouter,
togetherai,
aws_bedrock,
customai,
]
@@ -16441,30 +16776,30 @@ components:
ScriptLang:
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,
# for related places search: ADD_NEW_LANG
]
python3,
deno,
go,
bash,
powershell,
postgresql,
mysql,
bigquery,
snowflake,
mssql,
oracledb,
graphql,
nativets,
bun,
php,
rust,
ansible,
csharp,
nu,
java,
ruby,
duckdb,
# for related places search: ADD_NEW_LANG
]
Preview:
type: object
@@ -16941,6 +17276,8 @@ components:
format: date-time
is_flow:
type: boolean
enabled:
type: boolean
required:
- path
- script_path
@@ -16950,6 +17287,7 @@ components:
- edited_by
- edited_at
- is_flow
- enabled
AuthenticationMethod:
type: string
@@ -17175,6 +17513,8 @@ components:
type: boolean
wrap_body:
type: boolean
enabled:
type: boolean
raw_string:
type: boolean
error_handler_path:
@@ -17300,8 +17640,6 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
filters:
type: array
items:
@@ -17332,7 +17670,6 @@ components:
required:
- url
- enabled
- filters
- can_return_message
- can_return_error_result
@@ -17521,17 +17858,13 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
error_handler_path:
type: string
error_handler_args:
$ref: "#/components/schemas/ScriptArgs"
retry:
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
required:
- enabled
- subscribe_topics
- mqtt_resource_path
@@ -17653,8 +17986,6 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
error_handler_path:
type: string
error_handler_args:
@@ -17665,7 +17996,6 @@ components:
- gcp_resource_path
- topic_id
- subscription_id
- enabled
- delivery_type
- subscription_mode
@@ -17767,8 +18097,6 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
error_handler_path:
type: string
error_handler_args:
@@ -17779,7 +18107,6 @@ components:
required:
- queue_url
- aws_resource_path
- enabled
- aws_auth_resource_type
LoggedWizardStatus:
@@ -17976,8 +18303,6 @@ components:
- $ref: "#/components/schemas/TriggerExtraProperty"
type: object
properties:
enabled:
type: boolean
postgres_resource_path:
type: string
publication_name:
@@ -17998,7 +18323,6 @@ components:
retry:
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
required:
- enabled
- postgres_resource_path
- replication_slot_name
- publication_name
@@ -18089,8 +18413,6 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
error_handler_path:
type: string
error_handler_args:
@@ -18102,7 +18424,6 @@ components:
- kafka_resource_path
- group_id
- topics
- enabled
NewKafkaTrigger:
type: object
@@ -18194,8 +18515,6 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
error_handler_path:
type: string
error_handler_args:
@@ -18207,7 +18526,6 @@ components:
- nats_resource_path
- use_jetstream
- subjects
- enabled
NewNatsTrigger:
type: object
@@ -18321,7 +18639,8 @@ components:
$ref: "#/components/schemas/ScriptArgs"
retry:
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
enabled:
type: boolean
required:
- path
- script_path
@@ -18483,6 +18802,8 @@ components:
type: number
wm_memory_usage:
type: number
job_isolation:
type: string
required:
- worker
- worker_instance
@@ -18660,6 +18981,8 @@ components:
properties:
lock_error_logs:
type: string
version_id:
type: number
ExtraPerms:
type: object

View File

@@ -1,3 +1,4 @@
use crate::bedrock;
use crate::db::{ApiAuthed, DB};
use axum::{body::Bytes, extract::Path, response::IntoResponse, routing::post, Extension, Router};
@@ -6,12 +7,12 @@ use quick_cache::sync::Cache;
use reqwest::{Client, RequestBuilder};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
use windmill_common::variables::get_variable_or_self;
use std::collections::HashMap;
use windmill_audit::{audit_oss::audit_log, ActionKind};
use windmill_common::ai_providers::{AIProvider, ProviderConfig, ProviderModel, AZURE_API_VERSION};
use windmill_common::ai_providers::{AIProvider, ProviderConfig, ProviderModel};
use windmill_common::error::{to_anyhow, Error, Result};
use windmill_common::utils::configure_client;
use windmill_common::variables::get_variable_or_self;
lazy_static::lazy_static! {
static ref HTTP_CLIENT: Client = configure_client(reqwest::ClientBuilder::new()
@@ -66,6 +67,7 @@ struct AIStandardResource {
#[serde(alias = "apiKey")]
api_key: Option<String>,
organization_id: Option<String>,
region: Option<String>,
}
#[derive(Deserialize, Debug)]
@@ -98,7 +100,9 @@ impl AIRequestConfig {
) -> Result<Self> {
let (api_key, access_token, organization_id, base_url, user) = match resource {
AIResource::Standard(resource) => {
let base_url = provider.get_base_url(resource.base_url, db).await?;
let base_url = provider
.get_base_url(resource.base_url, resource.region, db)
.await?;
let api_key = if let Some(api_key) = resource.api_key {
Some(get_variable_or_self(api_key, db, w_id).await?)
} else {
@@ -119,7 +123,7 @@ impl AIRequestConfig {
None
};
let token = Self::get_token_using_oauth(resource, db, w_id).await?;
let base_url = provider.get_base_url(None, db).await?;
let base_url = provider.get_base_url(None, None, db).await?;
(None, Some(token), None, base_url, user)
}
@@ -180,15 +184,34 @@ impl AIRequestConfig {
let is_azure = provider.is_azure_openai(base_url);
let is_anthropic = matches!(provider, AIProvider::Anthropic);
let is_anthropic_sdk = headers.get("X-Anthropic-SDK").is_some();
let is_bedrock = matches!(provider, AIProvider::AWSBedrock);
let url = if is_azure && method != Method::GET {
let model = AIProvider::extract_model_from_body(&body)?;
AIProvider::build_azure_openai_url(base_url, &model, path)
// Handle AWS Bedrock transformation
let (url, body) = if is_bedrock && method != Method::GET {
let (model, transformed_body, is_streaming) =
bedrock::transform_openai_to_bedrock(&body)?;
let endpoint = if is_streaming {
"converse-stream"
} else {
"converse"
};
let bedrock_url = format!("{}/model/{}/{}", base_url, model, endpoint);
(bedrock_url, transformed_body)
} else if is_bedrock && (path == "foundation-models" || path == "inference-profiles") {
// AWS Bedrock foundation-models and inference-profiles endpoints use different base URL (without -runtime)
let bedrock_base_url = base_url.replace("bedrock-runtime.", "bedrock.");
let bedrock_url = format!("{}/{}", bedrock_base_url, path);
(bedrock_url, body)
} else if is_azure {
let azure_url = AIProvider::build_azure_openai_url(base_url, path);
(azure_url, body)
} else if is_anthropic_sdk {
let truncated_base_url = base_url.trim_end_matches("/v1");
format!("{}/{}", truncated_base_url, path)
let anthropic_url = format!("{}/{}", truncated_base_url, path);
(anthropic_url, body)
} else {
format!("{}/{}", base_url, path)
let default_url = format!("{}/{}", base_url, path);
(default_url, body)
};
tracing::debug!("AI request URL: {}", url);
@@ -205,10 +228,6 @@ impl AIRequestConfig {
request = request.body(body);
if is_azure {
request = request.query(&[("api-version", AZURE_API_VERSION)])
}
if let Some(api_key) = self.api_key {
if is_azure {
request = request.header("api-key", api_key.clone())
@@ -316,7 +335,7 @@ async fn global_proxy(
return Err(Error::BadRequest("API key is required".to_string()));
};
let base_url = provider.get_base_url(None, &db).await?;
let base_url = provider.get_base_url(None, None, &db).await?;
let url = format!("{}/{}", base_url, ai_path);
@@ -446,6 +465,21 @@ async fn proxy(
}
};
// Extract model and streaming flag for Bedrock transformation (only for POST requests)
let (model, is_streaming) =
if matches!(provider, AIProvider::AWSBedrock) && method == Method::POST {
#[derive(Deserialize, Debug)]
struct BedrockRequest {
model: String,
stream: bool,
}
let parsed: BedrockRequest = serde_json::from_slice(&body)
.map_err(|e| Error::internal_err(format!("Failed to parse request body: {}", e)))?;
(Some(parsed.model), parsed.stream)
} else {
(None, false)
};
let request = request_config.prepare_request(&provider, &ai_path, method, headers, body)?;
let response = request.send().await.map_err(to_anyhow)?;
@@ -469,8 +503,45 @@ async fn proxy(
return Err(Error::AIError(err_msg));
}
let status_code = response.status();
let headers = response.headers().clone();
let stream = response.bytes_stream();
Ok((status_code, headers, axum::body::Body::from_stream(stream)))
// Transform Bedrock responses back to OpenAI format
if matches!(provider, AIProvider::AWSBedrock) && model.is_some() {
if is_streaming {
// Transform streaming response
use http::StatusCode;
let mut response_headers = HeaderMap::new();
response_headers.insert("content-type", "text/event-stream".parse().unwrap());
response_headers.insert("cache-control", "no-cache".parse().unwrap());
response_headers.insert("connection", "keep-alive".parse().unwrap());
let stream = response.bytes_stream();
let transformed_stream =
bedrock::transform_bedrock_stream_to_openai(stream, model.unwrap());
Ok((
StatusCode::OK,
response_headers,
axum::body::Body::from_stream(transformed_stream),
))
} else {
// Transform non-streaming response
let transformed_body =
bedrock::transform_bedrock_to_openai(response, model.unwrap()).await?;
let mut response_headers = HeaderMap::new();
response_headers.insert("content-type", "application/json".parse().unwrap());
Ok((
http::StatusCode::OK,
response_headers,
axum::body::Body::from(transformed_body),
))
}
} else {
// Pass through for other providers
let status_code = response.status();
let headers = response.headers().clone();
let stream = response.bytes_stream();
Ok((status_code, headers, axum::body::Body::from_stream(stream)))
}
}

View File

@@ -1241,6 +1241,7 @@ async fn create_app_internal<'a>(
false,
None,
None,
None,
)
.await?;
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
@@ -1630,6 +1631,7 @@ async fn update_app_internal<'a>(
false,
None,
None,
None,
)
.await?;
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
@@ -1949,6 +1951,7 @@ async fn execute_component(
false,
end_user_email,
None,
None,
)
.await?;
tx.commit().await?;

View File

@@ -90,6 +90,7 @@ impl AuthCache {
w_id.as_ref(),
token.trim_start_matches("jwt_ext_"),
self.ext_jwks.clone(),
&self.db,
)
.await
{

View File

@@ -0,0 +1,602 @@
use axum::body::Bytes;
use bytes;
use futures;
use uuid;
use windmill_common::error::{Error, Result};
/// Transform OpenAI format request to AWS Bedrock Converse format
/// Returns: (model_id, transformed_body, is_streaming)
pub fn transform_openai_to_bedrock(body: &[u8]) -> Result<(String, Bytes, bool)> {
use serde_json::Value;
// Parse the OpenAI request
let openai_req: Value = serde_json::from_slice(body)
.map_err(|e| Error::internal_err(format!("Failed to parse OpenAI request: {}", e)))?;
// Extract model and streaming flag
let model = openai_req["model"]
.as_str()
.ok_or_else(|| Error::BadRequest("Missing 'model' field in request".to_string()))?
.to_string();
let is_streaming = openai_req["stream"].as_bool().unwrap_or(false);
// Build Bedrock request
let mut bedrock_req = serde_json::json!({});
// Transform messages
if let Some(messages) = openai_req["messages"].as_array() {
let mut system_messages = Vec::new();
let mut conversation_messages = Vec::new();
for msg in messages {
let role = msg["role"].as_str().unwrap_or("");
match role {
"system" => {
// Extract system messages to separate array
if let Some(content) = msg["content"].as_str() {
system_messages.push(serde_json::json!({"text": content}));
}
}
"user" | "assistant" => {
// Normalize content to array format
let mut content = if let Some(text) = msg["content"].as_str() {
// Simple string → array of content blocks
vec![serde_json::json!({"text": text})]
} else if let Some(content_array) = msg["content"].as_array() {
// Already an array - transform each item
content_array
.iter()
.filter_map(|item| {
if let Some(text) = item["text"].as_str() {
Some(serde_json::json!({"text": text}))
} else if item["type"].as_str() == Some("text") {
Some(serde_json::json!({"text": item["text"]}))
} else if item["type"].as_str() == Some("image_url") {
// Transform image_url format if needed
// For now, pass through - may need more sophisticated handling
Some(item.clone())
} else {
None
}
})
.collect()
} else {
vec![]
};
// Handle tool_calls for assistant messages (OpenAI → Bedrock toolUse)
if role == "assistant" {
if let Some(tool_calls) = msg["tool_calls"].as_array() {
for tool_call in tool_calls {
if tool_call["type"].as_str() == Some("function") {
let tool_use_id = tool_call["id"].as_str().unwrap_or("");
let function_name =
tool_call["function"]["name"].as_str().unwrap_or("");
let arguments_str =
tool_call["function"]["arguments"].as_str().unwrap_or("{}");
// Parse arguments JSON string to object
let input = serde_json::from_str::<Value>(arguments_str)
.map_err(|e| {
Error::internal_err(format!(
"Failed to parse tool call arguments: {}",
e
))
})?;
content.push(serde_json::json!({
"toolUse": {
"toolUseId": tool_use_id,
"name": function_name,
"input": input
}
}));
}
}
}
}
// Only add message if it has content
if !content.is_empty() {
conversation_messages.push(serde_json::json!({
"role": role,
"content": content
}));
}
}
"tool" => {
// Transform tool response to Bedrock format
let tool_call_id = msg["tool_call_id"].as_str().unwrap_or("");
let content = msg["content"].as_str().unwrap_or("");
// Try to parse content as JSON
// Bedrock requires json field to be an object, not a primitive or array
let tool_result_content =
if let Ok(json_content) = serde_json::from_str::<Value>(content) {
if json_content.is_object() {
vec![serde_json::json!({"json": json_content})]
} else {
// Wrap primitives and arrays in an object
vec![serde_json::json!({"json": {"result": json_content}})]
}
} else {
vec![serde_json::json!({"text": content})]
};
conversation_messages.push(serde_json::json!({
"role": "user",
"content": [{
"toolResult": {
"toolUseId": tool_call_id,
"content": tool_result_content
}
}]
}));
}
_ => {}
}
}
if !system_messages.is_empty() {
bedrock_req["system"] = Value::Array(system_messages);
}
bedrock_req["messages"] = Value::Array(conversation_messages);
}
// Transform inference parameters
let mut inference_config = serde_json::json!({});
if let Some(max_tokens) = openai_req["max_tokens"].as_i64() {
inference_config["maxTokens"] = Value::Number(max_tokens.into());
}
if let Some(temperature) = openai_req["temperature"].as_f64() {
inference_config["temperature"] = serde_json::json!(temperature);
}
if let Some(top_p) = openai_req["top_p"].as_f64() {
inference_config["topP"] = serde_json::json!(top_p);
}
if let Some(stop) = openai_req["stop"].as_array() {
let stop_sequences: Vec<String> = stop
.iter()
.filter_map(|s| s.as_str().map(|s| s.to_string()))
.collect();
if !stop_sequences.is_empty() {
inference_config["stopSequences"] =
Value::Array(stop_sequences.into_iter().map(Value::String).collect());
}
}
if !inference_config.as_object().unwrap().is_empty() {
bedrock_req["inferenceConfig"] = inference_config;
}
// Transform tools if present
if let Some(tools) = openai_req["tools"].as_array() {
let mut bedrock_tools = Vec::new();
for tool in tools {
if tool["type"].as_str() == Some("function") {
if let Some(function) = tool["function"].as_object() {
bedrock_tools.push(serde_json::json!({
"toolSpec": {
"name": function.get("name"),
"description": function.get("description")
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.unwrap_or("Tool function"),
"inputSchema": {
"json": function.get("parameters")
}
}
}));
}
}
}
if !bedrock_tools.is_empty() {
let mut tool_config = serde_json::json!({
"tools": bedrock_tools
});
// Transform tool_choice
if let Some(tool_choice) = openai_req.get("tool_choice") {
if tool_choice == "auto" {
tool_config["toolChoice"] = serde_json::json!({"auto": {}});
} else if tool_choice == "required" {
tool_config["toolChoice"] = serde_json::json!({"any": {}});
} else if let Some(obj) = tool_choice.as_object() {
if obj.get("type").and_then(|v| v.as_str()) == Some("function") {
if let Some(function) = obj.get("function").and_then(|v| v.as_object()) {
if let Some(name) = function.get("name").and_then(|v| v.as_str()) {
tool_config["toolChoice"] = serde_json::json!({
"tool": {"name": name}
});
}
}
}
}
}
bedrock_req["toolConfig"] = tool_config;
}
}
let transformed_body = serde_json::to_vec(&bedrock_req)
.map_err(|e| Error::internal_err(format!("Failed to serialize Bedrock request: {}", e)))?
.into();
Ok((model, transformed_body, is_streaming))
}
/// Transform AWS Bedrock Converse response to OpenAI format
pub async fn transform_bedrock_to_openai(
response: reqwest::Response,
model: String,
) -> Result<Bytes> {
use serde_json::Value;
let bedrock_resp: Value = response
.json()
.await
.map_err(|e| Error::internal_err(format!("Failed to parse Bedrock response: {}", e)))?;
// Generate unique ID and timestamp
let id = format!("chatcmpl-{}", uuid::Uuid::new_v4().simple());
let created = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
// Extract stop reason and map to finish_reason
let stop_reason = bedrock_resp["stopReason"].as_str().unwrap_or("end_turn");
let finish_reason = match stop_reason {
"end_turn" => "stop",
"max_tokens" => "length",
"tool_use" => "tool_calls",
"stop_sequence" => "stop",
"guardrail_intervened" | "content_filtered" => "content_filter",
_ => "stop",
};
// Extract message content
let message_content = &bedrock_resp["output"]["message"]["content"];
let mut text_content = String::new();
let mut tool_calls = Vec::new();
if let Some(content_array) = message_content.as_array() {
for (_index, block) in content_array.iter().enumerate() {
if let Some(text) = block["text"].as_str() {
text_content.push_str(text);
} else if let Some(tool_use) = block.get("toolUse") {
// Transform tool use to OpenAI tool_calls format
let tool_call_id = tool_use["toolUseId"].as_str().unwrap_or("");
let name = tool_use["name"].as_str().unwrap_or("");
let input = &tool_use["input"];
tool_calls.push(serde_json::json!({
"id": tool_call_id,
"type": "function",
"function": {
"name": name,
"arguments": serde_json::to_string(input).unwrap_or_default()
}
}));
}
}
}
// Build the message
let message = if !tool_calls.is_empty() {
serde_json::json!({
"role": "assistant",
"content": if text_content.is_empty() { Value::Null } else { Value::String(text_content) },
"tool_calls": tool_calls
})
} else {
serde_json::json!({
"role": "assistant",
"content": text_content
})
};
// Extract usage information
let usage = if let Some(usage_data) = bedrock_resp.get("usage") {
serde_json::json!({
"prompt_tokens": usage_data["inputTokens"].as_i64().unwrap_or(0),
"completion_tokens": usage_data["outputTokens"].as_i64().unwrap_or(0),
"total_tokens": usage_data["totalTokens"].as_i64().unwrap_or(0)
})
} else {
serde_json::json!({
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0
})
};
// Build OpenAI-format response
let openai_resp = serde_json::json!({
"id": id,
"object": "chat.completion",
"created": created,
"model": model,
"choices": [{
"index": 0,
"message": message,
"finish_reason": finish_reason
}],
"usage": usage
});
let response_body = serde_json::to_vec(&openai_resp)
.map_err(|e| Error::internal_err(format!("Failed to serialize OpenAI response: {}", e)))?
.into();
Ok(response_body)
}
/// Transform AWS Bedrock streaming response to OpenAI SSE format
/// Bedrock uses AWS event stream binary format, not SSE
pub fn transform_bedrock_stream_to_openai(
stream: impl futures::Stream<Item = std::result::Result<bytes::Bytes, reqwest::Error>>
+ Send
+ 'static,
model: String,
) -> impl futures::Stream<Item = std::result::Result<bytes::Bytes, std::io::Error>> + Send {
use futures::stream::StreamExt;
use serde_json::Value;
use std::collections::HashMap;
let id = format!("chatcmpl-{}", uuid::Uuid::new_v4().simple());
let created = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
// State to track partial tool calls and binary buffer
struct StreamState {
id: String,
model: String,
created: u64,
tool_calls: HashMap<usize, (String, String, String)>, // index -> (id, name, args)
buffer: Vec<u8>, // Binary buffer for AWS event stream
}
let state = std::sync::Arc::new(tokio::sync::Mutex::new(StreamState {
id: id.clone(),
model: model.clone(),
created,
tool_calls: HashMap::new(),
buffer: Vec::new(),
}));
stream
.then(move |chunk_result| {
let state = state.clone();
async move {
match chunk_result {
Ok(chunk) => {
let mut state = state.lock().await;
state.buffer.extend_from_slice(&chunk);
let mut events = Vec::new();
// Parse AWS event stream messages from buffer
loop {
// Need at least 12 bytes for prelude (8) + prelude CRC (4)
if state.buffer.len() < 12 {
break;
}
// Read prelude: total_length (4 bytes) + headers_length (4 bytes)
let total_length = u32::from_be_bytes([
state.buffer[0],
state.buffer[1],
state.buffer[2],
state.buffer[3],
]) as usize;
// Check if we have the complete message
if state.buffer.len() < total_length {
break;
}
let headers_length = u32::from_be_bytes([
state.buffer[4],
state.buffer[5],
state.buffer[6],
state.buffer[7],
]) as usize;
// Skip prelude CRC (4 bytes after prelude)
let headers_start = 12;
let payload_start = headers_start + headers_length;
let payload_end = total_length - 4; // Exclude message CRC
// Parse headers to extract event type
let mut event_type = None;
let mut pos = headers_start;
while pos < payload_start {
if pos + 1 > state.buffer.len() {
break;
}
let name_len = state.buffer[pos] as usize;
pos += 1;
if pos + name_len > state.buffer.len() {
break;
}
let name = String::from_utf8_lossy(&state.buffer[pos..pos + name_len]).to_string();
pos += name_len;
if pos + 3 > state.buffer.len() {
break;
}
let value_type = state.buffer[pos];
pos += 1;
let value_len = u16::from_be_bytes([state.buffer[pos], state.buffer[pos + 1]]) as usize;
pos += 2;
if pos + value_len > state.buffer.len() {
break;
}
if value_type == 7 && name == ":event-type" {
event_type = Some(String::from_utf8_lossy(&state.buffer[pos..pos + value_len]).to_string());
}
pos += value_len;
}
// Extract JSON payload (copy to avoid borrow issues)
let payload = state.buffer[payload_start..payload_end].to_vec();
// Remove processed message from buffer
state.buffer.drain(0..total_length);
// Process the event
if let Some(evt_type) = event_type {
if let Ok(payload_str) = std::str::from_utf8(&payload) {
if let Ok(parsed_data) = serde_json::from_str::<Value>(payload_str) {
// Transform based on event type
match evt_type.as_str() {
"messageStart" => {
// No output for messageStart
}
"contentBlockStart" => {
let index = parsed_data["contentBlockIndex"].as_u64().unwrap_or(0) as usize;
if let Some(tool_use) = parsed_data["start"].get("toolUse") {
let tool_id = tool_use["toolUseId"].as_str().unwrap_or("").to_string();
let name = tool_use["name"].as_str().unwrap_or("").to_string();
state.tool_calls.insert(index, (tool_id.clone(), name.clone(), String::new()));
// Send initial tool call chunk
let chunk = serde_json::json!({
"id": state.id,
"object": "chat.completion.chunk",
"created": state.created,
"model": state.model,
"choices": [{
"index": 0,
"delta": {
"tool_calls": [{
"index": index,
"id": tool_id,
"type": "function",
"function": {
"name": name,
"arguments": ""
}
}]
},
"finish_reason": Value::Null
}]
});
events.push(Ok(bytes::Bytes::from(format!("data: {}\n\n", chunk))));
}
}
"contentBlockDelta" => {
let index = parsed_data["contentBlockIndex"].as_u64().unwrap_or(0) as usize;
if let Some(text) = parsed_data["delta"]["text"].as_str() {
// Text content delta
let chunk = serde_json::json!({
"id": state.id,
"object": "chat.completion.chunk",
"created": state.created,
"model": state.model,
"choices": [{
"index": 0,
"delta": {
"content": text
},
"finish_reason": Value::Null
}]
});
events.push(Ok(bytes::Bytes::from(format!("data: {}\n\n", chunk))));
} else if let Some(tool_use_input) = parsed_data["delta"]["toolUse"]["input"].as_str() {
// Tool use arguments delta
if let Some((_tool_id, _name, ref mut args)) = state.tool_calls.get_mut(&index) {
args.push_str(tool_use_input);
let chunk = serde_json::json!({
"id": state.id,
"object": "chat.completion.chunk",
"created": state.created,
"model": state.model,
"choices": [{
"index": 0,
"delta": {
"tool_calls": [{
"index": index,
"function": {
"arguments": tool_use_input
}
}]
},
"finish_reason": Value::Null
}]
});
events.push(Ok(bytes::Bytes::from(format!("data: {}\n\n", chunk))));
}
}
}
"contentBlockStop" => {
// No output needed
}
"messageStop" => {
let stop_reason = parsed_data["stopReason"].as_str().unwrap_or("end_turn");
let finish_reason = match stop_reason {
"end_turn" => "stop",
"max_tokens" => "length",
"tool_use" => "tool_calls",
"stop_sequence" => "stop",
"guardrail_intervened" | "content_filtered" => "content_filter",
_ => "stop",
};
let chunk = serde_json::json!({
"id": state.id,
"object": "chat.completion.chunk",
"created": state.created,
"model": state.model,
"choices": [{
"index": 0,
"delta": {},
"finish_reason": finish_reason
}]
});
events.push(Ok(bytes::Bytes::from(format!("data: {}\n\n", chunk))));
}
"metadata" => {
// Could include usage info here if needed
}
_ => {}
}
}
}
}
} // end loop
events
}
Err(e) => {
vec![Err(std::io::Error::new(
std::io::ErrorKind::Other,
e.to_string(),
))]
}
}
}
})
.flat_map(|events| futures::stream::iter(events))
.chain(futures::stream::iter(vec![
// Send [DONE] at the end
Ok(bytes::Bytes::from("data: [DONE]\n\n"))
]))
}

View File

@@ -222,6 +222,7 @@ async fn get_concurrent_intervals(
all_workspaces: _,
concurrency_key: Some(_),
allow_wildcards: None,
trigger_kind: _,
} => true,
_ => false,
};

View File

@@ -53,6 +53,9 @@ lazy_static::lazy_static! {
).to_string()),
(20221105003256, "DELETE FROM workspace_invite WHERE workspace_id = 'demo' AND email = 'ruben@windmill.dev';".to_string()),
(20221123151919, "".to_string()),
(20251105100125, include_str!(
"../../migrations/20251105100125_legacy_sql_result_flag.up.sql"
).replace("", "")),
].into_iter().collect();
}
@@ -166,6 +169,7 @@ impl Migrate for CustomMigrator {
if let Some(migration_sql) = OVERRIDDEN_MIGRATIONS.get(&migration.version) {
tracing::info!("Using custom migration for version {}", migration.version);
// tracing::info!("Migration SQL: {}", migration_sql);
self.inner
.execute(&**migration_sql)

View File

@@ -13,7 +13,10 @@ use anyhow::anyhow;
#[cfg(all(feature = "enterprise", not(feature = "private")))]
use {std::sync::Arc, tokio::sync::RwLock};
#[cfg(not(feature = "private"))]
pub async fn validate_license_key(_license_key: String) -> anyhow::Result<(String, bool)> {
pub async fn validate_license_key(
_license_key: String,
_db: Option<&crate::db::DB>,
) -> anyhow::Result<(String, bool)> {
// Implementation is not open source
Err(anyhow!("License can't be validated in Windmill CE"))
}
@@ -23,6 +26,7 @@ pub async fn jwt_ext_auth(
_w_id: Option<&String>,
_token: &str,
_external_jwks: Option<Arc<RwLock<ExternalJwks>>>,
_db: &crate::db::DB,
) -> anyhow::Result<(crate::db::ApiAuthed, usize)> {
// Implementation is not open source

View File

@@ -11,7 +11,7 @@ use uuid::Uuid;
use crate::db::ApiAuthed;
use windmill_common::{
db::UserDB,
db::{UserDB, DB},
error::{JsonResult, Result},
flow_conversations::MessageType,
utils::{not_found_if_none, paginate, Pagination},
@@ -154,6 +154,7 @@ pub async fn get_or_create_conversation_with_id(
async fn delete_conversation(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Path((w_id, conversation_id)): Path<(String, Uuid)>,
) -> Result<String> {
let mut tx = user_db.clone().begin(&authed).await?;
@@ -185,10 +186,14 @@ async fn delete_conversation(
// Delete associated memory in background (non-blocking cleanup)
let w_id_clone = w_id.clone();
let db_clone = db.clone();
tokio::spawn(async move {
if let Err(e) =
windmill_worker::memory_oss::delete_conversation_memory(&w_id_clone, conversation_id)
.await
if let Err(e) = windmill_worker::memory_oss::delete_conversation_memory(
&db_clone,
&w_id_clone,
conversation_id,
)
.await
{
tracing::error!(
"Failed to delete memory for conversation {} in workspace {}: {:?}",

View File

@@ -562,6 +562,7 @@ async fn create_flow(
false,
None,
None,
None,
)
.await?;
@@ -1025,6 +1026,7 @@ async fn update_flow(
false,
None,
None,
None,
)
.await?;
@@ -1133,6 +1135,7 @@ async fn get_flow_by_path(
flow.timeout,
flow.visible_to_runner_only,
flow.on_behalf_of_email,
flow_version.id AS version_id,
flow_version.schema,
flow_version.value,
flow_version.created_at AS edited_at,
@@ -1172,8 +1175,9 @@ async fn get_flow_by_path(
flow.timeout,
flow.visible_to_runner_only,
flow.on_behalf_of_email,
flow_version.id AS version_id,
flow_version.schema,
flow_version.value,
flow_version.value,
flow_version.created_at AS edited_at,
flow_version.created_by AS edited_by,
NULL AS starred
@@ -1634,6 +1638,7 @@ mod tests {
early_return: None,
concurrency_key: None,
chat_input_enabled: None,
flow_env: None,
debounce_key: None,
debounce_delay_s: None,
};

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,10 @@ use tower_http::{
};
use windmill_common::db::UserDB;
use windmill_common::worker::CLOUD_HOSTED;
use windmill_common::{utils::{configure_client, GIT_VERSION}, BASE_URL, INSTANCE_NAME};
use windmill_common::{
utils::{configure_client, GIT_VERSION},
BASE_URL, INSTANCE_NAME,
};
use crate::scim_oss::has_scim_token;
use windmill_common::error::AppError;
@@ -77,6 +80,7 @@ pub mod args;
mod assets;
mod audit;
pub mod auth;
mod bedrock;
mod capture;
mod concurrency_groups;
mod configs;
@@ -151,6 +155,7 @@ mod smtp_server_oss;
pub mod teams_approvals_ee;
mod teams_approvals_oss;
mod public_app_layer;
mod static_assets;
#[cfg(all(feature = "stripe", feature = "enterprise", feature = "private"))]
pub mod stripe_ee;
@@ -320,6 +325,11 @@ pub async fn run_server(
#[cfg(feature = "embedding")]
load_embeddings_db(&db);
#[cfg(feature = "cloud")]
if *CLOUD_HOSTED {
windmill_queue::init_usage_buffer(db.clone());
}
let mut start_smtp_server = false;
if let Some(smtp_settings) =
load_value_from_global_settings(&db, EMAIL_DOMAIN_SETTING).await?
@@ -699,6 +709,15 @@ pub async fn run_server(
)
};
let app = if let Some(domain) = public_app_layer::PUBLIC_APP_DOMAIN.as_ref() {
tracing::info!("Public app domain filter enabled for domain: {}", domain);
app.layer(axum::middleware::from_fn(
public_app_layer::public_app_domain_filter,
))
} else {
app
};
let app = app.layer(CatchPanicLayer::custom(|err| {
tracing::error!("panic in handler, returning 500: {:?}", err);
Response::builder()

View File

@@ -39,6 +39,7 @@ use super::utils::{
FlowInfo, ResourceInfo, ResourceType, SchemaType, ScriptInfo, ToolableItem, WorkspaceId,
},
schema::transform_schema_for_resources,
scope_matcher::{is_resource_allowed, parse_mcp_scopes},
transform::{reverse_transform, reverse_transform_key},
};
@@ -151,6 +152,10 @@ impl ServerHandler for Runner {
check_scopes(authed)?;
// Parse MCP scopes for authorization
let scopes = authed.scopes.as_ref().map(|s| s.as_slice()).unwrap_or(&[]);
let scope_config = parse_mcp_scopes(scopes)?;
if request.name.ends_with("_TRUNC") {
return Ok(CallToolResult::error(
vec![
@@ -197,6 +202,19 @@ impl ServerHandler for Runner {
let endpoint_tools = all_endpoint_tools();
for endpoint_tool in endpoint_tools {
if endpoint_tool.name.as_ref() == request.name {
// Validate endpoint scope
if scope_config.granular
&& !is_resource_allowed(&endpoint_tool.name, &scope_config.endpoints)
{
return Err(ErrorData::internal_error(
format!(
"Access denied: endpoint '{}' not in token scope",
endpoint_tool.name
),
None,
));
}
// This is an endpoint tool, forward to the actual HTTP endpoint
let result =
call_endpoint_tool(&endpoint_tool, args.clone(), &workspace_id, &authed)
@@ -212,6 +230,21 @@ impl ServerHandler for Runner {
ErrorData::internal_error(format!("Failed to reverse transform path: {}", e), None)
})?;
// Validate script/flow scope
if !is_hub && scope_config.granular {
if tool_type == "script" && !is_resource_allowed(&path, &scope_config.scripts) {
return Err(ErrorData::internal_error(
format!("Access denied: script '{}' not in token scope", path),
None,
));
} else if tool_type == "flow" && !is_resource_allowed(&path, &scope_config.flows) {
return Err(ErrorData::internal_error(
format!("Access denied: flow '{}' not in token scope", path),
None,
));
}
}
let item_schema = if is_hub {
get_hub_script_schema(&format!("hub/{}", path), db).await?
} else {
@@ -337,53 +370,23 @@ impl ServerHandler for Runner {
})
.map(|w_id| w_id.0.clone())?;
let scopes = authed.scopes.as_ref();
let owned_scope = scopes.and_then(|scopes| {
scopes
.iter()
.find(|scope| scope.starts_with("mcp:") && !scope.contains("hub"))
});
let hub_scope =
scopes.and_then(|scopes| scopes.iter().find(|scope| scope.starts_with("mcp:hub")));
let (scope_type, scope_path) = owned_scope.map_or(("all", None), |scope| {
let parts = scope.split(":").collect::<Vec<&str>>();
(
parts[1],
if parts.len() == 3 {
Some(parts[2])
} else {
None
},
)
});
let scope_integrations = hub_scope.and_then(|scope| {
let parts = scope.split(":").collect::<Vec<&str>>();
if parts.len() == 3 {
Some(parts[2])
} else {
None
}
});
// Parse MCP scopes to determine what to expose
let scopes = authed.scopes.as_ref().map(|s| s.as_slice()).unwrap_or(&[]);
let scope_config = parse_mcp_scopes(scopes)?;
let scripts_fn = get_items::<ScriptInfo>(
user_db,
authed,
&workspace_id,
scope_type,
"script",
scope_path.as_deref(),
);
let flows_fn = get_items::<FlowInfo>(
user_db,
authed,
&workspace_id,
scope_type,
"flow",
scope_path.as_deref(),
);
let scope_type = if scope_config.favorites {
"favorites"
} else {
// Fetch all items if either all or granular scope set (we filter later for granular scopes)
"all"
};
let scripts_fn =
get_items::<ScriptInfo>(user_db, authed, &workspace_id, scope_type, "script");
let flows_fn = get_items::<FlowInfo>(user_db, authed, &workspace_id, scope_type, "flow");
let resources_types_fn = get_resources_types(user_db, authed, &workspace_id);
let hub_scripts_fn = get_scripts_from_hub(db, scope_integrations.as_deref());
let (scripts, flows, resources_types, hub_scripts) = if scope_integrations.is_some() {
let hub_scripts_fn = get_scripts_from_hub(db, scope_config.hub_apps.as_deref());
let (scripts, flows, resources_types, hub_scripts) = if scope_config.hub_apps.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)
@@ -396,7 +399,13 @@ impl ServerHandler for Runner {
let mut resources_cache: HashMap<String, Vec<ResourceInfo>> = HashMap::new();
let mut tools: Vec<Tool> = Vec::new();
// Filter and add scripts based on scope
for script in scripts {
// For granular scopes, filter by path
if scope_config.granular && !is_resource_allowed(&script.path, &scope_config.scripts) {
continue;
}
tools.push(
Runner::create_tool_from_item(
&script,
@@ -410,7 +419,13 @@ impl ServerHandler for Runner {
);
}
// Filter and add flows based on scope
for flow in flows {
// For granular scopes, filter by path
if scope_config.granular && !is_resource_allowed(&flow.path, &scope_config.flows) {
continue;
}
tools.push(
Runner::create_tool_from_item(
&flow,
@@ -438,10 +453,23 @@ impl ServerHandler for Runner {
);
}
// Add endpoint tools from the generated MCP tools
// Add endpoint tools from the generated MCP tools, filtered by scope
let endpoint_tools = all_endpoint_tools();
let mcp_tools_converted = endpoint_tools_to_mcp_tools(endpoint_tools);
tools.extend(mcp_tools_converted);
for endpoint_tool in endpoint_tools {
// For granular scopes, filter by endpoint name
if scope_config.granular
&& !is_resource_allowed(&endpoint_tool.name, &scope_config.endpoints)
{
continue;
}
tools.push(
endpoint_tools_to_mcp_tools(vec![endpoint_tool])
.into_iter()
.next()
.unwrap(),
);
}
Ok(ListToolsResult { tools, next_cursor: None })
}

View File

@@ -18,11 +18,10 @@ use crate::HTTP_CLIENT;
pub fn check_scopes(authed: &ApiAuthed) -> Result<(), ErrorData> {
let scopes = authed.scopes.as_ref();
if scopes.is_none()
|| scopes.unwrap().iter().all(|scope| {
!scope.starts_with("mcp:all")
&& !scope.starts_with("mcp:favorites")
&& !scope.starts_with("mcp:hub:")
})
|| scopes
.unwrap()
.iter()
.all(|scope| !scope.starts_with("mcp:"))
{
tracing::error!("Unauthorized: missing mcp scope");
return Err(ErrorData::internal_error(
@@ -141,7 +140,6 @@ pub async fn get_items<T: for<'a> sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen
workspace_id: &str,
scope_type: &str,
item_type: &str,
scope_path: Option<&str>,
) -> Result<Vec<T>, ErrorData> {
let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type));
let fields = vec!["o.path", "o.summary", "o.description", "o.schema"];
@@ -159,23 +157,6 @@ pub async fn get_items<T: for<'a> sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen
sqlb.and_where("(o.no_main_func IS NOT TRUE OR o.no_main_func IS NULL)");
}
// scope path is always a folder path, format is f/my_folder/*
if let Some(scope_path) = scope_path {
if scope_path.split("/").count() != 3
|| !scope_path.starts_with("f/")
|| !scope_path.ends_with("/*")
{
return Err(ErrorData::internal_error(
format!(
"Invalid folder format: {}, expected format is f/my_folder/*",
scope_path
),
None,
));
}
sqlb.and_where_like_left("o.path", &scope_path[..scope_path.len() - 2]);
}
sqlb.order_by(
if item_type == "flow" {
"o.edited_at"

View File

@@ -6,4 +6,5 @@
pub mod models;
pub mod database;
pub mod schema;
pub mod transform;
pub mod transform;
pub mod scope_matcher;

View File

@@ -0,0 +1,229 @@
//! MCP Scope matching utilities
//!
//! Contains utilities for parsing and matching MCP token scopes to determine
//! which scripts, flows, and endpoints a token has access to.
use rmcp::ErrorData;
/// Configuration for MCP scopes parsed from token scopes
#[derive(Debug, Clone, Default)]
pub struct McpScopeConfig {
/// Script paths/patterns allowed by this token
pub scripts: Vec<String>,
/// Flow paths/patterns allowed by this token
pub flows: Vec<String>,
/// Endpoint names/patterns allowed by this token
pub endpoints: Vec<String>,
/// Whether this is a legacy "all" scope
pub all: bool,
/// Whether this is a "favorites" scope
pub favorites: bool,
/// Whether a granular scope is detected
pub granular: bool,
/// Hub app filter (if any)
pub hub_apps: Option<String>,
}
/// Parse MCP scopes from token scope strings
pub fn parse_mcp_scopes(scopes: &[String]) -> Result<McpScopeConfig, ErrorData> {
let mut config = McpScopeConfig::default();
for scope in scopes {
if !scope.starts_with("mcp:") {
continue;
}
if scope == "mcp:all" {
// Legacy scope: grant access to everything
config.all = true;
config.scripts.push("*".to_string());
config.flows.push("*".to_string());
config.endpoints.push("*".to_string());
continue;
}
if scope == "mcp:favorites" {
// Legacy favorites scope
config.favorites = true;
continue;
}
// Legacy folder scope: mcp:all:f/folder/*
if scope.starts_with("mcp:all:") {
if let Some(folder_pattern) = scope.strip_prefix("mcp:all:") {
// Parse as folder pattern - add to both scripts and flows. Also add all endpoints.
config.scripts.push(folder_pattern.to_string());
config.flows.push(folder_pattern.to_string());
config.endpoints.push("*".to_string());
}
continue;
}
if scope.starts_with("mcp:hub:") {
// Legacy hub scope
if let Some(apps) = scope.strip_prefix("mcp:hub:") {
config.hub_apps = Some(apps.to_string());
}
continue;
}
if let Some(resources) = scope.strip_prefix("mcp:scripts:") {
// New granular script scope: mcp:scripts:path1,path2,f/folder/*
config.scripts.extend(parse_resource_list(resources)?);
continue;
}
if let Some(resources) = scope.strip_prefix("mcp:flows:") {
// New granular flow scope: mcp:flows:path1,path2,f/folder/*
config.flows.extend(parse_resource_list(resources)?);
continue;
}
if let Some(resources) = scope.strip_prefix("mcp:endpoints:") {
// New granular endpoint scope: mcp:endpoints:name1,name2
config.endpoints.extend(parse_resource_list(resources)?);
continue;
}
tracing::warn!("Unrecognized MCP scope format: {}", scope);
}
config.granular = !config.all && !config.favorites;
Ok(config)
}
/// Parse comma-separated resource list
fn parse_resource_list(resources: &str) -> Result<Vec<String>, ErrorData> {
if resources.is_empty() {
return Ok(vec![]);
}
Ok(resources
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect())
}
/// Check if a resource path matches any pattern in the allowed list
pub fn is_resource_allowed(resource_path: &str, allowed_patterns: &[String]) -> bool {
if allowed_patterns.is_empty() {
return false;
}
// Wildcard grants all access
if allowed_patterns.contains(&"*".to_string()) {
return true;
}
// Check against each pattern
for pattern in allowed_patterns {
if resource_matches_pattern(resource_path, pattern) {
return true;
}
}
false
}
/// Check if a resource path matches a pattern (supports wildcards like f/folder/*)
fn resource_matches_pattern(resource_path: &str, pattern: &str) -> bool {
// Exact match
if pattern == resource_path {
return true;
}
// Wildcard pattern matching
if pattern.ends_with("/*") {
let prefix = &pattern[..pattern.len() - 2];
if !resource_path.starts_with(prefix) {
return false;
}
// If the resource is exactly the prefix, it matches
if resource_path.len() == prefix.len() {
return true;
}
// If the resource is longer, the next character must be '/' for a valid match
// This prevents "u/user" from matching "u/use/*"
return resource_path.chars().nth(prefix.len()) == Some('/');
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_legacy_scopes() {
let scopes = vec!["mcp:all".to_string()];
let config = parse_mcp_scopes(&scopes).unwrap();
assert!(config.all);
assert_eq!(config.scripts, vec!["*"]);
assert_eq!(config.flows, vec!["*"]);
assert_eq!(config.endpoints, vec!["*"]);
let scopes = vec!["mcp:favorites".to_string()];
let config = parse_mcp_scopes(&scopes).unwrap();
assert!(config.favorites);
let scopes = vec!["mcp:hub:slack".to_string()];
let config = parse_mcp_scopes(&scopes).unwrap();
assert_eq!(config.hub_apps, Some("slack".to_string()));
}
#[test]
fn test_parse_granular_scopes() {
let scopes = vec![
"mcp:scripts:u/admin/script1,u/admin/script2".to_string(),
"mcp:flows:f/automation/*".to_string(),
"mcp:endpoints:list_jobs,get_job".to_string(),
];
let config = parse_mcp_scopes(&scopes).unwrap();
assert_eq!(config.scripts, vec!["u/admin/script1", "u/admin/script2"]);
assert_eq!(config.flows, vec!["f/automation/*"]);
assert_eq!(config.endpoints, vec!["list_jobs", "get_job"]);
}
#[test]
fn test_resource_matching() {
// Exact match
assert!(resource_matches_pattern("u/admin/script", "u/admin/script"));
// Wildcard folder match
assert!(resource_matches_pattern("f/folder/script", "f/folder/*"));
assert!(resource_matches_pattern(
"f/folder/sub/script",
"f/folder/*"
));
// Should NOT match - prefix is not complete
assert!(!resource_matches_pattern("u/username", "u/user/*"));
// Should match - exact prefix
assert!(resource_matches_pattern("u/user/script", "u/user/*"));
}
#[test]
fn test_is_resource_allowed() {
let patterns = vec!["u/admin/script1".to_string(), "f/folder/*".to_string()];
assert!(is_resource_allowed("u/admin/script1", &patterns));
assert!(is_resource_allowed("f/folder/anything", &patterns));
assert!(!is_resource_allowed("u/other/script", &patterns));
// Test wildcard
let wildcard = vec!["*".to_string()];
assert!(is_resource_allowed("any/path", &wildcard));
// Test empty patterns
let empty: Vec<String> = vec![];
assert!(!is_resource_allowed("any/path", &empty));
}
}

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