Merge branch 'main' into free-token-limit

Resolve conflicts: ee-repo-ref (EE branch merged with EE main), NoItemFound (keep branch's no-welcome layout), AIChatManager (keep both refreshFreeTierUsage + QueuedEntry), ItemsList (integrate FilterSearchbar UX onto main's #10297 merged-runnables data layer).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Diego Imbert
2026-07-26 00:55:30 +02:00
co-authored by Claude Opus 4.8
817 changed files with 64482 additions and 6633 deletions
+39 -2
View File
@@ -1,18 +1,19 @@
---
name: pr
user_invocable: true
description: Open a draft pull request on GitHub. MUST use when you want to create/open a PR.
description: Open a draft pull request on GitHub and drive CI review rounds until it is ready. MUST use when you want to create/open a PR.
---
# Pull Request Skill
Create a draft pull request with a clear title and explicit description of changes.
Create a draft pull request with a clear title and explicit description of changes, then drive it through CI review rounds to ready.
## Instructions
1. **Analyze branch changes**: Understand all commits since diverging from main
2. **Push to remote**: Ensure all commits are pushed
3. **Create draft PR**: Always open as draft for review before merging
4. **Drive review rounds**: trigger CI reviews on the draft and only flip to ready once every verdict is a go (see "Review rounds" below)
## PR Title Format
@@ -124,6 +125,42 @@ and continue once they confirm it's done.
)"
```
9. Return the PR URL to the user
10. Drive the PR through CI review rounds to ready (see "Review rounds" below)
## Review rounds (draft → ready)
A PR leaves draft **only after a clean CI review round**. Never run `gh pr ready` before that.
1. **Trigger a round and wait for it**: launch the waiter as a background Bash task (a round takes 1030 min; you are woken when it exits — do not stop the session or poll in the foreground while it runs):
```bash
bash .agents/skills/pr/review-round.sh <PR_NUMBER>
```
It comments `/review` on the PR — which runs the Codex, Claude and Pi CI reviewers even on a draft — waits for the spawned `PR Review Commands` workflow run(s) to complete, then prints one verdict line per reviewer and saves the full review comments to files.
`/review` (and `/codex`) are **idempotent per head SHA**: if a running or successful review already covers the current head, they skip that agent and post nothing new — the waiter reads the existing verdict for that head, so a skipped agent is *not* a missing one. A cancelled/failed head run is re-run in place; a fresh run is launched only when nothing covers the head. So an unchanged-head re-review is a near no-op, not a new round — push a commit to get genuinely fresh reviews.
2. **Judge the round.** Codex is mandatory; Claude, Pi and cubic count whenever they posted. Every review starts with one of the three `REVIEW.md` verdicts:
- Codex verdict missing → the round is void: the waiter warns only when the head has no green Codex run (cancelled/failed/absent — not merely skipped-because-already-reviewed). Comment `/codex` on the PR, which re-runs the interrupted run in place (or launches one if none exists), wait the same way, and judge again.
- Any **"Should address issues before merging"** → fix the P0/P1 findings (and the nits while you're there), commit, push, and start a new round (step 1).
- Only **"Mergeable, but should ideally address nits"** and/or **"Good to merge"** → fix the nits too; a nit that is wrong or genuinely not worth fixing may instead be dismissed by replying to the review comment with your reasoning. Push nit-only fixes without starting another full round.
3. **Flip to ready with the marker comment.** The review workflows skip the redundant `ready_for_review`-triggered round when the PR author has posted a marker naming the current head SHA **and** the PR's latest Codex review *posted before the marker* has a non-blocking verdict (reviewer evidence — a bare marker with no round behind it, or one whose last pre-marker Codex verdict is "Should address issues", skips nothing). Keep the prefix exact and use the full 40-char SHA of the head you are flipping:
- every verdict was "Good to merge" (head unchanged since the round):
`✅ Review round clean @ <head-sha>`
- nit-only round, nits fixed or dismissed afterwards (head may have moved past the reviewed SHA — say so):
`✅ Review round clean @ <head-sha> — nit-only verdicts at <round-sha>; nits addressed in <commit sha(s)> / dismissed in review replies`
```bash
gh pr comment <PR_NUMBER> --body "✅ Review round clean @ $(git rev-parse HEAD)"
gh pr ready <PR_NUMBER>
```
If any P0/P1 finding is unaddressed or the head moved for reasons other than nit fixes, do **not** post the marker or flip — run another round instead.
## EE Companion PR (when `*_ee.rs` files were modified)
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env bash
# Trigger a CI review round on a PR and wait for it to finish.
#
# Usage: bash .agents/skills/pr/review-round.sh [PR_NUMBER]
# PR_NUMBER defaults to the current branch's PR.
#
# Comments `/review` on the PR (works on drafts), waits for the spawned
# "PR Review Commands" workflow run(s) to complete, then prints one verdict
# line per reviewer and saves each full review comment to a file. A round
# takes 10-30 minutes: run this in the background and act on its output when
# it exits, per the pr skill ("Review rounds").
set -euo pipefail
REPO=${REPO:-$(gh repo view --json nameWithOwner --jq .nameWithOwner)}
PR=${1:-$(gh pr view --json number --jq .number)}
# Timestamp of the trigger comment, straight from GitHub, so local clock skew
# can't make the run/comment filters below miss part of the round.
TRIGGER_TIME=$(gh api "repos/$REPO/issues/$PR/comments" -f body='/review' --jq .created_at)
echo "Review round triggered on $REPO#$PR at $TRIGGER_TIME"
# Retry wrapper for one-off gh/API hiccups: a 45-minute wait must not die on
# a single transient failure.
retry() {
local attempt
for attempt in 1 2 3; do
if "$@"; then return 0; fi
sleep 10
done
return 1
}
# The /review comment spawns one "PR Review Commands" run holding the
# claude/codex/pi jobs. Runs aren't linked to a PR, so wait on every run of
# that workflow created after the trigger: a concurrent round on another PR
# can only delay the answer, never truncate it. Every issue comment on any PR
# spawns a fast-completing parse run of the same workflow, so the round's own
# run may briefly lag the listing while unrelated runs already show completed:
# require the all-completed state to hold past a floor and across two
# consecutive polls before trusting it.
DEADLINE=$(( $(date +%s) + 45 * 60 ))
NO_RUN_DEADLINE=$(( $(date +%s) + 5 * 60 ))
MIN_WAIT_UNTIL=$(( $(date +%s) + 3 * 60 ))
STABLE=0
FAILURES=0
while :; do
if RUNS=$(gh run list --repo "$REPO" --workflow=pr-review-commands.yml \
--created ">=$TRIGGER_TIME" --limit 100 --json status); then
FAILURES=0
else
FAILURES=$(( FAILURES + 1 ))
if [ "$FAILURES" -ge 5 ]; then
echo "ERROR: listing workflow runs failed $FAILURES times in a row; aborting the wait." >&2
exit 1
fi
echo "WARNING: listing workflow runs failed (attempt $FAILURES/5); retrying in 60s." >&2
sleep 60
continue
fi
TOTAL=$(jq length <<<"$RUNS")
PENDING=$(jq '[.[] | select(.status != "completed")] | length' <<<"$RUNS")
NOW=$(date +%s)
if [ "$TOTAL" -gt 0 ] && [ "$PENDING" -eq 0 ] && [ "$NOW" -gt "$MIN_WAIT_UNTIL" ]; then
STABLE=$(( STABLE + 1 ))
if [ "$STABLE" -ge 2 ]; then
break
fi
else
STABLE=0
fi
if [ "$TOTAL" -eq 0 ] && [ "$NOW" -gt "$NO_RUN_DEADLINE" ]; then
echo "ERROR: no 'PR Review Commands' run appeared within 5 minutes of the /review comment; check that the comment author has write access and the workflow is enabled." >&2
exit 1
fi
if [ "$NOW" -gt "$DEADLINE" ]; then
echo "WARNING: review round still pending after 45 minutes; reporting whatever has been posted so far." >&2
break
fi
sleep 60
done
# Head SHA at trigger time. `/review` is idempotent per head: it skips an agent a
# running/successful review already covers, re-runs a cancelled/failed one in place on a
# separate head-tied run, and launches fresh only when nothing covers the head. Verdict
# reading below therefore keys off the head, not just the trigger timestamp.
HEAD_SHA=$(retry gh api "repos/$REPO/pulls/$PR" --jq .head.sha)
echo "Reviewing head $HEAD_SHA"
# Newest non-skipped run of <workflow> tied to the head ("status conclusion"), or empty
# when none exists. A re-run-in-place or an already-covering review resolves on such a
# head-tied run — separate from the pr-review-commands run waited on above (a fresh
# launch instead runs inside it, and posts after the trigger). A `skipped` run is the
# draft/fork gate and produced no review, so it is ignored.
head_run_state() {
gh run list --repo "$REPO" --workflow "$1" --commit "$HEAD_SHA" --limit 20 \
--json databaseId,status,conclusion \
--jq '[.[] | select(.conclusion != "skipped")] | sort_by(.databaseId) | last | if . then "\(.status) \(.conclusion // "-")" else empty end' 2>/dev/null || true
}
# A re-run-in-place review lands on a head-tied run that finishes after the fast
# pr-review-commands run, so let those settle before reading verdicts.
for wf in codex-pr-review.yml pi-pr-review.yml pr-ready-review.yml; do
while :; do
case "$(head_run_state "$wf")" in
""|"completed "*) break ;;
*) if [ "$(date +%s)" -gt "$DEADLINE" ]; then break; fi; sleep 30 ;;
esac
done
done
OUT_DIR=$(mktemp -d -t review-round-XXXXXX)
COMMENTS_RAW=$(retry gh api "repos/$REPO/issues/$PR/comments?per_page=100" --paginate)
# Two views: comments from THIS round (after the trigger) and the full history. A fresh
# launch posts after the trigger; an idempotent skip leaves the covering verdict in the
# earlier run's comment, so fall back to history when that agent's head run is green.
jq -s --arg t "$TRIGGER_TIME" '[.[][] | select(.created_at > $t)]' \
<<<"$COMMENTS_RAW" > "$OUT_DIR/comments.json"
jq -s '[.[][]]' <<<"$COMMENTS_RAW" > "$OUT_DIR/comments-all.json"
# cubic posts through the PR reviews API, not issue comments.
REVIEWS_RAW=$(retry gh api "repos/$REPO/pulls/$PR/reviews?per_page=100" --paginate)
jq -s --arg t "$TRIGGER_TIME" '[.[][] | select((.submitted_at // "") > $t)]' \
<<<"$REVIEWS_RAW" > "$OUT_DIR/pr-reviews.json"
VERDICT_RE='(Good to merge|Mergeable, but should ideally address nits|Should address issues before merging)'
body_by_header() { # <file> <header-substring>
jq -r --arg h "$2" '[.[] | select(.body // "" | contains($h))] | last | .body // empty' "$1"
}
body_by_login() { # <file> <login>
jq -r --arg l "$2" '[.[] | select(.user.login == $l)] | last | .body // empty' "$1"
}
head_ok() { [ "$(head_run_state "$1")" = "completed success" ]; }
# Latest verdict for a reviewer: prefer this round's comment; if none and the reviewer's
# head run succeeded (an idempotent /review skipped re-reviewing an already-green head),
# fall back to the covering comment from the full history.
verdict_body() { # <header|login> <value> <workflow>
local body
body=$("body_by_$1" "$OUT_DIR/comments.json" "$2")
if [ -z "$body" ] && head_ok "$3"; then
body=$("body_by_$1" "$OUT_DIR/comments-all.json" "$2")
fi
printf '%s' "$body"
}
report() { # <reviewer-name> <comment-body>
local name=$1 body=$2 verdict
if [ -z "$body" ]; then
echo "$name: (no review posted for this head)"
return
fi
printf '%s\n' "$body" > "$OUT_DIR/$name.md"
verdict=$(printf '%s\n' "$body" | grep -m1 -oE "${VERDICT_RE}.*" | sed 's/\*\*//g' || true)
echo "$name: ${verdict:-(review posted but no verdict line; read $OUT_DIR/$name.md)}"
}
echo
echo "=== Review round verdicts for $REPO#$PR (head $HEAD_SHA) ==="
CODEX_BODY=$(verdict_body header '## Codex Review' codex-pr-review.yml)
report codex "$CODEX_BODY"
report claude "$(verdict_body login 'claude[bot]' pr-ready-review.yml)"
report pi "$(verdict_body header '## Pi Review' pi-pr-review.yml)"
CUBIC_BODY=$(jq -r '[.[] | select(.user.login | test("^cubic(-dev-ai)?(\\[bot\\])?$"; "i"))] | last | .body // empty' \
"$OUT_DIR/pr-reviews.json")
if [ -z "$CUBIC_BODY" ]; then
CUBIC_BODY=$(jq -r '[.[] | select(.user.login | test("^cubic(-dev-ai)?(\\[bot\\])?$"; "i"))] | last | .body // empty' \
"$OUT_DIR/comments.json")
fi
report cubic "$CUBIC_BODY"
echo
echo "Full round output: $OUT_DIR (comments.json, pr-reviews.json, one .md per reviewer)"
if [ -z "$CODEX_BODY" ]; then
echo "WARNING: no Codex verdict for $HEAD_SHA - its head run is not green (cancelled/failed/absent, not merely skipped-because-already-reviewed). Re-trigger with a '/codex' PR comment (re-runs the interrupted run in place, or launches one) and wait again." >&2
fi
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
# PreToolUse guard for `rm`: auto-allow ONLY a single, plain, single-line `rm` whose every
# operand is a whitelisted target — under /tmp, or inside a git working tree located in $HOME
# (a version-controlled project dir). Anything else makes no decision (exit 0) and falls back
# to the normal permission flow, where the `Bash(rm:*)` ask rule prompts (classifier as a
# backstop).
#
# The git-tree allowance trades on "this is a project under version control" being lower-stakes
# than a delete elsewhere — NOT on full recoverability: committed content is restorable via git,
# but untracked / .gitignore'd / uncommitted content, and an independent nested repo's history
# under a recursively-deleted parent, are NOT. Accepted as a deliberate convenience tradeoff.
#
# Deny-by-default: every token must consist only of a safe character set (alphanumerics,
# `. _ / -` and glob chars `* ? [ ]`). That set contains none of the characters bash uses for
# quoting, expansion, or command separation ($ ` ~ { } ( ) ' " \ ; & | < >), so those forms
# fail by construction rather than needing to be enumerated. `realpath -m` then resolves `..`
# and existing symlinks (so a symlink out of the allowed roots is caught), and a wildcard in a
# non-final path segment is refused because it can expand through a symlink realpath can't see.
#
# The git-repo allowance covers targets inside a git working tree under $HOME, and the tree's
# own root folder only when it is a linked worktree (`.git` is a pointer file, so history in
# the main repo survives); a primary checkout's root (`.git` is a history dir) and any `.git`
# path are never auto-allowed. Globs auto-allow only under /tmp — elsewhere their expansion
# could reach `.git` or a dotfile the literal checks never see. Relative operands resolve
# against the command's cwd (from the hook input). A PreToolUse `allow` overrides the ask rule.
#
# Assumes GNU `realpath` (-m) and `jq`, both present in this repo's Linux dev env.
set -uo pipefail
input=$(cat)
command -v jq >/dev/null 2>&1 || exit 0
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)
[ -z "$cmd" ] && exit 0
cwd=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)
# A newline separates commands, and the tokenizer below only reads the first line — defer.
case "$cmd" in *$'\n'*) exit 0 ;; esac
read -r -a toks <<< "$cmd"
# Bare leading `rm` only; wrappers (`timeout rm`), env prefixes, and `/bin/rm` defer.
[ "${toks[0]:-}" = "rm" ] || exit 0
# 0 (allow) iff the canonical path is an auto-allowable rm target: under /tmp, or strictly
# inside a git working tree located under $HOME. The walk stops at $HOME, so a dotfiles repo at
# ~ can't make all of $HOME deletable, and top-level ~ files stay protected.
allowed_target() {
local canon="$1" d root=""
case "$canon" in /tmp/?*) return 0 ;; esac
[ -n "${HOME:-}" ] || return 1
case "$canon" in "$HOME"/?*) ;; *) return 1 ;; esac
case "$canon" in *"/.git" | *"/.git/"*) return 1 ;; esac # protect history, not recoverable
d="$canon"
while [ "$d" != "/" ] && [ "$d" != "$HOME" ]; do
[ -e "$d/.git" ] && { root="$d"; break; }
d=$(dirname "$d")
done
[ -n "$root" ] || return 1 # not inside a git working tree under $HOME
if [ "$canon" = "$root" ]; then
# Deleting the repo root folder itself: allow only for a linked worktree, whose `.git` is
# a file/pointer so the history lives in the main repo and survives. A primary checkout's
# `.git` is a directory holding the history, so deleting it is unrecoverable — defer.
[ -f "$root/.git" ] && return 0
return 1
fi
return 0
}
had_operand=0
end_opts=0
i=1
while [ "$i" -lt "${#toks[@]}" ]; do
t="${toks[$i]}"
i=$((i + 1))
# Whitelist every token (flags included, so an operator hidden in a flag like `-rf;rm`
# can't slip past): any character outside the safe set makes it unsafe to reason about.
[ -n "$(printf '%s' "$t" | tr -d 'A-Za-z0-9._/*?[]-')" ] && exit 0
# A glob in an option-looking token (`-[-]`) can expand to `--` and turn a later `-name`
# into an operand — never a real option, so defer.
case "$t" in -*[*?[]*) exit 0 ;; esac
if [ "$end_opts" = 0 ]; then
[ "$t" = "--" ] && { end_opts=1; continue; }
# Skip real options only before the first operand. A bare `-` is a filename, and under
# POSIXLY_CORRECT GNU rm stops option parsing at the first operand, so a later `-name`
# is a filename too — validate it rather than skipping it.
if [ "$had_operand" = 0 ]; then
case "$t" in -?*) continue ;; esac
fi
fi
had_operand=1
# No wildcard in a non-final path segment (`a/*/b`): it can expand through a symlink
# realpath can't see. A slashless glob (`*.rs`) is a final-segment match — fine.
case "$t" in */*) case "${t%/*}" in *[*?[]*) exit 0 ;; esac ;; esac
case "$t" in
/*) canon=$(realpath -m -- "$t" 2>/dev/null) ;;
*) canon=$(realpath -m -- "${cwd:-$PWD}/$t" 2>/dev/null) ;;
esac
[ -n "$canon" ] || exit 0
# A glob may auto-allow only under /tmp, where everything is deletable. Elsewhere its
# expansion could match `.git`, a dotfile like `.*`, or a nested checkout root that the
# literal-path checks never see — so require literal operands in git repos.
case "$t" in *[*?[]*) case "$canon" in /tmp/?*) ;; *) exit 0 ;; esac ;; esac
allowed_target "$canon" || exit 0
done
[ "$had_operand" = 1 ] || exit 0
jq -nc '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",permissionDecisionReason:"rm operands are under /tmp or inside a git checkout in $HOME"}}'
+20 -5
View File
@@ -48,9 +48,6 @@
"Read(/tmp/**)",
"Write(/tmp/**)",
"Edit(/tmp/**)",
"Bash(rm:/tmp/*)",
"Bash(rm:/tmp/**)",
"Bash(rmdir:/tmp/*)",
"Bash(mkdir:/tmp/*)",
"Bash(mkdir:/tmp/**)",
"Bash(cp:/tmp/*)",
@@ -62,7 +59,12 @@
"Bash(chmod:/tmp/*)",
"Bash(chmod:/tmp/**)",
"Bash(tar * /tmp/*)",
"Bash(unzip * /tmp/*)"
"Bash(unzip * /tmp/*)",
"mcp__claude_ai_Gmail__search_threads",
"mcp__claude_ai_Gmail__get_thread",
"mcp__claude_ai_Gmail__get_message",
"mcp__claude_ai_Gmail__list_labels",
"mcp__claude_ai_Gmail__list_drafts"
],
"deny": [
"Read(.env)",
@@ -92,7 +94,15 @@
"Bash(shred:*)",
"Bash(unlink:*)",
"mcp__claude_ai_Stripe",
"mcp__claude_ai_Gmail",
"mcp__claude_ai_Gmail__create_draft",
"mcp__claude_ai_Gmail__update_draft",
"mcp__claude_ai_Gmail__create_label",
"mcp__claude_ai_Gmail__label_message",
"mcp__claude_ai_Gmail__label_thread",
"mcp__claude_ai_Gmail__unlabel_message",
"mcp__claude_ai_Gmail__unlabel_thread",
"mcp__claude_ai_Gmail__apply_sensitive_message_label",
"mcp__claude_ai_Gmail__apply_sensitive_thread_label",
"mcp__claude_ai_Google_Calendar",
"mcp__claude_ai_Google_Drive",
"mcp__claude_ai_Slack",
@@ -109,6 +119,11 @@
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guard-main-branch.sh",
"timeout": 5
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guard-rm-outside-tmp.sh",
"timeout": 5
}
]
}
+13 -2
View File
@@ -51,6 +51,11 @@ jobs:
with:
cache-workspaces: backend
toolchain: 1.97.0
# This action defaults RUSTFLAGS to "-D warnings"; unset it so the test
# run is not failed by cross-platform dead-code (cfg(unix)-only helpers
# are unused on Windows). Warning hygiene is enforced on the Linux CI
# and the build_windows_worker_ release build, not this test job.
rustflags: ""
- uses: actions/setup-dotnet@v4
with:
@@ -203,9 +208,15 @@ jobs:
WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES: 1
WMDEBUG_FORCE_RUNNABLE_SETTINGS_V0: 1
WMDEBUG_FORCE_NO_LEGACY_DEBOUNCING_COMPAT: 1
# Windows ships a worker-only binary, so test the crates a worker runs
# (windmill-worker/-common/-queue) via -p, not `--all`: this skips the
# disk-heavy windmill-api test binaries (LNK1180) and the server-only
# windmill-trigger-* crates (amqp does not build on Windows). Linux CI runs the rest.
run: >
cargo test
--no-fail-fast
--features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,csharp,php,quickjs,mcp,run_inline
--all
-p windmill-worker
-p windmill-common
-p windmill-queue
--features private,enterprise,deno_core,duckdb,python,rust,csharp,php,quickjs,parquet,mcp,scoped_cache,windmill-git-sync/private,windmill-object-store/private,windmill-object-store/enterprise
-- --nocapture --test-threads=10
@@ -10,6 +10,14 @@ on:
- main
paths:
- docker/DockerfileCaddyL4
- docker/entrypoint-caddy.sh
- docker/caddy-compat-normalize.awk
- docker/caddy-l4.version
- docker/test-caddy-compat.sh
- Caddyfile
# The version check below reads the pin out of docker-compose.yml, so a
# compose-only bump has to trigger this workflow or the check never runs.
- docker-compose.yml
- .github/workflows/build-caddy-l4-image.yml
permissions: write-all
@@ -20,6 +28,35 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: depot/setup-action@v1
# docker-compose.yml pins an exact tag, and the Caddyfile it must agree
# with lives in the same checkout. Fail the build rather than publish a
# version nothing references, which is how :latest drifted from the
# Caddyfile in the first place.
- name: Resolve and check image version
id: version
run: |
set -euo pipefail
version="$(tr -d '[:space:]' < docker/caddy-l4.version)"
pinned="$(grep -oE 'caddy-l4:[^[:space:]"]+' docker-compose.yml | head -1 | cut -d: -f2-)"
caddy="$(grep -m1 -oE '^FROM caddy:[0-9]+\.[0-9]+\.[0-9]+' docker/DockerfileCaddyL4 | cut -d: -f2)"
if [ "$version" != "$pinned" ]; then
echo "docker/caddy-l4.version is '$version' but docker-compose.yml pins '$pinned'" >&2
echo "Bump both together." >&2
exit 1
fi
# Otherwise a caddy bump that forgets the version file publishes a tag
# that names the wrong caddy.
case "$version" in
"$caddy"-*) ;;
*)
echo "docker/caddy-l4.version is '$version' but the Dockerfile pins caddy '$caddy'" >&2
echo "The version must be <caddy-version>-<revision>." >&2
exit 1
;;
esac
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Docker meta
id: meta-ee-public
uses: docker/metadata-action@v5
@@ -29,8 +66,22 @@ jobs:
tags: |
type=sha
type=ref,event=branch
# Not gated on the default branch: docker-compose.yml pins this tag,
# so it has to be publishable from a branch (workflow_dispatch)
# before the pin merges, or main would reference a tag that does not
# exist yet. The version is immutable, so republishing from main is
# a no-op. Only branch pushes to main and manual dispatch run this
# workflow, so a branch cannot claim the tag by accident.
type=raw,value=${{ steps.version.outputs.version }}
type=raw,value=latest,enable={{is_default_branch}}
# The shim rewrites config a self-hoster never sees, so a silent
# regression here strands them on a restart loop or a dead :80.
- name: Test the legacy-Caddyfile compatibility shim
run: |
docker build -f docker/DockerfileCaddyL4 -t caddy-l4:ci ./docker
docker/test-caddy-compat.sh caddy-l4:ci
- name: Login to registry
uses: docker/login-action@v3
with:
@@ -63,6 +63,7 @@ jobs:
push: true
build-args: |
features=ee_rhel
WM_BUILD_VERSION=${{ github.sha }}
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
@@ -65,6 +65,7 @@ jobs:
push: true
build-args: |
features=ee_rhel
WM_BUILD_VERSION=${{ github.sha }}
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
@@ -82,6 +83,7 @@ jobs:
push: true
build-args: |
features=ee_rhel
WM_BUILD_VERSION=${{ github.sha }}
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
+5 -1
View File
@@ -45,8 +45,12 @@ jobs:
env:
RUSTFLAGS: "-D warnings"
run: |
mkdir frontend/build && cd backend
cd backend
# Stub the openapi specs to empty: they are compiled in via an ungated
# include_str! but a worker binary never serves them, so this avoids
# embedding ~2.5MB of spec.
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
New-Item -Path . -Name "windmill-api/openapi-deref.json" -ItemType "File" -Force
cargo check --features=ee_windows
- name: Cargo build dynamic libraries windows
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
allowed_bots: 'windmill-internal-app[bot]'
trigger_phrase: '/plan'
claude_args: |
--model claude-opus-4-8
--model claude-opus-5
--system-prompt "# Claude Planning Mode
You are operating in PLANNING MODE ONLY. Your role is to create detailed, structured plans without making any code changes.
+1 -1
View File
@@ -93,4 +93,4 @@ jobs:
}
claude_args: |
--allowedTools "Bash,WebFetch,WebSearch"
--model claude-opus-4-8
--model claude-opus-5
+33
View File
@@ -82,6 +82,7 @@ jobs:
EVENT_BODY: ${{ github.event.pull_request.body }}
EVENT_FORK: ${{ github.event.pull_request.head.repo.fork }}
EVENT_AUTHOR: ${{ github.event.pull_request.user.login }}
EVENT_ACTION: ${{ github.event.action }}
run: |
if [ -n "$INPUT_PR_NUMBER" ]; then
PR_JSON=$(gh pr view "$INPUT_PR_NUMBER" --repo "${{ github.repository }}" \
@@ -113,6 +114,38 @@ jobs:
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# An agent-driven PR flips to ready only after a clean /review round on
# a draft, marked by an author comment naming the head SHA (pr skill,
# "Review rounds"). Re-reviewing that same head on ready_for_review is
# redundant. The marker alone is author attestation, so also require
# reviewer evidence: a Codex review (posted by github-actions[bot], not
# forgeable by the author) that predates the marker and carries a
# non-blocking verdict. Comment-triggered and synchronize runs never
# skip. Keep the three copies of this check in sync (pr-ready-review /
# codex-pr-review / pi-pr-review); a shared local action would need the
# repo checked out before the check, which the fork paths here
# deliberately avoid.
if [ "$EVENT_ACTION" = "ready_for_review" ] && [ -z "$INPUT_PR_NUMBER" ]; then
# Fetch failures fail open (no skip): an API hiccup must run the
# review, never skip it or fail the job.
COMMENTS=$(gh api "repos/${{ github.repository }}/issues/$PR_NUMBER/comments?per_page=100" --paginate | jq -s '[.[][]]') || COMMENTS='[]'
MARKER_TIME=$(jq -r --arg author "$PR_AUTHOR" --arg marker "✅ Review round clean @ $HEAD_SHA" \
'[.[] | select(.user.login == $author) | select(.body | contains($marker)) | .created_at] | min // empty' <<<"$COMMENTS")
CODEX_VERDICT=''
if [ -n "$MARKER_TIME" ]; then
# Only Codex evidence that predates the marker counts: the ready-
# triggered Codex run itself posts after the flip and must not
# vouch for a sibling reviewer's skip.
CODEX_VERDICT=$(jq -r --arg mt "$MARKER_TIME" \
'[.[] | select(.user.login == "github-actions[bot]") | select(.body | contains("## Codex Review")) | select(.created_at < $mt)] | last | .body // ""' <<<"$COMMENTS" \
| grep -m1 -oE '(Good to merge|Mergeable, but should ideally address nits|Should address issues before merging)' || true)
fi
if [ -n "$MARKER_TIME" ] && [ -n "$CODEX_VERDICT" ] && [ "$CODEX_VERDICT" != "Should address issues before merging" ]; then
echo "Clean review round marker found for $HEAD_SHA with pre-marker non-blocking Codex verdict; skipping redundant review."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
fi
# PR title/body are attacker-controlled free text. Use an unguessable
# per-run delimiter so a fork can't embed a fixed heredoc terminator to
# inject extra outputs — e.g. is_fork=false (last-write-wins), which
+1
View File
@@ -68,6 +68,7 @@ jobs:
push: true
build-args: |
features=ce_rpi
WM_BUILD_VERSION=${{ github.sha }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
${{ steps.meta-public.outputs.tags }}
+3
View File
@@ -93,6 +93,7 @@ jobs:
push: true
build-args: |
features=ce
WM_BUILD_VERSION=${{ github.sha }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
${{ steps.meta-public.outputs.tags }}
@@ -155,6 +156,7 @@ jobs:
push: true
build-args: |
features=ee
WM_BUILD_VERSION=${{ github.sha }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
${{ steps.meta-ee-public.outputs.tags }}
@@ -254,6 +256,7 @@ jobs:
target: debuginfo
build-args: |
features=ee
WM_BUILD_VERSION=${{ github.sha }}
outputs: type=local,dest=./debuginfo
- name: Rename debug file with corresponding architecture
+13 -2
View File
@@ -9,6 +9,10 @@ on:
- "backend/windmill-api-integration-tests/tests/git_sync*"
- "backend/ee-repo-ref.txt"
- "backend/windmill-common/src/workspaces.rs"
- "backend/windmill-worker/src/result_processor.rs"
- "backend/windmill-api-workspaces/**"
- "cli/src/commands/sync/**"
- "cli/src/utils/git.ts"
- "integration_tests/test/git_sync_test.py"
- ".github/workflows/git-sync-test.yml"
pull_request:
@@ -18,6 +22,10 @@ on:
- "backend/windmill-api-integration-tests/tests/git_sync*"
- "backend/ee-repo-ref.txt"
- "backend/windmill-common/src/workspaces.rs"
- "backend/windmill-worker/src/result_processor.rs"
- "backend/windmill-api-workspaces/**"
- "cli/src/commands/sync/**"
- "cli/src/utils/git.ts"
- "integration_tests/test/git_sync_test.py"
- ".github/workflows/git-sync-test.yml"
@@ -50,8 +58,8 @@ jobs:
echo "Changed files:"
echo "$CHANGED_FILES"
# Direct git sync file changes — always relevant
if echo "$CHANGED_FILES" | grep -qE '^(backend/windmill-git-sync/|backend/windmill-api-integration-tests/tests/git_sync|backend/windmill-common/src/workspaces\.rs|integration_tests/test/git_sync|\.github/workflows/git-sync-test\.yml)'; then
# Direct git sync file changes — always relevant.
if echo "$CHANGED_FILES" | grep -qE '^(backend/windmill-git-sync/|backend/windmill-worker/src/result_processor\.rs|backend/windmill-api-workspaces/|backend/windmill-api-integration-tests/tests/git_sync|backend/windmill-common/src/workspaces\.rs|cli/src/commands/sync/|cli/src/utils/git\.ts|integration_tests/test/git_sync|\.github/workflows/git-sync-test\.yml)'; then
echo "should_run=true" >> "$GITHUB_OUTPUT"
echo "Relevant: direct git sync file changes"
exit 0
@@ -180,6 +188,9 @@ jobs:
DENO_PATH: deno
BUN_PATH: bun
NODE_BIN_PATH: node
# The auto-pull poller's SSRF guard rejects localhost git remotes;
# the tests' Gitea runs on localhost.
ALLOW_LOCAL_GIT_REMOTES: "true"
run: |
./target/debug/windmill &
echo "Waiting for Windmill to be ready..."
+33
View File
@@ -75,6 +75,7 @@ jobs:
EVENT_BODY: ${{ github.event.pull_request.body }}
EVENT_FORK: ${{ github.event.pull_request.head.repo.fork }}
EVENT_AUTHOR: ${{ github.event.pull_request.user.login }}
EVENT_ACTION: ${{ github.event.action }}
run: |
if [ -n "$INPUT_PR_NUMBER" ]; then
PR_JSON=$(gh pr view "$INPUT_PR_NUMBER" --repo "${{ github.repository }}" \
@@ -106,6 +107,38 @@ jobs:
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# An agent-driven PR flips to ready only after a clean /review round on
# a draft, marked by an author comment naming the head SHA (pr skill,
# "Review rounds"). Re-reviewing that same head on ready_for_review is
# redundant. The marker alone is author attestation, so also require
# reviewer evidence: a Codex review (posted by github-actions[bot], not
# forgeable by the author) that predates the marker and carries a
# non-blocking verdict. Comment-triggered and synchronize runs never
# skip. Keep the three copies of this check in sync (pr-ready-review /
# codex-pr-review / pi-pr-review); a shared local action would need the
# repo checked out before the check, which the fork paths here
# deliberately avoid.
if [ "$EVENT_ACTION" = "ready_for_review" ] && [ -z "$INPUT_PR_NUMBER" ]; then
# Fetch failures fail open (no skip): an API hiccup must run the
# review, never skip it or fail the job.
COMMENTS=$(gh api "repos/${{ github.repository }}/issues/$PR_NUMBER/comments?per_page=100" --paginate | jq -s '[.[][]]') || COMMENTS='[]'
MARKER_TIME=$(jq -r --arg author "$PR_AUTHOR" --arg marker "✅ Review round clean @ $HEAD_SHA" \
'[.[] | select(.user.login == $author) | select(.body | contains($marker)) | .created_at] | min // empty' <<<"$COMMENTS")
CODEX_VERDICT=''
if [ -n "$MARKER_TIME" ]; then
# Only Codex evidence that predates the marker counts: the ready-
# triggered Codex run itself posts after the flip and must not
# vouch for a sibling reviewer's skip.
CODEX_VERDICT=$(jq -r --arg mt "$MARKER_TIME" \
'[.[] | select(.user.login == "github-actions[bot]") | select(.body | contains("## Codex Review")) | select(.created_at < $mt)] | last | .body // ""' <<<"$COMMENTS" \
| grep -m1 -oE '(Good to merge|Mergeable, but should ideally address nits|Should address issues before merging)' || true)
fi
if [ -n "$MARKER_TIME" ] && [ -n "$CODEX_VERDICT" ] && [ "$CODEX_VERDICT" != "Should address issues before merging" ]; then
echo "Clean review round marker found for $HEAD_SHA with pre-marker non-blocking Codex verdict; skipping redundant review."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
fi
# PR title/body are attacker-controlled free text. Use an unguessable
# per-run delimiter so a fork can't embed a fixed heredoc terminator to
# inject extra outputs — e.g. is_fork=false (last-write-wins), which
+48 -1
View File
@@ -48,12 +48,55 @@ jobs:
pull-requests: read
id-token: write
steps:
# An agent-driven PR flips to ready only after a clean /review round on a
# draft, marked by an author comment naming the head SHA (pr skill, "Review
# rounds"). Re-reviewing that same head on ready_for_review is redundant.
# The marker alone is author attestation, so also require reviewer evidence:
# a Codex review (posted by github-actions[bot], not forgeable by the author)
# that predates the marker and carries a non-blocking verdict. Comment-
# triggered (workflow_call) and opened runs never skip. Keep the three
# copies of this check in sync (pr-ready-review / codex-pr-review /
# pi-pr-review); a shared local action would need the repo checked out
# before the check, which the codex/pi fork paths deliberately avoid.
- name: Check clean-round marker
id: marker
if: github.event_name == 'pull_request' && github.event.action == 'ready_for_review'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
run: |
# Fetch failures fail open (skip=false): an API hiccup must run the
# review, never skip it or fail the job.
COMMENTS=$(gh api "repos/$REPO/issues/$PR_NUMBER/comments?per_page=100" --paginate | jq -s '[.[][]]') || COMMENTS='[]'
MARKER_TIME=$(jq -r --arg author "$PR_AUTHOR" --arg marker "✅ Review round clean @ $HEAD_SHA" \
'[.[] | select(.user.login == $author) | select(.body | contains($marker)) | .created_at] | min // empty' <<<"$COMMENTS")
CODEX_VERDICT=''
if [ -n "$MARKER_TIME" ]; then
# Only Codex evidence that predates the marker counts: the ready-
# triggered Codex run itself posts after the flip and must not vouch
# for a sibling reviewer's skip.
CODEX_VERDICT=$(jq -r --arg mt "$MARKER_TIME" \
'[.[] | select(.user.login == "github-actions[bot]") | select(.body | contains("## Codex Review")) | select(.created_at < $mt)] | last | .body // ""' <<<"$COMMENTS" \
| grep -m1 -oE '(Good to merge|Mergeable, but should ideally address nits|Should address issues before merging)' || true)
fi
if [ -n "$MARKER_TIME" ] && [ -n "$CODEX_VERDICT" ] && [ "$CODEX_VERDICT" != "Should address issues before merging" ]; then
echo "Clean review round marker found for $HEAD_SHA with pre-marker non-blocking Codex verdict; skipping redundant review."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Checkout repository
if: steps.marker.outputs.skip != 'true'
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Check EE access
if: steps.marker.outputs.skip != 'true'
id: ee
env:
EE_TOKEN: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
@@ -80,6 +123,7 @@ jobs:
run: ./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
- name: Resolve PR number
if: steps.marker.outputs.skip != 'true'
id: resolve
env:
GH_TOKEN: ${{ github.token }}
@@ -99,6 +143,7 @@ jobs:
echo "pr_author=$PR_AUTHOR" >> "$GITHUB_OUTPUT"
- name: Fetch prior PR discussion
if: steps.marker.outputs.skip != 'true'
id: prior
env:
GH_TOKEN: ${{ github.token }}
@@ -117,6 +162,7 @@ jobs:
' prior-comments.json > prior-comments.md
- name: Read review prompt
if: steps.marker.outputs.skip != 'true'
id: review-prompt
env:
EXTRA_PROMPT: ${{ inputs.extra_prompt }}
@@ -140,6 +186,7 @@ jobs:
} >> "$GITHUB_ENV"
- name: Automatic PR Review
if: steps.marker.outputs.skip != 'true'
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
@@ -152,4 +199,4 @@ jobs:
${{ env.REVIEW_PROMPT }}
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
--model claude-opus-4-8
--model claude-opus-5
+176 -6
View File
@@ -75,14 +75,153 @@ jobs:
"/repos/$REPO/issues/comments/$COMMENT_ID/reactions" \
-f content=eyes >/dev/null
claude:
# Decide, per agent, whether to launch a fresh run, re-run in place, or skip. A push
# already auto-triggers codex/pi (and claude on open) against the PR head. Relaunching
# via this issue_comment path both cancels those in-flight auto runs (shared concurrency
# group) AND lands the new run's status on main — issue_comment runs never attach a
# check to the PR head — leaving the PR showing only a cancelled review. So for every
# command, launch an agent only when nothing covers the head commit; if the head's run
# was cancelled/failed, re-run it in place (a re-run keeps the original pull_request
# event, so its checks re-attach to the PR head); skip when a running or successful run
# already covers it. `/review` applies this to all three agents; `/codex`, `/pi`,
# `/claude` apply the same decision to just their own agent.
plan:
needs: [parse, check-access]
if: |
needs.parse.outputs.command != '' &&
(
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) ||
needs.check-access.outputs.authorized == 'true'
)
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
pull-requests: read
statuses: write
outputs:
head_sha: ${{ steps.plan.outputs.head_sha }}
launch_codex: ${{ steps.plan.outputs.launch_codex }}
launch_pi: ${{ steps.plan.outputs.launch_pi }}
launch_claude: ${{ steps.plan.outputs.launch_claude }}
steps:
- name: Decide per-agent launch vs re-run for the head commit
id: plan
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.issue.number }}
COMMAND: ${{ needs.parse.outputs.command }}
run: |
set -euo pipefail
HEAD_SHA=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefOid --jq '.headRefOid')
echo "PR #$PR_NUMBER head: $HEAD_SHA"
echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
RUN_URL="$GITHUB_SERVER_URL/$REPO/actions/runs/$GITHUB_RUN_ID"
# A fresh launch runs from this issue_comment workflow (associated with main),
# so it never appears in the PR-head run query below and its own check lands on
# main, not the head. To keep fresh launches idempotent per head, mark the head
# SHA with a `review-launch/<agent>` commit status at launch; the `finalize` job
# resolves it to success/failure. A prior launch's status covering the head lets
# a second comment skip instead of relaunching (which would cancel the first via
# the reviewer's shared concurrency group). All status calls are best-effort — a
# GitHub API hiccup must degrade to a relaunch, never abort the decision.
mark_launch() {
agent="$1"
gh api -X POST "repos/$REPO/statuses/$HEAD_SHA" \
-f state=pending -f "context=review-launch/$agent" -f "target_url=$RUN_URL" \
-f "description=Review launched via /$COMMAND" >/dev/null 2>&1 || true
}
# Returns "covered" if a prior fresh launch (this or an earlier comment run)
# already covers the head: a success status, or a pending status whose launching
# run is still alive. A pending whose run has completed is stale (that run
# crashed before finalize) and does not count.
launch_coverage() {
agent="$1"
st_json=$(gh api "repos/$REPO/commits/$HEAD_SHA/statuses" \
--jq "[.[] | select(.context == \"review-launch/$agent\")] | first // empty" 2>/dev/null || true)
[ -n "$st_json" ] || return 0
state=$(jq -r '.state // empty' <<<"$st_json" 2>/dev/null || true)
[ "$state" = success ] && { echo covered; return 0; }
[ "$state" = pending ] || return 0
target=$(jq -r '.target_url // empty' <<<"$st_json" 2>/dev/null || true)
run_id=$(printf '%s' "$target" | grep -oE '[0-9]+$' || true)
if [ -n "$run_id" ]; then
run_state=$(gh run view "$run_id" --repo "$REPO" --json status --jq '.status' 2>/dev/null || true)
[ "$run_state" = completed ] && return 0 # stale pending -> not covered
fi
echo covered
}
decide() {
wf="$1"; key="$2"; agent="$3"
if [ "$(launch_coverage "$agent")" = covered ]; then
echo "$key: a prior launch already covers $HEAD_SHA (review-launch/$agent) -> skip"
echo "$key=false" >> "$GITHUB_OUTPUT"
return
fi
# `--commit` matches runs whose head SHA is the PR head. Auto reviews run on
# `pull_request` against that SHA; `/review` (issue_comment) runs execute on
# main, so they never match and are not counted as covering the head commit.
runs=$(gh run list --repo "$REPO" --workflow "$wf" --commit "$HEAD_SHA" --limit 40 \
--json databaseId,status,conclusion)
# Healthy = still running, or completed successfully: a review already
# covers this commit, so skip.
healthy=$(jq -r '[.[] | select(.status != "completed" or .conclusion == "success")] | length' <<<"$runs")
if [ "$healthy" -gt 0 ]; then
echo "$key: a running or successful review already covers $HEAD_SHA -> skip"
echo "$key=false" >> "$GITHUB_OUTPUT"
return
fi
# Re-run only genuinely interrupted runs (cancelled/failed/timed out) in
# place, so their checks re-attach to the PR head instead of posting on
# main. A `skipped` run produced no review and would just skip again (it is
# the draft/fork gate), so it does not count — fall through to a fresh launch.
retry_id=$(jq -r '[.[] | select(.status == "completed" and (.conclusion == "cancelled" or .conclusion == "failure" or .conclusion == "timed_out"))] | sort_by(.databaseId) | last | .databaseId // empty' <<<"$runs")
if [ -n "$retry_id" ]; then
if gh run rerun "$retry_id" --repo "$REPO" >/dev/null 2>&1; then
echo "$key: re-ran interrupted run $retry_id (re-attaches to PR head)"
echo "$key=false" >> "$GITHUB_OUTPUT"
return
fi
echo "$key: re-run of $retry_id failed -> fresh launch"
mark_launch "$agent"
echo "$key=true" >> "$GITHUB_OUTPUT"
return
fi
echo "$key: no usable review for $HEAD_SHA -> launch"
mark_launch "$agent"
echo "$key=true" >> "$GITHUB_OUTPUT"
}
# `/review` targets all three agents; `/codex`, `/pi`, `/claude` target only
# their own. A non-targeted agent is left untouched (no launch, no re-run).
decide_if_targeted() {
wf="$1"; key="$2"; agent="$3"
if [ "$COMMAND" = review ] || [ "$COMMAND" = "$agent" ]; then
decide "$wf" "$key" "$agent"
else
echo "$key: /$COMMAND does not target $agent -> skip"
echo "$key=false" >> "$GITHUB_OUTPUT"
fi
}
decide_if_targeted codex-pr-review.yml launch_codex codex
decide_if_targeted pi-pr-review.yml launch_pi pi
decide_if_targeted pr-ready-review.yml launch_claude claude
claude:
needs: [parse, check-access, plan]
if: |
(
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) ||
needs.check-access.outputs.authorized == 'true'
) &&
(needs.parse.outputs.command == 'review' || needs.parse.outputs.command == 'claude')
needs.plan.outputs.launch_claude == 'true'
permissions:
contents: read
pull-requests: read
@@ -97,13 +236,13 @@ jobs:
WINDMILL_EE_PRIVATE_ACCESS: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
codex:
needs: [parse, check-access]
needs: [parse, check-access, plan]
if: |
(
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) ||
needs.check-access.outputs.authorized == 'true'
) &&
(needs.parse.outputs.command == 'review' || needs.parse.outputs.command == 'codex')
needs.plan.outputs.launch_codex == 'true'
permissions:
contents: read
issues: write
@@ -119,13 +258,13 @@ jobs:
WINDMILL_EE_PRIVATE_ACCESS: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
pi:
needs: [parse, check-access]
needs: [parse, check-access, plan]
if: |
(
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) ||
needs.check-access.outputs.authorized == 'true'
) &&
(needs.parse.outputs.command == 'review' || needs.parse.outputs.command == 'pi')
needs.plan.outputs.launch_pi == 'true'
permissions:
contents: read
issues: write
@@ -138,3 +277,34 @@ jobs:
secrets:
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
WINDMILL_EE_PRIVATE_ACCESS: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
# Resolve the `review-launch/<agent>` head statuses that `plan` set to pending, so a
# fresh launch's outcome is visible on the PR head (not just on main) and never lingers
# as a stale pending check. Targets the exact SHA `plan` launched against, so a push
# that moved the head mid-review does not stamp a status on the new head.
finalize:
needs: [plan, claude, codex, pi]
if: always() && needs.plan.result == 'success' && needs.plan.outputs.head_sha != ''
runs-on: ubuntu-latest
permissions:
statuses: write
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
HEAD_SHA: ${{ needs.plan.outputs.head_sha }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
steps:
- name: Finalize launch statuses on the PR head
run: |
set -uo pipefail
finalize() {
agent="$1"; launched="$2"; result="$3"
[ "$launched" = true ] || return 0
state=$([ "$result" = success ] && echo success || echo failure)
gh api -X POST "repos/$REPO/statuses/$HEAD_SHA" \
-f "state=$state" -f "context=review-launch/$agent" -f "target_url=$RUN_URL" \
-f "description=Review $result" >/dev/null 2>&1 || true
}
finalize codex "${{ needs.plan.outputs.launch_codex }}" "${{ needs.codex.result }}"
finalize pi "${{ needs.plan.outputs.launch_pi }}" "${{ needs.pi.result }}"
finalize claude "${{ needs.plan.outputs.launch_claude }}" "${{ needs.claude.result }}"
+5 -1
View File
@@ -56,8 +56,12 @@ jobs:
vcpkg.exe integrate install
$env:VCPKGRS_DYNAMIC=1
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
mkdir frontend/build && cd backend
cd backend
# Stub the openapi specs to empty: they are compiled in via an ungated
# include_str! but a worker binary never serves them, so this avoids
# embedding ~2.5MB of spec.
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
New-Item -Path . -Name "windmill-api/openapi-deref.json" -ItemType "File" -Force
cargo build --release --features=ee_windows
- name: Rename binary with corresponding architecture
run: |
+277
View File
@@ -1,5 +1,282 @@
# Changelog
## [1.770.0](https://github.com/windmill-labs/windmill/compare/v1.769.0...v1.770.0) (2026-07-24)
### Features
* add explore button for object storage resources ([#10306](https://github.com/windmill-labs/windmill/issues/10306)) ([28a79ce](https://github.com/windmill-labs/windmill/commit/28a79ced155e6bb3540ab76feb5415ea8f3d8f8f))
* Add image when publishing a project ([#10310](https://github.com/windmill-labs/windmill/issues/10310)) ([48618cf](https://github.com/windmill-labs/windmill/commit/48618cff8c35a345babd12844653f83addbcd7e8))
### Bug Fixes
* do not apply workspace display name on git-sync pull ([#10308](https://github.com/windmill-labs/windmill/issues/10308)) ([992ed01](https://github.com/windmill-labs/windmill/commit/992ed0124492f5c6b9e3dcc6a309caeda8d6a7b1))
* pass Windows system env vars to R renv install subprocess ([#10313](https://github.com/windmill-labs/windmill/issues/10313)) ([85008e4](https://github.com/windmill-labs/windmill/commit/85008e47b4a21313ec66496d3c68eb746fc8a151))
* show scheduled singlestepflow runs in flow history sidebar ([#10312](https://github.com/windmill-labs/windmill/issues/10312)) ([f00fcb2](https://github.com/windmill-labs/windmill/commit/f00fcb2d1b1030b47c824bdefc9f0d12499bc6b6))
* WAC wait_for_approval reads its own approval result, not the first ([#10314](https://github.com/windmill-labs/windmill/issues/10314)) ([2143d45](https://github.com/windmill-labs/windmill/commit/2143d45815af7a95182721aeb0dc937a12c5759f))
## [1.769.0](https://github.com/windmill-labs/windmill/compare/v1.768.0...v1.769.0) (2026-07-24)
### Features
* **hub:** surface data pipelines in deploy-to-hub drawer ([#10299](https://github.com/windmill-labs/windmill/issues/10299)) ([010059a](https://github.com/windmill-labs/windmill/commit/010059a449f4d0cde0656086f1b3f03a02a1c78c))
* **pipeline:** collapse secondary top-bar controls into an overflow menu ([#10300](https://github.com/windmill-labs/windmill/issues/10300)) ([1d25d75](https://github.com/windmill-labs/windmill/commit/1d25d7539ed7fb17c47a732d6b1cd7fc19202a04))
### Bug Fixes
* pin table actions column so it stays visible on narrow screens ([#10301](https://github.com/windmill-labs/windmill/issues/10301)) ([75acf72](https://github.com/windmill-labs/windmill/commit/75acf7207b151f0227600be555bfa5e14dfa8dd1))
* pin validated DNS address to close SSRF DNS-rebinding TOCTOU ([#10303](https://github.com/windmill-labs/windmill/issues/10303)) ([3cf7a39](https://github.com/windmill-labs/windmill/commit/3cf7a390a37c07a818248c95defa4ecf5bf262e5))
### Performance Improvements
* optimize get_datatable_full_schema to avoid timeout on large catalogs ([#10304](https://github.com/windmill-labs/windmill/issues/10304)) ([1478d12](https://github.com/windmill-labs/windmill/commit/1478d12eb352b1b7906ccfe3ab5eaafe60ffbe4f))
## [1.768.0](https://github.com/windmill-labs/windmill/compare/v1.767.0...v1.768.0) (2026-07-24)
### Features
* Add section to deploy projects to hub ([#9332](https://github.com/windmill-labs/windmill/issues/9332)) ([30eedf9](https://github.com/windmill-labs/windmill/commit/30eedf9ee1754cb2bbf46e82d2b3eac766bd3e6d))
* **ai-chat:** let the session/global chat create email triggers ([#10282](https://github.com/windmill-labs/windmill/issues/10282)) ([248c875](https://github.com/windmill-labs/windmill/commit/248c8751b1fd5d6f7506a7c3fc8f500dd98af26e))
* **ai:** enable data pipelines in AI sessions with alpha notice ([#10273](https://github.com/windmill-labs/windmill/issues/10273)) ([1f912f4](https://github.com/windmill-labs/windmill/commit/1f912f410401ec68789d43973d6e0a1f8e595f86))
* alert on expired online license key ([#10295](https://github.com/windmill-labs/windmill/issues/10295)) ([30d8104](https://github.com/windmill-labs/windmill/commit/30d8104edcbdb7f0c63f186d22ac03000e0d4f07))
* data-pipeline recorder, interactive player, and deploy-to-hub recording (WIN-2156) ([#10055](https://github.com/windmill-labs/windmill/issues/10055)) ([65e5041](https://github.com/windmill-labs/windmill/commit/65e504146d832b105603ceec89c6de0ad1d66e32))
* let a workspace fall back to the instance critical alert channels ([#10292](https://github.com/windmill-labs/windmill/issues/10292)) ([717e38a](https://github.com/windmill-labs/windmill/commit/717e38a0c6b5bb2340e236a9d49645a4cebf4849))
* **monitor:** make between-steps zombie flows hand-recoverable ([#10287](https://github.com/windmill-labs/windmill/issues/10287)) ([f02df7f](https://github.com/windmill-labs/windmill/commit/f02df7fc454b0c2a2afa9ae9848e26af0e379246))
* surface workspace-script advanced settings in flow editor ([#10289](https://github.com/windmill-labs/windmill/issues/10289)) ([bf16e7d](https://github.com/windmill-labs/windmill/commit/bf16e7d49a7486d37cf9eb1907e80abae47a78a7))
* **windows:** enable ruby and rlang on the windows worker ([#10279](https://github.com/windmill-labs/windmill/issues/10279)) ([0f1b864](https://github.com/windmill-labs/windmill/commit/0f1b8641f26f0eda8a8517db1a37efafdb173464))
### Bug Fixes
* **ci:** make /review idempotent per head commit, re-run cancelled reviews in place ([#10283](https://github.com/windmill-labs/windmill/issues/10283)) ([3aaceb7](https://github.com/windmill-labs/windmill/commit/3aaceb7efb3bd232da73b79fcdcf4c712caad7d3))
* **cli:** surface shared UI changes in sync push dry-run preview ([#10278](https://github.com/windmill-labs/windmill/issues/10278)) ([14c29b7](https://github.com/windmill-labs/windmill/commit/14c29b77e90842b8c3d4395ce50cb44666cd998c))
* **jobs:** sanitize NUL in completed job result before jsonb insert ([#10274](https://github.com/windmill-labs/windmill/issues/10274)) ([c50a2ab](https://github.com/windmill-labs/windmill/commit/c50a2abad0c222bf8f76ff10b864450120ee9ee7))
* **monitor:** diagnose zombie-flow OOM on the transition worker, not q.worker ([#10286](https://github.com/windmill-labs/windmill/issues/10286)) ([fa36442](https://github.com/windmill-labs/windmill/commit/fa3644281f30d90aa0c4ca86520a38db5d8b91f0))
* resolve svelte/style export conditions in raw-app CLI bundler ([#10294](https://github.com/windmill-labs/windmill/issues/10294)) ([9713e60](https://github.com/windmill-labs/windmill/commit/9713e6074d2df55db1331cf31ee1357606b1dbe9))
* **resources:** apply resource_type changes on update (git-sync pull) — Fixes GIT-932 ([#10277](https://github.com/windmill-labs/windmill/issues/10277)) ([d7a0078](https://github.com/windmill-labs/windmill/commit/d7a0078b58f74f1ffecc03b348e8935a70f02e55))
* surface workspace ids on duplicate names and explain fork promotion ([#10291](https://github.com/windmill-labs/windmill/issues/10291)) ([9b182aa](https://github.com/windmill-labs/windmill/commit/9b182aaf3879d4d81b6a785222dca75f085e6fb7))
* treat concurrent_limit/timeout &lt;= 0 as unset instead of a zero cap ([#10288](https://github.com/windmill-labs/windmill/issues/10288)) ([8eb36ce](https://github.com/windmill-labs/windmill/commit/8eb36ce008b4efe2be9a9bfebc91af68070f7a6c))
## [1.767.0](https://github.com/windmill-labs/windmill/compare/v1.766.2...v1.767.0) (2026-07-22)
### Features
* **ai-chat:** email triggers in flow/script chat + trigger-intent eval guards (WIN-2228) ([#10267](https://github.com/windmill-labs/windmill/issues/10267)) ([e41440b](https://github.com/windmill-labs/windmill/commit/e41440b344d94ebe5746a89237c3d1be522aa59d))
* **ai:** improve data-pipeline building in AI sessions (prompt + evals + e2e) ([#10270](https://github.com/windmill-labs/windmill/issues/10270)) ([0819641](https://github.com/windmill-labs/windmill/commit/0819641f3a89abdf114cc5e341a9f585e5d71296))
### Bug Fixes
* **ai-chat:** improve resource-type search tool description and scoring ([#10272](https://github.com/windmill-labs/windmill/issues/10272)) ([ad53673](https://github.com/windmill-labs/windmill/commit/ad53673a2855e787af9fe10dcc8998ffc7f1b960))
* **embeddings:** retry on failed init instead of disabling for a day ([#10266](https://github.com/windmill-labs/windmill/issues/10266)) ([2318481](https://github.com/windmill-labs/windmill/commit/2318481f4f2932dc578d5cb64c4c705cf5c324a0))
* manual resource type sync fetches from hub first, cache as fallback ([#10269](https://github.com/windmill-labs/windmill/issues/10269)) ([07d4b67](https://github.com/windmill-labs/windmill/commit/07d4b674f1dbd24c89f7d1e77094a40e528a3740))
## [1.766.2](https://github.com/windmill-labs/windmill/compare/v1.766.1...v1.766.2) (2026-07-22)
### Bug Fixes
* **python,windows:** cross-platform cross-process wheel-install lock ([#10264](https://github.com/windmill-labs/windmill/issues/10264)) ([7e2f1af](https://github.com/windmill-labs/windmill/commit/7e2f1afffb4982099870e7ce32592cf3a447e513))
## [1.766.1](https://github.com/windmill-labs/windmill/compare/v1.766.0...v1.766.1) (2026-07-22)
### Bug Fixes
* **jobs:** enforce self_approval_disabled on the UI resume path ([#10262](https://github.com/windmill-labs/windmill/issues/10262)) ([2d24b3a](https://github.com/windmill-labs/windmill/commit/2d24b3ac49b65469072bca929730d2c9ecd54b8f))
## [1.766.0](https://github.com/windmill-labs/windmill/compare/v1.765.0...v1.766.0) (2026-07-22)
### Features
* **ai-sessions:** show item preview cards for tools ([#10254](https://github.com/windmill-labs/windmill/issues/10254)) ([703744f](https://github.com/windmill-labs/windmill/commit/703744fb8b76f70384cbc3341d5e1b3dbf30455e))
* make content search a full CE feature ([#10252](https://github.com/windmill-labs/windmill/issues/10252)) ([d2c5d6f](https://github.com/windmill-labs/windmill/commit/d2c5d6f4b4ede9449407ed3831713d3e3d1d6972))
* **sessions:** ship AI sessions as beta with legacy-chat opt-out ([#10242](https://github.com/windmill-labs/windmill/issues/10242)) ([0508cdd](https://github.com/windmill-labs/windmill/commit/0508cddf0a86b6a6a043cb31978ca68b76a95d02))
### Bug Fixes
* accept ssh/scheme-less git repo urls and $var: refs in app repo resolution ([#10246](https://github.com/windmill-labs/windmill/issues/10246)) ([b948efd](https://github.com/windmill-labs/windmill/commit/b948efd3c81aada103bd141eae03fbe626bf6ee5))
* **copilot:** stop write_flow forcing rawscript code into nested JSON ([#10260](https://github.com/windmill-labs/windmill/issues/10260)) ([ecb1a92](https://github.com/windmill-labs/windmill/commit/ecb1a92070fb18048a6a1467270469e8462a3cbf))
* prevent u16 underflow in suspend count causing permanent flow deadlock ([#10256](https://github.com/windmill-labs/windmill/issues/10256)) ([68b1fcc](https://github.com/windmill-labs/windmill/commit/68b1fcc5cdd3d217ebb1b9e09c0d52334faf0a63))
* **prompts:** prefer Bun over Deno for TypeScript runtime selection ([#10253](https://github.com/windmill-labs/windmill/issues/10253)) ([380cf75](https://github.com/windmill-labs/windmill/commit/380cf752ca8fc57eb70f7c8efd57290f7902a030))
* **tutorials:** repair broken frontend tutorials after UI redesigns ([#10255](https://github.com/windmill-labs/windmill/issues/10255)) ([5685981](https://github.com/windmill-labs/windmill/commit/5685981c9902ff37c3bd271dc5c5ede948a0787a))
## [1.765.0](https://github.com/windmill-labs/windmill/compare/v1.764.0...v1.765.0) (2026-07-21)
### Features
* **ai:** open the Compare & Deploy page from chat with item preselection ([#10232](https://github.com/windmill-labs/windmill/issues/10232)) ([572d69e](https://github.com/windmill-labs/windmill/commit/572d69e5ae8ae12207dca175fdb5ea378d852a45))
* **ai:** session chat nits — empty sends, command picker polish, session-state prompt ([#10233](https://github.com/windmill-labs/windmill/issues/10233)) ([9bc1f62](https://github.com/windmill-labs/windmill/commit/9bc1f6212837155c964a7db7103aabbcf14cbce2))
* attach text files to chat messages, read on demand via file tools ([#10215](https://github.com/windmill-labs/windmill/issues/10215)) ([d6cf1ef](https://github.com/windmill-labs/windmill/commit/d6cf1ef9872cd0db0f51e4881237bac383e45f61))
* **git-sync:** enable per-item promotion mode on dev workspaces ([#10205](https://github.com/windmill-labs/windmill/issues/10205)) ([2ce21c9](https://github.com/windmill-labs/windmill/commit/2ce21c9ef86c608d73e12a7e278d2f328e06b6fa))
* **pipelines:** catalog declared measures and dimensions ([#10190](https://github.com/windmill-labs/windmill/issues/10190)) ([fd51d40](https://github.com/windmill-labs/windmill/commit/fd51d40f1254a206de1f38e59cc3dadce8e13aa5))
* **triggers:** add AMQP (RabbitMQ) trigger via lapin ([#10230](https://github.com/windmill-labs/windmill/issues/10230)) ([68debab](https://github.com/windmill-labs/windmill/commit/68debab877c6dc8ee3732e0c23d467db85fd4584))
* unified read-only `diff` chat tool (drafts, fork vs parent, search) ([#10211](https://github.com/windmill-labs/windmill/issues/10211)) ([9739d5a](https://github.com/windmill-labs/windmill/commit/9739d5a2c2a21beded8848ea9049b2816196a898))
### Bug Fixes
* **apps:** let entitled viewers read pre-existing S3 files from deployed apps ([#10245](https://github.com/windmill-labs/windmill/issues/10245)) ([4a89824](https://github.com/windmill-labs/windmill/commit/4a898247a21ae918fa952a993fbe407ebc49e404))
* **db:** repair s3 asset paths missing default-storage leading slash ([#10243](https://github.com/windmill-labs/windmill/issues/10243)) ([555c751](https://github.com/windmill-labs/windmill/commit/555c751016fea4087be09fa2520af331cc872bfc))
* **frontend:** curl fallback for +Variable/+Resource in bash sandbox mode ([#10235](https://github.com/windmill-labs/windmill/issues/10235)) ([28966bd](https://github.com/windmill-labs/windmill/commit/28966bdbf190ac461aa5259e04d6c15cf699fd94))
* **frontend:** limit compare & deploy rows to the active direction ([#10234](https://github.com/windmill-labs/windmill/issues/10234)) ([7ac27c1](https://github.com/windmill-labs/windmill/commit/7ac27c1ef240729fc38d5fa506b96bf60be74e25))
* **frontend:** prevent browser back-swipe navigation over monaco editors ([#10229](https://github.com/windmill-labs/windmill/issues/10229)) ([b0bf256](https://github.com/windmill-labs/windmill/commit/b0bf25683ba3aac3e94bbe9d6bbd6e22545e734e))
* **parsers:** keep s3 asset path suffix verbatim to preserve storage distinction ([#10241](https://github.com/windmill-labs/windmill/issues/10241)) ([7fb8a2e](https://github.com/windmill-labs/windmill/commit/7fb8a2e390cef3cd01a33c89ee55d3f53cb1e20f))
* **parser:** spurious pg arg inferred from placeholders in comments ([#10226](https://github.com/windmill-labs/windmill/issues/10226)) ([d24e176](https://github.com/windmill-labs/windmill/commit/d24e1768163c10fbbc144bd1126457e42d282e37))
* **pg:** actionable error when s3object input exceeds jsonb 256MB cap ([#10228](https://github.com/windmill-labs/windmill/issues/10228)) ([6e42633](https://github.com/windmill-labs/windmill/commit/6e4263364325e4d4616697c6da601bbc887ff43c))
* **postgres-triggers:** enforce resource-path scopes on ancillary routes ([#10222](https://github.com/windmill-labs/windmill/issues/10222)) ([39058c0](https://github.com/windmill-labs/windmill/commit/39058c0a01c35cf553daae3af37c6d326a7c8763))
* return to parent workspace when a fork is deleted remotely ([#9898](https://github.com/windmill-labs/windmill/issues/9898)) ([32994df](https://github.com/windmill-labs/windmill/commit/32994df427e6df6747caf1ba6371512d5a5be5c2))
* **s3:** support instance-policy credentials in object storage tests ([#10238](https://github.com/windmill-labs/windmill/issues/10238)) ([ec63244](https://github.com/windmill-labs/windmill/commit/ec6324409d6c0e9ddcda5d110ac570ca79fe9e38))
* **triggers:** apply scope-path filtering to list and fix update scope check ([#10220](https://github.com/windmill-labs/windmill/issues/10220)) ([adc555d](https://github.com/windmill-labs/windmill/commit/adc555d1722cb59aa5b88503b3105357d76f5b71))
* **worker:** mount /dev/shm as tmpfs in the Docker v2 nsjail sandbox ([#10240](https://github.com/windmill-labs/windmill/issues/10240)) ([2caee41](https://github.com/windmill-labs/windmill/commit/2caee41fdfe1e69010a1f4544d45aaa5db49f590))
## [1.764.0](https://github.com/windmill-labs/windmill/compare/v1.763.0...v1.764.0) (2026-07-20)
### Features
* **ai:** add npm package search tool to global chat ([#10204](https://github.com/windmill-labs/windmill/issues/10204)) ([0593ff7](https://github.com/windmill-labs/windmill/commit/0593ff7d7d598af0b9f52d08bb22147e3a83e6e9))
* **ai:** expose get_db_schema tool in global chat ([#10207](https://github.com/windmill-labs/windmill/issues/10207)) ([f4308cf](https://github.com/windmill-labs/windmill/commit/f4308cf033b75e9a69c1e21041ff97f3bfa1787c))
* **ai:** extract prompt cache token usage from OpenAI and Azure providers ([#10214](https://github.com/windmill-labs/windmill/issues/10214)) ([2b58df5](https://github.com/windmill-labs/windmill/commit/2b58df57fc5bae08155ffd7198643ed739e6a463))
* **ai:** live web-search source list on chat tool cards ([#10210](https://github.com/windmill-labs/windmill/issues/10210)) ([542a484](https://github.com/windmill-labs/windmill/commit/542a4842a317a6607de973b4567bdb27dc088664))
* cap queued jobs per concurrency key on cloud ([#10197](https://github.com/windmill-labs/windmill/issues/10197)) ([71f2d47](https://github.com/windmill-labs/windmill/commit/71f2d47cb4ff331cbcc7843bf0704ddb63cb77db))
* detect server-handled git-sync so CLI picks git push vs wmill sync push ([#10201](https://github.com/windmill-labs/windmill/issues/10201)) ([b070f56](https://github.com/windmill-labs/windmill/commit/b070f56c5e6109c219a53cbb9628e28be4397fb7))
* expose windmill api endpoint catalog to global ai chat ([#10199](https://github.com/windmill-labs/windmill/issues/10199)) ([83a354f](https://github.com/windmill-labs/windmill/commit/83a354f831cbc1821f2316e6d4e1c7f7a2cd7cdb))
* **jobs:** cap total queued jobs per workspace on cloud ([#10218](https://github.com/windmill-labs/windmill/issues/10218)) ([ddec2ab](https://github.com/windmill-labs/windmill/commit/ddec2abbb3f56fc44d4c75fde60f00fb1c6373bb))
* **sessions:** live DOM access for the raw-app preview in AI sessions ([#10129](https://github.com/windmill-labs/windmill/issues/10129)) ([b448af1](https://github.com/windmill-labs/windmill/commit/b448af1da7f6a351973724721e52682259212292))
* **telemetry:** generic feature-usage telemetry with AI session metrics ([#10200](https://github.com/windmill-labs/windmill/issues/10200)) ([11fda89](https://github.com/windmill-labs/windmill/commit/11fda89b520cc8d1ce30cdba36bd10355cf025cc))
### Bug Fixes
* **db:** grant schema usage and re-run windmill role grants ([#10212](https://github.com/windmill-labs/windmill/issues/10212)) ([38ceae1](https://github.com/windmill-labs/windmill/commit/38ceae1a098ae165c75f43aa9049b5afafdd3bac))
* **forks:** show workspace settings link in sidebar for fork creators ([#10216](https://github.com/windmill-labs/windmill/issues/10216)) ([11bb37d](https://github.com/windmill-labs/windmill/commit/11bb37d7ca7fbd50cc66905f41851226a48e57fb))
* **git-sync:** avoid percent-encoded colon in git-sync hub script path ([#10213](https://github.com/windmill-labs/windmill/issues/10213)) ([87be041](https://github.com/windmill-labs/windmill/commit/87be041c091dd7fdd8c6be907a9b2894779f9ac0))
* **inputs:** add ownership check to saved-input args read endpoint ([#10194](https://github.com/windmill-labs/windmill/issues/10194)) ([f32d770](https://github.com/windmill-labs/windmill/commit/f32d7702bcb592a70f5cf7e01f9e830047645240))
* **schedules:** apply scope-path filtering to schedule list endpoints ([#10192](https://github.com/windmill-labs/windmill/issues/10192)) ([70359e3](https://github.com/windmill-labs/windmill/commit/70359e3a76d2e1fa5994acce4909753410c80664))
* **security:** enforce token scope filtering on folder list endpoints ([#10193](https://github.com/windmill-labs/windmill/issues/10193)) ([b5e69ff](https://github.com/windmill-labs/windmill/commit/b5e69ffba6afd49d147e9b5e88e2d7020c09142f))
* **sessions:** session bar badge readouts, job persistence, refresh bounce ([#10217](https://github.com/windmill-labs/windmill/issues/10217)) ([c8870d3](https://github.com/windmill-labs/windmill/commit/c8870d36aed2283bfa14419cd0bb346083f27b9c))
* steer ai chat away from draft-blind api catalog reads and runs ([#10202](https://github.com/windmill-labs/windmill/issues/10202)) ([0e04bc6](https://github.com/windmill-labs/windmill/commit/0e04bc6991486b2b2b05b13feed5b083e403ac80))
## [1.763.0](https://github.com/windmill-labs/windmill/compare/v1.762.2...v1.763.0) (2026-07-19)
### Features
* **ai:** gate data pipelines in sessions behind a dev flag ([#10178](https://github.com/windmill-labs/windmill/issues/10178)) ([42da20a](https://github.com/windmill-labs/windmill/commit/42da20ae9743eab2f86b8f9c261ee567f8bbb0c5))
### Bug Fixes
* **cli:** stop emitting has_on_behalf_of/has_permissioned_as: false on pull ([#10188](https://github.com/windmill-labs/windmill/issues/10188)) ([2d77e74](https://github.com/windmill-labs/windmill/commit/2d77e742077b71be192a04ed9e85d4a024dc7f91))
## [1.762.2](https://github.com/windmill-labs/windmill/compare/v1.762.1...v1.762.2) (2026-07-18)
### Bug Fixes
* **scripts:** populate auto_kind from draft JSON for draft-only scripts ([#10183](https://github.com/windmill-labs/windmill/issues/10183)) ([cba5f0d](https://github.com/windmill-labs/windmill/commit/cba5f0d8a8c2261a190d0f10396a6ad6aed89366))
## [1.762.1](https://github.com/windmill-labs/windmill/compare/v1.762.0...v1.762.1) (2026-07-17)
### Bug Fixes
* **schedules:** stop disabling schedules on transient push errors ([#10179](https://github.com/windmill-labs/windmill/issues/10179)) ([c82056c](https://github.com/windmill-labs/windmill/commit/c82056cfde93ab0e26cf82aa4f7650c060565542))
* **worker:** gate ansible socket-dir name check to unix ([#10180](https://github.com/windmill-labs/windmill/issues/10180)) ([203f6c6](https://github.com/windmill-labs/windmill/commit/203f6c69dd24fc055a4bf604315aa757f35d0565))
## [1.762.0](https://github.com/windmill-labs/windmill/compare/v1.761.0...v1.762.0) (2026-07-17)
### Features
* **ai-chat:** image attachments and agent raw-app screenshots ([#10130](https://github.com/windmill-labs/windmill/issues/10130)) ([7a139ab](https://github.com/windmill-labs/windmill/commit/7a139ab23e034321554d0c85540d54251e61cc72))
* **forks:** let a fork's creator manage developers on it without being an admin ([#10166](https://github.com/windmill-labs/windmill/issues/10166)) ([97f4477](https://github.com/windmill-labs/windmill/commit/97f44770698eb3dd9e296012360b7d9a4492e28d))
* **otel-tracing-proxy:** trust internal endpoints with untrusted CAs ([#10139](https://github.com/windmill-labs/windmill/issues/10139)) ([396fb1c](https://github.com/windmill-labs/windmill/commit/396fb1c4752b2b93ef0ec9d0c9c6be05aa403eef))
* **worker-tags:** add `*` fork marker to workspace-scoped custom tags ([#10177](https://github.com/windmill-labs/windmill/issues/10177)) ([2ff5a91](https://github.com/windmill-labs/windmill/commit/2ff5a918d54074bc340b6e92222e9f5bb4369ba0))
### Bug Fixes
* **ai:** unbreak session chat compaction for Anthropic models ([#10171](https://github.com/windmill-labs/windmill/issues/10171)) ([a9fc9f7](https://github.com/windmill-labs/windmill/commit/a9fc9f74b2c5b280cc087f0bfbac50112081c5e8))
* **ansible:** keep persistent-connection socket path under the AF_UNIX limit ([#10167](https://github.com/windmill-labs/windmill/issues/10167)) ([be57dd9](https://github.com/windmill-labs/windmill/commit/be57dd91e40551ff0d6fbbeccd97e2b98589c5b9))
* **extra:** make the extra container runnable as a non-root UID ([#10173](https://github.com/windmill-labs/windmill/issues/10173)) ([dcb9e40](https://github.com/windmill-labs/windmill/commit/dcb9e40ea237c3744611ac6e83ff5ecc6528e697))
* **flows:** make updateFlow body path optional so AI can update flows ([#10176](https://github.com/windmill-labs/windmill/issues/10176)) ([7d2c5ce](https://github.com/windmill-labs/windmill/commit/7d2c5ceb0fb8609aa58a1e28e61ae8ce86e48208))
* **frontend:** scope session pipeline trigger editors to the session workspace ([#10032](https://github.com/windmill-labs/windmill/issues/10032)) ([8828341](https://github.com/windmill-labs/windmill/commit/8828341a2b0912cbf44d6c6d81b4f4e30c5fe631))
* **mcp:** apply token scopes consistently across mcp endpoint tools ([#10162](https://github.com/windmill-labs/windmill/issues/10162)) ([ae3d9ce](https://github.com/windmill-labs/windmill/commit/ae3d9ce2c031beeafc4e8156e0c85f163647af0f))
* parse all names in grouped go param declarations ([#10165](https://github.com/windmill-labs/windmill/issues/10165)) ([d0aa7dc](https://github.com/windmill-labs/windmill/commit/d0aa7dca13aedfec90b7e023954cc824dafa3d34))
* **schedules:** re-arm enabled schedules left with no queued occurrence ([#10174](https://github.com/windmill-labs/windmill/issues/10174)) ([9762089](https://github.com/windmill-labs/windmill/commit/9762089fcbdb33dbf453d22342661e5857b2ad7e))
### Performance Improvements
* lazy-load session preview editor views for snappy AI sessions switch ([#10172](https://github.com/windmill-labs/windmill/issues/10172)) ([1edee8a](https://github.com/windmill-labs/windmill/commit/1edee8aa34503d2892c1ef7b1deabf99e4b528b4))
## [1.761.0](https://github.com/windmill-labs/windmill/compare/v1.760.1...v1.761.0) (2026-07-16)
### Features
* **ai-sessions:** CRUD markdown artifacts in sessions ([#10046](https://github.com/windmill-labs/windmill/issues/10046)) ([0ea5705](https://github.com/windmill-labs/windmill/commit/0ea570570e65abf64b3b171e592a8dd3eea0b105))
* **alerts:** include disk total and top consumers in low-disk alert ([#10144](https://github.com/windmill-labs/windmill/issues/10144)) ([4e0fd4d](https://github.com/windmill-labs/windmill/commit/4e0fd4db5589f91426ca025184da7b977d5a16d6))
* automatic git-to-windmill sync (polling, webhooks, in-app PRs + checks) ([#9552](https://github.com/windmill-labs/windmill/issues/9552)) ([51d8db6](https://github.com/windmill-labs/windmill/commit/51d8db6602bc192879e50b54a5fe6b6b8beb63f4))
* display openai reasoning summaries in ai chat ([#10147](https://github.com/windmill-labs/windmill/issues/10147)) ([4ee1d32](https://github.com/windmill-labs/windmill/commit/4ee1d32101eb527ef707c3c4af456892f0a059ab))
* **forks:** add "Hide unchanged drafts" toggle to fork deploy-draft tab ([#10022](https://github.com/windmill-labs/windmill/issues/10022)) ([4fc3f30](https://github.com/windmill-labs/windmill/commit/4fc3f304c632eda15876c28cb6f473a799a41f48))
* **frontend:** flatten workspace pickers, whole-tab picker trigger ([#10145](https://github.com/windmill-labs/windmill/issues/10145)) ([7fda6a0](https://github.com/windmill-labs/windmill/commit/7fda6a05345d14f62dc2124d682650d50eecbe98))
### Bug Fixes
* **ai:** show the question in askUserQuestion tool-call labels ([#10153](https://github.com/windmill-labs/windmill/issues/10153)) ([fa03984](https://github.com/windmill-labs/windmill/commit/fa03984a14bb71f0ea2a27bfa23339a9d0d8e86b))
* **alerts:** identify server replica in low-disk alert + per-host dedup tag ([#10143](https://github.com/windmill-labs/windmill/issues/10143)) ([3bd9f05](https://github.com/windmill-labs/windmill/commit/3bd9f0593866740935f79c8b2c1cb92829059105))
* **frontend:** sanitize job result markup, gate it on unsandboxed public apps ([#10127](https://github.com/windmill-labs/windmill/issues/10127)) ([7b813d1](https://github.com/windmill-labs/windmill/commit/7b813d1f74baef200d9f1387e02925083b79b265))
* **frontend:** show friendly draft path for draft-only items in pickers ([#10136](https://github.com/windmill-labs/windmill/issues/10136)) ([568dbbe](https://github.com/windmill-labs/windmill/commit/568dbbee852d59986581b0d16f5ac3e06d6bad6a))
* **frontend:** surface real tool call errors in AI chat ([#10146](https://github.com/windmill-labs/windmill/issues/10146)) ([0694b84](https://github.com/windmill-labs/windmill/commit/0694b84da7bbb9404fd386e3ce109a574a0e9c93))
* heartbeat job ping during s3object materialization in SQL executors ([#10152](https://github.com/windmill-labs/windmill/issues/10152)) ([7d5009e](https://github.com/windmill-labs/windmill/commit/7d5009e3928bb200150eb998ed0b3016c3395987))
* **mcp:** push granular scope patterns into SQL so scoped scripts/flows aren't truncated ([#10140](https://github.com/windmill-labs/windmill/issues/10140)) ([91d6606](https://github.com/windmill-labs/windmill/commit/91d6606868d0e9c5f78ab48a592ef95ffaeeca61))
* **migrations:** grant zombie_job_counter to windmill roles ([#10159](https://github.com/windmill-labs/windmill/issues/10159)) ([0e547ad](https://github.com/windmill-labs/windmill/commit/0e547adf23b615f0caccc88eb9101bb0604bb51b))
* **raw-apps:** full code ui builder improvements ([c55ac53](https://github.com/windmill-labs/windmill/commit/c55ac5326fbb4532f22b7250a58b69396a4c868a))
* **raw-apps:** prevent and surface the silent blank screen from an unmounted #root ([#10150](https://github.com/windmill-labs/windmill/issues/10150)) ([24750e6](https://github.com/windmill-labs/windmill/commit/24750e6ef1a1a975e0063df6809774f913b5d01b))
* **self-host:** unbreak self-hosted Caddy after the caddy-l4 syntax change ([#10156](https://github.com/windmill-labs/windmill/issues/10156)) ([2f6c35b](https://github.com/windmill-labs/windmill/commit/2f6c35b15bf70f17eb8ff08281e0ecf8313e7b35))
## [1.760.1](https://github.com/windmill-labs/windmill/compare/v1.760.0...v1.760.1) (2026-07-15)
### Bug Fixes
* **apps:** honor presigned S3 signature on app display/preview routes ([#10141](https://github.com/windmill-labs/windmill/issues/10141)) ([8c725d9](https://github.com/windmill-labs/windmill/commit/8c725d9e44e38bf3b35f41b847cdf8745e07942e))
## [1.760.0](https://github.com/windmill-labs/windmill/compare/v1.759.0...v1.760.0) (2026-07-15)
### Features
* **nsjail:** make python/ansible rlimit_as configurable per worker (GIT-921) ([#10138](https://github.com/windmill-labs/windmill/issues/10138)) ([1787201](https://github.com/windmill-labs/windmill/commit/17872018cc037e699b1f6e1589d0ad05e0883cca))
### Bug Fixes
* **ai:** disable redirects on worker AI provider client (GHSA-5q4v) ([#10122](https://github.com/windmill-labs/windmill/issues/10122)) ([27ead8d](https://github.com/windmill-labs/windmill/commit/27ead8d0848cceacaf0c49fed0e8896472b851e3))
* **ai:** stop sending the AI agent system prompt twice for OpenAI ([#10126](https://github.com/windmill-labs/windmill/issues/10126)) ([8bfe5c9](https://github.com/windmill-labs/windmill/commit/8bfe5c93404ba3f137394f16d0571a06d891dc3b))
* **apps:** invalidate cached app policy on change or deletion (GHSA-r5v4-cxh9-7qhq) ([#10121](https://github.com/windmill-labs/windmill/issues/10121)) ([f7eb5c4](https://github.com/windmill-labs/windmill/commit/f7eb5c460d78792c24297e20cb062638522a4f68))
* **bash:** normalize CRLF line endings before running scripts ([#10131](https://github.com/windmill-labs/windmill/issues/10131)) ([6407d9f](https://github.com/windmill-labs/windmill/commit/6407d9ff5ce51e71ff8b8fc503d89a2bdc2e1761))
* **cli-image:** patch fixable CRITICAL CVEs in windmill-cli image (GIT-922) ([#10135](https://github.com/windmill-labs/windmill/issues/10135)) ([5626768](https://github.com/windmill-labs/windmill/commit/56267684718944ef4d4ecd3b610bad81132e1990))
* **frontend:** graceful small-screen timeframe picker on the runs page ([#10073](https://github.com/windmill-labs/windmill/issues/10073)) ([af177ce](https://github.com/windmill-labs/windmill/commit/af177cefe07e6037e33cc90757088a98fb63a49f))
* **frontend:** keep session-exit URL clean by syncing new_draft strip with the router ([#10101](https://github.com/windmill-labs/windmill/issues/10101)) ([9705d60](https://github.com/windmill-labs/windmill/commit/9705d602848966f850613233d6e23b73a753c259))
* **frontend:** only carry custom-tag overrides on 'Run again' ([#10137](https://github.com/windmill-labs/windmill/issues/10137)) ([bd3adc9](https://github.com/windmill-labs/windmill/commit/bd3adc9781d8e77928c9feb03d0e05b63a1aaf7c))
* **frontend:** treat a displaced draft save as superseded, not failed ([#10094](https://github.com/windmill-labs/windmill/issues/10094)) ([2fe999f](https://github.com/windmill-labs/windmill/commit/2fe999f66cd15acd81850f970ada31e9892abff2))
* reject git URL fragment/query SSRF bypass (GHSA-p5cj-8cfh-mjv6) ([#10120](https://github.com/windmill-labs/windmill/issues/10120)) ([73c8d7f](https://github.com/windmill-labs/windmill/commit/73c8d7f08ad55cd2323d1bb9438f00a8ac30e046))
* **security:** enforce variables:write scope on resource-delete var cascade (GHSA-xmr2-98m6-cjf7) ([#10123](https://github.com/windmill-labs/windmill/issues/10123)) ([188647a](https://github.com/windmill-labs/windmill/commit/188647a942a0cb496d63c71bf3e0f7a3136ec217))
## [1.759.0](https://github.com/windmill-labs/windmill/compare/v1.758.0...v1.759.0) (2026-07-15)
+6 -2
View File
@@ -16,8 +16,12 @@
# site, silently disabling the HTTP proxy while the :25 layer4 listener stays up.
bind {$ADDRESS:0.0.0.0 ::}
# Extra services: LSP, Multiplayer, Debugger (windmill_extra gateway)
reverse_proxy /ws/* /ws_mp/* /ws_debug/* http://windmill_extra:3000
# Extra services: LSP, Multiplayer, Debugger (windmill_extra gateway).
# reverse_proxy only reads its first argument as a matcher, so listing
# several paths inline turns the rest into upstream addresses. The paths
# have to go through a named matcher.
@extra path /ws/* /ws_mp/* /ws_debug/*
reverse_proxy @extra http://windmill_extra:3000
# Search indexer, Enterprise Edition (windmill_indexer:8002)
# reverse_proxy /api/srch/* http://windmill_indexer:8002
+2
View File
@@ -79,6 +79,8 @@ COPY /python-client/docs/ /frontend/static/pydocs/
RUN npm run generate-backend-client
ENV NODE_OPTIONS "--max-old-space-size=8192"
ARG VITE_BASE_URL ""
# Must be declared for the build-arg to reach the bundle. See frontend/svelte.config.js.
ARG WM_BUILD_VERSION=""
# Read more about macro in docker/dev.nu
# -- MACRO-SPREAD-WASM-PARSER-DEV-ONLY -- #
RUN npm run build
@@ -3,7 +3,7 @@ import { tmpdir } from "os";
import { join } from "path";
import type { AIProvider } from "$lib/gen/types.gen";
import {
globalTools,
globalToolsFor,
prepareGlobalSystemMessage,
prepareGlobalUserMessage,
} from "../../../../../frontend/src/lib/components/copilot/chat/global/core";
@@ -43,6 +43,67 @@ const LIVE_EDITOR_ITEM_KINDS = {
app: "raw_app",
} as const;
// SessionArtifactsStore can't run here (bun has no IndexedDB, nor the compiled $state runes),
// so mirror only its tool-facing shape; its own logic (scoping, race guard) is unit-tested.
const EVAL_SESSION_ID = "eval-session";
function createEvalArtifactHelpers() {
const items = new Map<string, Record<string, unknown>>();
let seq = 0;
const store = {
create: async (sessionId: string, input: Record<string, any>) => {
const now = seq++;
const artifact = {
id: `eval-artifact-${now}`,
sessionId,
chatId: input.chatId,
kind: input.kind ?? "md",
name: input.name,
content: input.content,
createdAt: now,
updatedAt: now,
};
items.set(artifact.id, artifact);
return artifact;
},
get: async (id: string) => items.get(id),
update: async (
id: string,
input: Record<string, any>,
opts?: { sessionId?: string },
) => {
const existing = items.get(id);
if (!existing) return undefined;
if (
opts?.sessionId !== undefined &&
existing.sessionId !== opts.sessionId
)
return undefined;
const updated = {
...existing,
name: input.name ?? existing.name,
content: input.content ?? existing.content,
updatedAt: seq++,
};
items.set(id, updated);
return updated;
},
remove: async (id: string) => {
items.delete(id);
},
listForSession: async (sessionId: string) =>
[...items.values()].filter((a) => a.sessionId === sessionId),
};
return {
helpers: {
artifacts: store,
sessionId: EVAL_SESSION_ID,
getChatId: () => "eval-chat",
openArtifact: () => {},
},
snapshot: () => [...items.values()],
};
}
export interface GlobalLiveEditorDraftFixture {
type: keyof typeof LIVE_EDITOR_ITEM_KINDS;
storagePath?: string;
@@ -80,6 +141,8 @@ export interface GlobalEvalOptions {
workspaceFixtures?: BenchmarkWorkspaceRunnables;
liveEditorDrafts?: GlobalLiveEditorDraftFixture[];
user?: GlobalUserFixture;
// Emulate a session chat (preview tools + session prompt); default false = standalone baseline.
sessionChat?: boolean;
model?: string;
maxIterations?: number;
provider?: AIProvider;
@@ -98,7 +161,10 @@ export async function runGlobalEval(
(await mkdtemp(join(tmpdir(), "wmill-frontend-global-benchmark-")));
clearGlobalDrafts(workspaceRoot);
registerBenchmarkWorkspaceRunnables(workspaceRoot, options.workspaceFixtures ?? {});
registerBenchmarkWorkspaceRunnables(
workspaceRoot,
options.workspaceFixtures ?? {},
);
seedLiveEditorDrafts(workspaceRoot, options.liveEditorDrafts ?? []);
try {
@@ -107,18 +173,25 @@ export async function runGlobalEval(
process.env[DISABLE_ACTIVE_EDITOR_CONTEXT_ENV] !== "1";
// Pass the seeded identity straight to the prompt builder rather than mutating
// the process-global `userStore`, so concurrent cases never race on it.
const evalArtifacts = createEvalArtifactHelpers();
const rawResult = await runEval({
userPrompt,
systemMessage: prepareGlobalSystemMessage(undefined, { user: options.user }),
systemMessage: prepareGlobalSystemMessage(undefined, {
user: options.user,
previewTools: options.sessionChat ?? false,
}),
userMessage: prepareGlobalUserMessage(
userPrompt,
[],
injectActiveEditorContext ? { workspace: workspaceRoot } : {},
),
tools: getGlobalEvalTools(),
helpers: {},
tools: getGlobalEvalTools(options.sessionChat ?? false),
helpers: evalArtifacts.helpers,
apiKey,
getOutput: () => collectGlobalDraftState(workspaceRoot),
getOutput: async () => ({
...(await collectGlobalDraftState(workspaceRoot)),
artifacts: evalArtifacts.snapshot(),
}),
onAssistantMessageStart: options.runContext?.onAssistantMessageStart,
onAssistantToken: options.runContext?.onAssistantChunk,
onAssistantMessageEnd: options.runContext?.onAssistantMessageEnd,
@@ -213,10 +286,15 @@ function clearLiveEditorDrafts(
}
}
function getGlobalEvalTools(): ProductionTool<{}>[] {
// Gate session-preview tools on sessionChat, as production's globalToolsFor does.
function getGlobalEvalTools(sessionChat: boolean): ProductionTool<{}>[] {
const disableSearchApp = process.env[DISABLE_SEARCH_APP_ENV] === "1";
return (globalTools as ProductionTool<{}>[])
.filter((tool) => !(disableSearchApp && tool.def.function.name === "search_app"))
return (
globalToolsFor({ sessionPreview: sessionChat }) as ProductionTool<{}>[]
)
.filter(
(tool) => !(disableSearchApp && tool.def.function.name === "search_app"),
)
.map((tool) => {
if (!MUTATING_GLOBAL_TOOLS.has(tool.def.function.name)) {
return tool;
+169 -12
View File
@@ -10,6 +10,7 @@ import type {
import type {
DataTableTables,
DataTableTableSchema,
EndpointTool,
GetDraftForUserResponse,
GetOwnDraftResponse,
ListDraftsResponse,
@@ -303,13 +304,20 @@ export function getBenchmarkJobLogs(workspace: string, jobId: string): string {
*/
const benchmarkDrafts = new Map<
string,
{ workspace: string; kind: UserDraftItemKind; path: string; value: unknown }
{ workspace: string; kind: UserDraftItemKind; path: string; value: unknown; createdAt: string }
>()
// Fixed timestamp so artifacts stay deterministic. No eval simulates a
// concurrent writer, so every save is accepted and the conflict branch is
// never taken — the syncer just records this as its `last_sync` baseline.
const BENCHMARK_DRAFT_TIMESTAMP = '1970-01-01T00:00:00.000Z'
// Counter-based timestamps: deterministic run-to-run (same event order → same
// values) but MONOTONIC per update, because production bumps a draft row's
// created_at on every upsert and the diff snapshot cache keys patch reuse on
// it — a fixed timestamp would serve stale patches after an edit. No eval
// simulates a concurrent writer, so every save is accepted and the conflict
// branch is never taken.
let benchmarkDraftClock = 0
function nextBenchmarkDraftTimestamp(): string {
benchmarkDraftClock += 1
return new Date(benchmarkDraftClock * 1000).toISOString()
}
function benchmarkDraftKey(workspace: string, kind: string, path: string): string {
return `${workspace}::${kind}::${path}`
@@ -340,7 +348,8 @@ export function seedBenchmarkDraft(
workspace,
kind,
path,
value
value,
createdAt: nextBenchmarkDraftTimestamp()
})
}
@@ -353,6 +362,7 @@ export function updateBenchmarkDraft(input: {
}): UpdateDraftResponse {
const key = benchmarkDraftKey(input.workspace, input.kind, input.path)
const value = input.requestBody?.value
const createdAt = nextBenchmarkDraftTimestamp()
if (value == null) {
benchmarkDrafts.delete(key)
} else {
@@ -360,10 +370,11 @@ export function updateBenchmarkDraft(input: {
workspace: input.workspace,
kind: input.kind,
path: input.path,
value
value,
createdAt
})
}
return { status: 'saved', current_timestamp: BENCHMARK_DRAFT_TIMESTAMP }
return { status: 'saved', current_timestamp: createdAt }
}
/** Mirror `DraftService.getDraftForUser`: 404-shaped throw when absent so the
@@ -377,7 +388,7 @@ export function getBenchmarkDraftForUser(input: {
if (!entry) {
throw Object.assign(new Error(`no draft for "${input.path}"`), { status: 404 })
}
return { value: entry.value, created_at: BENCHMARK_DRAFT_TIMESTAMP }
return { value: entry.value, created_at: entry.createdAt }
}
/** Mirror `DraftService.getOwnDraft`: `null` (200) when absent — unlike
@@ -391,7 +402,18 @@ export function getBenchmarkOwnDraft(input: {
if (!entry) {
return null
}
return { value: entry.value, created_at: BENCHMARK_DRAFT_TIMESTAMP }
return { value: entry.value, created_at: entry.createdAt }
}
/** Whether a deployed benchmark item exists for a draft row's kind+path —
* drives `draft_only`, which production computes against the deployed tables. */
function benchmarkDeployedExists(workspace: string, kind: UserDraftItemKind, path: string): boolean {
if (kind === 'script') return Boolean(getBenchmarkScriptByPath(workspace, path))
if (kind === 'flow') return Boolean(getBenchmarkFlowByPath(workspace, path))
if (kind === 'app' || kind === 'raw_app') return Boolean(getBenchmarkAppByPath(workspace, path))
// Drawer kinds (variables/resources/schedules/triggers) have no deployed
// benchmark stores today.
return false
}
/** Mirror `DraftService.listDrafts`: metadata rows (no value) for a workspace. */
@@ -402,9 +424,9 @@ export function listBenchmarkDrafts(workspace: string): ListDraftsResponse {
kind: entry.kind,
path: entry.path,
summary: (entry.value as { summary?: string } | null)?.summary,
draft_only: true,
draft_only: !benchmarkDeployedExists(workspace, entry.kind, entry.path),
legacy_draft: false,
created_at: BENCHMARK_DRAFT_TIMESTAMP
created_at: entry.createdAt
}))
}
@@ -694,3 +716,138 @@ function buildBenchmarkApp(app: BenchmarkWorkspaceApp): AppWithLastVersion {
raw_app: true
}
}
// ============= API endpoint catalog (McpService.listMcpTools + raw fetch) =============
// The global chat's API catalog tools list endpoints via McpService and execute
// them with a plain relative fetch('/api/...'), which has no meaning in the
// vitest environment. A representative slice of the real catalog is served here,
// and `handleBenchmarkApiFetch` answers the executed calls.
const BENCHMARK_MCP_TOOLS: EndpointTool[] = [
{
name: 'listWorkers',
description: 'List workers',
instructions: 'List all workers with their last ping and job counts.',
path: '/workers/list',
method: 'GET',
query_params_schema: {
type: 'object',
properties: { page: { type: 'integer' }, per_page: { type: 'integer' } }
}
},
{
name: 'listQueue',
description: 'List queued jobs',
instructions: '',
path: '/w/{workspace}/jobs/queue/list',
method: 'GET',
path_params_schema: {
type: 'object',
properties: { workspace: { type: 'string' } },
required: ['workspace']
}
},
{
name: 'runScriptByPath',
description: 'Run the deployed version of a script by path',
instructions: '',
path: '/w/{workspace}/jobs/run/p/{path}',
method: 'POST',
path_params_schema: {
type: 'object',
properties: { workspace: { type: 'string' }, path: { type: 'string' } },
required: ['workspace', 'path']
},
body_schema: { type: 'object', properties: {} }
},
{
name: 'runFlowByPath',
description: 'Run the deployed version of a flow by path',
instructions: '',
path: '/w/{workspace}/jobs/run/f/{path}',
method: 'POST',
path_params_schema: {
type: 'object',
properties: { workspace: { type: 'string' }, path: { type: 'string' } },
required: ['workspace', 'path']
},
body_schema: { type: 'object', properties: {} }
},
// Draft-covered endpoints, present so steering cases exercise the guard the
// way production does (hidden from search, refused at call time).
{
name: 'getScriptByPath',
description: 'Get a script by path',
instructions: '',
path: '/w/{workspace}/scripts/get/p/{path}',
method: 'GET'
},
{
name: 'createFlow',
description: 'Create a flow',
instructions: '',
path: '/w/{workspace}/flows/create',
method: 'POST'
},
{
name: 'deleteSchedule',
description: 'Delete a schedule',
instructions: '',
path: '/w/{workspace}/schedules/delete/{path}',
method: 'DELETE'
},
{
name: 'getVariable',
description: 'Get a variable',
instructions: '',
path: '/w/{workspace}/variables/get/{path}',
method: 'GET'
}
]
export function listBenchmarkMcpTools(): EndpointTool[] {
return BENCHMARK_MCP_TOOLS
}
const BENCHMARK_WORKERS = [
{
worker: 'wk-benchmark-1',
worker_instance: 'benchmark-host',
last_ping: 2,
started_at: BENCHMARK_TIMESTAMP,
jobs_executed: 42,
custom_tags: null,
worker_group: 'default',
wm_version: 'benchmark'
},
{
worker: 'wk-benchmark-2',
worker_instance: 'benchmark-host',
last_ping: 5,
started_at: BENCHMARK_TIMESTAMP,
jobs_executed: 17,
custom_tags: null,
worker_group: 'default',
wm_version: 'benchmark'
}
]
/** True when `handleBenchmarkApiFetch` has an answer for this `/api/...` url.
* Any other relative fetch must keep its normal (non-benchmark) behavior —
* intercepting it with a synthetic 404 sends the model into retry loops. */
export function hasBenchmarkApiHandler(url: string): boolean {
const path = url.split('?')[0]
return path === '/api/workers/list' || /^\/api\/w\/[^/]+\/jobs\/queue\/list$/.test(path)
}
/** Answer a relative `/api/...` fetch issued by the API catalog executor. */
export function handleBenchmarkApiFetch(url: string): Response {
const path = url.split('?')[0]
if (path === '/api/workers/list') {
return Response.json(BENCHMARK_WORKERS)
}
if (/^\/api\/w\/[^/]+\/jobs\/queue\/list$/.test(path)) {
return Response.json([])
}
return Response.json({ error: `no benchmark handler for ${path}` }, { status: 404 })
}
@@ -3,6 +3,20 @@ import { expect, it, vi } from 'vitest'
import { mkdir, writeFile } from 'fs/promises'
// @ts-ignore - Node.js path
import { dirname, resolve } from 'path'
import { handleBenchmarkApiFetch, hasBenchmarkApiHandler } from './mockBackend'
// The API catalog executor issues relative fetch('/api/...') calls, which have
// no meaning in the vitest environment — serve the ones the benchmark handles.
// Every other relative fetch keeps its normal behavior (it fails the same way
// it does without this stub) so unrelated tools see an unchanged environment.
const ORIGINAL_FETCH = globalThis.fetch
globalThis.fetch = (async (input: unknown, init?: RequestInit) => {
const url = typeof input === 'string' ? input : ((input as Request | URL | null)?.url ?? '')
if (typeof url === 'string' && hasBenchmarkApiHandler(url)) {
return handleBenchmarkApiFetch(url)
}
return ORIGINAL_FETCH(input as Parameters<typeof fetch>[0], init)
}) as typeof fetch
vi.mock('monaco-editor', () => ({
editor: {},
@@ -57,7 +71,8 @@ vi.mock('$lib/gen', async () => {
runBenchmarkDatatableSql,
runBenchmarkFlowByPath,
runBenchmarkScriptPreview,
updateBenchmarkDraft
updateBenchmarkDraft,
listBenchmarkMcpTools
} = await import('./mockBackend')
function wrapService<T extends object>(target: T, overrides: Record<string, unknown>): T {
@@ -111,13 +126,32 @@ vi.mock('$lib/gen', async () => {
hasBenchmarkWorkspace(data.workspace)
? Boolean(getBenchmarkScriptByPath(data.workspace, data.path))
: actual.ScriptService.existsScriptByPath(data),
getScriptByPath: async (data: { workspace: string; path: string }) => {
getScriptByPath: async (data: { workspace: string; path: string; getDraft?: boolean }) => {
if (hasBenchmarkWorkspace(data.workspace)) {
const script = getBenchmarkScriptByPath(data.workspace, data.path)
// `getDraft` mirrors production's overlay: the row plus the caller's
// draft and a `no_deployed` marker (draft-only item). The diff tool
// reads through this shape — without it every draft looks absent.
const draft = data.getDraft
? getBenchmarkOwnDraft({ workspace: data.workspace, kind: 'script', path: data.path })
: null
if (!script) {
throw new Error(`Script "${data.path}" not found in benchmark workspace`)
if (data.getDraft && draft) {
return {
...(draft.value as Record<string, unknown>),
path: data.path,
draft: draft.value,
no_deployed: true
}
}
throw Object.assign(
new Error(`Script "${data.path}" not found in benchmark workspace`),
{ status: 404 }
)
}
return script
return data.getDraft
? { ...script, draft: draft?.value ?? undefined, no_deployed: false }
: script
}
return actual.ScriptService.getScriptByPath(data)
},
@@ -151,13 +185,30 @@ vi.mock('$lib/gen', async () => {
hasBenchmarkWorkspace(data.workspace)
? Boolean(getBenchmarkFlowByPath(data.workspace, data.path))
: actual.FlowService.existsFlowByPath(data),
getFlowByPath: async (data: { workspace: string; path: string }) => {
getFlowByPath: async (data: { workspace: string; path: string; getDraft?: boolean }) => {
if (hasBenchmarkWorkspace(data.workspace)) {
const flow = getBenchmarkFlowByPath(data.workspace, data.path)
// Mirror production's `getDraft` overlay (see getScriptByPath above).
const draft = data.getDraft
? getBenchmarkOwnDraft({ workspace: data.workspace, kind: 'flow', path: data.path })
: null
if (!flow) {
throw new Error(`Flow "${data.path}" not found in benchmark workspace`)
if (data.getDraft && draft) {
return {
...(draft.value as Record<string, unknown>),
path: data.path,
draft: draft.value,
no_deployed: true
}
}
throw Object.assign(
new Error(`Flow "${data.path}" not found in benchmark workspace`),
{ status: 404 }
)
}
return flow
return data.getDraft
? { ...flow, draft: draft?.value ?? undefined, no_deployed: false }
: flow
}
return actual.FlowService.getFlowByPath(data)
},
@@ -299,6 +350,12 @@ vi.mock('$lib/gen', async () => {
queryResourceTypes: async (data: { workspace: string }) =>
hasBenchmarkWorkspace(data.workspace) ? [] : actual.ResourceService.queryResourceTypes(data)
}),
McpService: wrapService(actual.McpService, {
listMcpTools: async (data: { workspace: string }) =>
hasBenchmarkWorkspace(data.workspace)
? listBenchmarkMcpTools()
: actual.McpService.listMcpTools(data)
}),
VariableService: wrapService(actual.VariableService, {
existsVariable: async (data: { workspace: string; path: string }) =>
hasBenchmarkWorkspace(data.workspace) ? false : actual.VariableService.existsVariable(data),
@@ -320,13 +377,37 @@ vi.mock('$lib/gen', async () => {
hasBenchmarkWorkspace(data.workspace)
? (listBenchmarkApps(data.workspace) ?? [])
: actual.AppService.listApps(data),
getAppByPath: async (data: { workspace: string; path: string }) => {
getAppByPath: async (data: {
workspace: string
path: string
getDraft?: boolean
rawApp?: boolean
}) => {
if (hasBenchmarkWorkspace(data.workspace)) {
const app = getBenchmarkAppByPath(data.workspace, data.path)
// Mirror production's `getDraft` overlay (see getScriptByPath above).
// Benchmark app drafts live under the raw_app kind.
const draft = data.getDraft
? getBenchmarkOwnDraft({ workspace: data.workspace, kind: 'raw_app', path: data.path })
: null
if (!app) {
throw new Error(`App "${data.path}" not found in benchmark workspace`)
if (data.getDraft && draft) {
return {
...(draft.value as Record<string, unknown>),
path: data.path,
raw_app: true,
draft: draft.value,
no_deployed: true
}
}
throw Object.assign(
new Error(`App "${data.path}" not found in benchmark workspace`),
{ status: 404 }
)
}
return app
return data.getDraft
? { ...app, draft: draft?.value ?? undefined, no_deployed: false }
: app
}
return actual.AppService.getAppByPath(data)
}
+82
View File
@@ -480,3 +480,85 @@
judgeChecklist:
- "the flow includes a final top-level step named `webhook_response`"
- "`webhook_response` returns `ok: true` and the order summary"
- id: flow-test17-implicit-schedule-intent
prompt: |-
I want this order processing flow to run on its own every morning at 07:30 UTC.
Set that up for me. Do not ask me for the flow path.
initial: ai_evals/fixtures/frontend/flow/initial/scheduled_order_flow.json
toolExpect:
requiredToolsUsed:
- create_schedule
toolCallArgs:
- tool: create_schedule
field: path
stringStartsWithAnyOf:
- f/
- u/
stringMustNotStartWithAnyOf:
- schedules/
- tool: create_schedule
field: schedule
stringIncludesAnyOf:
- 30 7
- tool: create_schedule
field: timezone
stringIncludesAnyOf:
- UTC
skipJudge: true
judgeChecklist:
- "a schedule is created for the flow that runs daily at 07:30 UTC"
- id: flow-test18-implicit-http-trigger-intent
prompt: |-
I need to be able to kick off this order processing flow by sending it an HTTP POST
from an external system, with no authentication. Use route path `ai-evals/order-processing-implicit`.
Do not ask me for the flow path.
initial: ai_evals/fixtures/frontend/flow/initial/scheduled_order_flow.json
toolExpect:
requiredToolsUsed:
- create_trigger
toolCallArgs:
- tool: create_trigger
field: path
stringStartsWithAnyOf:
- f/
- u/
stringMustNotStartWithAnyOf:
- schedules/
- tool: create_trigger
field: kind
stringStartsWithAnyOf:
- http
- tool: create_trigger
field: config.http_method
stringIncludesAnyOf:
- post
- tool: create_trigger
field: config.authentication_method
stringIncludesAnyOf:
- none
- tool: create_trigger
field: config.route_path
stringIncludesAnyOf:
- ai-evals/order-processing-implicit
skipJudge: true
judgeChecklist:
- "an HTTP trigger is created for the flow that accepts unauthenticated POST requests"
- id: flow-test19-implicit-email-trigger-intent
prompt: |-
Make this order processing flow run automatically whenever an email is received.
Do not ask me for the flow path.
initial: ai_evals/fixtures/frontend/flow/initial/scheduled_order_flow.json
toolExpect:
requiredToolsUsed:
- create_trigger
toolCallArgs:
- tool: create_trigger
field: kind
stringStartsWithAnyOf:
- email
skipJudge: true
judgeChecklist:
- "an email trigger (kind email) is created, or the user is told how to enable email triggering on the instance"
+371 -3
View File
@@ -1050,11 +1050,44 @@
- opens the Kafka triggers page
- does not write, deploy, or delete anything
- id: global-openpage7-compare-review
prompt: |-
Create a TypeScript script draft at f/evals/global/compare_review_demo that returns the string "ok" (no need to test it), then open the review page so I can look over the pending change and deploy it myself.
runtime:
maxTurns: 8
validate:
draftCountExactly: 1
toolExpect:
requiredToolsUsed:
- write_script
- open_page
forbiddenToolsUsed:
- deploy_workspace_item
- delete_workspace_item
toolCallArgs:
- tool: open_page
field: page
stringIncludesAnyOf:
- compare
# The eval chat is untracked (no modified-items mask), so the model must scope
# the review by passing the item it changed explicitly — an omitted mask would
# preselect every pending change in the workspace.
- tool: open_page
field: items
stringIncludesAnyOf:
- f/evals/global/compare_review_demo
skipJudge: true
judgeChecklist:
- creates the script draft, then opens the Compare & Deploy review page instead of deploying itself
- preselects only the created script on the review page
- does not deploy or delete anything
- id: global-closepage1-close-runs-tab
prompt: |-
You just opened the runs page for me in the side panel. Close that tab, I'm done looking at it.
runtime:
maxTurns: 6
sessionChat: true
validate:
draftCountExactly: 0
toolExpect:
@@ -1451,9 +1484,15 @@
- deploy_workspace_item
- delete_workspace_item
judgeChecklist:
# A pipeline node is DECLARATIVE: triggers are declared by `-- on <ref>`
# annotations (the trigger row is created separately) and a `-- materialize`
# output is a MANAGED write where the body is a bare SELECT that the runtime
# wraps in the create/replace. Do not expect a separate trigger config or a
# hand-written CREATE TABLE / INSERT — those would be wrong for a materialize node.
- builds a data pipeline node as a script (not a flow)
- marks the script as a pipeline member with the pipeline annotation in the script's comment syntax (`-- pipeline` for a DuckDB/SQL node, not `// pipeline`)
- declares a schedule trigger and writes its output to a managed DuckLake table
- declares the schedule trigger with the `-- on schedule` annotation comment (this annotation is the correct and complete way a pipeline node binds a schedule; no separate trigger configuration is expected)
- declares the managed DuckLake output with `-- materialize ducklake://<table>` and writes the body as a bare SELECT (materialize is a managed write, so the node correctly does NOT hand-write its own CREATE TABLE / INSERT)
- leaves the result as an AI draft and does not deploy or save it
- id: global-test-pipeline-two-node-chain
@@ -1467,6 +1506,12 @@
maxTurns: 14
validate:
draftCountAtLeast: 2
requiredDrafts:
- type: script
pathStartsWith: f/evals/global/
valueIncludes:
- pipeline
- ducklake
forbiddenDrafts:
- type: flow
pathStartsWith: f/evals/global/
@@ -1478,12 +1523,65 @@
- deploy_workspace_item
- delete_workspace_item
judgeChecklist:
# Pipeline nodes are declarative: `-- on <ref>` binds inputs/triggers and
# `-- materialize ducklake://<table>` is a managed write whose body is a bare
# SELECT. Do not expect hand-written CREATE TABLE / INSERT on a materialize node.
- creates two data pipeline nodes as scripts (not a flow) in f/evals/global
- both scripts carry the pipeline annotation in their comment syntax (`-- pipeline` for DuckDB/SQL nodes, not `// pipeline`)
- the first ingests orders into a DuckLake table
- the second reads that same table and writes a daily rollup, wired to the first step's output asset
- the first ingests orders into a DuckLake table (a `-- materialize ducklake://<table>` output with a bare SELECT body is correct; no hand-written CREATE TABLE / INSERT is expected)
- the second reads that same table via `-- on ducklake://<that-table>` and materializes a daily rollup table, wiring it to the first step's output asset
- leaves both as AI drafts without deploying
- id: global-test-pipeline-complex-incremental
prompt: |-
Build a data pipeline in the `f/evals/global` folder for our web shop's
orders. It has three steps:
1. On a schedule, ingest the raw order CSVs under `s3://raw/orders/` into a
managed DuckLake table.
2. An incremental daily rollup: read that raw orders table and, on each run,
append just the current day's order count and total revenue into a second
DuckLake table. It should process one day at a time, not rebuild the whole
table every run.
3. A final step that reads the daily rollup table and exports the latest data
as a Parquet file to `s3://reports/` for the BI team.
Wire each step to the previous step's output so they form one pipeline. Keep
everything as AI drafts — don't deploy or save.
initial: ai_evals/fixtures/frontend/global/initial/user_admin_evals_folder.json
runtime:
maxTurns: 18
validate:
draftCountAtLeast: 3
requiredDrafts:
- type: script
pathStartsWith: f/evals/global/
valueIncludes:
- pipeline
- ducklake
forbiddenDrafts:
- type: flow
pathStartsWith: f/evals/global/
toolExpect:
requiredToolsUsed:
- write_script
forbiddenToolsUsed:
- write_flow
- deploy_workspace_item
- delete_workspace_item
judgeChecklist:
# Pipeline nodes are declarative: `-- on <ref>` binds inputs/triggers, and a
# DuckLake `-- materialize` output is a managed write whose body is a bare SELECT
# (the runtime performs the create/replace/append/merge). Do not expect a
# separate trigger config or hand-written CREATE TABLE / INSERT on a
# materialize node. S3/Parquet output is NOT materialize: the body writes it.
- builds the pipeline as three independent scripts (not a flow) in f/evals/global
- every node carries the pipeline annotation in its own comment syntax (`-- pipeline` for DuckDB/SQL nodes, not `// pipeline`)
- step 1 binds a schedule with `-- on schedule` and declares a managed DuckLake output with `-- materialize ducklake://<table>` and a bare SELECT body (no separate trigger config or hand-written CREATE TABLE is expected)
- "step 2 is incremental: each run adds only that day's rows to a second DuckLake table rather than rebuilding the whole table every run (e.g. an `append` or `key=<col>` merge materialize mode, not a full replace). Selecting the day via the `-- partitioned daily` + `{partition}` / `wm_partition(...)` idiom is the idiomatic form, but an equivalent current-day filter also satisfies this; a full-refresh/replace of the whole table does not"
- step 2 reads the same DuckLake table step 1 writes (via `-- on ducklake://<that-table>`), wiring it to step 1's output asset
- step 3 reads the daily rollup table and exports it as a Parquet file to S3
- does not misuse `-- materialize` for the S3 Parquet export (materialize is DuckLake-only; the S3 output is written by the script body, e.g. a DuckDB COPY or an SDK write)
- leaves all three nodes as AI drafts without deploying or saving
- id: global-path5-create-folder-then-draft
prompt: |-
Create a new shared folder called "analytics" for our data work, then draft a
@@ -1509,3 +1607,273 @@
- creates a new shared folder named "analytics" via create_folder
- drafts a script placed in that folder (f/analytics/...) returning an ISO timestamp
- leaves the script as a draft only
- id: global-artifact-plan-create
prompt: |-
I'm about to build a customer onboarding flow, but first I want a short written plan I can review and iterate on before any code.
Draft a markdown plan with a title, a one-sentence summary, and three or four bullet steps.
Keep it as something I can reopen and revise later — don't build the flow itself yet.
initial: ai_evals/fixtures/frontend/global/initial/user_admin_empty.json
runtime:
maxTurns: 6
sessionChat: true
validate:
draftCountExactly: 0
toolExpect:
requiredToolsUsed:
- create_artifact
forbiddenToolsUsed:
- write_flow
- write_script
- deploy_workspace_item
judgeChecklist:
- saves the plan as a markdown artifact via create_artifact rather than only replying inline
- the artifact content has a title, a one-line summary, and three or four bullet steps for onboarding
- does not create a flow or script draft yet
- id: global-npm1-script-search-package
prompt: |-
Find a good npm package for parsing RSS/Atom feeds and use it to create a draft Bun script
at `f/evals/global/fetch_feed`. The script should take a string `url` input and return the
feed title along with the titles of the 5 most recent items. Tell me which package you
picked and link its documentation. Leave it as an AI draft only; do not deploy.
initial: ai_evals/fixtures/frontend/global/initial/user_admin_evals_folder.json
runtime:
maxTurns: 12
validate:
draftCountExactly: 1
requiredDrafts:
- type: script
path: f/evals/global/fetch_feed
language: bun
valueIncludes:
- url
toolExpect:
requiredToolsUsed:
- search_npm_packages
- write_script
forbiddenToolsUsed:
- deploy_workspace_item
- delete_workspace_item
judgeChecklist:
- searches npm for a feed-parsing package before writing the code
- the draft Bun script imports the chosen npm package rather than hand-rolling an XML/RSS parser
- the script takes a url input and returns the feed title plus the recent item titles
- the reply names the chosen package and links its documentation
- the result stays as an AI draft and is not deployed
- id: global-dbschema1-postgres-resource-tables
prompt: |-
I have a postgres resource at f/data/reports_pg in this workspace.
What tables does that database have?
initial: ai_evals/fixtures/frontend/global/initial/user_admin_empty.json
runtime:
maxTurns: 12
validate:
draftCountExactly: 0
toolExpect:
requiredToolsUsed:
- get_db_schema
forbiddenToolsUsed:
- write_script
- write_resource
- test_run_script
- deploy_workspace_item
toolCallArgs:
- tool: get_db_schema
field: resourcePath
stringIncludesAnyOf:
- f/data/reports_pg
skipJudge: true
judgeChecklist:
- fetches the schema through get_db_schema with the resource path f/data/reports_pg
- when the lookup fails, tells the user instead of inventing table names
- does not write scripts or resources to answer a read-only question
# --- API catalog (search_api_endpoints / call_api_get / call_api_endpoint) ---
# The harness serves the catalog and the executed calls itself (mock
# listMcpTools + benchmark fetch handlers in adapters/frontend), so these cases
# do not require an mcp-enabled eval backend.
- id: global-test30-api-catalog-workers
prompt: |-
Are any workers online right now? Give me a quick summary of the workers and when they last pinged.
initial: ai_evals/fixtures/frontend/global/initial/user_admin_empty.json
runtime:
maxTurns: 8
validate:
draftCountExactly: 0
toolExpect:
requiredToolsUsed:
- search_api_endpoints
- call_api_get
forbiddenToolsUsed:
- call_api_endpoint
- write_script
- deploy_workspace_item
toolCallArgs:
- tool: call_api_get
field: name
stringIncludesAnyOf:
- listWorkers
# Read-only workspace inspection produces no draft; validate via tool use.
skipJudge: true
judgeChecklist:
- discovers the workers endpoint through the API catalog instead of guessing or fabricating
- reports worker status from the returned data
- id: global-test31-draft-test-run-not-deployed
prompt: |-
Update `f/evals/global/format_greeting` so the provided name is uppercased in the greeting, then run it with name "ada" to check it works.
Keep it as an AI draft only; do not deploy or save it.
initial: ai_evals/fixtures/frontend/global/initial/format_greeting_script.json
runtime:
maxTurns: 10
validate:
draftCountExactly: 1
requiredDrafts:
- type: script
path: f/evals/global/format_greeting
language: bun
valueIncludes:
- toUpperCase
toolExpect:
requiredToolsUsed:
- test_run_script
forbiddenToolsUsed:
- call_api_endpoint
- deploy_workspace_item
- delete_workspace_item
# The judge only sees the drafts artifact and cannot observe runs, so it always
# docks the prompt's "run it" requirement — validate deterministically instead:
# draft content via valueIncludes, the test run via toolExpect (test_run_script
# required, call_api_endpoint forbidden).
skipJudge: true
judgeChecklist:
- creates an AI draft of f/evals/global/format_greeting with the name uppercased in the greeting
- does not deploy or save the draft
- id: global-test32-delete-via-dedicated-tool
prompt: |-
Delete the deployed script `f/evals/global/send_report_digest` from the workspace — we don't use it anymore.
initial: ai_evals/fixtures/frontend/global/initial/report_digest_script.json
runtime:
maxTurns: 6
validate:
draftCountExactly: 0
toolExpect:
requiredToolsUsed:
- delete_workspace_item
forbiddenToolsUsed:
- call_api_endpoint
- search_api_endpoints
- write_script
# Deletion produces no draft; validate via tool use.
skipJudge: true
judgeChecklist:
- deletes the deployed script via delete_workspace_item rather than a raw API endpoint
- id: global-draft-diff-report
prompt: |-
Update the existing workspace script at `f/evals/global/format_greeting` so the returned message ends with an exclamation mark, keeping everything else the same.
Then show me exactly what your draft changes compared to the deployed version.
Leave the result as an AI draft only; do not deploy or save it.
initial: ai_evals/fixtures/frontend/global/initial/format_greeting_script.json
runtime:
maxTurns: 8
validate:
draftCountExactly: 1
requiredDrafts:
- type: script
path: f/evals/global/format_greeting
language: bun
valueIncludes:
- "!"
toolExpect:
requiredToolsUsed:
- diff
forbiddenToolsUsed:
- deploy_workspace_item
- delete_workspace_item
toolCallArgs:
- tool: diff
field: path
stringIncludesAnyOf:
- f/evals/global/format_greeting
judgeChecklist:
- creates an AI draft for the existing f/evals/global/format_greeting script with the exclamation-mark change
- the draft changes only the returned message's punctuation — summary, language, path, and the rest of the code are untouched
- does not deploy or save the draft to the workspace
- id: global-resource-manual-credentials
prompt: |-
Set up a resource for our production Postgres database at `f/evals/global/prod_db` (host db.internal.example.com, port 5432, database `orders`, user `app`).
I don't want to paste the password into this chat — prepare everything so I can enter it myself.
Leave it as an AI draft only; do not deploy or save it.
initial: ai_evals/fixtures/frontend/global/initial/user_admin_evals_folder.json
runtime:
maxTurns: 12
validate:
draftCountAtLeast: 1
requiredDrafts:
- type: resource
path: f/evals/global/prod_db
valueIncludes:
- db.internal.example.com
- orders
toolExpect:
requiredToolsUsed:
- write_resource
- open_page
forbiddenToolsUsed:
- deploy_workspace_item
- delete_workspace_item
toolCallArgs:
# The model may land the user in the resource's drawer or in the drawer of
# the secret variable it created for the password — both are correct.
- tool: open_page
field: page
stringIncludesAnyOf:
- resources
- variables
- tool: open_page
field: open
stringIncludesAnyOf:
- prod_db
- password
judgeChecklist:
- creates a postgres resource draft at f/evals/global/prod_db with the provided host, port, database, and user
- the password is left for the user to provide (empty, a placeholder, or a secret variable reference) — no invented password value presented as real
- does not deploy or save anything to the workspace
- id: global-test29-email-trigger-draft
prompt: |-
Set up a draft auto-reply job.
Create a Bun script at `f/evals/global/email_pong` that returns the string "pong".
Then set it up so it runs whenever an email is received at the inbox `pong`.
Leave everything as AI drafts only; do not deploy or save anything to the workspace.
runtime:
maxTurns: 10
validate:
requiredDrafts:
- type: script
path: f/evals/global/email_pong
language: bun
valueIncludes:
- pong
toolExpect:
requiredToolsUsed:
- write_script
- write_trigger
forbiddenToolsUsed:
- deploy_workspace_item
- delete_workspace_item
toolCallArgs:
# "Runs when an email is received" must resolve to the native email trigger kind,
# never a faked HTTP webhook. Assert the recorded tool-call kind (not the draft):
# it holds even on a CE backend where email trigger routes (smtp+private) 404.
- tool: write_trigger
field: kind
stringIncludesAnyOf:
- email
skipJudge: true
+2
View File
@@ -31,6 +31,8 @@ export interface EvalCaseRuntimeSpec {
maxTurns?: number;
backendPreview?: EvalCaseRuntimeBackendPreview;
appContext?: EvalCaseRuntimeAppContextSpec;
// Global mode: run as a session chat (preview tools + session prompt) vs the standalone chat.
sessionChat?: boolean;
}
export interface FlowValidationSpec {
+34
View File
@@ -174,6 +174,40 @@ describe("validateToolExpectations", () => {
expect(checks.every((check) => check.passed)).toBe(true);
});
it("accepts a stringIncludesAnyOf substring inside an array-valued field", () => {
const checks = validateToolExpectations({
run: {
success: true,
actual: {},
assistantMessageCount: 1,
toolCallCount: 1,
toolsUsed: ["open_page"],
toolCallDetails: [
{
name: "open_page",
arguments: {
page: "compare",
items: ["script:f/evals/global/compare_review_demo"],
},
},
],
skillsInvoked: [],
},
toolExpect: {
requiredToolsUsed: ["open_page"],
toolCallArgs: [
{
tool: "open_page",
field: "items",
stringIncludesAnyOf: ["f/evals/global/compare_review_demo"],
},
],
},
});
expect(checks.every((check) => check.passed)).toBe(true);
});
it("accepts stringIncludesAnyOf when only one of several calls matches", () => {
// Existential: a mutation mixed with verification SELECTs still passes.
const checks = validateToolExpectations({
+9 -3
View File
@@ -239,9 +239,15 @@ export function validateToolExpectations(input: {
// model mixes the requested statement (e.g. an UPDATE) with verification
// SELECTs that would otherwise fail an "all calls" check.
const needles = rule.stringIncludesAnyOf.map((needle) => needle.toLowerCase());
const hasMatch = values.some(
(value) =>
typeof value === "string" && needles.some((needle) => value.toLowerCase().includes(needle))
// Array-valued fields (e.g. open_page.items) match on any element.
const haystacks = (value: unknown): string[] =>
typeof value === "string"
? [value]
: Array.isArray(value)
? value.filter((v): v is string => typeof v === "string")
: [];
const hasMatch = values.some((value) =>
haystacks(value).some((hay) => needles.some((needle) => hay.toLowerCase().includes(needle)))
);
checks.push(
check(
+1
View File
@@ -41,6 +41,7 @@ export function createGlobalModeRunner(
workspaceFixtures: initial?.workspace,
liveEditorDrafts: initial?.liveEditorDrafts,
user: initial?.user,
sessionChat: context.evalCase?.runtime?.sessionChat,
maxIterations: context.evalCase?.runtime?.maxTurns,
provider: modelConfig.provider,
model: modelConfig.model,
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT args->'__git_sync_auto_pull' FROM v2_job WHERE id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
null
]
},
"hash": "020cc73e39782e7be50a146373096bd4378988af6447feeafd243262c246a9f5"
}
@@ -0,0 +1,38 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO amqp_trigger (\n amqp_resource_path,\n queue_name,\n exchange,\n options,\n workspace_id,\n path,\n script_path,\n is_flow,\n permissioned_as,\n mode,\n edited_by,\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\n )",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Jsonb",
"Jsonb",
"Varchar",
"Varchar",
"Varchar",
"Bool",
"Varchar",
{
"Custom": {
"name": "trigger_mode",
"kind": {
"Enum": [
"enabled",
"disabled",
"suspended"
]
}
}
},
"Varchar",
"Varchar",
"Jsonb",
"Jsonb"
]
},
"nullable": []
},
"hash": "02e39bb9957d3ccfe3d573994cd281a22467010d2c4a0611c8ae02742336f433"
}
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM feature_usage WHERE day < CURRENT_DATE - 60",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "0390d9e4fae597aabdeef940aa5b5014e1c889ba9181ee3331f128c56c9d22de"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_settings (workspace_id, slack_team_id, slack_name, slack_command_script, slack_email, customer_id, plan, webhook, deploy_to, ai_config, large_file_storage, git_sync, default_app, default_scripts, deploy_ui, mute_critical_alerts, color, operator_settings, teams_command_script, teams_team_id, teams_team_name, git_app_installations, ducklake, slack_oauth_client_id, slack_oauth_client_secret, datatable, teams_team_guid, auto_invite, error_handler, success_handler, public_app_execution_limit_per_minute, error_handler_fallback_to_instance_alerts) SELECT $1, slack_team_id, slack_name, slack_command_script, slack_email, customer_id, plan, webhook, deploy_to, ai_config, large_file_storage, git_sync, default_app, default_scripts, deploy_ui, mute_critical_alerts, color, operator_settings, teams_command_script, teams_team_id, teams_team_name, git_app_installations, ducklake, slack_oauth_client_id, slack_oauth_client_secret, datatable, teams_team_guid, auto_invite, error_handler, success_handler, public_app_execution_limit_per_minute, error_handler_fallback_to_instance_alerts FROM workspace_settings WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "042c42957221352891f7433f2ec0d6b7f686d9c7d505590eec7ba3bfeeb406ca"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_completed AS cj\n ( workspace_id\n , id\n , started_at\n , duration_ms\n , result\n , result_columns\n , canceled_by\n , canceled_reason\n , flow_status\n , workflow_as_code_status\n , memory_peak\n , status\n , worker\n )\n SELECT q.workspace_id, q.id, started_at, COALESCE($9::bigint, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE(started_at, now()))))*1000), $3, $10, $5, $6,\n flow_status, workflow_as_code_status,\n $8, CASE WHEN $4::BOOL THEN 'canceled'::job_status\n WHEN $7::BOOL THEN 'skipped'::job_status\n WHEN $2::BOOL THEN 'success'::job_status\n ELSE 'failure'::job_status END AS status,\n q.worker\n FROM v2_job_queue q LEFT JOIN v2_job_status USING (id) WHERE q.id = $1\n ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status, result = $3 RETURNING duration_ms AS \"duration_ms!\"",
"query": "INSERT INTO v2_job_completed AS cj\n ( workspace_id\n , id\n , started_at\n , duration_ms\n , result\n , result_columns\n , canceled_by\n , canceled_reason\n , flow_status\n , workflow_as_code_status\n , memory_peak\n , status\n , worker\n )\n SELECT q.workspace_id, q.id, started_at, COALESCE($9::bigint, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE(started_at, now()))))*1000), $3::text::jsonb, $10, $5, $6,\n flow_status, workflow_as_code_status,\n $8, CASE WHEN $4::BOOL THEN 'canceled'::job_status\n WHEN $7::BOOL THEN 'skipped'::job_status\n WHEN $2::BOOL THEN 'success'::job_status\n ELSE 'failure'::job_status END AS status,\n q.worker\n FROM v2_job_queue q LEFT JOIN v2_job_status USING (id) WHERE q.id = $1\n ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status, result = $3::text::jsonb RETURNING duration_ms AS \"duration_ms!\"",
"describe": {
"columns": [
{
@@ -13,7 +13,7 @@
"Left": [
"Uuid",
"Bool",
"Jsonb",
"Text",
"Bool",
"Varchar",
"Text",
@@ -27,5 +27,5 @@
false
]
},
"hash": "36c4e57afcab22f4b6825ccebe47767b8a8fe0a638250f7c7777e5a9f7530e5c"
"hash": "042ff3003bf82d11a78a1074a63379c19880e4d66dbf4dd5391f053b6dfa6b01"
}
@@ -0,0 +1,41 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT kind, path, script_path, is_flow FROM (\n SELECT 'schedule' AS kind, path, script_path, is_flow FROM schedule\n WHERE workspace_id = $1\n AND script_path IS NOT NULL\n UNION ALL\n SELECT 'email', path, script_path, is_flow FROM email_trigger\n WHERE workspace_id = $1\n UNION ALL\n SELECT 'kafka', path, script_path, is_flow FROM kafka_trigger\n WHERE workspace_id = $1\n UNION ALL\n SELECT 'mqtt', path, script_path, is_flow FROM mqtt_trigger\n WHERE workspace_id = $1\n UNION ALL\n SELECT 'amqp', path, script_path, is_flow FROM amqp_trigger\n WHERE workspace_id = $1\n UNION ALL\n SELECT 'nats', path, script_path, is_flow FROM nats_trigger\n WHERE workspace_id = $1\n UNION ALL\n SELECT 'postgres', path, script_path, is_flow FROM postgres_trigger\n WHERE workspace_id = $1\n UNION ALL\n SELECT 'sqs', path, script_path, is_flow FROM sqs_trigger\n WHERE workspace_id = $1\n UNION ALL\n SELECT 'gcp', path, script_path, is_flow FROM gcp_trigger\n WHERE workspace_id = $1\n ) t\n WHERE ($2::text IS NULL OR script_path LIKE $2)\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "kind",
"type_info": "Text"
},
{
"ordinal": 1,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "is_flow",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null,
null,
null,
null
]
},
"hash": "05843e117361d6b7cd9da652596d3be7a970d602d3809396d9253d7e5dc88152"
}
@@ -46,11 +46,11 @@
]
},
"nullable": [
true,
true,
true,
true,
true,
false,
false,
false,
false,
false,
true,
true
]
@@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"query": "SELECT parent_workspace_id, is_dev_workspace FROM workspace WHERE id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "parent_workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "is_dev_workspace",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true,
false
]
},
"hash": "079e1dab42a783a1e5e4cba5faa854b4b727b1fab89e42f2efaf97c63dad6384"
}
@@ -36,7 +36,8 @@
"github",
"azure",
"asset",
"freshness"
"freshness",
"amqp"
]
}
}
@@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n (elem->>'installation_id')::bigint as installation_id,\n elem->>'github_base_url' as github_base_url\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"
},
{
"ordinal": 1,
"name": "github_base_url",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null,
null
]
},
"hash": "0bb3de62f920a4f6c5da6f0a7bfc780a5ac35fa7639c4ee3a449b693add24241"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job (id, workspace_id, tag) VALUES ($1, 'test-workspace', 'other')",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "0bd853be57b43a7820cc1d73d92b8babd50b1092d4ad70e022431bdcf32e378f"
}
@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "WITH RECURSIVE descendants AS (\n SELECT id, 0 AS depth FROM workspace\n WHERE parent_workspace_id = $1 AND NOT deleted\n UNION ALL\n SELECT w.id, d.depth + 1 FROM workspace w\n JOIN descendants d ON w.parent_workspace_id = d.id\n WHERE NOT w.deleted AND d.depth < 10\n )\n SELECT id as \"id!\" FROM descendants WHERE (id = $2 OR id = $3)\n ORDER BY (id = $2) DESC LIMIT 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "0e016aaa6842767ac636e5b3e1febfa9cee08b93e0562beb629d84c4c689f4e7"
}
@@ -27,7 +27,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -27,7 +27,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COALESCE(username, split_part(email, '@', 1)) AS \"username!\", email FROM password WHERE super_admin = true AND disabled = false ORDER BY email LIMIT 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "username!",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "email",
"type_info": "Varchar"
}
],
"parameters": {
"Left": []
},
"nullable": [
null,
false
]
},
"hash": "17cdf02b4912078459526205849246fd2bdf9e3fce89852120aa4ad4ad6abfc3"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id FROM workspace WHERE parent_workspace_id = $1 AND NOT deleted AND is_dev_workspace AND COALESCE(dev_workspace_label, 'dev') = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "1b32339718406531acd2d051386d2817e9ba0b3a66f7eb4fc1177764563a0665"
}
@@ -0,0 +1,112 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n j.id AS \"id!\", j.workspace_id AS \"workspace_id!\", j.parent_job, j.flow_step_id IS NOT NULL AS \"is_flow_step?\",\n COALESCE(s.flow_status, s.workflow_as_code_status)::text AS \"flow_status: Box<str>\", r.ping AS last_ping, j.same_worker AS \"same_worker?\",\n q.worker AS \"worker?\",\n wp.ping_at AS \"worker_last_ping?\",\n wp.memory_usage AS \"worker_memory_usage?\",\n wp.wm_memory_usage AS \"worker_wm_memory_usage?\",\n wp.memory AS \"worker_memory_total?\",\n wp.worker_group AS \"worker_group?\",\n wp.wm_version AS \"worker_version?\",\n wp.current_job_id AS \"worker_current_job_id?\",\n wp.worker_instance AS \"worker_instance?\"\n FROM v2_job_queue q JOIN v2_job j USING (id) LEFT JOIN v2_job_runtime r USING (id) LEFT JOIN v2_job_status s USING (id)\n LEFT JOIN worker_ping wp ON wp.worker = q.worker\n WHERE q.running = true AND q.suspend = 0 AND q.suspend_until IS null AND q.scheduled_for <= now()\n AND (j.kind = 'flow' OR j.kind = 'flowpreview' OR j.kind = 'flownode' OR j.kind = 'singlestepflow')\n AND r.ping IS NOT NULL AND r.ping < NOW() - ($1 || ' seconds')::interval\n AND q.canceled_by IS NULL\n\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "workspace_id!",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "parent_job",
"type_info": "Uuid"
},
{
"ordinal": 3,
"name": "is_flow_step?",
"type_info": "Bool"
},
{
"ordinal": 4,
"name": "flow_status: Box<str>",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "last_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 6,
"name": "same_worker?",
"type_info": "Bool"
},
{
"ordinal": 7,
"name": "worker?",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "worker_last_ping?",
"type_info": "Timestamptz"
},
{
"ordinal": 9,
"name": "worker_memory_usage?",
"type_info": "Int8"
},
{
"ordinal": 10,
"name": "worker_wm_memory_usage?",
"type_info": "Int8"
},
{
"ordinal": 11,
"name": "worker_memory_total?",
"type_info": "Int8"
},
{
"ordinal": 12,
"name": "worker_group?",
"type_info": "Varchar"
},
{
"ordinal": 13,
"name": "worker_version?",
"type_info": "Varchar"
},
{
"ordinal": 14,
"name": "worker_current_job_id?",
"type_info": "Uuid"
},
{
"ordinal": 15,
"name": "worker_instance?",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
true,
null,
null,
true,
false,
true,
false,
true,
true,
true,
false,
false,
true,
false
]
},
"hash": "1bf87fe9667f860c6089bffb803291f4ff5c979be956369fa4406d789cca92fc"
}
@@ -0,0 +1,46 @@
{
"db_name": "PostgreSQL",
"query": "SELECT w.parent_workspace_id, w.is_dev_workspace, w.dev_workspace_label,\n p.is_dev_workspace as \"parent_is_dev_workspace?\", p.dev_workspace_label as \"parent_dev_label?\"\n FROM workspace w LEFT JOIN workspace p ON p.id = w.parent_workspace_id\n WHERE w.id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "parent_workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "is_dev_workspace",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "dev_workspace_label",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "parent_is_dev_workspace?",
"type_info": "Bool"
},
{
"ordinal": 4,
"name": "parent_dev_label?",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true,
false,
true,
false,
true
]
},
"hash": "1c018b483795d9d33baa7be2ee41f48da6fe47bc29b6987cf60edd56bb1663b6"
}
@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "WITH t1 AS (UPDATE websocket_trigger SET script_path = $1, server_id = NULL WHERE script_path = $2 AND workspace_id = $3 AND is_flow = $4), t2 AS (UPDATE kafka_trigger SET script_path = $1, server_id = NULL WHERE script_path = $2 AND workspace_id = $3 AND is_flow = $4), t3 AS (UPDATE postgres_trigger SET script_path = $1, server_id = NULL WHERE script_path = $2 AND workspace_id = $3 AND is_flow = $4), t4 AS (UPDATE mqtt_trigger SET script_path = $1, server_id = NULL WHERE script_path = $2 AND workspace_id = $3 AND is_flow = $4), t5 AS (UPDATE nats_trigger SET script_path = $1, server_id = NULL WHERE script_path = $2 AND workspace_id = $3 AND is_flow = $4), t6 AS (UPDATE sqs_trigger SET script_path = $1, server_id = NULL WHERE script_path = $2 AND workspace_id = $3 AND is_flow = $4), t7 AS (UPDATE amqp_trigger SET script_path = $1, server_id = NULL WHERE script_path = $2 AND workspace_id = $3 AND is_flow = $4) UPDATE gcp_trigger SET script_path = $1, server_id = NULL WHERE script_path = $2 AND workspace_id = $3 AND is_flow = $4",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Text",
"Bool"
]
},
"nullable": []
},
"hash": "1cd16f93bdc90b24ceb1e86495069c3592218d34e58d617253e8b563bc661a7a"
}
@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag, running)\n VALUES ($1, $2, now() + ($3::bigint::text || ' s')::interval, 'other', $4)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Varchar",
"Int8",
"Bool"
]
},
"nullable": []
},
"hash": "1ce0728a6b0942fecf8ce5f4a06857a2601b10b057877c3b2795b8ea9c928c1b"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag, running)\n VALUES ($1, 'test-workspace', now() + ($2::bigint::text || ' s')::interval, 'other', $3)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Int8",
"Bool"
]
},
"nullable": []
},
"hash": "20ae664da5ebd70a6d37609f6b9a3e10d8d524fbaaa8eddf74f12a906b3c8306"
}
@@ -40,7 +40,8 @@
"github",
"azure",
"asset",
"freshness"
"freshness",
"amqp"
]
}
}
@@ -79,7 +80,8 @@
"github",
"azure",
"asset",
"freshness"
"freshness",
"amqp"
]
}
}
@@ -27,7 +27,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -43,7 +43,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "SELECT s.workspace_id, s.path\n FROM schedule s JOIN workspace w ON w.id = s.workspace_id AND NOT w.deleted\n WHERE s.enabled IS TRUE\n AND NOT EXISTS (\n SELECT 1 FROM v2_job_queue q JOIN v2_job j USING (id)\n WHERE j.workspace_id = s.workspace_id\n AND j.trigger_kind = 'schedule'\n AND j.trigger = s.path\n AND j.runnable_path = s.script_path\n AND j.parent_job IS NULL\n )",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false
]
},
"hash": "2756fab586489be33a3031fabfdc87be642091a2f8a6973d001e59be892a984d"
}
@@ -5,7 +5,7 @@
"columns": [],
"parameters": {
"Left": [
"Varchar"
"Text"
]
},
"nullable": []
@@ -43,7 +43,8 @@
"trigger_nextcloud",
"trigger_google",
"trigger_github",
"data_pipeline"
"data_pipeline",
"trigger_amqp"
]
}
}
@@ -0,0 +1,18 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO feature_usage (feature, kind, key, entity_id, value)\n SELECT * FROM UNNEST($1::text[], $2::text[], $3::text[], $4::text[], $5::bigint[])\n ON CONFLICT (feature, kind, key, entity_id, day)\n DO UPDATE SET value = feature_usage.value + EXCLUDED.value, updated_at = now()",
"describe": {
"columns": [],
"parameters": {
"Left": [
"TextArray",
"TextArray",
"TextArray",
"TextArray",
"Int8Array"
]
},
"nullable": []
},
"hash": "2cdb9076747b61c01b6d389157e42bcbf66f0cc5900c7777141df47776e32fa3"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_settings SELECT $1, slack_team_id, slack_name, slack_command_script, slack_email, customer_id, plan, webhook, deploy_to, ai_config, large_file_storage, git_sync, default_app, default_scripts, deploy_ui, mute_critical_alerts, color, operator_settings, teams_command_script, teams_team_id, teams_team_name, git_app_installations, ducklake, slack_oauth_client_id, slack_oauth_client_secret, datatable, teams_team_guid, auto_invite, error_handler, success_handler, public_app_execution_limit_per_minute, error_handler_fallback_to_instance_alerts FROM workspace_settings WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "2dc5a74c4e614b43148925cdbac234f6a2f295a9c46897f5e9aab8f7298ff0d7"
}
@@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"query": "SELECT username, email FROM usr WHERE workspace_id = $1 AND is_admin = true AND operator = false AND disabled = false ORDER BY username LIMIT 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "username",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "email",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false
]
},
"hash": "3202bed875693ae923f496272cd8ad89b2f17a9d3ef4659c2d2284415177b32c"
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "WITH RECURSIVE up AS (\n SELECT id, parent_workspace_id, 0 AS depth FROM workspace WHERE id = $1\n UNION ALL\n SELECT w.id, w.parent_workspace_id, up.depth + 1\n FROM workspace w JOIN up ON w.id = up.parent_workspace_id\n WHERE up.depth < 10\n )\n SELECT git_sync FROM workspace_settings\n WHERE workspace_id = (SELECT id FROM up WHERE parent_workspace_id IS NULL LIMIT 1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "git_sync",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true
]
},
"hash": "3451cbd6a783da68324f64c2e95ad146e2ca8c1cd7c10891fe21a7638aedc381"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT count(*) FROM v2_job_completed\n WHERE workspace_id = $1 AND id = ANY($2) AND status = 'success'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"UuidArray"
]
},
"nullable": [
null
]
},
"hash": "387e135bf363533089493c35eb39dd4864f4e0aa0093df47df7d444ee748dbb2"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE schedule SET error = $1 WHERE workspace_id = $2 AND path = $3 AND enabled IS TRUE",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "392681cc6aa1ed2119d33aa68c8de907889fdfab77fa3af85a96ddaa068af733"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE workspace_settings\n SET git_sync = jsonb_set(\n git_sync,\n '{repositories}',\n (SELECT jsonb_agg(\n CASE WHEN elem->>'git_repo_resource_path' = $2\n AND jsonb_typeof(elem->'auto_pull') = 'object'\n THEN jsonb_set(elem, '{auto_pull}',\n ((elem->'auto_pull') - 'webhook_id' - 'webhook_secret' - 'webhook_error') || $3)\n ELSE elem END)\n FROM jsonb_array_elements(git_sync->'repositories') AS elem)\n )\n WHERE workspace_id = $1\n AND jsonb_typeof(git_sync->'repositories') = 'array'\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Jsonb"
]
},
"nullable": []
},
"hash": "3b7498768197d7e002c3965ad16e6b88daa38ba272d22f846bd4267a134c404c"
}
@@ -129,7 +129,8 @@
"github",
"azure",
"asset",
"freshness"
"freshness",
"amqp"
]
}
}
@@ -1,44 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT provider, model, mode,\n COUNT(*)::BIGINT as \"session_count!\",\n COALESCE(SUM(message_count), 0)::BIGINT as \"message_count!\"\n FROM ai_chat_usage\n WHERE created_at > NOW() - INTERVAL '30 days'\n GROUP BY provider, model, mode\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "provider",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "model",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "mode",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "session_count!",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "message_count!",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false,
null,
null
]
},
"hash": "3ec92c1682f3ce701028f66f7ce83030e7a7ce32971a5621aceb738a3673f943"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET error_handler_fallback_to_instance_alerts = $1 WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Bool",
"Text"
]
},
"nullable": []
},
"hash": "40c591ab93adf3bb4f17598f78b76705eeda7d17d49cafb3e9e6285dee6804c9"
}
@@ -27,7 +27,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT (kind = 'flow' AND parent_job IS NULL) AS \"restartable!\"\n FROM v2_job WHERE id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "restartable!",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
null
]
},
"hash": "4371e59b85cc279dba03800aacde8a4b96c9629cdc785edc7a389c3eb7269608"
}
@@ -33,7 +33,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -42,7 +42,8 @@
"trigger_nextcloud",
"trigger_google",
"trigger_github",
"data_pipeline"
"data_pipeline",
"trigger_amqp"
]
}
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_completed SET status = 'canceled', canceled_by = 'monitor',\n flow_status = $2 WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Jsonb"
]
},
"nullable": []
},
"hash": "4a35f9a0201e283d854bd77f7f79d4c24699c2c7abad4086e0bb876c5c6b2c38"
}
@@ -81,7 +81,8 @@
"github",
"azure",
"asset",
"freshness"
"freshness",
"amqp"
]
}
}
@@ -40,7 +40,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -47,7 +47,8 @@
"trigger_nextcloud",
"trigger_google",
"trigger_github",
"data_pipeline"
"data_pipeline",
"trigger_amqp"
]
}
}
@@ -35,7 +35,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -76,7 +77,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM v2_job_queue",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "521367aaea7beefe4ff9cdb66273f8e3cddfbaf598536f1753f0824f84604826"
}
@@ -36,7 +36,8 @@
"trigger_nextcloud",
"trigger_google",
"trigger_github",
"data_pipeline"
"data_pipeline",
"trigger_amqp"
]
}
}
@@ -43,7 +43,8 @@
"trigger_nextcloud",
"trigger_google",
"trigger_github",
"data_pipeline"
"data_pipeline",
"trigger_amqp"
]
}
}
@@ -36,7 +36,8 @@
"trigger_nextcloud",
"trigger_google",
"trigger_github",
"data_pipeline"
"data_pipeline",
"trigger_amqp"
]
}
}
@@ -0,0 +1,63 @@
{
"db_name": "PostgreSQL",
"query": "SELECT script_path, table_path, kind, name, expr, filter FROM data_metric dm WHERE dm.workspace_id = $1 AND ($2::text IS NULL OR dm.table_path = $2) AND ($3::text IS NULL OR dm.script_path = $3 OR dm.script_path LIKE $4) AND ($6::text IS NULL OR (dm.table_path, dm.kind, dm.name, dm.script_path) > ($6, $7, $8, $9)) AND ( $10 OR dm.script_path = ANY($11) OR EXISTS ( SELECT 1 FROM unnest($12::text[]) AS pfx WHERE dm.script_path = pfx OR left(dm.script_path, length(pfx) + 1) = pfx || '/' ) ) AND EXISTS ( SELECT 1 FROM script s WHERE s.workspace_id = dm.workspace_id AND s.path = dm.script_path AND s.archived = false AND s.deleted = false ) ORDER BY dm.table_path, dm.kind, dm.name, dm.script_path LIMIT $5",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "table_path",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "kind",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "expr",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "filter",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Text",
"Text",
"Int8",
"Text",
"Text",
"Text",
"Text",
"Bool",
"TextArray",
"TextArray"
]
},
"nullable": [
false,
false,
false,
false,
false,
true
]
},
"hash": "58a79f32f880c5ea0ee8821ca4ad07f0f0519a31300e597776ad8b77130501d2"
}
@@ -15,7 +15,7 @@
]
},
"nullable": [
null
true
]
},
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
@@ -0,0 +1,208 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n workspace_id,\n slack_team_id,\n teams_team_id,\n teams_team_name,\n teams_team_guid,\n slack_name,\n slack_command_script,\n teams_command_script,\n slack_email,\n slack_oauth_client_id,\n slack_oauth_client_secret,\n customer_id,\n plan,\n webhook,\n deploy_to,\n ai_config,\n large_file_storage,\n datatable,\n ducklake,\n git_sync,\n deploy_ui,\n default_app,\n default_scripts,\n mute_critical_alerts,\n color,\n operator_settings,\n git_app_installations,\n auto_invite,\n error_handler,\n success_handler,\n public_app_execution_limit_per_minute,\n error_handler_fallback_to_instance_alerts\n FROM\n workspace_settings\n WHERE\n workspace_id = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "slack_team_id",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "teams_team_id",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "teams_team_name",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "teams_team_guid",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "slack_name",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "slack_command_script",
"type_info": "Varchar"
},
{
"ordinal": 7,
"name": "teams_command_script",
"type_info": "Text"
},
{
"ordinal": 8,
"name": "slack_email",
"type_info": "Varchar"
},
{
"ordinal": 9,
"name": "slack_oauth_client_id",
"type_info": "Varchar"
},
{
"ordinal": 10,
"name": "slack_oauth_client_secret",
"type_info": "Varchar"
},
{
"ordinal": 11,
"name": "customer_id",
"type_info": "Varchar"
},
{
"ordinal": 12,
"name": "plan",
"type_info": "Varchar"
},
{
"ordinal": 13,
"name": "webhook",
"type_info": "Text"
},
{
"ordinal": 14,
"name": "deploy_to",
"type_info": "Varchar"
},
{
"ordinal": 15,
"name": "ai_config",
"type_info": "Jsonb"
},
{
"ordinal": 16,
"name": "large_file_storage",
"type_info": "Jsonb"
},
{
"ordinal": 17,
"name": "datatable",
"type_info": "Jsonb"
},
{
"ordinal": 18,
"name": "ducklake",
"type_info": "Jsonb"
},
{
"ordinal": 19,
"name": "git_sync",
"type_info": "Jsonb"
},
{
"ordinal": 20,
"name": "deploy_ui",
"type_info": "Jsonb"
},
{
"ordinal": 21,
"name": "default_app",
"type_info": "Varchar"
},
{
"ordinal": 22,
"name": "default_scripts",
"type_info": "Jsonb"
},
{
"ordinal": 23,
"name": "mute_critical_alerts",
"type_info": "Bool"
},
{
"ordinal": 24,
"name": "color",
"type_info": "Varchar"
},
{
"ordinal": 25,
"name": "operator_settings",
"type_info": "Jsonb"
},
{
"ordinal": 26,
"name": "git_app_installations",
"type_info": "Jsonb"
},
{
"ordinal": 27,
"name": "auto_invite",
"type_info": "Jsonb"
},
{
"ordinal": 28,
"name": "error_handler",
"type_info": "Jsonb"
},
{
"ordinal": 29,
"name": "success_handler",
"type_info": "Jsonb"
},
{
"ordinal": 30,
"name": "public_app_execution_limit_per_minute",
"type_info": "Int4"
},
{
"ordinal": 31,
"name": "error_handler_fallback_to_instance_alerts",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
true,
true,
true,
true,
true,
true,
true,
false,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
false,
true,
true,
true,
true,
false
]
},
"hash": "5ae9ad14effe923f1952d0e1d1f67ad00ff6c13918d46dd474cc48824b12ccaf"
}
@@ -0,0 +1,27 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE\n amqp_trigger\n SET\n amqp_resource_path = $1,\n queue_name = $2,\n exchange = $3,\n options = $4,\n is_flow = $5,\n edited_by = $6,\n permissioned_as = $7,\n script_path = $8,\n path = $9,\n edited_at = now(),\n error = NULL,\n server_id = NULL,\n error_handler_path = $12,\n error_handler_args = $13,\n retry = $14\n WHERE\n workspace_id = $10 AND\n path = $11\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Jsonb",
"Jsonb",
"Bool",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Text",
"Text",
"Varchar",
"Jsonb",
"Jsonb"
]
},
"nullable": []
},
"hash": "5f2e66e53166840c68efbc521fe9393023e79f948976e6ffc0e9bfc94623393c"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_completed SET status = 'canceled', canceled_by = 'admin',\n flow_status = $2 WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Jsonb"
]
},
"nullable": []
},
"hash": "60201b41a0bfea89a201a25ae2431fe94999ca4f11934fbab4638b2653a678dc"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET deploy_to = $1, error_handler_fallback_to_instance_alerts = false WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "620c9efca071cb5fb8b33857129e63954f39b291f525e36e6ece916b98b4d2c9"
}
@@ -38,7 +38,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT username FROM usr\n WHERE workspace_id = $1 AND email = $2 AND NOT operator AND NOT disabled",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "username",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "67d86d334029276d78bf8fa14b25702eba0b0d674b99f952af94487b3e54211c"
}
@@ -162,7 +162,8 @@
"github",
"azure",
"asset",
"freshness"
"freshness",
"amqp"
]
}
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT count(*) FROM (\n SELECT ck.job_id FROM concurrency_key ck\n WHERE ck.key = $1 AND ck.ended_at IS NULL\n LIMIT $2\n ) s WHERE EXISTS (\n SELECT 1 FROM v2_job_queue q WHERE q.id = s.job_id AND q.running = false\n )",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Int8"
]
},
"nullable": [
null
]
},
"hash": "694d787e06b5ef9fbb5ca1315652023ecc0b3122501b72ec7f825966419759ac"
}
@@ -0,0 +1,46 @@
{
"db_name": "PostgreSQL",
"query": "SELECT w.parent_workspace_id, w.is_dev_workspace, w.dev_workspace_label,\n p.is_dev_workspace as \"parent_is_dev_workspace?\", p.dev_workspace_label as \"parent_dev_label?\"\n FROM workspace w LEFT JOIN workspace p ON p.id = w.parent_workspace_id\n WHERE w.id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "parent_workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "is_dev_workspace",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "dev_workspace_label",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "parent_is_dev_workspace?",
"type_info": "Bool"
},
{
"ordinal": 4,
"name": "parent_dev_label?",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true,
false,
true,
false,
true
]
},
"hash": "69d0c59bb11a01ffe9934f62feb93c4e64166a6d4d999911dfab743c43bdac35"
}
@@ -47,7 +47,8 @@
"trigger_nextcloud",
"trigger_google",
"trigger_github",
"data_pipeline"
"data_pipeline",
"trigger_amqp"
]
}
}
@@ -32,7 +32,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE workspace_settings\n SET git_sync = jsonb_set(\n git_sync,\n '{repositories}',\n (SELECT jsonb_agg(\n CASE WHEN elem->>'git_repo_resource_path' IN ($2, '$res:' || $2)\n THEN CASE WHEN $3::text IS NULL THEN elem - 'open_pr_error'\n ELSE jsonb_set(elem, '{open_pr_error}', to_jsonb($3::text), true) END\n ELSE elem END)\n FROM jsonb_array_elements(git_sync->'repositories') AS elem)\n )\n WHERE workspace_id = $1\n AND jsonb_typeof(git_sync->'repositories') = 'array'\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "71faf95db96589e5554882e1dd036bd19923e0b0581efbae448197d6accf01f3"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE schedule SET error = NULL WHERE workspace_id = $1 AND path = $2 AND enabled IS TRUE",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "72b44f728dea65b0542cb72244259703e5029536898469a484ebed23d6226e82"
}
@@ -27,7 +27,8 @@
"nextcloud",
"google",
"github",
"azure"
"azure",
"amqp"
]
}
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job (id, workspace_id, tag) VALUES ($1, $2, 'other')",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Varchar"
]
},
"nullable": []
},
"hash": "750eb7365e2590a42d9f3c4cbbf193ea4129874c36e2c334e9e73f4b4c54ad07"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_completed\n SET status = 'canceled', canceled_by = 'monitor', canceled_reason = 'zombie flow',\n flow_status = $2\n WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Jsonb"
]
},
"nullable": []
},
"hash": "7d2923f940cf5a8cbbdc51de91c7e00942a8f4f5251502ef8efc0f510a857551"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_settings\n (workspace_id, color, error_handler_fallback_to_instance_alerts)\n VALUES ($1, $2, $3)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Bool"
]
},
"nullable": []
},
"hash": "7da2e04f64cef6634256d7b3b12d8a46dc7320e7dcdd64befe808dec8cbe2265"
}

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