Commit Graph

12959 Commits

Author SHA1 Message Date
Ruben Fiszel abcd920964 test: isolate WAC v2 python test from stack overflow (#8979)
* test: isolate WAC v2 python test from test-thread stack overflow

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

* ci: bump RUST_MIN_STACK to 4MB for backend tests

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-29 19:50:17 +00:00
hugocasa e9e72fbbf8 feat: edit scopes on existing API tokens (#8967)
* feat: edit scopes on existing API tokens

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

* fix: address PR review feedback on token scope edit

- add SECURITY DEFINER to notify_token_scopes_change so trigger fires under windmill_user/admin roles (cubic P1)
- drop banned $bindable(default) on optional props (CLAUDE.md): make ScopesPicker.value and EditTokenScopesModal.open required
- detect MCP only when *every* scope starts with mcp: so mixed/null-scope tokens fall back to standard picker without dropping non-mcp scopes
- audit log scope payload via serde_json instead of Rust {:?}

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 19:49:56 +00:00
Ruben Fiszel de0b6b1528 feat: workspace-shared ui/ folder reusable across raw apps (#8974)
* feat: add workspace-shared ui/ folder reusable across raw apps

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

* feat: add shared ui/ drawer in raw app editor sidebar

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

* feat: forward workspace shared ui/ to raw app editor iframe

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

* all

* all

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:54:55 +00:00
Guilhem 5861dcad58 fix(cli): debounce wmill dev flow round-trip 200ms (#8977)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-29 18:02:39 +00:00
Ruben Fiszel 1169d9bfd3 feat: add delete_after_secs and sensitive_inputs for raw app runnables (#8975)
* feat: add delete_after_secs and sensitive_inputs to raw app policy

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

* chore: simplify sensitive toggle label

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

* chore: use tertiary text for sensitive toggle label

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

* chore: unset sensitive field when toggled off

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

* fix: address PR review feedback

- plumb force_viewer_sensitive_inputs/delete_after_secs so editor preview
  matches deployed-mode encryption
- reuse resolve_delete_after_secs helper for consistency with scripts/flows
- log+ignore schedule_job_deletion errors so a failed schedule doesn't
  surface as an execute_component failure
- fix text-primay typo in CacheTtlPopup and DeleteAfterUsePopup
- tighten extraFields return type to Partial<Pick<...>>

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:15:03 +00:00
hugocasa 97b8bb73ab chore: bump protobufjs and other transitive deps in pulumi benchmark (#8972)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 16:10:47 +00:00
Ruben Fiszel 8627d3c5ae fix: show skipped label on flow progress bar (#8973)
* fix: show skipped label on flow progress bar for skipped flows

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

* refactor: narrow is_skipped via 'in' operator on Job union

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 15:53:34 +00:00
centdix 4098793db2 fix: split flow prompts for frontend chat (#8968)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-29 14:56:51 +00:00
Ruben Fiszel cec84849b9 feat: OTEL span status on failed jobs + Python stderr severity classification (#8918)
* feat: set OTEL span status on failed jobs and add stderr severity toggle

Record otel.status_code=ERROR and otel.status_description on the job /
job_postprocessing tracing spans when a job fails, so OTel exporters
(Sentry, Honeycomb, Datadog) see the standard span-level failure signal
instead of just the success=false attribute. Description is truncated to
512 chars to keep span payloads bounded.

Add OtelSettings.stderr_default_severity instance setting (error | warn |
info | debug, default error) to let operators downgrade the OTEL severity
used for job stderr output. Python logging routes every record >= WARNING
to stderr, so the historical blind stderr->error mapping produces false
positives for scripts like dlt.

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

* chore: update ee-repo-ref for stderr severity toggle

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

* fix: clarify OTEL span description for already-completed jobs

When handle_queued_job returns Ok(false) on Error::AlreadyCompleted
(another worker already finished the job during a race), the span
was labeled "job returned false" which is opaque to operators.

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

* feat: parse Python logging severity from job stderr

Replace the global stderr_default_severity instance toggle with a
worker-side classifier that recognizes Python's canonical
basicConfig() format (LEVELNAME:logger.name:message) and emits the
corresponding tracing level. Lines that don't match keep the
historical tracing::error! fallback, so genuine failures still
surface and non-Python output is unaffected.

Removes StderrLogSeverity, STDERR_LOG_SEVERITY, and the
otel.stderr_default_severity field; adds
classify_python_logging_line in windmill-common.

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

* test: cover classify_python_logging_line + fix truncate_description doc

Add unit tests for the Python stderr-severity classifier and correct
the truncate_description docstring to say "bytes" (the cap is byte-
based, with UTF-8 boundary rounding for safety). Addresses Claude
review feedback on PR #8918.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: hugocasa <hugo@casademont.ch>
2026-04-29 14:14:47 +00:00
centdix b883f9a9d2 feat: add ai chat schedule and trigger tools (#8961)
* feat: add ai chat schedule and trigger tools

* refactor: use zod for ai chat workspace tools

* refactor: let ai provide runnable target fields

* refactor: generate ai chat workspace tool schemas

* fix: add object type to composed tool schemas

* fix: avoid top-level trigger schema unions

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

* fix: block undeployed workspace ai tools

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

* fix: inject ai workspace tool target

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

* test: add ai evals for workspace tools

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

* test: make workspace tool eval prompts realistic

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

* fix: surface workspace tool errors

* fix: show workspace tool success details

* fix: describe workspace tool path format

* fix: clarify workspace path examples

* fix: tighten workspace tool validation

* fix: align workspace tool prompts

* chore: mark generated chat schemas

* chore: mark generated cli skills

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-29 14:00:01 +00:00
centdix 34b549cfe2 perf: optimize datatable app chat schemas (#8960)
* perf: optimize datatable app chat schemas

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

* perf: optimize datatable catalog queries

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

* refactor: narrow datatable chat optimization

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

* fix: restrict datatable schema lookups

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

* fix: block system datatable schema lookups

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

* fix: handle datatable context edge cases

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

* fix: handle datatable schema edge cases

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-29 13:58:56 +00:00
hugocasa c0eeea9c83 feat: support S3Object input args in native SQL scripts (#8954)
* feat: support S3Object input args in native SQL scripts

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

* fix: review fixes from local-review

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

* update parser

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-28 20:01:06 +00:00
hugocasa c95642863e feat: support restart from steps inside BranchOne, ForLoop, Subflow (#8955)
* feat: support restart from steps inside BranchOne, ForLoop, Subflow

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

* fix: preserve original job kind in nested restart, support expanded subflow steps

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

* fix: read selected iteration from graph state for nested ForLoop restart

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

* feat: iteration selectors per ForLoop in restart popup, more nested restart tests

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

* refactor: extract useNestedRestartState composable

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

* test: cover deployed-subflow + FlowDependencies path in nested restart

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

* chore: update sqlx prepare cache

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

* fix: detect BranchOne/ForLoop ancestors inside expanded subflows for nested restart

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

* fix: hide restart button for non-restartable steps (parallel containers, untaken branches)

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

* fix: address review feedback on nested restart PR

- preview FlowRestartButton: hide nested case (chain UUIDs aren't resolvable in
  preview path; users can use the run page for nested restart instead)
- branchOneAncestorMatchesOriginal: be permissive when status isn't reachable
  (don't hide the button for BranchOnes nested deeper than top-level)
- worker_flow.rs: apply nested_restart_payload swap on the is_simple ForLoop
  fast path too, so simple iterations don't bypass restart spawn interception
- FlowStatusViewer: reset expandedSubflows cache on jobId change; drop
  $bindable({}) banned pattern for the new prop
- API resolver: validate the leaf step exists before returning (fail-fast)
- doc fix: branch_or_iteration_n is 0-based, not 1-based
- selectedJobStepIsTopLevel reset on early-return in composable
- comment iterationCounts collision caveat
- new HTTP-level integration tests covering the API endpoint contract:
  happy path (top-level + nested), unknown step, out-of-range iteration,
  parallel-loop rejection

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

* revert: remove unreachable nested-restart swap on is_simple ForLoop fast path

The swap is unreachable in valid flows: `is_simple_modules` requires the body
to be a single `script` / `rawscript` / `flowscript` (per `FlowModule::is_simple`),
none of which spawn flow-kind children. Any nested-restart chain targeting a
leaf inside such an iteration is rejected by the API at leaf validation. Even
if a chain reached the worker via `JobPayload::RawFlow.restarted_from`, the
resulting `RestartedFlow` would fail to push (script kind isn't a flow kind).

Replaced the swap with an explanatory comment so the next reader knows why
the symmetry with the non-simple path was deliberately not added.

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

* fix: handle undefined expandedSubflows + tighten branchOne match check

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:00:03 +00:00
centdix 77d9a53423 fix: strip additionalProperties from google schemas (#8964)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-28 19:59:45 +00:00
hugocasa 70b90c41dc fix: prevent React app editor from overwriting files on theme switch (#8965)
* fix: prevent React app editor from overwriting files on theme switch

The ui_builder iframe src embeds the dark-mode flag, so toggling theme
reloads it. iframeLoaded was sticky-true, so the populate effect didn't
refire and the iframe's default "Hello World" template clobbered the
user's files via its initial setFiles message.

Reset iframeLoaded on darkMode change and suppress inbound setFiles from
the iframe until our files are re-pushed.

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

* fix: cancel suppress timer on rapid theme toggles

On a second theme toggle while the previous reload's clear-timer was
still pending, that timer would fire mid-reload and drop suppression
before the iframe finished booting — letting the iframe's default
template setFiles overwrite the user's files.

Track the timer ID, cancel it whenever we re-assert suppression, and
fold the two 500ms timers into one. Race identified by cubic.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:59:26 +00:00
centdix 866623a39e chore: copy ai env files for webmux worktrees (#8966) 2026-04-28 19:59:07 +00:00
Ruben Fiszel 1d279e7a1e feat: add min release age instance settings for bun and uv (#8956)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-27 22:45:53 +00:00
Ruben Fiszel b2004f357d chore(main): release 1.692.0 (#8950)
* chore(main): release 1.692.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.692.0
2026-04-27 20:22:48 +00:00
Guilhem eebe24d8b0 feat(cli): wmill dev with per-flow proxy and responsive Dev UI (#8529)
* feat(cli): add `wmill flow dev` subcommand with per-flow reverse proxy and launch.json

Also generates .claude/launch.json for existing flow folders during `wmill init`.

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

* feat: responsive dev layout and hide splitter for single-pane views

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

* fix: clamp flow graph height between minHeight and maxHeight

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

* feat(cli): enhance app new with Claude Desktop integration and better defaults

- Add .claude/launch.json to generated app scaffold for Claude Code preview support
- Add "Open in Claude Desktop?" prompt that creates a CLI session and opens it
  in Claude Desktop Code mode via the claude://resume deep link
- Improve default CSS template with body background, system fonts, and padding

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

* fix(cli): handle both .flow and __flow suffixes in wmill dev

The flow detection in loadPaths only checked the configured suffix
(dotted or non-dotted), so users with nonDottedPaths=true who had
.flow folders (or vice versa) would see inline script edits treated
as standalone script changes instead of flow changes.

Now checks both suffix forms everywhere: type classification,
folder path extraction, path stripping, and loadWmPath lookup.
Also adds raw_app launch.json generation to init and sync pull.

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

* docs(cli): update generated skills with dev workflow and preview commands

Update cli-commands, write-flow, and raw-app skills to document the new
local dev workflow (wmill dev --path, --proxy-port, .claude/launch.json).
Add wmill script preview and wmill flow preview to all script/flow skills
so agents know how to test without deploying.

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

* fix(cli): include path in dev URL and use open.default for browser

- Append &path= to the printed/opened URL when --path is specified
- Use open.default(url) instead of open.openApp for more reliable browser opening

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

* feat(cli): add Claude CLI/Desktop detection hints in wmill flow new

Show contextual instructions for previewing flows based on available tools.

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

* chore: regenerate auto-generated CLI skills for new dev flags

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

* fix(cli): handle mixed flow suffixes in dev file watcher

The ignore() function uses isFlowPath() which only checks the configured
suffix (__flow or .flow), causing files in the other variant to be silently
ignored. Bypass the ignore check for any file inside a flow folder and
force flow type detection regardless of suffix configuration.

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

* feat(cli): drop default proxy in flow folders, open browser, add --no-browser

Manual `wmill dev` in a flow folder should not implicitly enable the
reverse proxy. Both proxy and legacy modes now open the browser; the
new --no-browser flag opts out. Claude Code launch.json templates pass
--no-browser so the IDE preview doesn't fight a system browser window.

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

* feat(cli): gate dev broadcasts by --path and push currentLastEdit on connect

When --path (or auto-detected flow path) is set, drop file events for
any other path so the dev page stays locked to the requested resource
and currentLastEdit can never reflect an unrelated edit. The connection
handler proactively pushes currentLastEdit so the page renders without
waiting for the first file change.

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

* fix(frontend): prefer WebSocket for flow round-trip when wmill dev is connected

updateFlow used isInIframe priority, which routed Claude Code's iframe
preview through postMessage (no listener) and silently dropped flow
edits. Flip the priority: when the wmill dev WebSocket is open, use it
(covers standalone tabs and Claude Code's preview); fall back to
postMessage only when no WS is connected (the VS Code extension's iframe
URL has no `local=true`, so it never opens one). Also stop assigning
lastSent before a channel actually accepted the message, so a CONNECTING
WS doesn't silently swallow the first change.

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

* feat(dev): url is source of truth for path; add workspace file picker

Drops the server-side --path gate added in 3c2d5155e1. The dev page now
filters by its URL's ?path= and the CLI is a dumb broadcaster, which
lets multiple tabs each watch different paths. When the URL has no
?path=, the page asks the CLI for a list of workspace items (flows,
scripts, raw_apps) via a new {type:'listPaths'} WS message and renders
a picker. Clicking a flow or script soft-updates the URL via
history.pushState and loads it; raw_apps surface a hint to use
`wmill app dev` since they don't render here.

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

* feat(dev): picker uses homepage tree view with summaries

Replace the hand-rolled Button-list picker with a TreeView-style layout
that mirrors the Windmill homepage: folder/user tree grouping via
`groupItems`, item rows rendered through the shared `Row.svelte` (no
actions, no favourites, no link — just the visual), a `SearchItems`
fuzzy filter with the same search input styling and placeholder as the
homepage, and `group-open:` chevron toggling on native <details>.

The CLI's listWorkspacePaths now also reads summaries from each item's
metadata (flow.yaml for flows, <script>.script.yaml for scripts) in
parallel so the picker shows summaries as the primary row label, same
as the homepage. Raw apps have no standard manifest so they show the
path only.

Additional polish: title shows "<workspace> (local)" instead of
generic text, subtitle trimmed, item-wrapper owns the border-b so
Row's internal last:border-b-0 doesn't zero it out, summary border
gated on group-open: to avoid doubled lines when a folder is
collapsed.

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

* fix(cli): wmill dev --no-browser was a no-op

Cliffy's `.option("--no-browser", ...)` creates an option named
`browser` (boolean, default undefined) that becomes `false` when the
flag is passed. The previous code checked `opts.noBrowser`, which
Cliffy never populates, so the guard silently no-op'd and the browser
always opened. Rename to `browser` and check `=== false` explicitly,
matching the `wmill app dev --no-open` convention.

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

* feat(dev): picker warns when wmill dev server is unreachable

Track WS state in Dev.svelte (connecting/open/closed) — 'closed' is
set on either the WS error or close event. When closed, the picker
replaces the toggle + search + tree with a warning Alert telling the
user to run `wmill dev` from the workspace root. Toggle and search are
hidden rather than rendered disabled because there's nothing to filter
anyway.

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

* refactor(cli): rename wmill dev --no-browser to --no-open

Match the pre-existing `wmill app dev --no-open` flag. Having
`--no-browser` on one dev command and `--no-open` on the other was
just an oversight from my earlier change. All three launch.json
templates (init, flow new, sync pull) switch to `--no-open`.

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

* feat(cli): wmill init creates root .claude/launch.json for the picker

Adds a workspace-root .claude/launch.json so Claude Code can launch
`wmill dev` from the project root and land on the file picker (no
--path → picker mode). Per-flow and per-raw_app launch.json files are
already generated by the existing scans. Skipped (with a gray log) if
the file already exists, so the user's customizations are preserved.

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

* feat(cli): add skipClaudeAssets wmill.yaml flag

When `skipClaudeAssets: true` is set in wmill.yaml, all generators
that previously wrote Claude-specific assets become no-ops:

- writeAiGuidanceFiles skips CLAUDE.md and .claude/skills/
  (AGENTS.md is still written — vendor-neutral)
- wmill init skips the root .claude/launch.json + per-flow +
  per-raw_app launch.json scans
- wmill sync pull skips the per-flow + per-raw_app launch.json scans
- wmill flow new skips the new flow's .claude/launch.json
- wmill app new skips the new raw_app's .claude/ folder + launch.json

The flag is added to SyncOptions, DEFAULT_SYNC_OPTIONS, and the
generated wmill.yaml template (commented out — opt-in).

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

* feat(cli): wmill init removes existing Claude assets when skipClaudeAssets is set

Re-running `wmill init` with `skipClaudeAssets: true` now removes
previously-generated Claude assets so the workspace state matches the
config. Narrow scope, no confirmation:

- per-flow / per-raw_app .claude/launch.json (each parent .claude/
  collapsed if empty)
- root .claude/launch.json
- .claude/skills/ (wholly ours; safe to remove the subtree)
- root .claude/ collapsed if empty
- CLAUDE.md only if its content matches the default
  ("Instructions are in @AGENTS.md\n"); otherwise left in place
  with a note

Each removal is logged in yellow under a single gray intro line that
prints lazily on the first removal — a clean tree stays silent.

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

* fix(cli): wmill workspace add browser open silently no-ops

`open.openApp(open.apps.browser, { arguments: [url] })` resolves its
Promise even when the OS-level launch does nothing, so the CLI prints
"Opened browser for you" but no tab appears. Same pattern was already
fixed in `dev.ts` by commit 3272c29c2e — use `open.default(url)`,
which delegates to the native URL opener (`open` on macOS, `xdg-open`
on Linux, `start` on Windows) and actually rejects on failure.

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

* fix(cli): wmill init workspace prompt no longer duplicates active profile name

Cliffy's Select.prompt renders `default: X` as `(X)` next to the
question header, which duplicates whichever workspace name the
default points to. Drop `default` and instead reorder the list so
the active profile is first (cursor-preselected by virtue of position)
and append "— active" to its label so the indicator lives where it's
contextually relevant.

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

* docs(skills): expand preview-vs-run guidance for write-flow + all write-script-* skills

Both `wmill flow preview/run` and `wmill script preview/run` have the
same intent split — preview hits the local file, run hits the deployed
version, sync push deploys. The skills' "after writing" sections used
to terse-list the commands and just say "do not run them yourself",
which encouraged the wrong reflex of `sync push` + `run` to "test".

Rewrite the section in both `system_prompts/base/flow-base.md` (drives
write-flow) and the `script_cli_intro` block in
`system_prompts/generate.py` (drives all write-script-<lang>) to:

- explicitly list `preview` as the default for local iteration,
- spell out the few cases when `run` or `sync push` are appropriate,
- offer to test as a one-sentence next step (no multi-option menus),
- mark `preview` as safe to run autonomously.

Regenerate auto-generated/ + cli/src/guidance/skills.ts.

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

* refactor(cli): wmill dev — clearer mode names and accurate startup messaging

- Rename `startLegacyServer` to `startDirectServer`. "Legacy" implied
  it was on the way out; the two modes (proxy vs direct WS) actually
  serve different topologies and both stay. Add comments above each
  section spelling out who they're for: proxy mode for embedders that
  require a localhost origin (Claude Code preview), direct mode for
  standalone browser tabs and the VS Code extension iframe.

- Replace the stale "Dev server will automatically point to the last
  script edited locally" log line. Now print path-aware text:
  - with --path (or auto-detected): "Watching <path> — edits will live
    -reload in the dev page"
  - without: "Open the dev page and pick a flow or script to preview —
    edits will live-reload" plus a hint about --path
  Mirror the same in proxy mode after the listen callback.

- Drop the redundant "Go to <url>" line when --no-open isn't passed
  (maybeOpenBrowser already prints "Opened browser at <url>").

- Rename "Server listening on port 3001" to
  "Dev WebSocket listening on ws://localhost:<port>/ws" so the line's
  purpose is obvious.

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

* refactor(cli): drop per-folder .claude/launch.json generation

Stop creating `.claude/launch.json` inside every flow folder, raw_app
folder, and at `wmill flow new`/`wmill app new` time. The workspace-
root `.claude/launch.json` from `wmill init` stays — it's the picker
entry point and the one place where the deterministic "click → preview"
UX is high-value.

Removed from:
- `wmill init` — per-flow + per-raw_app scans
- `wmill sync pull` — per-flow + per-raw_app scans (also drops the
  now-unused `node:fs` mkdirSync/writeFileSync import)
- `wmill flow new` — bootstrap no longer scaffolds `.claude/`
- `wmill app new` — same; also drops the `.claude/launch.json` lines
  from the post-create directory listing

Skills already give the agent the right CLI commands, so per-folder
launch.json was redundant context. Existing files in user projects
keep working but won't be regenerated; `wmill init` with
`skipClaudeAssets: true` cleans them up.

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

* feat(cli): wmill app new flags + tighten raw-app skill for AI agents

`wmill app new` is interactive by default, which hangs forever when an
AI agent tries to use it. Add flags so the wizard can be bypassed
end-to-end:

- `--summary <text>`, `--path <path>`, `--framework <react19|react18|
  svelte5|vue>` (required for non-interactive)
- `--datatable <name>` (opt into the datatable wizard)
- `--schema <name>` (creates schema with CREATE SCHEMA IF NOT EXISTS
  if it doesn't already exist; only valid with --datatable)
- `--overwrite` (replace existing directory without prompting)
- `--no-open-in-desktop` (suppress the Claude Desktop offer)

Mode is auto-detected: providing any of --summary/--path/--framework
puts the run into non-interactive mode where the datatable wizard,
overwrite prompt, and Claude Desktop prompt all skip silently (or fail
fast on conflict instead of waiting for stdin). Each provided flag is
validated upfront with a clear error message.

Skill side: rewrite `system_prompts/base/raw-app.md`'s "Creating a Raw
App" section so the AI agent knows it should run the command itself
with flags (not tell the user to run it interactively). Direct the
agent to use `AskUserQuestion` with one bundled call to gather any
missing summary/path/framework — refuse to invent values, refuse to
default. Anti-patterns spelled out explicitly.

AGENTS.md template (`cli/src/guidance/core.ts`) had a contradicting
line ("MUST ask the user to run wmill app new in its terminal first")
that was loaded eagerly into agent context and overrode the skill —
replaced with the same agent-driven guidance, pointing to the
raw-app skill for the full procedure.

Regenerate auto-generated/ + cli/src/guidance/skills.ts.

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

* feat(cli): per-target preview launch.json + agent uses wmill flow new

Refactor the agent's dev/preview workflow:

- Drop root .claude/launch.json generation from `wmill init`. Sharing one
  generic entry across sessions caused preview collisions; agents now add
  per-target named entries (windmill: <wmill_path>) on demand.
- New `preview` skill in system_prompts/base/preview.md. Branches on
  whether `mcp__Claude_Preview__*` MCP tools are available: with them,
  add a per-target launch.json entry pinning its own port + --proxy-port
  + --path + --no-open and invoke the MCP preview tool; without them,
  start `wmill dev --path <X> --no-open` directly and hand the URL the
  CLI prints to the user. Never touch launch.json in the direct case.
- Agents must run `wmill flow new <path>` themselves to scaffold flows
  (folder + flow.yaml with the right suffix), parallel to the existing
  `wmill app new` rule. Missing path/summary trigger AskUserQuestion;
  no inventing values.
- write-flow skill: 4-step Creating a Flow procedure that opens the
  visual preview *before* editing flow.yaml so the user watches the
  flow take shape via live reload.
- `wmill flow new` always prints the `wmill dev --path <X>` preview
  hint; drop the Claude CLI/Desktop detection branches.

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

* docs(skills): open app preview before editing in raw-app skill

Mirrors the flow skill's Step 3 — opening `wmill app dev` via the
preview skill before touching App.tsx so the user watches the app
take shape via live reload, instead of seeing the finished result
at the end.

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

* fix(dev): guard WS replaceFlow with lockChanges to prevent echo

The postMessage handler at Dev.svelte:306-312 wraps replaceFlow with
lockChanges = true (cleared 500 ms later) so the $effect on
flowStore.val doesn't immediately re-serialize and re-send the freshly
received payload. The WebSocket handler did not, so on the initial
flow push (dev.ts:568-574 sends currentLastEdit on connect), the
client would echo back to handleFlowRoundTrip, which runs the
orphan-file scan. On content equality the write was a no-op, but the
scan could still delete files the server did not list.

Mirror the same lockChanges/timeout pattern in the WS replaceData
handler. Apply to both flow and script paths for symmetry.

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

* fix(cli): correct wmill dev description + gate broadcasts server-side

Two related fixes:

1. The 'auto-pushes them to the remote workspace' wording in the
   wmill dev description was wrong — the command never deploys, it
   only broadcasts file changes over WS for live preview. Reworded
   to call this out explicitly and point at 'wmill sync push' for
   the deploy case.

2. Move the path filter out of the client (Dev.svelte:491-495) and
   into broadcastChanges. Earlier the filter was client-side with
   the comment 'server stays a dumb broadcaster' even though commit
   3c2d5155 was titled 'gate dev broadcasts by --path'. Doing the
   compare server-side aligns the implementation with the commit
   narrative, cuts WS traffic when --path is set, and keeps the
   per-tab semantics for the picker (each picker tab still gets the
   full 'paths' listing on first connect).

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

* fix(cli): drop dead launch.json cleanup, fix description, narrow orphan scope

Three review fixes:

1. cleanupClaudeAssets removed both root and per-folder
   .claude/launch.json files that this CLI never generates anymore.
   Per the user's "feature hasn't been released yet" guidance, no
   migration is needed — drop the dead scan and the root rm. Also
   drop the now-unused nonDottedPaths argument (and its flowSuffix
   / rawAppSuffix locals).

2. The skipClaudeAssets description in template.ts listed
   .claude/launch.json among the assets it skips, but launch.json
   is no longer generated. Drop it from the description string.

3. The dev round-trip's orphan cleanup deleted any non-dot file in
   a flow folder that wasn't in extractedPaths — including
   README.md, fixtures, TODO.md, etc. Restrict the deletion to
   files whose extension is in a known inline-script set
   (.ts/.js/.py/.go/.sh/.sql/.ps1/.php/.rs/.java/.cs/.r/.graphql).

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

* refactor(cli, frontend): dedupe flow suffix helpers, use UI components

Five small follow-ups from the PR review:

1. dev.ts already had stripFolderSuffix() but three callsites were
   reimplementing the same .flow/__flow if-else inline. Add an
   isFlowFolderName(name) helper next to it and replace the duplicates
   in startProxyServer's cwd check, the file-watcher localPath strip,
   and normalizeWmPath.

2. Dev.svelte:866 was a <div onclick> with two svelte-ignore comments
   for the missing a11y handlers. Replace with a real <button
   type="button"> — kills the warnings, no visual change.

3. Dev.svelte:1283 was a raw <input type="text"> for the module
   summary. Replace with the existing <TextInput> component (same one
   the picker search at :1010 uses), per frontend/CLAUDE.md.

4. Dev.svelte:197 typed relativePaths as any[]; tighten to the actual
   union (string | [number, string])[] — the python helper returns
   tuples, the typescript one returns strings.

5. app/new.ts:822 fired exec("open <deeplink>") with no callback, so
   an OS that refused the URL scheme silently failed and we still
   logged "Opened in Claude Desktop!". Move the success log inside an
   exec callback that surfaces the error and prints the deep link for
   manual opening.

Plus a brief comment above parseWatchPath explaining its resync
contract (initial load + popstate + explicit pickPath, no generic
pushState listener).

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

* fix(cli): regenerate auto-gen for dev description; drop apostrophe to satisfy parser

generate.py:326 extracts .description() with the regex
[^"\']+ which bails on either quote type. Commit ff3a8e4ebd's new
description had an apostrophe inside double quotes ('wmill sync
push'), so the parser saw no description at all and the
auto-generated files dropped the line entirely — which is what
check-freshness caught on origin/main.

Quickest path to green CI: rephrase the description without the
inner apostrophe, then regenerate. The generator's regex is the
real bug but fixing it is out of scope here.

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

* feat(cli): seed app .claude/launch.json before opening Claude Desktop

When the user accepts "Open in Claude Desktop?" in wmill app new, write
a per-app .claude/launch.json (named "windmill: <appPath>") into the
freshly-created app folder before the deep link fires. Entry runs
'wmill app dev --no-open --port ${PORT:-4001}' from the app folder
(which is the cwd Claude Desktop opens with), so the user can hit play
right away to launch the preview.

Skip if .claude/launch.json already exists — never clobber user edits.

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

* fix toggles positions

* fix(dev): gate picker mode on ?local= so VS Code iframe still renders content

The VS Code extension iframe loads the dev page without ?path= and
without ?local=true. After the picker rework, an empty watchPath
flipped pickerMode on, so the page rendered the picker UI even
though the extension was sending replaceScript / replaceFlow
postMessages — leaving the user stuck on the picker forever.

Picker mode only makes sense on the local dev page, where the wmill
dev WebSocket can supply the workspace listing. Anywhere else (VS
Code iframe, plain remote tab) the picker has no data source and no
purpose. Add an isLocalDevPage check so the picker only shows when
?local=true is present.

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

* refactor(dev): mirror vscode extension's processFlowMessage round-trip

Three changes that bring our wmill dev round-trip into lockstep with
the windmill-vscode extension's processFlowMessage in src/extension.ts:

1. New cli/src/commands/dev/pathscript-restore.ts — verbatim port of
   the extension's src/utils/pathscript-restore.ts. Adds AI-agent tool
   walking that the previous local copy was missing (flows with
   PathScript-shaped tools weren't being preserved across round-trip).
   Header comment makes the cross-repo link explicit.

2. handleFlowRoundTrip rewritten to mirror processFlowMessage step-
   for-step: reads failure_module + preprocessor_module from the
   current flow.yaml, passes them to extractCurrentMapping, shares one
   pathAssigner across all extraction calls, extracts inline scripts
   from those special modules too, skips writing files whose content
   starts with !inline (treats as pointer directives), and only
   rewrites flow.yaml when the serialized YAML actually differs.

3. snapshotPathScripts / tagReplacedPathScripts callsites in loadPaths
   were passing the FlowFile wrapper instead of FlowFile.value — the
   helpers walk .modules / .failure_module / .preprocessor_module,
   which only exist on .value, so PathScript snapshots silently
   no-op'd on the file-watcher path. Pass .value at all four sites.

Deliberate divergence from the extension: orphan-cleanup keeps the
INLINE_SCRIPT_EXTS allow-list so README.md / fixtures aren't deleted.
The extension's version still over-deletes; that's tracked separately.

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

* docs(skills): offer visual preview after create instead of auto-opening

Both write-flow and raw-app skills used to instruct the agent to open
the visual preview without asking right after wmill flow new /
wmill app new, on the rationale that live reload is most useful when
the page is already up. In practice this surprised users — opening
the dev page has side effects (browser window pop, possibly a
launch.json entry under MCP-preview Branch A) that warrant consent.

Change Step 3 in both skills from "open it without asking" to "offer
it as a one-sentence next step" — same pattern the same skills
already use for programmatic wmill flow preview offers. Two then-
necessary anti-patterns ("just open it", "open it before editing")
are dropped along with the auto-open instruction.

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

* fix(cli): probe both ip stacks before binding wmill dev proxy / app dev port

Node's default listen() has platform-dependent dual-stack behaviour.
If the requested port is already held on the IPv4 stack, listen() can
silently fall back to binding IPv6-only ([::1]:N). The OS then routes
new localhost connections to the older IPv4 listener, so the user
opens http://localhost:N and sees a stale prior server with no signal
that anything is wrong. Bit us in practice: a leftover wmill dev
--proxy-port 4000 served traffic for a freshly-started wmill app dev
--port 4000.

New helper at cli/src/utils/port-probe.ts probes both 0.0.0.0 and ::
before binding. On collision it walks upward to the next free port
(up to +20) and logs a prominent warning naming the holder when lsof
/ ss can find it:

  Port 4000 is already in use (held by PID 91418 `bun`). Using
  port 4001 instead.

Wired into:
- wmill dev --proxy-port: the resolved port flows into both
  proxyServer.listen() and the &port=N parameter in the redirect
  URL, so they always match. Bind explicitly to 0.0.0.0.
- wmill app dev --port: only when the user passed --port explicitly
  (the default getPort.default(...) path already handles fallback).

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

* fix(dev): pass placeholder via TextInput inputProps not as top-level prop

`<TextInput>`'s top-level Props don't include `placeholder` — native
input attributes go through the `inputProps` field. The previous
`<TextInput placeholder="Summary" .../>` failed `npm run check` with
"Object literal may only specify known properties, and
'\"placeholder\"' does not exist in type 'Props<\"input\">'.".

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

* nit

* fix(cli): sequential port probe + sync dev test regex with renamed log

Two CI regressions on test-linux:

1. port-probe parallel race on Linux. isPortFreeOnBothStacks ran the
   IPv4 and IPv6 binds via Promise.all. On Linux the default is
   net.ipv6.bindv6only=0, so a bind(::, port) socket also takes the
   IPv4 stack on the same port. Concurrent v4 + v6 binds then race for
   v4 — one wins, the other gets EADDRINUSE on a port that is actually
   free. Walks 20 ports up, all fail the same way, throws, child exits.
   Tests that fetch http://localhost:port time out at 60s.
   Doesn't repro on macOS (bindv6only=1 by default — what I tested
   against). Probe sequentially so each bind fully releases before the
   next starts.

2. dev_server.test 1 regex out of sync. Commit 018dc3861a renamed the
   startup log from "Server listening on port N" to "Dev WebSocket
   listening on ws://localhost:N/ws" but didn't update the test, which
   times out at 30s waiting for the old string. Update the regex to
   match the current log.

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

* Update system_prompts/auto-generated/skills/write-script-graphql/SKILL.md

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* fix(cli): address dev/app PR review — bugs 1-7

Per code review:

1. app new.ts — wrap claude --session-id exec in try/finally so the
   spinner setInterval is always cleared. On rejection control jumped
   to the outer catch and the spinner kept writing \r forever, garbling
   subsequent output.

2. app new.ts — make --overwrite actually wipe the dir before
   re-creating. Previously logged "Overwriting" but only skipped the
   prompt; leftover files from a different framework (e.g. App.tsx
   from a prior react18 install when re-scaffolding as svelte5)
   survived and produced a hybrid scaffold.

3. dev/dev.ts — anchor the flow-folder match on path segments. The
   substring checks (cpath.includes(".flow/") / "__flow/") also fired
   on names like notes_about__flow_design/readme.md. New
   isInsideFlowFolder + findFlowFolderPrefix split on "/" and check
   segment suffixes. Drops the now-unreachable script→flow fallback
   inside the else branch.

4. dev/dev.ts — direct mode also routes through resolveBindPort so it
   detects dual-stack collisions like the proxy mode does. Bare getPort
   only probes one stack, defeating the whole point of port-probe.ts.
   Also bind to BIND_HOST explicitly. Drops the unused getPort import.

5. dev/dev.ts — normalize opts.path once after mergeConfigWithConfigFile.
   broadcastChanges compared against a non-normalized opts.path, so
   --path f/foo/ or --path f/foo.flow silently dropped every broadcast.
   Also pulls normalizeWmPath to module scope (was a closure inside dev()).

6. dev/dev.ts — guard the initial-state ws.send with readyState === OPEN,
   matching the other branches' pattern.

7. dev/dev.ts — typo: "givena" → "given a".

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

* fix(cli): address dev/app PR review — items 8-10

8. dev/dev.ts — derive INLINE_SCRIPT_EXTS from exts so adding a new
   script language to script.ts auto-extends orphan cleanup. Previously
   .gql, .nu, .rb were missing — flows using those languages would
   leave orphaned inline files behind. Excludes .yml because user
   fixtures commonly use it in flow folders, and leaving a stale
   .playbook.yml inline script is preferable to deleting a fixture.
   Keeps .js for hand-written flows that aren't in the exts list.

9. app/new.ts — wrap Claude Desktop install probe + prompt in
   process.platform === "darwin". The probe (ls /Applications/Claude.app)
   and the open command both only work on macOS — the explicit guard
   makes the platform scope grep-able.

10. app/new.ts — switch the deep-link spawn from exec(`open ${shell-
    escaped url}`) to execFile("open", [deepLink]). sessionId is a UUID
    and absAppDir is URI-encoded today so the old form was safe, but
    execFile removes the shell entirely.

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

* fix(cli,dev): cubic review — port-probe error semantics, pluralize spacing

[2] cli/src/utils/port-probe.ts — distinguish IPv6-unsupported from port
collision in isPortFree. Previously every error code returned false,
including EAFNOSUPPORT / EADDRNOTAVAIL on the IPv6 probe when the host
has no v6 stack at all (IPv4-only containers). resolveBindPort would
then walk all 20 ports getting the same error and throw. Treat only
EADDRINUSE / EACCES as "not free"; everything else as free.

[13] cli/src/commands/app/dev.ts — only probe both stacks when binding
to localhost. The dual-stack collision risk is specific to localhost
(which resolves to 127.0.0.1 + ::1); for an explicit IPv4 host there's
only one stack to worry about, so don't move the user's requested port
over a phantom v6 collision.

[14] frontend/src/lib/components/Dev.svelte — pluralize already inserts
a space between quantity and word, so " item" produced "3  items".
Drop the leading space in both call sites.

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

* docs(system_prompts): cubic review — preview args, skill scopes

Source changes in base/ + generate.py, then regenerated auto-generated/
via python system_prompts/generate.py. Per cubic review:

[4]+[7] generate.py — "pick plausible args from the `main` signature"
was language-blind. SQL queries and Bash scripts use $1/$2 positional
parameters, not a main(...) signature. Reword to call out both shapes
explicitly so the wording survives across all 19 generated language
skills (postgresql, bash, mysql, …) instead of just the ones that
happen to have main().

[5] base/raw-app.md — the "CLI Commands" table said "Tell the user
they can run these commands (do NOT run them yourself)" while the
"Creating a Raw App" section above (added in this PR) tells the agent
to run `wmill app new` itself. Carve `wmill app new` out of the table
and add a one-line note pointing back to the create flow, so the
guidance no longer self-contradicts.

[10] base/preview.md — "These print a `Go to <url>` line on stdout"
was wrong for `wmill app dev`, which prints
"🚀 Dev server running at <url>". List both line shapes explicitly and
suggest a loose http:// match for URL capture.

[12] base/flow-base.md — "regenerate lock files for the flow you
modified" misstated the default scope. `wmill generate-metadata`
scans scripts, flows, and apps by default
(see cli/src/commands/generate-metadata/generate-metadata.ts:71-73).
Update wording to call out the default scope and how to narrow it.

Also folds the cubic [1] graphql safety wording (originally a one-off
edit on the auto-generated file in a895db7) back into generate.py
itself, so it survives regeneration and applies to all language skills.

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

* docs(system_prompts): cubic round 2 — language-specific placeholder syntax

Round 1 wording was too narrow:

- "$1, $2 placeholders for SQL queries and Bash" was wrong for MySQL
  (`?`), Snowflake (`?`), MSSQL (`@P1`), BigQuery (`@name`), and
  PowerShell (which uses `param(...)`, not main()).
- The preview-skill URL match said "first `http://...` token" — remote
  workspaces serve HTTPS, so the regex would miss them.

Source-only fixes in generate.py and base/preview.md, then regenerated
auto-generated/ via python system_prompts/generate.py.

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

* fix(flow): track maxHeight in FlowGraphV2 height effect

cubic [3]: updateHeight() reads both minHeight and maxHeight, but the
$effect only tracked minHeight. Changing maxHeight alone (e.g. when a
parent shrinks the cap during a layout transition) left height frozen
at the previously clamped value.

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

* docs(system_prompts): tool-agnostic wording in base/

cubic [11]: system_prompts/README.md says these prompts must NOT
contain tool usage instructions. Three base files violated this:

- base/flow-base.md (4× AskUserQuestion). Worst offender — leaks into
  the frontend copilot via FLOW_BASE in prompts.ts (consumed by
  getFlowPrompt in frontend/src/lib/components/copilot/chat/flow/
  core.ts:1287). Frontend has no AskUserQuestion tool, so the wording
  was both irrelevant and confusing there.
- base/raw-app.md (5× AskUserQuestion + 1× mcp__Claude_Preview__).
  CLI-skill-only but covered by the same scope rule.
- base/preview.md (5× mcp__Claude_Preview__). CLI-skill-only, same.

Replaced with role descriptions: "ask the user (use a structured-
question tool if your runtime has one)" and "a tool that can embed a
localhost URL inside the IDE / chat surface". Kept one mention of
mcp__Claude_Preview__ in preview.md as an illustrative example, since
documentation of one runtime is fine — what's not fine is gating
behaviour on a specific tool name.

Source-only edits, then regenerated auto-generated/ via
python system_prompts/generate.py.

Verification: grep -r AskUserQuestion system_prompts/auto-generated/
now returns nothing. The remaining AskUserQuestion refs in
cli/src/guidance/core.ts are hand-written CLI-only AGENTS.md content
(not part of system_prompts), and Claude Code does have that tool, so
those are correctly scoped.

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

* fix(flow): drop .no-splitter CSS hack, use snippets to skip Splitpanes

cubic [8]: the previous fix for "top pane is empty in aiagent / noEditor
mode" was a CSS rule that hid `:global(.splitpanes__splitter)` inside
.no-splitter. That cascaded into nested splitpanes too — the aiagent
left/right tabs panel (line 1043), the debug-console editor split
(line 877), and the doubly-nested debug panel (line 1472) all lost
their resize handles.

Refactor the layout instead. Extract top-pane and bottom-pane content
as snippets, then conditionally render either:
  - just the bottom snippet (no Splitpanes wrapper) when the top pane
    would be empty (aiagent or noEditor), or
  - the original two-Pane Splitpanes layout otherwise.

This removes the splitter at its root rather than hiding it, so
nested splitters are unaffected. The bottom Pane's complex bind:size
getter/setter (which returned 100 when aiagent) collapses to a simple
binding now that the aiagent path no longer goes through the wrapping
Pane at all.

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

* nit

* fix(flow,preview): cubic round 3 — FlowPathViewer regression + preview skill rewrite

[3149192182] FlowModuleComponent.svelte: my last refactor's
"aiagent || noEditor" condition stripped the FlowPathViewer for
noEditor + type === 'flow', because the top-pane snippet was no longer
rendered. The flow-viewer pane is the only thing that *does* show in
that mode, so it shouldn't have been collapsed. Tighten the condition
to "aiagent || (noEditor && type !== 'flow')".

[3149060930] system_prompts/base/preview.md: Branch A detection was
too broad — "can embed or open a localhost URL" is strictly weaker
than "can read .claude/launch.json and launch a configuration". Only
the Claude Desktop / Code MCP integration does the latter; most
embedders only do the former. Restructure preview.md around two
orthogonal axes:

  1. Mode (proxy vs direct) — driven by "does the embedder need a
     localhost URL?". Direct is the default; proxy is for embedders
     that sandbox cross-origin loads.
  2. Who starts the server — you spawn `wmill dev` yourself, OR a
     launch.json-aware runtime (currently only the
     `mcp__Claude_Preview__*` MCP family) launches it on demand.

The two compose into four common cases (regular browser tab, generic
preview pane, localhost-only preview pane, Claude MCP), each with a
clear instruction. The launch.json/MCP machinery is now scoped to a
single section gated on actually having that tool in your tool list.

Source-only edit in base/preview.md, then regenerated auto-generated/
via python system_prompts/generate.py.

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

* Fix dev step display

* nit

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-04-27 20:14:21 +00:00
Ruben Fiszel e636f589a5 fix: prevent flow-dep job stalls under row-lock contention (#8952)
* refactor: split flow-dep job tx so subprocesses don't hold row locks

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

* feat: link flow version from run page to pinned flow viewer

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

* fix: time-out dep job phase 1/3 db ops and surface error on flow page

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

* fix: dissolve dep_map in phase 1 and recheck flow version unconditionally

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

* fix: address PR review — view-latest reload, decimal truncation, app version

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

* fix: keep dissolve in phase 3 for relative-import dep jobs

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

* chore: trim verbose comments and refresh sqlx offline cache

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

* fix: address cubic — propagate dissolve errors, include workspace in reload key

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:03:57 +00:00
Guilhem 02f1581e5b align folders page empty state with table style (#8920)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 15:53:01 +00:00
Ruben Fiszel 581658d881 fix(wac): recognize @workflow main, list WAC in scripts/list, run preprocessor (#8951)
* fix(wac): recognize @workflow main, list WAC in scripts/list, run preprocessor

Three workflow-as-code bug fixes:

- #8945: Python WAC template with `@workflow async def main(...)` was not
  detected as `auto_kind = "wac"`. The detection only ran when no `main`
  function was found. Hoist the heuristic so it runs whether or not `main`
  is the entrypoint.

- #8946: `scripts/list?kinds=script` filtered out WAC scripts because they
  set `auto_kind = 'wac'` and the SQL hid everything that wasn't NULL.
  Allow both NULL and 'wac' (still excluding 'lib' library scripts).

- #8947: Preprocessor functions defined alongside a WAC workflow were
  ignored. Inject the preprocessor invocation into the Python WAC wrapper
  so it runs before the workflow on the first iteration, then plumb the
  preprocessed args through `handle_wac_v2_output` so inline child
  re-runs see the post-preprocessor args via `checkpoint.input_args`.

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

* test(wac): integration tests for #8946 (scripts/list) and #8947 (preprocessor)

- test_scripts_list_includes_wac: hit GET /scripts/list?kinds=script and
  assert WAC scripts are in the response (would have failed pre-#8946 fix
  because of the auto_kind IS NULL filter).
- test_python_wac_v2_with_preprocessor: deploy a Python WAC script with a
  preprocessor, run with raw event args, assert the workflow saw the
  preprocessed shape and v2_job.args/preprocessed were updated.
- New wac_preprocessor.sql fixture with auto_kind = 'wac' set explicitly.

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

* fix(wac): address PR review feedback

Five review fixes:

- python_executor.rs: WAC preprocessor now runs inside the wrapper's
  `try:` block so failures route through the same `result.json` error
  serializer as workflow failures. Switched async-coroutine handling
  from deprecated `asyncio.get_event_loop().run_until_complete(...)` to
  `asyncio.run(...)` (the recommended primitive on 3.10+).

- bun_executor.rs: when copying preprocessed args into
  `checkpoint.input_args`, surface JSON parse failures via `?` instead
  of silently coercing to `Value::Null` (which would persist a corrupted
  arg into every child re-run). Also collapsed the redundant double
  iteration into a single pass.

- windmill-api-scripts/scripts.rs: switched the runnable-script filter
  from an allow-list (`auto_kind IS NULL OR = 'wac'`) to a deny-list
  (`<> 'lib'`), so future `auto_kind` values aren't silently filtered
  from triggers/dropdowns.

- windmill-parser-py: aligned the parser's WAC heuristic with the
  runtime detector `is_wac_v2_py` — `@task` is now optional, matching
  the runtime which says workflows that only use inline `step()` are
  still WAC. Added a regression test `test_parse_python_wac_step_only`.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:38:49 +00:00
centdix abbfd504ac feat: add agents skills to cli init (#8948)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-27 12:56:47 +00:00
Diego Imbert 15bba79ef2 fix: Audit logs filters UI spacing (#8944)
* fix: Audit logs filters UI spacing

* nit
2026-04-27 12:56:28 +00:00
Ruben Fiszel e8f7589d7a fix: delete instance settings cleared via bulk endpoint (#8949)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 12:55:12 +00:00
Ruben Fiszel 76108ed5b2 chore(main): release 1.691.1 (#8941)
* chore(main): release 1.691.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.691.1
2026-04-27 04:49:19 +00:00
Ruben Fiszel ddf14a51d8 refactor: split git repo viewer effects to remove redundant calls (#8943)
* refactor: split git repo viewer effects to remove redundant calls

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

* refactor: compact file preview header in s3 file picker

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

* refactor: skip empty preview status row when no message applies

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 04:46:22 +00:00
Ruben Fiszel b8fcb7f04b fix: preserve s3 rootPath when reloading git repo viewer (#8942)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 04:23:19 +00:00
Ruben Fiszel 2f58a31d00 fix(cli): preserve case in raw-app runnable filenames (#8940)
* fix(cli): preserve case in raw-app runnable filenames

The path assigner lowercased filesystem-safe names, so a raw-app
runnable id like CamelCaseTSRunnable produced a YAML metadata file
keeping the original case but a code file lowercased to
camelcasetsrunnable.ts. On the next push, loadRunnablesFromBackend
paired the two by case-sensitive name match, failed, and registered
the lowercase code file as a separate empty runnable — surfacing as
duplicate runnables in the app editor.

Stop lowercasing in sanitizeForFilesystem and dedupe path assigners
case-insensitively so case-only collisions still get a counter on
case-insensitive filesystems. Also match content/lock files and the
processed-id set case-insensitively in loadRunnablesFromBackend so
repos already pulled by the buggy CLI can recover on the next push
without re-pulling.

Fixes #8939

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

* refactor(cli): extract readSiblingLock helper, drop test #ref comments

Address review:
- Both call sites in loadRunnablesFromBackend now use the same
  case-insensitive lock-file lookup, so a partial-legacy repo with a
  mixed-case code file and lowercase lock (or vice versa) works in the
  orphan-code branch too, not just the YAML branch.
- Remove `Regression for #8939 …` comments from the new tests; CLAUDE.md
  bans referencing the current task/issue from source comments. Test
  names already describe the behavior under test.

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

* test(cli): update assigner-path assertions for case preservation

Three tests in inline_scripts_failure_preprocessor_unit.test.ts asserted
that the assigner produced lowercased filenames (get_users_data, step_b)
from mixed-case summaries — that was the buggy lowercasing behavior.
With case preserved end-to-end, the assigner now returns Get_Users_Data
and Step_B.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 04:02:39 +00:00
Ruben Fiszel 612a39bcfc chore(main): release 1.691.0 (#8931)
* chore(main): release 1.691.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.691.0
2026-04-24 19:49:44 +00:00
centdix 483fb1fb9a perf: reduce app ai chat token usage (#8928)
* test: add app chat token usage evals

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

* perf: make app file listing metadata only

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

* perf: reduce app datatable prompt context

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

* test: add app datatable persistence eval

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

* test: fix file manager rename app eval

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

* test: remove selected app context eval cases

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

* fix: address app eval review feedback

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-24 19:49:16 +00:00
Ruben Fiszel e732004180 fix(nativets): forward OTEL-prefixed console logs to tracing events (#8937)
* fix(nativets): forward OTEL-prefixed console logs to tracing events

Nativets jobs run in-process and bypass the handle_child.rs stdout loop
where `OTEL: ` lines are turned into `tracing::event!` calls when
`OTEL_JOB_LOGS=true`. Apply the same prefix handling in the nativets
log receiver so `console.log("OTEL: ...")` reaches the OTEL exporter
like it does for other runtimes.

Moves `OTEL_JOB_LOGS` and `OTEL_PREFIX` into windmill-common so both
crates share the same definition.

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

* fix(nativets): emit job_log tracing target so logs reach OTEL bridge

For non-native runtimes, `lines_to_stream` → `process_streaming_log_lines`
(EE) emits every stdout line as `tracing::info!(target: "windmill:job_log", ...)`,
which is picked up by the EE `LogContextBridge` and exported to OTEL
(the bridge's filter is `EnvFilter` only, not the targets filter that
drops `windmill:job_log` from stdout/file sinks).

Nativets delivers logs in-process via a channel, so it never goes
through that path and console.log output only reached the Windmill UI.
Emit the same `windmill:job_log` event per line from the nativets log
receiver.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 19:42:05 +00:00
Ruben Fiszel 749aff024f sqlx 2026-04-24 18:47:26 +00:00
Ruben Fiszel 489337d533 feat: cli diff/deploy no-op handling + promotion debouncing (#8936)
* feat: cli diff & deploy no-op handling + promotion debouncing

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

* chore: bump ee-repo-ref

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

* chore: update ee-repo-ref to ed842061576c3ac9b9eb89bb87f6db5b67904474

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

Previous ee-repo-ref: 1210d9f63de8eea4c3a210a10c60fe6382df477b

New ee-repo-ref: ed842061576c3ac9b9eb89bb87f6db5b67904474

Automated by sync-ee-ref workflow.

* test(git-sync): e2e tests for promotion-mode debounce keys

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-04-24 18:12:10 +00:00
Alexander Petric 95d4c6a94d feat(cli): non-interactive Slack connect/disconnect + sync round-trip fixes (#8935)
* feat(cli): non-interactive Slack connect/disconnect

Extract create_slack_workspace_artifacts / create_slack_instance_artifacts
from the browser OAuth callbacks and expose them via two new endpoints that
accept a pre-minted xoxb bot token:

- POST /w/{workspace}/workspaces/connect_slack (admin)
- POST /oauth/connect_slack_instance (super-admin)

Both produce bit-for-bit identical DB state to the UI browser flow.

Wire three CLI commands as thin wrappers:
- wmill workspace connect-slack
- wmill workspace disconnect-slack
- wmill instance connect-slack

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

* fix(cli): round-trip stability for workspace settings handlers

wmill sync push was destroying UI-configured error_handler/success_handler
state on every deploy. Two orthogonal bugs:

(a) pushWorkspaceSettings called editErrorHandler with `path: undefined`
    when the YAML lacked the handler block, which the backend treats as a
    clear — so syncing settings.yaml that didn't mention the handler wiped
    the DB row. Fix: skip the call entirely when absent from YAML.

(b) edit_error_handler omitted muted_on_cancel / muted_on_user_path when
    false, but the CLI always sends them, causing perpetual deepEqual
    drift and a spurious editErrorHandler call on every sync push. Fix:
    always persist both booleans.

migrateToGroupedFormat now preserves explicit `null` on
error_handler / success_handler as a "clear remote" signal distinct from
absence. Widen ErrorHandlerConfig | null / SuccessHandlerConfig | null to
make this explicit in the type.

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

* feat(cli): sync support for workspace-level Slack OAuth override

Add slack_oauth_client_id and slack_oauth_client_secret to the v2 tarball
export and to pushWorkspaceSettings, so the workspace-level OAuth override
is now fully managed as code through settings.yaml.

Semantics:
  - both defined and truthy → setWorkspaceSlackOauthConfig (upsert)
  - both defined but falsy (e.g. empty strings) and remote has a value
    → deleteWorkspaceSlackOauthConfig
  - either omitted → leave remote alone ("not managed by git")

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

* refactor(cli): normalize workspace settings sync to "omit = clear"

Earlier commits on this branch introduced an "omit = keep" rule for
error_handler / success_handler / slack_oauth_client_{id,secret} that
diverged from every other workspace setting (webhook, deploy_to, etc. all
treat YAML as canonical: absence = clear). Normalize:

- v2 tarball always emits these 4 fields (null when remote is NULL) so
  round-trip is bijective and settings.yaml is a complete snapshot.
- pushWorkspaceSettings drops the absent-from-YAML guards; YAML is
  canonical. Absence and explicit null both clear the remote — same rule
  as every other field.
- set_slack_oauth_config / delete_slack_oauth_config now fire
  handle_deployment_metadata so UI mutations reach git-sync-enabled
  workspaces' committed settings.yaml.

Policy for users: pull before push (same as every other setting). On first
post-upgrade pull, explicit `null` keys appear for any workspace whose
handlers / oauth override are unset — one-time YAML diff, no semantic
change.

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

* test(cli): add unit + integration coverage for Slack settings sync

Unit tests (settings_unit.test.ts): cover migrateToGroupedFormat preserving
explicit `null` on error_handler / success_handler, and passthrough of
slack_oauth_client_id / _secret (both populated and null values).

Integration tests (slack_settings_sync.test.ts, skipped on CI per the same
convention as datatable_settings_sync.test.ts): exercise the full backend
via withTestBackend to verify

  1. pull emits null for unset error_handler / success_handler /
     slack_oauth_client_id / _secret;
  2. round-trip with all-null handlers is idempotent;
  3. push of populated slack_oauth_config upserts;
  4. omitting the slack_oauth keys from YAML clears remote (universal
     "omit = clear" rule);
  5. explicit null error_handler in YAML clears remote;
  6. round-trip preserves a populated error_handler exactly, including the
     always-persisted muted_on_cancel / muted_on_user_path booleans.

Also feature-gates `use crate::oauth2_oss::workspace_connect_slack` and its
route registration behind `cfg(feature = "oauth2")`: the import caused a
build failure on subsets of the workspace without the oauth2 feature,
surfaced by the integration test harness.

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

* chore: bump ee-repo-ref to 59b6123

Pins windmill-ee-private to the tip of branch alp/slack_cli, which
contains the companion EE changes (helper extraction, non-interactive
Slack connect handlers, git-sync for Slack settings mutations).

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

* Update SQLx metadata

* chore: regenerate system prompts for new slack CLI commands

Captures the new workspace connect-slack, workspace disconnect-slack,
and instance connect-slack commands in the auto-generated files that
CI enforces via system_prompts/check-freshness.sh.

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

* chore: update ee-repo-ref to b4a5ca11e3b96ff03793c2bd396dbc1fe6ea1022

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

Previous ee-repo-ref: d7e44d0519327ec9077625130365e887826f324b

New ee-repo-ref: b4a5ca11e3b96ff03793c2bd396dbc1fe6ea1022

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.7 (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-04-24 17:14:08 +00:00
hugocasa a1a73309fd refactor: remove force_branch from git sync settings (#8934)
* [ee] refactor: remove force_branch from git sync settings

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

* chore: update ee-repo-ref to 680885a4e8c8de5185650cddeb56b926e722718f

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

Previous ee-repo-ref: 37fe2e1286a162119df885062e50461400631850

New ee-repo-ref: 680885a4e8c8de5185650cddeb56b926e722718f

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-04-24 16:19:41 +00:00
hugocasa 8a986500b9 feat: WM_TESTED_RUNNABLE env var + wildcards in test: annotation (#8926)
* feat: WM_TESTED_RUNNABLE env var + wildcards in test: annotation

Extends the CI test feature so a single test script can cover multiple
runnables and branch on which one triggered it.

- test: annotation now supports glob wildcards: `*` matches one path
  segment, `**` matches any depth. A new `ci_test_path_matches` helper
  in windmill-common compiles patterns to anchored regexes with a small
  quick_cache LRU.
- New migration adds a Postgres GENERATED `has_wildcard` column + partial
  index on ci_test_reference so exact-match lookups keep using the
  primary index and only wildcard rows are scanned for regex matching.
- ci_test trigger query and the UI `ci_test_results` / `ci_test_results_batch`
  endpoints split into exact + wildcard paths; the batch endpoint now
  issues one query per distinct kind instead of one per item.
- Worker injects `WM_TESTED_RUNNABLE={kind}/{path}` into CI test jobs,
  derived from the trigger metadata stored at push time.

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

* fix: scope CI test job lookup by trigger + populate WM_TESTED_RUNNABLE in resource interpolation

Scope the ci_test_results LATERAL lookup by v2_job.trigger so multi-target
tests (via wildcards or multiple exact annotations) report the correct job
per target. Also pass the tested runnable through transform_json_value in
resources.rs for consistency with schedule_path.

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

* chore: update ee-repo-ref to 489eb0d89702e5d1cc7c6e0f9ba9e0c8e5063741

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

Previous ee-repo-ref: e7534bcafcd8c27fcf870b2ea868e901b00b7960

New ee-repo-ref: 489eb0d89702e5d1cc7c6e0f9ba9e0c8e5063741

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-04-24 12:52:25 +00:00
Ruben Fiszel d60adac494 ref pointer 2026-04-24 04:57:22 +00:00
Ruben Fiszel d7d79dfbee sqlx 2026-04-24 04:39:43 +00:00
Ruben Fiszel 73fab0c264 fix(autoscaling): native worker stuck at max + wrong TimeAgo (#8930)
* fix(autoscaling): return applied_at with UTC timezone in events API

autoscaling_event.applied_at is a naive TIMESTAMP column. Serializing as
NaiveDateTime produces an ISO string with no timezone, which the browser
parses as local time — for users west of UTC this lands in the future and
TimeAgo's Math.max(0, …) clamps every event to "0s ago".

Cast the column with AT TIME ZONE 'UTC' and type the field as DateTime<Utc>
so the response includes a Z suffix.

Also pulls in the EE count-distinct fix for native worker autoscaling.

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

* chore: update ee-repo-ref to 4128203739a973330599dacfb054203cf9832f3a

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

Previous ee-repo-ref: 32636bc3e3996101554d5ef504785346929a593b

New ee-repo-ref: 4128203739a973330599dacfb054203cf9832f3a

Automated by sync-ee-ref workflow.

* chore: bump ee-repo-ref for applied_at UTC insert fix

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-04-24 04:35:12 +00:00
Ruben Fiszel 4cf53a44bb feat: add auto-login SSO provider instance setting (#8929)
* [ee] feat: add auto-login SSO provider instance setting

Adds an instance-level `auto_login_provider` setting that, when set to
an OAuth provider key (e.g. "okta") or "saml", causes the login page
to auto-redirect users to the configured SSO flow on mount.

Useful for orgs with a single SSO where the provider button grid adds
a pointless extra click.

- Backend: new global setting constant, read from DB in list_logins
  handler and returned as the `auto_login` field in the response
- Frontend: Login.svelte auto-redirects in loadLogins() when the
  configured provider is actually present in the response
- Escape hatch: `?no_sso=1` skips the auto-redirect and shows the
  normal login form (admin fallback when SSO is broken)
- No redirect loop: if the `error` prop is set (SSO callback failed),
  the redirect is skipped
- Admin UI: new text field under Auth/OAuth/SAML in instance settings

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

* fix: skip auto-redirect on /user/login page

Auto-redirect should only fire on embeds where the user did not
explicitly navigate to a login screen (public app popup, approval
pages). Visiting /user/login is an explicit sign-in action — often by
an admin who needs password fallback — so we must never hijack it.

Gate the logic on a new `autoRedirect` prop (default true). The main
login page passes `autoRedirect={false}`.

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

* chore: update ee-repo-ref to b7157d55fb9f8d8f7aeb7b1fb69bc935af895a2f

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

Previous ee-repo-ref: e32a48499d206a24e0c12817b465775321b0ee41

New ee-repo-ref: b7157d55fb9f8d8f7aeb7b1fb69bc935af895a2f

Automated by sync-ee-ref workflow.

* fix: handle popup-blocked auto-redirect in popup mode

When Login is embedded with popup=true (public app), auto-redirect
funnels through window.open() without a user gesture — browsers block
it by default, leaving the user stuck on "Signing you in…".

Detect window.open returning null, clean up listeners, reset
autoRedirecting so the provider button grid re-renders, and surface a
toast pointing users at the manual button. The grid click retains its
user gesture and passes the popup blocker.

Also extracts a redirectSaml() helper so the SAML auto-redirect path
and the SSO button click share the same logic.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-04-24 04:20:01 +00:00
Ruben Fiszel 161ec8d722 chore(main): release 1.690.0 (#8921)
* chore(main): release 1.690.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.690.0
2026-04-23 14:33:53 -04:00
hugocasa f429cb5e48 feat: add OTEL_HOST_NAME env override for host.name attribute (#8923)
* feat: add OTEL_HOST_NAME env override for host.name attribute

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

* chore: update ee-repo-ref to f6bc5647cc41ce348111f781b4a0db2153a28f8a

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

Previous ee-repo-ref: c92441ae0d6d8e89b48677db8cb6b78e5bdae2db

New ee-repo-ref: f6bc5647cc41ce348111f781b4a0db2153a28f8a

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.7 (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-04-23 18:26:31 +00:00
Ruben Fiszel 664d0f838d fix: ensure schema is inferred on script/flow module load (#8927)
* fix: ensure schema is inferred on script/flow module load

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

* fix: memoize all WASM parser init promises

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-23 16:46:24 +00:00
hugocasa 1722a7a2af fix(cli): use wmill.yaml key consistently for workspace-specific items (#8900)
* fix(cli): use wmill.yaml key (not branch name) for workspace-specific filenames

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

* fix(cli): resolve --workspace as config key even with --base-url

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

* test(integration): assert workspace config key drives filename suffix in git-sync

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

* Revert "test(integration): assert workspace config key drives filename suffix in git-sync"

This reverts commit 528993fe29.

* fix(cli): filter reserved keys and preserve validation-skip on non-config --workspace

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 16:31:41 +00:00
hugocasa d6c642b170 feat: add Azure Event Grid triggers (#8888)
* feat: add Azure Event Grid triggers (EE)

Introduces a new enterprise trigger kind `azure` that supports three
modes via a single unified trigger type:
- basic_push: Azure Event Grid basic — custom topics, system topics
  (Storage, Resource Manager, Key Vault, etc.), domains (push only)
- namespace_push: Event Grid Namespace topics (CloudEvents over HTTP push)
- namespace_pull: Event Grid Namespace topics (HTTP pull with lock-token
  ack/reject for dead-lettering)

Auth uses a Service Principal resource (tenant_id, client_id,
client_secret, subscription_id). Subscriptions are created in
CloudEvents 1.0 schema so the push webhook handler and the pull listener
share one payload parser.

Backend
- New crate `windmill-trigger-azure` (OSS stubs + EE impl symlinked from
  windmill-ee-private)
- Migration `azure_trigger` table with CHECK constraints enforcing
  mode/columns coherence
- `TriggerKind::Azure`, `JobTriggerKind::Azure`,
  `DeployedObject::AzureTrigger` variants
- Push route `/api/azure/w/{workspace}/*path` handles classic
  Event Grid SubscriptionValidation handshake and CloudEvents 1.0
  abuse-protection OPTIONS handshake
- Optional inbound JWT validation (audience check only for v1)
- Feature flag `azure_trigger` propagated through windmill-api,
  windmill-store (resource helper), and added to ee_core

Frontend
- `triggers/azure/` editor with mode toggle (basic/namespace-push/
  namespace-pull) and per-mode config (topic ARM id / namespace +
  topic name / subscription / filters / push auth / pull options)
- Registered in icon map, display names, save functions, badge,
  wrapper, editor, add-trigger menu

OpenAPI
- `AzureTrigger`, `AzureTriggerData`, `AzureMode`,
  `AzureSubscriptionMode`, `AzureDeliveryConfig`, `TestAzureConnection`
  schemas; `/azure_triggers/*` endpoints; client regenerated

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

* chore: update ee-repo-ref to eaa7c3a9cb37a9ccc93f10a2535d929365acd2d8

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

Previous ee-repo-ref: 9689014e8c12c36c1059fd8fa5758d550b8b8bc9

New ee-repo-ref: eaa7c3a9cb37a9ccc93f10a2535d929365acd2d8

Automated by sync-ee-ref workflow.

* feat(azure-trigger): secret-auth push, ARM discovery, capture isolation, CLI + parity

Frontend:
- Split mode selector into Namespace/Basic + Pull/Push
- ARM resource dropdowns (namespaces, Basic topics, namespace topics)
  populated from the service principal; cascade with stale-selection
  reset on SP / edition change
- Remove stale authenticate toggle + audience input (server-managed
  push_auth_config has replaced them)
- Azure listing page: "Create from template" button; "Also delete Azure
  subscription" toggle in the delete modal; simplified trigger label
  falling back to path
- AzureCapture.svelte: "Test subscription name" with -wm-capture suffix
- CompareWorkspaces.svelte: wire Azure for fork/compare
- Drop Trigger-deployed/event-loss warning (capture subscription is
  isolated with -wm-capture)

Backend:
- Shared-secret push auth (see EE crate for detail)
- JSONB push_auth_config column (renamed from delivery_config), #[serde(skip)]
  so clients/CLI/exports never see it
- Drop redundant enabled column; mode supersedes
- Azure capture infra: AzureTriggerConfig + set_azure_trigger_config +
  azure_payload route + TriggerKind::Azure arm; PT15M queue TTL on
  capture subscriptions so they bound storage after tab close
- Granular ACLs, users offboarding, trash, git-sync deployed-object:
  all include azure_trigger

CLI:
- Add azure to TRIGGER_TYPES, pushObj dispatch, getTypeStrFromPath,
  trigger commands (get/update/create/list/template), sync delete
  switch + regex; e2e test for `trigger new --kind azure`
- system_prompts: SCHEMA_MAPPINGS + schema_names include AzureTrigger;
  auto-generated/* regenerated

Skill:
- .claude/skills/adding-a-trigger/ checklist covering every file that
  needs editing when wiring a new trigger type (learned from this PR)

ee-repo-ref bumped to b0e490cbf3724b7b64c6a5b010e3bdf24acd873c.

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

* fix(azure-trigger): ci — ShareModal Kind + regenerated system_prompts

- frontend/src/lib/components/ShareModal.svelte: add 'azure_trigger'
  to the Kind type so the listing page's "Permissions" action compiles
  (ts2345 — caught by npm_check on CI, missed by fast-check locally).
- system_prompts/auto-generated/: regenerate to drop the stale
  delivery_config / AzureDeliveryConfig fields from the Azure schema
  (check-freshness on CI).

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

* refactor(azure-trigger): use workspace constant_time_eq crate

Drop hand-rolled constant-time compare in favour of the workspace
constant_time_eq crate (same one used by http_trigger_auth).

ee-repo-ref bumped to 9659382d47286e7f7f66d01b6f5dd8d4ed34848b.

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

* fix(azure-trigger): pass placeholder + disabled via inputProps

`TextInput`'s `placeholder` and `disabled` go through its `inputProps`
prop — CI's `npm run check` caught the stale top-level passing that
`npm run check:fast` missed. Align with the DefaultEmailConfigSection
pattern.

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

* fix(azure-trigger): correct LATEST_GIT_SYNC_SCRIPT_PATH version to 28213

The hub deploy of the azure-aware sync-script is version 28213, not
28214. Backend was pinning a non-existent hub script, which broke the
git_sync_e2e suite (every deploy's sync step 404'd).

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

* fix(azure-trigger): add azure_triggers to token scope selector + skill

- windmill-api/src/token.rs: `build_trigger_scope_domains` was missing
  `("azure_triggers", "Azure Event Grid")`, so the CreateToken UI's scope
  selector didn't surface azure_triggers:read/write. Backend already had
  `ScopeDomain::AzureTriggers` wired (scopes.rs), this just exposes it.
- .claude/skills/adding-a-trigger/SKILL.md: capture both scope-related
  files under the hardcoded-arrays section so future triggers don't miss
  the UI surface.

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

* docs(adding-a-trigger-skill): clarify token.rs scope effect

Not a regression — nothing was working before. Skipping TRIGGER_DOMAINS
just means the scope works via API/CLI but has no UI checkbox.

* docs(adding-a-trigger-skill): trim token.rs bullet

* fix(azure-trigger): regen openapi-deref + swap textarea for TextInput

- Run build_openapi.sh to regenerate openapi-deref.{yaml,json} with the
  12 azure_triggers paths + schemas. These files are served by the
  runtime (include_str! in windmill-api/src/lib.rs) to external SDK
  consumers; without this regen the new endpoints wouldn't be advertised.
- Replace the raw <textarea> for event type filters with the
  design-system TextInput in textarea mode (frontend/CLAUDE.md bans raw
  HTML elements).

Addresses cubic + claude PR review items.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-04-23 16:30:18 +00:00
centdix 7fa924e67e fix: correct flow conversation pagination (#8919)
* fix: remove conversation after_id filter

* fix: implement message after_id cursor

* fix: use persisted cursor for chat polling

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

* refactor: simplify message cursor ordering

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

* refactor: use cte for message cursor

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

* Update SQLx metadata

* fix: use monotonic flow message cursor

* Update SQLx metadata

* fix: tighten flow message cursor pagination

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

* Update SQLx metadata

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-04-23 16:29:29 +00:00
centdix 132d8a61f9 fix: slim app ai chat context (#8922)
* fix: slim app ai chat context

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

* fix: remove stale app chat selection UI

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

* refactor: trim app chat selected context

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

* test: trim app chat context coverage

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

* test: remove app tool assertion

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-23 16:21:58 +00:00
centdix 07951e81ae fix: include endpoint descriptions in mcp tools (#8925)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-23 16:21:15 +00:00
Ruben Fiszel 2ed26c2254 rust client nit 2026-04-23 16:16:10 +00:00