Commit Graph

13639 Commits

Author SHA1 Message Date
Ruben Fiszel e9f016cf66 fix: serialize concurrent python wheel-cache installs to prevent flaky ModuleNotFoundError
The wheel cache (ROOT_CACHE_DIR/python_<v>/<pkg>==<ver>) is shared by every
job on a worker, and uv installs into it with `--reinstall --target <dir>`,
which transiently empties the directory mid-run. Two jobs installing the same
package concurrently (e.g. several WAC tests all importing wmill) clobber each
other's files, and a job that imports from the dir during a concurrent
reinstall sees a partially populated or wiped package -> flaky
ModuleNotFoundError (httpx/wmill).

Serialize installs per target dir with a process-wide keyed lock and re-check
the `.valid.windmill` marker once the lock is held, so a waiter reuses the
freshly populated cache instead of reinstalling over a dir other jobs may be
importing from. Reinstall now only runs when no valid install exists, so no
concurrent importer can observe a half-populated dir.

Reproduced with a cold-wheel-cache loop over the wmill-importing python tests:
8/10 iterations failed before, 0/12 after. Adds a unit test guarding the
per-target lock keying.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:03:11 +00:00
Ruben Fiszel 248540ac4d feat: bounded-cascade selective execution for pipelines (UI + CLI) (#9695)
* feat: bounded-cascade selective execution for pipelines (UI + CLI)

Run a prefix of a pipeline cascade: from a schedule/manual root, fan
downstream but stop at chosen end node(s) — the path-between set over the
asset-graph lineage DAG. Exposed as a canvas 'Run downstream up to…' pick
mode and a 'wmill pipeline run <folder> --to' CLI command. No backend or
parser changes; reads the existing graph, tags, and triggers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: surface bounded-run on the run caret, trigger-node kebab, and Test button

Move 'Run downstream up to…' from the runnable kebab onto the play-button
caret popover (Edit mode, next to Run / Run + trigger N downstream); add it
to the trigger-node kebab so schedule/data_upload entrypoints expose it on
the View page; and to the ScriptEditor Test split caret for the open script.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: address CI review on bounded-cascade (cubic)

- Port CLI engine test from Deno to bun:test under cli/test/ (won't run under bun test otherwise).
- closure() now excludes the start node on a cycle back to it (descendants/ancestors contract); regression tests both engines.
- CLI 'pipeline run --to' rejects unresolved/ambiguous end tokens instead of silently running a different subset.
- Sort a copy in the runSelection order test so the launch-order assertions aren't invalidated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: address standing review nits on bounded-cascade

Resolves the four recurring P1/P2 findings from the codex/pi/claude
reviews:

- UI gate (P1): the canvas/trigger-node "Run downstream up to…"
  affordance was gated on the subscriber-only downstream map, so a valid
  start whose only downstream is a pure reader had a non-empty bounded
  set but no menu entry. Gate on the read-aware lineage downstream
  (buildLineageDownstreamMap), matching the bounded engine.
- waitJob (CLI): a completed job without explicit success:true now
  counts as a failure, mirroring the frontend waitJobTerminal — the
  cascade only advances on a confirmed success.
- Comment fix (CLI): the unbounded `run` path uses the read-aware
  lineage DAG (pure readers included); dropped the false "parity with
  the canvas cascade" (subscriber-only) claim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: expose bounded-run caret for pure-reader-only starts (codex P1)

The canvas wiring from the prior commit passed `onStartBoundedRun` from
the read-aware lineage map, but the leaf components still hid the popover
that holds the "Run downstream up to…" action behind a subscriber-only
gate:

- RunnableNode rendered the Run-button caret only when
  `hasCascade = downstreamCount > 0` (subscriber-only). A valid start
  whose only downstream is a pure reader got `onStartBoundedRun` but no
  visible action. Now the caret opens when there's a cascade OR a
  bounded-run start (`hasCaret`), and the "Run + trigger N downstream"
  item is gated on `hasCascade` so it never reads "trigger 0".
- ScriptEditor's Test split button activated only when
  `downstreamSubscribers > 0`, falling through to a plain Test button
  (no caret) otherwise. Now it also activates when `onBoundedRun` is
  set, with the "Test + trigger N" item gated on the count.

For a manual root (no trigger-node kebab fallback) with a pure-reader
downstream this was the only UI entry point, so it was previously
unreachable. Verified in-browser: a manual-root script writing an asset
read-only downstream now exposes "Run downstream up to…" on the
ScriptEditor Test caret with the cascade item hidden.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: gate ScriptEditor bounded-run on read-aware downstream; fix CLI asset-end warning (codex P2)

- Details-pane (ScriptEditor) bounded-run entry was gated only on
  `validStartPaths`, broader than the canvas which also requires
  read-aware downstream (`hasLineageDownstream`). An isolated start could
  thus expose "Run downstream up to…" and enter pick mode with no
  selectable end. Now gated on `lineageDownstreamPaths` (script paths with
  a downstream in `buildLineageDownstreamMap`), matching the canvas.
- CLI dropped-end warning called `scriptPathOf(d)` unconditionally, which
  slices `script:`-length chars off an asset id too — `datatable:main/raw`
  printed as `le:main/raw`. Now prefix-checks like the JSON output.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: correct --from error to exclude only row-backed event triggers (codex P2)

The bounded-start validation message listed `kafka/webhook/…` as event
triggers that can't start a bounded run, but webhook/data_upload are
rowless and read as manual roots (valid starts). Only the row-backed
native kinds (kafka/mqtt/nats/postgres/sqs/gcp/email — EVENT_TRIGGER_KINDS)
are excluded; the message now names those.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: surface dropped ends in CLI JSON; disambiguate shared-trigger bounded start (codex P2)

- CLI `run --json` silenced the dropped-end warning, and the JSON payload
  echoed the originally-resolved `--to` list with no reachable/dropped
  split — a resolved-but-unreachable end looked like a clean plan that
  silently runs only the start. JSON now includes `reachableEnds` and
  `droppedEnds` (shared `idLabel` helper, asset-id safe).
- Trigger nodes dedupe per (kind, ref), so a schedule shared across
  scripts collapses to one node, but `recordSourceTrigger` kept only the
  first target path — the bounded-run action then rooted at an arbitrary
  script (or hid when only that first script lacked downstream). Now all
  target paths are tracked and the action is offered only when exactly one
  is a valid start with downstream; multi-eligible nodes suppress it
  rather than guess.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: don't run hidden drafts in View-mode bounded cascade (codex P1)

launchCascadeScript unconditionally preferred drafts.get(path) over the
deployed script. In View mode with drafts hidden (displayGraph is
deployed-only), a bounded run started from a trigger-node kebab would
execute preview jobs from hidden local draft content instead of the
deployed scripts the user is looking at.

Gate draft execution on `mode === 'edit' || includeDrafts` — the exact
condition under which displayGraph includes drafts — so execution always
matches the displayed graph. No-op for scripts without a draft; the
edit-mode "Run + trigger N downstream" cascade is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:08:05 +02:00
Ruben Fiszel d7939e514d chore: trim unused apt packages from server docker image (#9783)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:07:06 +02:00
Ruben Fiszel cd42c6ca18 fix: decrypt secret variables via external backend in common resolvers (#9784)
`get_variable_or_self`, `get_variable_or_self_as`, `get_secret_value_as_admin`
(and `transform_json_unchecked`'s `$var:` branch) in windmill-common always ran
the raw `variable.value` through `decrypt()`. With an external secret backend
(HashiCorp Vault / Azure Key Vault / AWS Secrets Manager) configured, that
column holds a `$vault:`/`$azure_kv:`/`$aws_sm:` marker rather than base64
ciphertext, so base64 decoding failed with `Invalid byte 36, offset 0` (the
`$`). This broke GitHub App git sync (git_sync_ee.rs) and any other consumer of
these resolvers when an external backend is active.

Move backend resolution (`get_secret_backend`, `get_secret_value`,
`is_*_stored_value`, caching) into `windmill-common::secret_backend::resolver`
so the low-level variable resolvers can route external markers through the
configured backend's `get_secret()` instead of `decrypt()`. The windmill-store
and windmill-api `secret_backend_ext` modules now re-export these from
windmill-common (single source of truth / single backend cache) and keep only
their write-side helpers. No `_ee.rs` files change.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:05:21 +02:00
Diego Imbert 170cd79aaf fix: allow hyphens in postgresql database name validation (#9782)
* fix: allow hyphens in postgresql database name validation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: cover hyphen acceptance in validate_dbname

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:02:35 +02:00
Ruben Fiszel 74ebfc67f0 fix(frontend): nested-loop "Test this step" resolves iter to innermost loop (#9778)
* fix(frontend): nested-loop "Test this step" resolves iter to innermost loop

In a loop-inside-a-loop, the inner step's "Test this step" tab prefilled
its arguments using the outermost ancestor as the parent module, so
flow_input.iter resolved to the parent loop's iteration value instead of
the inner loop's.

dfs(id, flow, true) returns [step, immediate parent, ..., root], so
modules[modules.length - 1] is the outermost ancestor. The prop picker
needs the immediate parent (modules[1]) so getFlowInput resolves iter at
the innermost loop's level. A single loop was unaffected because both
indices coincide; only depth >= 2 broke.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(frontend): nested-loop parent selection for test-step args

Pins that modules[1] from dfs(stepId, flow, true) is the immediate parent
for every step across all container types (for/while loops, branchone,
branchall, aiagent tools) and nesting depths, and that getStepPropPicker
then resolves flow_input.iter to the innermost enclosing loop.

Covers >400 step positions across 107 generated flow shapes, plus explicit
single/nested/while/branch iter-resolution cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(frontend): remove nested-loop parent selection test

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:19:55 +02:00
Ruben Fiszel f6998ec54c feat: data tests for ducklake pipeline materialization (#9708)
* feat: data tests for ducklake pipeline materialization

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): data_test count badge on pipeline graph nodes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: surface annotation badges (incl. data_test) on deployed pipeline nodes

Backend graph endpoint now parses each pipeline member's deployed body and returns partition/freshness/tag/retry/data_test, so badges render on deployed nodes, not only live drafts. Aligns the TS DataTest.relationships fields to snake_case to match the Rust serde wire shape (the type is now populated from both the parser and the backend JSON).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): keep materialize output edge when editing the producer in the pipeline graph

The live-edit overlay re-derived a selected/edited script's lineage from // on inputs + body-inferred assets only, so the // materialize <asset> output (an annotation, not body SQL) was judged stale and its write-edge dropped on select — leaving the materialized asset unlinked (and the node's annotation badges hidden). Include the parsed materialize target in liveRefKeys and the draft writeOuts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: run all data tests in one pass with a structured per-test result

Replace the raise-on-first-violation probes with a single materialize summary that embeds every test's violating-row count in a data_tests column (computed in a CTE, since DuckDB rejects subqueries inside struct literals). The worker reads the breakdown and decides pass/fail: a clean run returns the per-test summary in the result; a failing run errors with the FULL list (every test, ✓/✗ + counts), not just the first failure. Verified live (EE) for built-ins + custom, pass and multi-failure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): data-test pass/fail checklist in the job result

DisplayResult renders a per-test checklist (✓/✗ + violation counts) above the raw result for managed materialize runs — from the structured data_tests on success, and parsed from the worker's breakdown message on failure. Shows in the script editor Test panel, the runs page, and the pipeline asset run pane.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): move data-test badge onto the producer→asset edge with run status

The test badge now sits on the write-edge (the transformation link) rather than the producer node, since the tests assert on what the transformation produces. It's tinted by the producer's last-run status (green = passed, red = a test failed) and its hover title lists every declared test. Removes the now-redundant node badge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): render custom data-test scripts as their own clickable graph nodes

A // data_test <script_path> custom test now appears as its own node below the asset it validates, joined by a dashed 'tests' edge. Clicking it opens the test script in the detail pane (dispatched like any runnable). Built-in tests stay folded into the edge badge; only script-backed tests become nodes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): type data-test edge field via AssetGraphResponse, not in-scope g

BuiltEdge is declared at component scope, outside build(g), so referencing typeof g.runnables in its type failed CI's svelte-check (Cannot find name 'g'). Use the imported AssetGraphResponse type instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): anchor edge badge on routed path + a11y text on test icons

Address review: the data-test edge badge anchored on the straight-line midpoint, floating off detoured edges — anchor it at detourX when the edge is routed through a gutter lane. Add sr-only pass/fail text so the checklist icons are distinguishable to screen readers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: close data-test enforcement bypass + gate badges to scripts + reject multi-stmt custom tests

Address review (cubic) findings:
- P1: managed materialize generates its own summary row carrying data_tests, and enforcement reads that column — but a // result_collection annotation (e.g. a scalar mode) could reshape the row and drop data_tests, silently bypassing a failing test. Force LastStatementAllRows for managed materialize runs so the summary row is always intact.
- P2: asset-graph annotation badges were keyed by path only, so a flow sharing a path with a pipeline script inherited its badges. Gate the lookup on usage_kind == Script.
- P2: a custom test body is embedded as a subquery, so a multi-statement body produced invalid SQL with an opaque DuckDB error. Validate single-statement up front with an actionable error; align docs/comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: fail loud if fewer data-test outcomes recovered than declared

Defense-in-depth from the fresh-context review: enforcement reads per-test outcomes off the materialize summary row, but if the data_tests column were ever dropped/reshaped at the FFI boundary, extract_data_tests would return fewer (or zero) outcomes and the run would silently pass unverified tests. Track the embedded test count on MaterializeExec and abort with a clear error when recovered < declared. Verified: normal run (4==4) unaffected; the scalar-result_collection bypass already fails.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: relationships data test same-lake reuse + schema-qualified target quoting

Address Codex/Pi review (two P1s in the relationships codegen):
- A relationship into the same ducklake as the materialize target minted a second ATTACH of that lake under _wm_ref_N while _wm_target already held it — DuckDB forbids attaching one database twice, so the test failed before it could run. Reuse _wm_target for same-lake references.
- A schema-qualified target (ducklake://warehouse/main.dim_products.sku) emitted FROM _wm_ref_0."main.dim_products" — one quoted identifier with a literal dot — silently querying a nonexistent table. Quote each dotted segment so the dot stays a schema separator.
Adds tests for both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): refresh data_test badge on deployed-script drafts + scope to materialize target

Address Codex review nits (both P2):
- resolveGraph: the existing-runnable draft-overlay branch kept the deployed data_tests, so adding/removing // data_test lines on an already-deployed script left the badge stale until redeploy. Refresh it from the live parse like the new-runnable branch.
- AssetGraphCanvas: data tests were attached to every write-edge from a producer. They assert on the // materialize target (always a ducklake asset in v1), so only the ducklake write-edge now carries the badge and custom-test nodes — a producer's other (S3/datatable) outputs no longer show them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 23:47:02 +02:00
hugocasa 88fca6a8c1 fix: enforce containment of python module dir for preview jobs (#9704)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 23:13:59 +02:00
Ruben Fiszel d131d754e1 feat: ducklake time-travel UX (snapshot history + AT VERSION reads) (#9709)
* feat: ducklake time-travel UX (snapshot history + AT VERSION reads)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: catalog-qualify ducklake time-travel FROM hints (lake. prefix)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: render ducklake snapshot_time (microseconds since epoch) correctly

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor: merge ducklake History + Query into one master-detail tab

Snapshot list (left) selects the version previewed in the read-only grid (right); newest auto-selected. Copy-clause moved to the preview's SQL line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: scope ducklake snapshot history to the table's versions

Catalog-wide snapshots predate a table's creation; previewing AT a version before the table existed errored ("Table ... does not exist at version N"). The DUCKLAKE_SNAPSHOTS marker now takes the table and lists only snapshots from its first creation onward. Also: narrower snapshot-list pane on large screens (target a fixed width, not a fixed fraction).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: load ducklake preview columns at the pinned version + reset on asset switch

Addresses CI review (codex/pi P1, cubic P2):
- Historical previews loaded current-schema columns, so an AT(VERSION) read enumerating a column added in a later snapshot failed. Now DESCRIBE-loads the column set at the pinned version; the read is gated on columns matching the current version to avoid a stale-colDefs race on version switch.
- selectedVersion no longer sticks across assets: the panel is keyed on path (remounts per asset) and effectiveVersion falls back to newest when the pick isn't in the current list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: match History tab UI (master-detail, full-FROM copy) after merge

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: handle catalog-only ducklake asset paths (no table segment)

parseDbInputFromAssetSyntax threw on a catalog-only path like 'ducklake://main' (undefined.split('.')) — a real graph node (e.g. a consumer of the whole catalog). It now returns a table-less input instead of throwing, and DucklakeAssetPanel renders only the partition grid (no per-table history/time-travel) for table-less nodes. Adds parser unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: escape ducklake catalog name in client-built time-travel DESCRIBE

fetchDucklakeColumnsAtVersion interpolated the catalog name into an ATTACH string literal without escaping; double single-quotes (mirrors backend escape_sql_literal) so a quote-containing catalog name can't break out. Also fixed the v1.x docs checklist line to match the shipped full-FROM copy affordance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 23:11:47 +02:00
Ruben Fiszel 920f5688ca chore(main): release 1.739.0 (#9746)
* chore(main): release 1.739.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.739.0
2026-06-24 18:01:19 +00:00
centdix 3fafac275d feat(ai-chat): add /clear session command to start a fresh conversation (#9769)
* feat(ai-chat): add /clear session command to start a fresh conversation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ai-chat): don't re-queue a built-in command flushed from the queue

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 17:27:46 +00:00
centdix 89c46c803e docs: add ai-chat and ai-evals skills (#9770)
Add two agent skills under .agents/skills (symlinked into .claude/skills):

- ai-chat: guidance for improving the Windmill AI chat / copilot, especially
  global mode — benchmark before/after with ai_evals, optimize finalContextTokens
  over cumulative, keep tool params and tool-result payloads minimal (no echoing
  content the model already has), treat prompts/tool-descriptions as benchmarkable
  surface.
- ai-evals: author and run black-box benchmark cases for the AI generation modes,
  migrated from ai_evals/AGENTS.md and extended with run mechanics (workspace reuse,
  reading the summary). ai_evals/AGENTS.md becomes a pointer stub to the skill.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 17:27:29 +00:00
Ruben Fiszel f99781ca5f fix: persist on-behalf-of user across app deploy paths (#9773)
* fix(frontend): persist on-behalf-of user when redeploying raw apps

The raw-app deploy drawer reused AppEditorHeaderDeploy but never wired up
the `preserveOnBehalfOf` bindable nor forwarded `preserve_on_behalf_of` in
the createAppRaw/updateAppRaw request bodies. Without that flag, the shared
backend handler (create_app_internal/update_app_internal) resets the policy's
on_behalf_of to the deploying user on every deploy. So a publisher who set
"App executed on behalf of <other user>" would silently lose it on the next
deploy, unlike every other setting on the deploy page.

Mirror the classic (low-code) app header: declare `preserveOnBehalfOf`, bind
it to the deploy component, and send `preserve_on_behalf_of` on both create
and update.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): preserve on-behalf-of in the draft-deploy path

The draft-deploy path (deployDraft → AppService.createApp/updateApp for visual
apps, deployRawAppDraft → createAppRaw/updateAppRaw for raw apps) carries the
deployed app's policy forward but never sent preserve_on_behalf_of. So
deploying a draft via the "Review & deploy drafts" UI silently reset the
policy's on_behalf_of to the deploying user — the same backend reset behind the
deploy-drawer bug, on a surface that has no on-behalf-of selector to re-set it.

Send preserve_on_behalf_of whenever the carried policy has an on_behalf_of, for
both app types. The backend still gates actual preservation on
can_preserve_on_behalf_of, so a non-deployer cannot escalate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): preserve on-behalf-of in the AI-chat raw-app deploy

The global AI-chat deploy path (`deploy_workspace_item` → createAppRaw/
updateAppRaw in copilot/chat/global/core.ts) carried the recomputed policy
forward but omitted preserve_on_behalf_of, so deploying a raw app via chat
reset the policy's on_behalf_of to the deploying user — the last of the
deploy surfaces with this gap. Send the flag when the policy has an
on_behalf_of, mirroring the editor and draft-deploy paths; the backend still
gates preservation on can_preserve_on_behalf_of.

Add a regression test asserting the flag is forwarded when the deployed
policy carries an on_behalf_of.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 17:10:21 +00:00
Guilhem a116715c41 feat(frontend): restore raw app 'open preview in separate window' (#9765)
* feat(frontend): restore raw app 'open preview in separate window'

Re-adds the detached preview window dropped when preview hosting moved to
the host (ui-builder f52d8e5b). Live-synced preview + dark mode, and wires
the runnable bridge to the detached window so backend calls resolve there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): repaint detached raw app preview after refresh

The detached preview tab is a blank app-preview.html shell fed by the
editor over postMessage. A one-shot opener load listener can't survive the
tab refreshing itself, so a manual reload left it blank. It now posts
'appPreviewReady' on every (re)load and the editor re-sends the build; the
orphaned window is also closed when the editor unmounts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): keep load-based feed for detached preview initial open

Relying solely on the appPreviewReady handshake left the detached window
blank on first open against app-preview.html artifacts that predate the
handshake (the pinned UI Builder tarball). Restore the one-shot load feed so
initial open works regardless of the served shell; the handshake still
covers manual refresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): re-sync dark mode when refocusing detached preview

The focus-reuse path replayed the build but not the theme, so re-opening an
existing detached window after a dark-mode toggle kept the stale theme until
the next build. Extract a feedExternalPreview() helper (theme + build) used by
the open, focus-reuse, load and handshake paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(frontend): bump ui_builder artifact to 062d11c (preview handshake)

Pins the UI Builder artifact built from windmill-code-ui-builder#14, which
adds the appPreviewReady handshake, detached-preview favicon and title.
Activates refresh-repaint + favicon for the detached raw app preview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(frontend): serve ui_builder static bundle in dev, proxy :4000 only as fallback

The postinstall downloads the pinned UI Builder artifact into static/ui_builder,
which SvelteKit already serves at /ui_builder. Skip the :4000 proxy when that
bundle is present so dev uses it directly (matching prod / the backend's
static-vs-:4000 fallback); no separate UI Builder dev server needed. Delete
static/ui_builder to develop the builder against :4000.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): harden detached preview origin + scope window name

Addresses cubic review on #9765:
- P1: only honor appPreviewReady from a same-origin sender and post the build
  with targetOrigin=location.origin, so user app code that navigates the
  detached window cross-origin can't trigger/receive a build (app source).
- P2: scope the detached window name per app path so two open editors don't
  collide over one OS-level preview window.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:39:16 +00:00
Guilhem 2e020b2ccc feat(ai-chat): context usage gauge + unified model settings menu (#9763)
* feat(ai-chat): show context usage as a gauge with hover tooltip

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ai-chat): consolidate model, thinking & params into one dropdown

Merge the model picker, reasoning-effort selector and prompt settings
into a single dropdown with a model list, a thinking-effort slider and a
hover-revealed Parameters submenu. The trigger shows the model and effort.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ai-chat): polish model/thinking dropdown interactions

Register the model rows and thinking slider as melt menu items (roving
highlight + arrow-key navigation), keep the menu open on selection via a
new DropdownV2 closeOnItemClick prop, use melt's createSubmenu for the
Parameters flyout so it flips on screen edges, and use the brand accent
for the context-usage gauge and slider.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ai-chat): stop popover drift and keep Thinking section when unsupported

Freeze the trigger width while the dropdown is open so the bottom-end
popover doesn't shift as the effort label resizes (released on close, so
no reserved padding). When a model has no reasoning support, show the
Thinking section disabled with a message instead of removing it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ai-chat): restore reasoning slider drag inside the menu

The slider lives in a melt menu item, whose roving focus blurs the
focused element on pointermove and aborted the native thumb drag. Stop
the slider's pointer events from bubbling to the item so melt leaves it
alone; focus-based highlighting still works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ai-chat): move Parameters to the top of the model settings menu

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ai-chat): hide the @ context picker in global mode

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ai-chat): only mark context gauge as a meter when the window is known

