Commit Graph

43 Commits

Author SHA1 Message Date
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 7155a0bb96 feat: Data Pipelines alpha (#9193)
* feat: add workspace asset graph view

Workspace-wide canvas of assets and their producer/consumer scripts,
reachable from the assets page. Left-to-right layered layout via
d3-dag sugiyama, rendered with @xyflow/svelte (same stack as the
flow editor). GET /w/:ws/assets/graph returns deduped nodes + edges.

Follow-ups: filters (kind/folder/search), node detail drawer, inline
script edit from a clicked node.

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

* all

* all

* all

* update

* all

* all

* all

* feat(pipeline): output-kind picker and per-(lang, output) templates

Add a third stage to PipelineInsertMenu that asks what kind of asset the
new script will produce (datatable / ducklake / s3 parquet / s3 object /
none). The picked kind drives a real wmill SDK skeleton — typed
datatable inserts, ducklake CREATE+INSERT, s3 parquet COPY, etc. — with
the upstream asset auto-wired as the input source when added from an
asset node. Reorder languages to bun → duckdb → python → sql so
data-shaped languages surface first.

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

* all

* chore(main): release 1.693.4 (#8994)

* chore(main): release 1.693.4

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* feat: ansible delegate_to_git_repo install_requirements, dynamic fields, --limit (#8997)

* feat: ansible delegate_to_git_repo install_requirements, dynamic fields, --limit

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

* fix: include .yaml variants in collections/roles requirements lookup

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

---------

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

* fix(cli): only preserve case for raw-app runnableIds, not app/flow summaries (#9000)

* fix(cli): only preserve case for raw-app runnableIds, not app/flow summaries

PR #8940 stopped lowercasing in sanitizeForFilesystem to fix #8939, where
a raw-app runnableId like CamelCaseTSRunnable produced a CamelCase YAML
metadata file but a lowercased code file, making them desync and
register as duplicate runnables on push.

That fix overshot. sanitizeForFilesystem is also reached by
newPathAssigner, which serves normal apps and flows where the input is
the script's human summary ("Get Users Data") rather than an identifier.
There the on-disk filename is the only artifact — there's no companion
YAML to keep in sync — so lowercasing was the right behavior. Removing
it changed both the on-disk filename and the !inline reference in
app.yaml / flow.yaml from get_users_data.inline_script.ts to
Get_Users_Data.inline_script.ts on the next pull, surfacing as
unwanted case churn for users updating to 1.693.x.

Add a preserveCase option to sanitizeForFilesystem (default false →
lowercase). newRawAppPathAssigner opts in; newPathAssigner stays on
the default. Update unit tests accordingly and add an end-to-end
raw-app round-trip in raw_app_sync.test.ts that pushes a CamelCase
backend runnable, pulls it back, and asserts both YAML and code file
preserve case with no lowercase orphan.

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

* test(cli): use readdir for exact-case orphan check on Windows

The CamelCase round-trip test used fileExists("camelcasetsrunnable.ts")
to assert no lowercase orphan was produced, which false-positives on
Windows since the filesystem is case-insensitive and resolves the
lookup to the existing CamelCaseTSRunnable.ts. Switch to readdir +
toContain so the exact on-disk casing is compared identically on Linux
and Windows.

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

---------

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

* feat(cli): wmill-lock.yaml auto-fill + --rehash-only + path-prefix dedup (#8978)

* fix(cli): canonical lockfile hashes + lock upgrade migration to v3

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

* fix(cli): use __app_hash subpath in rehash missing-entry check

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

* fix(cli): run sync pull lockfile auto-fill regardless of changes

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

* chore: regenerate system prompts for new lock and rehash-only commands

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

* fix(cli): address review feedback on lock upgrade

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

* fix(cli): drop v3 marker; always run fallback; fail-fast on unknown lockfile version

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

* fix(cli): drop yaml-round-trip legacy hash variant; recover via --rehash-only

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

* fix(cli): include legacy hash in script push staleness warning check

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

* revert(cli): drop canonical hash formula; keep raw-bytes hashing

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

* perf(cli): reuse change-tracker map for sync pull lockfile auto-fill

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

* fix(cli): address review feedback on rehash-only

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

* test(cli): pin lockfile hash + yaml format and cover regression cases

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

* test(cli): byte-stable snapshot tests for flow.yaml format

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

* test(cli): add app and script-metadata yaml snapshot fixtures

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

* fix(cli): address claude review on rehash-only

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

* refactor(cli): factorize script-path to remote-path derivation

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

* fix(cli): address claude + cubic review (dry-run mutation, rehash short-circuit)

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

* refactor(cli): make rehash a subcommand and factorize fs walks

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

* fix(cli): normalize line endings in yaml snapshot tests for windows ci

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

* fix(cli): address review feedback on rehash + auto-fill

- Flat-layout scripts now clearGlobalLock before rehash write so legacy
  ./-prefixed duplicates get cleaned up (matches flow/app behavior).
- Add MalformedLockfileError; sync pull auto-fill re-throws it alongside
  UnknownLockVersionError instead of silently warning + continuing.
- Document the legacy step-removal false-negative in
  isFlowDirectlyStale / isAppDirectlyStale and the categorizeLocalFiles
  ignore-filter invariant.

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

---------

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

* fix: use otel.status_message for OTLP Status.message on failed jobs (#8995)

tracing-opentelemetry only recognizes otel.status_code and
otel.status_message as fields that map to the OTLP Status proto.
The previously-used otel.status_description fell through to the
generic attribute recorder, leaving Status.message unset and
preventing OTLP consumers from filtering spans on error status.

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

* fix: route email trigger path through standard info channel (#8996)

* docs(skill): document email triggers and S3 attachments

Add an "Email triggers" section to the triggers skill covering the
local-part config, the parsed_email/raw_email/email_extra_args payload,
the URL-style extras convention, where to find trigger_path (only with
a preprocessor, at event.trigger_path), and — most importantly — that
binary attachments are uploaded to the workspace S3 bucket and surface
as `{ s3: "windmill_emails/<job_id>/attachments/<filename>" }`. Scripts
must use wmill.loadS3File / wmill.load_s3_file to read them.

Also pulls EmailTrigger into the schema mappings so a real
`email_trigger.schema.yaml` is generated, and adds Email/Azure to the
trigger kinds list in the CLI agent guidance.

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

* chore: update ee-repo-ref for email trigger path fix

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

* chore: update ee-repo-ref to 26184ab7a4aadfc529dcedf038aa08d36c7ad381

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

Previous ee-repo-ref: 318a46897a605dc9be3817901f35ba5a99a0a525

New ee-repo-ref: 26184ab7a4aadfc529dcedf038aa08d36c7ad381

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>

* update git sync version to 1.693.5

* fix: pair PG arg type with actual Rust binding to keep query_typed_raw safe (#8999)

* fix: pair PG arg type with actual Rust binding to keep query_typed_raw safe

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

* fix(pg): wrap encoder errors with arg context, add fallback test

Followups on #8999 review:

- Wrap rust-postgres "error serializing parameter N" failures with the arg
  name, JSON value kind, and asserted Postgres type plus a hint about an
  explicit cast — so users see actionable context instead of an opaque
  WrongType.
- Drift-prevention meta-test: assert otyp_to_pg_type and convert_val agree
  on the Type for every recognised arg_t when the JSON value matches its
  natural Rust kind. Catches future drift if either side changes.
- Integration test for the prepare + query_raw fallback path: confirms
  unrecognised arg_t (custom enum) is routed through prepare and the
  server-resolved type appears in the failure surface — flips into a
  test failure if a regression accidentally routes unrecognised types
  through query_typed_raw.

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

* fix(pg): add otyp_inferred flag + regex-based placeholder renumbering

Two follow-ups from the review of #8999:

1. **Issue #1 (Number/Bool + explicit text decl in WHERE)**

   Add `Arg::otyp_inferred: bool` to the parser. The PG SQL parser sets
   it `true` only at the "no info → fall back to text" site (bare `$N`,
   no inline cast, no `-- $N (TYPE)` decl). All other arg sources keep
   it `false`.

   In `convert_val` this flag distinguishes:
   - explicit text-like target (`-- $1 (text)` or `$1::text`) — coerce
     `Bool`/`Number` → `Box<String>` so `WHERE text_col = $1` works
     (`text = text` operator). Pre-#8988 behaviour, restored.
   - parser-default text (bare `$N`) — bind the value's natural Rust
     type so the regression case (`Value::Bool` against a real `bool`
     column via `CAST AS bool`) keeps working.

   `Arg` is in `windmill-parser`; the new field has `#[serde(default)]`
   so persisted signatures stay backward-compatible.

2. **Issue #4 ($5/$50 substring rewrite collision)**

   Replace the per-index `String::replace` chain (which turned `$50`
   into `$10` when oidx=5 was processed first) with a single regex
   pass. `\d+` is greedy, so `$5` and `$50` match as distinct units;
   indices outside the mapping are left intact.

3. Tests:
   - parser: `test_parse_pgsql_otyp_inferred_flag` covers bare/inline-
     cast/decl/mixed shapes.
   - executor unit: `convert_val_bool_against_every_arg_t` and
     `convert_val_*_number_*` split each text-like target into explicit
     vs inferred expectations.
   - executor unit: `renumber_sparse_placeholders_no_collision`.
   - integration: `test_postgresql_arg_type_combinations` adds 4 cases
     covering decl(text)+Number/Bool in WHERE, bare $1+Bool, and
     sparse positional args ($5/$50).

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

* fix(pg+sdk): enum support, extended String arms, position-aware $N rewrite, SDK quality

Backend:

1. **`AnyTextValue` ToSql/FromSql wrapper**: vanilla `tokio_postgres`'s
   `ToSql for String` / `FromSql for String` reject `Kind::Enum` and
   `Kind::Domain` even though the wire format is plain UTF-8. The wrapper
   accepts those kinds in both directions. End result: explicit
   `$1::my_enum` / `CAST($1 AS my_enum)` casts now round-trip without the
   ugly `CAST($1::text AS my_enum)` workaround, AND `SELECT enum_col`
   results come back as JSON strings instead of erroring at the FromSql
   layer.

2. **#10 — Value::String → numeric/real/double/oid/bool**. Without these
   arms, a string-encoded value (`"3.14"`, `"true"`) for a non-text /
   non-temporal arg_t fell through to `Box<String> + TEXT`, which then
   failed at the server (no implicit cast text→numeric in expression
   context). Now strings are parsed into the matching native type with
   clear error messages on parse failure.

3. **Position-aware `$N` rewrite**: replaces the regex-based renumbering
   (which fixed the `$5/$50` substring collision but still walked through
   string literals and comments, mangling `'price: $5'` etc.) with a
   walk over `parse_pg_statement_arg_positions` — the same
   string/comment/dollar-quote-aware tokenizer used for index discovery.
   Adds `parse_pg_statement_arg_positions` to the parser's public API.

SDK:

4. **BigInt support**: `JSON.stringify(BigInt)` throws. The SDK now
   stringifies bigints before serialisation; the executor accepts
   numeric strings into BIGINT arg slots via the existing
   `Value::String → INT8` parsing arm. SDK-side `inferSqlType` is split
   so `BigInt` always resolves to `BIGINT` (was reaching
   `Number.isInteger(BigInt)` which returns false → wrong default).

5. **Homogeneous array auto-tag**: `${[1,2,3]}` against an `int[]` column
   now emits `$1::BIGINT[]` instead of `$1::JSON`. Detection covers
   primitive types only (number / bigint / string / boolean); mixed or
   nested arrays still fall back to JSON. Mixed int/float widens to
   `DOUBLE PRECISION[]`.

6. **`.query()` positional bug**: previously the `.query()` method
   abused the template-tag builder, which appended `$N::TYPE` after the
   user's literal SQL string instead of binding by position
   (`SELECT $1, $2` became `SELECT $1, $2$1::BIGINT`). Now `.query()`
   builds the executor-shaped content directly: a `-- $N argN (TYPE)`
   declaration block followed by the user's SQL verbatim.

Tests:

- Parser: `test_parse_pg_statement_arg_positions_skips_strings_and_comments`
  asserts string literals, comments, and dollar-quoted blocks don't
  produce positions (so renumbering doesn't mangle them).
- Executor unit: `renumber_sparse_placeholders_no_collision_no_string_mangling`
  uses the new position-aware path and includes string-literal + comment
  + `$$…$$` cases. Existing convert_val tests grow to cover new
  String→numeric/real/double/oid/bool arms.
- Integration: `test_postgresql_arg_type_combinations` adds 13 cases
  (enum round-trip both directions, string→numeric/real/double/bool/oid,
  string-literal `$N` non-mangling). The prepare-fallback test now
  asserts SUCCESS (not failure) for enum encoding via AnyTextValue.
- SDK: new `typescript-client/tests/sqlUtils.test.ts` (42 tests)
  exhaustively covering inferSqlType primitives + arrays,
  parseTypeAnnotation, datatable() template tag (with all the new
  shapes — BigInt, homogeneous arrays, RawSql, schema preamble),
  datatable().query() positional, and ducklake() shape.

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

* fix(pg): replace DISCARD ALL with curated reset (preserves typeinfo cache)

Found while exhaustively probing custom-type DX: every cached-connection
reuse was running `DISCARD ALL`, whose included `DEALLOCATE ALL`
deallocates *all* prepared statements server-side — including the typeinfo
statements that tokio_postgres caches per-Client to resolve custom enum /
domain Oids. tokio_postgres still held `Statement` objects whose names
the server had forgotten, so the next custom-type query failed with
intermittent "prepared statement \"sN\" does not exist" errors. The
failure was easy to reproduce: any sequence that forced typeinfo lookup
for two different custom-type kinds on the same cached connection (e.g.
enum followed by domain) would hit it.

Replace `DISCARD ALL` with a curated reset that explicitly targets the
state we actually care about, *without* touching prepared statements:

  RESET ALL                     — GUC parameters (search_path, application
                                  _name, statement_timeout, …)
  RESET SESSION AUTHORIZATION   — undoes both `SET SESSION AUTHORIZATION`
                                  and `SET ROLE` (RESET ALL does NOT —
                                  these aren't GUC parameters, so without
                                  this an elevated role from a previous
                                  job would silently leak)
  UNLISTEN *                    — drops LISTEN registrations
  CLOSE ALL                     — closes open cursors

Trade-off: temp tables, advisory locks (session-scoped), and user-created
PREPARE statements may persist across cached-connection reuse — rare in
datatable / PG-script workloads. tokio_postgres's typeinfo cache survives
intact, so custom enum / domain queries are fast on subsequent reuse.

Tests:
- `test_postgresql_custom_types_on_cached_connection` — runs 10×
  alternating enum + domain queries on a cached connection. Pre-fix this
  failed with `prepared statement "sN" does not exist` after the first
  reuse; post-fix passes.
- `test_postgresql_set_role_does_not_leak_across_cached_connection` —
  switches `SET ROLE` and `SET SESSION AUTHORIZATION` to a non-postgres
  role, then runs a follow-up job and asserts current_user/session_user
  are restored. Specifically catches the case where someone might switch
  back to `RESET ALL` alone (which doesn't cover SET ROLE / SESSION
  AUTHORIZATION) and silently introduce a permission-leak vector.
- All existing session-isolation tests
  (`test_postgresql_cached_connection_resets_session`,
   `test_postgresql_single_worker_session_isolation`,
   `test_postgresql_100_jobs_cached`) continue to pass.

Found via end-to-end probing of datatable / PG-script DX, not previously
covered: the existing isolation tests only did `SET ROLE postgres`, the
connecting user, so the leak was invisible.

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

* fix(pg): address PR #8999 review (cubic + claude)

cubic (P1, real bug):
- `convert_vec_val` for `timetz` array asserted `Type::TIMETZ_ARRAY`, but
  chrono `NaiveTime` only encodes for TIME (same caveat as the scalar
  arm). Switch to `Type::TIME_ARRAY`; rely on PG's implicit `time→timetz`
  assignment cast at the column site. Add an explicit unit test.

claude (#1, silent failure → explicit error):
- `Bool` + explicit `(char)` / `(character)` decl previously silently
  bound BOOL, hoping the server would cast at the use site — but PG has
  no implicit `bool→char` and the resulting error
  ("operator does not exist: bool = char") was opaque. Now error at
  bind time with an actionable hint to use `bool` decl or pass the
  value as a "t"/"f" string.

claude (#2, asymmetry doc):
- Object/Array still coerce to text on `matches!(typ, Typ::Str(_))`
  (covers both explicit AND inferred-default text), unlike Bool/Number
  which key on `explicit_text_target`. The asymmetry is intentional
  (no implicit `jsonb → text` cast in expression context vs PG having
  implicit `bool/int → text` casts) — added a body comment so future
  maintainers don't try to "align" them.

claude (#3, perf):
- `parse_pg_statement_arg_indices` and `parse_pg_statement_arg_positions`
  walked the SQL tokenizer twice. Fold into a single pass that derives
  the index set from the position list.

claude (#4, fmt drift):
- `cargo fmt` over the parser crates I touched with perl scripts in the
  earlier commit (windmill-parser-{sql,bash,ts,go,php,java,csharp,nu,py,
  rust,graphql,yaml,r}). Net cosmetic.

claude (#5, parseTypeAnnotation):
- One-line caveat in the SDK's `parseTypeAnnotation` that the returned
  string is presence-only (e.g. `${x}::DOUBLE PRECISION` returns
  `"DOUBLE"`, `CAST(${x} AS int)` returns `"int)"` — neither matches a
  real PG type, but the only consumer just checks `!== undefined`).

While here — discovered + fixed independently while exhaustively probing
DX:

- **Replace `DISCARD ALL` with curated reset** (`RESET ALL; RESET
  SESSION AUTHORIZATION; UNLISTEN *; CLOSE ALL;`). DISCARD's
  `DEALLOCATE ALL` killed tokio_postgres' typeinfo cache, producing
  intermittent `prepared statement "sN" does not exist` errors on
  custom-type queries after cached-conn reuse. New regression tests:
  `test_postgresql_custom_types_on_cached_connection` and
  `test_postgresql_set_role_does_not_leak_across_cached_connection`
  (the latter catches the case where someone might switch back to
  `RESET ALL` alone and silently introduce a permission-leak vector —
  RESET ALL doesn't cover SET ROLE / SET SESSION AUTHORIZATION).

- **ISO-8601 timestamp results** (`pg_cell_to_json_value`). Pre-fix
  `TIMESTAMP` was rendered with a space separator ("2024-01-15 10:30:00")
  and `TIMESTAMPTZ` with " UTC" suffix ("2024-01-15 10:30:00 UTC") —
  neither parseable by `date-fns parseISO`, JavaScript `new Date()` is
  lenient enough to handle them but several frontend `App*Input.svelte`
  components use parseISO and fail silently. Switched to ISO-8601 with
  `T` separator and `+00:00` offset; arg-parsing path still accepts the
  legacy " UTC" suffix for back-compat.

Test coverage:
- 17/17 unit (`pg_executor::tests`)
- 9/9 integration (`backend/tests/worker.rs`, `test_postgresql_*`)
- 27/27 parser (`windmill-parser-sql`)
- 42/42 SDK (`typescript-client/tests/sqlUtils.test.ts`)

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

* fix(pg): bounded one-shot warning on numeric precision loss + ISO-8601 + NaN handling

Found while probing PG-script DX with millions of numeric cells:

1. **Numeric precision-loss warning**: `numeric` results are still serialised
   as JSON Number (back-compat — switching to JSON String would silently
   break user code doing arithmetic on results), but we now detect
   `Decimal -> f64 -> Decimal` round-trip failure and emit a single
   job-log warning recommending a `::text` cast in the SQL. Bounded by
   `NUMERIC_PRECISION_CHECK_BUDGET = 256` cells per query (one atomic
   load + one fetch_sub on the hot path; first lossy value
   short-circuits to a single load thereafter). Worst-case overhead on
   a 1M-cell numeric-heavy query: ~25µs of checks + 5ns × N atomic
   loads (vs. ~100ms unbounded).

2. **ISO-8601 timestamps**: `pg_cell_to_json_value` previously returned
   `"2024-01-15 10:30:00"` (TIMESTAMP) and `"2024-01-15 10:30:00 UTC"`
   (TIMESTAMPTZ) — neither parseable by date-fns `parseISO`, which is
   what the apps `App*Input.svelte` components use, so timestamp values
   silently failed to round-trip into date pickers. Switch to ISO-8601
   (`T` separator + `+00:00` offset) on the result side; arg-parser
   continues to accept the legacy `" UTC"`-suffixed format for
   back-compat.

3. **Float NaN / Infinity results**: `Number::from_f64` returns None for
   NaN / ±Inf, which `pg_cell_to_json_value` was raising as
   "invalid json-float" — failing the *entire* query if any cell held
   one of these special values. Now serialise them as JSON strings
   ("NaN", "Infinity", "-Infinity") and let the rest of the row come
   through. Arg-side: `s.parse::<f64>()` already accepts the same
   strings.

Tests:
- `decimal_fits_f64_losslessly_predicate` — covers fits / doesn't-fit
  cases for the precision-loss predicate.
- `precision_check_budget_caps_per_query_overhead` — locks in the
  budget cap and the loss-flag short-circuit.
- All 9 PG integration tests + 17 unit tests pass.

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

* fix(pg): add pg_advisory_unlock_all to reset; warn on missing args; honor decl defaults

While probing PG-script DX further found three more frictions:

1. **Advisory lock leak** (cubic P2): switching from `DISCARD ALL` to
   `RESET ALL; RESET SESSION AUTHORIZATION; UNLISTEN *; CLOSE ALL;`
   meant session-scoped advisory locks (`pg_advisory_lock`) leaked
   across cached-connection reuse. Add `SELECT pg_advisory_unlock_all()`
   to the chain — `DISCARD ALL` covered this implicitly via
   `DISCARD PLANS / DEALLOCATE / pg_advisory_unlock_all` and we lost it
   in the switch.

2. **Missing-arg silent NULL**: an arg declared in the SQL (e.g.
   `-- $1 amount (numeric)`) but not provided in the args object was
   bound as NULL with no error / warning. Misspelling the key in the
   args object silently produced a row of NULLs — a notorious DX
   debugging trap. Now: collect the names of declared-but-missing
   args during dispatch and emit a single one-shot warning to the job
   logs at end-of-query naming each one. Bound NULL is preserved for
   back-compat.

3. **Declaration defaults ignored**: `-- $1 a (int) = 5` carries
   `arg.default = Some(Number(5))`, but the dispatch fell straight to
   NULL when the arg was missing. Now: respect the default —
   user-supplied value > declaration default > NULL. Also fixes the
   warning logic above (only warn for args that *don't* have a default).

Tests: existing 19 unit + 9 integration pass.

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

* fix(pg): multi-word PG types with [] suffix lost the array-ness; array arms accept stringified values

Two more frictions found while probing SDK end-to-end against a real
datatable resource:

1. **Multi-word array types lose the [] suffix in the parser**.
   `transform_types_with_spaces` recognises aliases for "double
   precision", "character varying", "timestamp with time zone", etc.
   but its return type was `&'a str` — only the bare alias, never with
   a trailing `[]`. The `RE_CODE_PGSQL` regex's `\w+` captures stop at
   the first space, so the regex's own `(?:\[\])?` array-suffix branch
   sees only `"double"` (not `"double precision[]"`); the `[]` was
   silently lost. Result: `$1::double precision[]` (which the SDK now
   emits for homogeneous float arrays via the new auto-tag) routed
   through `Value::Array → Type::JSONB` and the server failed with
   "cannot cast type jsonb to double precision[]".

   Fix: switch `transform_types_with_spaces` to return `Cow<'a, str>`
   and re-check the trailing bytes after a multi-word match. If they
   start with `[]`, return `format!("{alias}[]")` — Owned. Single-word
   types and the no-match path keep returning Borrowed slices, so no
   allocation in the hot path.

2. **Array arms in `convert_vec_val` rejected stringified values for
   numeric / int* / bool / oid / real / double**. The scalar `convert_val`
   already parses strings into the matching native type for these arg_ts,
   but the array variant only accepted JSON-native counterparts. Sending
   `["1.5", "2.5", "3.5"]` against `$1::numeric[]` (e.g. via `unnest` for
   bulk loading, or `JSON.stringify(BigInt[])` round-trip) failed with
   "Mixed types in array". Now the array arms mirror the scalar ones —
   `as_<native>().or_else(|| as_str().and_then(parse))` — so both shapes
   round-trip cleanly.

Tests: 19 unit + 9 integration pass; existing parser tests cover the
multi-word array forms (the regex-cap behaviour didn't break for
single-word types, and Cow plumbing is transparent to all callers).

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

* fix(parsers): add otyp_inferred field to Arg literals in tests + 3 missed src files

CI failures: the perl-driven sweep that added `otyp_inferred: false` to
every `Arg { ... }` literal when I introduced the field in the parser
schema covered `src/lib.rs` files but missed:

  - parsers/windmill-parser-bash/src/lib.rs       (mass-edited but a
    later format pass un-applied a few sites)
  - parsers/windmill-parser-go/src/lib.rs         (same)
  - parsers/windmill-parser-graphql/src/lib.rs    (same)
  - parsers/windmill-parser-nu/tests/tests.rs     (test file — not
    swept the first time)
  - parsers/windmill-parser-ts/tests/tests.rs     (test file — same)

Also tightened the regex to handle `oidx: None` without the trailing
comma (some test files had the field as the last initialiser line).

`cargo build --features <CI feature combo> --workspace --all-targets`
is clean.

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

* fix(sdk): Date → TIMESTAMPTZ; NaN / ±Infinity → string

Two more frictions found while running the actual SDK end-to-end against
a live datatable resource:

1. **JS `Date`** fell into the typeof "object" branch and was tagged
   `::JSON`. It worked accidentally for `${date}::timestamptz` via PG's
   `json → text → timestamptz` implicit cast chain, but `${date}` against
   a `timestamptz` column without a user-supplied cast bound the value
   as a JSON string and the comparison `timestamptz = json` failed. Now:
   `inferSqlType` recognises `Date` and tags `::TIMESTAMPTZ`;
   `serializeArgValue` emits `Date.toISOString()` so the executor's
   `Value::String → TIMESTAMPTZ` arm parses it cleanly.

2. **JS `NaN` / `±Infinity`** silently became NULL. `JSON.stringify(NaN)`
   returns `"null"` per the JS spec, so the value reached the executor as
   JSON null — the SDK's `::DOUBLE PRECISION` tag then bound a NULL
   double. Fix: detect non-finite numbers in `serializeArgValue` and
   stringify them as `"NaN" / "Infinity" / "-Infinity"`. The executor's
   `Value::String → FLOAT8` arm (`f64::from_str`) accepts these literals
   directly, and the result-side already renders the values as JSON
   strings (matching round-trip).

SDK unit tests grow from 42 → 44 passing.

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

* test(pg): integration coverage for multi-word arrays + stringified array elements

Locks in the two array fixes from the previous commit
(`fix(pg): multi-word PG types with [] suffix lost the array-ness`)
with end-to-end cases in `test_postgresql_arg_type_combinations`:

- `double precision[]`, `character varying[]`, `timestamp without time
  zone[]` — verifies the parser keeps the `[]` suffix after multi-word
  alias resolution.
- `numeric[]` / `int[]` / `bool[]` from stringified primitives — verifies
  the array arms of `convert_vec_val` apply the same string-coercion
  the scalar arms do.

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

* style: fix indentation drift on otyp_inferred lines

cargo fmt cleanup of leftover indentation where the perl-driven sweep
that introduced the otyp_inferred field landed at the wrong column.
No behaviour change.

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

---------

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

* feat: support assigning a worker tag to app inline scripts (#9002)

* feat: support assigning a worker tag to app/raw-app inline scripts

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

* fix: omit empty tag field from inline script raw_code payload

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

* style: shrink tag popover width

---------

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

* feat(pipeline): 2-col picker, draft path edit, save-all + leave guard

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

* all

* all

* update

* fix(cli): forward HEADERS env var on every backend fetch call (#9075)

Several `fetch()` callers in the CLI bypassed `OpenAPI.HEADERS` and skipped
the `HEADERS` env var, causing requests to fail behind auth gateways like
Cloudflare Access (same shape as #6421):

- `pushScript()` `/scripts/create` and `/scripts/create_snapshot` — regressed
  in #8936 when the call switched from `wmill.createScript()` (SDK) to a raw
  `fetch` for the `skip_if_noop` query param.
- Script preview `/jobs/run/preview_bundle`.
- App dev `/jobs_u/getupdate_sse` SSE stream.
- `wmill docs` `/api/inkeep`.

All four now spread `getHeaders()` and call `detectAuthGatewayChallenge()`
so a Cloudflare/SSO challenge surfaces a clear error instead of an opaque
JSON parse failure.

Adds `test/headers_env_var.test.ts`: spins up an auth-gateway proxy that
403s requests missing `CF-Access-Client-Id` / `CF-Access-Client-Secret` and
otherwise reverse-proxies to the test backend, then runs `wmill sync push`
of a fresh script through the proxy. Negative case (no `HEADERS` env)
verifies the proxy actually gates; positive case asserts every request
including `/scripts/create` reaches the backend with the headers attached.

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

* feat(cli): add --parallel flag to generate-metadata (#9074)

* feat(cli): add --parallel flag to generate-metadata

* fix(cli): validate --parallel input and harden flush ordering

* perf(flows): skip flow_env DB+transform work when no resolution is needed (#9078)

* fix(cli-tests): stabilize flow lock-gen race + Windows path (#9080)

* fix(cli-tests): stabilize flow lock-gen race + Windows path

Three CLI test failures on the latest main, all flaky on CI:

1. `Mixed Case Paths: pull and push flow with capitalized folder` and
   `Integration: Mixed scripts and flows with nonDottedPaths are
   idempotent`: flow create/update queues an async FlowDependencies job
   that fills inline-script lockfiles and rewrites flow.value. The tests
   pulled/pushed before the worker finished, so dry-run idempotency saw
   phantom `*.inline_script.lock` adds and `flow.yaml` edits. Added a
   `waitForFlowDependencyJob` helper that polls `/flows/get` for the
   latest `dependency_job` and `/jobs_u/completed/get` until it lands,
   and called it after each API/CLI flow write in both tests.

2. `HEADERS env var is forwarded on every CLI fetch` (Windows-only,
   added in #9075): the new test built the CLI entrypoint via
   `new URL("..", import.meta.url).pathname`, which yields `/C:/...` on
   Windows and `Bun.spawn` rejected before reaching the proxy, leaving
   `rejectedRequests.length` at 0. Switched to
   `fileURLToPath` + `node:path.join` to match `cargo_backend.ts`.

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

* fix(cli-tests): use /flows/deployment_status to actually wait for dep job

CI reviewers (Claude, Codex) flagged the prior `waitForFlowDependencyJob`
as a no-op: it read `flow.dependency_job` from `/api/w/{ws}/flows/get`,
but `Flow` / `FlowWithStarred` (backend/windmill-types/src/flows.rs:20-60)
do not include that field. The helper exited on the first iteration
without polling.

Switch to `/api/w/{ws}/flows/deployment_status/p/{path}`, which returns
`{ lock_error_logs, job_id }`. `job_id` is the FlowDependencies UUID
written into `deployment_metadata` in the same tx as the dep-job push
(backend/windmill-api-flows/src/flows.rs:660-672 and :1275-1292), so by
the time the create/update API call returns, the response carries the
latest dep-job UUID. Then poll `/jobs_u/completed/get/{job_id}` as
before. Local runtime for `mixed_case_paths.test.ts` jumps from ~9s to
~32s, confirming the helper now actually waits instead of returning
immediately. The 404 short-circuit in `sync_pull_push.test.ts` still
works — `get_deployment_status` returns 404 when the flow is absent.

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

---------

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

* perf(flows): cache resolved flow_env per flow execution (#9079)

* perf(flows): cache resolved flow_env per flow execution

* perf(flows): tighten flow_env cache cap to 1024 and clarify memory note

* perf(flows): don't cache transient flow_env resolution failures

* chore(main): release 1.698.0 (#9076)

* chore(main): release 1.698.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* fix: reject root-rooted paths in ansible playbook validator on windows (#9081)

* fix(native-triggers): serialize Google channel renewal across replicas (#9060)

* fix(native-triggers): serialize Google channel renewal across replicas

`sync_all_triggers` runs every 5 minutes on every windmill-app replica
with no leader election. Multiple replicas were each rotating the
webhook token, creating a new Google watch channel, and racing the
trigger UPDATE — leaving the loser's new token (in `token`) and channel
(in Google) orphaned. Cloud was accumulating ~5 leaked tokens/week
without the silent best-effort `delete_token_by_hash` ever logging a
warning.

Wrap each per-trigger renewal in a transaction and acquire the row with
`SELECT … FOR UPDATE SKIP LOCKED`. Contending replicas skip the row
instead of duplicating the work. The lock spans `rotate_webhook_token`
→ Google API call → `update_native_trigger_service_config` and is only
released on commit. Re-checks `should_renew_channel` after acquiring
the lock so a replica that committed seconds earlier doesn't trigger a
duplicate renewal.

The pattern matches existing batch-cleanup paths in `monitor.rs`
(job-retention sweep) and other `FOR UPDATE SKIP LOCKED` call sites.

Also logs at `debug!` when `delete_token_by_hash` finds no matching row,
so future investigations can distinguish "deleted" from "not found"
without changing the `Ok(false)` contract.

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

* fixup! fix(native-triggers): serialize Google channel renewal across replicas

* fixup! fix(native-triggers): serialize Google channel renewal across replicas

fixup! fix(native-triggers): serialize Google channel renewal across replicas

Address claude review:
- #5: per-skip log info -> debug (expected outcome under SKIP LOCKED)
- #2: warn moved out of delete_token_by_hash to the call site that knows the
  expected state (try_renew_channel_locked); other callers are race-prone and
  shouldn't warn
- #3: NULL service_config now warns (anomalous case)
- #4: post-Google-API DB-update + commit failures log distinctly so the
  channel-orphan case is grep-able

Plus: add 14d expiry to Google webhook tokens via ServiceName::webhook_token_expiration,
mint fresh ephemeral-webhook-{service}-{rd5} labels at create + rotate so the
existing 'ephemeral-' filter excludes them from user-token email/critical-alert
paths (no filter changes in 3 places). Orphans now self-clean via the existing
expiry sweep in monitor.rs.

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

* fixup! fix(native-triggers): serialize Google channel renewal across replicas

fixup! fix(native-triggers): serialize Google channel renewal across replicas

Address second-round review:
- Claude #1 (P2): username_override_from_label now strips the 'ephemeral-'
  prefix for ephemeral-webhook-* labels, so created_by stays
  webhook-{service}-{rd5} instead of changing to label-ephemeral-webhook-...
  (preserves audit/job-list filter compatibility)
- Codex (P2): updated renew_channel doc — labels are no longer copied; rotate
  mints fresh ephemeral-webhook-google-{rd5} with 14d expiration
- Claude #3 (optional): test_rotate_webhook_token now asserts the rotated
  Google token has an ephemeral-webhook-google-* label and a populated
  expiration

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

* fixup! fix(native-triggers): serialize Google channel renewal across replicas

fixup! fix(native-triggers): serialize Google channel renewal across replicas

Reconsider the previous fixup: stripping the 'ephemeral-' prefix made
created_by no longer match token.label exactly, defeating the linking
purpose. Just allowlist 'ephemeral-webhook-' alongside the other
recognized webhook/email/ws prefixes — created_by becomes
ephemeral-webhook-google-XXXXX, matching token.label exactly. The
'ephemeral-' substring also informs operators that this is a
system-managed auto-expiring token vs a user-managed webhook trigger.

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

---------

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

* fix(cli): bump svelte version in `wmill app new` template (#9084)

* fix(cli): bump svelte version in `wmill app new` template

The svelte5 template pinned `svelte` to `5.45.2`, but the Svelte
compiler bundled in `wmill app dev` emits `$.delegated('click', ...)`
calls. The `delegated` export was added later, so 5.45.2 doesn't have
it — esbuild warns `Import "delegated" will always be undefined`,
replaces the call with `void 0`, and the page crashes at first
event-handler bind (white screen).

Bump to `^5.55.5` so the compiler and runtime stay in sync.

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

* fix(frontend): bump svelte version in raw_apps UI template

Mirror the CLI fix: the UI's `Add raw app` flow scaffolds a
package.json with `svelte: "5.45.2"`. That works today only because
the bundled rolldown worker also pins 5.45.2 — when the worker is
upgraded past 5.51.1, the compiler will emit `$.delegated()` and the
runtime won't have it, producing the same white-page crash that hit
the CLI.

5.55.5 still exports `event` (used by the current bundled compiler),
so this is forward-compatible: it works with the 5.45.2 compiler now
and won't break when the worker is upgraded.

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

---------

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

* perf(flows): gate flow_env resolve on expr text and share cache with handle_flow (#9085)

* feat: parse windmill_failure field to tag run as failure (#9073)

* feat: parse windmill_failure field in job result to tag run as failure

* feat: preserve top-level fields when windmill_failure tags a run as failure

* fix: address review findings on windmill_manual_failure

* refactor: rename windmill_manual_failure to wm_failure and add wm_* aliases

* fix: prefer injected ManualFailure error over sibling name/message in OTel

* fix: hide _ENTRYPOINT_OVERRIDE jobs from script/flow history panel (#9088)

* fix(flows): populate error handler input args from failure picker (#9087)

* fix(flows): populate error handler input args from failure picker

* style(flows): fix indentation in failure-step branch

* fix(python): verify wheel RECORD on cache pull/install, finalize piptar (#9090)

The Python per-package dependency cache could persist an incomplete wheel
extraction with `.valid.windmill` set, then propagate that broken artifact
to every worker through the object store. Customer hit this on
argon2-cffi==25.1.0 (missing argon2/_utils.py), and previously on
botocore/httpx (truncated tars). Symptom is a runtime ImportError that
looks like a missing dependency declaration rather than a Windmill bug.

Three changes that together stop the propagation:

1. After `pull_from_tar`, parse the wheel's `<dist-info>/RECORD` and
   confirm every listed path exists on disk before writing
   `.valid.windmill`. On failure, wipe the directory and fall through
   to a fresh local install — the next install also self-heals the
   broken object-store entry by pushing a fresh tar.

2. After `uv pip install` succeeds, run the same RECORD check before
   queuing the piptar upload or writing `.valid.windmill`. A bad install
   never becomes the source of a broken tar in the object store.

3. Finalize the tar (`drop(tar.into_inner()?)`) before reading its bytes
   for upload, so we never push an unfinalized archive (no end-of-archive
   marker) to the object store.

Verified with a 60-package end-to-end integration test (first-fill →
clear-local-cache → re-pull-from-objectstore → corrupt-objectstore-tar
→ detect-and-self-heal). All 27 packages on the live test pulled cleanly,
and the deliberately corrupted argon2-cffi tar was caught with the exact
expected log line ("wheel RECORD lists files missing on disk: argon2/_utils.py")
and replaced with a fresh tar.

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

* chore(main): release 1.699.0 (#9082)

* chore(main): release 1.699.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* feat(cli): auto-infer args for `wmill app push` (#9091)

Run `wmill app push` from inside an app folder (e.g. `f/foo/my_app.app/`)
with no args. The local path defaults to CWD, and the remote path is
derived from CWD relative to `wmill.yaml`, with `.app`/`.raw_app`/
`__app`/`__raw_app` suffixes stripped. Either, both, or neither
positional argument can be passed.

Also resolves `file_path` against the user's original CWD before
`resolveWorkspace` may chdir to the wmill.yaml root, so a relative
`file_path` argument is interpreted from where the user invoked the
command (previously it could resolve against the wrong directory).

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

* all

* fix(pipeline): live-update graph for annotations and body assets

* fix(pipeline): persist draft body edits across node switches

* fix(pipeline): persist live writes per draft to keep output node fresh after switch

* feat(pipeline): animate graph edges only while a runnable is executing

* feat(pipeline): add run button on script nodes + recomputing hint on preview

* feat(pipeline): compact preview layout, two-way Test/Run sync

* fix(pipeline): test button cross-browser placement (no overflow trick)

* style(log-viewer): replace took/mem-peak labels with timer/cpu icons

* style(log-viewer): hyphenate Auto-scroll label and prevent wrapping

* style(log-viewer): lowercase auto-scroll label, force vertical scrollbar

* style(log-viewer): force horizontal scrollbar instead of vertical

* fix(log-viewer): scope overflow-x to top bar so pre doesn't drive panel width

* fix(pipeline): overlay live body-asset writes for persisted scripts too

* fix(pipeline): persist inferred body assets at save so edges survive page reload

* fix(pipeline): snapshot live draft writes at persist time so they survive reload

* fix(pipeline): keep inferred body writes on the canvas across selection changes

* fix(pipeline): untrack inferredWrites cache mutation to break effect loop

* fix(pipeline): refetch asset graph after persisted-script save

* feat(pipeline): optional AI prompt when creating a pipeline script

* all

* all

* test: cover asset-trigger dispatch end-to-end through worker

* feat(pipeline): split-button Test with optional downstream cascade

* feat(pipeline): cascade option on graph Run + match button heights

* style(pipeline): match caret bg/text to Test button's accent-secondary

* feat(pipeline): split Run pill on graph node exposes cascade option

* feat: live run activity + status badges in pipeline asset graph

- folder-scoped queue poll lights up the downstream asset-trigger
  cascade (not just the launched script); zero requests at rest,
  catch-up for fast hops, auto-disarm when idle
- per-runnable node badge: last-run status + session run count
- animate unsaved/live-parsed edges (was unconditionally suppressed)
- background-pane click no longer clears selection
- run-bridge guarded so node selection/save no longer triggers a test

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

* feat: live activity log, optimistic badges, node-avoiding graph edges

- collapsible folder activity log (PipelineEventLog): live job feed,
  polls only while open/active, slow idle cadence, capped + pruned
- composable: observe mode + events list + run-count anchored to
  graph-open time (pre-existing history excluded)
- optimistic node badge: launched script shows running instantly via
  the zero-latency activeRunnable hint, keeps the polled run count
- activity pane height capped (min(18rem,40vh)) then scrolls
- route asset-graph edges through sugiyama-computed waypoints so they
  go around nodes instead of under them; bezier fallback for
  adjacent-layer / draft-overlay edges

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

* feat: prefetch all folder script assets so graph is stable on load

On pipeline load, eagerly infer body assets for every persisted folder
script and seed the existing inferredWritesByPath overlay, instead of
only filling it when a node is selected. Scripts whose persisted asset
rows are missing (e.g. object-form writeS3File) now have their edges
from first paint, so clicking a node no longer re-layouts the graph.
One-shot per (workspace, base-graph) load, untracked map reads,
generation-cancelled, pool-capped fetches.

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

* perf: guard no-op poll re-layout; dedupe write-asset extraction

- skip reactive ids/states/events reassignment when unchanged, so an
  idle poll tick no longer re-runs the full sugiyama layout every 3-6s
- bound countedJobIds (rebuilt from eventsById in lockstep with prune)
- extract shared extractWrites() helper, replacing 4 copy-pasted
  write-asset filter/map blocks in the pipeline page
- compute activeRunnable node-id once, reuse for the active-edge set
  and the optimistic badge (flattened ternary); trim narrating docs

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

* feat: live read-lineage overlay for inferred body assets

Renaming e.g. duckdb read_parquet('s3://...') / loadS3File now updates
the asset->reader edge live instead of only after Save re-derives the
persisted asset rows.

- extractReads() (+ shared refsByAccess) mirroring extractWrites
- inferredReadsByPath sticky cache, filled by handleAssetsChange and
  the load prefetch alongside writes
- replace the write-only overlay loop with one overlayLineage(map,
  access) helper invoked for both 'w' and 'r' (net DRY)

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

* fix: detect S3 assets passed as SDK object arg in ts parser

Mirrors merged PR #9181 so feat/asset-graph-view is self-contained
(local origin/main is stale and lacks it). Object/{ s3, storage }
form of writeS3File/loadS3File is now detected, not only the bare
s3:// string literal.

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

* chore: regenerate wasm Cargo.lock + frontend package-lock

Lockfile churn from local wasm-pack (asset target) + npm operations
during the asset-graph work. No source/dependency-intent change.

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

* fix: revert to bezier graph edges; add parsing-assets hint

The sugiyama-waypoint routing looked worse than the original; revert
AssetGraphEdge/assetGraphLayout to the pre-routing bezier logic (same
as the flow editor's BaseEdge) and drop the now-unused route plumbing
from the canvas. Add a small 'Parsing assets…' hint shown while the
load-time prefetch sweep is still inferring folder scripts.

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

* refactor: extract pure resolveGraph merge + unit tests

Move the ~230-line graphWithDraft precedence/merge (base < session-
inferred < draft-seeded < open-script-live, +read/write/annotation
overlays, +dedup) out of the 1648-line route into a pure, testable
resolveGraph() module; the route's graphWithDraft is now a thin
$derived. Behaviour extracted verbatim. 10 unit tests cover the
precedence matrix. Phase 1 of the state/render split.

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

* style: graph controls top-right, lift minimap, hide Save when unchanged

Controls -> top-right horizontal, no lock toggle; MiniMap !mb-10 so
it clears the activity bar; hide the per-script Save button when the
script is already at its latest save point (drafts still show Create).

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

* fix: scope runtime-asset prune by id to spare static lineage rows

prune_runtime_assets deleted by (workspace_id, path, kind) tuple, so
trimming surplus usage_kind='job' rows for an s3 path also wiped the
static usage_kind='script'/'flow' producer rows for the same path —
silently breaking the asset-trigger cascade (fetch_producer_writes
found no writes; downstream never dispatched; required band-aid
re-syncs). Delete the surplus job rows by id instead; the inner query
is already scoped to usage_kind='job'.

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

* fix: don't re-pulse already-running jobs after they finish

The catch-up pulse re-added a completed job to the active set if its
start was within the (lagging) lookback window — even one we'd already
animated the whole time it ran — keeping its edges lit ~a poll
interval past completion (~5s after a 3.5s test). Track job ids seen
in-flight and skip the pulse for them; it still fires for hops whose
whole lifetime fell between two polls. Bound the set in lockstep with
eventsById; cleared on dispose.

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

* fix: don't catch-up-pulse the runnable launched from the graph

If the poll never sampled a launched run's in-flight window, the
catch-up pulse re-flashed its edges one tick after it correctly
stopped (the page already animated it zero-latency via activeRunnable).
arm(launchedId) records the launched runnable id; catch-up skips it.
Cascade hops (other ids) still pulse. launchedIds cleared on stop.

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

* style: nudge graph controls left to clear panel toggle

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

* feat: partition value resolver + asset-cascade propagation

windmill-common/partition: pure resolver — time kinds (tz/format/start
anchor) + dynamic $.a.b JSONPath; 9 unit tests. asset_dispatch:
read the producer's resolved partition and thread it into every
cascaded subscriber's args + trigger.partition, so a chain resolves
once at the top. No migration (cascade needs no spec lookup). Stage
1+3 of pipeline partition runtime; run-start resolution is Stage 2.

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

* feat: show args form in compact pipeline preview when script has inputs

AssetGraphDetailsPane keeps the compact (hideArgs) preview but, via a new
previewPanel.argsAboveLogs flag, renders a compact SchemaForm between the
floating Test button and the logs/result panel when the script declares
inputs (e.g. a partitioned script needing a `partition` arg). The preview
pane also grows ~18pts so the args form doesn't shrink logs/result.

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

* feat: parser join-mode (`// trigger all`) + script_trigger.join_all

Stage A: JoinMode{Any(default),All} + `// trigger any|all` directive in
parse_pipeline_annotations; TriggerSpec::is_partition_bearing() (path
contains {partition}); join_mode threaded through all 4 asset-parser
crates (ts/py/sql/yaml). Stage B: reversible migration adds
script_trigger.join_all; insert_script_trigger writes it; deploy path
sets it from the parsed annotation. No reader yet (AND-join dispatch is
the next stage) so runtime behaviour is unchanged.

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

* feat: resolve pipeline partition at job execution time

Stage C: in handle_code_execution_job, once the script content is loaded,
parse the // partitioned annotation (free here) and resolve the concrete
partition once — schedule fire-time (scheduled_for anchor, not wall-clock)
for time kinds, triggering payload for dynamic. The value is injected
into the in-memory args the body sees (via a shadowed job clone) and
persisted back to v2_job.args so dispatch_asset_triggers propagates the
same value down the cascade. Already-set (explicit/backfill/cascade)
partitions are never re-resolved (run identity immutable); unresolvable
partitioned runs fail with a clear error. Integration test exercises the
full worker loop + cascade propagation.

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

* feat: AND-join barrier for partitioned pipeline subscribers

Stage D: a // trigger all subscriber no longer fires on any input. New
join_pending_inputs slot table keyed (workspace, subscriber, partition);
fetch_subscribers now returns join_all and the dispatch loop records each
partition-bearing input arrival, pushing the subscriber once only when
every partition-bearing input it declares is present for that partition.
Per-partition slots, cleared on fire (re-accumulate, no double-fire),
skew-immune (unlike debounce). Case-3 guard: an unpartitioned producer or
a reference (non-{partition}) input never fires a partitioned join.
Integration test covers wait/fire/isolation/no-double-fire.

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

* feat: opt-in // debounce for asset-cascade subscribers (parser + schema)

Stage E1+E2. Parser: script-level // debounce <dur> + per-// on
debounce=<dur> override (edge wins, else script default, else none =
fan-out, unchanged); TriggerSpec::Asset carries the per-edge override;
split_trailing_kv_opts separates the ref from trailing key=val opts.
Schema/deploy: reversible migration adds script_trigger.debounce_s;
parse_duration_secs (bare int or <n>s|m|h|d, fail-safe on garbage)
resolves the effective per-edge window at deploy and writes it per row.
No reader yet (dispatch wiring is E3) so runtime is unchanged. New unit
tests for the parser directive and duration parsing.

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

* feat: apply opt-in debounce to asset-cascade subscriber dispatch

Stage E3. fetch_subscribers now also returns debounce_s; push_subscriber
builds real DebouncingSettings (delay + a (subscriber, partition) key,
so distinct partitions never collapse and latest-in-window falls out)
instead of ::default() when the edge opted in. Default stays no-debounce
(fan-out — the prior deliberate behaviour, now overridable rather than
reversed). Wiring test asserts the dispatched job carries the configured
window/key and an undebounced edge carries none.

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

* fix: atomic AND-join gate + preserve resolved partition; drop scratch artifacts

Addresses local-review findings before PR:
- P1: record_and_check_join_slot was a non-atomic check-then-act on a
  pooled connection; concurrent completion of a subscriber's last two
  partition-bearing inputs on different workers could double-dispatch.
  Now one transaction guarded by a tx-scoped advisory lock keyed on
  (workspace, subscriber, partition) so the gate fires exactly once.
- P2: the preprocessed-args overwrite in result_processor replaced args
  wholesale, dropping a partition resolved by resolve_partition_for_job;
  the UPDATE now preserves an existing persisted partition key.
- P2: gate resolve_partition_for_job on a cheap code.contains check so
  non-pipeline script jobs skip the annotation scan on the hot path.
- P2: remove 40 scratch screenshot PNGs, a flicker-debug script and a
  local scheduler lock accidentally committed; gitignore the lock.

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

* test: AND-join fires once under concurrent upstream completion

Regression for the check-then-act race fixed by the advisory-locked
transactional gate: releases N producer dispatches simultaneously via a
barrier and asserts the AND subscriber is pushed exactly once and the
slot is cleared. The invariant holds for the correct gate regardless of
interleaving; a non-atomic regression fails it.

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

* test: fuller partitioned join + multi-hop pipeline coverage

Exercises a complex pipeline combining options end to end: two
partitioned producers fanning into a // trigger all join, then a
multi-hop downstream chain. Asserts the resolved partition propagates
unchanged at every hop, chain depth increments per hop, the AND barrier
fires exactly once, and a second partition opens an independent slot
with no cross-partition bleed across the whole graph.

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

* refactor: simplify pipeline code per review (dedup, single-parse, constant)

- ParseAssetsOutput::new() collapses the 6-line annotation copy-paste
  across the 4 asset-parser crates to one call site.
- asset_dispatch: parse the cascade trigger object once and pass it to
  the depth/partition readers instead of deserializing it twice; add a
  TRIGGER_ARG constant for the previously stringly-typed key (3 sites).
- scripts deploy: drop a redundant debounce_default clone.
No behavior change; 29 parser + 6 dispatch integration tests green.

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

* feat: reap abandoned AND-join slots after a TTL (default 60d, per-slot)

join_pending_inputs slots are normally cleared when the join fires;
partial slots whose inputs never all arrive (upstream removed/renamed,
one-off dynamic partition key, permanent skew) would otherwise leak.
windmill_queue::asset_dispatch::reap_stale_join_slots, called from the
monitor's delete_expired_items loop, deletes a (workspace, subscriber,
partition) slot only when its MOST RECENT row is older than
JOIN_SLOT_TTL_SECS (60d) — per-slot, never per-row, so a legitimately
slow join is not corrupted mid-accumulation. Conservative default;
per-join configurable TTL via the annotation is a planned follow-up.
Test covers stale-reaped / fresh-kept / mixed-slot-kept.

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

* update

* feat: path-less native trigger markers + missing-trigger placeholder

* feat: pipeline // tag and // retry annotations + dispatch_event log

* fix: derive test-pane min from split-axis dimension (height in bottom layout)

* feat: show last run logs/result when a script node is selected

* fix: backfill asset rows from script.assets for pre-feature scripts

* feat: job-id link + dispatch popover above script log/result

* style: drop 'dispatched' label, keep just the check icon

* fix: drop tag picker from pipeline script editor (set via // tag annotation)

* Nicer UI

* refactor: move google ai proxy handling to windmill-ai (#9260)

* refactor: add ai proxy execution mode

* refactor: move google ai proxy handling

* refactor: share google ai request building

* fix: early return should consider failure_module result (#9241)

* fix(flows): flag noLogs jobs and lazily resolve them in log panel (#9099)

* fix(flows): flag noLogs jobs and lazily resolve them in log panel

* fix appending to flag

* fix: preserve WM_LOGS_SKIPPED sentinel on SSE/replay completion

pickMoreCompleteLogs resolved both sentinel and undefined to '', so the
SSE completion event (whose job field is fetched .without_logs()) would
clobber the sentinel placed by flagSkippedLogs. The module log panel
then saw '' instead of the sentinel, defeating the lazy-resolve path.

Also wire onLogsResolved on the OutputPickerInner inline LogViewer so a
lazy resolve writes back to flowStateStore.previewLogs, matching
ModulePreviewResultViewer and avoiding repeated fetches on remount.

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

---------

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

* chore(main): release 1.705.0 (#9229)

* chore(main): release 1.705.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* chore: add playwright mcp for frontend verification (#9269)

* feat: CLI datatable serve / psql (#9267)

* feat(cli): add datatable list and run commands

* feat(cli): render datatable query results as a table

* feat(cli): serve datatables as a postgres-wire endpoint

* feat(cli): add 'datatable psql' to launch psql against the proxy

* feat(cli): route datatable serve by client-supplied database name

* override database list + password option

* fix: support extended queries in datatable serve

* fix: correct cloud size threshold log and parse CLI descriptions with parens/trailing comma

* refactor: extract raw_output envelope encoding into pg_raw_output module

---------

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

* oom_adj nit

* feat: add UV_PYTHON_INSTALL_MIRROR env and instance setting (#9271)

* feat: add UV_PYTHON_INSTALL_MIRROR env and instance setting

Allows operators to point `uv python install` at a private mirror of the
python-build-standalone releases. Configurable via the
`UV_PYTHON_INSTALL_MIRROR` env var or the `uv_python_install_mirror`
instance setting, with the env var as the boot fallback and the instance
setting taking precedence at reload.

Fixes WIN-1966

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

* fix: hoist uv_python_install_mirror binding above sandboxing branch

The non-sandboxed uv pip install branch referenced a binding that was
only declared inside the sandboxed branch.

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

* fix: neutral placeholder for uv_python_install_mirror

The previous placeholder was the default public URL the setting is meant
to redirect away from. A neutral example mirror URL is clearer.

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

---------

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

* fix(indexer): tell admins when ingress routes search to wrong pod (#9274)

* [ee] fix(indexer): tell admins when ingress routes search to wrong pod

When the IndexReader is absent on the pod handling a search request but
another pod is actively holding the indexer lock, the EE handler now
returns a tailored error pointing at the ingress/load-balancer
configuration instead of the generic "indexer not running" message.

The indexer status endpoint reads the DB lock so it reports "running"
from any pod, but search endpoints need the in-memory IndexReader that
only exists on the lock holder. In multi-replica deployments this looks
like the indexer is healthy but every search 404s.

Companion: windmill-labs/windmill-ee-private#TBD

Fixes WIN-1968.

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

* chore: update ee-repo-ref to eb18d7b4c0e37fea3f6e1e2cc44e0fddd74ff817

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

Previous ee-repo-ref: 7dd43d1850813071cc18ba49ba090583e7321f4b

New ee-repo-ref: eb18d7b4c0e37fea3f6e1e2cc44e0fddd74ff817

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>

* feat(cli): add `wmill init prompts` and custom override slot (#9266)

* feat(cli): add `wmill init prompts` and custom override slot

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

* refactor(cli): replace init prompts with refresh prompts + AGENTS.md/AGENTS.cli.md split

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

* feat(cli): dedupe claude skills via @-includes and add prompts freshness check

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

* refactor(cli): drop migration-choice flags from `refresh prompts`

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

* docs(cli): add 'Running and previewing local changes' section to AGENTS.cli.md

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

* fix(cli): write full skill content to .claude/, drop @-include wrapper

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

* feat(cli): reconcile CLAUDE.md the same way as AGENTS.md

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

* fix(cli): address PR review nits — argv parsing, lazy import, comment detection, error propagation

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

---------

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

* feat: add yolo mode for ai chat tools (#9258)

* feat: add yolo mode for ai chat tools

* nit

* fix: align chat footer controls

* feat: add ai chat autonomy modes

* feat: add autonomy mode dropdown

* fix: highlight yolo autonomy icon

* fix: auto accept flow edits

* fix: hide unsupported autonomy modes

* fix: handle auto-accept flow editor races

* fix(debugger): add non-root user support to Dockerfile (#9277)

Mirrors the main Windmill Dockerfile pattern: creates a windmill user
(UID/GID 1000) and makes cache/work directories world-writable so the
image runs cleanly under Kubernetes securityContext.runAsNonRoot or
runAsUser: 1000 without permission errors on Bun, pip, or windmill
cache writes.

Fixes WIN-1969

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

* fix(ai): enforce RLS and scope check on user-supplied X-Resource-Path (#9276)

* fix(ai): enforce RLS and scope check on user-supplied X-Resource-Path

The AI proxy handler accepts an X-Resource-Path header to override the
configured workspace AI provider. When supplied, the handler loaded the
resource value from the resource table using the root DB pool with no
resources:read scope check, so any authenticated workspace user could
point X-Resource-Path at a restricted AI resource (e.g. one in a folder
they cannot read) and the proxy would use that resource's provider
credentials for the outbound AI request.

For user-supplied resource paths, now require resources:read:{path}
scope and fetch the resource through user_db.begin(&authed) so RLS
enforces the same folder/group boundary as the resource API. The RLS-
scoped $var: resolution stays in place as defense in depth. The
admin-configured workspace/instance ai_config path is unchanged.

Fixes WIN-1971

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

* test(ai): regression test for X-Resource-Path RLS enforcement

Cover all four cases:
- non-admin pointing X-Resource-Path at a restricted resource is rejected
- non-admin pointing it at a resource they own still works
- admin can point it at any resource
- workspace-configured proxy flow (no X-Resource-Path) is unchanged

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

---------

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

* feat: add userdraft listing primitives (#9268)

* feat: add userdraft listing primitives

* fix: cancel stale userdraft discard writes

* docs: remove global ai userdraft plan

* feat(nsjail): optional disk-backed /tmp via instance setting (#9272)

* feat(nsjail): optional disk-backed /tmp via instance setting

* test(nsjail): unit-test tmp mount resolver and narrow visibility

* refactor(nsjail): switch tmp backing to select + conditional UI

* ui(nsjail): make tmpfs the visible default in /tmp backing select

* fix(nsjail): refuse preexisting jail_tmp to block symlink escape

* fix(nsjail): allow jail_tmp reuse on sequential nsjail calls

Codex flagged that python/ruby/rust executors invoke nsjail twice per
job_dir (install then run). The previous resolver treated any preexisting
jail_tmp as hostile and silently fell back to tmpfs on the second call,
so disk-backed mode never reached the main script run for those langs.

Use symlink_metadata().is_dir() to distinguish a real directory left by
an earlier call in the same job_dir (safe to reuse) from a symlink or
other entity (still refused, as the codebase-tar escape requires).

Also loosen the frontend visibility predicate: only hide nsjail settings
when job_isolation is explicitly 'none' or 'unshare', so deployments
that enable nsjail via DISABLE_NSJAIL=false with no DB setting can
still see the controls.

* chore(main): release 1.706.0 (#9270)

* chore(main): release 1.706.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* fix(nsjail): gate unix-symlink test behind cfg(unix) for Windows build (#9280)

The disk_backed_refuses_preexisting_symlink_at_jail_tmp test calls
std::os::unix::fs::symlink directly, which doesn't exist on Windows
targets. Without a cfg gate, `cargo check --tests` fails on Windows
with E0433. Other symlink call sites in this crate (php_executor,
bun_executor, rust_executor, etc.) already follow this pattern.

Fixes WIN-1972

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

* Reduce slim image vulnerability surface (#9279)

* Reduce slim image vulnerability surface

* chore(docker): drop apt-get upgrade -y from slim images

apt-get upgrade hurts build reproducibility (same Dockerfile + same
commit at different times produces divergent images) and trips hadolint
DL3005. The freshness it buys is dominated by simply rebuilding against
the periodically-refreshed debian:bookworm-slim base image.

The --no-install-recommends and apt-list cleanup wins are kept.

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>

* fix(git-sync): bump to hub/28234 with stateless gpg.program wrapper (WIN-1974) (#9282)

* fix(git-sync): revert LATEST_GIT_SYNC_SCRIPT_PATH to hub/28230 to restore GPG-signed deploys (WIN-1974)

hub/28231 (PR #9230) is the "thin" script that hands the actual `git commit`
to the CLI's hidden `sync git-deploy`. The hub script still does the GPG
setup (import key into a fresh GNUPGHOME, dummy `gpg -bsau` to warm the
agent passphrase cache, then `git config user.signingkey` + `commit.gpgsign`
locally), but the commit no longer runs in the same `git_push` flow — it
runs minutes later inside the CLI after workspace API resolution, zip pull,
file extraction, and lockfile autofill. By the time the spawned `git commit`
asks gpg-agent for the cached passphrase, the cache state is no longer
reliable (or the spawned `gpg` ends up talking to a fresh agent), so signing
fails non-interactively with `gpg failed to sign the data`.

hub/28230 is hub/28217's in-script logic rebuilt with windmill-cli@1.703.3:
the GPG setup and the in-script `sh_run("git commit ...")` happen back-to-back
in `git_push`, so the cache is always fresh. It preserves wm_deploy / fork
branch behavior, the EE deployment-callback `main()` signature is unchanged,
and the only min-version check in EE (`is_script_meets_min_version(28103)`)
is comfortably below 28230 — so this revert is safe.

Forward fix (separate PR): publish a new thin script that, alongside the
existing GPG setup, writes a `gpg.program` wrapper using `--pinentry-mode
loopback --passphrase-file` so signing is independent of the agent's cache
state. Re-bump past 28231 then.

Fixes WIN-1974

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

* chore(git-sync): check in source-of-truth for the next hub script (gpg.program wrapper)

This is the script that will be published to hub.windmill.dev once verified
on a customer GPG-signed deploy. It replaces hub/28231's agent-cache
pre-warm (`gpg -bsau` with --passphrase) with a stateless gpg.program
wrapper + chmod-600 passphrase file. Every git-invoked gpg call goes
through the wrapper, which always uses --pinentry-mode loopback (and
--passphrase-file when a passphrase exists). Signing no longer depends on
gpg-agent having a cached passphrase by the time the CLI's `git commit`
runs — which closes WIN-1974.

Not wired in yet: LATEST_GIT_SYNC_SCRIPT_PATH stays on hub/28230 until this
script is uploaded and the new hub id is known. This file is checked in so
the diff is reviewable, future bumps have a source of truth, and a CLI
regression test can `cat` it for fixture parity.

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

* fix(frontend): skip format/pattern validation for $var/$res/$jsonvar references in ArgInput

A resource field with a `pattern` constraint (e.g. the gpg_key.private_key
field, whose pattern enforces a `-----BEGIN PGP PRIVATE KEY BLOCK-----`
prefix) rejects values like `$var:u/me/gpg-private-key` with an "invalid
format" error in the resource editor — even though `$var:`/`$res:`/`$jsonvar:`
are placeholders the backend resolves at runtime, not the actual string
that needs to match the regex.

Bail out of all format/pattern checks (email, ipv4, ipv6, uuid, custom
pattern) when the value is one of these references. Required/numeric
bounds/array checks still apply since they're shape-level, not regex.

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

* feat(git-sync): bump LATEST_GIT_SYNC_SCRIPT_PATH to hub/28234 (gpg.program-wrapper fix)

hub/28234 is the forward fix for WIN-1974: replaces hub/28231's agent-cache
pre-warm (which became stale by the time the CLI's `git commit` ran) with
a stateless `gpg.program` wrapper that uses `--pinentry-mode loopback`
(and `--passphrase-file` when a passphrase exists) on every gpg invocation.
Bundled CLI is windmill-cli@1.705.0.

Verified via reproducer at /tmp/git-sync-diff/test-gpg-fix.sh: deliberately
killing gpg-agent between GPG setup and `git commit` reproduces the
customer's `gpg failed to sign the data` error verbatim under the old
flow, and the wrapper signs through it. Holds for passphrase-protected
keys, split-subkey [C]+[S] layouts, and unprotected keys.

Drops the local source-of-truth copy (`hub-scripts/`) — hub is canonical
now that 28234 is published.

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

* chore(git-sync): drop verbose comment above LATEST_GIT_SYNC_SCRIPT_PATH

The git history (this PR) carries the why; the constant name + value carry
the what.

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

---------

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

* refactor(cli): wmill sync git-deploy stops committing; caller owns commit+push (#9284)

Single contract for the deployment-callback path: the CLI does branch
checkout + pull, the caller (hub script in production, test in test)
does git add + commit + push. This restores the WIN-1974 invariant —
GPG setup and `git commit` run back-to-back in the same process, so
the agent's pre-warmed passphrase cache is still warm at sign time —
without needing a `--skip-commit` flag for the hub case and a default
"also-commit" for everything else. Same behavior in every call site.

Changes:
  - sync.ts: drop the gitSyncDeployPush call from pull()'s deploy path
    (both the onlyCreateBranch fast-return and the post-pull commit).
    `gitSyncDeployPush` stays exported for any caller that wants the
    same commit/push semantics — just not invoked by the CLI subcommand.
  - gitsync_promotion.test.ts: e2e test now does its own git add +
    commit + push after `wmill sync git-deploy`, mirroring what the
    hub script does in production. Same regression coverage
    (wm_deploy branch created in Case A, main untouched; main updated
    in Case B, no new wm_deploy).

CLI typecheck unchanged (two pre-existing TarAsZip errors at lines
2578/3307, present before this PR). All 743 unit tests still pass.

The accompanying hub script (option-C — CLI for branch+pull, script
for commit+push) lives at /tmp/git-sync-diff/sync-script-to-git-repo-windmill.option-C.ts.
Once published, a follow-up bumps LATEST_GIT_SYNC_SCRIPT_PATH to its id.

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

* bump git sync to 28236

* fix: fork compare visibility for non-admins and stale-token superadmins (#9283)

* fix: use fork-scoped authed for fork visibility in compare_workspaces

* test: add EE end-to-end repro for fork rename visibility

* chore: restore concurrency_locks sqlx cache lost in cleanup

* test: add regression for stale-superadmin-token fork visibility bug

* chore: update sqlx cache for new test queries

* chore(main): release 1.706.1 (#9281)

* chore(main): release 1.706.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* feat: add wmill job rerun subcommand (#9275)

* feat: add wmill job rerun subcommand

* feat: add wmill job restart subcommand for flow restart-at-step

* chore(system_prompts): point plugin skills sync at plugins/windmill/ (#9287)

* chore(system_prompts): point plugin skills sync at plugins/windmill/

The plugin checkout's plugin folder is being renamed from
`plugins/windmill-code-plugin/` to `plugins/windmill/` to shorten the
slash-command namespace and align with the matching Cursor plugin
layout.

Paired with windmill-labs/windmill-claude-plugin#8. That PR must merge
first so the next sync run finds the new folder.

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

* docs(system_prompts): update plugin-dir example to plugins/windmill

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: centdix <centdix@users.noreply.github.com>

* fix(cli): wmill sync pull updates wmill-lock.yaml for raw apps (#9289)

* fix: flow recording teardown crash + rename package to @windmill-labs/components (#9288)

* fix: guard against null recording during FlowRecordingReplay teardown

Navigating away from a flow recording inside a workspace file-tree view
threw `TypeError: Cannot read properties of null (reading 'flow')` from
FlowGraphViewer once during the teardown tick.

Svelte 5 compiles child component props as live getters that close over
`$$props.recording.flow`. When `recording` flips to null on the parent's
navigation, an outer `{#if !recording?.flow}` doesn't stop those getters
from firing one more time as derived effects re-evaluate before the
unmount lands — so the getter dereferences null and throws.

Fix at the two layers where the deref actually happens:

- FlowRecordingReplay: use `recording?.flow` at the binding sites
  (FlowViewer + graph-snippet FlowGraphViewer) so the compiler emits an
  optional-chained getter, and guard the snippet branch with
  `{:else if recording?.flow}` so it doesn't mount when there's nothing
  to show.
- FlowGraphViewer: finish the optional chaining the rest of the file
  already used everywhere else (`flow?.value?.skip_expr`,
  `flow?.value?.cache_ttl`, `flow?.schema`). When the upstream
  binding returns undefined during teardown, the graph degrades to an
  empty frame instead of crashing.

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

* chore: rename package to @windmill-labs/components

- frontend/package.json: rename `windmill-components` → `@windmill-labs/components`
- frontend/publish.sh: drop the in-place sed rename dance; the checked-in name now matches what's published, so `npm run package && npm publish` is enough
- frontend/package-lock.json, system_prompts/auto-generated/prompts.d.ts: regenerated by `npm run package` under the new name

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

---------

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

* default script name

* save logic

* Keyboard nav

* finish keynav

* nits

* CI fix

* nit stop propagation

* Merge branch 'main' into feat/asset-graph-view

* commit

* update

* fix: cropped save button on small screens

* progress

* managed scheduled removed

* all

* progress

* feat: add data upload pipeline trigger with auto S3 picker

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

* fix: avoid pane editor remount flicker when deploying a pipeline draft

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

* fix: show only the edited script's I/O in the asset graph, not the saved version's

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

* feat: derive script asset rows server-side at deploy

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

* test: shared fixture corpus keeps annotation parsers in parity

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

* feat: dev-run draft pipeline chains, live badges, deploy drift warning

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

* refactor: ungate cascade producers, squash pipeline migrations

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

* chore: drop committed cli-sync fixtures and stray screenshots

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

* feat: show skip-asset-dispatch flag as badge instead of args row

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

* feat: pipeline view mode default with activity feed, drafts overlay chip

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

* fix: treat DROP TABLE as table-level write in sql asset parser

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

* feat: wmill datatable create + actionable sql extension error

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

* chore: ephemeral data-pipelines demo sync repo zip for handoff

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

* feat: wmill pipeline list/show renders the asset DAG in the terminal

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

* nits

* nits

* nits

* nits

* fix: defer draft persist-back past the batch so discard sticks first click

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

* feat: band-reserving tidy-tree asset graph layout with join breakpoints

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

* fix: route skip-layer and long graph edges around occupied columns

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

* fix: seed s3 template outputs with canonical leading-slash paths

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

* all

* feat: bundle data-pipeline drafts into the DB-backed user draft system

Pipeline drafts were browser-only (localStorage `pipeline-<folder>`), so they
didn't sync across devices, weren't server-visible, and never showed in the
drafts list. Store them instead as one per-user `draft` row of a new
`data_pipeline` kind, keyed at the folder (`f/<folder>/data_pipeline`), holding
the same `{ drafts, activeDraftPath }` bundle.

Stage 1 — backend kind: add `data_pipeline` to DRAFT_KIND (migration) and
`UserDraftItemKind` (deployed_table=None, private). The list/update handlers
and folder-path access check already cover a backing-table-less kind.

Stage 2 — sync: add `GET /drafts/get_own/{kind}/{path}` so an editor with no
deployed-overlay GET can load its own draft. The pipeline page now hydrates
from the DB on mount (one-time localStorage import for in-flight drafts) and
persists via UserDraftDbSyncer (debounce + optimistic-concurrency), keeping a
localStorage crash mirror.

Stage 3 — surface: the drafts review page renders the bundle as a "pipeline"
row that opens `/pipeline/<folder>` (open-only; excluded from bulk deploy).

Verified end-to-end in-browser: DB-seeded draft hydrates to "Edit (1)", edits
persist back, and the row shows with Open pipeline / Discard.

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

* feat: pipeline Activity panel grouping, run↔graph highlight, deploy-conflict handling

Activity panel (view mode):
- Group cascade runs by the connected component of the asset-dispatch graph
  (new GET /jobs/asset_dispatch_edges over the dispatch_event table, incl.
  join_pending inputs), headed by the earliest originating run + its trigger,
  with a "+N" chip for joins fed by multiple triggers.
- Success/failure count histogram with drag-to-filter brushing, an always-on
  time axis + per-bar tooltips, a Reset, and Last hour/24h/48h/7/30/90d ranges.
- Node run-count/status badges now derive from the same merged historic+live
  events the panel shows (previously session-only).

Run ↔ graph highlight:
- Hovering a run row (or a group header → the whole cascade) rings the
  node(s), animates their incident edges, and borders the adjacent assets in
  the edge hue (blue write / gray read); expanding a run pins a soft-blue ring.
- Switching edit→view re-surfaces the Activity feed.

Deploy:
- Live-content autosave for the open pipeline draft + an autosave indicator.
- Re-saving a script now chains off the hash just created instead of a stale
  parent_hash (fixes the "lineage must be linear" error on a second save), and
  a genuine concurrent deploy opens a keep-mine / view-latest conflict modal.

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

* fix: pipeline editor badge requires asset-parse, not just main-function parse

A pipeline script's asset lineage is load-bearing — a deploy that can't parse
assets silently records no edges. The editor "parsable" dot only reflected
inferArgs (the main function), so a body the asset parser rejects (e.g. a
trailing `/////` in DuckDB) still showed green and deployed with empty lineage.

ScriptEditor gains `requireValidAssets` (set by the pipeline pane); when on, the
EditorBar badge is green only if BOTH the main function and inferAssets parse,
with the tooltip distinguishing "Main function not parsable" / "Assets not
parsable" / "Parsable".

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

* feat: route asset-graph edges around nodes that sit in their path

Edges could draw straight through an unrelated node (a join fan-out or long
cross-component edge), making it ambiguous whether that node shared the input.
AssetGraphEdge only saw its own endpoints, so it could only detour the
near-vertical same-column skip case.

The canvas now (once per layout, O(edges × nodes) — no per-frame cost) samples
each edge's straight run against every non-incident node center and, on a
crossing, passes a clear gutter lane to the edge via `data.detourX`;
AssetGraphEdge routes the rounded-orthogonal detour through it. Verified: 0
edge↔node box crossings on the orders pipeline.

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

* fix: deploy pipeline drafts with freshly-inferred assets, not a stale snapshot

"Save all" spread `...draft.script` into createScript, which carries a `assets`
snapshot that isn't refreshed when the body is edited. So a renamed/removed
output (e.g. an old `CREATE TABLE exciting_en32z9` later changed to
`exciting_880909`) was re-deployed as a phantom write edge and lingered as an
orphan asset on the graph — shown with no producer, and shifting position on
click as the graph re-derived.

saveDraft now re-runs inferAssets on the current body and passes the result as
`assets`, overriding the snapshot — mirroring the per-pane save. The backend
clears+reinserts from the sent set, so a re-deploy drops the stale rows.
Verified: deploying with the fresh asset set removes the orphan from the graph.

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

* fix: collect upstream reads from CTAS and CREATE VIEW in SQL asset parser

`CREATE TABLE x AS SELECT … FROM y` (and `CREATE VIEW`) recorded only the
write to x — the source read of y was silently dropped. Table-level reads are
gathered in the `Statement::Query` arm via handle_table_with_joins; the generic
table-factor visitor only picks up read-functions and string literals, not
plain `FROM <table>` references. The AS-query of a CTAS isn't a
`Statement::Query`, so its FROM tables were never walked. On the pipeline
canvas this meant a `datatable://…` upstream consumed by a CTAS step showed no
read node/edge — the step looked like it produced its output from nothing.

Factor the Query arm's read collection into handle_query_reads and call it from
the CreateTable (when it has an AS-query) and CreateView arms, balancing the
cte_name_stack push in post_visit_statement. Updated the drop_then_create test
(which had pinned the old drop-the-read behavior) and added CTAS + CREATE VIEW
read coverage. Verified against the rebuilt asset wasm: the live editor now
infers the read.

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

* update

* updates

* refactor: dedup asset-graph code, squash migrations, drop artifacts

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

* perf: gate asset dispatch on a cached per-workspace producer set

Cache the producer-path→writes map per workspace and invalidate it from the asset-clear paths via the notify_event polling system, so a top-level script/preview completion that isn't an asset producer costs an in-memory lookup instead of a per-completion query.

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

* fix: remove dead unquote fn that failed backend check under -D warnings

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

* fix: green the frontend check (pin published wasm-asset, fix type errors)

Pin windmill-parser-wasm-asset to the published 1.728.1 (was a file: link to a gitignored, CI-unbuilt pkg-asset). Exclude test files from svelte-check (the parity test reads a backend fixture via node:fs, which the browser app tsconfig has no @types/node for; vitest still runs them). Fix pre-existing branch type errors: drop the unsupported 2nd getScriptByPath arg, cast script.schema to Schema for inferArgs, coerce has_preprocessor to a definite boolean, and wrap the cancelJob handler so it isn't possibly-undefined.

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

* feat: move pipeline partition resolution to ee-private (free-CE)

Partition resolution becomes a private module (partition_ee in windmill-ee-private, hidden from the public repo) with an OSS no-op fallback (partition_oss); call sites resolve via the aliased windmill_common::partition. Not enterprise-gated — free to run in CE. Bumps ee-repo-ref to the ee branch carrying partition_ee. Verified building in default, private, and private,enterprise (offline). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: move asset-cascade join/debounce/retry to ee-private (free-CE)

Join barrier, debounce, and retry become the private windmill_queue::cascade module (cascade_ee in windmill-ee-private); OSS gets cascade_oss no-op fallbacks (plain OR fan-out). Core cascade stays public. Bumps ee-repo-ref. Verified default/private/private,enterprise. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: skeleton enterprise pipeline freshness + backfill (TODO, ee-private)

Gated windmill_common::pipeline_advanced (private; pipeline_advanced_ee) with OSS fallback; entry points return a clear not-implemented error. Deploy surfaces a TODO when a script declares // freshness. Bumps ee-repo-ref. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: repair asset_trigger_dispatch test after cascade carve-out + cache its queries

Stage-2 moved reap_stale_join_slots to windmill_queue::cascade; update the integration test's import. Also commit the test's sqlx query cache (was never prepared with --tests, so SQLX_OFFLINE cargo test failed pre-existing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: invalidate producer-cache in asset dispatch tests (mirror deploy)

The tests seed asset rows directly and run no notify poller, so the per-workspace producer cache went stale across tests → 0 dispatched. Clear it at the seed point, as a deploy would via notify_event. All 8 asset_trigger_dispatch tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to ba677ea142011462ad4dfe77e8375a6dd274cdef

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

Previous ee-repo-ref: 925c350cff55d3ea738d9e2e4098d9ce4bdda418

New ee-repo-ref: ba677ea142011462ad4dfe77e8375a6dd274cdef

Automated by sync-ee-ref workflow.

* test: disable producer cache in asset dispatch tests (isolated-DB safe)

The .remove(WS) approach still raced: #[sqlx::test] gives each test its own DB but they share one workspace id, so the WS-keyed process-global cache clobbered across DBs under concurrent threads. Add an ASSET_PRODUCER_CACHE_DISABLED test hook and set it in the tests so every dispatch reads its own DB. 8/8 pass at --test-threads=10. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: replace asset-cascade depth cap with cycle detection

The hardcoded MAX_CHAIN_DEPTH=5 truncated legitimate deep pipelines (silently — the check returned before event logging). Replace it with per-edge cycle detection: carry the producer lineage in trigger.chain and skip only a subscriber already in the chain, recording a visible cycle_detected dispatch_event. Acyclic pipelines of any depth now cascade fully; a high MAX_CHAIN_LEN backstop guards against runaway. Tests + UI label updated; 8/8 pass at --test-threads=10.

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

* docs: update dispatch_event reason examples (depth_cap → cycle_detected)

Comment-only; the migration is idempotent and already in the potentially_stale self-heal list, so the checksum change re-applies cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: park cascade retry (P1 dead-end) + clear stale script_triggers on rename

Two deploy-path fixes:
- Retry is parked: a retried subscriber is wrapped in a SingleStepFlow, whose run is a flow step and ineligible for asset dispatch, so it would silently dead-end the cascade (P1). Stop persisting retry to script_trigger and warn at deploy; TODO(pipeline-retry) to re-enable once dispatch handles flow-wrapped producers. (Dispatch plumbing kept + still tested via direct seeding.)
- Rename leaves stale script_trigger rows: clear was keyed on ns.path only, so old-path '// on' edges lingered and could trigger a script later recreated at that path. Also clear the old path on rename (assets already handled via the parent-hash clear).

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
Co-authored-by: hugocasa <hugo@casademont.ch>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Arnaud <31803803+Araden14@users.noreply.github.com>
Co-authored-by: Diego Imbert <diego@windmill.dev>
Co-authored-by: centdix <40307056+centdix@users.noreply.github.com>
Co-authored-by: Diego Imbert <70353967+diegoimbert@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Aldrin Jenson <aldrinjenson@gmail.com>
Co-authored-by: centdix <centdix@users.noreply.github.com>
2026-06-18 18:09:02 +02:00
hugocasa e80c62b958 docs(cli): improve generate-metadata guidance, fix description parser (#9635)
* docs(cli): improve generate-metadata guidance, fix description parser

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

* docs(cli): surface dependency version bumps after generate-metadata

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

* docs(cli): explain generate-metadata scope, import cascade, and --dry-run troubleshooting

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-17 14:08:50 +00:00
hugocasa 4e9e0c024b feat(cli): add --yes, --secret/--no-secret and --description to variable add (#9548)
* feat(cli): add --yes, --secret/--no-secret and --description to variable add

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

* test(cli): cover variable add create/update flag semantics

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

* fix(cli): warn on secret downgrade in variable add and pin preserve semantics in test

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:22:46 +02:00
hugocasa 5bdc4f83ce feat(cli): improve agent prompts/skills and workspace fork workflow (#9531)
* feat(cli): improve agent prompts/skills and workspace fork workflow

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

* fix(cli): refuse fork --from-branch rename of a base branch

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

* refactor(cli): auto-detect fork branch workflow, drop rt.d.ts refresh and legacy-name warning

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

* docs(skills): reconcile raw-app generate-metadata stance (agent offers+runs)

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

* docs(skills): agent runs all CLI commands, gated on intent not on user typing them

Extends #9467's safe-vs-destructive model: the agent runs consequential commands (sync push, generate-metadata) itself too, gated on explicit user intent rather than handed to the user to type. The explicit-intent rule is the safeguard; an approval prompt is treated as a possible backstop, not assumed (auto-approve/headless runs have none).

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

* Revert "docs(skills): agent runs all CLI commands, gated on intent not on user typing them"

Reverts 9225e1759b. That commit over-reached: #9467 already established the safe-vs-destructive split, and the targeted item-6 fix already removed the passive "tell the user they can run <safe next step>" phrasing. The blanket "agent runs everything" principle pushed deploys to be more eager and carried a wrong "permission layer prompts for approval" claim (untrue in auto-approve/headless mode). Keep deploys conservative.

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

* feat(cli): default fork workspace name/id to the current branch when renaming it

When 'wmill workspace fork' converts the current working branch into the fork branch, default the fork's name and id to that branch (sanitized to a slug, since branch names can contain '/'). Interactive: the prompt is pre-filled (enter to accept); non-interactive (--yes): used automatically. Adds a unit test for the slug derivation.

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

* fix(cli): address fork review — guard fork-branch rename, cap+validate fork id

Two P2s from review:
- --from-branch refused when the current branch is already a fork branch (would detach the existing fork by renaming its branch).
- fork id slug capped to 42 chars (backend max 50 incl. wm-fork- prefix); auto-derived id is slugged; full id validated client-side before existsWorkspace/datatable cloning so an invalid id fails fast instead of leaving cloned Postgres databases behind.

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-12 00:16:16 +02:00
hugocasa 220cd35cf7 feat: support $f/ and $u/ import path aliases for scripts (#9378)
* feat: support $f/ and $u/ import path aliases for scripts

$f/ and $u/ are local-friendly aliases for the absolute workspace
import paths /f/ and /u/. Unlike the /-prefixed form (which local tools
treat as a filesystem-root path), the $-prefixed form is a bare specifier
that can be remapped via tsconfig paths / Deno import maps, so the same
import resolves on the Windmill worker and in a local editor.

- worker: recognize $f//$u/ in the Deno import map and both Bun loaders
- dep-map/parser: normalize $f/->f/, $u/->u/ for lockgen + dep tracking
- cli: emit $f/$u path aliases in generated tsconfig.json / deno.json
- frontend: ATA + Monaco paths resolve $f//$u/ type hints in the editor

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

* feat(cli): split generated tsconfig into managed + user file with refresh command

Mirror the AGENTS.cli.md/AGENTS.md prompts model for the IDE tsconfig so the
recommended settings can evolve without ever clobbering user customizations:

- tsconfig.wmill.json: wmill-managed, always refreshed, holds recommended
  compilerOptions incl. the $f/$u path aliases (Deno: import_map.wmill.json)
- tsconfig.json: user-owned, created once, just extends the managed file;
  warn (never auto-edit) when an existing one doesn't reference it
- add 'wmill refresh tsconfig'; init generates it unconditionally (no longer
  gated behind resource-type namespace / a bound workspace)
- regenerate CLI guidance docs for the new subcommand

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

* fix(cli): address PR review on $f/ tsconfig generation

- handle existing deno.jsonc so we don't shadow it with a new deno.json
  (P1 identified by cubic)
- fix the bun-types hint that pointed users at the managed do-not-edit
  tsconfig.wmill.json; tell them to install + re-run 'wmill refresh tsconfig'
- document the .ts-extension-only local-resolution limitation (cross-flavor
  .bun.ts/.deno.ts/.fetch.ts scripts won't resolve in a local editor)

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

* feat(cli): warn when a project's tsconfig isn't wired to tsconfig.wmill.json

Mirror the prompts freshness check for the managed tsconfig so users with an
existing setup actually discover they're missing $f//$u/ resolution:

- embed a version hash in tsconfig.wmill.json (excludes the env-dependent
  bun-types 'types' entry so it doesn't false-positive)
- add warnIfTsconfigStale to the main.ts freshness hook, gated identically to
  the prompts check (skips init/refresh/help/version). When a tsconfig.json
  exists it warns one line (stderr) if the managed file is missing, not
  referenced via extends, or out of date; silent for non-TS projects.

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

* refactor(cli): make tsconfig setup equivalent to prompts (auto-wire + stale-only)

Unify the two managed-file systems so they behave identically:

- auto-wire an existing unlinked tsconfig.json/deno.json on init/refresh
  (add extends / importMap; merge into an array extends), instead of only
  warning. Parses JSON and falls back to a warning when it can't round-trip
  (JSONC comments, or a conflicting deno imports/importMap) — never corrupts.
- narrow warnIfTsconfigStale to stale-only, gated on the managed file
  existing, exactly like warnIfPromptsStale: it no longer nags about a
  missing or unlinked tsconfig.json, so a deliberately-custom/unlinked setup
  stays silent and a not-yet-initialized project isn't bothered.

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

* fix(cli): place tsconfig.wmill.json first in extends to preserve user base config

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

* feat(cli): migrate legacy tsconfig and require consent for custom configs

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

* refactor(cli): align prompts wiring to the same consent model

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

* chore(cli): bump windmill-parser-wasm-ts to 1.714.0 for $f/ $u/ aliases

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

* fix(worker): resolve $f/ and $u/ in deno lock generation

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

* test: narrow relative-imports lock-gen guard to deno import-map failure

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

* chore(cli): sync bun.lock with windmill-parser-wasm-ts 1.714.0

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

* fix(cli): warn when a custom tsconfig's paths would shadow $f/ $u/ aliases

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-03 11:58:12 +00:00
Ruben Fiszel e356bb1f5d fix(cli): make encryption key push non-interactive-safe + add --skip-reencrypt-on-key-change (#9402)
When encryption_key.yaml changes and is pushed via `wmill sync push`,
pushWorkspaceKey prompted interactively to confirm re-encrypting the
remote secrets with the new key. That prompt ignored `--yes` and had no
TTY guard, so a CI/non-interactive push that included the key would
block (or behave undefinedly) on the prompt.

Thread a key-push options object (non-interactive flag + explicit
re-encryption choice) through pushObj into pushWorkspaceKey:

- Non-interactive (`--yes` or no TTY) and no explicit choice: skip the
  prompt and default to re-encrypting all remote secrets with the new
  key (matches the interactive default), preserving their plaintext
  values.
- New `--skip-reencrypt-on-key-change` flag (and the
  WMILL_NO_REENCRYPT_ON_KEY_CHANGE=true env var for CI) opt out of
  re-encryption — only safe when the remote ciphertexts are already
  encrypted with the new key (e.g. workspace/instance migration).
- Interactive behavior (TTY, no `--yes`) is unchanged.

Regenerates system_prompts for the new option and adds unit tests for
the no-op, re-encrypt-by-default, flag-skip, and env-skip paths.

Fixes WIN-2005

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 07:11:29 +00:00
hugocasa 979b086b08 refactor(cli): fold flow test-step into flow preview --step (#9330) 2026-05-26 11:21:54 +00:00
hugocasa 36f574ff95 feat(cli): add object-storage commands and flow test-step (#9326)
* feat(cli): add object-storage commands and flow test-step

* docs(cli): clarify flow test-step doesn't recurse into aiagent tools

* fix(cli): correct failure step id in docs, handle bare flow.yaml path
2026-05-26 10:43:43 +00:00
hugocasa e0ffea2deb feat: add wmill job rerun subcommand (#9275)
* feat: add wmill job rerun subcommand

* feat: add wmill job restart subcommand for flow restart-at-step
2026-05-22 08:31:21 +00:00
hugocasa 1ba8ed8abd feat(cli): add wmill init prompts and custom override slot (#9266)
* feat(cli): add `wmill init prompts` and custom override slot

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

* refactor(cli): replace init prompts with refresh prompts + AGENTS.md/AGENTS.cli.md split

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

* feat(cli): dedupe claude skills via @-includes and add prompts freshness check

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

* refactor(cli): drop migration-choice flags from `refresh prompts`

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

* docs(cli): add 'Running and previewing local changes' section to AGENTS.cli.md

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

* fix(cli): write full skill content to .claude/, drop @-include wrapper

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

* feat(cli): reconcile CLAUDE.md the same way as AGENTS.md

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

* fix(cli): address PR review nits — argv parsing, lazy import, comment detection, error propagation

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-05-21 10:44:15 +00:00
Diego Imbert 28c8b5c60f feat: CLI datatable serve / psql (#9267)
* feat(cli): add datatable list and run commands

* feat(cli): render datatable query results as a table

* feat(cli): serve datatables as a postgres-wire endpoint

* feat(cli): add 'datatable psql' to launch psql against the proxy

* feat(cli): route datatable serve by client-supplied database name

* override database list + password option

* fix: support extended queries in datatable serve

* fix: correct cloud size threshold log and parse CLI descriptions with parens/trailing comma

* refactor: extract raw_output envelope encoding into pg_raw_output module

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-05-20 16:56:50 +00:00
Diego Imbert 1d04904a47 feat(cli): add datatable and ducklake list/run commands (#9257)
* feat(cli): add datatable list and run commands

* feat(cli): render datatable query results as a table

* feat(cli): add ducklake list/run, scope --name to run subcommand
2026-05-20 12:50:39 +00:00
Ruben Fiszel 01bad16c0c feat: add wmill protection-rules pull/push CLI commands (#9240)
* feat: add wmill protection-rules pull/push CLI commands

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

* refactor: use directional keys for protection-rules pull --json diff

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

* fix: address review — exit non-zero on failure, resolve override workspace key

- failure paths in pull/push now exit 1 so CI/scripts detect failed reconciles
- --override writes under the resolved workspace key (findWorkspaceByGitBranch),
  not the raw branch, so gitBranch-mapped entries aren't left inert
- pull --replace clears a shadowing protectionRules override so top-level takes
  effect (was an infinite pull --diff loop)
- push reports applied create/update/delete counts on partial failure and warns
  loudly when an empty list would wipe all backend rules

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

* fix: address review — dry-run pull --diff no longer writes; --promotion coherent

- pull --diff returns before the no-wmill.yaml bootstrap, so a dry run never
  creates/mutates wmill.yaml
- pull --promotion now writes/clears the promotion target's promotionOverrides
  (the same block getEffectiveSettings reads), instead of the current branch's
  regular overrides — read and write are now coherent

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

* refactor: move protection rules to a per-workspace protection-rules.yaml

Replaces the wmill.yaml/SyncOptions integration (top-level + overrides +
promotionOverrides) with a dedicated protection-rules.yaml keyed by workspace
name. This removes the getEffectiveSettings layering that caused the override
shadowing / promotion-coherence / dry-run bugs entirely.

- protection-rules.yaml: { <workspace>: ProtectionRuleEntry[] }, keys must
  match wmill.yaml 'workspaces' (source of truth for backend id/baseUrl/token)
- commands reduced to: pull/push [workspace] | --all, with --dry-run
- per-workspace auth resolved via tryResolveBranchWorkspace + setClient
- push remains a full reconcile (create/update/delete) with delete confirm,
  empty-list wipe warning, partial-failure reporting, non-zero exit on failure
- conf.ts reverted to main; SyncOptions no longer carries protectionRules

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

* fix: address review — honor explicit --base-url/--token in protection-rules

configureClientForWorkspace bypassed the credential precedence other commands
use: explicit --base-url/--token now work for stateless CI (no stored profile
or wmill.yaml baseUrl needed), and an explicit --token overrides a stored
profile's token. The backend workspace id still derives from the wmill.yaml
mapping (feature invariant).

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

* fix: address cubic review — consistent status on partial --all failure

cubic found that pull/push reported success:true while exiting non-zero on
partial --all failures, and that the push command description was missing from
the generated CLI docs.

- pull/push now report success:false + partialFailure:true (and exit 1) when
  any --all workspace fails; success:true only on full success
- .description() calls use single string literals (not + concatenation) so
  system_prompts/generate.py parses them; regenerated CLI docs

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

* fix: address review — --json-output must emit only JSON on stdout

Codex flagged that workspace resolution (tryResolveBranchWorkspace's log.info)
and push's empty-list delete warning print to stdout before the JSON payload,
breaking machine callers. Silence human logs via log.setSilent(true) as the
first action when --json-output is set (before readConfigFile / resolution);
log.error still goes to stderr.

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-05-19 17:40:34 +00:00
Ruben Fiszel 43b18006f3 feat(cli): auto-infer args for wmill app push (#9091)
Run `wmill app push` from inside an app folder (e.g. `f/foo/my_app.app/`)
with no args. The local path defaults to CWD, and the remote path is
derived from CWD relative to `wmill.yaml`, with `.app`/`.raw_app`/
`__app`/`__raw_app` suffixes stripped. Either, both, or neither
positional argument can be passed.

Also resolves `file_path` against the user's original CWD before
`resolveWorkspace` may chdir to the wmill.yaml root, so a relative
`file_path` argument is interpreted from where the user invoked the
command (previously it could resolve against the wrong directory).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 08:38:47 +00:00
hugocasa bc527fd929 feat(cli): add --parallel flag to generate-metadata (#9074)
* feat(cli): add --parallel flag to generate-metadata

* fix(cli): validate --parallel input and harden flush ordering
2026-05-08 04:39:47 +00:00
Ruben Fiszel 66c9063919 feat(cli): add --as-superadmin flag to workspace list-remote (#9043)
* feat(cli): add --as-superadmin flag to workspace list-remote

Wires the existing /workspaces/list_as_superadmin endpoint into the
CLI so superadmins can enumerate every workspace on an instance from
CI tooling, not just the ones the calling user is a member of.

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

* chore: regenerate CLI prompts and document the regen step

Adds an AGENTS.md note pointing to system_prompts/generate.py so future
CLI command edits keep the agent-facing CLI docs in sync, and
regenerates the auto-generated outputs for the new --as-superadmin flag.

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-05-05 14:34:49 +00:00
hugocasa d60dd745e4 feat(forks): handle triggers and schedules in workspace forks (#8976)
* feat(forks): strip operational state from triggers/schedules on git-sync export

When the source workspace is a fork (`wm-fork-*`), the tarball export now
omits `mode` from triggers and `enabled` from schedules. The trigger update
handler also preserves the existing DB `mode` when both fields are absent
from the request, instead of falling back to the BaseTriggerData default.

This prevents a fork's git-sync round-trip from flipping the parent
workspace's enabled/disabled state when a merge applies the fork's YAML
back to main.

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

* feat(forks): opt-in fork_triggers flag clones triggers/schedules disabled

Adds `workspace.fork_triggers` (default false) and a matching field on
CreateWorkspaceFork. When the user opts in, fork creation also runs
clone_triggers_and_schedules: every row in schedule and the ten
*_trigger tables is copied to the fork with mode='disabled' /
enabled=false. Listener identifiers (group_id, replication_slot_name,
subscription_name, …) are copied verbatim — the runtime suffix that
prevents the fork from competing with the parent ships in a follow-up
PR.

native_trigger is intentionally skipped: those triggers manage external
webhook state we don't want duplicated.

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

* feat(forks): warn before enabling triggers/schedules that conflict with parent

set_trigger_mode and schedule's set_enabled now check whether the parent
workspace has the same path actively enabled. If so, the call is rejected
with a `fork-conflict:<kind>:<parent_id>` error unless the request includes
`force=true`. The frontend interprets the prefix to surface a confirm-to-
proceed dialog.

This is the placeholder safety net until the Phase 3 listener-suffix work
removes the conflict for the namespaceable kinds (Kafka/MQTT/NATS/Postgres/
Azure/GCP-CreateNew). For SQS, GCP-Existing, and schedules — where there's
no namespacing fix — the warning is the durable solution.

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

* feat(forks): UI: opt-in clone-triggers checkbox + confirm-on-fork-conflict

Adds the user-facing surface for the fork-trigger work:

- CreateWorkspaceInner: new "Clone triggers and schedules" toggle in the
  fork-creation dialog (default off). Sends fork_triggers in the request.

- forkConflict utility: detects the `fork-conflict:<kind>:<parent_id>`
  error string from the backend, shows a confirm() dialog explaining
  why the action is blocked, retries with `force: true` if accepted.

- Wires withForkConflictRetry into every trigger setMode and the
  schedule setEnabled call, both in the per-kind editor components and
  the +page.svelte list views (HTTP, websocket, kafka, NATS, SQS, MQTT,
  GCP, Azure, Postgres, email, schedule).

OpenAPI spec gains the `force` field on each setmode/setenabled body.

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

* feat(forks): CLI --fork-triggers flag, fork-trigger docs, skill update

- Adds --fork-triggers boolean to wmill workspace fork; passes
  fork_triggers through to the create_fork API call.
- New docs/fork-triggers.md describing the model end-to-end (default,
  opt-in clone, merge-direction filter, conflict warning, future
  runtime-suffix work).
- Updates the adding-a-trigger SKILL.md to mention the fork-export
  ignore-keys participation and the clone_triggers_and_schedules
  block that new trigger kinds must extend.

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

* chore: regenerate sqlx offline query cache for fork-trigger SQL

* fix(forks): replace browser confirm() with ConfirmationModal for fork conflict

The fork-conflict warning previously used the browser's native confirm()
which doesn't match Windmill's design system. Switches to a singleton
ConfirmationModal mounted at the (logged) layout root, driven by a new
forkConflictModal store. The withForkConflictRetry helper now sets the
store and awaits the user's choice via a Promise, instead of blocking
on window.confirm.

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

* fix(forks): filter unchanged triggers in merge UI, add diff view, surface parent-only ones

The fork merge UI listed every trigger from the fork as a deployable item
regardless of whether it differed from the parent — so a fork created with
fork_triggers=true (which clones triggers in disabled state, otherwise
identical) showed every trigger as a "Fork-only" change. The 'Update
current' tab also missed triggers newly created in the parent that the
fork hadn't pulled yet.

This refactor:

- fetchAllTriggers now lists both fork and parent in parallel for each
  trigger kind, then merges by path.
- Computes a per-trigger `changeKind` (new / modified / deleted-in-source)
  using a JSON comparison that strips runtime + fork-local fields
  (mode/enabled/server_id/last_server_ping/edited_at/edited_by/etc.) so
  the disabled-on-clone difference doesn't show up as a change.
- Filters the trigger items in deployableItems by the current direction:
  Deploy mode shows fork-side new/modified, Update mode shows parent-side
  new/modified.
- Replaces the always-on "Fork-only" badge with proper New/Modified
  badges and surfaces a Diff button (modal Drawer + Monaco DiffEditor)
  for modified triggers — the diff strips the same ignored fields so
  users see only the meaningful config differences.

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

* fix(forks): always clone triggers/schedules disabled, drop opt-in flag

Disabled triggers and schedules are inert — no listener attaches, no cron
fires — so cloning them by default is safe by construction. Drops the
fork_triggers opt-in flag introduced earlier in this PR:

- Drops workspace.fork_triggers column (migration removed)
- Removes fork_triggers from CreateWorkspaceFork (API + OpenAPI)
- Removes the conditional in create_workspace_fork — clone always runs
- Removes the toggle from the fork-creation dialog
- Removes --fork-triggers from `wmill workspace fork`
- Updates docs/fork-triggers.md and adding-a-trigger SKILL.md

The merge UI continues to exclude triggers from the deploy/update default
selection, so a routine merge from a fork doesn't accidentally push
trigger config the user hasn't intentionally changed.

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

* fix(http-triggers): scope route exists check by workspace, skip non-workspaced clones in forks

The non-CLOUD branch of `route_path_key_exists` self-excluded by trigger
path alone, which silently masked cross-workspace collisions once forks
started cloning trigger rows verbatim. Tighten it to exclude only the
exact `(workspace_id, path)` row.

Fork creation also now skips non-workspaced HTTP triggers — their URL
has no workspace prefix, so a clone collides with the parent at the
matchit router (which silently drops one of two duplicates) and there is
no namespacing escape hatch. The clone copies all rows when CLOUD_HOSTED
or HTTP_ROUTE_WORKSPACED_ROUTE forces every route workspaced regardless.

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

* fix(forks-ui): silent cancel on enable conflict, clean up trigger rows in compare view

forkConflict helper now returns undefined when the user dismisses the
modal instead of throwing, so the redundant 'Cannot enable: undefined'
toast no longer appears.

CompareWorkspaces trigger rows now mirror the script row layout: drop
the redundant Disabled badge and the Trash/Details buttons (both belong
on the dedicated trigger pages, not in the deploy/compare view); pass
triggerKind through so RowIcon picks the right kind-specific icon; move
extraLabel into the summary line; replace the yellow Modified badge
with the same green ↗ ahead / blue ↘ behind treatment scripts use.

Trigger diff drawer: switch JSON → YAML for parity with DiffDrawer, fix
zero-height monaco render with className=!h-full, drop the redundant
Original/Modified label banner above the diff.

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

* fix(email-trigger): scope local_part exists check, skip non-workspaced clones in forks

Mirrors the HTTP route fix for the email-trigger non-CLOUD `email_exists`
check (in EE) which had the same path-only self-exclusion bug, and the
fork clone of `email_trigger` rows which copied non-workspaced
`local_part` verbatim. Skip non-workspaced rows in the clone unless the
instance is CLOUD_HOSTED (where lookup is workspace-scoped natively).

EE companion change in windmill-trigger-email/src/handler_ee.rs.

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

* chore: update ee-repo-ref to 78512dd73b4a1c9f70574cff863374179e3a621b

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

Previous ee-repo-ref: 1ac77f50747b58e720a11162dfd309bc252a24ab

New ee-repo-ref: 78512dd73b4a1c9f70574cff863374179e3a621b

Automated by sync-ee-ref workflow.

* fix(forks): always-warn on parent row, kind-specific modal copy, cancel-aware toggles

- Conflict check now fires whenever the parent has the path (regardless of
  parent's mode), since the cloned upstream identifier is shared by
  construction; closes the Postgres slot-takeover gap when the parent is
  disabled. Schedule's set_schedule_enabled gets the same treatment.
- Skip the warning entirely for HTTP and Email via a new
  TriggerCrud::FORK_CONFLICT_ON_ENABLE const — both kinds are workspace-
  scoped at runtime so cloned rows can't collide with the parent.
- Modal copy branches by failure family: split-events (Kafka/NATS/MQTT/SQS/
  GCP/Azure), duplicate-firing (Websocket/Schedule), slot-takeover
  (Postgres). Generic fallback for unknown kinds.
- withForkConflictRetry now returns boolean (true=committed, false=
  cancelled). TriggerModeToggle reuses its existing innerTriggerMode local
  state via a function binding for the regular Toggle, snapping back to
  the prop when onToggleMode signals a cancel — needed because the native
  bind:checked diverges from the parent's prop after a click and Svelte's
  reactivity won't re-push a same-valued prop down. Schedule list page
  uses {#key} on a reset version since it renders Toggle directly.
- Editor inners revert mode = previousMode on cancel; list pages skip the
  re-fetch (loadTriggers/loadSchedules) on cancel to avoid pointless
  network traffic and the schedule "Job stats loading..." flash.
- Drop withForkConflictRetry from HTTP and Email editors + list pages
  since the backend never emits the conflict for those kinds.

* fix(forks-ui): widen onToggleMode types, scope schedule toggle reset by path

- TriggerEditorToolbar and TriggerSuspendedJobsModal forwarded
  onToggleMode as `(mode) => void`, dropping the new boolean return so
  any caller wired through them would silently no-op the cancel-revert.
  Match the wider TriggerModeToggle signature.
- Schedule list page used a single resetVersion counter for every row's
  {#key}, so cancelling on any one schedule remounted every <Toggle> on
  the page. Switch to a per-path Record<string, number> bumped only for
  the affected row.

* chore: bump ee-repo-ref to c3a4553 (email FORK_CONFLICT_ON_ENABLE override)

* fix(forks): include Suspended in conflict gate, use parent_workspace_id for fork detection

Three fixes from the Claude review on PR #8976:

- Suspended mode still attaches the listener (it just pauses auto-run of
  queued jobs); two suspended fork+parent listeners would still split
  Kafka events / share a PG slot. Gate set_trigger_mode on
  `mode != Disabled` instead of `mode == Enabled` so Suspended also
  surfaces the warning.
- workspaces_export.rs::fork_*_ignore_keys keyed off the wm-fork-* prefix
  while set_trigger_mode and set_schedule_enabled key off
  parent_workspace_id. Switch the export filter to query
  parent_workspace_id once at the top of tarball_workspace and pass
  is_fork through. The column is the contract; the prefix is a
  creation-time naming convention that could in principle drift.
- TriggerModeToggle's suspend-dropdown action reassigned the non-bindable
  `triggerMode` prop instead of the local `innerTriggerMode` mirror,
  leaking inconsistent state if the dispatch was cancelled. Now writes
  to innerTriggerMode like the Toggle's on:change handler does.

* fix(cli): skip setScheduleEnabled when local YAML lacks `enabled`

Tarball export from a fork strips `enabled` from schedules so the
fork→parent git-sync round-trip can't flip the parent's operational
state. The CLI's pushSchedule called setScheduleEnabled whenever
`localSchedule.enabled != schedule.enabled`, which evaluates truthy
when local is undefined (fork-pulled YAML) and remote is true/false —
sending `{ enabled: undefined }` that serializes to `{}` and gets
rejected by the backend (`SetEnabled.enabled` is required).

Skip the call when `localSchedule.enabled === undefined` so a sync push
of fork-pulled YAMLs preserves the target's existing enabled state
instead of erroring out. Trigger updates were already safe — the
backend's update_trigger preserves `mode` when the request omits it.

* Revert "fix(cli): skip setScheduleEnabled when local YAML lacks `enabled`"

This reverts commit 23ba7e72fc.

* feat(cli): --force flag and friendlier error on fork-conflict for schedule enable

`wmill schedule enable foo/bar` against a fork whose parent has the same
path used to surface the raw `fork-conflict:schedule:<parent>` error
body. The CLI now:

- accepts `--force` to bypass the warning (mirrors the API field and the
  UI's "Enable anyway" confirmation),
- detects the `fork-conflict:` prefix on errors and prints a one-screen
  explanation pointing at --force instead of the raw body.

Disable doesn't trigger the warning (the gate fires only on transitions
to listener-attaching modes), so no flag there. Trigger enable/disable
isn't exposed as a standalone CLI command — sync push goes through
updateTrigger which has its own backend mode-preservation, so no
fork-conflict surfaces from the CLI for those.

* chore: regenerate cli-commands docs after adding --force to schedule enable

* chore: update ee-repo-ref to 967f961f0a88b027d894aebd03977181129477a8

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

Previous ee-repo-ref: c3a4553296473932e15392a06415dd7fb9aa6591

New ee-repo-ref: 967f961f0a88b027d894aebd03977181129477a8

Automated by sync-ee-ref workflow.

* fix(forks): address CI dead-code, claude/cubic review feedback

- backend: cfg-gate `fork_trigger_ignore_keys` to match its already-gated
  callsite. CI compiles with `-D warnings`, so the unused-fn under feature
  combos that disable all trigger crates was breaking check_oss/check_ee/
  cargo_test/test-linux/test-windows.
- cli: re-apply the `pushSchedule` undefined-skip (originally 23ba7e7,
  reverted in 4d172a1). Tarball export from forks strips `enabled`, so
  fork-pulled YAMLs that get sync-pushed back via `wmill schedule push`
  would otherwise serialize `{ enabled: undefined }` → `{}` and the
  backend's required `SetEnabled.enabled` rejects the body. Skipping
  preserves the target's existing flag, which is the round-trip-safe
  behavior. (`wmill workspace merge` extension to triggers/schedules is
  tracked in #9001 — until then sync push is the only CLI path.)
- TriggerModeToggle suspend-dropdown action awaits onToggleMode and
  resets `innerTriggerMode = triggerMode` on cancel, matching the Toggle
  on:change handler. Without this, dismissing the fork-conflict modal on
  a Suspend transition leaves the toggle stuck in 'suspended'.
- forkConflict: when a new modal opens with a previous resolver still
  pending, resolve the older promise to false. Avoids a dangling promise
  if the user clicks toggles on two rows in quick succession.
- schedules list: bump `toggleResetVersions[path]` on the
  permission-denied branch so the Toggle re-mounts back to the prop's
  `enabled` value. Without this, a user without write permission could
  click the toggle and have it stick visually flipped.
- docs/fork-triggers.md: switch the merge-direction filter description
  from `wm-fork-*` prefix to `parent_workspace_id IS NOT NULL` (matches
  the code after 4dd38fe). Drop the misleading "merge-direction filter
  strips identifier columns too" line in Future Work — the runtime
  suffix is applied at listener attach, the stored column never carries
  it, so no export filtering is needed there.

---------

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-05-01 20:55:24 +00:00
hugocasa 0b959b8ec6 feat(cli): wmill-lock.yaml auto-fill + --rehash-only + path-prefix dedup (#8978)
* fix(cli): canonical lockfile hashes + lock upgrade migration to v3

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

* fix(cli): use __app_hash subpath in rehash missing-entry check

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

* fix(cli): run sync pull lockfile auto-fill regardless of changes

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

* chore: regenerate system prompts for new lock and rehash-only commands

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

* fix(cli): address review feedback on lock upgrade

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

* fix(cli): drop v3 marker; always run fallback; fail-fast on unknown lockfile version

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

* fix(cli): drop yaml-round-trip legacy hash variant; recover via --rehash-only

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

* fix(cli): include legacy hash in script push staleness warning check

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

* revert(cli): drop canonical hash formula; keep raw-bytes hashing

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

* perf(cli): reuse change-tracker map for sync pull lockfile auto-fill

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

* fix(cli): address review feedback on rehash-only

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

* test(cli): pin lockfile hash + yaml format and cover regression cases

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

* test(cli): byte-stable snapshot tests for flow.yaml format

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

* test(cli): add app and script-metadata yaml snapshot fixtures

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

* fix(cli): address claude review on rehash-only

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

* refactor(cli): factorize script-path to remote-path derivation

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

* fix(cli): address claude + cubic review (dry-run mutation, rehash short-circuit)

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

* refactor(cli): make rehash a subcommand and factorize fs walks

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

* fix(cli): normalize line endings in yaml snapshot tests for windows ci

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

* fix(cli): address review feedback on rehash + auto-fill

- Flat-layout scripts now clearGlobalLock before rehash write so legacy
  ./-prefixed duplicates get cleaned up (matches flow/app behavior).
- Add MalformedLockfileError; sync pull auto-fill re-throws it alongside
  UnknownLockVersionError instead of silently warning + continuing.
- Document the legacy step-removal false-negative in
  isFlowDirectlyStale / isAppDirectlyStale and the categorizeLocalFiles
  ignore-filter invariant.

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-01 15:11:56 +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
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 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
Ruben Fiszel 60211c1d19 feat: folder default_permissioned_as rules for ownership defaults on deploy (#8801)
* feat: add folder default_permissioned_as rules for ownership defaults on deploy

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

* fix: remove unnecessary auth guard on default_permissioned_as — rules are advisory only

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

* chore: regenerate system prompts with new CLI commands

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

* fix: address CI review findings — TOCTOU, race condition, email validation, type coercion

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

* fix: add sqlx offline cache for test queries (fixes cargo_test CI)

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

* fix: address remaining review findings — incomplete request bodies, dead code, redundant import

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

* fix: address remaining review findings — full script fields, reactive stores, catch-all validation

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

* fix: app/schedule/trigger set-permissioned-as fetch remote first to avoid data loss

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

* fix: app set-permissioned-as avoid creating redundant app version

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

* feat: compact user/group toggle + select for folder default_permissioned_as rules

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

* feat: collapse default_permissioned_as section by default in folder editor

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

* feat: include default_permissioned_as in FolderFile CLI type for YAML round-trip

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

* fix: process folder.meta changes before items in push to apply new rules immediately

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

* fix: clone default_permissioned_as on fork/rename + add full lifecycle tests

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

* test: add no-op guarantee test — folder without rules behaves like before

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

* refactor: rename cliBehavior to syncBehavior — more accurate scope

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:14:07 +00:00
Ruben Fiszel 5b97092997 feat: unify CLI config to workspaces, deprecate gitBranches/environments (#8767)
* refactor: unify CLI config to workspaces, deprecate gitBranches/environments

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

* chore: update frontend examples and regenerate system prompts for workspaces config

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

* test: update test files to use workspaces config instead of gitBranches

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

* fix: handle --branch with --base-url correctly in sync pull/push

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

* feat: warn when --workspace overrides auto-detected branch or misses config entry

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

* feat: show reason why workspace was selected in log message

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

* docs: clarify specificItems file naming uses gitBranch as suffix

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

* refactor: rename branch-specific to workspace-specific, use workspace name as file suffix

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

* refactor: rename branch-specific to workspace-specific, add comprehensive integration tests

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

* refactor: simplify bind and init to be workspace-centric

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

* refactor: make bind/unbind interactive with --workspace and --branch flags

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

* refactor: make bind interactive with profile selection, workspace name, and optional branch

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

* refactor: init offers to bind workspace using same flow as wmill workspace bind

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

* fix: skip backend git-sync check in init when no workspace was bound

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

* fix: skip all API calls in init when no workspace was bound

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

* feat: log when RT namespace is skipped, offer to generate it after bind

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

* fix: warn when no workspace bound during init

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

* fix: init git-sync check uses bound workspace, not active profile

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

* fix: init uses selected profile directly, avoids re-resolving and duplicate prompt

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

* fix: init skips requireLogin, uses bound profile token directly

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

* feat: auto-pick or prompt workspace from config when no branch matches

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

* fix: show configured workspaces list and bind hint in resolution messages

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

* fix: cache bound profile to avoid duplicate profile selection prompts in init

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

* fix: hoist boundProfile scope, add 2 comprehensive integration tests covering all flows

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

* fix: rt.d.ts prompt defaults to no when file exists, better description

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

* fix: remove empty overrides from generated config, add specificItems hint

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

* fix: add inline comments for non-trivial fields, add overrides/promotionOverrides hints to bound workspaces

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

* chore: regenerate system prompts

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-09 19:46:34 +00:00
Ruben Fiszel 80c8e076fc cli nit 2026-04-08 16:16:34 +00:00
Ruben Fiszel 4c16877366 update sys prompts 2026-04-08 05:38:38 +00:00
centdix b960598431 fix: hide deprecated cli metadata commands (#8699)
* fix: hide deprecated cli metadata commands

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

* fix: simplify generate-metadata guidance

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-03 15:38:01 +00:00
Alexander Petric 6c3c971af5 feat: improve CLI flow log streaming and job inspection (#8644)
* fix: improve CLI flow log streaming, sub-job listing, and failure handling

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

* feat: add hierarchical flow status in job get and aggregated flow logs

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

* fix: remove duplicate ansi color hint in job logs output

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

* docs: update cli-commands skill with new job/flow features

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

* test: add integration tests for flow job inspection and log aggregation

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

* chore: remove internal friction discovery doc from branch

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

* docs: trim cli-commands skill to reduce context bloat

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

* docs: update job command descriptions and regenerate skills.ts

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

* chore: commit auto-generated files from system_prompts

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

* fix: address review comments on flow streaming and test assertions

- Move for-loop waiting logic outside --silent guard (Cubic #2)
- Break outer loop when for-loop module fails (Cubic #3)
- Strengthen test assertion: toContain("a") -> toContain("a: Generate data") (Cubic #1)

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

* fix: generator regex truncating descriptions with parentheses

The .command() regex used [^)]+ for the second arg, stopping at the
first ')' inside description strings like "(machine-friendly)".
Now matches quoted strings properly before falling back.

Fixes 6 truncated descriptions across job, flow, and script commands.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 18:22:18 +00:00
Ruben Fiszel c6ce3197a7 fix(cli): phantom diffs, flow safety, trigger DX, lint watch, error clarity (#8588)
* fix(cli): phantom diffs, flow push safety, error messages, digest stability

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

* fix(cli): differentiate stale vs missing metadata warnings on script push

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

* fix(cli): job list --limit off-by-one, deps push double error

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

* fix(cli): flow get shows nested steps, lint works on specific directories

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

* feat(cli): add lint --watch mode for continuous validation

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

* fix(cli): email trigger template missing local_part, trigger get shows all fields

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

* fix(cli): fix CI — flow push warns instead of failing, lint subdir detection

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 20:56:10 +00:00
Ruben Fiszel f40cdaf434 fix(cli): app push crash, lint path, push --message, run validation, history timestamps (#8585)
* fix(cli): app push crash, lint entry point, push --message, run arg validation, history timestamps

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

* fix(cli): update sqlx cache and fix second history query missing created_at

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

* chore(cli): regenerate system prompts after new CLI options

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:33:49 +00:00
Ruben Fiszel d29cb234db feat(cli): add job, group, audit, token commands and schedule enable/disable (#8581)
* feat(cli): add job, group, audit, token commands and schedule enable/disable

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

* chore(cli): regenerate system prompts after new commands

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

* fix(cli): address PR review feedback

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

* chore(cli): regenerate system prompts after review fixes

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

* refactor(cli): extract shared formatTimestamp util and remove unused resolveWorkspace in token

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 09:18:55 +00:00
Ruben Fiszel 501a4ff2a9 fix: Improve CLI developer experience: error handling, sync workflow, JSON output, workspace forks (#8578)
* fix(cli): address 28 DX friction points across CLI commands

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

* chore(cli): regenerate system prompts after help text updates

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

* fix(cli): address PR review feedback

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

* fix(cli): update removeType tests to match lenient behavior

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

* fix(cli): address CE/EE sync friction and improve JSON output

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

* fix(cli): revert instance config masking to avoid breaking push flow

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

* fix(cli): mask instance secrets by default with interactive prompt

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

* chore(cli): regenerate system prompts

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

* fix(cli): use stderr for errors, optimize skipped-files scan, rename --auto to --auto-metadata

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

* feat(cli): improve workspace fork lifecycle — delete-fork fallback, list-forks, --workspace override

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

* fix(cli): update fork merge instructions to reference all merge methods

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

* fix(cli): clarify skipped-files warning comment re DynFSElement traversal

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-28 08:41:52 +00:00
centdix d06b42613f feat(cli): generate commented wmill.yaml and add config reference command (#8546)
* feat: generate commented wmill.yaml template and add config reference command

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

* fix: add missing options to config reference (promotion, skipBranchValidation, commonSpecificItems)

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

* refactor: generate YAML template from CONFIG_REFERENCE instead of handwritten string

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

* fix: preserve YAML comments when binding workspace profile during init

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

* refactor: simplify to `wmill config` and reorder table columns

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

* feat: generate JSON Schema for wmill.yaml editor autocomplete and validation

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

* refactor: remove redundant templateValue fields and make specificItemsSchema data-driven

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

* refactor: use native JSON Schema types in CONFIG_REFERENCE, strip non-schema keys for generation

Eliminates typeToJsonSchema, specificItemsSchema, codebaseItemSchema,
branchConfigSchema, and the complex generateJsonSchema body. Each
CONFIG_REFERENCE entry is now a JSON Schema property with extra metadata.
Schema generation just iterates and strips non-schema keys.

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

* refactor: remove typeLabel and displayType — use schema types directly

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

* refactor: remove hidden entries, auto-expand nested schemas in reference table

Sub-fields (codebases[], gitBranches.<branch>.*) are now derived from
the parent's inline schema instead of being maintained as duplicate
hidden entries. Removes 29 entries and the hidden field entirely.

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

* fix: use console.log for JSON output and quote YAML-special branch names

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

* chore: regenerate system prompts to include new config command

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

* fix: review feedback + add tests for template, schema, and config reference

- Use console.log for --json output (no ANSI escape codes)
- Quote branch names with YAML-special characters
- Add 28 tests covering template generation, JSON Schema validation,
  config reference formatting, and CONFIG_REFERENCE integrity

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

* feat: add generate-schema script and commit wmill.schema.json to repo

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

* refactor: remove schema.json generation from wmill init

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

* fix: eliminate read-back cycle, harden yamlKey, fix triple negation

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:35:28 +00:00
Ruben Fiszel 8cfaa91d43 update cli freshness 2026-03-24 16:01:18 +00:00
centdix 435de95e7d feat(cli): use local scripts when previewing flows (#8365)
* feat(cli): use local scripts when previewing flows

When previewing a flow, PathScript modules (type: "script") now resolve
to local file content instead of remote versions. This ensures flow
preview and dev mode test the actual local changes.

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

* test(cli): add tests for PathScript local replacement in flow preview

Unit tests for replacePathScriptsWithLocal covering:
- basic PathScript→RawScript conversion
- tag_override preservation
- missing local file fallback
- mixed module types
- nested structures (loops, branches)

Integration test verifying flow preview with a PathScript step
uses the local script file content.

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

* refactor(cli): extract shared helpers and add aiagent support for PathScript replacement

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

* refactor(cli): replace `as any` casts with proper type assertions

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

* fix(cli): preserve local flow preview script context

* fix(cli): normalize inline flow preview bundles for bun

* fix(cli): make local flow path scripts opt-in

* fix(cli): only merge flow preview config for local mode

* chore(system-prompts): regenerate cli command guidance

* fix(cli): skip deno defaultTs test in CI without deno runtime

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

* chore(cli): clean up local path script helpers

* feat(cli): make flow preview use local path scripts

* fix(cli): ignore normalized preview metadata drift

* chore(cli): address review follow-ups

* test(cli): cover custom bundler path quoting

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 10:29:30 +00:00
hugocasa fe051aa22b feat(cli): add --env alias for --branch and environments config alias (#8415)
* feat(cli): add --env alias for --branch and environments config alias

Add --env as a CLI alias for --branch on sync pull, sync push, workspace
bind, and workspace unbind commands. Add environments as a permanent
config alias for gitBranches in wmill.yaml. This helps users who use
single-branch multi-environment workflows where "branch" terminology
is confusing.

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

* chore: regenerate auto-generated system prompts

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 20:13:04 +00:00
Ruben Fiszel 87215193ca system promps generate metadata 2026-03-14 04:51:54 +00:00
Ruben Fiszel 749964e326 ci: add freshness check for auto-generated system prompts (#8338)
* ci: add freshness check for auto-generated system prompts

Add a CI workflow and script to verify system_prompts/auto-generated/
stays in sync with its source files (SDKs, schemas, CLI commands, etc).
Also remove the hardcoded CLI version from generated output to avoid
unnecessary churn on every release.

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

* imports

* imports

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

* regenerate system prompts after rebase on main

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 14:44:27 +00:00
centdix 7fb729cc84 fix(cli): instruct agent to tell user about generate-metadata and sync push instead of running them (#8318)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-12 10:18:03 +00:00
centdix b40cf80fdd fix: optimize flow lock generation and add rt.d.ts guidance for TS resource types (#8295)
Instruct AI to pass specific flow folder path to `wmill flow generate-locks`
instead of running it on all flows. Also add guidance for TypeScript language
files to check `rt.d.ts` for available resource types before using them.
Re-ran generate.py to propagate changes to all auto-generated files.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 13:03:21 +00:00
centdix 835db5d290 feat(cli): detect missing folders on sync push and add 'wmill folder add-missing' (#8011)
* fix: auto-create missing folders during sync push for non-admin users

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

* fix: show missing folders in sync push summary before confirmation

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

* fix: improve sync push folder auto-creation error handling and json output

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

* fix: only treat 404 as missing folder in getFolder check

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

* chore: remove obsolete Deno compatibility layer from yaml-validator

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

* chore(cli): add @types/bun dev dependency

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

* feat(cli): replace auto-create folders with `wmill folder add-missing` command

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

* fix(cli): improve folder commands with summary field and simpler push API

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

* feat(cli): add confirmation prompt to folder add-missing command

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

* refactor(cli): simplify missing folder check to use local stat instead of remote API

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

* update skills

* feat(cli): warn admins but block non-admins on missing folder.meta.yaml

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

* cleaning

* cleaning

* test(cli): add tests for missing folder detection and folder commands

- Add tests for `folder new`, `folder push`, `folder add-missing` commands
- Add tests for sync push missing folder.meta.yaml detection (admin warning, non-admin block)
- Fix getBasePostgresUrl to strip query params (e.g. ?sslmode=disable) from DATABASE_URL
- Add createNonAdminUser and runCLIWithToken test utilities to test_backend.ts

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

* refactor(cli): unify runCLICommand with optional token parameter

Replace separate runCLIWithToken utility with an optional { workspace?, token? }
options object on the existing runCLICommand across all backends.

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

* own workspace

* test(cli): isolate folder_missing_meta tests with per-test workspace

* test(cli): shorten isolated workspace id/name for workspace limits

* test(cli): archive temp isolated workspaces after each folder test

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-24 08:38:17 +00:00
centdix a7ce5484b8 feat(local-dev): create Claude skills when doing wmill init (#7699)
* use skills

* add prompts

* update system prompts

* generate skills on init

* add prompts in cli

* better for raw apps

* nit

* test pipeline draft

* better

* yaml for triggers and schedules

* cleaning

* better

* add descriptions to ai agent fileds

* adjust

* better openapi

* better

* nit

* feat: add typed provider and memory schemas for ai agent in openapi

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

* feat: improve zod validation errors with dynamic schema extraction

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

* regen

* fix

* cleaning

* refactor: deduplicate skill descriptions in generate_skills_ts_export

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

* cleaning

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-02-05 13:48:32 +00:00
centdix 31e002ad41 feat(ai): standardize and improve system prompts (#7346)
* init

* test in frontend

* copy files

* use in cli

* better

* add desc to sdks

* better

* fix ts parsing

* add docs to ts client

* add docs to python client

* use script prompt in frontend

* regen

* use in flow

* rm

* use in cli, create AGENTS.md instead of cursor rules

* remove apply

* better

* better

* simplify cli

* more docs

* cleaning

* update readme

* generate cli file

* better folder names

* fix ts

* fix multiline
2025-12-12 17:26:19 +00:00