Merge origin/main into fix-runnable-test-draft-app

Four conflicts, all where main reshaped code this branch had added to.

`open_preview` grew an access-gated `schemaFor` and a view/edit `mode`, and
`openSessionPreview` gained `workspace` + `access` parameters: kept both, with this
branch's `draftTarget` and `ctx.target` threaded through the new signature. Main
also added an early return for pipelines, which makes this branch's
`kind === 'pipeline'` guard on the path unreachable — dropped it, along with the
pipeline comment the restructure stranded above non-pipeline code.

`draftWriteFailure` took main's `DRAFT_CONFLICT_RESULT` / `DRAFT_SAVE_FAILED_RESULT`
constants with this branch's `displayPath`, and `init_app` took main's rewritten
template message with this branch's resolved target.

`setDeployedInSessionHandler`: main replaced `syncPreviewWithDeployed` with
`itemDeployed`, which also stops sync, discards, bumps the deploy revision and
invalidates both workspace caches, and added those invalidations to the
not-mounted path. Kept all of it, with this branch's forget-and-retarget when the
deploy moved the item, and `itemDeployed` now called at the path the tab was
retargeted to — identical to the addressed path when nothing moved.

Outside the conflicts, main renamed `isEditorTabFor(url, target)` to
`isItemTabFor(url, target, side)` for its editor/viewer tab split. The merge kept
this branch's two calls to the old name without flagging it, so `retargetEditorItem`
threw at runtime while compiling clean; both now pass `'edit'`, the side whose cell
can be raced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-10-01 09:13:14 -07:00
co-authored by Claude Opus 5
518 changed files with 36382 additions and 9263 deletions
+9
View File
@@ -11,6 +11,15 @@ authoring and the full run reference.
Run the affected mode **before** your change and **after**, same model(s), same cases.
## Where guidance goes
What the model should know about Windmill itself (flow shapes, groups, data tables, app
access, secrets…) belongs in `system_prompts/base/` or `system_prompts/languages/`, which also
feed the CLI's skills — not in a `*/core.ts` prompt builder, where only this chat would see it.
The builders add tool plumbing and runtime values only. Scope a sentence to one consumer with
`<!-- chat-only -->` / `<!-- cli-only -->`, keep chat tool names out of the shared files, and
rerun `python system_prompts/generate.py`. `system_prompts/README.md` has the mechanics.
## Measure the window first, and cumulative second
Optimize **`finalContextTokens`** (window occupancy — what drives overflow and
+3 -3
View File
@@ -1,6 +1,6 @@
---
name: local-review-codex
description: Run the CI Codex PR review locally against this branch's unpushed work (committed + uncommitted) before pushing. Same policy and reasoning effort as the codex-pr-review GitHub action, on a newer model.
description: Run the CI Codex PR review locally against this branch's unpushed work (committed + uncommitted) before pushing. Same policy, model and reasoning effort as the codex-pr-review GitHub action.
---
# Local Codex Review (pre-push)
@@ -11,18 +11,18 @@ before the PR exists. Use this before `git push` on a non-trivial change.
**Correspondence with CI** — identical:
- Policy: `REVIEW.md` (severity triage, public-surface checklist, AGENTS.md compliance, test coverage).
- Model: `gpt-6.1-sol`.
- Reasoning effort: `model_reasoning_effort="xhigh"`.
- Output: markdown starting with `## Codex Review`, findings tagged P0 / P1 / P2 with file:line.
**Differences from CI** — local-only:
- Model is `gpt-6-astra`; CI stays on `gpt-5.6-sol`. Not an oversight to reconcile: `gpt-6-astra` is confirmed on the ChatGPT auth `codex login` uses locally, while CI authenticates with `OPENAI_API_KEY` (`codex-pr-review.yml` prefers it over `CODEX_AUTH_JSON`) and that tier is unverified for the model. Move CI once API access is confirmed, or once CI switches to `CODEX_AUTH_JSON`.
- Scope is the current branch vs `main` at the merge-base, **including uncommitted changes** (CI reviews a pushed PR diff).
- Sandbox is `read-only` (CI uses `danger-full-access` on an ephemeral runner). Codex reads the diff and files but cannot modify your working tree.
- Fresh context is inherent: `codex exec` is a separate cold process, so it does not anchor on the current chat session — the same reason `local-review` insists on a subagent.
## Prerequisites
- `codex` CLI **>= 0.153.4** installed and authed via `codex login` (an `OPENAI_API_KEY` in the environment takes priority and may not reach `gpt-6-astra` — see the model note above). Older CLIs reject the model with "requires a newer version of Codex"; `run.sh` checks the version up front. Upgrade with `npm install --global @openai/codex@0.153.4` (may need `sudo` for a global install). This matches the pin in `.github/workflows/codex-pr-review.yml` — the CLI version is the same on both sides, only the model differs.
- `codex` CLI **>= 0.159.3** installed and authed via `codex login` or an `OPENAI_API_KEY` in the environment (which takes priority). Older CLIs reject the model with "requires a newer version of Codex"; `run.sh` checks the version up front. Upgrade with `npm install --global @openai/codex@0.159.3` (may need `sudo` for a global install). This matches the pin in `.github/workflows/codex-pr-review.yml` — the CLI version is the same on both sides.
- `git fetch` the base ref if it's stale, so the merge-base is accurate.
## Run
+4 -7
View File
@@ -1,17 +1,14 @@
#!/usr/bin/env bash
# Local Codex review — mirrors the .github/workflows/codex-pr-review.yml CI job,
# but scoped to this branch's unpushed work (committed + uncommitted) so you can
# review before pushing. Same policy (REVIEW.md) and reasoning effort (xhigh) as CI.
#
# The model deliberately differs from CI: gpt-6-astra is confirmed available on the
# ChatGPT auth `codex login` uses here, but CI authenticates with OPENAI_API_KEY and
# that tier is unverified for it, so codex-pr-review.yml stays on gpt-5.6-sol.
# review before pushing. Same policy (REVIEW.md), model and reasoning effort (xhigh)
# as CI.
#
# Usage: run.sh [BASE_REF] (BASE_REF defaults to "main")
set -euo pipefail
MODEL="gpt-6-astra"
CODEX_MIN="0.153.4"
MODEL="gpt-6.1-sol"
CODEX_MIN="0.159.3"
BASE_REF="${1:-main}"
REPO_ROOT="$(git rev-parse --show-toplevel)"
+2 -2
View File
@@ -119,8 +119,8 @@ Form components (TextInput, Toggle, Select, etc.) should use the unified size sy
New user-facing UX is the main source of `feature_usage` counters — propose them in the plan, not
as a separate question, and read `docs/feature-telemetry.md` first. `logFeatureUsage()` from
`$lib/utils/featureUsage` is only half the change: the `(feature, kind)` pair must also be
registered in the backend allowlist or every event is silently discarded, and the disclosure copy
in `InstanceSettings.svelte` must name what you added.
registered in the backend allowlist or every event is silently discarded. The disclosure copy in
`InstanceSettings.svelte` stays at the category level; see the recipe's step 3.
## Svelte MCP Server
+171
View File
@@ -0,0 +1,171 @@
name: AI Evals (scheduled)
# Full ai_evals suites on current models, posted to the AI evals dashboard in
# the windmill-prod workspace (f/ai/ai_evals_dashboard) so quality is tracked
# over time. Weekly, since one pass of every suite at --runs 3 costs about 50M
# tokens per model: every suite runs on MODELS, and global (the mode users get)
# also runs on GLOBAL_EXTRA_MODELS, one flagship per other provider. Run it by
# hand to measure a branch against main.
on:
schedule:
- cron: "0 3 * * 1"
workflow_dispatch:
inputs:
modes:
description: "Space-separated modes"
default: "global flow app script cli"
models:
description: "Space-separated model aliases (bun run cli -- models)"
default: "sonnet-5.5"
global_extra_models:
description: "Extra model aliases for global mode only"
default: "gpt-6-astra gemini-3.8-flash"
runs:
description: "Runs per case"
default: "3"
reasoning:
description: "Reasoning effort for frontend modes (empty: the product default)"
default: ""
concurrency:
group: ai-evals-scheduled-${{ github.ref }}
env:
MODELS: ${{ inputs.models || 'sonnet-5.5' }}
GLOBAL_EXTRA_MODELS: ${{ inputs.global_extra_models || 'gpt-6-astra gemini-3.8-flash' }}
RUNS: ${{ inputs.runs || '3' }}
REASONING: ${{ inputs.reasoning }}
INGEST_URL: https://app.windmill.dev/api/r/f/ai/ingest_ai_eval_run
jobs:
setup:
runs-on: ubuntu-latest
outputs:
modes: ${{ steps.modes.outputs.modes }}
steps:
- id: modes
env:
MODES: ${{ inputs.modes || 'global flow app script cli' }}
run: |
echo "modes=$(jq -cn --arg m "$MODES" '$m | split(" ")
| map(select(IN("global", "flow", "app", "script", "cli")))')" >> "$GITHUB_OUTPUT"
evals:
needs: setup
runs-on: ubicloud-standard-16
timeout-minutes: 330
strategy:
fail-fast: false
matrix:
mode: ${{ fromJSON(needs.setup.outputs.modes) }}
services:
postgres:
image: postgres:16
ports:
- 5432:5432
env:
POSTGRES_DB: windmill
POSTGRES_PASSWORD: changeme
options: >-
--health-cmd pg_isready --health-interval 10s --health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.97.0
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.4.0
- uses: actions/setup-node@v7
with:
node-version: "24"
- name: Build Windmill
working-directory: ./backend
env:
SQLX_OFFLINE: true
CARGO_BUILD_JOBS: 12
RUSTFLAGS: ""
run: cargo build --features quickjs
- name: Start Windmill
working-directory: ./backend
env:
DATABASE_URL: postgres://postgres:changeme@localhost:5432/windmill
RUST_LOG: info
run: |
mkdir -p ../ai_evals/logs
./target/debug/windmill > ../ai_evals/logs/windmill.log 2>&1 &
for i in $(seq 1 60); do
curl -sf http://localhost:8000/api/version > /dev/null 2>&1 && break
sleep 2
done
curl -sf http://localhost:8000/api/version > /dev/null || { tail -50 ../ai_evals/logs/windmill.log; exit 1; }
- name: Install frontend deps + generate client
working-directory: ./frontend
run: |
npm ci
npm run generate-backend-client
- name: Install CLI deps + generate CLI client
working-directory: ./cli
run: bun install && ./gen_wm_client.sh && ./windmill-utils-internal/gen_wm_client.sh
- name: Run ${{ matrix.mode }} evals
working-directory: ./ai_evals
env:
MODE: ${{ matrix.mode }}
WMILL_AI_EVAL_BACKEND_URL: http://localhost:8000
WMILL_AI_EVAL_BACKEND_WORKSPACE: integration-tests
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
run: |
bun install
mkdir -p results
models="$MODELS"
[ "$MODE" = global ] && models="$models $GLOBAL_EXTRA_MODELS"
reasoning=()
[ -n "$REASONING" ] && [ "$MODE" != cli ] && reasoning=(--reasoning "$REASONING")
for m in $models; do
bun run cli -- run "$MODE" --model "$m" --runs "$RUNS" "${reasoning[@]}" \
--output "$PWD/results/$MODE-$m.json" || echo "::warning::$MODE on $m errored"
done
- name: Post results to the dashboard
if: always()
working-directory: ./ai_evals
env:
MODE: ${{ matrix.mode }}
INGEST_TOKEN: ${{ secrets.AI_EVALS_INGEST_TOKEN }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
[ -n "$INGEST_TOKEN" ] || { echo "::warning::AI_EVALS_INGEST_TOKEN is not set"; exit 0; }
shopt -s nullglob
for f in results/"$MODE"-*.json; do
# Keep what the dashboard reads; the full traces stay in the run's artifacts.
jq -c --arg ref "$GITHUB_REF" --arg trigger "$GITHUB_EVENT_NAME" --arg url "$RUN_URL" '
{result_json: (del(.cases[].prompt, .cases[].initialPath, .cases[].expectedPath)
| .cases[].attempts[] |= {attempt, passed, durationMs, toolCallCount, judgeScore,
judgeSummary, error, tokenUsage, checks: [.checks[]? | {name, passed}]}),
git_ref: $ref, trigger: $trigger, run_url: $url}' "$f" \
| curl -sf --retry 3 -X POST "$INGEST_URL" \
-H "Authorization: Bearer $INGEST_TOKEN" -H 'content-type: application/json' \
--data-binary @- && echo " <- $f" || echo "::warning::failed to post $f"
done
- name: Archive logs and results
uses: actions/upload-artifact@v4
if: always()
with:
name: ai-evals-${{ matrix.mode }}
path: |
ai_evals/logs
ai_evals/results
+1 -1
View File
@@ -147,7 +147,7 @@ jobs:
mkdir -p results
# One cheap model per provider (anthropic/openai/googleai/deepseek).
fail=0
for m in haiku 4o gemini-3-flash-preview deepseek-v4-flash; do
for m in haiku gpt-6-luna gemini-3.8-flash deepseek-flash; do
echo "::group::global-test1-script-create ($m)"
if ! bun run cli -- run global global-test1-script-create \
--model "$m" --execution-only --output "$PWD/results/ci-$m.json"; then
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
allowed_bots: 'windmill-internal-app[bot]'
trigger_phrase: '/plan'
claude_args: |
--model claude-opus-5
--model claude-opus-5-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-5
--model claude-opus-5-5
+2 -2
View File
@@ -222,7 +222,7 @@ jobs:
- name: Install Codex CLI
if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
run: npm install --global @openai/codex@0.153.4
run: npm install --global @openai/codex@0.159.3
- name: Configure Codex auth
if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
@@ -359,7 +359,7 @@ jobs:
# e.g. a GitHub Action's index.js, which then runs with our credentials.
codex exec \
-C "$GITHUB_WORKSPACE" \
-m gpt-5.6-sol \
-m gpt-6.1-sol \
-c 'model_reasoning_effort="xhigh"' \
-s "$SANDBOX_MODE" \
-o "$RUNNER_TEMP/codex-final-message.md" \
+1 -1
View File
@@ -208,4 +208,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-5
--model claude-opus-5-5
+1 -1
View File
@@ -57,7 +57,7 @@ jobs:
uses: docker/setup-buildx-action@v3
- name: Build test image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v7
with:
context: .
file: ./docker/DockerfileExtra
+1 -1
View File
@@ -1,3 +1,3 @@
{
".": "1.819.0"
".": "1.820.0"
}
+8 -1
View File
@@ -29,10 +29,16 @@ Open-source platform for internal tools, workflows, API integrations, background
- **Agent workers**: `docs/agent-worker-e2e.md` — building and running one locally. An agent
reaches the DB only through the API, so `Connection::Http` paths are never taken by a plain
`cargo run`; a normal build cannot start one at all.
- **External instance data tables**: `docs/external-instance-datatables.md` — the cluster Windmill
administers behind `external_instance` data tables and Ducklake catalogs: its invariants (one
lifecycle lock, managed-object markers, the setup gate, per-cluster roles, fork copy ownership)
and how to run one locally
- **Enterprise**: `docs/enterprise.md` — EE file conventions and PR workflow
- **Operator write rights**: `docs/operator-write-rights.md` — which `operator_settings` flags are
enforced rather than cosmetic, and why a right that is granted-unless-withdrawn needs `Option`
fields and a jsonb merge rather than a serde default
- **Operator builder rights**: `docs/operator-builder-rights.md` — the workspace setting that lets
operators compose flows, what the composition check must cover, and why it costs a full seat
- **Auth surface**: `docs/auth-surface.md` — credential precedence, session/cache invalidation
scope, which token labels email their owner at expiry, how OAuth login matches `login_type`, and
that every superadmin route refuses `$WM_TOKEN`. Read before designing anything that creates
@@ -43,11 +49,12 @@ Open-source platform for internal tools, workflows, API integrations, background
- **Backend patterns**: use the `rust-backend` skill when writing Rust code
- **Frontend patterns**: use the `svelte-frontend` skill when writing Svelte code. Do NOT edit svelte files unless you have read that skill.
- **Frontend UUIDs**: do not call `crypto.randomUUID()` in frontend code. Import `randomUUID` from `$lib/utils/uuid` instead.
- **Code review**: review the current PR or branch against the shared review policy in `REVIEW.md` (severity triage, public-surface checklist, AGENTS.md compliance, test-coverage assessment). The skill at `.agents/skills/local-review/SKILL.md` orchestrates it. All three CLIs auto-discover the same SKILL — Claude reads `.claude/skills/` (symlinked to the canonical `.agents/skills/` file), Codex and Pi read `.agents/skills/` directly. Invoke with `/local-review` in Claude Code, `$local-review` (or `/skills` selector) in Codex, or `pi --skill local-review` / `/skill:local-review` in Pi. For a Codex-driven pass that mirrors the `codex-pr-review` GitHub action against your unpushed work (committed + uncommitted) before you push, use `/local-review-codex` (`.agents/skills/local-review-codex/`) — same `REVIEW.md` policy and `xhigh` reasoning, on `gpt-6-astra` rather than the action's `gpt-5.6-sol`; requires the `codex` CLI >= 0.153.4.
- **Code review**: review the current PR or branch against the shared review policy in `REVIEW.md` (severity triage, public-surface checklist, AGENTS.md compliance, test-coverage assessment). The skill at `.agents/skills/local-review/SKILL.md` orchestrates it. All three CLIs auto-discover the same SKILL — Claude reads `.claude/skills/` (symlinked to the canonical `.agents/skills/` file), Codex and Pi read `.agents/skills/` directly. Invoke with `/local-review` in Claude Code, `$local-review` (or `/skills` selector) in Codex, or `pi --skill local-review` / `/skill:local-review` in Pi. For a Codex-driven pass that mirrors the `codex-pr-review` GitHub action against your unpushed work (committed + uncommitted) before you push, use `/local-review-codex` (`.agents/skills/local-review-codex/`) — same `REVIEW.md` policy, `gpt-6.1-sol` model and `xhigh` reasoning as the action; requires the `codex` CLI >= 0.159.3.
- **Domain guides**: `.claude/skills/native-trigger/`
- **Brand/UI guidelines**: `frontend/brand-guidelines.md`
- **Domain vocabulary**: `CONTEXT.md` — the words this codebase uses for its own concepts (step, step setting, trigger step, …). Name things the way it does.
- **CLI commands**: when adding/modifying/removing a command, subcommand, option, or description in `cli/src/commands/`, run `python system_prompts/generate.py` to refresh `system_prompts/auto-generated/` and `cli/src/guidance/skills.gen.ts`. The CLI docs the agents use to operate `wmill` are derived from the source — stale generated files give agents the wrong flags.
- **AI guidance** (chat prompts and CLI skills): what the AI knows about Windmill itself — how flows, scripts, apps, pipelines and resources work — is written once, in `system_prompts/base/` and `system_prompts/languages/`, and `system_prompts/generate.py` builds both the chat's `$system_prompts` exports and the CLI's skills from it. Add or change such guidance there, never inline in one consumer: a sentence only one side should see goes in a `<!-- cli-only -->` / `<!-- chat-only -->` block, and a new file reaches both through the `TOPICS` table. The chat prompt builders under `frontend/src/lib/components/copilot/chat/` add only tool plumbing and runtime values, and shared markdown names no chat tool, since it reaches every chat mode. Rerun `generate.py` after editing; `system_prompts/README.md` has the details.
- **Session recorder**: `frontend/src/lib/components/recording/` is also the recorder `wmill app dev --recording` serves, vendored into the CLI as `cli/src/commands/app/devRecorderBundle.gen.ts`. After changing `rawAppSnapshot.ts` or `rawAppRecording.svelte.ts`, run `bun run gen:dev-recorder` from `cli/` (`cli/test/dev_recorder_bundle_unit.test.ts` fails otherwise).
- **Raw-app policy**: `frontend/src/lib/components/raw_apps/rawAppPolicy.ts` also derives the policy the server's raw-app deploy stores, vendored into the bundle job as `backend/windmill-api/src/apps_raw_policy.gen.js`. After changing it or anything it imports, run `bun run gen:app-policy` from `cli/` (`cli/test/app_policy_bundle_unit.test.ts` fails otherwise). It rides in the job rather than being read from the CLI the job runs because the images install `windmill-cli` unpinned, so an image can carry one older than its server.
+31
View File
@@ -1,5 +1,36 @@
# Changelog
## [1.820.0](https://github.com/windmill-labs/windmill/compare/v1.819.0...v1.820.0) (2026-09-29)
### Features
* add pull_batch to claim jobs for many waiting workers at once ([#11350](https://github.com/windmill-labs/windmill/issues/11350)) ([9f40cdc](https://github.com/windmill-labs/windmill/commit/9f40cdca6258a9218770ebe1e7b89179e7d6667f))
* add tree view to the schedules page ([#11360](https://github.com/windmill-labs/windmill/issues/11360)) ([e68ff0d](https://github.com/windmill-labs/windmill/commit/e68ff0d969edaacafc0fc5e246ac3945ae28109b))
* **ai-agent:** add compaction memory that summarizes older context ([#10928](https://github.com/windmill-labs/windmill/issues/10928)) ([c1b59f7](https://github.com/windmill-labs/windmill/commit/c1b59f70dd0e1f4a7597d31cff031ce6c0e7d0ab))
* detect and alert when a schedule skips occurrences ([#10917](https://github.com/windmill-labs/windmill/issues/10917)) ([97fa55b](https://github.com/windmill-labs/windmill/commit/97fa55b719a874067d1a81a1d680a42d1f1a4c98))
* external instance cluster for data tables and Ducklake catalogs ([#11197](https://github.com/windmill-labs/windmill/issues/11197)) ([8741843](https://github.com/windmill-labs/windmill/commit/8741843d2e884783eed1c00bbc2293e526f3e80d))
* refuse OIDC tokens to previews that impersonate a path their user cannot write ([#11396](https://github.com/windmill-labs/windmill/issues/11396)) ([d1260d7](https://github.com/windmill-labs/windmill/commit/d1260d7c5a3d132d4427079b794452309e03e28e))
* restart perpetual runs on the version a deploy makes runnable ([#11200](https://github.com/windmill-labs/windmill/issues/11200)) ([cf5c49c](https://github.com/windmill-labs/windmill/commit/cf5c49c3dca2201f739fb872d68bcd6c0c7665f7))
* run turns in several flow chat conversations at once ([#11202](https://github.com/windmill-labs/windmill/issues/11202)) ([f367eaf](https://github.com/windmill-labs/windmill/commit/f367eaf6d0258649ca0b3d47b1270af9a3fcd484))
* share AI guidance with the CLI skills and lint flow groups ([#11397](https://github.com/windmill-labs/windmill/issues/11397)) ([ede4103](https://github.com/windmill-labs/windmill/commit/ede4103b00c6ada3b5ea8ba3e98bf6b78a791d63))
* show native web search in the assistant settings modal ([#11394](https://github.com/windmill-labs/windmill/issues/11394)) ([235410c](https://github.com/windmill-labs/windmill/commit/235410c1e417bb64b432c96efed870cab74cb364))
* stop cross-origin isolating the raw app editor ([#11411](https://github.com/windmill-labs/windmill/issues/11411)) ([6064aec](https://github.com/windmill-labs/windmill/commit/6064aecdecc49facde3870446b570abd8e8124b2))
### Bug Fixes
* bound a resumed session fork's wait, keep its intent while in flight ([#11413](https://github.com/windmill-labs/windmill/issues/11413)) ([e6df8d7](https://github.com/windmill-labs/windmill/commit/e6df8d78d496cf9640bd5a1e9d7e8f4c2cc2adf7))
* create workspace forks in the background, with progress ([#11406](https://github.com/windmill-labs/windmill/issues/11406)) ([054109d](https://github.com/windmill-labs/windmill/commit/054109d8559cc0a41ef361786c4d1d99eab9c502))
* drop caller-supplied _MODULES at push for every job ([#11418](https://github.com/windmill-labs/windmill/issues/11418)) ([c5f59d6](https://github.com/windmill-labs/windmill/commit/c5f59d6ccc71d08fc34ede47e7a77b35a197b9db))
* keep new-menu submenus open while crossing the gutter ([#11414](https://github.com/windmill-labs/windmill/issues/11414)) ([2f87bc6](https://github.com/windmill-labs/windmill/commit/2f87bc6a2d237382641ad3f0e7a8df36f0b74c15))
* keep secret variable values hidden when toggling secret ([#11383](https://github.com/windmill-labs/windmill/issues/11383)) ([da5c58d](https://github.com/windmill-labs/windmill/commit/da5c58de2a0c297cfa81a601f9f543f46107e3f6))
* refuse flow preview restarts from runs the caller cannot read ([#11407](https://github.com/windmill-labs/windmill/issues/11407)) ([c34abf7](https://github.com/windmill-labs/windmill/commit/c34abf7330e7f3fbbf5b68e325f0f0991c6c60d5))
* report a worker's last job when it ran under one poll interval ([#11399](https://github.com/windmill-labs/windmill/issues/11399)) ([797147e](https://github.com/windmill-labs/windmill/commit/797147ea7a4a911d3c82e9ac9cdaf066ad2795d6))
* run custom schedule handlers as the schedule ([#11398](https://github.com/windmill-labs/windmill/issues/11398)) ([bda00c6](https://github.com/windmill-labs/windmill/commit/bda00c6cad2de2ce0927651fe0e53653bdd20a96))
* surface raw app build errors to the session AI ([#11415](https://github.com/windmill-labs/windmill/issues/11415)) ([50ffad5](https://github.com/windmill-labs/windmill/commit/50ffad52d765497fa3a561ebe8842b40fc8bd58e))
* turn the default raw app into a feature tour ([#11417](https://github.com/windmill-labs/windmill/issues/11417)) ([83b8954](https://github.com/windmill-labs/windmill/commit/83b8954ae26f8d453d38e867e62366341cfe7729))
## [1.819.0](https://github.com/windmill-labs/windmill/compare/v1.818.0...v1.819.0) (2026-09-28)
+18
View File
@@ -48,8 +48,26 @@ _Avoid_: thread, session (that names an AI session, a different thing), chat (th
One question and the answer to it: the run the question started, the handle that stops it,
and the rows it is writing. At most one per conversation, and the chat is held for its whole
length — from the moment the question takes the chat, before it has a job, until it is ended.
The server holds the same rule: a question sent to a conversation whose turn is still running
is refused, whoever sends it. Several conversations of one flow can each have a turn running.
_Avoid_: request, exchange, message round
**Running turn**:
A turn whose run has not finished. Which conversations have one is the server's to say, so a
chat opened after the turn started — a reload, another tab — still sees it and follows it.
_Avoid_: busy, active, in flight
**Queued message**:
A question typed into a conversation while its turn runs, sent when that turn ends answered.
At most one per conversation; typing another adds to it. A turn that fails, or is stopped,
hands it back to the composer instead of sending it.
_Avoid_: pending message (a pending message is one already sent and not yet confirmed)
**Unread**:
The answers that arrived in a conversation while it was not the one shown. Counted per open
chat and forgotten on reload.
_Avoid_: new messages, notifications
**Transcript**:
The rows a conversation's chat holds. Not the conversation: it is the newest page plus
whatever older pages the reader has scrolled back through, so a question it cannot answer
+3 -14
View File
@@ -77,31 +77,20 @@ Public CLI surface:
- `--verbose`: stream assistant output for frontend runs
- `--skip-judge`: skip LLM judge scoring for the run
- `--execution-only`: only require the model/proxy/frontend loop to complete; skip validators, tool expectations, backend artifact validation, and judge scoring
- `--reasoning <effort>`: reasoning effort for frontend modes (`off`, `low`, `medium`, `high`, `max`, …); without it the product's default applies (`high` on models that can reason). The effort is appended to the recorded model label (`anthropic:claude-sonnet-5-5@max`)
- `--record`: append a compact tracked summary line to `ai_evals/history/<mode>.jsonl` for full-suite runs only
- `--backend-validation <mode>`: optional backend smoke validation (`off` or `preview`) for `script` and `flow` evals
## Models
Use `bun run cli -- models` to see the current aliases.
Today:
- `haiku`
- `sonnet`
- `opus`
- `4o`
- `gpt-5.5`
- `gemini-3-flash-preview`
- `gemini-3.1-pro-preview`
- `deepseek-v4-flash`
- `deepseek-v4-pro`
Use `bun run cli -- models` to see the current aliases; `core/models.ts` is the list.
Notes:
- the command also prints accepted alias spellings such as `gpt-4o`, `gpt-55`, `claude-opus-4.6`, and `claude-haiku-4.5`
- frontend modes (`flow`, `script`, `app`, `global`) can use Anthropic, OpenAI, Gemini, and DeepSeek-backed aliases
- `cli` mode always uses the Anthropic agent SDK, so only Anthropic aliases are valid there
- the judge model is separate and currently defaults to `claude-sonnet-4-6`; use `--skip-judge` for deterministic-only runs
- the judge model is separate and currently defaults to `claude-sonnet-5-5`; use `--skip-judge` for deterministic-only runs
## Case Format
@@ -15,6 +15,7 @@ import { runEval } from "../shared";
import type { ModeRunContext } from "../../../../core/types";
import type { TokenUsage, ToolCallDetail } from "../shared/types";
import type { WindmillBackendSettings } from "../../../../core/windmillBackendSettings";
import { evalReasoningEffort } from "../shared/providerConfig";
export interface ScriptEvalResult {
success: boolean;
@@ -41,14 +42,15 @@ export interface ScriptEvalOptions {
function resolveModelProvider(
model: string,
provider?: AIProvider,
): AIProviderModel {
): AIProviderModel & { reasoning?: string } {
const reasoning = evalReasoningEffort();
if (provider) {
return { provider, model };
return { provider, model, reasoning };
}
if (model.startsWith("claude")) {
return { provider: "anthropic", model };
return { provider: "anthropic", model, reasoning };
}
return { provider: "openai", model };
return { provider: "openai", model, reasoning };
}
export async function runScriptEval(
@@ -12,6 +12,12 @@ export interface EvalClients {
export interface ResolvedEvalModelProvider {
provider: FrontendEvalProvider;
model: string;
reasoning?: string;
}
/** `run --reasoning` hands the effort to the frontend runtime through the environment. */
export function evalReasoningEffort(): string | undefined {
return process.env.WMILL_AI_EVAL_REASONING || undefined;
}
export interface WindmillAiProxyClientConfig {
@@ -73,6 +79,13 @@ export function createEvalClients(input: {
export function resolveEvalModelProvider(
model: string,
provider?: FrontendEvalProvider,
): ResolvedEvalModelProvider {
return { ...resolveProvider(model, provider), reasoning: evalReasoningEffort() };
}
function resolveProvider(
model: string,
provider?: FrontendEvalProvider,
): ResolvedEvalModelProvider {
if (provider) {
return { provider, model };
+332 -24
View File
@@ -1,4 +1,7 @@
import { randomUUID } from 'node:crypto'
import confluenceFixture from '../../fixtures/frontend/global/hub/confluence.json'
import servicenowFixture from '../../fixtures/frontend/global/hub/servicenow.json'
import outreachFixture from '../../fixtures/frontend/global/hub/outreach.json'
import type {
AppWithLastVersion,
CompletedJob,
@@ -250,9 +253,7 @@ export function listBenchmarkApps(workspace: string): ListableApp[] | null {
}
export function getBenchmarkAppByPath(workspace: string, path: string): AppWithLastVersion | null {
const app = benchmarkWorkspaceRunnables
.get(workspace)
?.apps?.find((entry) => entry.path === path)
const app = benchmarkWorkspaceRunnables.get(workspace)?.apps?.find((entry) => entry.path === path)
return app ? buildBenchmarkApp(app) : null
}
@@ -448,7 +449,10 @@ export function createBenchmarkCompletedJob(input: {
labels: input.label ? [input.label] : undefined
}
benchmarkJobs.set(benchmarkJobKey(input.workspace, jobId), { workspace: input.workspace, job })
benchmarkJobs.set(benchmarkJobKey(input.workspace, jobId), {
workspace: input.workspace,
job
})
return jobId
}
@@ -532,7 +536,13 @@ export function getBenchmarkFlowAllResults(workspace: string, jobId: string) {
*/
const benchmarkDrafts = new Map<
string,
{ workspace: string; kind: UserDraftItemKind; path: string; value: unknown; createdAt: string }
{
workspace: string
kind: UserDraftItemKind
path: string
value: unknown
createdAt: string
}
>()
// Counter-based timestamps: deterministic run-to-run (same event order → same
@@ -614,7 +624,9 @@ export function getBenchmarkDraftForUser(input: {
}): GetDraftForUserResponse {
const entry = benchmarkDrafts.get(benchmarkDraftKey(input.workspace, input.kind, input.path))
if (!entry) {
throw Object.assign(new Error(`no draft for "${input.path}"`), { status: 404 })
throw Object.assign(new Error(`no draft for "${input.path}"`), {
status: 404
})
}
return { value: entry.value, created_at: entry.createdAt }
}
@@ -635,7 +647,11 @@ export function getBenchmarkOwnDraft(input: {
/** 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 {
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))
@@ -762,10 +778,11 @@ export function runBenchmarkDatatableSql(input: {
* shape `pollJobResult` consumes. The job is created synchronously before
* polling, so it is always present and completed.
*/
export function getBenchmarkCompletedJobResultMaybe(input: {
workspace: string
id: string
}): { success: boolean; completed: boolean; result: unknown } {
export function getBenchmarkCompletedJobResultMaybe(input: { workspace: string; id: string }): {
success: boolean
completed: boolean
result: unknown
} {
const job = getBenchmarkCompletedJob(input.workspace, input.id)
if (!job) {
throw new Error(`Job "${input.id}" not found in benchmark workspace`)
@@ -896,7 +913,9 @@ export function previewBenchmarkSchedule(input: {
}): Record<string, unknown> {
const schedule = input.requestBody?.schedule
if (typeof schedule !== 'string' || schedule.trim().split(/\s+/).length !== 6) {
throw new Error(`schedule must use a six-field cron expression, got ${JSON.stringify(schedule)}`)
throw new Error(
`schedule must use a six-field cron expression, got ${JSON.stringify(schedule)}`
)
}
return {
@@ -928,7 +947,9 @@ export function createBenchmarkHttpTrigger(input: {
typeof input.requestBody.route_path === 'string' &&
input.requestBody.route_path.startsWith('/')
) {
throw new Error(`HTTP trigger route_path must not start with /, got "${input.requestBody.route_path}"`)
throw new Error(
`HTTP trigger route_path must not start with /, got "${input.requestBody.route_path}"`
)
}
return {
path: input.requestBody.path,
@@ -1024,7 +1045,39 @@ function buildBenchmarkApp(app: BenchmarkWorkspaceApp): AppWithLastVersion {
* hub tools throw and no case can exercise hub reuse. Serving fixtures rather
* than the live hub also keeps assertions on script content stable as the real
* hub republishes new versions. */
const BENCHMARK_INTEGRATION_META_PATH = /^\/api\/integrations\/hub\/([^/]+)\/meta$/
/** One integration lifted verbatim from the content repo — its shipped scripts, its
* authored meta.json and its resource type. Hand-written fixtures make both routes to
* an integration's conventions look equally cheap; a real one is the only way to tell
* whether reading the metadata beats reading a script. */
const REAL_HUB_INTEGRATIONS = [
confluenceFixture,
servicenowFixture,
outreachFixture
] as unknown as Array<{
app: string
display_name: string
description: string
docs_url: string
curated: boolean | null
meta: unknown
resource_type: { name: string; description: string; schema: unknown }
scripts: Array<{
version_id: number
app: string
summary: string
description: string
terms: string
kind: string
language: string
content: string
schema: unknown
}>
}>
const BENCHMARK_HUB_SCRIPTS = [
...REAL_HUB_INTEGRATIONS.flatMap((integration) => integration.scripts),
{
version_id: 22235,
app: 'holded',
@@ -1112,6 +1165,87 @@ export async function main(discord_webhook: DiscordWebhook, message: string) {
message: { type: 'string' }
}
}
},
// Baremetrics carries no annotation script, on purpose: it stands for an
// integration whose conventions (an `apiKey` resource, bearer auth, the /v1
// base) are only learnable by reading a script that does something else.
{
version_id: 8995,
app: 'baremetrics',
summary: 'List Sources',
description: 'List all the sources attached to a Baremetrics account.',
terms: 'baremetrics sources list revenue metrics',
language: 'bunnative',
content: `//native
type Baremetrics = {
apiKey: string;
};
export async function main(resource: Baremetrics) {
const response = await fetch("https://api.baremetrics.com/v1/sources", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: \`Bearer \${resource.apiKey}\`,
},
});
if (!response.ok) {
throw new Error(\`\${response.status} \${await response.text()}\`);
}
return await response.json();
}
`,
schema: {
type: 'object',
required: ['resource'],
properties: {
resource: { type: 'object', format: 'resource-baremetrics' }
}
}
},
{
version_id: 8996,
app: 'baremetrics',
summary: 'Create Customer',
description: 'Create a customer on a Baremetrics source.',
terms: 'baremetrics customer create source',
language: 'bunnative',
content: `//native
type Baremetrics = {
apiKey: string;
};
export async function main(
resource: Baremetrics,
sourceId: string,
body: { oid: string; name?: string; email?: string },
) {
const response = await fetch(
\`https://api.baremetrics.com/v1/\${sourceId}/customers\`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: \`Bearer \${resource.apiKey}\`,
},
body: JSON.stringify(body),
},
);
if (!response.ok) {
throw new Error(\`\${response.status} \${await response.text()}\`);
}
return await response.json();
}
`,
schema: {
type: 'object',
required: ['resource', 'sourceId', 'body'],
properties: {
resource: { type: 'object', format: 'resource-baremetrics' },
sourceId: { type: 'string' },
body: { type: 'object' }
}
}
}
]
@@ -1121,20 +1255,26 @@ export async function main(discord_webhook: DiscordWebhook, message: string) {
* integration, or overlapping on three meaningful words. A looser bar answers
* "send a Slack message" with the Discord fixture, handing an unrelated case a
* plausible-looking wrong integration. */
function searchBenchmarkHubScripts(text: string) {
function searchBenchmarkHubScripts(text: string, app: string | null, kind: string | null) {
const tokens = new Set(
text
.toLowerCase()
.split(/[^a-z0-9]+/)
.filter((token) => token.length > 2)
)
return BENCHMARK_HUB_SCRIPTS.map((script) => {
const words = new Set(
`${script.app} ${script.summary} ${script.terms}`.toLowerCase().split(/[^a-z0-9]+/)
)
const score = [...tokens].filter((token) => words.has(token)).length
return { script, score, namesApp: tokens.has(script.app) }
})
// The real endpoint filters by app and kind before ranking, so honour both here —
// otherwise a narrowed search silently returns other integrations' scripts, and a
// trigger answers a search production would never have shown it to.
return BENCHMARK_HUB_SCRIPTS.filter(
(script) => (!app || script.app === app) && (!kind || (script.kind ?? 'script') === kind)
)
.map((script) => {
const words = new Set(
`${script.app} ${script.summary} ${script.terms}`.toLowerCase().split(/[^a-z0-9]+/)
)
const score = [...tokens].filter((token) => words.has(token)).length
return { script, score, namesApp: tokens.has(script.app) }
})
.filter((entry) => entry.namesApp || entry.score >= 3)
.sort((a, b) => b.score - a.score)
.map(({ script }, index) => ({
@@ -1142,12 +1282,104 @@ function searchBenchmarkHubScripts(text: string) {
id: script.version_id,
version_id: script.version_id,
summary: script.summary,
description: script.description ?? null,
app: script.app,
kind: 'script',
kind: script.kind ?? 'script',
score: 1 - index * 0.01
}))
}
/** Listing an integration is unranked and description-bearing, matching the hub's
* top-scripts endpoint — that asymmetry with the semantic search is the whole
* reason the chat browses by app when no script matches the task. */
function listBenchmarkHubScriptsByApp(app: string | null, kind: string | null) {
return BENCHMARK_HUB_SCRIPTS.filter(
(script) => (!app || script.app === app) && (!kind || (script.kind ?? 'script') === kind)
).map((script) => ({
id: script.version_id,
ask_id: script.version_id,
version_id: script.version_id,
summary: script.summary,
description: script.description ?? null,
app: script.app,
kind: script.kind ?? 'script',
views: 0,
votes: 0
}))
}
/** What `/integrations/hub/<app>/meta` serves. Carries the integration's conventions
* and none of the endpoints a case asks the model to write, so the tool shortens the
* path to that knowledge without supplying answers. */
/** The integrations a case writes against, none of which the content repo documents —
* the same majority shape as the live hub, where 18 of ~216 carry a meta.json. Their
* conventions live in their shipped scripts, which is what these cases exercise. */
const BENCHMARK_HUB_INTEGRATION_META: Record<
string,
{
display_name: string
description: string
docs_url: string
curated: boolean | null
meta: unknown
}
> = {
baremetrics: {
display_name: 'Baremetrics',
description: 'Subscription analytics for recurring-revenue businesses.',
docs_url: 'https://developers.baremetrics.com/reference',
curated: null,
meta: null
},
holded: {
display_name: 'Holded',
description: 'Invoicing, accounting and CRM for small businesses.',
docs_url: 'https://developers.holded.com/reference',
curated: null,
meta: null
}
}
/** Mirrors the hub's own derivation: hosts seen in the shipped scripts, whether they
* call the provider directly, and how many there are of each kind. */
function benchmarkDerivedFacts(app: string) {
const scripts = BENCHMARK_HUB_SCRIPTS.filter((script) => script.app === app)
const hosts = new Map<string, number>()
const languages: Record<string, number> = {}
for (const script of scripts) {
languages[script.language] = (languages[script.language] ?? 0) + 1
for (const match of script.content.matchAll(/https?:\/\/([a-zA-Z0-9._-]+)/g)) {
hosts.set(match[1], (hosts.get(match[1]) ?? 0) + 1)
}
}
return {
api_hosts: [...hosts.entries()]
.sort((a, b) => b[1] - a[1])
.map(([host, count]) => ({ host, count })),
style: 'fetch',
languages,
script_counts: {
total: scripts.length,
by_kind: scripts.reduce<Record<string, number>>((acc, script) => {
const kind = script.kind ?? 'script'
acc[kind] = (acc[kind] ?? 0) + 1
return acc
}, {})
},
top_scripts: scripts.map((script) => ({
path: `hub/${script.version_id}/${script.app}/${script.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
ask_id: script.version_id,
version_id: script.version_id,
summary: script.summary,
description: script.description ?? null,
kind: script.kind ?? 'script',
language: script.language,
views: 0,
votes: 0
}))
}
}
/** The hub keys a script by its version id; the app and slug segments that
* follow are descriptive, so match on the id exactly as the real hub does. */
function getBenchmarkHubScript(path: string) {
@@ -1190,6 +1422,9 @@ export function hasBenchmarkApiHandler(url: string): boolean {
return (
path === '/api/workers/list' ||
path === '/api/embeddings/query_hub_scripts' ||
path === '/api/scripts/hub/top' ||
path === '/api/integrations/hub/list' ||
BENCHMARK_INTEGRATION_META_PATH.test(path) ||
path.startsWith('/api/scripts/hub/get_full/') ||
BENCHMARK_AI_MODELS_PATH.test(path)
)
@@ -1213,8 +1448,81 @@ export function handleBenchmarkApiFetch(url: string, init?: RequestInit): Respon
return Response.json({ data: (seed?.models ?? []).map((id) => ({ id })) })
}
if (path === '/api/embeddings/query_hub_scripts') {
const text = new URLSearchParams(url.split('?')[1] ?? '').get('text') ?? ''
return Response.json(searchBenchmarkHubScripts(text))
const params = new URLSearchParams(url.split('?')[1] ?? '')
return Response.json(
searchBenchmarkHubScripts(params.get('text') ?? '', params.get('app'), params.get('kind'))
)
}
if (path === '/api/scripts/hub/top') {
const params = new URLSearchParams(url.split('?')[1] ?? '')
return Response.json({
asks: listBenchmarkHubScriptsByApp(params.get('app'), params.get('kind'))
})
}
const integrationMeta = BENCHMARK_INTEGRATION_META_PATH.exec(path)
if (integrationMeta) {
const app = decodeURIComponent(integrationMeta[1])
const real = REAL_HUB_INTEGRATIONS.find((integration) => integration.app === app)
if (real) {
return Response.json({
app,
display_name: real.display_name,
description: real.description,
docs_url: real.docs_url,
curated: real.curated,
metadata_source: real.meta ? 'curated' : 'derived',
meta: real.meta,
meta_updated_at: null,
derived: benchmarkDerivedFacts(app),
resource_types: [{ id: 1, ...real.resource_type }]
})
}
const entry = BENCHMARK_HUB_INTEGRATION_META[app]
if (!entry) {
return Response.json({ error: 'integration not found' }, { status: 404 })
}
return Response.json({
app,
display_name: entry.display_name,
description: entry.description,
docs_url: entry.docs_url,
curated: entry.curated,
metadata_source: entry.meta ? 'curated' : 'derived',
meta: entry.meta,
meta_updated_at: null,
derived: benchmarkDerivedFacts(app),
resource_types: [
{
id: 1,
name: app,
description: `${entry.display_name} credentials`,
schema: {
type: 'object',
required: ['apiKey'],
properties: {
apiKey: {
type: 'string',
description: `${entry.display_name} API key`
}
}
}
}
]
})
}
if (path === '/api/integrations/hub/list') {
const apps = [...new Set(BENCHMARK_HUB_SCRIPTS.map((script) => script.app))].sort()
// Read off both fixtures the metadata endpoint serves from, or the list would
// call an integration undocumented and then hand back its authored notes.
const documented = new Set([
...REAL_HUB_INTEGRATIONS.filter((integration) => integration.meta).map(
(integration) => integration.app
),
...Object.entries(BENCHMARK_HUB_INTEGRATION_META)
.filter(([, entry]) => entry.meta)
.map(([app]) => app)
])
return Response.json(apps.map((name) => ({ name, documented: documented.has(name) })))
}
if (path.startsWith('/api/scripts/hub/get_full/')) {
const script = getBenchmarkHubScript(path)
@@ -16,3 +16,21 @@ describe('benchmark API fetch handlers', () => {
expect(body[0]).toMatchObject({ worker: expect.any(String), worker_group: expect.any(String) })
})
})
// A benchmark whose list calls an integration undocumented while its metadata endpoint
// hands back authored notes teaches the model the flag means nothing.
describe('benchmark hub integration list', () => {
it('flags exactly the integrations whose metadata carries authored notes', async () => {
const listed = (await handleBenchmarkApiFetch('/api/integrations/hub/list').json()) as Array<{
name: string
documented: boolean
}>
expect(listed.length).toBeGreaterThan(0)
for (const { name, documented } of listed) {
const res = handleBenchmarkApiFetch(`/api/integrations/hub/${name}/meta`)
const authored = res.status === 200 && !!((await res.json()) as { meta?: unknown }).meta
expect(authored).toBe(documented)
}
})
})
@@ -1,10 +1,14 @@
import { expect, it, vi } from 'vitest'
// @ts-ignore - Node.js fs/promises
import { mkdir, writeFile } from 'fs/promises'
import { mkdir, readFile, writeFile } from 'fs/promises'
// @ts-ignore - Node.js path
import { dirname, resolve } from 'path'
// @ts-ignore - Node.js url
import { fileURLToPath } from 'url'
import { handleBenchmarkApiFetch, hasBenchmarkApiHandler } from './mockBackend'
const FRONTEND_DIR = fileURLToPath(new URL('../../../frontend/', import.meta.url))
// Some tools reach the backend by relative fetch('/api/...'), which has 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
@@ -24,9 +28,30 @@ globalThis.fetch = (async (input: unknown, init?: RequestInit) => {
if (typeof url === 'string' && hasBenchmarkApiHandler(url)) {
return handleBenchmarkApiFetch(url, init)
}
// The parsers behind inferArgs load their wasm from a vite `?url` path, which only a dev
// server serves. Unserved, every script schema infers as empty, and the tools then tell the
// model its arguments are undeclared, so it rewrites a correct script until it runs out of turns.
const wasmPath = typeof url === 'string' ? wasmFilePath(url) : undefined
if (wasmPath) {
return new Response(await readFile(wasmPath), {
headers: { 'Content-Type': 'application/wasm' }
})
}
return ORIGINAL_FETCH(input as Parameters<typeof fetch>[0], init)
}) as typeof fetch
function wasmFilePath(url: string): string | undefined {
const raw = url.split(/[?#]/)[0]
if (!raw.endsWith('.wasm')) return undefined
const pathname = decodeURIComponent(raw)
// Located past whatever base path the config prefixes.
const fs = pathname.indexOf('/@fs/')
if (fs !== -1) return pathname.slice(fs + '/@fs'.length)
const modules = pathname.indexOf('/node_modules/')
if (modules !== -1) return resolve(FRONTEND_DIR, `.${pathname.slice(modules)}`)
return undefined
}
vi.mock('monaco-editor', () => ({
editor: {},
languages: {},
@@ -640,6 +665,13 @@ benchmarkIt(
async () => {
const { resetBenchmarkMockBackend } = await import('./mockBackend')
resetBenchmarkMockBackend()
// The tools swallow inference failures, so a wasm the fetch stub stops serving would
// only show up as empty schemas the model chases, never as an error.
const { inferArgs } = await import('$lib/infer')
const { emptySchema } = await import('$lib/utils')
const probe = emptySchema()
await inferArgs('bun', 'export async function main(name: string) {}', probe)
expect(Object.keys(probe.properties)).toEqual(['name'])
const { runFrontendBenchmarkFromEnv } = await import('./benchmarkRunner')
try {
const payload = await runFrontendBenchmarkFromEnv()
+29 -91
View File
@@ -5,8 +5,8 @@
"": {
"name": "windmill-ai-evals",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.25",
"@anthropic-ai/sdk": "^0.39.0",
"@anthropic-ai/claude-agent-sdk": "^0.3.284",
"@anthropic-ai/sdk": "^0.129.0",
"commander": "^14.0.3",
"openai": "^6.9.1",
"yaml": "^2.8.3",
@@ -18,66 +18,44 @@
},
},
"packages": {
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.87", "", { "dependencies": { "@anthropic-ai/sdk": "^0.74.0", "@modelcontextprotocol/sdk": "^1.27.1" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.34.2", "@img/sharp-darwin-x64": "^0.34.2", "@img/sharp-linux-arm": "^0.34.2", "@img/sharp-linux-arm64": "^0.34.2", "@img/sharp-linux-x64": "^0.34.2", "@img/sharp-linuxmusl-arm64": "^0.34.2", "@img/sharp-linuxmusl-x64": "^0.34.2", "@img/sharp-win32-arm64": "^0.34.2", "@img/sharp-win32-x64": "^0.34.2" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-WWmgBPxPhBOvNT0ujI8vPTI2lK+w5YEkEZ/y1mH0EDkK/0kBnxVJNhCtG5vnueiAViwLoUOFn66pbkDiivijdA=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.3.284", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.284", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.284", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.284", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.284", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.284", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.284", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.284", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.284" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-NSoJwEq6nFSf8dtaacYx37QdGgqApI3eHFUlxclMLYi8irb6ZJwEaUnPnLUCyAyg9W/tMkgZC0GXWLRCc01I0w=="],
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.39.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg=="],
"@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.284", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gKY9MUjY83398uCiPLHsd87kyzu7agIM7ApqWpJkpSINep6hxx4rNoR8bUbNWg49Aoe/PW/DJkQByAQuNzR9rg=="],
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
"@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.284", "", { "os": "darwin", "cpu": "x64" }, "sha512-P+q6Z7sKeYz99uE7RJc4au1IK+4SiWe69pyHY3QXMnjZq8hSFU/5Mo8iSurLk9jRlihsMltV1rLA5naouYyCAQ=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.284", "", { "os": "linux", "cpu": "arm64" }, "sha512-LDpuYDaz+pCdG29iy1pw4P1D2YVtpZOb4rWOPxxSpg2fFyUeQ5WrfnuszOxhDWpNh1k1ekDiKm3A9OzGcrkdtA=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.284", "", { "os": "linux", "cpu": "arm64" }, "sha512-U3/uAv1TS8sMWsgWQSxYbdND5AaRXXpP5RxHU1oYC5ZizlZE8/xAVum9dj1QX3rVZJg8EzsqZveq/AkEv5ABfQ=="],
"@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.284", "", { "os": "linux", "cpu": "x64" }, "sha512-yGytBCCwJvWeg1FzFpgnLM9BOM2vKVExtv6pMJHMtF82J5yhKHcxodRaYlQVd/HzJJ2nruKb9EnrhRBZ//4yRw=="],
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.284", "", { "os": "linux", "cpu": "x64" }, "sha512-4x0Q8CFkCNbeENE7pRhuUmkwh/sQfTq1yWi4ZE56stve9pgoKzu6ZDa1SEtvK1YIooGE/qM5rVS8xFLE1gbQ0w=="],
"@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.284", "", { "os": "win32", "cpu": "arm64" }, "sha512-zOXkdPHhElyxyFJ6eY5R+YGUswqBDZ+ZHNaRmQGJ28RkQYgwG5imNy/nfFR+/CPGlMnH9ZzbGc2rKYApm1K4Tg=="],
"@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.284", "", { "os": "win32", "cpu": "x64" }, "sha512-VGaFRDCOPloj5IjvJTyy5JsSl9sE2HR3W+A6f/cjgh23/7Y5vK7/ka+1JQD+AkoGO9tbHqP2O6god3IBrLvOaw=="],
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.129.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1", "standardwebhooks": "^1.0.0" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-MH7LB20kNpGLUpPTe2OG5XvL/KhX8HUO9XyBaFjgFk6TLS4Xjt0VPIuMKywe7POKKPslX+QxSlVok9e+8kG5Nw=="],
"@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
"@hono/node-server": ["@hono/node-server@1.19.12", "", { "peerDependencies": { "hono": "^4" } }, "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
"@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="],
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
"@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
@@ -88,8 +66,6 @@
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
"content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
@@ -106,8 +82,6 @@
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
@@ -122,14 +96,10 @@
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
"eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
@@ -140,16 +110,12 @@
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
@@ -164,16 +130,12 @@
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
"humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
@@ -208,10 +170,6 @@
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
@@ -262,30 +220,24 @@
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"standardwebhooks": ["standardwebhooks@1.1.1", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "fast-sha256": "^1.3.0" } }, "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ=="],
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
@@ -295,19 +247,5 @@
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
"@anthropic-ai/claude-agent-sdk/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.74.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-srbJV7JKsc5cQ6eVuFzjZO7UR3xEPJqPamHFIe29bs38Ij2IripoAhC0S5NslNbaFUYqBKypmmpzMTpqfHEUDw=="],
"@types/node-fetch/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
"bun-types/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"@types/node-fetch/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"bun-types/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
}
}
-24
View File
@@ -11,12 +11,6 @@
forbiddenSkills:
- write-script-python3
- write-flow
orderedAssistantMentions:
- wmill generate-metadata
- wmill sync push
orderedProposedCommands:
- wmill generate-metadata
- wmill sync push
forbiddenExecutedCommands:
- ^wmill generate-metadata
- ^wmill sync push
@@ -38,12 +32,6 @@
- write-flow
forbiddenSkills:
- write-script-python3
orderedAssistantMentions:
- wmill generate-metadata
- wmill sync push
orderedProposedCommands:
- wmill generate-metadata
- wmill sync push
forbiddenExecutedCommands:
- ^wmill generate-metadata
- ^wmill sync push
@@ -65,12 +53,6 @@
forbiddenSkills:
- write-script-bun
- write-flow
orderedAssistantMentions:
- wmill generate-metadata
- wmill sync push
orderedProposedCommands:
- wmill generate-metadata
- wmill sync push
forbiddenExecutedCommands:
- ^wmill generate-metadata
- ^wmill sync push
@@ -114,12 +96,6 @@
- write-flow
requiredSkillsBeforeFirstMutation:
- write-flow
orderedAssistantMentions:
- wmill generate-metadata
- wmill sync push
orderedProposedCommands:
- wmill generate-metadata
- wmill sync push
forbiddenExecutedCommands:
- ^wmill generate-metadata
- ^wmill sync push
+171
View File
@@ -2312,6 +2312,105 @@
- email
skipJudge: true
# `List Pages` takes a cursor without ever following one, so where the next cursor
# comes from (`_links.next`) is stated in the authored metadata and in no script body.
# Paginating is therefore the one task where reading scripts is not an equal route.
- id: global-hub4-confluence-paginate-all-pages
prompt: |-
I need a script that returns every page in one of my Confluence spaces, not just the
first batch — it should keep fetching until there are no more.
Save it as a draft at `f/evals/global/confluence_all_pages` and leave it as an AI
draft; do not deploy it.
initial: ai_evals/fixtures/frontend/global/initial/user_admin_evals_folder.json
runtime:
maxTurns: 14
validate:
draftCountExactly: 1
requiredDrafts:
- type: script
path: f/evals/global/confluence_all_pages
valueIncludes:
- /wiki/
- Basic
toolExpect:
requiredToolsUsed:
- write_script
# Either route to the integration's conventions is fine; the checklist is where the
# difference between them has to show up.
requiredToolsAnyOf:
- - search_hub_scripts
- get_hub_integration
forbiddenToolsUsed:
- deploy_workspace_item
judgeChecklist:
- the script loops until the API stops returning a next link, rather than making a single request
- the next cursor is read from the response's `_links.next` or the Link header, not from an invented field such as `next_cursor`, `nextPageToken` or `offset`
- it authenticates with HTTP Basic built from the Confluence resource's email and API token rather than a bearer token
- the script takes a Confluence resource as an input parameter rather than loose credential strings
- the result stays an AI draft and is not deployed
# Unlike the Confluence case, both facts this needs — the {"result"} envelope and
# PATCH-not-PUT — are visible in ServiceNow's own shipped scripts. That makes it the
# control for whether the metadata earns its place when the scripts already answer.
- id: global-hub5-servicenow-assign-incident
prompt: |-
Write me a script that assigns an existing ServiceNow incident to a person, looking
that person up by their email address.
Save it as a draft at `f/evals/global/servicenow_assign_incident` and leave it as an
AI draft; do not deploy it.
initial: ai_evals/fixtures/frontend/global/initial/user_admin_evals_folder.json
runtime:
maxTurns: 14
validate:
draftCountExactly: 1
requiredDrafts:
- type: script
path: f/evals/global/servicenow_assign_incident
toolExpect:
requiredToolsUsed:
- write_script
requiredToolsAnyOf:
- - search_hub_scripts
- get_hub_integration
forbiddenToolsUsed:
- deploy_workspace_item
judgeChecklist:
- it looks the person up in the sys_user table by email rather than assuming a sys_id was supplied
- it reads records out of the `result` envelope the ServiceNow API wraps responses in
- it sets assigned_to to the user's sys_id rather than to the raw email address
- it updates the incident with a partial update (PATCH) rather than a full replace that would clear the fields it did not send
- the result stays an AI draft and is not deployed
# Outreach is JSON:API. Its content type and payload envelope are both stated in the
# metadata and visible in the shipped update scripts, so this is a second control.
- id: global-hub6-outreach-reschedule-task
prompt: |-
Write me a script that moves an existing Outreach task to a new due date.
Save it as a draft at `f/evals/global/outreach_reschedule_task` and leave it as an AI
draft; do not deploy it.
initial: ai_evals/fixtures/frontend/global/initial/user_admin_evals_folder.json
runtime:
maxTurns: 14
validate:
draftCountExactly: 1
requiredDrafts:
- type: script
path: f/evals/global/outreach_reschedule_task
toolExpect:
requiredToolsUsed:
- write_script
requiredToolsAnyOf:
- - search_hub_scripts
- get_hub_integration
forbiddenToolsUsed:
- deploy_workspace_item
judgeChecklist:
- the request sends the JSON:API content type application/vnd.api+json for its body
- the payload wraps the change as data.type and data.attributes rather than sending bare top-level fields
- it identifies the task by id inside the payload rather than only in the URL, and treats the id as a number
- it updates the existing task with PATCH rather than creating a new one
- the result stays an AI draft and is not deployed
# --- Windmill Hub reuse (search_hub_scripts + read_workspace_item on a hub/ path) ---
# Holded's API is obscure enough that a model writing from memory cannot reproduce
# its endpoint and `key` auth header — so the draft's fidelity to the published
@@ -2358,6 +2457,78 @@
- the document type, document id, and recipient emails are inputs to the script
- the result stays an AI draft and is not deployed
# Same task as global-hub1 with the hub never mentioned: this is what tells us the
# model reaches for the hub on its own rather than only when pointed at it.
- id: global-hub2-checks-hub-unprompted
prompt: |-
I want to email one of my Holded invoices to a customer from Windmill.
Save it as a draft script at `f/evals/global/holded_send_document` and leave it as
an AI draft; do not deploy it.
initial: ai_evals/fixtures/frontend/global/initial/user_admin_evals_folder.json
runtime:
maxTurns: 12
validate:
draftCountExactly: 1
requiredDrafts:
# No language assertion: the Holded script's body is `//native`, so a faithful
# fork lands on bun or bunnative depending on how literally it is copied.
- type: script
path: f/evals/global/holded_send_document
valueIncludes:
- api.holded.com/api/invoicing/v1/documents
- mailTemplateId
toolExpect:
requiredToolsUsed:
- search_hub_scripts
- write_script
forbiddenToolsUsed:
- deploy_workspace_item
judgeChecklist:
- the draft sends an existing Holded document by email rather than creating one
- the request targets Holded's document send endpoint, not an invented URL
- authentication uses Holded's own key header rather than a bearer token
- the result stays an AI draft and is not deployed
# The fixture hub carries two Baremetrics scripts, neither for annotations, so nothing
# does what is asked. Its conventions — an `apiKey` resource, bearer auth, the
# api.baremetrics.com/v1 base — are only knowable by reading one of those near-misses.
- id: global-hub3-integration-as-example
prompt: |-
Write me a script that adds an annotation to my Baremetrics account.
Save it as a draft at `f/evals/global/baremetrics_create_annotation` and leave it as
an AI draft; do not deploy it.
initial: ai_evals/fixtures/frontend/global/initial/user_admin_evals_folder.json
runtime:
maxTurns: 14
validate:
draftCountExactly: 1
requiredDrafts:
# No language assertion: matching the hub script's own dialect is the point,
# and Baremetrics' scripts are `//native`, so both bun and bunnative are right.
- type: script
path: f/evals/global/baremetrics_create_annotation
valueIncludes:
- api.baremetrics.com/v1
- apiKey
toolExpect:
requiredToolsUsed:
- search_hub_scripts
- write_script
# Reading a near-miss script and reading the integration's own metadata are two
# routes to the same conventions, so requiring either keeps the case about the
# outcome rather than about which route the model took.
requiredToolsAnyOf:
- - read_workspace_item
- get_hub_integration
forbiddenToolsUsed:
- deploy_workspace_item
judgeChecklist:
- the script takes a Baremetrics resource as an input parameter rather than a bare API key string
- the resource type it declares carries an apiKey field, matching how other Baremetrics hub scripts take credentials
- the request authenticates with a bearer token built from that resource
- the script posts an annotation rather than reading data back
- the result stays an AI draft and is not deployed
# The value of a secret variable is unreadable, so a metadata-only edit must leave
# it untouched: passing any `value` here means inventing one, which silently
# replaces the real secret at deploy.
+18
View File
@@ -108,6 +108,10 @@ async function main() {
"--record",
"append a compact summary line to ai_evals/history/<mode>.jsonl",
)
.option(
"--reasoning <effort>",
"reasoning effort for frontend modes (e.g. off, low, medium, high, max); default: the product's",
)
.option(
"--backend-validation <mode>",
`backend smoke validation (${BACKEND_VALIDATION_MODES.join(", ")})`,
@@ -126,6 +130,7 @@ async function main() {
executionOnly?: boolean;
record?: boolean;
backendValidation?: string;
reasoning?: string;
},
) => {
await handleRun({
@@ -140,6 +145,7 @@ async function main() {
executionOnly: options.executionOnly ?? false,
record: options.record ?? false,
backendValidation: options.backendValidation,
reasoning: options.reasoning,
});
},
);
@@ -190,6 +196,7 @@ async function handleRun(input: {
executionOnly: boolean;
record: boolean;
backendValidation?: string;
reasoning?: string;
}) {
if (input.record && input.caseIds.length > 0) {
throw new Error(
@@ -217,6 +224,13 @@ async function handleRun(input: {
"--backend-validation currently supports only flow and script modes",
);
}
if (input.reasoning) {
if (input.mode === "cli") {
throw new Error("--reasoning only applies to frontend modes");
}
// The frontend runtime runs in a child process, which inherits it.
process.env.WMILL_AI_EVAL_REASONING = input.reasoning;
}
if (input.mode !== "cli") {
await assertWindmillBackendReachable(resolveWindmillBackendSettings());
}
@@ -257,6 +271,10 @@ async function handleRun(input: {
backendValidation,
});
if (input.reasoning && result.runModel) {
result.runModel = `${result.runModel}@${input.reasoning}`;
}
const resolvedOutputPath =
models.length === 1
? resolveRunOutputPath(input.mode, input.outputPath)
+6 -8
View File
@@ -1,7 +1,7 @@
import Anthropic from "@anthropic-ai/sdk";
import type { EvalMode, JudgeResult } from "./types";
export const DEFAULT_JUDGE_MODEL = "claude-sonnet-4-6";
export const DEFAULT_JUDGE_MODEL = "claude-sonnet-5-5";
const JUDGE_TOOL_NAME = "submit_judgement";
@@ -29,6 +29,7 @@ export async function judgeOutput(input: {
const system = [
"You evaluate benchmark outputs for Windmill AI generation.",
`Always answer by calling the ${JUDGE_TOOL_NAME} tool.`,
"Deterministic checks already run separately. Focus on whether the final output satisfies the user request.",
"If expected state is provided, treat it as a valid example and reward semantically equivalent outputs.",
"If a checklist is provided, treat it as the explicit acceptance criteria for this case.",
@@ -69,8 +70,8 @@ export async function judgeOutput(input: {
try {
const response = await client.messages.create({
model,
max_tokens: 1024,
temperature: 0,
// The judge thinks by default, and thinking shares this budget with the verdict.
max_tokens: 16000,
system,
messages: [{ role: "user", content: user }],
tools: [
@@ -93,11 +94,8 @@ export async function judgeOutput(input: {
},
},
],
tool_choice: {
type: "tool",
name: JUDGE_TOOL_NAME,
disable_parallel_tool_use: true,
},
// Current models refuse a forced tool_choice ("tool"/"any").
tool_choice: { type: "auto", disable_parallel_tool_use: true },
});
const toolUseBlock = response.content.find(
+4
View File
@@ -34,6 +34,10 @@ describe("resolveEvalModel", () => {
it("supports DeepSeek aliases for frontend evals", () => {
expect(resolveEvalModel("flow", "deepseek").frontend).toEqual({
provider: "deepseek",
model: "deepseek-flash",
});
expect(resolveEvalModel("flow", "deepseek-v4-flash").frontend).toEqual({
provider: "deepseek",
model: "deepseek-v4-flash",
});
+81 -1
View File
@@ -78,6 +78,32 @@ export const EVAL_MODELS: EvalModelSpec[] = [
model: "opus",
},
},
{
id: "sonnet-5.5",
label: "Claude Sonnet 5.5",
aliases: ["sonnet-5.5", "claude-sonnet-5.5", "claude-sonnet-5-5"],
frontend: {
provider: "anthropic",
model: "claude-sonnet-5-5",
},
cli: {
provider: "anthropic",
model: "claude-sonnet-5-5",
},
},
{
id: "opus-5.5",
label: "Claude Opus 5.5",
aliases: ["opus-5.5", "claude-opus-5.5", "claude-opus-5-5"],
frontend: {
provider: "anthropic",
model: "claude-opus-5-5",
},
cli: {
provider: "anthropic",
model: "claude-opus-5-5",
},
},
{
id: "4o",
label: "GPT-4o",
@@ -96,6 +122,51 @@ export const EVAL_MODELS: EvalModelSpec[] = [
model: "gpt-5.5",
},
},
{
id: "gpt-5.6-sol",
label: "GPT-5.6 Sol",
aliases: ["gpt-5.6-sol"],
frontend: {
provider: "openai",
model: "gpt-5.6-sol",
},
},
{
id: "gpt-6-astra",
label: "GPT-6 Astra",
aliases: ["gpt-6-astra", "gpt-6"],
frontend: {
provider: "openai",
model: "gpt-6-astra",
},
},
{
id: "gpt-6-sol",
label: "GPT-6 Sol",
aliases: ["gpt-6-sol"],
frontend: {
provider: "openai",
model: "gpt-6-sol",
},
},
{
id: "gpt-6-luna",
label: "GPT-6 Luna",
aliases: ["gpt-6-luna"],
frontend: {
provider: "openai",
model: "gpt-6-luna",
},
},
{
id: "gemini-3.8-flash",
label: "Gemini 3.8 Flash",
aliases: ["gemini-3.8-flash"],
frontend: {
provider: "googleai",
model: "gemini-3.8-flash",
},
},
{
id: "gemini-3-flash-preview",
label: "Gemini 3 Flash Preview",
@@ -121,12 +192,21 @@ export const EVAL_MODELS: EvalModelSpec[] = [
{
id: "deepseek-v4-flash",
label: "DeepSeek V4 Flash",
aliases: ["deepseek", "deepseek-v4", "deepseek-v4-flash"],
aliases: ["deepseek-v4", "deepseek-v4-flash"],
frontend: {
provider: "deepseek",
model: "deepseek-v4-flash",
},
},
{
id: "deepseek-flash",
label: "DeepSeek V4.1 Flash",
aliases: ["deepseek", "deepseek-flash", "deepseek-v4.1-flash"],
frontend: {
provider: "deepseek",
model: "deepseek-flash",
},
},
{
id: "deepseek-v4-pro",
label: "DeepSeek V4 Pro",
@@ -0,0 +1,983 @@
{
"app": "confluence",
"display_name": "Confluence",
"description": "Atlassian's team workspace: pages, blog posts and spaces.",
"docs_url": "https://developer.atlassian.com/cloud/confluence/rest/v2/intro/",
"curated": true,
"meta": {
"api_docs": "https://developer.atlassian.com/cloud/confluence/rest/v2/intro/",
"shipped_surfaces": [
"pages",
"blogposts",
"spaces",
"search",
"users"
],
"validation": {
"status": "live_validated",
"method": "smoke test via `bun --env-file=.env.test -e` (+ `bun test` with the windmill-client fake for the trigger) against a free Confluence Cloud site (a test site) with an API token. All 13 actions + the trigger returned 2xx: full page lifecycle (create -> get -> update -> list -> delete), blog post create/update/delete, list_spaces, search_content (CQL), get_current_user, and the new_or_updated_page polling trigger.",
"sources": {
"pages": "HIGH \u2014 Pipedream actions + official v2 OpenAPI; create/get/update/list/delete all run live (2xx)",
"blogposts": "HIGH \u2014 Pipedream actions + official v2 OpenAPI; create/update/delete all run live (2xx)",
"spaces": "HIGH \u2014 Pipedream list-spaces + official v2 OpenAPI; list_spaces run live (2xx)",
"search": "HIGH \u2014 Pipedream search-content + official v1 REST; search_content (CQL) run live (2xx)",
"users": "HIGH \u2014 v1 /wiki/rest/api/user/current run live (2xx) with Basic auth",
"trigger": "HIGH \u2014 new_or_updated_page e2e-tested under bun test with the auto-fake; first run sets the watermark, a subsequent run emits a freshly created page"
},
"low_confidence_fields": []
},
"pagination": {
"pattern": "cursor",
"request_params": {
"cursor": "cursor",
"limit": "limit"
},
"response_fields": {
"next": "_links.next"
},
"default_limit": 25,
"notes": "v2 endpoints return an opaque cursor inside the `_links.next` relative URL (?cursor=...) and also in the Link response header. The v1 /search endpoint paginates the same way."
},
"gotchas": [
"Two API versions are in play: pages / blog posts / spaces use the v2 REST API (/wiki/api/v2); CQL search and current-user use the v1 REST API (/wiki/rest/api).",
"Auth is HTTP Basic with the Atlassian account email as the username and an API token (NOT the account password) as the password.",
"v2 query params are kebab-case (body-format, created-date, get-draft, root-level); v2 JSON request-body fields are camelCase (spaceId, parentId).",
"Updating a page or blog post requires version.number = currentVersion + 1; the update_* scripts fetch the current version and increment it automatically.",
"spaceId / parentId in v2 request bodies are numeric IDs passed as strings, not space keys.",
"DELETE returns 204 with an empty body and trashes by default; pass purge=true to permanently remove an item that is already in the trash."
],
"enums": {
"page_status": [
"current",
"archived",
"deleted",
"trashed",
"draft",
"historical"
],
"body_representation": [
"storage",
"atlas_doc_format"
],
"body_format_read": [
"storage",
"atlas_doc_format",
"view",
"export_view",
"anonymous_export_view",
"styled_view",
"editor"
],
"space_type": [
"global",
"collaboration",
"knowledge_base",
"personal"
],
"page_sort": [
"id",
"-id",
"created-date",
"-created-date",
"modified-date",
"-modified-date",
"title",
"-title"
]
},
"errors": {
"401": "Authentication failed \u2014 verify the email + API token pair and that the token has not been revoked.",
"404": "Resource not found, or the authenticating account lacks permission to view it (Confluence often returns 404 rather than 403 for content the user cannot access)."
}
},
"resource_type": {
"name": "confluence",
"description": "Confluence Cloud credentials. Authenticate with an Atlassian account email and an API token (HTTP Basic auth) against your site base URL.",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"apiToken": {
"description": "API token for your Atlassian account. Create one at https://id.atlassian.com/manage-profile/security/api-tokens",
"type": "string"
},
"baseUrl": {
"description": "Base URL of your Confluence Cloud site, e.g. https://your-domain.atlassian.net (no trailing slash, no /wiki).",
"type": "string"
},
"email": {
"description": "Email of the Atlassian account used to authenticate.",
"type": "string"
}
},
"required": [
"email",
"apiToken",
"baseUrl"
],
"type": "object"
}
},
"scripts": [
{
"version_id": 28765,
"app": "confluence",
"summary": "Create Blog Post",
"description": "Create a new blog post in a space.",
"terms": "confluence create blog post create a new blog post in a space.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * Create Blog Post\n * Create a new blog post in a space. The body is supplied as a content string in the chosen representation (default \"storage\", i.e. Confluence storage HTML).\n */\nexport async function main(\n auth: RT.Confluence,\n space_id: string,\n title: string,\n body: string,\n status: \"current\" | \"draft\" | undefined,\n representation: \"storage\" | \"atlas_doc_format\" | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const url = new URL(`${base}/wiki/api/v2/blogposts`)\n\n const payload = {\n spaceId: space_id,\n status: status ?? \"current\",\n title,\n body: {\n representation: representation ?? \"storage\",\n value: body,\n },\n }\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"body": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"representation": {
"default": null,
"description": "",
"enum": [
"storage",
"atlas_doc_format"
],
"originalType": "enum",
"type": "string"
},
"space_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"status": {
"default": null,
"description": "",
"enum": [
"current",
"draft"
],
"originalType": "enum",
"type": "string"
},
"title": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth",
"space_id",
"title",
"body"
],
"type": "object"
}
},
{
"version_id": 28766,
"app": "confluence",
"summary": "Create Page",
"description": "Create a new page in a space.",
"terms": "confluence create page create a new page in a space.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * Create Page\n * Create a new page in a space. The body is supplied as a content string in the chosen representation (default \"storage\", i.e. Confluence storage HTML).\n */\nexport async function main(\n auth: RT.Confluence,\n space_id: string,\n title: string,\n body: string,\n status: \"current\" | \"draft\" | undefined,\n parent_id: string | undefined,\n representation: \"storage\" | \"atlas_doc_format\" | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const url = new URL(`${base}/wiki/api/v2/pages`)\n\n const payload = {\n spaceId: space_id,\n status: status ?? \"current\",\n title,\n parentId: parent_id,\n body: {\n representation: representation ?? \"storage\",\n value: body,\n },\n }\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"body": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"parent_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"representation": {
"default": null,
"description": "",
"enum": [
"storage",
"atlas_doc_format"
],
"originalType": "enum",
"type": "string"
},
"space_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"status": {
"default": null,
"description": "",
"enum": [
"current",
"draft"
],
"originalType": "enum",
"type": "string"
},
"title": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth",
"space_id",
"title",
"body"
],
"type": "object"
}
},
{
"version_id": 28767,
"app": "confluence",
"summary": "Delete Blog Post",
"description": "Delete a blog post by its ID (moved to trash by default; purge to permanently remove).",
"terms": "confluence delete blog post delete a blog post by its id (moved to trash by default; purge to permanently remove).",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * Delete Blog Post\n * Delete a blog post by its ID. By default the post is moved to the trash; set purge to permanently remove an already-trashed post.\n */\nexport async function main(\n auth: RT.Confluence,\n blog_post_id: string,\n purge: boolean | undefined,\n draft: boolean | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const url = new URL(`${base}/wiki/api/v2/blogposts/${blog_post_id}`)\n if (purge !== undefined) url.searchParams.append(\"purge\", String(purge))\n if (draft !== undefined) url.searchParams.append(\"draft\", String(draft))\n\n const response = await fetch(url, {\n method: \"DELETE\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n if (response.status === 204) return { success: true }\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"blog_post_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"draft": {
"default": null,
"description": "",
"type": "boolean"
},
"purge": {
"default": null,
"description": "",
"type": "boolean"
}
},
"required": [
"auth",
"blog_post_id"
],
"type": "object"
}
},
{
"version_id": 28768,
"app": "confluence",
"summary": "Delete Page",
"description": "Delete a page by its ID (moved to trash by default; purge to permanently remove).",
"terms": "confluence delete page delete a page by its id (moved to trash by default; purge to permanently remove).",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * Delete Page\n * Delete a page by its ID. By default the page is moved to the trash; set purge to permanently remove an already-trashed page.\n */\nexport async function main(\n auth: RT.Confluence,\n page_id: string,\n purge: boolean | undefined,\n draft: boolean | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const url = new URL(`${base}/wiki/api/v2/pages/${page_id}`)\n if (purge !== undefined) url.searchParams.append(\"purge\", String(purge))\n if (draft !== undefined) url.searchParams.append(\"draft\", String(draft))\n\n const response = await fetch(url, {\n method: \"DELETE\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n if (response.status === 204) return { success: true }\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"draft": {
"default": null,
"description": "",
"type": "boolean"
},
"page_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"purge": {
"default": null,
"description": "",
"type": "boolean"
}
},
"required": [
"auth",
"page_id"
],
"type": "object"
}
},
{
"version_id": 28769,
"app": "confluence",
"summary": "Get Current User",
"description": "Returns the profile of the user that the API token authenticates as.",
"terms": "confluence get current user returns the profile of the user that the api token authenticates as.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * Get Current User\n * Returns the profile of the user that the API token authenticates as (account ID, display name, email).\n */\nexport async function main(auth: RT.Confluence) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const url = new URL(`${base}/wiki/rest/api/user/current`)\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
}
},
"required": [
"auth"
],
"type": "object"
}
},
{
"version_id": 28770,
"app": "confluence",
"summary": "Get Page by ID",
"description": "Retrieve a single page by its ID, optionally returning its body in a specific format or a previous version.",
"terms": "confluence get page by id retrieve a single page by its id, optionally returning its body in a specific format or a previous version.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * Get Page by ID\n * Retrieve a single page by its ID, optionally returning its body in a specific format or a previous version.\n */\nexport async function main(\n auth: RT.Confluence,\n page_id: string,\n body_format:\n | \"storage\"\n | \"atlas_doc_format\"\n | \"view\"\n | \"export_view\"\n | \"anonymous_export_view\"\n | \"styled_view\"\n | \"editor\"\n | undefined,\n version: number | undefined,\n get_draft: boolean | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const url = new URL(`${base}/wiki/api/v2/pages/${page_id}`)\n if (body_format !== undefined)\n url.searchParams.append(\"body-format\", body_format)\n if (version !== undefined) url.searchParams.append(\"version\", String(version))\n if (get_draft !== undefined)\n url.searchParams.append(\"get-draft\", String(get_draft))\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"body_format": {
"default": null,
"description": "",
"enum": [
"storage",
"atlas_doc_format",
"view",
"export_view",
"anonymous_export_view",
"styled_view",
"editor"
],
"originalType": "enum",
"type": "string"
},
"get_draft": {
"default": null,
"description": "",
"type": "boolean"
},
"page_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"version": {
"default": null,
"description": "",
"type": "number"
}
},
"required": [
"auth",
"page_id"
],
"type": "object"
}
},
{
"version_id": 28771,
"app": "confluence",
"summary": "Get Pages in Space",
"description": "Retrieve a paginated list of pages within a given space.",
"terms": "confluence get pages in space retrieve a paginated list of pages within a given space.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * Get Pages in Space\n * Retrieve a paginated list of pages within a given space.\n */\nexport async function main(\n auth: RT.Confluence,\n space_id: string,\n status: (\"current\" | \"archived\" | \"deleted\" | \"trashed\")[] | undefined,\n title: string | undefined,\n sort:\n | \"id\"\n | \"-id\"\n | \"created-date\"\n | \"-created-date\"\n | \"modified-date\"\n | \"-modified-date\"\n | \"title\"\n | \"-title\"\n | undefined,\n body_format: \"storage\" | \"atlas_doc_format\" | undefined,\n cursor: string | undefined,\n limit: number | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const url = new URL(`${base}/wiki/api/v2/spaces/${space_id}/pages`)\n for (const s of status ?? []) url.searchParams.append(\"status\", s)\n if (title !== undefined && title !== \"\")\n url.searchParams.append(\"title\", title)\n if (sort !== undefined) url.searchParams.append(\"sort\", sort)\n if (body_format !== undefined)\n url.searchParams.append(\"body-format\", body_format)\n if (cursor !== undefined && cursor !== \"\")\n url.searchParams.append(\"cursor\", cursor)\n if (limit !== undefined) url.searchParams.append(\"limit\", String(limit))\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"body_format": {
"default": null,
"description": "",
"enum": [
"storage",
"atlas_doc_format"
],
"originalType": "enum",
"type": "string"
},
"cursor": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"limit": {
"default": null,
"description": "",
"type": "number"
},
"sort": {
"default": null,
"description": "",
"enum": [
"id",
"-id",
"created-date",
"-created-date",
"modified-date",
"-modified-date",
"title",
"-title"
],
"originalType": "enum",
"type": "string"
},
"space_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"status": {
"default": null,
"description": "",
"items": {
"enum": [
"current",
"archived",
"deleted",
"trashed"
],
"type": "string"
},
"originalType": "enum[]",
"type": "array"
},
"title": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth",
"space_id"
],
"type": "object"
}
},
{
"version_id": 28772,
"app": "confluence",
"summary": "List Blog Posts",
"description": "Retrieve a paginated list of blog posts on the site, with optional filtering and sorting.",
"terms": "confluence list blog posts retrieve a paginated list of blog posts on the site, with optional filtering and sorting.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * List Blog Posts\n * Retrieve a paginated list of blog posts on the site, with optional filtering and sorting.\n */\nexport async function main(\n auth: RT.Confluence,\n status: (\"current\" | \"archived\" | \"deleted\" | \"trashed\")[] | undefined,\n title: string | undefined,\n sort:\n | \"id\"\n | \"-id\"\n | \"created-date\"\n | \"-created-date\"\n | \"modified-date\"\n | \"-modified-date\"\n | undefined,\n body_format: \"storage\" | \"atlas_doc_format\" | undefined,\n cursor: string | undefined,\n limit: number | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const url = new URL(`${base}/wiki/api/v2/blogposts`)\n for (const s of status ?? []) url.searchParams.append(\"status\", s)\n if (title !== undefined && title !== \"\")\n url.searchParams.append(\"title\", title)\n if (sort !== undefined) url.searchParams.append(\"sort\", sort)\n if (body_format !== undefined)\n url.searchParams.append(\"body-format\", body_format)\n if (cursor !== undefined && cursor !== \"\")\n url.searchParams.append(\"cursor\", cursor)\n if (limit !== undefined) url.searchParams.append(\"limit\", String(limit))\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"body_format": {
"default": null,
"description": "",
"enum": [
"storage",
"atlas_doc_format"
],
"originalType": "enum",
"type": "string"
},
"cursor": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"limit": {
"default": null,
"description": "",
"type": "number"
},
"sort": {
"default": null,
"description": "",
"enum": [
"id",
"-id",
"created-date",
"-created-date",
"modified-date",
"-modified-date"
],
"originalType": "enum",
"type": "string"
},
"status": {
"default": null,
"description": "",
"items": {
"enum": [
"current",
"archived",
"deleted",
"trashed"
],
"type": "string"
},
"originalType": "enum[]",
"type": "array"
},
"title": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth"
],
"type": "object"
}
},
{
"version_id": 28773,
"app": "confluence",
"summary": "List Pages",
"description": "Retrieve a paginated list of pages on the site, with optional filtering and sorting.",
"terms": "confluence list pages retrieve a paginated list of pages on the site, with optional filtering and sorting.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * List Pages\n * Retrieve a paginated list of pages on the site, with optional filtering and sorting.\n */\nexport async function main(\n auth: RT.Confluence,\n status: (\"current\" | \"archived\" | \"deleted\" | \"trashed\")[] | undefined,\n title: string | undefined,\n sort:\n | \"id\"\n | \"-id\"\n | \"created-date\"\n | \"-created-date\"\n | \"modified-date\"\n | \"-modified-date\"\n | \"title\"\n | \"-title\"\n | undefined,\n body_format: \"storage\" | \"atlas_doc_format\" | undefined,\n cursor: string | undefined,\n limit: number | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const url = new URL(`${base}/wiki/api/v2/pages`)\n for (const s of status ?? []) url.searchParams.append(\"status\", s)\n if (title !== undefined && title !== \"\")\n url.searchParams.append(\"title\", title)\n if (sort !== undefined) url.searchParams.append(\"sort\", sort)\n if (body_format !== undefined)\n url.searchParams.append(\"body-format\", body_format)\n if (cursor !== undefined && cursor !== \"\")\n url.searchParams.append(\"cursor\", cursor)\n if (limit !== undefined) url.searchParams.append(\"limit\", String(limit))\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"body_format": {
"default": null,
"description": "",
"enum": [
"storage",
"atlas_doc_format"
],
"originalType": "enum",
"type": "string"
},
"cursor": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"limit": {
"default": null,
"description": "",
"type": "number"
},
"sort": {
"default": null,
"description": "",
"enum": [
"id",
"-id",
"created-date",
"-created-date",
"modified-date",
"-modified-date",
"title",
"-title"
],
"originalType": "enum",
"type": "string"
},
"status": {
"default": null,
"description": "",
"items": {
"enum": [
"current",
"archived",
"deleted",
"trashed"
],
"type": "string"
},
"originalType": "enum[]",
"type": "array"
},
"title": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth"
],
"type": "object"
}
},
{
"version_id": 28774,
"app": "confluence",
"summary": "List Spaces",
"description": "Retrieve a paginated list of spaces on the site, with optional filtering by key, type, and status.",
"terms": "confluence list spaces retrieve a paginated list of spaces on the site, with optional filtering by key, type, and status.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * List Spaces\n * Retrieve a paginated list of spaces on the site, with optional filtering by key, type, and status.\n */\nexport async function main(\n auth: RT.Confluence,\n keys: string[] | undefined,\n type: \"global\" | \"collaboration\" | \"knowledge_base\" | \"personal\" | undefined,\n status: \"current\" | \"archived\" | undefined,\n sort: \"id\" | \"-id\" | \"key\" | \"-key\" | \"name\" | \"-name\" | undefined,\n cursor: string | undefined,\n limit: number | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const url = new URL(`${base}/wiki/api/v2/spaces`)\n if (keys !== undefined && keys.length > 0)\n url.searchParams.append(\"keys\", keys.join(\",\"))\n if (type !== undefined) url.searchParams.append(\"type\", type)\n if (status !== undefined) url.searchParams.append(\"status\", status)\n if (sort !== undefined) url.searchParams.append(\"sort\", sort)\n if (cursor !== undefined && cursor !== \"\")\n url.searchParams.append(\"cursor\", cursor)\n if (limit !== undefined) url.searchParams.append(\"limit\", String(limit))\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"cursor": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"keys": {
"default": null,
"description": "",
"items": {
"type": "string"
},
"originalType": "string[]",
"type": "array"
},
"limit": {
"default": null,
"description": "",
"type": "number"
},
"sort": {
"default": null,
"description": "",
"enum": [
"id",
"-id",
"key",
"-key",
"name",
"-name"
],
"originalType": "enum",
"type": "string"
},
"status": {
"default": null,
"description": "",
"enum": [
"current",
"archived"
],
"originalType": "enum",
"type": "string"
},
"type": {
"default": null,
"description": "",
"enum": [
"global",
"collaboration",
"knowledge_base",
"personal"
],
"originalType": "enum",
"type": "string"
}
},
"required": [
"auth"
],
"type": "object"
}
},
{
"version_id": 28775,
"app": "confluence",
"summary": "Search Content (CQL)",
"description": "Search for content using the Confluence Query Language (CQL).",
"terms": "confluence search content (cql) search for content using the confluence query language (cql).",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * Search Content (CQL)\n * Search for content using the Confluence Query Language (CQL), e.g. `type=page AND space=ENG AND text ~ \"release notes\"`.\n */\nexport async function main(\n auth: RT.Confluence,\n cql: string,\n cursor: string | undefined,\n limit: number | undefined,\n expand: string | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const url = new URL(`${base}/wiki/rest/api/search`)\n url.searchParams.append(\"cql\", cql)\n if (cursor !== undefined && cursor !== \"\")\n url.searchParams.append(\"cursor\", cursor)\n if (limit !== undefined) url.searchParams.append(\"limit\", String(limit))\n if (expand !== undefined && expand !== \"\")\n url.searchParams.append(\"expand\", expand)\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"cql": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"cursor": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"expand": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"limit": {
"default": null,
"description": "",
"type": "number"
}
},
"required": [
"auth",
"cql"
],
"type": "object"
}
},
{
"version_id": 28776,
"app": "confluence",
"summary": "Update Blog Post",
"description": "Update a blog post's title, body, or status. The current version is fetched automatically and incremented.",
"terms": "confluence update blog post update a blog post's title, body, or status. the current version is fetched automatically and incremented.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * Update Blog Post\n * Update a blog post's title, body, or status. The current version is fetched automatically and incremented; omitted fields keep their existing values.\n */\nexport async function main(\n auth: RT.Confluence,\n blog_post_id: string,\n title: string | undefined,\n body: string | undefined,\n status: \"current\" | \"draft\" | \"archived\" | undefined,\n representation: \"storage\" | \"atlas_doc_format\" | undefined,\n version_message: string | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const repr = representation ?? \"storage\"\n const authHeader = \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`)\n\n // Fetch the current blog post to obtain the version number and any fields not being changed.\n const currentRes = await fetch(\n `${base}/wiki/api/v2/blogposts/${blog_post_id}?body-format=${repr}`,\n {\n method: \"GET\",\n headers: { Authorization: authHeader, Accept: \"application/json\" },\n }\n )\n if (!currentRes.ok) {\n throw new Error(`${currentRes.status} ${await currentRes.text()}`)\n }\n const current = (await currentRes.json()) as {\n title: string\n status: string\n version: { number: number }\n body?: { [key: string]: { value?: string } }\n }\n\n const payload = {\n id: blog_post_id,\n status: status ?? current.status,\n title: title ?? current.title,\n body: {\n representation: repr,\n value: body ?? current.body?.[repr]?.value ?? \"\",\n },\n version: {\n number: current.version.number + 1,\n message: version_message,\n },\n }\n\n const response = await fetch(\n `${base}/wiki/api/v2/blogposts/${blog_post_id}`,\n {\n method: \"PUT\",\n headers: {\n Authorization: authHeader,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(payload),\n }\n )\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"blog_post_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"body": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"representation": {
"default": null,
"description": "",
"enum": [
"storage",
"atlas_doc_format"
],
"originalType": "enum",
"type": "string"
},
"status": {
"default": null,
"description": "",
"enum": [
"current",
"draft",
"archived"
],
"originalType": "enum",
"type": "string"
},
"title": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"version_message": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth",
"blog_post_id"
],
"type": "object"
}
},
{
"version_id": 28777,
"app": "confluence",
"summary": "Update Page",
"description": "Update a page's title, body, or status. The current version is fetched automatically and incremented.",
"terms": "confluence update page update a page's title, body, or status. the current version is fetched automatically and incremented.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\n/**\n * Update Page\n * Update a page's title, body, or status. The current version is fetched automatically and incremented; omitted fields keep their existing values.\n */\nexport async function main(\n auth: RT.Confluence,\n page_id: string,\n title: string | undefined,\n body: string | undefined,\n status: \"current\" | \"draft\" | \"archived\" | undefined,\n representation: \"storage\" | \"atlas_doc_format\" | undefined,\n version_message: string | undefined\n) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const repr = representation ?? \"storage\"\n const authHeader = \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`)\n\n // Fetch the current page to obtain the version number and any fields not being changed.\n const currentRes = await fetch(\n `${base}/wiki/api/v2/pages/${page_id}?body-format=${repr}`,\n {\n method: \"GET\",\n headers: { Authorization: authHeader, Accept: \"application/json\" },\n }\n )\n if (!currentRes.ok) {\n throw new Error(`${currentRes.status} ${await currentRes.text()}`)\n }\n const current = (await currentRes.json()) as {\n title: string\n status: string\n version: { number: number }\n body?: { [key: string]: { value?: string } }\n }\n\n const payload = {\n id: page_id,\n status: status ?? current.status,\n title: title ?? current.title,\n body: {\n representation: repr,\n value: body ?? current.body?.[repr]?.value ?? \"\",\n },\n version: {\n number: current.version.number + 1,\n message: version_message,\n },\n }\n\n const response = await fetch(`${base}/wiki/api/v2/pages/${page_id}`, {\n method: \"PUT\",\n headers: {\n Authorization: authHeader,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"body": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"page_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"representation": {
"default": null,
"description": "",
"enum": [
"storage",
"atlas_doc_format"
],
"originalType": "enum",
"type": "string"
},
"status": {
"default": null,
"description": "",
"enum": [
"current",
"draft",
"archived"
],
"originalType": "enum",
"type": "string"
},
"title": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"version_message": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth",
"page_id"
],
"type": "object"
}
},
{
"version_id": 28778,
"app": "confluence",
"summary": "New or Updated Page",
"description": "Emits pages created or updated since the last poll, tracked via Windmill state. Optionally restrict to a single space.",
"terms": "confluence new or updated page emits pages created or updated since the last poll, tracked via windmill state. optionally restrict to a single space.",
"kind": "trigger",
"language": "bunnative",
"content": "//native\n\nimport * as wmill from \"windmill-client\"\n\n/**\n * New or Updated Page\n * Emits pages created or updated since the last poll, tracked via Windmill state. Optionally restrict to a single space.\n */\nexport async function main(auth: RT.Confluence, space_id: string | undefined) {\n const base = auth.baseUrl.replace(/\\/$/, \"\")\n const lastChecked: number = (await wmill.getState()) ?? 0\n\n const path = space_id\n ? `/wiki/api/v2/spaces/${space_id}/pages`\n : `/wiki/api/v2/pages`\n const url = new URL(`${base}${path}`)\n url.searchParams.append(\"sort\", \"-modified-date\")\n url.searchParams.append(\"limit\", \"50\")\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: \"Basic \" + btoa(`${auth.email}:${auth.apiToken}`),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n const data = (await response.json()) as {\n results?: { version?: { createdAt?: string } }[]\n }\n const pages = data.results ?? []\n\n const ts = (p: { version?: { createdAt?: string } }) =>\n p.version?.createdAt ? Date.parse(p.version.createdAt) : 0\n const maxTs = pages.reduce((m, p) => Math.max(m, ts(p)), 0)\n\n // First run: set the watermark to the newest page seen and don't emit a backlog.\n if (!lastChecked) {\n if (maxTs) await wmill.setState(maxTs)\n return []\n }\n\n const fresh = pages\n .filter((p) => ts(p) > lastChecked)\n .sort((a, b) => ts(a) - ts(b))\n\n if (maxTs > lastChecked) {\n await wmill.setState(maxTs)\n }\n\n return fresh\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-confluence",
"type": "object"
},
"space_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth"
],
"type": "object"
}
}
]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,672 @@
{
"app": "servicenow",
"display_name": "ServiceNow",
"description": "IT service management: incidents, records and attachments.",
"docs_url": "https://www.servicenow.com/docs/bundle/zurich-api-reference/page/integrate/inbound-rest/concept/c_RESTAPI.html",
"curated": true,
"meta": {
"api_docs": "https://www.servicenow.com/docs/bundle/zurich-api-reference/page/integrate/inbound-rest/concept/c_RESTAPI.html",
"shipped_surfaces": [
"table",
"stats",
"attachment",
"import_set",
"user"
],
"validation": {
"status": "live_validated",
"method": "All surfaces run live against a ServiceNow PDI (a developer instance, Zurich) on 2026-06-04 with Basic auth: Table CRUD (create / get / update PATCH+PUT / delete on incident, delete verified by 404), list_records (encoded query + fields + pagination), aggregate (Stats group-by count), get_current_user, list_tables, and the table/fields/group_by dynselect resolvers. Attachment upload (binary /file) -> list -> download -> delete round-trip with the downloaded bytes byte-for-byte matching the upload. insert_import_set + get_import_set_result against imp_user (transform status surfaced). Trigger e2e-tested via bun test with the windmill-client fake. All created records cleaned up.",
"sources": {
"table_crud": "HIGH - run live (create/get/update PATCH & PUT/delete on incident)",
"aggregate_stats": "HIGH - run live (/api/now/stats group-by count on incident)",
"attachment": "HIGH - run live (binary /file upload -> list -> download -> delete round-trip; downloaded bytes verified equal)",
"import_set": "HIGH - run live (POST /api/now/import/imp_user + GET result; top-level {import_set,staging_table,result} envelope, transform status surfaced)",
"user": "HIGH - run live (/api/now/ui/user/current_user -> admin sys_user)",
"trigger": "HIGH - e2e-tested live via bun test (first run sets watermark + no backlog, second run emits the new record)",
"dropdowns": "HIGH - run live (sys_db_object table list; sys_dictionary super_class inheritance walk confirmed - incident resolves 106 fields incl. task-inherited short_description/priority/state)"
},
"low_confidence_fields": [
"trigger datetime comparison assumes the integration user's timezone is UTC/GMT (validated with the PDI admin user, whose TZ is UTC); a non-UTC integration user could shift the sys_updated_on> watermark boundary by the offset"
]
},
"pagination": {
"pattern": "offset",
"request_params": {
"offset": "sysparm_offset",
"limit": "sysparm_limit"
},
"response_fields": {
"next": "Link (response header, rel=\"next\")",
"total": "X-Total-Count (response header)"
},
"default_limit": 10000,
"notes": "Table API pages with sysparm_offset + sysparm_limit. Each page response carries a Link header (RFC 5988) with rel=\"first\"/\"prev\"/\"next\"/\"last\" full URLs, plus X-Total-Count with the total match count. Add sysparm_no_count=true to skip the count on huge tables. The list_records action exposes limit+offset and returns one page; iterate offset until fewer than limit rows return."
},
"gotchas": [
"Base path is {instance_url}/api/now; instance_url lives on the resource. Auth is Basic (username:password) OR Bearer token \u2014 scripts send Bearer when auth.token is set, otherwise Basic. Both hit identical paths.",
"Table/Attachment/Stats reads/writes return a {\"result\": ...} envelope: list/stats -> result is an array, get/create/update -> result is an object. EXCEPTION: the Import Set API (POST /api/now/import/{staging}, GET .../{sys_id}) returns a TOP-LEVEL object {import_set, staging_table, result:[...]} with NO outer 'result' wrapper. Scripts return the raw JSON (envelope intact).",
"Datetimes are 'YYYY-MM-DD HH:MM:SS' in UTC with NO trailing 'Z'. Used directly in encoded queries, e.g. sys_updated_on>2026-01-01 00:00:00.",
"sysparm_query is an ENCODED query string: '^' = AND clause separator, '^OR' = OR, operators =, !=, LIKE, >=, <=, IN, STARTSWITH, ORDERBY, ORDERBYDESC. A literal '^' inside a value must be doubled to '^^'. Example: active=true^priority=1^ORDERBYDESCsys_updated_on.",
"System fields sys_id, sys_created_on, sys_updated_on, sys_created_by are auto-managed; do not send them on create/update.",
"DELETE (record and attachment) returns 204 No Content with an empty body \u2014 guard with `if (response.status === 204) return { success: true }` before response.json().",
"Update: PATCH partial-updates; PUT replaces the whole record (clears omitted fields). update_record sends PUT when replace=true, PATCH otherwise.",
"Attachment upload uses POST /api/now/attachment/file with table_name, table_sys_id, file_name in the QUERY STRING and the raw file bytes as the body (Content-Type = the file's MIME type), NOT multipart. Returns 201. Download is GET /api/now/attachment/{sys_id}/file (raw bytes).",
"Import Set transform runs SYNCHRONOUSLY. A result[].status of 'error' is an application-level failure even on HTTP 200/201 \u2014 inspect result[].status / error_message, don't trust the HTTP code alone.",
"Table-name DROPDOWN comes from GET /api/now/table/sys_db_object?sysparm_fields=name,label (use the 'name' as the value). A table's COLUMNS come from sys_dictionary \u2014 but sys_dictionary stores each field once, under the table where it is DEFINED, so name=<table> returns ONLY that table's own fields. Extended tables (incident/change_request/sc_task all extend task) inherit columns like short_description/priority/assigned_to/state that live on the parent. To get the full column set, walk sys_db_object.super_class up the hierarchy (GET sys_db_object?sysparm_query=name=<t>&sysparm_fields=super_class.name, repeat) and query GET sys_dictionary?sysparm_query=nameIN<table>,<ancestors>^elementISNOTEMPTY&sysparm_fields=element,column_label (elementISNOTEMPTY drops the collection/meta row).",
"Table API enforces row- and field-level ACLs: a 200 can omit fields the user can't read; inaccessible tables/ops return 403."
],
"enums": {
"sysparm_display_value": [
"true",
"false",
"all"
],
"sysparm_view": [
"desktop",
"mobile",
"both"
],
"import_set_result_status": [
"inserted",
"updated",
"ignored",
"error"
],
"incident_state_note": "incident.state and priority/urgency/impact are instance-configurable; defaults: state 1=New,2=In Progress,3=On Hold,6=Resolved,7=Closed,8=Canceled; priority/urgency/impact 1-5. On task/incident, priority is CALCULATED from impact+urgency (a priority value sent on create/update is recomputed/ignored) \u2014 set impact+urgency instead. Never hardcode for generic actions."
},
"errors": {
"401": "Authentication failed or OAuth token expired/invalid. Check username/password or refresh the bearer token.",
"403": "ACL denies the table or operation, or the user lacks the required role (e.g. rest_api_explorer / table ACL). Response: {\"error\":{\"message\":\"User Not Authorized\"}}.",
"404": "No Record found (bad sys_id) or Invalid table (bad/inaccessible table name, common Import Set staging-table error).",
"400": "Bad encoded query, invalid field name, or malformed request body."
}
},
"resource_type": {
"name": "servicenow",
"description": "ServiceNow instance credential. Set instance_url to your instance base (https://<instance>.service-now.com). Authenticate with EITHER Basic auth (username + password) OR an OAuth 2.0 bearer token \u2014 scripts send a Bearer header when token is set, otherwise Basic auth.",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"order": [
"instance_url",
"username",
"password",
"token"
],
"properties": {
"instance_url": {
"default": "",
"description": "Instance base URL, e.g. https://dev12345.service-now.com (no trailing slash). Every REST call is made against this host.",
"placeholder": "https://dev12345.service-now.com",
"type": "string"
},
"password": {
"default": "",
"description": "Basic auth password. Leave blank when using an OAuth bearer token.",
"password": true,
"type": "string"
},
"token": {
"default": "",
"description": "OAuth 2.0 access token (Bearer). Populated automatically when you connect via Windmill's ServiceNow OAuth flow (admin configures the instance + client id/secret in instance settings), or paste one manually. Provide this instead of username/password to authenticate with OAuth.",
"password": true,
"type": "string"
},
"username": {
"default": "",
"description": "Basic auth username. Leave blank when using an OAuth bearer token.",
"type": "string"
}
},
"required": [
"instance_url"
],
"type": "object"
}
},
"scripts": [
{
"version_id": 28665,
"app": "servicenow",
"summary": "Aggregate Records",
"description": "Count records on a table (Aggregate API), optionally filtered by an encoded query and grouped by a field.",
"terms": "servicenow aggregate records count records on a table (aggregate api), optionally filtered by an encoded query and grouped by a field.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nexport type DynSelect_table = string\nexport type DynSelect_group_by = string\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n// Dropdown of the instance's tables (sys_db_object).\nexport async function table(auth: RT.Servicenow) {\n const response = await fetch(\n `${auth.instance_url}/api/now/table/sys_db_object?sysparm_fields=name,label&sysparm_limit=10000`,\n {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n const { result } = (await response.json()) as {\n result: { name: string; label: string }[]\n }\n return result\n .filter((t) => t.name)\n .map((t) => ({ value: t.name, label: `${t.label} (${t.name})` }))\n .sort((a, b) => a.label.localeCompare(b.label))\n}\n\n// Walk the table's inheritance chain (sys_db_object.super_class) so the group-by\n// options include fields inherited from parent tables, not just fields defined\n// on the chosen table (sys_dictionary stores each field under its defining\n// table \u2014 e.g. incident extends task, so priority/state live on task).\n// Best-effort: any failure falls back to just the table itself.\nasync function tableChain(auth: RT.Servicenow, table: string) {\n const chain: string[] = []\n let current: string | undefined = table\n while (current && !chain.includes(current)) {\n chain.push(current)\n const response = await fetch(\n `${auth.instance_url}/api/now/table/sys_db_object?sysparm_query=name=${current}&sysparm_fields=super_class.name&sysparm_limit=1`,\n {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!response.ok) break\n const { result } = (await response.json()) as {\n result: { \"super_class.name\"?: string }[]\n }\n current = result[0]?.[\"super_class.name\"] || undefined\n }\n return chain\n}\n\n// Dependent select: the chosen table's columns (sys_dictionary) to group by,\n// including columns inherited from parent tables.\nexport async function group_by(auth: RT.Servicenow, table: DynSelect_table) {\n if (!table) return []\n const chain = await tableChain(auth, table)\n const url = new URL(`${auth.instance_url}/api/now/table/sys_dictionary`)\n url.searchParams.append(\n \"sysparm_query\",\n `nameIN${chain.join(\",\")}^elementISNOTEMPTY`\n )\n url.searchParams.append(\"sysparm_fields\", \"element,column_label\")\n url.searchParams.append(\"sysparm_limit\", \"10000\")\n\n const response = await fetch(url, {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n })\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n const { result } = (await response.json()) as {\n result: { element: string; column_label: string }[]\n }\n return result\n .filter((f) => f.element)\n .map((f) => ({\n value: f.element,\n label: `${f.column_label} (${f.element})`,\n }))\n .sort((a, b) => a.label.localeCompare(b.label))\n}\n\n/**\n * Aggregate Records\n * Count records on a table (Aggregate API), optionally filtered by an encoded query and grouped by a field. Returns ServiceNow's stats envelope with the count per group.\n */\nexport async function main(\n auth: RT.Servicenow,\n table: DynSelect_table,\n query: string | undefined,\n group_by: DynSelect_group_by | undefined\n) {\n const url = new URL(`${auth.instance_url}/api/now/stats/${table}`)\n url.searchParams.append(\"sysparm_count\", \"true\")\n if (query !== undefined && query !== \"\") {\n url.searchParams.append(\"sysparm_query\", query)\n }\n if (group_by !== undefined && group_by !== \"\") {\n url.searchParams.append(\"sysparm_group_by\", group_by)\n }\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
},
"group_by": {
"default": null,
"description": "",
"format": "dynselect-group_by",
"type": "object"
},
"query": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"table": {
"default": null,
"description": "",
"format": "dynselect-table",
"type": "object"
}
},
"required": [
"auth",
"table"
],
"type": "object"
}
},
{
"version_id": 28666,
"app": "servicenow",
"summary": "Create Record",
"description": "Insert a record into any table (incident, change_request, sys_user, cmdb_ci, or a custom table). Pick the table from a live dropdown and pass a column/value map.",
"terms": "servicenow create record insert a record into any table (incident, change_request, sys_user, cmdb_ci, or a custom table). pick the table from a live dropdown and pass a column/value map.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nexport type DynSelect_table = string\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n// Dropdown of the instance's tables (sys_db_object).\nexport async function table(auth: RT.Servicenow) {\n const response = await fetch(\n `${auth.instance_url}/api/now/table/sys_db_object?sysparm_fields=name,label&sysparm_limit=10000`,\n {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n const { result } = (await response.json()) as {\n result: { name: string; label: string }[]\n }\n return result\n .filter((t) => t.name)\n .map((t) => ({ value: t.name, label: `${t.label} (${t.name})` }))\n .sort((a, b) => a.label.localeCompare(b.label))\n}\n\n/**\n * Create Record\n * Insert a record into any table (incident, change_request, sys_user, cmdb_ci, or a custom table). Pick the table from a live dropdown and pass a column/value map.\n */\nexport async function main(\n auth: RT.Servicenow,\n table: DynSelect_table,\n body: { [key: string]: any }\n) {\n const url = new URL(`${auth.instance_url}/api/now/table/${table}`)\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: authHeader(auth),\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
},
"body": {
"default": null,
"description": "",
"properties": {},
"type": "object"
},
"table": {
"default": null,
"description": "",
"format": "dynselect-table",
"type": "object"
}
},
"required": [
"auth",
"table",
"body"
],
"type": "object"
}
},
{
"version_id": 28667,
"app": "servicenow",
"summary": "Delete Attachment",
"description": "Delete an attachment by sys_id. Returns {success, sys_id} (ServiceNow replies 204 No Content).",
"terms": "servicenow delete attachment delete an attachment by sys_id. returns {success, sys_id} (servicenow replies 204 no content).",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n/**\n * Delete Attachment\n * Delete an attachment by its sys_id. ServiceNow returns 204 No Content on success, so this returns {success, sys_id}.\n */\nexport async function main(auth: RT.Servicenow, attachment_sys_id: string) {\n const response = await fetch(\n `${auth.instance_url}/api/now/attachment/${attachment_sys_id}`,\n {\n method: \"DELETE\",\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return { success: true, sys_id: attachment_sys_id }\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"attachment_sys_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
}
},
"required": [
"auth",
"attachment_sys_id"
],
"type": "object"
}
},
{
"version_id": 28668,
"app": "servicenow",
"summary": "Delete Record",
"description": "Delete a record by sys_id. Returns {success, sys_id} (ServiceNow replies 204 No Content).",
"terms": "servicenow delete record delete a record by sys_id. returns {success, sys_id} (servicenow replies 204 no content).",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nexport type DynSelect_table = string\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n// Dropdown of the instance's tables (sys_db_object).\nexport async function table(auth: RT.Servicenow) {\n const response = await fetch(\n `${auth.instance_url}/api/now/table/sys_db_object?sysparm_fields=name,label&sysparm_limit=10000`,\n {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n const { result } = (await response.json()) as {\n result: { name: string; label: string }[]\n }\n return result\n .filter((t) => t.name)\n .map((t) => ({ value: t.name, label: `${t.label} (${t.name})` }))\n .sort((a, b) => a.label.localeCompare(b.label))\n}\n\n/**\n * Delete Record\n * Delete a record by sys_id. ServiceNow returns 204 No Content on success, so this returns {success, sys_id}.\n */\nexport async function main(\n auth: RT.Servicenow,\n table: DynSelect_table,\n sys_id: string\n) {\n const url = new URL(`${auth.instance_url}/api/now/table/${table}/${sys_id}`)\n\n const response = await fetch(url, {\n method: \"DELETE\",\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return { success: true, sys_id }\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
},
"sys_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"table": {
"default": null,
"description": "",
"format": "dynselect-table",
"type": "object"
}
},
"required": [
"auth",
"table",
"sys_id"
],
"type": "object"
}
},
{
"version_id": 28670,
"app": "servicenow",
"summary": "Get Current User",
"description": "Return the sys_user record of the authenticated user.",
"terms": "servicenow get current user return the sys_user record of the authenticated user.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n/**\n * Get Current User\n * Return the sys_user record of the authenticated user (resolves current_user, then fetches the full record from sys_user).\n */\nexport async function main(auth: RT.Servicenow) {\n const currentResponse = await fetch(\n `${auth.instance_url}/api/now/ui/user/current_user`,\n {\n method: \"GET\",\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!currentResponse.ok) {\n throw new Error(`${currentResponse.status} ${await currentResponse.text()}`)\n }\n const { result } = (await currentResponse.json()) as {\n result: { user_sys_id: string }\n }\n\n const userResponse = await fetch(\n `${auth.instance_url}/api/now/table/sys_user/${result.user_sys_id}`,\n {\n method: \"GET\",\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!userResponse.ok) {\n throw new Error(`${userResponse.status} ${await userResponse.text()}`)\n }\n\n return await userResponse.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
}
},
"required": [
"auth"
],
"type": "object"
}
},
{
"version_id": 28671,
"app": "servicenow",
"summary": "Get Import Set Result",
"description": "Fetch the outcome of a prior import row by the staging-row sys_id returned by Insert Into Import Set (result.sys_id).",
"terms": "servicenow get import set result fetch the outcome of a prior import row by the staging-row sys_id returned by insert into import set (result.sys_id).",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n/**\n * Get Import Set Result\n * Fetch the outcome of a prior import row by the staging-row sys_id returned by Insert Into Import Set (result.sys_id), returning the transform result (target table, status, target record link).\n */\nexport async function main(\n auth: RT.Servicenow,\n staging_table: string,\n sys_id: string\n) {\n const url = new URL(\n `${auth.instance_url}/api/now/import/${staging_table}/${sys_id}`\n )\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
},
"staging_table": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"sys_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth",
"staging_table",
"sys_id"
],
"type": "object"
}
},
{
"version_id": 28672,
"app": "servicenow",
"summary": "Get Record",
"description": "Retrieve a single record by sys_id. Optionally restrict the returned columns, and choose raw vs. display values.",
"terms": "servicenow get record retrieve a single record by sys_id. optionally restrict the returned columns, and choose raw vs. display values.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nexport type DynSelect_table = string\nexport type DynMultiselect_fields = string[]\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n// Dropdown of the instance's tables (sys_db_object).\nexport async function table(auth: RT.Servicenow) {\n const response = await fetch(\n `${auth.instance_url}/api/now/table/sys_db_object?sysparm_fields=name,label&sysparm_limit=10000`,\n {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n const { result } = (await response.json()) as {\n result: { name: string; label: string }[]\n }\n return result\n .filter((t) => t.name)\n .map((t) => ({ value: t.name, label: `${t.label} (${t.name})` }))\n .sort((a, b) => a.label.localeCompare(b.label))\n}\n\n// Walk the table's inheritance chain (sys_db_object.super_class) so the column\n// list includes fields inherited from parent tables, not just fields defined on\n// the chosen table (sys_dictionary stores each field under its defining table \u2014\n// e.g. incident extends task, so short_description/priority live on task).\n// Best-effort: any failure falls back to just the table itself.\nasync function tableChain(auth: RT.Servicenow, table: string) {\n const chain: string[] = []\n let current: string | undefined = table\n while (current && !chain.includes(current)) {\n chain.push(current)\n const response = await fetch(\n `${auth.instance_url}/api/now/table/sys_db_object?sysparm_query=name=${current}&sysparm_fields=super_class.name&sysparm_limit=1`,\n {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!response.ok) break\n const { result } = (await response.json()) as {\n result: { \"super_class.name\"?: string }[]\n }\n current = result[0]?.[\"super_class.name\"] || undefined\n }\n return chain\n}\n\n// Dependent multi-select: the chosen table's columns (sys_dictionary), including\n// columns inherited from parent tables.\nexport async function fields(auth: RT.Servicenow, table: DynSelect_table) {\n if (!table) return []\n const chain = await tableChain(auth, table)\n const url = new URL(`${auth.instance_url}/api/now/table/sys_dictionary`)\n url.searchParams.append(\n \"sysparm_query\",\n `nameIN${chain.join(\",\")}^elementISNOTEMPTY`\n )\n url.searchParams.append(\"sysparm_fields\", \"element,column_label\")\n url.searchParams.append(\"sysparm_limit\", \"10000\")\n\n const response = await fetch(url, {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n })\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n const { result } = (await response.json()) as {\n result: { element: string; column_label: string }[]\n }\n return result\n .filter((f) => f.element)\n .map((f) => ({\n value: f.element,\n label: `${f.column_label} (${f.element})`,\n }))\n .sort((a, b) => a.label.localeCompare(b.label))\n}\n\n/**\n * Get Record\n * Retrieve a single record by sys_id. Optionally restrict the returned columns, and choose raw vs. display values.\n */\nexport async function main(\n auth: RT.Servicenow,\n table: DynSelect_table,\n sys_id: string,\n fields: DynMultiselect_fields | undefined,\n display_value: \"false\" | \"true\" | \"all\" | undefined\n) {\n const url = new URL(`${auth.instance_url}/api/now/table/${table}/${sys_id}`)\n if (fields && fields.length > 0) {\n url.searchParams.append(\"sysparm_fields\", fields.join(\",\"))\n }\n if (display_value !== undefined && display_value !== \"\") {\n url.searchParams.append(\"sysparm_display_value\", display_value)\n }\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
},
"display_value": {
"default": null,
"description": "",
"enum": [
"false",
"true",
"all"
],
"originalType": "enum",
"type": "string"
},
"fields": {
"default": null,
"description": "",
"format": "dynmultiselect-fields",
"type": "object"
},
"sys_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"table": {
"default": null,
"description": "",
"format": "dynselect-table",
"type": "object"
}
},
"required": [
"auth",
"table",
"sys_id"
],
"type": "object"
}
},
{
"version_id": 28673,
"app": "servicenow",
"summary": "Insert Into Import Set",
"description": "Push a row into an import set staging table; the transform map runs synchronously. Inspect result[].status.",
"terms": "servicenow insert into import set push a row into an import set staging table; the transform map runs synchronously. inspect result[].status.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n/**\n * Insert Into Import Set\n * Push a row into an import set staging table; the associated transform map runs synchronously. Inspect result[].status (inserted/updated/ignored/error) \u2014 a transform error returns 200/201 with status \"error\".\n */\nexport async function main(\n auth: RT.Servicenow,\n staging_table: string,\n body: { [key: string]: any }\n) {\n const url = new URL(`${auth.instance_url}/api/now/import/${staging_table}`)\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: authHeader(auth),\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
},
"body": {
"default": null,
"description": "",
"properties": {},
"type": "object"
},
"staging_table": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth",
"staging_table",
"body"
],
"type": "object"
}
},
{
"version_id": 28674,
"app": "servicenow",
"summary": "List Attachments",
"description": "List attachment metadata, optionally filtered with an encoded query (e.g. table_name=incident^table_sys_id=<id>).",
"terms": "servicenow list attachments list attachment metadata, optionally filtered with an encoded query (e.g. table_name=incident^table_sys_id=<id>).",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n/**\n * List Attachments\n * List attachment metadata (sys_id, file_name, content_type, size, download_link). Filter with an encoded query, e.g. table_name=incident^table_sys_id=<record sys_id>.\n */\nexport async function main(\n auth: RT.Servicenow,\n query: string | undefined,\n limit: number | undefined\n) {\n const url = new URL(`${auth.instance_url}/api/now/attachment`)\n if (query !== undefined && query !== \"\") {\n url.searchParams.append(\"sysparm_query\", query)\n }\n url.searchParams.append(\"sysparm_limit\", String(limit ?? 100))\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
},
"limit": {
"default": null,
"description": "",
"type": "number"
},
"query": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth"
],
"type": "object"
}
},
{
"version_id": 28675,
"app": "servicenow",
"summary": "List Records",
"description": "Query records from any table with an encoded query (sysparm_query), pagination, column selection, and raw/display values.",
"terms": "servicenow list records query records from any table with an encoded query (sysparm_query), pagination, column selection, and raw/display values.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nexport type DynSelect_table = string\nexport type DynMultiselect_fields = string[]\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n// Dropdown of the instance's tables (sys_db_object).\nexport async function table(auth: RT.Servicenow) {\n const response = await fetch(\n `${auth.instance_url}/api/now/table/sys_db_object?sysparm_fields=name,label&sysparm_limit=10000`,\n {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n const { result } = (await response.json()) as {\n result: { name: string; label: string }[]\n }\n return result\n .filter((t) => t.name)\n .map((t) => ({ value: t.name, label: `${t.label} (${t.name})` }))\n .sort((a, b) => a.label.localeCompare(b.label))\n}\n\n// Walk the table's inheritance chain (sys_db_object.super_class) so the column\n// list includes fields inherited from parent tables, not just fields defined on\n// the chosen table (sys_dictionary stores each field under its defining table \u2014\n// e.g. incident extends task, so short_description/priority live on task).\n// Best-effort: any failure falls back to just the table itself.\nasync function tableChain(auth: RT.Servicenow, table: string) {\n const chain: string[] = []\n let current: string | undefined = table\n while (current && !chain.includes(current)) {\n chain.push(current)\n const response = await fetch(\n `${auth.instance_url}/api/now/table/sys_db_object?sysparm_query=name=${current}&sysparm_fields=super_class.name&sysparm_limit=1`,\n {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!response.ok) break\n const { result } = (await response.json()) as {\n result: { \"super_class.name\"?: string }[]\n }\n current = result[0]?.[\"super_class.name\"] || undefined\n }\n return chain\n}\n\n// Dependent multi-select: the chosen table's columns (sys_dictionary), including\n// columns inherited from parent tables.\nexport async function fields(auth: RT.Servicenow, table: DynSelect_table) {\n if (!table) return []\n const chain = await tableChain(auth, table)\n const url = new URL(`${auth.instance_url}/api/now/table/sys_dictionary`)\n url.searchParams.append(\n \"sysparm_query\",\n `nameIN${chain.join(\",\")}^elementISNOTEMPTY`\n )\n url.searchParams.append(\"sysparm_fields\", \"element,column_label\")\n url.searchParams.append(\"sysparm_limit\", \"10000\")\n\n const response = await fetch(url, {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n })\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n const { result } = (await response.json()) as {\n result: { element: string; column_label: string }[]\n }\n return result\n .filter((f) => f.element)\n .map((f) => ({\n value: f.element,\n label: `${f.column_label} (${f.element})`,\n }))\n .sort((a, b) => a.label.localeCompare(b.label))\n}\n\n/**\n * List Records\n * Query records from any table with an encoded query (sysparm_query), pagination, column selection, and raw/display values. Returns one page; iterate offset until fewer than limit rows return.\n */\nexport async function main(\n auth: RT.Servicenow,\n table: DynSelect_table,\n query: string | undefined,\n limit: number | undefined,\n offset: number | undefined,\n fields: DynMultiselect_fields | undefined,\n display_value: \"false\" | \"true\" | \"all\" | undefined\n) {\n const url = new URL(`${auth.instance_url}/api/now/table/${table}`)\n if (query !== undefined && query !== \"\") {\n url.searchParams.append(\"sysparm_query\", query)\n }\n url.searchParams.append(\"sysparm_limit\", String(limit ?? 100))\n if (offset !== undefined) {\n url.searchParams.append(\"sysparm_offset\", String(offset))\n }\n if (fields && fields.length > 0) {\n url.searchParams.append(\"sysparm_fields\", fields.join(\",\"))\n }\n if (display_value !== undefined && display_value !== \"\") {\n url.searchParams.append(\"sysparm_display_value\", display_value)\n }\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
},
"display_value": {
"default": null,
"description": "",
"enum": [
"false",
"true",
"all"
],
"originalType": "enum",
"type": "string"
},
"fields": {
"default": null,
"description": "",
"format": "dynmultiselect-fields",
"type": "object"
},
"limit": {
"default": null,
"description": "",
"type": "number"
},
"offset": {
"default": null,
"description": "",
"type": "number"
},
"query": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"table": {
"default": null,
"description": "",
"format": "dynselect-table",
"type": "object"
}
},
"required": [
"auth",
"table"
],
"type": "object"
}
},
{
"version_id": 28676,
"app": "servicenow",
"summary": "List Tables",
"description": "List the instance tables (sys_db_object) with API name, label, and sys_id. Optionally filter by a name/label substring.",
"terms": "servicenow list tables list the instance tables (sys_db_object) with api name, label, and sys_id. optionally filter by a name/label substring.",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n/**\n * List Tables\n * List the instance's tables (sys_db_object) with their API name, label, and sys_id. Optionally filter by a substring matched against name or label.\n */\nexport async function main(\n auth: RT.Servicenow,\n name_filter: string | undefined\n) {\n const url = new URL(`${auth.instance_url}/api/now/table/sys_db_object`)\n url.searchParams.append(\"sysparm_fields\", \"name,label,sys_id\")\n if (name_filter !== undefined && name_filter !== \"\") {\n url.searchParams.append(\n \"sysparm_query\",\n `nameLIKE${name_filter}^ORlabelLIKE${name_filter}`\n )\n }\n url.searchParams.append(\"sysparm_limit\", \"10000\")\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
},
"name_filter": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
}
},
"required": [
"auth"
],
"type": "object"
}
},
{
"version_id": 28677,
"app": "servicenow",
"summary": "Update Record",
"description": "Update a record by sys_id with a column/value map. PATCH (partial) by default; set replace=true to PUT (full overwrite).",
"terms": "servicenow update record update a record by sys_id with a column/value map. patch (partial) by default; set replace=true to put (full overwrite).",
"kind": "script",
"language": "bunnative",
"content": "//native\n\nexport type DynSelect_table = string\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n// Dropdown of the instance's tables (sys_db_object).\nexport async function table(auth: RT.Servicenow) {\n const response = await fetch(\n `${auth.instance_url}/api/now/table/sys_db_object?sysparm_fields=name,label&sysparm_limit=10000`,\n {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n const { result } = (await response.json()) as {\n result: { name: string; label: string }[]\n }\n return result\n .filter((t) => t.name)\n .map((t) => ({ value: t.name, label: `${t.label} (${t.name})` }))\n .sort((a, b) => a.label.localeCompare(b.label))\n}\n\n/**\n * Update Record\n * Update a record by sys_id with a column/value map. PATCH (partial update) by default; set replace=true to PUT (full overwrite that clears omitted fields).\n */\nexport async function main(\n auth: RT.Servicenow,\n table: DynSelect_table,\n sys_id: string,\n body: { [key: string]: any },\n replace: boolean | undefined\n) {\n const url = new URL(`${auth.instance_url}/api/now/table/${table}/${sys_id}`)\n\n const response = await fetch(url, {\n method: replace ? \"PUT\" : \"PATCH\",\n headers: {\n Authorization: authHeader(auth),\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n return await response.json()\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
},
"body": {
"default": null,
"description": "",
"properties": {},
"type": "object"
},
"replace": {
"default": null,
"description": "",
"type": "boolean"
},
"sys_id": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"table": {
"default": null,
"description": "",
"format": "dynselect-table",
"type": "object"
}
},
"required": [
"auth",
"table",
"sys_id",
"body"
],
"type": "object"
}
},
{
"version_id": 28679,
"app": "servicenow",
"summary": "New or Updated Record",
"description": "Emits records whose timestamp is newer than the last poll, tracked via Windmill state. Defaults to sys_updated_on.",
"terms": "servicenow new or updated record emits records whose timestamp is newer than the last poll, tracked via windmill state. defaults to sys_updated_on.",
"kind": "trigger",
"language": "bunnative",
"content": "//native\n\nimport * as wmill from \"windmill-client\"\n\nexport type DynSelect_table = string\n\nfunction authHeader(auth: RT.Servicenow) {\n return auth.token\n ? `Bearer ${auth.token}`\n : `Basic ${btoa(`${auth.username}:${auth.password}`)}`\n}\n\n// Dropdown of the instance's tables (sys_db_object).\nexport async function table(auth: RT.Servicenow) {\n const response = await fetch(\n `${auth.instance_url}/api/now/table/sys_db_object?sysparm_fields=name,label&sysparm_limit=10000`,\n {\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n }\n )\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n const { result } = (await response.json()) as {\n result: { name: string; label: string }[]\n }\n return result\n .filter((t) => t.name)\n .map((t) => ({ value: t.name, label: `${t.label} (${t.name})` }))\n .sort((a, b) => a.label.localeCompare(b.label))\n}\n\n/**\n * New or Updated Record\n * Emits records from a table whose timestamp is newer than the last poll, tracked via Windmill state. Defaults to sys_updated_on (set timestamp_field to sys_created_on for new-only). Optionally narrow with an extra encoded query.\n */\nexport async function main(\n auth: RT.Servicenow,\n table: DynSelect_table,\n timestamp_field: \"sys_updated_on\" | \"sys_created_on\" | undefined,\n query: string | undefined\n) {\n const tsField =\n timestamp_field && timestamp_field !== \"\"\n ? timestamp_field\n : \"sys_updated_on\"\n const lastChecked: string | undefined = await wmill.getState()\n\n // ServiceNow datetimes are 'YYYY-MM-DD HH:MM:SS' in UTC (no trailing Z).\n const nowUtc = new Date().toISOString().slice(0, 19).replace(\"T\", \" \")\n\n // First run: set the watermark to now and don't emit a backlog.\n if (!lastChecked) {\n await wmill.setState(nowUtc)\n return []\n }\n\n let encoded = `${tsField}>${lastChecked}`\n if (query !== undefined && query !== \"\") {\n encoded += `^${query}`\n }\n encoded += `^ORDERBY${tsField}`\n\n const url = new URL(`${auth.instance_url}/api/now/table/${table}`)\n url.searchParams.append(\"sysparm_query\", encoded)\n url.searchParams.append(\"sysparm_limit\", \"200\")\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: authHeader(auth),\n Accept: \"application/json\",\n },\n })\n\n if (!response.ok) {\n throw new Error(`${response.status} ${await response.text()}`)\n }\n\n const { result } = (await response.json()) as {\n result: Array<{ [key: string]: any }>\n }\n\n if (result.length > 0) {\n await wmill.setState(result[result.length - 1][tsField])\n }\n\n return result\n}\n",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"auth": {
"default": null,
"description": "",
"format": "resource-servicenow",
"type": "object"
},
"query": {
"default": null,
"description": "",
"originalType": "string",
"type": "string"
},
"table": {
"default": null,
"description": "",
"format": "dynselect-table",
"type": "object"
},
"timestamp_field": {
"default": null,
"description": "",
"enum": [
"sys_updated_on",
"sys_created_on"
],
"originalType": "enum",
"type": "string"
}
},
"required": [
"auth",
"table"
],
"type": "object"
}
}
]
}
+5 -1
View File
@@ -17,8 +17,12 @@ import type { BenchmarkArtifactFile, CliTrace, ModeRunner } from "../core/types"
const IGNORE_WORKSPACE_FILES = new Set([
".claude",
".agents",
"AGENTS.md",
"AGENTS.wmill.md",
"CLAUDE.md",
// the guidance has the agent create one for every new folder
"folder.meta.yaml",
"rt.d.ts",
".wmill-benchmark-bin",
".wmill-benchmark-wmill-invocations.log",
@@ -89,7 +93,7 @@ export function createCliModeRunner(
const run = await runPromptAndCapture(
renderedPrompt,
workspaceDir,
context.evalCase?.runtime?.maxTurns ?? 6,
context.evalCase?.runtime?.maxTurns ?? 12,
modelConfig
);
const workspaceFiles = await readDirectoryFiles(workspaceDir, { ignore: IGNORE_WORKSPACE_FILES });
+2 -2
View File
@@ -8,8 +8,8 @@
"test:frontend-graph": "cd ../frontend && node_modules/.bin/vitest run --project server --config ../ai_evals/adapters/frontend/vitest.unit.config.ts"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.25",
"@anthropic-ai/sdk": "^0.39.0",
"@anthropic-ai/claude-agent-sdk": "^0.3.284",
"@anthropic-ai/sdk": "^0.129.0",
"commander": "^14.0.3",
"openai": "^6.9.1",
"yaml": "^2.8.3"
@@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COALESCE((operator_settings->>'builder_flows')::boolean, false) AS \"flows!\",\n COALESCE((operator_settings->>'manage_schedules')::boolean, true) AS \"schedules!\",\n COALESCE((operator_settings->>'manage_triggers')::boolean, true) AS \"triggers!\"\n FROM workspace_settings WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "flows!",
"type_info": "Bool"
},
{
"ordinal": 1,
"name": "schedules!",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "triggers!",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null,
null,
null
]
},
"hash": "094a07eaa5714ec739786717f110ea539e84c33de23476e042a4a1a7cb529dac"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n WITH job_info AS (\n SELECT id, kind::text AS kind, parent_job\n FROM v2_job\n WHERE id = $1 AND workspace_id = $2\n )\n SELECT\n q.id AS \"id!\",\n s.flow_status,\n q.suspend AS \"suspend!\",\n j.runnable_path AS script_path,\n j.permissioned_as_email AS email,\n (ji.kind IN ('flow', 'flowpreview', 'singlestepflow')) AS \"is_flow_level!\",\n (ji.kind NOT IN ('flow', 'flowpreview', 'singlestepflow') AND q.id = ji.id) AS \"is_wac!\"\n FROM job_info ji\n JOIN v2_job_queue q ON q.id = CASE\n WHEN ji.kind IN ('flow', 'flowpreview', 'singlestepflow') THEN ji.id\n ELSE COALESCE(ji.parent_job, ji.id)\n END\n JOIN v2_job j ON j.id = q.id\n LEFT JOIN v2_job_status s ON s.id = q.id\n FOR UPDATE OF q\n ",
"query": "\n WITH job_info AS (\n SELECT id, kind::text AS kind, parent_job\n FROM v2_job\n WHERE id = $1 AND workspace_id = $2\n )\n SELECT\n q.id AS \"id!\",\n q.suspend AS \"suspend!\",\n j.runnable_path AS script_path,\n j.permissioned_as_email AS email,\n (ji.kind IN ('flow', 'flowpreview', 'singlestepflow')) AS \"is_flow_level!\",\n (ji.kind NOT IN ('flow', 'flowpreview', 'singlestepflow') AND q.id = ji.id) AS \"is_wac!\"\n FROM job_info ji\n JOIN v2_job_queue q ON q.id = CASE\n WHEN ji.kind IN ('flow', 'flowpreview', 'singlestepflow') THEN ji.id\n ELSE COALESCE(ji.parent_job, ji.id)\n END\n JOIN v2_job j ON j.id = q.id\n FOR UPDATE OF q\n ",
"describe": {
"columns": [
{
@@ -10,31 +10,26 @@
},
{
"ordinal": 1,
"name": "flow_status",
"type_info": "Jsonb"
},
{
"ordinal": 2,
"name": "suspend!",
"type_info": "Int4"
},
{
"ordinal": 3,
"ordinal": 2,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 4,
"ordinal": 3,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 5,
"ordinal": 4,
"name": "is_flow_level!",
"type_info": "Bool"
},
{
"ordinal": 6,
"ordinal": 5,
"name": "is_wac!",
"type_info": "Bool"
}
@@ -47,7 +42,6 @@
},
"nullable": [
false,
true,
false,
true,
false,
@@ -55,5 +49,5 @@
null
]
},
"hash": "11b5d006acf040b2461b9539863b1e511c23e1a5f9be97da066a6808a49f2092"
"hash": "0a18205b64e68529b7f90bc1c5db2cbea9ee5eaa962970b65a92597fd5e62707"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "WITH RECURSIVE descendants AS (\n SELECT id FROM v2_job WHERE parent_job = $1 AND workspace_id = $2\n UNION ALL\n SELECT j.id FROM v2_job j JOIN descendants d ON j.parent_job = d.id\n WHERE j.workspace_id = $2\n ) SELECT d.id AS \"id!\" FROM descendants d JOIN v2_job_queue q ON q.id = d.id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
null
]
},
"hash": "0c2bf6925de6dd4d9f8d47d7fdd9e7801129570106b5acd49c0f708398606771"
}
@@ -1,31 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n schedule.path, t.jobs FROM schedule,\n LATERAL(SELECT ARRAY(\n SELECT json_build_object('id', id, 'success', status = 'success', 'duration_ms', duration_ms)\n FROM v2_job_completed c JOIN v2_job j USING (id)\n WHERE trigger_kind = 'schedule'\n AND trigger = schedule.path\n AND c.workspace_id = $1\n AND j.workspace_id = $1\n AND parent_job IS NULL AND runnable_path = schedule.script_path\n AND status <> 'skipped'\n ORDER BY completed_at DESC\n LIMIT 20\n ) AS jobs) t\n WHERE workspace_id = $1 AND NOT starts_with(schedule.path, $4)\n ORDER BY edited_at DESC\n LIMIT $2 OFFSET $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "jobs",
"type_info": "JsonArray"
}
],
"parameters": {
"Left": [
"Text",
"Int8",
"Int8",
"Text"
]
},
"nullable": [
false,
null
]
},
"hash": "0f141ca6a58901dee1ddcf25694d76a4e5702c0324a27ef3a5740ef5d98d8946"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "WITH RECURSIVE up AS (\n SELECT id, parent_job, kind, runnable_id, runnable_path, 0 AS depth\n FROM v2_job WHERE id = $1 AND workspace_id = $2\n UNION ALL\n SELECT p.id, p.parent_job, p.kind, p.runnable_id, p.runnable_path, u.depth + 1\n FROM v2_job p JOIN up u ON p.id = u.parent_job\n WHERE p.workspace_id = $2 AND u.kind <> 'flow' AND u.depth < 100\n )\n SELECT EXISTS (SELECT 1 FROM up WHERE kind = 'flow'\n AND runnable_id = (SELECT f.versions[array_upper(f.versions, 1)]\n FROM flow f WHERE f.path = up.runnable_path AND f.workspace_id = $2))\n AND NOT EXISTS (SELECT 1 FROM up WHERE kind <> 'flow' AND (\n EXISTS (SELECT 1 FROM v2_job_completed c WHERE c.id = up.id\n AND jsonb_typeof(c.flow_status->'restarted_from') = 'object')\n OR EXISTS (SELECT 1 FROM v2_job_status st WHERE st.id = up.id\n AND jsonb_typeof(st.flow_status->'restarted_from') = 'object')))\n AS \"current!\"",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "current!",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
null
]
},
"hash": "141725e44ccd3f9cc0b5eb070ea3de94b89a4940901e11c48c5f2014ff28086a"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO schedule (\n workspace_id, path, schedule, timezone, edited_by, script_path,\n is_flow, args, enabled, email, permissioned_as,\n on_failure, on_failure_times, on_failure_exact, on_failure_extra_args,\n on_recovery, on_recovery_times, on_recovery_extra_args,\n on_success, on_success_extra_args,\n ws_error_handler_muted, retry, summary, no_flow_overlap,\n tag, paused_until, cron_version, description, dynamic_skip, labels\n ) VALUES (\n $1, $2, $3, $4, $5, $6,\n $7, $8, $9, $10, $11,\n $12, $13, $14, $15,\n $16, $17, $18,\n $19, $20,\n $21, $22, $23, $24,\n $25, $26, $27, $28, $29, $30\n )\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip,\n labels\n ",
"query": "\n INSERT INTO schedule (\n workspace_id, path, schedule, timezone, edited_by, script_path,\n is_flow, args, enabled, email, permissioned_as,\n on_failure, on_failure_times, on_failure_exact, on_failure_extra_args,\n on_recovery, on_recovery_times, on_recovery_extra_args,\n on_success, on_success_extra_args,\n ws_error_handler_muted, retry, summary, no_flow_overlap,\n tag, paused_until, cron_version, description, dynamic_skip, labels\n ) VALUES (\n $1, $2, $3, $4, $5, $6,\n $7, $8, $9, $10, $11,\n $12, $13, $14, $15,\n $16, $17, $18,\n $19, $20,\n $21, $22, $23, $24,\n $25, $26, $27, $28, $29, $30\n )\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip,\n labels,\n late_run_streak\n ",
"describe": {
"columns": [
{
@@ -167,6 +167,11 @@
"ordinal": 32,
"name": "labels",
"type_info": "TextArray"
},
{
"ordinal": 33,
"name": "late_run_streak",
"type_info": "Int4"
}
],
"parameters": {
@@ -236,8 +241,9 @@
true,
true,
true,
true
true,
false
]
},
"hash": "2effe885aafaa0c5fc1f969b8c6ffbd38c53fb586251ba780b75a007e5a47ed6"
"hash": "1c51aab269efb558640ea502e63ebed4c3d8b025947becd1c614757e7e29a5d8"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT restart_unless_cancelled FROM script WHERE hash = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "restart_unless_cancelled",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": [
true
]
},
"hash": "1d27895aa42ccbb542479b19baefd62790205b529ab0d8af36f18c470e8bb838"
}
@@ -1,26 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT 'schedule' AS \"kind!\", COUNT(*)::BIGINT AS \"count!\" FROM schedule\n UNION ALL SELECT 'http', COUNT(*)::BIGINT FROM http_trigger\n UNION ALL SELECT 'websocket', COUNT(*)::BIGINT FROM websocket_trigger\n UNION ALL SELECT 'kafka', COUNT(*)::BIGINT FROM kafka_trigger\n UNION ALL SELECT 'nats', COUNT(*)::BIGINT FROM nats_trigger\n UNION ALL SELECT 'postgres', COUNT(*)::BIGINT FROM postgres_trigger\n UNION ALL SELECT 'mqtt', COUNT(*)::BIGINT FROM mqtt_trigger\n UNION ALL SELECT 'sqs', COUNT(*)::BIGINT FROM sqs_trigger\n UNION ALL SELECT 'gcp', COUNT(*)::BIGINT FROM gcp_trigger\n UNION ALL SELECT 'azure', COUNT(*)::BIGINT FROM azure_trigger\n UNION ALL SELECT 'amqp', COUNT(*)::BIGINT FROM amqp_trigger\n UNION ALL SELECT 'email', COUNT(*)::BIGINT FROM email_trigger\n -- Grouped, not a single 'native' key: these fire as nextcloud/google/github,\n -- so a lone key would not line up with the `trigger`/`fired` series.\n UNION ALL SELECT service_name::text, COUNT(*)::BIGINT FROM native_trigger GROUP BY service_name\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "kind!",
"type_info": "Text"
},
{
"ordinal": 1,
"name": "count!",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null,
null
]
},
"hash": "1d346a14ad5586af347b8e7ac413500a39efa20e4915ffa56fd40537597db36e"
}
@@ -1,83 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "WITH RECURSIVE lineage AS (\n SELECT id, parent_job, kind, runnable_path, runnable_id, trigger_kind, trigger,\n 0 AS depth\n FROM v2_job WHERE id = $1 AND workspace_id = $2\n UNION ALL\n SELECT p.id, p.parent_job, p.kind, p.runnable_path, p.runnable_id, p.trigger_kind,\n p.trigger, l.depth + 1\n FROM v2_job p JOIN lineage l ON p.id = l.parent_job\n WHERE p.workspace_id = $2 AND l.depth < 100\n )\n SELECT id AS \"id!\", parent_job, kind AS \"kind!: JobKind\", runnable_path,\n trigger_kind::text AS trigger_kind,\n -- A restart takes its flow version from the request, so a trusted flow path\n -- can carry another flow's code: the version must belong to that path.\n CASE kind\n WHEN 'flow' THEN EXISTS (SELECT 1 FROM flow_version fv\n WHERE fv.id = runnable_id AND fv.path = runnable_path AND fv.workspace_id = $2)\n WHEN 'script' THEN EXISTS (SELECT 1 FROM script s\n WHERE s.hash = runnable_id AND s.path = runnable_path AND s.workspace_id = $2)\n -- An app editor preview also runs an app script, at an app path it does not\n -- have to own; only deployed-app runs are stamped with their app.\n WHEN 'appscript' THEN COALESCE(trigger_kind = 'app'\n AND starts_with(runnable_path, trigger || '/'), false)\n ELSE true\n END AS \"origin_verified!\"\n FROM lineage ORDER BY depth",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "parent_job",
"type_info": "Uuid"
},
{
"ordinal": 2,
"name": "kind!: JobKind",
"type_info": {
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlestepflow",
"flowscript",
"flownode",
"appscript",
"aiagent",
"unassigned_script",
"unassigned_flow",
"unassigned_singlestepflow"
]
}
}
}
},
{
"ordinal": 3,
"name": "runnable_path",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "trigger_kind",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "origin_verified!",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
null,
null,
null,
null,
null,
null
]
},
"hash": "201c4d014dd71da5a38ba832f836ca71b0fe6dbee8645449e97517aeb4ef9655"
}
@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM script WHERE workspace_id = $1 AND path = $2 AND hash = $3)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Int8"
]
},
"nullable": [
null
]
},
"hash": "298e0e30afdc973c517a9b4ea99f5dd1616a62c6ecbaaea312c686038d21e6fd"
}
@@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "SELECT c.id AS \"id!\", u.job_id AS \"job_id!\", u.created_seq AS \"user_seq!\"\n FROM unnest($1::uuid[]) AS c(id)\n CROSS JOIN LATERAL (\n SELECT job_id, created_seq\n FROM flow_conversation_message\n WHERE conversation_id = c.id AND message_type = 'user'\n ORDER BY created_seq DESC\n LIMIT 1\n ) u\n WHERE u.job_id IS NOT NULL\n AND EXISTS (SELECT 1 FROM v2_job_queue q WHERE q.id = u.job_id)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "job_id!",
"type_info": "Uuid"
},
{
"ordinal": 2,
"name": "user_seq!",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"UuidArray"
]
},
"nullable": [
null,
true,
false
]
},
"hash": "2bf039a2880555c5e6eec9a29c4a380aa832d72d2aaf5b109ee9554f1e9da6a4"
}
@@ -1,12 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM script WHERE workspace_id = $1 AND path = ANY($2) RETURNING path",
"query": "DELETE FROM script WHERE workspace_id = $1 AND path = ANY($2) RETURNING path, hash",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "hash",
"type_info": "Int8"
}
],
"parameters": {
@@ -16,8 +21,9 @@
]
},
"nullable": [
false,
false
]
},
"hash": "819c4d78f10979a83529155c3f101f42f6958742bc68ab384944fa3c5cba094c"
"hash": "2d73fe76e45becbcae7a9d4384bbb2f9739cab232b8f3e357e27dc32bb35c3a8"
}
@@ -0,0 +1,49 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n schedule.path, t.jobs, schedule.late_run_streak, schedule.missed_occurrences, schedule.last_missed_at FROM schedule,\n LATERAL(SELECT ARRAY(\n SELECT json_build_object('id', id, 'success', status = 'success', 'duration_ms', duration_ms)\n FROM v2_job_completed c JOIN v2_job j USING (id)\n WHERE trigger_kind = 'schedule'\n AND trigger = schedule.path\n AND c.workspace_id = $1\n AND j.workspace_id = $1\n AND parent_job IS NULL AND runnable_path = schedule.script_path\n AND status <> 'skipped'\n ORDER BY completed_at DESC\n LIMIT 20\n ) AS jobs) t\n WHERE workspace_id = $1 AND NOT starts_with(schedule.path, $4)\n ORDER BY edited_at DESC\n LIMIT $2 OFFSET $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "jobs",
"type_info": "JsonArray"
},
{
"ordinal": 2,
"name": "late_run_streak",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "missed_occurrences",
"type_info": "Int4"
},
{
"ordinal": 4,
"name": "last_missed_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text",
"Int8",
"Int8",
"Text"
]
},
"nullable": [
false,
null,
false,
false,
true
]
},
"hash": "39793465641124401a732744777bc3553985b757b48962e9976b5da5b812bd05"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM flow WHERE workspace_id = $1 AND path = $2)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "3dccc8e745f4a0973541088f66172e74af6828c1ab52cf7a6fd10b305deade85"
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_queue SET started_at = now() WHERE id = $1 RETURNING started_at",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "started_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
true
]
},
"hash": "451f303f4a24d848ba4ccdc2441ca7cb85bdfc66110b6534260f8642f91f006a"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE schedule SET late_run_streak = 0\n WHERE workspace_id = $1 AND path = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "466b2c68074202146da10d4b0757128fa332a52e3b451aac307234d02a51de8f"
}
@@ -0,0 +1,77 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n j.runnable_path as script_path, j.args AS \"args: sqlx::types::Json<HashMap<String, Box<RawValue>>>\",\n j.tag AS \"tag!\", j.priority, j.kind AS \"kind!: JobKind\"\n FROM v2_job j\n WHERE j.id = $1 and j.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "args: sqlx::types::Json<HashMap<String, Box<RawValue>>>",
"type_info": "Jsonb"
},
{
"ordinal": 2,
"name": "tag!",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "priority",
"type_info": "Int2"
},
{
"ordinal": 4,
"name": "kind!: JobKind",
"type_info": {
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlestepflow",
"flowscript",
"flownode",
"appscript",
"aiagent",
"unassigned_script",
"unassigned_flow",
"unassigned_singlestepflow"
]
}
}
}
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
true,
true,
false,
true,
false
]
},
"hash": "4a257470121ee6bffd9c68e88aabb7563c0ba31233854c8922f9449da7b43e9e"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT late_run_streak FROM schedule WHERE workspace_id = $1 AND path = $2 FOR SHARE",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "late_run_streak",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "4a6fb702794ce250a8217034ae9b0b719f61fe0cb8a7a2c76be4b7422a7138a0"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM script WHERE workspace_id = $1 AND path = $2)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "4bc47050c74a02ab3169c3165898b2af07e995de71564d867b171f97f719fbde"
}
@@ -1,41 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT c.id IS NOT NULL AS \"completed!\",\n c.result AS \"result: sqlx::types::Json<Box<RawValue>>\",\n c.status = 'success' AS \"success\",\n EXISTS(SELECT 1 FROM v2_job_queue WHERE id = $1) AS \"queued!\"\n FROM (SELECT 1) one\n LEFT JOIN v2_job_completed c ON c.id = $1 AND c.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "completed!",
"type_info": "Bool"
},
{
"ordinal": 1,
"name": "result: sqlx::types::Json<Box<RawValue>>",
"type_info": "Jsonb"
},
{
"ordinal": 2,
"name": "success",
"type_info": "Bool"
},
{
"ordinal": 3,
"name": "queued!",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
null,
true,
null,
null
]
},
"hash": "52f9582423689f098a337f88341239a136ee5132bc97f4b06cc1ee5beaa621e5"
}
@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT bool_and(operator) FROM (\n SELECT operator FROM usr WHERE email = $1 AND is_service_account IS false\n UNION ALL\n SELECT operator FROM workspace_invite WHERE email = $1\n ) t",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "bool_and",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "5312b8db714139a94d7ff1c0794af063c36ac17e9d331cd9980b91b28d713c72"
}
@@ -0,0 +1,25 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_queue SET canceled_by = $1, canceled_reason = $2, scheduled_for = now(), suspend = 0 WHERE id = $3 AND workspace_id = $4 AND canceled_by IS NULL RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Varchar",
"Text",
"Uuid",
"Text"
]
},
"nullable": [
false
]
},
"hash": "611dd43eb1b629db860c1b477444cb3dadcbf8058a24883bc22f143762c5ea3d"
}
@@ -0,0 +1,31 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE schedule SET late_run_streak = late_run_streak + 1,\n missed_occurrences = CASE WHEN late_run_streak = 0 THEN $3\n ELSE missed_occurrences + $3 END,\n last_missed_at = $4\n WHERE workspace_id = $1 AND path = $2\n RETURNING late_run_streak, missed_occurrences",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "late_run_streak",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "missed_occurrences",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Int4",
"Timestamptz"
]
},
"nullable": [
false,
false
]
},
"hash": "640482b37fe2aa63a5082250170406bcbe888ddece7dec3330778591573031fb"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option<ScriptLang>\", envs AS \"envs: Vec<String>\", schema AS \"schema: String\", schema_validation AS \"schema_validation: bool\", codebase LIKE '%.tar' as use_tar, codebase LIKE '%.esm%' as is_esm, modules AS \"modules: serde_json::Value\" FROM script WHERE hash = $1 LIMIT 1",
"query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option<ScriptLang>\", envs AS \"envs: Vec<String>\", schema AS \"schema: String\", schema_validation AS \"schema_validation: bool\", codebase LIKE '%.tar' as use_tar, codebase LIKE '%.esm%' as is_esm, modules AS \"modules: serde_json::Value\", deleted FROM script WHERE hash = $1 ORDER BY deleted LIMIT 1",
"describe": {
"columns": [
{
@@ -80,6 +80,11 @@
"ordinal": 8,
"name": "modules: serde_json::Value",
"type_info": "Jsonb"
},
{
"ordinal": 9,
"name": "deleted",
"type_info": "Bool"
}
],
"parameters": {
@@ -96,8 +101,9 @@
false,
null,
null,
true
true,
false
]
},
"hash": "b44afcf1b9c047ac525638f0952c2cb01d65b1b46693331ac157dfca0dab6824"
"hash": "65e33a933ed8568c82f9b4e121b9ba4280123fe22a959d4d3d978ca767aeed29"
}
@@ -1,38 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, name, enabled, pwd FROM datatable_role",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "enabled",
"type_info": "Bool"
},
{
"ordinal": 3,
"name": "pwd",
"type_info": "Text"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false,
true
]
},
"hash": "71ee2cb6661cca1fa4d8874a7f6d368347c59f36fd87df6dc7996152ccb84af0"
}
@@ -0,0 +1,35 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, status = 'success' AS \"success!\", result AS \"result: Json<Box<RawValue>>\"\n FROM v2_job_completed WHERE workspace_id = $1 AND id = ANY($2)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "success!",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "result: Json<Box<RawValue>>",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text",
"UuidArray"
]
},
"nullable": [
false,
null,
true
]
},
"hash": "789c3f6d29f46fcb17a22fe97405a1754ccb8ddeb08c23795dfe14e9f332e9dc"
}
@@ -0,0 +1,35 @@
{
"db_name": "PostgreSQL",
"query": "WITH RECURSIVE chain(id, parent_job) AS (\n SELECT id, parent_job FROM v2_job WHERE id = $1 AND workspace_id = $2\n UNION ALL\n SELECT j.id, j.parent_job FROM v2_job j\n JOIN chain c ON j.id = c.parent_job AND j.workspace_id = $2\n )\n SELECT\n CASE WHEN a.agent THEN regexp_replace(j.runnable_path, '\\.chat$', '')\n ELSE j.runnable_path END AS runnable_path,\n CASE\n WHEN a.agent THEN 'agents'\n WHEN j.kind IN ('script', 'script_hub', 'unassigned_script') THEN 'scripts'\n WHEN j.kind IN ('flow', 'unassigned_flow') THEN 'flows'\n WHEN j.kind IN ('singlestepflow', 'unassigned_singlestepflow') THEN\n CASE WHEN COALESCE(\n (SELECT m->'value'->>'type'\n FROM jsonb_array_elements(j.raw_flow->'modules') m\n WHERE m->>'id' IN ('a', 'main')\n LIMIT 1),\n 'script'\n ) = 'flow' THEN 'flows' ELSE 'scripts' END\n END AS scope_kind,\n CASE WHEN j.trigger_kind = 'app' THEN j.trigger END AS launched_by_app\n FROM v2_job j JOIN chain c ON c.id = j.id,\n LATERAL (SELECT j.kind = 'flowpreview'\n AND j.raw_flow->'modules'->1 IS NULL\n AND j.raw_flow->'modules'->0->>'id' = '__wm_agent_root' AS agent) a\n WHERE j.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "runnable_path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "scope_kind",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "launched_by_app",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
null,
null,
null
]
},
"hash": "7904ac35748b1de2c7ff764f22787436d0fe74f99f298e5c399233f0d247f2ec"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE schedule SET\n schedule = $1,\n timezone = $2,\n args = $3,\n on_failure = $4,\n on_failure_times = $5,\n on_failure_exact = $6,\n on_failure_extra_args = $7,\n on_recovery = $8,\n on_recovery_times = $9,\n on_recovery_extra_args = $10,\n on_success = $11,\n on_success_extra_args = $12,\n ws_error_handler_muted = $13,\n retry = $14,\n summary = $15,\n no_flow_overlap = $16,\n tag = $17,\n paused_until = $18,\n path = $19,\n workspace_id = $20,\n cron_version = COALESCE($21, cron_version),\n description = $22,\n dynamic_skip = $23,\n email = $24,\n edited_by = $25,\n edited_at = now(),\n permissioned_as = $26,\n labels = COALESCE($27, labels)\n WHERE path = $19 AND workspace_id = $20\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip,\n labels\n ",
"query": "\n UPDATE schedule SET\n schedule = $1,\n timezone = $2,\n args = $3,\n on_failure = $4,\n on_failure_times = $5,\n on_failure_exact = $6,\n on_failure_extra_args = $7,\n on_recovery = $8,\n on_recovery_times = $9,\n on_recovery_extra_args = $10,\n on_success = $11,\n on_success_extra_args = $12,\n ws_error_handler_muted = $13,\n retry = $14,\n summary = $15,\n no_flow_overlap = $16,\n tag = $17,\n paused_until = $18,\n path = $19,\n workspace_id = $20,\n cron_version = COALESCE($21, cron_version),\n description = $22,\n dynamic_skip = $23,\n email = $24,\n edited_by = $25,\n edited_at = now(),\n permissioned_as = $26,\n labels = COALESCE($27, labels),\n late_run_streak = 0,\n missed_occurrences = 0,\n last_missed_at = NULL\n WHERE path = $19 AND workspace_id = $20\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip,\n labels,\n late_run_streak\n ",
"describe": {
"columns": [
{
@@ -167,6 +167,11 @@
"ordinal": 32,
"name": "labels",
"type_info": "TextArray"
},
{
"ordinal": 33,
"name": "late_run_streak",
"type_info": "Int4"
}
],
"parameters": {
@@ -233,8 +238,9 @@
true,
true,
true,
true
true,
false
]
},
"hash": "4d2c91937d87d385290b918af0238825cdd7784d0d611cd0069b15d11039ac56"
"hash": "793810c58d9e872da52d478db61c0a20126a207397cb9ed2dc18cd0b2baa57fa"
}
@@ -1,29 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT ws.workspace_id AS \"workspace_id!\", dt.key AS \"datatable!\"\n FROM workspace_settings ws\n JOIN workspace w ON w.id = ws.workspace_id AND w.deleted = false\n CROSS JOIN LATERAL jsonb_each(COALESCE(ws.datatable->'datatables', '{}'::jsonb)) dt\n WHERE ws.workspace_id <> $1\n AND dt.value->'database'->>'resource_type' = 'instance'\n AND dt.value->'database'->>'resource_path' = $2\n ORDER BY ws.workspace_id, dt.key\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "workspace_id!",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "datatable!",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
null
]
},
"hash": "79799b5a2e499df6c28e286c42b9ad2db940c2455ab19cc95e5198baf96d5629"
}
@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_queue SET suspend = $1 WHERE id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Uuid"
]
},
"nullable": []
},
"hash": "84ad6f24aa9ccdc6f6e705b7de1bb707637a9bb1475711f9f9f3f2a6e28cb169"
}
@@ -1,35 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "WITH RECURSIVE chain(id, parent_job) AS (\n SELECT id, parent_job FROM v2_job WHERE id = $1 AND workspace_id = $2\n UNION ALL\n SELECT j.id, j.parent_job FROM v2_job j\n JOIN chain c ON j.id = c.parent_job AND j.workspace_id = $2\n )\n SELECT j.runnable_path,\n CASE\n WHEN j.kind IN ('script', 'script_hub', 'unassigned_script') THEN 'scripts'\n WHEN j.kind IN ('flow', 'unassigned_flow') THEN 'flows'\n WHEN j.kind IN ('singlestepflow', 'unassigned_singlestepflow') THEN\n CASE WHEN COALESCE(\n (SELECT m->'value'->>'type'\n FROM jsonb_array_elements(j.raw_flow->'modules') m\n WHERE m->>'id' IN ('a', 'main')\n LIMIT 1),\n 'script'\n ) = 'flow' THEN 'flows' ELSE 'scripts' END\n END AS scope_kind,\n CASE WHEN j.trigger_kind = 'app' THEN j.trigger END AS launched_by_app\n FROM v2_job j JOIN chain c ON c.id = j.id\n WHERE j.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "runnable_path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "scope_kind",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "launched_by_app",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
true,
null,
null
]
},
"hash": "84fcddaf5bc61d607a6e6e5e31de7436b203a3baa7ef0509cb8e6c52270ae3a9"
}
@@ -1,17 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO datatable_role (id, name, enabled, pwd) VALUES ($1, $2, $3, $4)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Bool",
"Text"
]
},
"nullable": []
},
"hash": "86af9d51a158ea5cb6161461ecddf2a63695f8cbf8af648da5a0a77a5b9d02ba"
}
@@ -0,0 +1,32 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT 'schedule' AS \"kind!\", COUNT(*)::BIGINT AS \"count!\", 0::BIGINT AS \"suspended!\" FROM schedule\n UNION ALL SELECT 'http', COUNT(*)::BIGINT, COUNT(*) FILTER (WHERE mode = 'suspended')::BIGINT FROM http_trigger\n UNION ALL SELECT 'websocket', COUNT(*)::BIGINT, COUNT(*) FILTER (WHERE mode = 'suspended')::BIGINT FROM websocket_trigger\n UNION ALL SELECT 'kafka', COUNT(*)::BIGINT, COUNT(*) FILTER (WHERE mode = 'suspended')::BIGINT FROM kafka_trigger\n UNION ALL SELECT 'nats', COUNT(*)::BIGINT, COUNT(*) FILTER (WHERE mode = 'suspended')::BIGINT FROM nats_trigger\n UNION ALL SELECT 'postgres', COUNT(*)::BIGINT, COUNT(*) FILTER (WHERE mode = 'suspended')::BIGINT FROM postgres_trigger\n UNION ALL SELECT 'mqtt', COUNT(*)::BIGINT, COUNT(*) FILTER (WHERE mode = 'suspended')::BIGINT FROM mqtt_trigger\n UNION ALL SELECT 'sqs', COUNT(*)::BIGINT, COUNT(*) FILTER (WHERE mode = 'suspended')::BIGINT FROM sqs_trigger\n UNION ALL SELECT 'gcp', COUNT(*)::BIGINT, COUNT(*) FILTER (WHERE mode = 'suspended')::BIGINT FROM gcp_trigger\n UNION ALL SELECT 'azure', COUNT(*)::BIGINT, COUNT(*) FILTER (WHERE mode = 'suspended')::BIGINT FROM azure_trigger\n UNION ALL SELECT 'amqp', COUNT(*)::BIGINT, COUNT(*) FILTER (WHERE mode = 'suspended')::BIGINT FROM amqp_trigger\n UNION ALL SELECT 'email', COUNT(*)::BIGINT, COUNT(*) FILTER (WHERE mode = 'suspended')::BIGINT FROM email_trigger\n -- Grouped, not a single 'native' key: these fire as nextcloud/google/github,\n -- so a lone key would not line up with the `trigger`/`fired` series.\n UNION ALL SELECT service_name::text, COUNT(*)::BIGINT, 0::BIGINT FROM native_trigger GROUP BY service_name\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "kind!",
"type_info": "Text"
},
{
"ordinal": 1,
"name": "count!",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "suspended!",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null,
null,
null
]
},
"hash": "872c06b3098cf59797bcaa90d6da5c6acbf5468c190fa72b6b6707d950267752"
}
@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_queue SET suspend = GREATEST(suspend - 1, 0) WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "a35164456ade8e79cb8f5418c8fe82c45be45409f881292f0d4a3316362ba1f4"
}
@@ -1,20 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT jsonb_object_keys(value->'databases') FROM global_settings\n WHERE name = 'custom_instance_pg_databases'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "jsonb_object_keys",
"type_info": "Text"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "b9842d2d8abf382bd82d8fa1de012373638be391f884f81dc387ffc465badac6"
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT NOT starts_with(COALESCE(label, ''), 'impersonation:') FROM token\n WHERE token_hash = $1 AND (expiration IS NULL OR expiration > now())",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "be004608fc1de826803f266dfce96e09e634977478443b6372bca5e3dd0ce5ff"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE schedule SET\n enabled = $1,\n email = $2\n WHERE path = $3 AND workspace_id = $4\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip,\n labels\n ",
"query": "\n UPDATE schedule SET\n enabled = $1,\n email = $2,\n late_run_streak = 0,\n missed_occurrences = 0,\n last_missed_at = NULL\n WHERE path = $3 AND workspace_id = $4\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip,\n labels,\n late_run_streak\n ",
"describe": {
"columns": [
{
@@ -167,6 +167,11 @@
"ordinal": 32,
"name": "labels",
"type_info": "TextArray"
},
{
"ordinal": 33,
"name": "late_run_streak",
"type_info": "Int4"
}
],
"parameters": {
@@ -210,8 +215,9 @@
true,
true,
true,
true
true,
false
]
},
"hash": "642ca096c1c151fc01445593c8d2ed879a9a7be6ff044a9ce1b581ba725665fe"
"hash": "ca54e732d521416cb2692c8c4406c63922f0eebcba2b2cefc4290c4baee5c071"
}
@@ -0,0 +1,38 @@
{
"db_name": "PostgreSQL",
"query": "WITH active_users as (SELECT distinct username as email FROM audit_partitioned WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh') AND username NOT IN (SELECT email FROM usr WHERE is_service_account)),\n active_authors as (SELECT distinct t.email FROM usr t LEFT JOIN workspace_settings ws ON ws.workspace_id = t.workspace_id WHERE NOT (t.operator AND NOT COALESCE((ws.operator_settings->>'builder_flows')::boolean, false)) AND t.email IN (SELECT email FROM active_users)),\n active_authors_agg as (SELECT array_agg(email) as authors FROM active_authors),\n active_ops_agg as (SELECT array_agg(email) as operators from active_users WHERE email NOT IN (SELECT email FROM active_authors))\n SELECT active_authors_agg.authors, active_ops_agg.operators, array_length(active_authors_agg.authors, 1) as author_count, array_length(active_ops_agg.operators, 1) as operator_count FROM active_authors_agg, active_ops_agg",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "authors",
"type_info": "VarcharArray"
},
{
"ordinal": 1,
"name": "operators",
"type_info": "VarcharArray"
},
{
"ordinal": 2,
"name": "author_count",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "operator_count",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
null,
null,
null,
null
]
},
"hash": "cbc521b505c3953dc624ae6cefe6999e6d0c732da83ebc74ef7678cacd22ad6b"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE worker_ping SET ping_at = now(), jobs_executed = $1, custom_tags = $2,\n occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, vcpus = COALESCE($7, vcpus),\n memory = COALESCE($8, memory), occupancy_rate_15s = $9, occupancy_rate_5m = $10, occupancy_rate_30m = $11, native_mode = $12, ip = COALESCE($13, ip) WHERE worker = $6",
"query": "UPDATE worker_ping SET ping_at = now(), jobs_executed = $1, custom_tags = $2,\n occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, vcpus = COALESCE($7, vcpus),\n memory = COALESCE($8, memory), occupancy_rate_15s = $9, occupancy_rate_5m = $10, occupancy_rate_30m = $11, native_mode = $12, ip = COALESCE($13, ip),\n current_job_id = COALESCE($14, current_job_id), current_job_workspace_id = COALESCE($15, current_job_workspace_id) WHERE worker = $6",
"describe": {
"columns": [],
"parameters": {
@@ -17,10 +17,12 @@
"Float4",
"Float4",
"Bool",
"Varchar",
"Uuid",
"Varchar"
]
},
"nullable": []
},
"hash": "0c18351237816fe0c56e23801fcb8e70dbffcf08ed121e55c871f727c4ddf626"
"hash": "d267e03115ee74ab7ca7edd8a010e424819133ce38dab7300a5fca4c6f54536d"
}
@@ -1,24 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT dt.key AS \"datatable!\"\n FROM workspace_settings ws\n CROSS JOIN LATERAL jsonb_each(COALESCE(ws.datatable->'datatables', '{}'::jsonb)) dt\n WHERE ws.workspace_id = $1\n AND dt.key <> $2\n AND NOT dt.value ? 'permissions'\n AND dt.value->'database'->>'resource_type' = 'instance'\n AND dt.value->'database'->>'resource_path' = $3\n ORDER BY dt.key",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "datatable!",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "d48ca62c86b1af7a9dd2450c1c28dc45020a2a553d8874c49f9eafedea5a9d40"
}
@@ -1,38 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "WITH active_users as (SELECT distinct username as email FROM audit_partitioned WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),\n active_authors as (SELECT distinct email FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users)),\n active_authors_agg as (SELECT array_agg(email) as authors FROM active_authors),\n active_ops_agg as (SELECT array_agg(email) as operators from active_users WHERE email NOT IN (SELECT email FROM active_authors))\n SELECT active_authors_agg.authors, active_ops_agg.operators, array_length(active_authors_agg.authors, 1) as author_count, array_length(active_ops_agg.operators, 1) as operator_count FROM active_authors_agg, active_ops_agg",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "authors",
"type_info": "VarcharArray"
},
{
"ordinal": 1,
"name": "operators",
"type_info": "VarcharArray"
},
{
"ordinal": 2,
"name": "author_count",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "operator_count",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
null,
null,
null,
null
]
},
"hash": "dc37f9ad685a2f2bd781be8678e0548a860815f30e866362344434f23aa7c83c"
}
@@ -0,0 +1,95 @@
{
"db_name": "PostgreSQL",
"query": "SELECT q.id AS \"id!\", j.created_by, j.permissioned_as, j.permissioned_as_email, j.trigger, j.trigger_kind AS \"trigger_kind: TriggerKindLabel\", j.preprocessed, j.args AS \"args: sqlx::types::Json<HashMap<String, Box<RawValue>>>\" FROM v2_job_queue q JOIN v2_job j USING (id) JOIN script s ON s.workspace_id = j.workspace_id AND s.hash = j.runnable_id WHERE j.workspace_id = $1 AND j.runnable_path = $2 AND j.kind = 'script' AND j.flow_step_id IS NULL AND j.runnable_id != $3 AND q.canceled_by IS NULL AND s.restart_unless_cancelled",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "permissioned_as",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "permissioned_as_email",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "trigger",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "trigger_kind: TriggerKindLabel",
"type_info": {
"Custom": {
"name": "job_trigger_kind",
"kind": {
"Enum": [
"webhook",
"http",
"websocket",
"kafka",
"email",
"nats",
"schedule",
"app",
"ui",
"postgres",
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google",
"ci_test",
"github",
"azure",
"asset",
"freshness",
"amqp"
]
}
}
}
},
{
"ordinal": 6,
"name": "preprocessed",
"type_info": "Bool"
},
{
"ordinal": 7,
"name": "args: sqlx::types::Json<HashMap<String, Box<RawValue>>>",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
true,
true,
true,
true
]
},
"hash": "df8586283178b0e684b9a0e359efb16e38fd68a5fe2b01cd77f74a725bbf37a3"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS (SELECT 1 FROM v2_job_queue WHERE id = $1 AND workspace_id = $2 AND running)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
null
]
},
"hash": "ee769c78ec0a782de18911cdcb655aad93cf23321cd0f42f7ffa5348bfbea26e"
}
@@ -0,0 +1,125 @@
{
"db_name": "PostgreSQL",
"query": "WITH RECURSIVE lineage AS (\n SELECT id, parent_job, kind, runnable_path, runnable_id, trigger_kind, trigger,\n permissioned_as, args, 0 AS depth\n FROM v2_job WHERE id = $1 AND workspace_id = $2\n UNION ALL\n SELECT p.id, p.parent_job, p.kind, p.runnable_path, p.runnable_id, p.trigger_kind,\n p.trigger, p.permissioned_as, p.args, l.depth + 1\n FROM v2_job p JOIN lineage l ON p.id = l.parent_job\n WHERE p.workspace_id = $2 AND l.depth < 100\n )\n SELECT id AS \"id!\", parent_job, kind AS \"kind!: JobKind\", runnable_path, runnable_id,\n trigger_kind::text AS trigger_kind, permissioned_as AS \"permissioned_as!\",\n -- A restart takes its flow version from the request, so a trusted flow path\n -- can carry another flow's code: the version must belong to that path.\n CASE kind\n WHEN 'flow' THEN EXISTS (SELECT 1 FROM flow_version fv\n WHERE fv.id = runnable_id AND fv.path = runnable_path AND fv.workspace_id = $2)\n WHEN 'script' THEN EXISTS (SELECT 1 FROM script s\n WHERE s.hash = runnable_id AND s.path = runnable_path AND s.workspace_id = $2\n AND NOT s.deleted)\n ELSE true\n END AS \"origin_verified!\",\n -- What `run/f` and `run/p` resolve the path to now; for scripts the predicate of\n -- `get_latest_deployed_script_hash`.\n COALESCE(CASE kind\n WHEN 'flow' THEN runnable_id = (SELECT f.versions[array_upper(f.versions, 1)]\n FROM flow f WHERE f.path = runnable_path AND f.workspace_id = $2)\n WHEN 'script' THEN runnable_id = (SELECT s.hash FROM script s\n WHERE s.path = runnable_path AND s.workspace_id = $2 AND NOT s.deleted\n AND s.lock IS NOT NULL AND s.lock_error_logs IS NULL\n ORDER BY s.created_at DESC LIMIT 1)\n END, false) AS \"current_version!\",\n EXISTS (SELECT 1 FROM v2_job_status st WHERE st.id = lineage.id\n AND jsonb_typeof(st.flow_status->'restarted_from') = 'object')\n OR EXISTS (SELECT 1 FROM v2_job_completed c WHERE c.id = lineage.id\n AND jsonb_typeof(c.flow_status->'restarted_from') = 'object')\n AS \"restarted!\",\n COALESCE(\n (SELECT st.flow_status->'restarted_from'->>'flow_job_id' FROM v2_job_status st\n WHERE st.id = lineage.id),\n (SELECT c.flow_status->'restarted_from'->>'flow_job_id' FROM v2_job_completed c\n WHERE c.id = lineage.id)\n ) AS restarted_from,\n -- Only deployed-app runs are stamped with their app; an app editor preview\n -- runs app code at an app path it does not have to own.\n COALESCE(trigger_kind = 'app' AND starts_with(runnable_path, trigger || '/'), false)\n AS \"app_stamped!\",\n -- A preview's modules come from its args, which its parent may have taken from\n -- the caller.\n COALESCE(jsonb_typeof(args->'_MODULES') = 'object', false) AS \"args_modules!\"\n FROM lineage ORDER BY depth",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id!",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "parent_job",
"type_info": "Uuid"
},
{
"ordinal": 2,
"name": "kind!: JobKind",
"type_info": {
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlestepflow",
"flowscript",
"flownode",
"appscript",
"aiagent",
"unassigned_script",
"unassigned_flow",
"unassigned_singlestepflow"
]
}
}
}
},
{
"ordinal": 3,
"name": "runnable_path",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "runnable_id",
"type_info": "Int8"
},
{
"ordinal": 5,
"name": "trigger_kind",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "permissioned_as!",
"type_info": "Varchar"
},
{
"ordinal": 7,
"name": "origin_verified!",
"type_info": "Bool"
},
{
"ordinal": 8,
"name": "current_version!",
"type_info": "Bool"
},
{
"ordinal": 9,
"name": "restarted!",
"type_info": "Bool"
},
{
"ordinal": 10,
"name": "restarted_from",
"type_info": "Text"
},
{
"ordinal": 11,
"name": "app_stamped!",
"type_info": "Bool"
},
{
"ordinal": 12,
"name": "args_modules!",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null
]
},
"hash": "ee92100fcd681515ba81f23970e64af7f33464879bef4d1eff682126dffeef64"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value AS \"value: sqlx::types::Json<serde_json::Value>\"\n FROM resource WHERE workspace_id = $1 AND path = $2 AND resource_type = 'ai_agent'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "value: sqlx::types::Json<serde_json::Value>",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
true
]
},
"hash": "f3a740394038ddfced97254d8dcd4de49f63fb6dc4c8aab126dbe7cd611a862c"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_queue SET worker = $1, running = true, started_at = now()\n WHERE id = $2 AND tag = ANY($3)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Uuid",
"TextArray"
]
},
"nullable": []
},
"hash": "f5c87b6fc1decd6dc53590fe4d16f59430530b8b7428e42d1777927e63395ff8"
}
@@ -1,41 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n j.runnable_path as script_path, j.args AS \"args: sqlx::types::Json<HashMap<String, Box<RawValue>>>\",\n j.tag AS \"tag!\", j.priority\n FROM v2_job j\n WHERE j.id = $1 and j.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "args: sqlx::types::Json<HashMap<String, Box<RawValue>>>",
"type_info": "Jsonb"
},
{
"ordinal": 2,
"name": "tag!",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "priority",
"type_info": "Int2"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
true,
true,
false,
true
]
},
"hash": "f7753f8f7b40aebf66de607203f97681d06ac04f9d547b4c49c032d9e933a29e"
}
@@ -1,12 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM script WHERE path = $1 AND workspace_id = $2 RETURNING path",
"query": "DELETE FROM script WHERE path = $1 AND workspace_id = $2 RETURNING hash",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
"name": "hash",
"type_info": "Int8"
}
],
"parameters": {
@@ -19,5 +19,5 @@
false
]
},
"hash": "0e7d95f4913e5775651971d741a3b5c1ef5dfe079be5325abe2866d39a7fe5fb"
"hash": "fa85a22c77844bb2c538b802b0984d4d69aecdd8a65db02e9495b0d6011c5c1f"
}
+91 -79
View File
@@ -14792,7 +14792,7 @@ dependencies = [
[[package]]
name = "windmill"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-nats",
@@ -14876,11 +14876,12 @@ dependencies = [
"windmill-worker-volumes",
"windows-service",
"windows-sys 0.52.0",
"winresource",
]
[[package]]
name = "windmill-ai"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"async-stream",
"async-trait",
@@ -14914,7 +14915,7 @@ dependencies = [
[[package]]
name = "windmill-alerting"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -14927,7 +14928,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"argon2",
@@ -15067,7 +15068,7 @@ dependencies = [
[[package]]
name = "windmill-api-agent-workers"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -15090,7 +15091,7 @@ dependencies = [
[[package]]
name = "windmill-api-assets"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -15107,7 +15108,7 @@ dependencies = [
[[package]]
name = "windmill-api-auth"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"axum 0.8.9",
@@ -15133,7 +15134,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"reqwest 0.12.28",
"serde",
@@ -15143,7 +15144,7 @@ dependencies = [
[[package]]
name = "windmill-api-configs"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -15160,7 +15161,7 @@ dependencies = [
[[package]]
name = "windmill-api-debug"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"base64 0.22.1",
@@ -15182,7 +15183,7 @@ dependencies = [
[[package]]
name = "windmill-api-embeddings"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"axum 0.8.9",
@@ -15205,7 +15206,7 @@ dependencies = [
[[package]]
name = "windmill-api-flow-conversations"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -15221,7 +15222,7 @@ dependencies = [
[[package]]
name = "windmill-api-flows"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -15243,7 +15244,7 @@ dependencies = [
[[package]]
name = "windmill-api-groups"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -15265,7 +15266,7 @@ dependencies = [
[[package]]
name = "windmill-api-inputs"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -15279,7 +15280,7 @@ dependencies = [
[[package]]
name = "windmill-api-integration-tests"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-nats",
@@ -15315,7 +15316,7 @@ dependencies = [
[[package]]
name = "windmill-api-jobs"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"axum 0.8.9",
@@ -15340,7 +15341,7 @@ dependencies = [
[[package]]
name = "windmill-api-npm-proxy"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -15368,7 +15369,7 @@ dependencies = [
[[package]]
name = "windmill-api-openapi"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"axum 0.8.9",
@@ -15390,7 +15391,7 @@ dependencies = [
[[package]]
name = "windmill-api-schedule"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -15410,7 +15411,7 @@ dependencies = [
[[package]]
name = "windmill-api-scripts"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"axum 0.8.9",
@@ -15452,7 +15453,7 @@ dependencies = [
[[package]]
name = "windmill-api-settings"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"axum 0.8.9",
@@ -15481,7 +15482,7 @@ dependencies = [
[[package]]
name = "windmill-api-sse"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"lazy_static",
"serde",
@@ -15493,7 +15494,7 @@ dependencies = [
[[package]]
name = "windmill-api-users"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"argon2",
"axum 0.8.9",
@@ -15517,7 +15518,7 @@ dependencies = [
[[package]]
name = "windmill-api-workers"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -15531,7 +15532,7 @@ dependencies = [
[[package]]
name = "windmill-api-workspaces"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"axum 0.8.9",
"chrono",
@@ -15551,6 +15552,7 @@ dependencies = [
"strum",
"tokio",
"tokio-postgres",
"tokio-util",
"tracing",
"url",
"uuid",
@@ -15569,7 +15571,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"chrono",
"lazy_static",
@@ -15583,7 +15585,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"axum 0.8.9",
@@ -15602,7 +15604,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"aes-gcm",
"aho-corasick",
@@ -15660,6 +15662,7 @@ dependencies = [
"pin-project-lite",
"pkcs1",
"postgres-native-tls 0.5.3",
"postgres-protocol",
"prometheus",
"quick_cache",
"rand 0.9.5",
@@ -15714,7 +15717,7 @@ dependencies = [
[[package]]
name = "windmill-dep-map"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"chrono",
"futures",
@@ -15734,7 +15737,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"regex",
"serde",
@@ -15751,7 +15754,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"astral-tokio-tar",
@@ -15778,7 +15781,7 @@ dependencies = [
[[package]]
name = "windmill-jseval"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"futures",
@@ -15795,7 +15798,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -15811,7 +15814,7 @@ dependencies = [
[[package]]
name = "windmill-mcp"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -15832,7 +15835,7 @@ dependencies = [
[[package]]
name = "windmill-native-triggers"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -15863,7 +15866,7 @@ dependencies = [
[[package]]
name = "windmill-oauth"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -15888,7 +15891,7 @@ dependencies = [
[[package]]
name = "windmill-object-store"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-stream",
@@ -15924,7 +15927,7 @@ dependencies = [
[[package]]
name = "windmill-operator"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"futures",
@@ -15942,7 +15945,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -15951,7 +15954,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15963,7 +15966,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15975,7 +15978,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"gosyn",
@@ -15987,7 +15990,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15999,7 +16002,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"serde_json",
@@ -16011,7 +16014,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"nu-parser",
@@ -16022,7 +16025,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -16033,7 +16036,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -16045,7 +16048,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-asset"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -16056,7 +16059,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -16078,7 +16081,7 @@ dependencies = [
[[package]]
name = "windmill-parser-r"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"serde_json",
@@ -16090,7 +16093,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -16104,7 +16107,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -16121,7 +16124,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -16134,7 +16137,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql-asset"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"serde",
@@ -16146,7 +16149,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -16164,7 +16167,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts-asset"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"serde-wasm-bindgen",
@@ -16180,7 +16183,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wac"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -16196,7 +16199,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -16210,7 +16213,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -16249,7 +16252,7 @@ dependencies = [
[[package]]
name = "windmill-runtime-nativets"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"const_format",
@@ -16289,7 +16292,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"getrandom 0.3.4",
"wasm-bindgen",
@@ -16300,7 +16303,7 @@ dependencies = [
[[package]]
name = "windmill-store"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -16335,7 +16338,7 @@ dependencies = [
[[package]]
name = "windmill-test-utils"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16359,7 +16362,7 @@ dependencies = [
[[package]]
name = "windmill-trigger"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16392,7 +16395,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-amqp"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16419,7 +16422,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-azure"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16452,7 +16455,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-email"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16472,7 +16475,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-gcp"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16506,7 +16509,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-http"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16543,7 +16546,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-kafka"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16566,7 +16569,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-mqtt"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16590,7 +16593,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-nats"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-nats",
@@ -16614,7 +16617,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-postgres"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16649,7 +16652,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-sqs"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16677,7 +16680,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-websocket"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16702,7 +16705,7 @@ dependencies = [
[[package]]
name = "windmill-types"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"bitflags 2.13.2",
@@ -16721,7 +16724,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-once-cell",
@@ -16839,7 +16842,7 @@ dependencies = [
[[package]]
name = "windmill-worker-volumes"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"bytes",
"futures",
@@ -17380,6 +17383,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "winresource"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087"
dependencies = [
"version_check",
]
[[package]]
name = "winsafe"
version = "0.0.19"
+7 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.819.0"
version = "1.820.0"
authors.workspace = true
edition.workspace = true
@@ -88,7 +88,7 @@ members = [
exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"]
[workspace.package]
version = "1.819.0"
version = "1.820.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -329,6 +329,9 @@ strum.workspace = true
windows-service = "0.7"
windows-sys = { version = "0.52", features = ["Win32_System_Services", "Win32_System_Console", "Win32_Foundation"] }
[target.'cfg(windows)'.build-dependencies]
winresource = { version = "0.1.31", default-features = false }
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = { optional = true, workspace = true }
tikv-jemalloc-sys = { optional = true, workspace = true }
@@ -519,7 +522,7 @@ serde_urlencoded = "^0"
astral-tokio-tar = "^0.6.2"
tempfile = "^3"
x509-parser = "^0.16"
tokio-util = { version = "=0.7.17", features = ["io"] }
tokio-util = { version = "=0.7.17", features = ["io", "rt"] }
json-pointer = "^0"
itertools = "^0.14.0"
regex = "^1"
@@ -626,6 +629,7 @@ wasm-bindgen-test = "^0"
convert_case = "0.6.0"
getrandom = "0.2"
tokio-postgres = {version = "^0.7", features = ["array-impls", "with-serde_json-1", "with-chrono-0_4", "with-uuid-1", "with-bit-vec-0_6"]}
postgres-protocol = "0.6"
rust-postgres = { package = "tokio-postgres", git = "https://github.com/MaterializeInc/rust-postgres", rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe"}
rust-postgres-native-tls = { package = "postgres-native-tls", git = "https://github.com/MaterializeInc/rust-postgres", features = ["runtime"], rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe" }
bit-vec = "=0.6.3"
+15
View File
@@ -2,4 +2,19 @@
fn main() {
// trigger recompilation when a new migration is added
println!("cargo:rerun-if-changed=migrations");
// Embed a VERSIONINFO resource so the exe's file properties carry the release
// version (from Cargo.toml), which Windows worker update scripts read.
// `cfg(windows)` is the host, matching the Windows-only build-dependency, so
// cross builds to Windows get no version resource.
#[cfg(windows)]
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
winresource::WindowsResource::new()
.set("ProductName", "Windmill")
.set("FileDescription", "Windmill")
.set("CompanyName", "Windmill Labs, Inc.")
.set("LegalCopyright", "Copyright Windmill Labs, Inc.")
.compile()
.expect("failed to embed the Windows version resource");
}
}
+1 -1
View File
@@ -1 +1 @@
421cf2a8b4f98b421e93c0fc7c1c378314a66e50
ff4d04f17721d84fb9a1f655af50ff7e4e7dcbc7
@@ -0,0 +1,22 @@
-- Roles on the external cluster are live logins there; dropping the column would forget them.
LOCK TABLE datatable_role;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM datatable_role WHERE cluster <> 'instance') THEN
RAISE EXCEPTION 'datatable_role holds roles on the external instance cluster. Delete them in instance settings first.';
END IF;
-- Before this, only data tables on Windmill's own cluster could be under roles, and a role
-- block left with just `admin` survives deleting every external role.
IF EXISTS (
SELECT 1 FROM workspace_settings ws,
jsonb_each(CASE WHEN jsonb_typeof(ws.datatable->'datatables') = 'object'
THEN ws.datatable->'datatables' ELSE '{}'::jsonb END) dt
WHERE dt.value->'database'->>'resource_type' = 'external_instance'
AND dt.value ? 'permissions'
) THEN
RAISE EXCEPTION 'external instance data tables are still under roles. Turn their roles off first.';
END IF;
END $$;
ALTER TABLE datatable_role DROP CONSTRAINT datatable_role_cluster_name_key;
ALTER TABLE datatable_role ADD CONSTRAINT datatable_role_name_key UNIQUE (name);
ALTER TABLE datatable_role DROP COLUMN cluster;
@@ -0,0 +1,8 @@
-- A data table role is a Postgres login on one cluster: Windmill's own ('instance'), or the external
-- instance cluster ('external_instance'). Role names are the cluster's own key, so they are unique
-- per cluster rather than across the instance.
ALTER TABLE datatable_role
ADD COLUMN cluster VARCHAR(20) NOT NULL DEFAULT 'instance'
CHECK (cluster IN ('instance', 'external_instance'));
ALTER TABLE datatable_role DROP CONSTRAINT datatable_role_name_key;
ALTER TABLE datatable_role ADD CONSTRAINT datatable_role_cluster_name_key UNIQUE (cluster, name);
@@ -0,0 +1,3 @@
ALTER TABLE schedule DROP COLUMN last_missed_at;
ALTER TABLE schedule DROP COLUMN missed_occurrences;
ALTER TABLE schedule DROP COLUMN late_run_streak;
@@ -0,0 +1,7 @@
-- A late run finished (or, for flows, started) after the next occurrence was due, so that
-- occurrence was missed. late_run_streak counts late runs in a row and is reset by a run on
-- time; missed_occurrences is what the latest streak missed and last_missed_at when, both
-- kept after the streak ends so a recent one stays visible. An edit or toggle clears all.
ALTER TABLE schedule ADD COLUMN late_run_streak INTEGER NOT NULL DEFAULT 0;
ALTER TABLE schedule ADD COLUMN missed_occurrences INTEGER NOT NULL DEFAULT 0;
ALTER TABLE schedule ADD COLUMN last_missed_at TIMESTAMPTZ;
@@ -0,0 +1 @@
DROP TABLE IF EXISTS workspace_fork_creation;
@@ -0,0 +1,20 @@
-- A fork being created in the background, and how that ended. The fork's own workspace row only
-- exists once the copy commits, so this is where a failure is reported to whoever polls for it.
-- `heartbeat_at` is refreshed while the copy runs: an unfinished row that stops being refreshed
-- belongs to a server that went away mid-copy. `step` names the part of the copy it is in.
-- `creation_id` names one attempt: the row is reused when a failed or abandoned one is retried, and
-- whoever polls reads only the attempt it started.
CREATE TABLE workspace_fork_creation (
fork_workspace_id VARCHAR(50) PRIMARY KEY,
parent_workspace_id VARCHAR(50) NOT NULL,
creation_id UUID NOT NULL,
created_by VARCHAR(255) NOT NULL,
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
heartbeat_at TIMESTAMPTZ NOT NULL DEFAULT now(),
step TEXT,
finished_at TIMESTAMPTZ,
error TEXT
);
GRANT ALL ON workspace_fork_creation TO windmill_user;
GRANT ALL ON workspace_fork_creation TO windmill_admin;
+86 -28
View File
@@ -18,7 +18,32 @@
"slack": {
"auth_url": "https://slack.com/oauth/v2/authorize",
"token_url": "https://slack.com/api/oauth.v2.access",
"scopes": ["chat:write", "chat:write.public", "channels:join", "files:write"]
"scopes": [
"chat:write",
"chat:write.public",
"channels:join",
"files:write",
"users:read",
"users:read.email",
"channels:read",
"channels:history"
],
"user_scopes": [
"channels:history",
"groups:history",
"im:history",
"mpim:history",
"search:read",
"users:read",
"chat:write",
"users:read.email",
"channels:read",
"groups:read",
"im:read",
"mpim:read",
"files:read"
],
"token_response_path": "authed_user"
},
"supabase_wizard": {
"auth_url": "https://api.supabase.com/v1/oauth/authorize",
@@ -30,8 +55,8 @@
"token_url": "https://oauth2.googleapis.com/token",
"scopes": ["https://www.googleapis.com/auth/spreadsheets"],
"scope_options": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/spreadsheets.readonly"
{ "value": "https://www.googleapis.com/auth/spreadsheets", "label": "Read, edit, create and delete spreadsheets" },
{ "value": "https://www.googleapis.com/auth/spreadsheets.readonly", "label": "Read spreadsheets" }
],
"extra_params": {
"access_type": "offline",
@@ -43,9 +68,9 @@
"token_url": "https://oauth2.googleapis.com/token",
"scopes": ["https://www.googleapis.com/auth/drive"],
"scope_options": [
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive"
{ "value": "https://www.googleapis.com/auth/drive.file", "label": "Only files created or opened with this connection" },
{ "value": "https://www.googleapis.com/auth/drive.readonly", "label": "Read all Drive files" },
{ "value": "https://www.googleapis.com/auth/drive", "label": "Read, edit, create and delete all Drive files" }
],
"extra_params": {
"access_type": "offline",
@@ -57,11 +82,11 @@
"token_url": "https://oauth2.googleapis.com/token",
"scopes": ["https://www.googleapis.com/auth/gmail.send"],
"scope_options": [
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/gmail.labels"
{ "value": "https://www.googleapis.com/auth/gmail.send", "label": "Send mail only" },
{ "value": "https://www.googleapis.com/auth/gmail.readonly", "label": "Read and search mail" },
{ "value": "https://www.googleapis.com/auth/gmail.compose", "label": "Draft and send mail" },
{ "value": "https://www.googleapis.com/auth/gmail.modify", "label": "Read, search, label, draft and send mail" },
{ "value": "https://www.googleapis.com/auth/gmail.labels", "label": "Create, edit and delete labels" }
],
"extra_params": {
"access_type": "offline",
@@ -73,10 +98,10 @@
"token_url": "https://oauth2.googleapis.com/token",
"scopes": ["https://www.googleapis.com/auth/calendar.events"],
"scope_options": [
"https://www.googleapis.com/auth/calendar.events",
"https://www.googleapis.com/auth/calendar.events.readonly",
"https://www.googleapis.com/auth/calendar.readonly",
"https://www.googleapis.com/auth/calendar"
{ "value": "https://www.googleapis.com/auth/calendar.events", "label": "Read and edit events on all calendars" },
{ "value": "https://www.googleapis.com/auth/calendar.events.readonly", "label": "Read events on all calendars" },
{ "value": "https://www.googleapis.com/auth/calendar.readonly", "label": "Read all calendars and events" },
{ "value": "https://www.googleapis.com/auth/calendar", "label": "Read, edit, share and delete all calendars" }
],
"extra_params": {
"access_type": "offline",
@@ -88,10 +113,43 @@
"token_url": "https://oauth2.googleapis.com/token",
"scopes": ["https://www.googleapis.com/auth/forms"],
"scope_options": [
"https://www.googleapis.com/auth/forms",
"https://www.googleapis.com/auth/forms.body",
"https://www.googleapis.com/auth/forms.body.readonly",
"https://www.googleapis.com/auth/forms.responses.readonly"
{ "value": "https://www.googleapis.com/auth/forms", "label": "Read and manage forms in Drive" },
{ "value": "https://www.googleapis.com/auth/forms.body", "label": "Read, edit, create and delete forms" },
{ "value": "https://www.googleapis.com/auth/forms.body.readonly", "label": "Read forms" },
{ "value": "https://www.googleapis.com/auth/forms.responses.readonly", "label": "Read form responses" }
],
"extra_params": {
"access_type": "offline",
"prompt": "consent"
}
},
"gdocs": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"scopes": ["https://www.googleapis.com/auth/documents"],
"scope_options": [
{ "value": "https://www.googleapis.com/auth/documents", "label": "Read, edit, create and delete documents" },
{ "value": "https://www.googleapis.com/auth/documents.readonly", "label": "Read documents" },
{ "value": "https://www.googleapis.com/auth/drive.file", "label": "Only files created or opened with this connection" }
],
"extra_params": {
"access_type": "offline",
"prompt": "consent"
}
},
"gchat": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"scopes": [
"https://www.googleapis.com/auth/chat.spaces.readonly",
"https://www.googleapis.com/auth/chat.messages.create"
],
"scope_options": [
{ "value": "https://www.googleapis.com/auth/chat.spaces.readonly", "label": "Read spaces" },
{ "value": "https://www.googleapis.com/auth/chat.messages", "label": "Read, send, edit and delete messages" },
{ "value": "https://www.googleapis.com/auth/chat.messages.readonly", "label": "Read messages" },
{ "value": "https://www.googleapis.com/auth/chat.messages.create", "label": "Send messages only" },
{ "value": "https://www.googleapis.com/auth/chat.memberships.readonly", "label": "Read space members" }
],
"extra_params": {
"access_type": "offline",
@@ -103,8 +161,8 @@
"token_url": "https://oauth2.googleapis.com/token",
"scopes": ["https://www.googleapis.com/auth/cloud-platform"],
"scope_options": [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/cloud-platform.read-only"
{ "value": "https://www.googleapis.com/auth/cloud-platform", "label": "Read and manage all Google Cloud resources" },
{ "value": "https://www.googleapis.com/auth/cloud-platform.read-only", "label": "Read Google Cloud resources" }
],
"extra_params": {
"access_type": "offline",
@@ -121,13 +179,13 @@
"https://www.googleapis.com/auth/admin.directory.orgunit"
],
"scope_options": [
"https://www.googleapis.com/auth/admin.directory.user",
"https://www.googleapis.com/auth/admin.directory.user.readonly",
"https://www.googleapis.com/auth/admin.directory.group",
"https://www.googleapis.com/auth/admin.directory.group.readonly",
"https://www.googleapis.com/auth/admin.directory.orgunit",
"https://www.googleapis.com/auth/admin.directory.orgunit.readonly",
"https://www.googleapis.com/auth/admin.directory.user.security"
{ "value": "https://www.googleapis.com/auth/admin.directory.user", "label": "Read and manage users" },
{ "value": "https://www.googleapis.com/auth/admin.directory.user.readonly", "label": "Read users" },
{ "value": "https://www.googleapis.com/auth/admin.directory.group", "label": "Read and manage groups" },
{ "value": "https://www.googleapis.com/auth/admin.directory.group.readonly", "label": "Read groups" },
{ "value": "https://www.googleapis.com/auth/admin.directory.orgunit", "label": "Read and manage organizational units" },
{ "value": "https://www.googleapis.com/auth/admin.directory.orgunit.readonly", "label": "Read organizational units" },
{ "value": "https://www.googleapis.com/auth/admin.directory.user.security", "label": "Manage user security settings" }
],
"extra_params": {
"access_type": "offline",
+24 -24
View File
@@ -6216,7 +6216,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill-common"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"aho-corasick",
"anyhow",
@@ -6302,7 +6302,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"proc-macro2",
"quote",
@@ -6314,7 +6314,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"convert_case",
"serde",
@@ -6323,7 +6323,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6335,7 +6335,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"serde_json",
@@ -6347,7 +6347,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"gosyn",
@@ -6359,7 +6359,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6371,7 +6371,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"serde_json",
@@ -6383,7 +6383,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"nu-parser",
@@ -6394,7 +6394,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -6405,7 +6405,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -6417,7 +6417,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-asset"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -6428,7 +6428,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -6450,7 +6450,7 @@ dependencies = [
[[package]]
name = "windmill-parser-r"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"serde_json",
@@ -6462,7 +6462,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6476,7 +6476,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"convert_case",
@@ -6493,7 +6493,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6506,7 +6506,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql-asset"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"serde",
@@ -6518,7 +6518,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6536,7 +6536,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts-asset"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"serde-wasm-bindgen",
@@ -6552,7 +6552,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wac"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -6568,7 +6568,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"getrandom 0.2.17",
@@ -6600,7 +6600,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -6614,7 +6614,7 @@ dependencies = [
[[package]]
name = "windmill-types"
version = "1.819.0"
version = "1.820.0"
dependencies = [
"anyhow",
"bitflags",
@@ -12,7 +12,7 @@ resolver = "2"
members = ["."]
[workspace.package]
version = "1.819.0"
version = "1.820.0"
edition = "2021"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
+11
View File
@@ -1232,6 +1232,11 @@ Windmill Community Edition {GIT_VERSION}
default_base_internal_url.clone()
};
// Deletions are delivered by `notify_event` to running processes only, so one that
// was down when a version was deleted would keep its code on disk: start from an
// empty script cache, refilled from the database.
windmill_common::cache::script::clear();
initial_load(
&conn,
killpill_tx.clone(),
@@ -1884,6 +1889,12 @@ async fn process_notify_event(
);
windmill_common::workspaces::PUBLIC_APP_RATE_LIMIT_CACHE.remove(payload);
}
windmill_common::SCRIPT_VERSION_DELETED_CHANNEL => {
match serde_json::from_str(payload) {
Ok(deleted) => windmill_api_scripts::scripts::evict_deleted_script_versions(deleted),
Err(e) => tracing::error!("Invalid script version deletion payload {payload}: {e}"),
}
}
"notify_runnable_version_change" => {
tracing::info!("Runnable version change detected: {}", payload);
match payload.split(':').collect::<Vec<&str>>().as_slice() {
+220
View File
@@ -0,0 +1,220 @@
use axum::{
extract::State,
routing::{get, post},
Json, Router,
};
use serde_json::{json, Value};
use sqlx::{Pool, Postgres};
use std::{sync::Arc, time::Duration};
use tokio::sync::Notify;
use windmill_common::{flows::FlowValue, jobs::JobPayload};
use windmill_test_utils::{
completed_job, in_test_worker, listen_for_completed_jobs, ApiServer, RunJob, StreamFind,
};
async fn model(Json(body): Json<Value>) -> ([(&'static str, &'static str); 1], String) {
let finished = body["messages"]
.as_array()
.unwrap()
.iter()
.any(|m| m["role"] == "tool");
let delta = if finished {
json!({"role":"assistant", "content":"done"})
} else {
json!({"role":"assistant", "tool_calls": (0..3).map(|i| json!({
"index":i, "id":format!("call_{i}"), "type":"function",
"function":{"name":format!("tool_{i}"), "arguments":"{}"}
})).collect::<Vec<_>>()})
};
let event = json!({"choices":[{"index":0, "delta":delta, "finish_reason":null}]});
let end = json!({"choices":[{"index":0, "delta":{}, "finish_reason":if finished {"stop"} else {"tool_calls"}}]});
(
[("content-type", "text/event-stream")],
format!("data: {event}\n\ndata: {end}\n\ndata: [DONE]\n\n"),
)
}
async fn run_batch(
db: Pool<Postgres>,
parallel: bool,
limited: bool,
unsupported: bool,
) -> anyhow::Result<()> {
std::env::set_var("ALLOW_PRIVATE_AI_BASE_URLS", "true");
let server = ApiServer::start(db.clone()).await?;
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
let base = format!("http://{}", listener.local_addr()?);
let gate = Arc::new(Notify::new());
let router = Router::new()
.route("/v1/chat/completions", post(model))
.route(
"/first",
get(|State(gate): State<Arc<Notify>>| async move {
gate.notified().await;
"ok"
}),
)
.route(
"/second",
get(|State(gate): State<Arc<Notify>>| async move {
gate.notify_one();
"ok"
}),
)
.with_state(gate);
let stub = tokio::spawn(async move {
axum::serve(listener, router).await.unwrap();
});
let tools: Vec<Value> = (0..3).map(|i| {
let wait = if parallel && !limited && i < 2 {
format!("await fetch('{base}/{}');", if i == 0 {"first"} else {"second"})
} else { String::new() };
let finish = if i == 2 { "throw new Error('expected tool failure');".to_string() }
else { format!("return {i};") };
json!({"id":format!("t{i}"),"summary":format!("tool_{i}"),"value":{
"type":"rawscript", "language":"bun", "input_transforms":{},
"tag": if unsupported && i != 1 { "unserved-tool-tag" } else { "bun" },
"concurrent_limit": if limited { Some(1) } else { None },
"custom_concurrency_key": if limited { Some("agent-tool-test") } else { None },
"content":format!("export async function main() {{ {wait} await Bun.sleep({}); {finish} }}", if i == 0 {200} else {0})
}})
}).collect();
let flow: FlowValue = serde_json::from_value(
json!({"modules":[{"id":"agent","timeout":if unsupported {Some(3)} else {None},"value":{
"type":"aiagent", "tools":tools, "input_transforms":{
"provider":{"type":"static","value":{"kind":"customai","model":"queue-test","resource":{"base_url":format!("{base}/v1")}}},
"user_message":{"type":"static","value":"run the tools"},
"max_iterations":{"type":"static","value":3}
}
}}]}),
)?;
let id = RunJob::from(JobPayload::RawFlow {
value: flow,
path: Some("u/test/agent_queue".into()),
restarted_from: None,
})
.push(&db)
.await;
let notifications = listen_for_completed_jobs(&db).await;
let wait = async {
if parallel {
in_test_worker(&db, notifications.find(&id), server.addr.port()).await;
} else {
notifications.find(&id).await;
}
};
tokio::time::timeout(
Duration::from_secs(45),
in_test_worker(&db, wait, server.addr.port()),
)
.await?;
let result = completed_job(id, &db).await;
if unsupported {
assert!(
!result.success,
"agent must time out waiting for unsupported tools"
);
let children: Vec<(String, bool, bool)> = sqlx::query_as(
"SELECT j.runnable_path, c.status = 'success', c.started_at IS NULL
FROM v2_job j JOIN v2_job_completed c USING(id)
WHERE j.parent_job IN (SELECT id FROM v2_job WHERE parent_job = $1)
ORDER BY j.runnable_path",
)
.bind(id)
.fetch_all(&db)
.await?;
assert_eq!(children.len(), 3);
assert_eq!(
children.iter().map(|c| (c.1, c.2)).collect::<Vec<_>>(),
vec![(false, true), (true, false), (false, true)],
"neither the first-tool reservation nor fallback may execute unsupported tags"
);
stub.abort();
server.close().await?;
return Ok(());
}
assert!(result.success, "{:?}", result.result);
let result = result.json_result().expect("agent result");
let messages: Vec<_> = result["messages"]
.as_array()
.unwrap()
.iter()
.filter(|m| m["role"] == "tool")
.collect();
assert_eq!(messages.len(), 3);
for (index, message) in messages.iter().enumerate() {
assert_eq!(message["tool_call_id"], format!("call_{index}"));
}
assert_eq!(messages[0]["content"], "0");
assert_eq!(messages[1]["content"], "1");
assert!(messages[2]["content"]
.as_str()
.unwrap()
.contains("expected tool failure"));
let (parent_id, parent_worker): (uuid::Uuid, String) = sqlx::query_as(
"SELECT j.id, c.worker FROM v2_job j JOIN v2_job_completed c USING(id) WHERE j.parent_job = $1"
).bind(id).fetch_one(&db).await?;
let children: Vec<(String, String, bool)> = sqlx::query_as(
"SELECT j.runnable_path, c.worker, c.status = 'success' FROM v2_job j JOIN v2_job_completed c USING(id) WHERE j.parent_job = $1 ORDER BY j.runnable_path"
).bind(parent_id).fetch_all(&db).await?;
assert_eq!(children.len(), 3);
assert_eq!(
children[0].1, parent_worker,
"first tool must stay on the parent worker"
);
assert_eq!(
children.iter().map(|c| c.2).collect::<Vec<_>>(),
vec![true, true, false]
);
if limited {
let overlapping: i64 = sqlx::query_scalar(
"SELECT count(*) FROM v2_job j1 JOIN v2_job_completed c1 ON c1.id = j1.id
JOIN v2_job j2 ON j2.parent_job = j1.parent_job AND j2.id > j1.id
JOIN v2_job_completed c2 ON c2.id = j2.id
WHERE j1.parent_job = $1 AND c1.started_at < c2.completed_at AND c2.started_at < c1.completed_at"
).bind(parent_id).fetch_one(&db).await?;
assert_eq!(
overlapping, 0,
"the reserved first job must count toward the shared limit"
);
} else if parallel {
assert_ne!(
children[1].1, parent_worker,
"second tool must unblock the first from another worker"
);
} else {
assert!(children.iter().all(|child| child.1 == parent_worker));
}
stub.abort();
server.close().await?;
Ok(())
}
#[sqlx::test(fixtures("base"))]
#[serial_test::serial]
async fn parent_drains_tools_without_another_worker(db: Pool<Postgres>) -> anyhow::Result<()> {
run_batch(db, false, false, false).await
}
#[sqlx::test(fixtures("base"))]
#[serial_test::serial]
async fn parent_keeps_first_tool_while_another_worker_runs_siblings(
db: Pool<Postgres>,
) -> anyhow::Result<()> {
run_batch(db, true, false, false).await
}
#[cfg(all(feature = "enterprise", feature = "private"))]
#[sqlx::test(fixtures("base"))]
#[serial_test::serial]
async fn reserved_tool_obeys_shared_concurrency_limit(db: Pool<Postgres>) -> anyhow::Result<()> {
run_batch(db, true, true, false).await
}
#[sqlx::test(fixtures("base"))]
#[serial_test::serial]
async fn parent_leaves_unsupported_first_and_remaining_tools_queued(
db: Pool<Postgres>,
) -> anyhow::Result<()> {
run_batch(db, false, false, true).await
}
+110
View File
@@ -0,0 +1,110 @@
//! A caller's `_MODULES` never reaches a job: the worker builds that arg in as module code,
//! so on a deployed runnable it would run caller code as the runnable (and its
//! `on_behalf_of`). `push` drops it from `args` and `extra` alike and only sets it from a
//! preview's own `RawCode::modules`.
use std::collections::HashMap;
use serde_json::{json, value::RawValue};
use sqlx::{Pool, Postgres};
use windmill_common::{
jobs::{JobPayload, RawCode},
runnable_settings::{ConcurrencySettings, DebouncingSettings},
scripts::{ScriptHash, ScriptLang, ScriptModule},
};
use windmill_queue::{PushArgs, PushIsolationLevel};
fn modules(v: &str) -> serde_json::Value {
json!({ "helper.ts": { "content": format!("export const v = \"{v}\""), "language": "bun" } })
}
/// Pushes `payload` with a caller `_MODULES` both in `args` and in `extra` (where webhook
/// query and headers land), and returns the stored `_MODULES`.
async fn stored_modules(db: &Pool<Postgres>, payload: JobPayload) -> Option<serde_json::Value> {
let caller: Box<RawValue> = serde_json::value::to_raw_value(&modules("caller")).unwrap();
let args = HashMap::from([("_MODULES".to_string(), caller.clone())]);
let extra = HashMap::from([("_MODULES".to_string(), caller)]);
let (id, tx) = windmill_queue::push(
db,
PushIsolationLevel::IsolatedRoot(db.clone()),
"test-workspace",
payload,
PushArgs { args: &args, extra: Some(extra) },
"test-user",
"test@windmill.dev",
"u/test-user".to_string(),
None,
None,
None,
None,
None,
None,
None,
None,
false,
false,
None,
true,
None,
None,
None,
None,
None,
false,
None,
None,
None,
)
.await
.expect("push must succeed");
tx.commit().await.unwrap();
sqlx::query_scalar::<_, Option<serde_json::Value>>(
"SELECT args->'_MODULES' FROM v2_job WHERE id = $1",
)
.bind(id)
.fetch_one(db)
.await
.unwrap()
}
#[sqlx::test(fixtures("base"))]
async fn caller_modules_never_reach_a_job(db: Pool<Postgres>) {
let deployed = JobPayload::ScriptHash {
hash: ScriptHash(123412),
path: "f/system/hello".to_string(),
cache_ttl: None,
cache_ignore_s3_path: None,
dedicated_worker: None,
language: ScriptLang::Bun,
priority: None,
apply_preprocessor: false,
concurrency_settings: ConcurrencySettings::default(),
debouncing_settings: DebouncingSettings::default(),
labels: None,
};
assert_eq!(stored_modules(&db, deployed).await, None);
let preview = |modules: Option<HashMap<String, ScriptModule>>| {
JobPayload::Code(RawCode {
hash: None,
content: "import { v } from \"./helper\"; export function main() { return v }"
.to_string(),
path: None,
language: ScriptLang::Bun,
lock: None,
concurrency_settings: ConcurrencySettings::default().into(),
debouncing_settings: DebouncingSettings::default(),
cache_ttl: None,
cache_ignore_s3_path: None,
dedicated_worker: None,
modules,
tag: None,
})
};
assert_eq!(stored_modules(&db, preview(None)).await, None);
let own = serde_json::from_value(modules("preview")).unwrap();
assert_eq!(
stored_modules(&db, preview(Some(own))).await,
Some(modules("preview"))
);
}
@@ -0,0 +1,40 @@
//! Turning Windmill's own Postgres off as a data table substrate takes the data tables already on
//! it out of use, not only new ones: resolution is the one path every job, API call and trigger
//! reaches a data table through.
use sqlx::{Pool, Postgres};
use windmill_common::workspaces::get_datatable_resource_from_db_unchecked;
#[sqlx::test]
async fn test_instance_datatable_refused_once_instance_pg_disabled(
db: Pool<Postgres>,
) -> anyhow::Result<()> {
sqlx::query("INSERT INTO workspace (id, name, owner) VALUES ('dtoff-ws', 'DTOFF', 'dtoff')")
.execute(&db)
.await?;
sqlx::query(
"INSERT INTO workspace_settings (workspace_id, datatable) VALUES ('dtoff-ws', \
'{\"datatables\": {\"main\": {\"database\": {\"resource_type\": \"instance\", \
\"resource_path\": \"dt_dtoff\"}}}}')",
)
.execute(&db)
.await?;
let resolved = get_datatable_resource_from_db_unchecked(&db, "dtoff-ws", "main").await?;
assert_eq!(resolved["dbname"], "dt_dtoff");
sqlx::query(
"INSERT INTO global_settings (name, value) VALUES ('instance_pg_disabled', 'true')",
)
.execute(&db)
.await?;
let refused = get_datatable_resource_from_db_unchecked(&db, "dtoff-ws", "main")
.await
.expect_err("an instance data table must not resolve while Windmill's database is off");
assert!(
refused.to_string().contains("disabled on this instance"),
"unexpected error: {refused}"
);
Ok(())
}
+56
View File
@@ -0,0 +1,56 @@
//! Script data is cached by hash, and the hash leaves out the workspace, so forks and clones
//! share it: a deleted copy in one workspace must not stop the live copy in another.
use sqlx::{Pool, Postgres};
use windmill_common::{cache, scripts::ScriptHash, worker::Connection};
#[sqlx::test(fixtures("base"))]
async fn a_deleted_copy_does_not_shadow_a_live_one(db: Pool<Postgres>) {
const SHARED: i64 = 0x5de1_e7ed_0002;
sqlx::query("INSERT INTO workspace (id, name, owner) VALUES ('fork', 'fork', 'test-user')")
.execute(&db)
.await
.unwrap();
sqlx::query(
"INSERT INTO script (workspace_id, hash, path, summary, description, content,
created_by, language, lock, archived, deleted)
VALUES ('fork', $1, 'f/infra/tool', '', '', '', 'test-user', 'bash', '', true, true),
('test-workspace', $1, 'f/infra/tool', '', '', 'echo shared', 'test-user',
'bash', '', false, false)",
)
.bind(SHARED)
.execute(&db)
.await
.unwrap();
let conn = Connection::Sql(db.clone());
let (data, _) = cache::script::fetch(&conn, ScriptHash(SHARED))
.await
.unwrap();
assert_eq!(data.code, "echo shared");
}
#[sqlx::test(fixtures("base"))]
async fn a_large_deletion_is_split_across_events(db: Pool<Postgres>) {
let deleted = windmill_common::DeletedScriptVersions::new(
"test-workspace",
(0..501).map(|h| (format!("f/infra/s{}", h % 2), h)),
);
let mut conn = db.acquire().await.unwrap();
deleted.notify(&mut conn).await.unwrap();
let events: Vec<String> = sqlx::query_scalar(
"SELECT payload FROM notify_event WHERE channel = $1 ORDER BY id",
)
.bind(windmill_common::SCRIPT_VERSION_DELETED_CHANNEL)
.fetch_all(&db)
.await
.unwrap();
let events: Vec<windmill_common::DeletedScriptVersions> =
events.iter().map(|e| serde_json::from_str(e).unwrap()).collect();
assert_eq!(events.len(), 2);
let hashes: Vec<i64> = events.iter().flat_map(|e| e.hashes.clone()).collect();
let paths: Vec<String> = events.iter().flat_map(|e| e.paths.clone()).collect();
assert_eq!(hashes, deleted.hashes);
assert_eq!(paths, deleted.paths);
}
+31
View File
@@ -30,3 +30,34 @@ INSERT INTO v2_job (id, workspace_id, kind, runnable_path, trigger_kind, trigger
VALUES
('3bb0c0de-0000-4000-8000-000000000007', 'test-workspace', 'appscript', 'f/t/app/comp', 'app', 'f/t/app', 'test-user', 'u/test-user', 'test@windmill.dev'),
('3bb0c0de-0000-4000-8000-000000000008', 'test-workspace', 'appscript', 'f/t/app/comp', NULL, NULL, 'test-user', 'u/test-user', 'test@windmill.dev');
-- Inline steps of the deployed flow f/t/agent run as previews (no flow node, or
-- DISABLE_FLOW_SCRIPT): a direct step, a step in a loop body, and a step whose path
-- leaves the flow's namespace.
INSERT INTO v2_job (id, workspace_id, kind, runnable_path, parent_job, created_by, permissioned_as, permissioned_as_email)
VALUES
('3bb0c0de-0000-4000-8000-000000000009', 'test-workspace', 'preview', 'f/t/agent/a', '3bb0c0de-0000-4000-8000-000000000001', 'test-user', 'u/test-user', 'test@windmill.dev'),
('3bb0c0de-0000-4000-8000-00000000000a', 'test-workspace', 'flowpreview', 'f/t/agent/loop-0', '3bb0c0de-0000-4000-8000-000000000001', 'test-user', 'u/test-user', 'test@windmill.dev'),
('3bb0c0de-0000-4000-8000-00000000000b', 'test-workspace', 'preview', 'f/t/agent/loop-0/a', '3bb0c0de-0000-4000-8000-00000000000a', 'test-user', 'u/test-user', 'test@windmill.dev'),
('3bb0c0de-0000-4000-8000-00000000000c', 'test-workspace', 'preview', 'f/t/agentx', '3bb0c0de-0000-4000-8000-000000000001', 'test-user', 'u/test-user', 'test@windmill.dev');
-- Steps of the flow preview claiming f/t/agent: one nested in a branch, and one whose
-- path names another item.
INSERT INTO v2_job (id, workspace_id, kind, runnable_path, parent_job, created_by, permissioned_as, permissioned_as_email)
VALUES
('3bb0c0de-0000-4000-8000-00000000000d', 'test-workspace', 'flowpreview', 'f/t/agent/branchone-1', '3bb0c0de-0000-4000-8000-000000000003', 'test-user', 'u/test-user', 'test@windmill.dev'),
('3bb0c0de-0000-4000-8000-00000000000e', 'test-workspace', 'preview', 'f/t/agent/branchone-1/a', '3bb0c0de-0000-4000-8000-00000000000d', 'test-user', 'u/test-user', 'test@windmill.dev'),
('3bb0c0de-0000-4000-8000-00000000000f', 'test-workspace', 'preview', 'f/prod/deploy', '3bb0c0de-0000-4000-8000-000000000003', 'test-user', 'u/test-user', 'test@windmill.dev');
-- A deployed app without app_script entries runs its inline script as a stamped preview.
INSERT INTO v2_job (id, workspace_id, kind, runnable_path, trigger_kind, trigger, created_by, permissioned_as, permissioned_as_email)
VALUES
('3bb0c0de-0000-4000-8000-000000000010', 'test-workspace', 'preview', 'f/t/app/comp', 'app', 'f/t/app', 'test-user', 'u/test-user', 'test@windmill.dev');
-- A step of the deployed flow whose modules came in its args, and a flow preview at the
-- bare `f` whose step names another item.
INSERT INTO v2_job (id, workspace_id, kind, runnable_path, parent_job, args, created_by, permissioned_as, permissioned_as_email)
VALUES
('3bb0c0de-0000-4000-8000-000000000011', 'test-workspace', 'preview', 'f/t/agent/m', '3bb0c0de-0000-4000-8000-000000000001', '{"_MODULES": {}}', 'test-user', 'u/test-user', 'test@windmill.dev'),
('3bb0c0de-0000-4000-8000-000000000012', 'test-workspace', 'flowpreview', 'f', NULL, NULL, 'test-user', 'u/test-user', 'test@windmill.dev'),
('3bb0c0de-0000-4000-8000-000000000013', 'test-workspace', 'preview', 'f/prod/deploy', '3bb0c0de-0000-4000-8000-000000000012', NULL, 'test-user', 'u/test-user', 'test@windmill.dev');

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