A meter is a 0–100% reading; with an unknown context window there is no max
to measure against, so role/aria-value* are dropped (previously valuenow fell
back to the raw token count against an implicit valuemax of 100).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(frontend): note closeOnItemClick is read at mount-time

Addresses a non-blocking review note on DropdownV2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(ai-chat): fix showContextPicker comment to match GLOBAL removal

Addresses Pi review P2: GLOBAL no longer offers the @ context picker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(ai-chat): clarify showContextPicker hides only the manual @ button

In GLOBAL, @-context is still invoked inline by typing @ in the input; only
the redundant picker button is hidden.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:07:20 +00:00
Ruben Fiszel 4dbf873723 fix(frontend): stop flow step id generation from being poisoned by non-canonical keys (#9766)
* fix(frontend): stop flow step id generation from being poisoned by non-canonical keys

nextId computed the next step id from the max of charsToNumber over every
module id and flowState key. Only canonical auto-ids (a, b, ... aa, ab) have a
meaningful charsToNumber value, but flowState also holds copy ids ("z2"),
subflow result keys ("subflow:..."), reserved keys ("failure"/"preprocessor")
and user-renamed ids. The old `length >= 4` guard filtered long junk but let
short junk through, so e.g. duplicating step "z" (key "z2", charsToNumber 629)
made the next new step jump to "xg" and escalate from there.

nextId now only counts a key if it round-trips through numberToChars and is not
reserved, and the broken length cap is removed so large flows still get correct
ids.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): keep length cap in nextId to avoid regressing long renames

Address CI review: removing the length cap made all-lowercase renamed step
ids (e.g. "process", which round-trips through numberToChars) feed into the
max and poison id generation again — a regression versus the prior behavior,
since step ids can be renamed to ^[a-zA-Z][a-zA-Z0-9_]*$.

Restore the length>=4 skip and pair it with the round-trip canonical check,
so short non-canonical keys (copy ids "z2"/"c10", reserved/renamed short ids)
no longer poison the max while long renames stay out of the sequence. Update
the tests to reflect the actual coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:40:07 +00:00
hugocasa b5bd8245d8 fix: reject symlink traversal in job-dir path validation (#9713)
* fix: reject symlink traversal in job-dir path validation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: cover dangling symlink in job-dir path validation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: close symlink-traversal bypass via in-bounds `..` in path check

Walk the normalized relative path instead of raw user components, so an
in-bounds `..` (e.g. `foo/../evil/payload`) can no longer drift the walk
past a planted symlink. Adds regression coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:08:45 +00:00
Ruben Fiszel de6192bec1 fix(frontend): highlight the runtime-chosen branch in flow graph viewer (#9755)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:37:56 +00:00
Ruben Fiszel 2a70ccc386 feat(frontend): show approval wait as a distinct segment in flow timeline (#9756)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:37:33 +00:00
centdix 83cc5533ee feat: add /compact session chat command (#9764)
* feat: add session chat slash commands

* feat: add /compact session chat command

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: dedupe built-in commands against same-named workspace skills

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:33:55 +00:00
Guilhem 42c5e7a3fc feat: scope AI sessions per workspace root with lifecycle reconcile (#9734)
* feat: scope AI sessions per workspace family with lifecycle reconcile

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor: centralize session reconcile trigger + extract pure lifecycle decision

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf: remove unused workspace family index

* refactor: scope sessions by workspace root id, drop family_id column

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(sessions): preserve user-archived sessions when archiving their workspace

archiveSessionsForWorkspace tagged every session archivedByWorkspace, including ones the user had already archived by hand, so a later workspace unarchive auto-restored them. Skip already-archived sessions so only workspace-archived ones are tagged, matching decideSessionLifecycle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: archived-session banner with unarchive, suppress workspace-gone banner while archived

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: re-root sub-fork sessions on reconcile when an ancestor is deleted

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: group AI sessions by workspace family with show-all-workspaces filter

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: revert unrelated AIProviderPicker cosmetic changes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: hide per-session unarchive when workspace is gone, show move/discard instead

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: GC attached files on lifecycle delete + reconcile on sidebar fork delete

Addresses Codex review: deleteSessionsForWorkspace/reconcile delete now GC linked files (deleteItemsForSession), matching deleteSession; sidebar deleteFork now reconciles so surviving child forks re-root off the deleted ancestor. Also de-flaked post-rehydrate reads in the IndexedDB tests via vi.waitFor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: don't strand user if post-delete reconcile throws; refresh stale warmSessions comment

Addresses auto-review P2s: wrap reconcileAfterWorkspaceChange in deleteFork so the parent switch + navigation always runs even on reconcile failure; correct the warmSessions comment which no longer holds under 'Show all workspaces'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: don't fail/strand fork archive+delete when client session cleanup throws

Addresses cubic P1/P2 on forks/compare: the workspace archive/delete is authoritative; wrap the best-effort session cleanup + reconcile so a local IndexedDB failure neither falsely reports failure nor blocks navigation away from the gone fork. Mirrors the SidebarContent fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: drop drafting-history aside from reconcileAfterWorkspaceChange comment

Addresses auto-review P2: keep the refresh-before-reconcile invariant, drop the 'which they did inconsistently' narration per AGENTS.md (comments record constraints, not drafting history).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: clean up sessions on fork-id reuse + make all workspace-mutation cleanup best-effort

Addresses Codex P1s: (1) CreateWorkspaceInner 'permanently delete existing fork' (id-reuse) now drops local sessions for that id so they don't resurface on the recreated fork; (2) workspace_settings archive/delete and SidebarContent child-delete loop + main delete now treat post-mutation session cleanup as best-effort, so a local IndexedDB failure can't strand the user or abort remaining deletes (matching the compare-page fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: make fork-reuse session cleanup fire-and-forget (non-blocking)

Addresses cubic P2: don't await the best-effort cleanup so a slow IndexedDB op can't block the delete/reuse flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: drop previous user's transient drafts on user change

Addresses Pi P1: hydrateSessions preserved transient (unsent) drafts across user changes, so user A's draft + its pending fork/workspace state bled into user B's list and got reused by createSession. onUserChange now drops transients when the email changes; reconcile (intra-user) still preserves them. Regression test added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:41:18 +02:00
Ruben Fiszel 288318ac26 fix(apps): realign legacy raw-app drafts to raw_app draft kind (#9761)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:14:52 +02:00
Guilhem 3d48ba7738 feat(frontend): add filter submenu to collapsed AI sessions popover (#9757)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:58:31 +02:00
Ruben Fiszel fada673bb4 chore: bump uv to 0.11.24 in images and CI (#9759)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:58:08 +02:00
centdix 24b95e9fe1 feat: add session chat slash commands (#9748) 2026-06-24 13:43:40 +02:00
Ruben Fiszel f5828780fd fix(backend): resolve folder_labels search_path on non-public (PG_SCHEMA) schemas (#9758)
* fix(backend): strip search_path=public from folder_labels migrations for non-public schema

The folder-labels migrations (20260610151334_folder_labels,
20260614075900_dedup_folder_labels) define `folder_labels(...)` with
`SET search_path = public` in their `CREATE FUNCTION` bodies. When Windmill
runs in a non-public schema (PG_SCHEMA), PostgreSQL validates the function
body against the `public` schema, where the `folder` table lacks the new
`labels` column, failing with `column "labels" does not exist`.

Add both migrations to OVERRIDDEN_MIGRATIONS, stripping the
`SET search_path = public` clause so the function inherits the current
search_path (which resolves the correct schema). Same regression and fix
pattern as PR #5400.

Fixes WIN-2093

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(backend): pin folder_labels search_path FROM CURRENT instead of stripping it

Keep the SECURITY DEFINER injection hardening while resolving the correct
schema on non-public (PG_SCHEMA) installs: FROM CURRENT snapshots the
migration connection's search_path at function creation time (public on
normal installs, the custom schema otherwise) instead of dropping the pin
and inheriting the caller's search_path at call time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(backend): repair migration to re-pin folder_labels search_path on applied instances

Instances that already applied the folder-labels migrations with the hardcoded
SET search_path = public have a folder_labels function pinned to public. On a
non-public (PG_SCHEMA) schema that reads the wrong folder table at runtime; the
OVERRIDDEN_MIGRATIONS fix only helps instances that have not applied them yet.

Add a CREATE OR REPLACE ... SET search_path FROM CURRENT migration that re-pins
the function to the migration connection's schema. No-op on public installs
(re-pins to public) and idempotent on already-correct ones.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:38:44 +00:00
Guilhem 5e09c50171 fix(frontend): keep #content portal target present on AI-session route (#9754)
The global fork modal (and other modals) portal into `#content`, but that
element only existed in AiChatLayout's `!disableAi` branch. On the AI-session
route `disableAi` is true, so the `{:else}` branch rendered without `#content`,
and opening the fork modal there threw "No element found matching css selector:
#content". Give the else-branch container the same `id` so the portal target is
always present in this layout.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:21:17 +02:00
Ruben Fiszel 8912e21d15 perf(monitor): vacuum job_perms/job_result_stream right after each orphan sweep (#9753)
A customer's top-load query was the job_perms orphan sweep (cleanup_job_perms_orphaned:
6.9s mean, 41s max). The cost is discovery, not deletion (~2.1ms per row deleted): the
NOT EXISTS anti-join seq-scans the whole job_perms heap to find a few orphans, and that
scan tracks the heap's physical size. job_perms / job_result_stream_v2 get one row per job
and are drained only by these per-cycle sweeps, so they churn hard — but the bulk
vacuuming_tables() runs only ~hourly, so dead tuples bloat the heap between bulk vacuums.

Reclaim right after each sweep instead: VACUUM (SKIP_LOCKED) the swept table when it
deleted rows. Plain VACUUM (not FULL) takes only SHARE UPDATE EXCLUSIVE so concurrent job
creates/reads proceed; the visibility map skips unchanged pages so repeated runs are cheap;
SKIP_LOCKED means HA replicas don't pile up (one vacuums, the rest skip). Benchmarked ~7x:
a bloated 268MB job_perms heap swept in 35ms vs 5ms vacuumed. Chosen over an autovacuum
reloptions migration so the behavior is explicit and lives with the sweep it pairs with.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:20:53 +02:00
Ruben Fiszel 55bed4abcf perf(audit): adaptive timestamp floor for S3 audit-log export (#9752)
* perf(audit): adaptive timestamp floor for S3 audit-log export (ee)

EE change in windmill-ee-private (src/ee.rs); this OSS commit carries the regenerated
sqlx cache for the new oldest-in-flight query and bumps ee-repo-ref.txt to the EE branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to ed89574be9117cda5e2d7d9de02cb5db066e93e3

This commit updates the EE repository reference after PR #628 was merged in windmill-ee-private.

Previous ee-repo-ref: 8a7f645c0a194a284fe19dd20dbe79dd0733dfdb

New ee-repo-ref: ed89574be9117cda5e2d7d9de02cb5db066e93e3

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-06-24 09:49:22 +02:00
Guilhem e98df38ac4 feat(apps): show raw-app fork diffs as per-file tree items (#9491)
* feat(apps): show raw-app fork diffs as per-file tree items

Raw-app diffs previously rendered as one big YAML diff of the whole
serialized app. This explodes a raw app into separate, independently
collapsible diff items — one per file, one per runnable, and an
app.yaml metadata item — that flow through the existing fork-diff
list, sidebar tree, search and count via composite paths
(<appPath>/<file>). Runnables render as script/flow rows (code shown
in a Content tab), and files get extension-specific icons reused from
the raw-app editor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: remove raw-app tree-diff plan doc from the branch

The implementation plan was an authoring aid, not product documentation; drop it so it doesn't ship in the PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: present raw app as an app-headed folder in the diff tree

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: narrow RawAppFileItem in diff viewer branch (fixes svelte-check)

DiffRow.kind is a plain string so the kind check didn't narrow the union; assert the synthetic item. Also size-guard on the larger side's line count instead of the doubled total, and document normalizeRawApp's per-field value-wrapper precedence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* style: single-line, lighter diff-tree rows for all item kinds

Add a singleLine mode to WorkspaceItemRow (summary ?? path on one line; DRY'd via a shared body snippet) and use it for every diff-tree leaf, so scripts/flows/triggers/resources/etc. match the raw-app header. Bump rows to py-1.5, force font-normal, and split colours: items in text-primary, folders in text-secondary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor: extract pure diffTree model from WorkspaceDiffDrawer

Move tree construction + keyboard-nav traversal + the folder-keying convention out of the 775-line component into a pure, generic, tested module (buildDiffTree → root/order/parentKeyOf/firstChildKeyOf). Parent and first-child come from a child→parent map built during construction, not from re-splitting a path at the call site, so a node's tree position and its nav parent can't drift — the class of bug behind the ArrowLeft regression. Deletes the forkDiffNav half-seam (its bug lived in the untested caller). 12 new unit tests cover order/parent/first-child incl. the storage-key-vs-friendly-path case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(apps): keep raw-app metadata flag + dedup runnables across path collisions

Addresses two P2 review nits (Codex/claude): (1) rawAppDiffToItems marked metadata by matching path==='app.yaml', so when a real file is named app.yaml the reserved app.yaml~2 metadata item lost its flag/full-YAML toggle — now parseRawAppDiff tags the entry with isMetadata and the items read the flag; (2) runnable composite leaves weren't deduped against real files, so a real file at runnables/<name> could produce a duplicate leaf — now reserved (slash-normalized) like parseRawAppDiff. +2 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(apps): dedup /app.yaml metadata collision + disambiguate synthetic row keys

Two follow-up P2s from Pi/Codex re-review of the prior fix: (1) parseRawAppDiff's collision set used raw file keys, so a real file /app.yaml (leading slash, which joinAppPath strips) still collided with the synthetic app.yaml leaf — now slash-normalized via a shared stripLeadingSlash, +test. (2) synthetic raw-app items (runnables rendered as script/flow) could share kind+path identity with a real workspace script/flow at <appPath>/runnables/<name>, causing duplicate {#each} keys and broken nav — itemKey now prefixes synthetic items (rawapp:).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(apps): canonicalize raw-app file keys to dedup leading-slash collisions

Codex P2: a file keyed /App.tsx on one side and App.tsx on the other became two entries that joinAppPath collapsed to one composite path → duplicate row key. asFileMap now strips the leading slash so both sides resolve to one file. +test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(apps): lazy-mount per-file diff editors as they scroll into view

Exploding a raw app into N per-file rows mounted N Monaco DiffEditors at once (3 reviews flagged it). Each block's editor now mounts only when it scrolls within ~200px of the viewport (IntersectionObserver rooted on the scroll container), showing a light placeholder until then; mountedRows latches so it never unmounts on scroll-away. Verified: ~6 of 13 mount initially, the rest on scroll.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:23:14 +02:00
centdix c017f7f891 fix(frontend): show AI skills settings only when global mode enabled (#9747)
AI skills are only consumed by the GLOBAL chat mode's system prompt, and
global mode itself is dev-gated by isGlobalAiEnabled(). Gate the workspace
AI skills settings tab on the same flag so it isn't shown when the skills
can't be used, and add it to gate.ts's rip-out inventory.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 07:54:11 +02:00
Akira Yamazaki f1b5c43e02 chore: bump nixpkgs for uv 0.9.25 (#9749) 2026-06-24 07:53:43 +02:00
centdix 250a05f544 fix(ai-chat): strip unclosed <summary> tag leaking into compaction summary (#9750)
* fix(ai-chat): strip unclosed <summary> tag leaking into compaction summary

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ai-chat): strip analysis before matching summary to avoid scratchpad leak

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 07:53:14 +02:00
hugocasa 043c2c05b7 fix: forbid superadmin job tokens from global user and token management (#9715)
* fix: forbid superadmin job tokens from global user and token management

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: extend superadmin job token guard to offboard and export routes

Apply forbid_superadmin_job_token to offboard_global_user and
export_global_users, the remaining global user-management routes that
were gated only by require_super_admin. Offboarding can delete a user
along with their tokens, password, invites and instance-group
membership, and export returns every user's password_hash, so both must
be unreachable by a superadmin job token.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 00:38:27 +02:00
centdix ae088fd032 stabilize global ai eval smoke path (#9745) 2026-06-24 00:31:14 +02:00
Ruben Fiszel 9e4cf139b1 chore(main): release 1.738.0 (#9735)
* chore(main): release 1.738.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.738.0
2026-06-23 21:08:15 +00:00
Ruben Fiszel cfb9f1dbc2 feat: render mermaid diagrams in chat code blocks (#9738)
* feat: render mermaid diagrams in chat code blocks

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: guard mermaid render against out-of-order async and transient streaming failures

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: only show mermaid diagram while it matches current source

Addresses Codex review: keeping the last good SVG through parse failures left a stale, mismatched diagram on screen when the source changed to something invalid. Tie the rendered SVG to the source that produced it and only display it while it still matches the current code, falling back to the raw source otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:02:10 +00:00
hugocasa cbf54d4eb4 fix: preserve fork parent linkage on workspace id change (#9716)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:54:07 +00:00
hugocasa 9793d01575 feat: add resource and infrastructure telemetry (#9737)
* feat(telemetry): disclose resource and infra usage stats

When minimal telemetry is disabled, the stats payload now includes resource
counts (workspaces, scripts per language, flows, workflows as code, low-code
and raw apps) and, on EE only, infrastructure info (container runtime,
database size, max connections, RDS detection).

Update the telemetry disclosure in instance settings accordingly: resource
counts are listed for both CE and EE; infra info is shown only on EE since it
is collected only there. Bump the EE ref and add the sqlx cache for the new
queries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(telemetry): expand EE infra disclosure and add sysinfo dep

Disclose the expanded EE infrastructure telemetry (deployment mode, host
OS/arch/CPU/memory, filesystem space, Postgres version and connection counts,
object storage backend, sandboxing and retention settings) in instance
settings. Add sysinfo as a windmill-common dependency for host memory and
filesystem stats, bump the EE ref, and add the sqlx cache for the new queries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(telemetry): focus EE infra disclosure on wrapping platform

Drop the single-server host details (OS, arch, CPU, memory, filesystem) and
tuning config from the EE infra disclosure, and revert the sysinfo dependency
they required. Reflect managed-database-provider detection in place of the RDS
flag. Bump the EE ref and update the sqlx cache for the revised queries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(telemetry): drop deployment mode and worker count from disclosure

Remove deployment mode and worker count from the EE infra disclosure to match
the backend, and bump the EE ref. They reflect only the node sending telemetry,
not the deployment topology.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to 6d3301507db50818f1683dac3941d3e0cf1152a7

This commit updates the EE repository reference after PR #627 was merged in windmill-ee-private.

Previous ee-repo-ref: d30e7d18d14992598a97356d0ed13f7d5d585115

New ee-repo-ref: 6d3301507db50818f1683dac3941d3e0cf1152a7

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-06-23 20:53:12 +00:00
hugocasa 24446e8009 fix: allow object storage test for non-super-admins, harden on cloud (#9739)
* fix: allow non-super-admin object storage test, harden SSRF surface on cloud

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: validate effective object storage host to close region/bucket SSRF bypass

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: validate gcs_base_url/token_uri in GCS service account key to close SSRF bypass

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: match url scheme case-insensitively in object storage host validation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:50:40 +00:00
Ruben Fiszel e90b2be8fa perf(monitor): skip protected prefix in retention delete via cross-batch watermark (WIN-2088) (#9744)
The expired-job retention loop re-scanned the same oldest rows on every batch. When
the oldest completed jobs are undeletable (children of a still-active root flow), the
ORDER BY completed_at ASC scan walked that protected prefix on each of the up-to-20
batches, doing a v2_job PK lookup per row — quadratic in prefix size (measured ~9s/batch,
~180s/cleanup-cycle on a 1.5M-row prefix).

Carry a completed_at watermark (max deleted) across batches and re-apply it as
completed_at >= floor so each batch resumes past the already-processed prefix. Also skip
the v2_job join entirely when no old root flow is active (the common case), since nothing
is protected then. Measured: subsequent batches 9000ms -> 159ms; empty-set path 154 -> 36ms.

The watermark only ever skips rows the current run already deleted, was protecting, or
skip-locked — all deferred to the next run, identical to the unbounded scan's row set
(verified: union of batched deletes == single delete, 0 diff). Mirrored in
windmill-api-settings log_cleanup.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:49:55 +00:00
Ruben Fiszel 29c67ced97 fix(frontend): debounce external code→Monaco sync in Editor (#9743)
* fix(frontend): debounce external code→Monaco sync in Editor

Make the external `code` prop → Monaco sync always-on and 500ms
debounced, replacing the opt-in `syncExternalCode` prop. Removes the
prop from the two inline rawscript call sites in FlowModuleComponent.

Includes temporary debug scaffolding (A→B executeEdits button and
console logs) for diagnosing successive-edit behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(frontend): share alignCodeWithEditor + bump debounce to 800ms

Extract the full-range executeEdits sync into alignCodeWithEditor() and
reuse it from both setCode and the debounced external-code effect. Bump
the external-sync debounce 500ms -> 800ms. ScriptEditor now calls
editor.setCode when syncing external code in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nits

* nits

* Fix AI not seeing latest code

* remvoe debug button

* nit types

* nits

* Nits

* Check timeoutModel is undefined

* fix(frontend): suppress editor echo in external code sync to prevent typing clobber

* fix(frontend): cancel pending keystroke debounce in setCode to prevent clobber

* fix(frontend): preserve pending external code write in updateCode

* Revert "fix(frontend): preserve pending external code write in updateCode"

This reverts commit 731d877730.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Diego Imbert <diego@windmill.dev>
2026-06-23 20:45:02 +00:00
Ruben Fiszel 6dfccd9d88 frontend improvements 2026-06-23 17:52:22 +00:00
Diego Imbert fc797a35fe fix(ai-chat): Fix incorrect editor edits from ai chat #1 (#9741) 2026-06-23 15:23:06 +00:00
Diego Imbert 11d0e65f3a fix(frontend): preserve editor content when closing instance settings drawer (#9740)
Closing the Instance settings drawer cleared the underlying script
editor. On unmount, SuperadminSettingsInner.removeHash() stripped the
`#superadmin-settings` hash with a SvelteKit `goto()`, and that
navigation re-fired the script editor page's path-reactive `$effect`,
reloading the script and wiping unsaved editor content.

Use `replaceState` to drop the hash without a navigation (matching the
existing RunForm.svelte pattern), guarded against router-teardown throws.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:31:41 +00:00
Ruben Fiszel 984ea728d9 fix: pipeline annotation false-positives from body comments (#9736)
* fix: reject pipeline `# tag` annotation false-positives on regular comments

`parse_pipeline_annotations` treats any comment line starting with
`# tag <text>` as a worker-tag annotation. In Python scripts, ordinary
English comments beginning with "# tag ..." were misinterpreted: values
over 50 chars failed the `script.tag` INSERT (varchar(50)), and shorter
ones silently overrode the script's worker tag.

Worker tags are single-word identifiers (e.g. `heavy`, `gpu`), so reject
any candidate that contains whitespace or exceeds 50 characters. Mirror
the same validation in the TS parity parser and add regression tests on
both sides.

Fixes WIN-2090

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: restrict pipeline annotation scan to the leading comment header

The root cause of the `# tag` false-positive is broader than the `tag`
keyword: `parse_pipeline_annotations` scanned every comment line in the
whole file, so any body comment matching an annotation grammar
(`on`, `freshness`, `tag`, `retry`, ...) was misinterpreted. The `tag`
case was the most visible because an over-length value crashed the
`script.tag` INSERT (varchar(50)).

Windmill's other comment-directive parsers (BashAnnotations::sandbox_image,
ssh_target) already scan only the leading comment header and stop at the
first line of real code. Align parse_pipeline_annotations (and its TS
mirror) with that convention: skip blank lines, break on the first
non-comment line. This eliminates body-comment false-positives for every
annotation, not just `tag`.

The `tag` whitespace/length guard from the previous commit is kept as
defense for prose that sits in the header itself.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 12:35:37 +00:00
hugocasa ba4b368706 fix: prevent variable push from corrupting is_secret variables (#9705)
* fix: prevent variable push from corrupting is_secret variables

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(cli): unit-test looksLikeWorkspaceCiphertext shape detection

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(cli): scope is_secret downgrade to single-file push, not sync push

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(cli): warn when variable push stores a secret value as already-encrypted

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(cli): route workspace-resolution and auth diagnostics to stderr

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(cli): rephrase comments to describe current behavior, not history

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 12:45:36 +02:00
Ruben Fiszel 723a65920f chore(main): release 1.737.0 (#9728)
* chore(main): release 1.737.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.737.0
2026-06-23 12:10:15 +02:00
Ruben Fiszel c644311eca fix(ext-jwt): reject external JWT auth for non-existent workspaces (#9723)
* fix(ext-jwt): reject external JWT auth for non-existent workspaces

External JWTs are validated (not generated) on our side and never revoked
by us. The usage-tracking upsert into unique_ext_jwt_token ran
unconditionally, so a token carrying a workspace_id whose workspace no
longer exists kept refreshing its row on every presentation — surfacing
as a "new token" in the superadmin external-JWT view.

Gate jwt_ext_auth on the requested workspace existing (EE companion). When
it does not, auth fails (token is unusable) and no usage row is written.
The check is existence-only and intentionally ignores the soft-delete
flag, so deleted-then-restored workspaces keep working.

Bumps ee-repo-ref.txt to the EE companion commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(ext-jwt): cache workspace-existence lookups in jwt_ext_auth

Bumps ee-repo-ref.txt to the EE companion commit that caches the
workspace-existence check added in the previous commit, so a token aimed
at a missing workspace no longer hits the DB on every request (auth
failures aren't cached upstream).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to ac1f6f666f36141cb6ba6f8eaa614821a90464ad

This commit updates the EE repository reference after PR #626 was merged in windmill-ee-private.

Previous ee-repo-ref: e23fa03ec16909c127e8ecf0855595911c29512d

New ee-repo-ref: ac1f6f666f36141cb6ba6f8eaa614821a90464ad

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-06-23 11:25:23 +02:00
Ruben Fiszel 31d9215e5a fix: bound orphan-cleanup drain rate with capped multi-batch loop (#9730)
Follow-up to #9727. The orphan cleanups (cleanup_job_perms_orphaned and
cleanup_job_result_stream_orphaned_jobs) deleted at most one 100k batch per
monitor iteration. Each statement stays short and lock-light, but a single
batch per ~30s cycle caps the drain rate at ~100k/30s, so a large one-time
backlog (tens of millions of rows) takes ~hours to clear.

Loop the batched delete up to ORPHAN_CLEANUP_MAX_BATCHES (10) times per cycle,
stopping early once a batch deletes fewer than ORPHAN_CLEANUP_BATCH_SIZE rows.
Each DELETE remains bounded (≤100k, short locks, no long single statement),
while per-cycle throughput rises to ~1M rows so backlogs drain ~10x faster.
The per-cycle cap keeps monitor_db responsive.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:24:36 +02:00