13584 Commits
Author SHA1 Message Date
Ruben Fiszelandrubenfiszel e82a6a6830 chore(main): release 1.736.0 (#9720)
* chore(main): release 1.736.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.736.0
2026-06-23 09:44:35 +02:00
Ruben FiszelandClaude Opus 4.8 e16061df06 fix(health): detect read-only replica via pg_is_in_recovery() (#9722)
The /api/health/status database check used `SELECT 1`, which succeeds
even on a read-only standby. After a PostgreSQL failover where the
primary becomes a secondary, the health check kept reporting healthy
while all writes failed with "cannot execute INSERT in a read-only
transaction", so Kubernetes liveness probes never restarted the pod.

Use `SELECT NOT pg_is_in_recovery()` instead: it returns true on a
primary and false on a standby, so a read-only replica is now reported
unhealthy. Result handling checks the returned bool (Ok(Some(true)))
rather than just query success.

Fixes WIN-2085

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:37:29 +02:00
Ruben Fiszel 3bf5b72afa fix(drafts): stop mis-filing workspace-blind legacy drafts on migration (#9725) 2026-06-23 09:27:02 +02:00
centdixandClaude Opus 4.8 6f4017d694 feat(ai-chat): workspace AI chat skills (SKILL.md upload + read_skill tool) (#9648)
* feat(ai-chat): workspace ai_skill table + CRUD API

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

* feat(ai-chat): AI Skills workspace settings tab with SKILL.md upload

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

* feat(ai-chat): advertise skills in global system prompt + read_skill tool

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

* refactor(ai-chat): move custom skills into AI settings (paste or folder)

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

* feat(ai-chat): cap folder import (depth<=3, max 50 skills, confirm dialog)

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

* style(ai-chat): give import folder its own labeled subsection

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

* fix(ai-chat): resolve svelte-check never-narrowing in skills preview

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

* fix: address ai skills review issues

* fix: validate ai skills and reload workspace list

* fix(ai-chat): spec-align skill validation and cap skills per workspace

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

* fix(ai-chat): reject duplicate skill uploads, audit skill names

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

* fix(ai-chat): sync deref openapi specs with skill validation rules

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 00:16:13 +02:00
Ruben FiszelandClaude Opus 4.8 d5cb944cf9 fix(frontend): ensure type:object in test_run_flow tool schema for Anthropic (#9721)
Flows with no defined inputs can produce a sparse schema (e.g. { order: [] })
that lacks the "type": "object" field. buildSchemaForTool spread this schema
into the tool parameters as-is, so the Anthropic API rejected the tool
definition with `400 invalid_request_error:
tools.N.custom.input_schema.type: Field required`. The existing fallback in
anthropic.ts only triggers when parameters is falsy, but the sparse schema is
truthy.

Default type:object before spreading the schema in buildSchemaForTool, and
backfill type/properties/required in FlowAIChat's getFlowInputsSchema as a
defense-in-depth measure.

Fixes WIN-2087

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 00:08:13 +02:00
hugocasaandClaude Opus 4.8 e19594df2a fix: re-enforce scoped API token boundaries across handlers (#9712)
* fix: re-enforce per-path token scope on store rename, delete and interpolation

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

* fix: enforce token scope on workspace export and resume-url minting

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

* fix: enforce per-item and runnable scope on trigger create paths

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

* fix: enforce app write scope before persistence and on rename

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

* fix: enforce scope containment on mcp oauth approval

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

* fix: scope mcp endpoint-proxy jwt to the proxied route

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

* fix: treat resource-linked variables and resources as covered by resource scope

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

* fix: only require variables:read for plaintext-secret workspace export

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

* fix: handle singlestepflow resume, reject empty mcp grant, scope var-skipped tarball

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-22 23:55:19 +02:00
Ruben FiszelandClaude Opus 4.8 6e96f90065 fix(frontend): destroy old WebsocketProvider on workspace switch in MultiplayerMenu (#9719)
Switching workspaces created a new WebsocketProvider without destroying
the old one. The leaked provider kept reconnecting, causing alternating
websocket traffic between old and new workspace rooms and flickering in
the Live Activity sidebar.

Add a disconnectWorkspace() cleanup that destroys the provider and resets
connected/awareness state, call it at the start of connectWorkspace()
before creating a new provider (matching ScriptEditor.svelte), and run it
from an onDestroy hook so the provider is torn down on unmount.

Fixes WIN-2086

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 23:20:08 +02:00
Ruben Fiszelandrubenfiszel 83ec0dd07a chore(main): release 1.735.0 (#9700)
* chore(main): release 1.735.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.735.0
2026-06-22 14:07:47 +02:00
GuilhemandClaude Opus 4.8 e20a27745a fix(frontend): strip raw-app post-deploy diff noise (raw_app/lock/data) (#9706)
* fix(frontend): strip raw-app post-deploy diff noise (raw_app/lock/data)

The raw-app editor's Diff drawer showed a spurious deployed-vs-current
diff immediately after deploy, even with no edits: `raw_app: true`, a
server-recomputed inline-script `lock`, and an empty `data` mismatch.

These come from comparing the deployed app row (from getAppByPath) against
the editor's current value, which differ on server-managed fields the
editor never carries, on inline-script locks (recomputed at deploy, cleared
on edit), and on `data` (the deployed row omits an empty `data` while the
editor always carries the default `{tables: []}`).

Add `stripRawAppDiffNoise` (strip server columns, null inline locks,
canonicalize data) and apply it symmetrically to both diff sides in the
editor header. For the session/compare draft diff, the draft is stored flat
(files/runnables/data top-level) while the deployed row nests under `value`,
so add `canonicalRawAppDiffValue` (= appSourceToDraftValue + stripRawAppDiffNoise)
and route both sides through it in getDraftDiffValues. Both diff surfaces now
share the same normalizer.

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

* fix(frontend): use canonicalized current value in deploy-drawer raw-app diff

The Deploy drawer's "Diff" action still built the current side inline from
raw editor state, bypassing stripRawAppDiffNoise — so inline-script `lock`
and data-shape noise could resurface via Deploy → Diff even though the
top-level Diff button was already fixed. Route it through `currentDiffValue`
(and strip the savedApp fallback) so both entry points behave identically.

Addresses Codex review finding on PR #9706.

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-22 13:15:14 +02:00
hugocasaandClaude Opus 4.8 e403f92d7e fix: enforce job_dir containment when writing module files (#9703)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 13:14:14 +02:00
Ruben FiszelandClaude Opus 4.8 8a0b0abead fix: ignore NotFound errors when deleting log files from object store (#9707)
* fix: ignore NotFound errors when deleting log files from object store

Periodic and manual log cleanup delete log files from instance object
storage. S3's DeleteObjects silently ignores missing keys, but GCS
returns a 404 for each individual delete, which the object_store crate's
default delete_stream surfaces as Error::NotFound. This produced noisy
error/warning logs on every cleanup cycle even though the cleanup
succeeded (DB records are removed regardless).

Treat a NotFound delete as a successful no-op in both delete handlers:
- monitor.rs: skip logging NotFound errors
- log_cleanup.rs: count NotFound as deleted instead of an error

Fixes WIN-2081

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

* feat: report 404 (already-absent) count in object store log cleanup

Track delete calls that returned 404 (object already absent) separately
from real deletes so operators can see how many of the attempted deletes
were no-ops, instead of those numbers silently folding into s3_deleted.

- monitor.rs: emit a final info summary per cleanup cycle:
  "N deleted, M already absent (404), K failed" (only when work occurred)
- log_cleanup.rs: add s3_not_found to LogCleanupProgress (serde default for
  backward-compatible deserialization of in-flight rows), thread it through
  s3_bulk_delete and all call sites, and log a final summary on release
- openapi.yaml + generated client + ObjectStoreConfigSettings.svelte:
  surface the 404 count in the manual cleanup status UI

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

* fix: import ObjectStoreError directly from object_store_reexports

The object_store_reexports module already re-exports object_store::Error
under the name ObjectStoreError, so `Error as ObjectStoreError` failed to
resolve (no `Error` in that module). This compiles only behind the
parquet feature, which the local dev `cargo watch` doesn't enable, so it
was caught by CI's full-feature check rather than locally.

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-22 13:12:29 +02:00
GuilhemandClaude Opus 4.8 ed016a5edb feat: clarify session draft bar tracks all workspace draft changes (#9714)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 13:11:45 +02:00
ef4962e52a fix(oauth): restore bring-your-own CC token URL override (#9711)
* fix(oauth): restore bring-your-own CC token URL override

Re-add the optional resource-level token URL field for client-credentials
connections, sent only with the caller's own client_id/secret. Updates the
connect/create_account request schemas and bumps the EE ref.

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

* chore(oauth): keep openapi-deref unchanged from main

The dereferenced specs are not regenerated per-PR (already stale on main,
CI only lint-validates them). Revert the incidental full regen so the PR
diff stays focused on openapi.yaml.

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

* fix(oauth): host-pin CC token URL override server-side

Add is_instance_templated_cc so the EE handlers can reject a bring-your-own
token URL override for {instance}-templated providers (defense in depth for
direct API callers). Bump the EE ref.

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

* fix(oauth): serve cc_token_url in deref specs, enforce CC grant gate

Add cc_token_url to the dereferenced OpenAPI artifacts served at /openapi.yaml
and /openapi.json so generated clients see the new field (kept to a focused add
rather than a full regen). Bump the EE ref for the grant-gate enforcement.

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

* chore: update ee-repo-ref to de49fda2320504ad9e7d2d31c7033d71dbf6ca43

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

Previous ee-repo-ref: a939228d0314c21937687d43c8ef354bdc87c40e

New ee-repo-ref: de49fda2320504ad9e7d2d31c7033d71dbf6ca43

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-06-22 13:05:15 +02:00
centdixandClaude Opus 4.8 b0ddcf31e4 ci: add path-gated AI agent + ai_evals smoke workflows (#9640)
* ci: add path-gated AI agent integration tests workflow

Runs integration_tests/ai_agent_tests against real LLM providers
(Anthropic/OpenAI/Google) only when AI-agent backend code or the tests
change, since runs make paid LLM calls. Adds a conftest fixture that
skips provider-parametrized cases whose API keys are absent, so CI
exercises only the providers it has secrets for.

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

* ci: add path-gated ai_evals global-mode smoke workflow

Runs the global AI chat eval (global-test1) across one cheap model per
provider (Anthropic/OpenAI/Google/DeepSeek) only when the eval harness or
copilot chat code change, since runs make paid LLM calls. Builds Windmill
CE from source as the AI proxy; global tools/drafts run in the Vitest
bridge. Gates on the deterministic draft pipeline (run succeeded +
produced a draft + used write_script), not the variable LLM judge score.

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

* ci: run AI smokes on PR ready-for-review instead of every push

Switch the pull_request trigger from `synchronize` (every commit) to
`ready_for_review`, with a job guard skipping draft PRs, so the paid LLM
runs only fire when a PR is marked ready to merge (plus push-to-main and
manual dispatch).

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

* fix(ai_evals): lazily load cli mode so non-cli evals skip the cli toolchain

The entrypoint eagerly imported modes/cli, which pulls the wmill CLI
guidance modules and their JSR deps (@cliffy/*). Global/flow/script/app
runs then crashed with "Cannot find module '@cliffy/ansi/colors'" when
the cli workspace deps were not installed. Import createCliModeRunner
dynamically inside runCliBenchmark instead.

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

* test(ai_agent): raise low max_completion_tokens to OpenAI's 16 minimum

OpenAI's /v1/responses rejects max_output_tokens < 16 with a 400, failing
test_low_max_tokens for openai. 16 still exercises a truncated response.

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

* ci: run ai_evals workflow on Node 22 for the frontend undici 8.x dep

The Vitest bridge loads frontend/node_modules/undici@8.x, which requires
Node >=22.19; Node 20 failed with "webidl.util.markAsUncloneable is not a
function" when loading vitest.config.ts.

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

* fix(ai_evals): run frontend evals autonomously + give global-test1 more turns

Frontend evals (flow/script/app/global) ran the production chat prompt, which
assumes an interactive human — so cheaper models burned their turn budget
asking for confirmation, waiting for approval, or presenting a plan, sometimes
hitting maxTurns without producing a draft. Append a shared autonomy note in
baseEvalRunner (the path all frontend modes share, mirroring cli mode): act
directly on clear requests; only ask on genuinely ambiguous ones (preserving
the askUserQuestion cases). Also raise global-test1's maxTurns 8 -> 10 so a
model that over-explores still converges.

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

* ci(ai_evals): watch draft/prompt deps outside copilot/

The global eval runs production frontend code in-process, so the smoke's
behavior depends on files outside frontend/src/lib/components/copilot/**:
the draft model (userDraft.svelte.ts, userDraftDbSyncer.svelte.ts), script
inference (infer.ts), and the chat system prompts ($system_prompts ->
system_prompts/auto-generated). Add them to both push and PR path filters so
a change there actually triggers the smoke that gates on draft production.

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

* fix: skip direct provider tests without credentials

* feat: add ai evals skip judge flag

* fix: simplify ai evals ci gate

* fix: simplify ai evals smoke gate

* fix: handle ai eval workflow triggers

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 12:41:31 +02:00
centdixandClaude Opus 4.8 74a2329d2e feat(copilot): improve global-mode path selection + add path-selection evals (#9698)
* test: add global-mode path-selection eval cases with seeded user

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

* feat(copilot): guide global-mode path selection with injected folder list

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

* fix(copilot): tailor global-mode folder guidance for workspace admins

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

* fix(copilot): type folders_read; isolate global-eval user from store

Addresses PR review:
- Add folders_read to the User/whoami openapi schema and UserExt; the global prompt builder and eval harness now read it typed instead of via inline casts (regen the client to pick it up).
- prepareGlobalSystemMessage takes an explicit user; the eval harness passes it rather than mutating the process-global userStore, removing the concurrency race (path cases no longer need --verbose).
- Rewrite the path-selection case comment as a current invariant.
- Add buildFolderGuidance unit tests in core.test.ts.

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-22 11:59:59 +02:00
Ruben FiszelandClaude Opus 4.8 ace7b68a28 fix: sanitize git credentials from ansible executor errors and logs (#9697)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 11:02:24 +02:00
6eb03d2590 round worker usage occupancy up to 5-minute chunks (#9702)
* feat: round worker usage occupancy up to 5-minute chunks

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

* chore: update ee-repo-ref to 8b12fa14ef7969e948169eadf1bf672d7928e5b1

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

Previous ee-repo-ref: 168498974150ff309658b2da43bce8444f13a765

New ee-repo-ref: 8b12fa14ef7969e948169eadf1bf672d7928e5b1

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-06-22 10:59:28 +02:00
GuilhemandClaude Opus 4.8 23bf6bf3da fix(frontend): deploy full script/flow draft from AI chat via shared module (#9642)
* fix(frontend): deploy full script/flow draft from AI chat via shared module

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

* fix(frontend): drop non-persisted priority/timeout from flow draft deploy

The flow branch of the shared deployDraft set `priority`/`timeout` on the
create/update body, but the backend does not persist those fields on flows
(a direct API write returns them as null). Remove the dead fields and the
unit-test assertions for them; the flow deploy still carries every config
field the backend actually stores (tag, dedicated_worker, …).

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

* fix(frontend): chat deploy resolves draft storage path (honor chosen path)

The chat addresses drafts by their display/chosen path, but a draft_only item
created in the editor lives at a synthetic `u/{user}/draft_{uuid}` storage key
(chosen path held in the draft value). The shared deployer reads the draft via
getScriptByPath/getFlowByPath at the path passed, so passing the chosen path
404'd. Resolve to the storage path via getGlobalDraftStoragePath before
delegating; the deployer then deploys at the draft's own `path`. Regression from
the deploy-unification: the old builder read the already-resolved draft and
deployed at the chosen path directly.

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

* fix(frontend): chat raw-app deploy honors the draft's chosen path

The raw-app branch deployed at the path the chat was addressed by (args.path),
which for an editor-created draft_only raw app is the synthetic
`u/{user}/draft_{uuid}` storage key, not the chosen path. Resolve the storage
path and read the chosen path from the backend raw_app draft's `draft_path`
(confirmed shape: getAppByPath{getDraft,rawApp}.draft.draft_path), then create/
update there — mirroring the script/flow storage-path resolution. Content still
comes from the flat AppDraftValue, which the editor and chat both use.

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

* fix(frontend): flush live draft before chat deploy; narrow raw-app catch

Addresses review feedback on the AI-chat deploy:

- Codex P1: script/flow deploy delegates to the shared deployer, which re-reads
  the persisted DB draft. An open editor's edit may still be parked in a
  debounced/disabled autosave, so the deploy could publish a stale draft and the
  post-deploy draft delete could drop the unsaved edit. Flush the draft's
  UserDraftDbSyncer key before delegating (always saves, like Ctrl/Cmd+S, since
  the user explicitly asked to deploy).
- Cubic P2: the raw-app draft_path lookup caught all errors and fell back to the
  storage path, masking real failures (network/5xx). Only fall back on 404;
  re-throw other errors so the deploy aborts instead of deploying to the wrong path.

Adds tests for both; updates the existing raw-app deploy tests to mock getAppByPath.

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

* fix(frontend): flush raw-app draft before reading draft_path on chat deploy

Codex P1 follow-up: the raw-app branch derives the deploy targetPath by re-reading
draft_path from the persisted backend draft, but — unlike script/flow — didn't
flush first. An editor rename mirrored into draft_path can still be parked in a
debounced/disabled autosave, so an immediate chat deploy could read a stale
draft_path and deploy to the old path. Flush the raw_app draft key before the
getAppByPath read, mirroring the script/flow fix.

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

* fix(frontend): abort chat deploy when pre-deploy draft flush conflicts/fails

Codex P1 follow-up: the pre-delegation UserDraftDbSyncer.flush() resolves even
when the save recorded a conflict (server has a newer version) or failed
(network/5xx) — it does not throw. The deploy would then re-read a stale or
conflicting persisted draft and publish it. Add flushDraftOrThrow(): after flush,
check getConflict() and getState().state === 'failed' and abort with a clear
message. Used by both the script/flow and raw-app deploy paths.

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-22 10:55:15 +02:00
Diego ImbertandClaude Opus 4.8 4a8a724895 feat: scope default instance db name to workspace (dt_/dl_) (#9699)
* feat: default instance db name to dt_/dl_ workspace scope

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

* test: cap instance db name at 63 chars and add unit tests

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-22 10:33:49 +02:00
hugocasaandClaude Fable 5 3a55800224 add whatsapp business icon (#9541)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-22 09:33:55 +02:00
GuilhemandClaude Opus 4.8 84cc043406 feat: link files & folders to the global AI chat (#9520)
* feat: add file attachments to the global AI chat

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

* feat: add folder linking and file-type icons to chat attachments

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

* feat: persist linked files, add @-menu file tree, and polish chat file UI

Persistence (survive reload, scoped to session.id):
- IndexedDB store (attachedFilesDB) holding Blob snapshots (every browser)
  and re-grantable File System Access directory handles (capable browsers)
- restore on session activation; re-grant locked handles on the next send;
  flush in-memory items when the session persists; GC on session delete
- capability via feature-detection (fsAccess), never UA sniffing
- folders auto-refresh (live re-enumerate + reconcile) on each send

@-mention file picker:
- Files branch in ChatContextPicker (new DrillPicker architecture); a linked
  folder's files render as a nested directory tree, picking inserts @filename
- attached-file mentions highlight in the input just like context mentions

UI polish:
- file/folder chips reuse the context-element chip style (icon -> X on hover)
- file + context badges sit above the fork/draft bar
- disabled dropdown items can surface an explanatory tooltip (DropdownV2Inner)

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

* refactor: deepen the attached-files store — folders as first-class objects

Two seam fixes from an architecture pass, no behaviour change:

- addFolder(dirHandle) now enumerates internally (same junk-filtered walk
  used on restore/refresh), so callers never pre-enumerate. The dead
  drop-walkers (collectDroppedEntries, filterFolderPickerFiles) are deleted;
  isIgnoredPath/MAX_FOLDER_FILES move next to enumerateDir in fsAccess.

- The store exposes `folders` (name + aggregate status + children) and
  `standalone` as derived views, so the bar, the @-menu picker, the folder
  chip and the system-prompt roster stop re-grouping the flat row list and
  re-deriving folder status. Placeholder rows (isFolderRoot) become an
  implementation detail; the roster renders a locked folder as one line.

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

* fix: drop the redundant context-badge row in the global chat

In GLOBAL mode selected context already appears as a highlighted @mention
in the input (deleting the mention deselects), so the hoisted badge row
above the chat duplicated it. File chips keep their row — attachments
aren't represented in the input.

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

* fix: harden attachment edge cases found in review

- requestReadPermission/queryReadPermission never reject (the spec rejects
  with SecurityError when user activation is missing — now mapped to
  denied/prompt), and sendRequest wraps attachment upkeep in try/catch, so
  a permission hiccup can never silently swallow a Send.
- regrantLocked expands before dropping the locked placeholder: when the
  re-granted directory is gone from disk, the folder now shows
  "unavailable" instead of vanishing into a zombie that resurrects locked
  on the next reload.
- addFolder: re-picking a locked/unavailable folder relinks it (natural
  recovery gesture); a genuine second folder with the same basename gets a
  visible "already linked" rejection instead of a silent no-op.
- fileEngine: readFile clamps its byte slice to maxChars*4 before decoding
  and streamLines caps its per-line buffer, so newline-sparse files
  (minified JS, single-line JSONL) can't materialize unbounded strings;
  corrected the scan-cap comment's claim about catastrophic backtracking.

4 new unit tests (41 total).

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

* fix: surface folder-picker failures instead of swallowing them

`pickDirectory` caught every `showDirectoryPicker` rejection and returned
undefined, so a real failure (an enterprise/browser policy blocking the File
System Access API, a lost user-activation, …) was indistinguishable from a
no-op — the picker just silently never opened. Now only `AbortError` (user
dismissed the dialog, or CDP intercepted it under automation) is treated as a
cancel; anything else is rethrown and `linkFolder` surfaces it as a toast.

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

* feat: support folders in browsers without the File System Access API

Folders can now be added in every browser, not just Chromium. Where the File
System Access API is absent (Firefox/Safari), a dropped or picked folder's files
are snapshotted into the browser (via a webkitGetAsEntry drop-walk or a
`webkitdirectory` input) instead of linked as a live handle, and grouped/displayed
identically to a File System Access folder. The dropdown item reads "Link folder"
when a live link is possible and "Add folder" otherwise, with a tooltip pointing
to Chrome/Edge for a live link.

Snapshot folder children persist their `folder`/`relPath`, so they regroup into
the same folder chip on reload.

Removes the arbitrary file-count caps (500 per folder, 100 total) — only the
browser's memory / IndexedDB quota now bound a folder. Junk paths
(node_modules/.git/dist/dotfiles) are still skipped, folder-contents only, so an
explicitly attached standalone dotfile is kept.

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

* fix: address review feedback — index-race guard + read_file line numbers

Both automated reviewers flagged two issues on the attached-files feature:

- (P1) Stale async indexing could corrupt a newer file. `#indexFile` applied
  its unawaited `buildLineIndex` result by display name, so if a row's file was
  swapped while indexing was in flight (remove + re-add a same-named file, or a
  folder refresh re-indexing an edited file) the stale result stamped the wrong
  lineIndex/lineCount — and `read_file` then sliced the new Blob with old
  offsets. Now patched via `#patchFile`, which applies the result only while the
  row still holds the exact file object that was indexed.

- (P2) `read_file` promised "line-numbered context" but returned raw text. It now
  prefixes each line with its absolute 1-based number (`<n>→<content>`), matching
  the tool contract; `numberLines` lives in fileEngine and is unit-tested.

Adds regression tests: a deterministic stale-index race test (controlled
buildLineIndex ordering) and numberLines coverage.

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

* fix: address re-review nits — read_file pagination + searchFiles regex state

- read_file: when the maxChars cap truncated a window short of its requested
  end line, the pagination note still reported the full range and gave no/wrong
  resume point, so the model couldn't reach the unread lines. The note now
  reports the last line actually returned and resumes at the next unread line
  (advancing past a single over-long line rather than re-truncating it forever).
- searchFiles: reset `regex.lastIndex` before each `.test()` — a caller-supplied
  `g`/`y` flag makes test() stateful and would silently drop matches. Not
  reachable from the current caller, but searchFiles is exported.

Adds regression tests for both.

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

* fix: keep an emptied live folder linked and refreshing

A live (File System Access) folder carried its directory handle only on its child
file rows. When the folder was emptied on disk, refreshFolders/#reconcileFolder
removed the last child — dropping the only handle-bearing row — so the folder
vanished from the chip bar AND was never re-enumerated again (files added back on
disk weren't picked up until a reload). #expandFolder had the same gap on restore.

Now #ensureFolderRow leaves one handle-carrying placeholder row when a folder has
no readable children (keeps the chip visible and the live source alive), and drops
it once children return; refreshFolders collects sources from placeholder rows too,
and readyFiles never exposes a placeholder to the read/search tools. Adds a
regression test (empty → still visible → file returns → picked up).

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

* fix: trim read_file char-cap output to match its pagination note

When the char cap cut partway into the line after some whole lines, readFile set
the note/endLine to the last complete line but still returned the partial next
line in `text` — so read_file showed (line-numbered) a line the note said would
come on the next read. Trim the returned text back to the last complete newline
so the body and the note agree. Test now asserts res.text for that case.

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

* fix: isolate search_files in a Worker (ReDoS) + path-aware folder dedup

- search_files runs a model-supplied regex, and a catastrophic-backtracking
  pattern (e.g. /^(a+)+$/) can't be interrupted mid-test, freezing the tab. Run
  the search in a Web Worker (searchFilesInWorker) and terminate it on a timeout,
  returning "pattern too expensive" instead of hanging. Degrades gracefully to a
  main-thread search where Workers are unavailable / fail to load.
- #isDuplicate keyed its content check on the file basename, so two distinct
  files sharing a basename under different folder subdirs (proj/a/index.ts vs
  proj/b/index.ts) were wrongly deduped and silently dropped from snapshotted
  folders. Key it on the relative path instead.

Adds tests: worker result/timeout-and-terminate, and same-basename-different-subdir.

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

* fix: keep an initially-empty live folder linked (placeholder + persist)

addFolder only created rows / persisted the dir-handle when at least one text file
was found, so linking a folder that's empty (or all-binary) at pick time was a
silent no-op: no chip, nothing persisted, and refreshFolders had no source to
re-enumerate when files were added later. Now it always leaves a placeholder
(#ensureFolderRow) and persists the handle — matching the became-empty behavior —
so the folder stays visible, survives reload, and picks up files added afterward.

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

* fix: keep empty-folder placeholders out of the real-file name space

The placeholder row for an empty live folder uses name = folder, which could
collide with a standalone file of the same name: addFiles deduped the file
against the placeholder, removeFile(name) dropped both rows, and #uniqueName
pushed the file to a "(2)" suffix. Placeholders are managed via removeFolder and
never read by the tools, so exclude isFolderRoot rows from #isDuplicate,
removeFile, get(), and #uniqueName. Adds a placeholder/standalone collision test.

(codex's other nit — @-mentions not highlighting filenames with spaces — left as
a known cosmetic limitation per the chosen scope.)

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

* fix: highlight @-mentions of filenames containing spaces

A file mention was inserted verbatim as `@my file.txt`, but the highlighter regex
`@[\w/.\-\[\]]+` stops at the space, so only `@my` was parsed/highlighted and the
mention didn't behave as advertised. Introduce a small shared `mention` module:
names with whitespace are inserted in a bracketed form `@[my file.txt]`, and the
shared regex + `mentionTitle` parse both bare and bracketed tokens. Both insertion
entry points (the inline `@` picker in ContextTextarea and the toolbar path in
AIChatInput) now use `formatMention`, so the full name highlights.

Verified in a real browser: `@[my file.txt]` renders as a single highlight span.
Unit tests cover format/parse/round-trip.

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

* fix: search_files reports a requested file's real status, not "not attached"

search_files filtered the store down to readyFiles() before validating a
requested `file`, so searching an attached-but-not-ready file (indexing / errored
/ locked / unavailable) while another file was ready returned "No attached file
named X" — even though it is attached. Factor read_file's status reporting into a
shared notReadyMessage() and have search_files report the same accurate status
before searching the ready subset.

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

* fix: clear attached files on new/loaded chat in the non-session global chat

saveAndClear() (the "New chat" button) and loadPastChat() left attachedFiles
intact. In an AI session that's intended — files are session-scoped and persist
across conversations. But the ephemeral global side-panel chat has no session, so
the next, unrelated conversation still got the previous file roster injected and
could read_file/search_files against it. Clear attachments on both transitions
when `!isSessionChat`; sessions keep them. Adds a lifecycle regression test.

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

* fix: keep an empty folder linked when regranting access after reload

regrantLocked() dropped the locked placeholder unconditionally after #expandFolder.
If the regranted folder was empty (or all-binary), #expandFolder's #ensureFolderRow
no-op'd (the locked placeholder still existed), so dropping it removed the only
handle-bearing row — unlinking the folder and stopping future refreshFolders from
ever seeing files added back. Re-ensure a ready placeholder after dropping the
locked one. Adds a regression test for the empty-regrant path.

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

* fix: round-trip @-mentions of filenames containing a closing bracket

The bracketed mention form `@[name]` broke when the name contained a `]`
(e.g. `notes ] draft.md`): the regex stopped at the first `]` and mentionTitle
resolved the wrong name, so it wouldn't highlight. Escape `\` and `]` when
bracketing, match escaped chars in MENTION_RE, and unescape in mentionTitle.

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

* fix: highlight @-mentions of filenames with HTML-sensitive / special chars

getHighlightedText() escapes the textarea value to HTML before parsing mentions,
then looked the parsed title up against raw attached names — so a file like
`R&D notes.md` (escaped to `R&amp;D notes.md`) never matched and wasn't highlighted.
Also, names with chars outside the bare set (`<`, `>`, `&`, parens, …) weren't
bracketed, so the bare regex truncated them. Now formatMention brackets any
non-bare-safe name, and the highlighter HTML-unescapes the parsed title before the
store lookup. Verified in a real browser with `R&D notes.md`.

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

* fix: report the real reason search_files has no readable targets

When attachments existed but readyFiles() was empty, search_files always told the
model "still being indexed, try again shortly". That's wrong for the placeholder
states this PR introduces: an empty or binary-only linked folder leaves only a
filtered-out `ready` placeholder, and a locked/unavailable restored folder exposes
no readable children. Now the message reflects the actual state — no searchable
text, restore access, or re-link — and only says "indexing" when something is.
Adds a focused fileTools test for the empty-ready states.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 09:21:14 +02:00
Ruben Fiszelandrubenfiszel 346cc30e2d chore(main): release 1.734.0 (#9691)
* chore(main): release 1.734.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.734.0
2026-06-20 16:32:16 +02:00
Diego ImbertandClaude Opus 4.8 b0973c3023 hide primary storage row until added in workspace storage settings (#9692)
* feat(frontend): hide primary storage row until added in workspace storage settings

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

* feat(frontend): red border on empty storage resource picker

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

* feat(frontend): allow deleting primary storage when no secondary storages exist

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

* feat(frontend): disable storage save when a row is missing its resource

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-20 16:18:00 +02:00
Ruben Fiszel 3ebf24359d feat: ducklake materialization for data pipelines (#9689) 2026-06-20 15:42:03 +02:00
Diego ImbertandClaude Opus 4.8 09a80040ca fix(frontend): clear branch step state when switching outer loop iterations (#9650)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 12:22:15 +02:00
Ruben Fiszelandrubenfiszel d5388da953 chore(main): release 1.733.1 (#9685)
* chore(main): release 1.733.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.733.1
2026-06-19 17:37:27 +02:00
Ruben FiszelandClaude Opus 4.8 119d94601d feat(jobs): auto-grant approvers a run-detail view link on the approval page (#9686)
* feat(jobs): auto-grant approvers a run-detail view link on the approval page

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

* feat(approval): show a clear run-details button for logged-in workspace members

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

* fix(approval): build run-details link from route params, not undefined job

getJob is fire-and-forget on the new approval page and is denied for an approver who lacks direct run read access — the exact case this link serves — leaving job undefined and producing /run/undefined. page.params.job is the flow id the view_token is minted for.

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-19 17:29:19 +02:00
Ruben FiszelandClaude Opus 4.8 6aa9e515f7 de-flake asset-dispatch debounce test by reading settings from DB (#9687)
The debounce assertion resolved the dispatched job's debounce window through
`prefetch_cached_from_handle`, which goes through the process-global
runnable-settings cache (shared by every test running concurrently in the
binary) and its tempdir-backed file I/O. Read the persisted rows directly from
the test's isolated DB instead, removing that cross-test coupling and extra I/O
from the assertion path. Still validates the full wiring
(handle -> runnable_settings -> debouncing_settings).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:16:18 +02:00
960c55e1f3 preserve URL filter state on page refresh (#9680)
* fix(home): preserve URL filter state on page refresh

ListFilters.loadFilterFromUrl() runs synchronously at script init and
sets ownerFilter via binding. When $workspaceStore resolves asynchronously
after mount it triggered the $effect that resets ownerFilter, wiping the
URL-loaded filter before the user saw any results.

Skip the first $workspaceStore resolution using the same firstRun guard
pattern already used in this file (firstWorkspaceRun). Workspace switches
still correctly clear the filter.

Fixes #9624

* refactor(home): reset filters on workspace change instead of first-run guard

Track the previous workspace value and clear filters only when it actually
changes, rather than skipping the first $workspaceStore resolution. Encodes
the real invariant (reset on change) without depending on child/parent init
ordering, and preserves URL-loaded filters on initial mount by construction.

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

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:12:34 +02:00
hugocasaandClaude Opus 4.8 c1f31c0e47 fix(backend): validate ansible vault_id entries before config generation (#9681)
* fix(backend): validate ansible vault_id entries before config generation

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

* test(backend): cover ansible.cfg write-boundary vault_id validation

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:54:25 +02:00
hugocasaandClaude Opus 4.8 c39ee07c0b fix: validate websocket trigger urls and gate trigger test route (#9682)
* fix: validate websocket trigger urls and gate trigger test route

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

* docs: clarify validate_websocket_url_for_ssrf call sites

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-19 16:54:04 +02:00
hugocasaandClaude Opus 4.8 fb44fe7af2 fix: require super admin for object storage config test endpoint (#9683)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:53:49 +02:00
Ruben FiszelandClaude Opus 4.8 1be4df9acb fix(frontend): group live pipeline runs in the activity panel (#9684)
* fix(frontend): refetch pipeline dispatch edges on live runs so cascades group

The activity panel groups runs into cascades by connected components of the
dispatch-edge graph, but edges came only from the one-shot history preload
(refetched on workspace/folder/days change). dispatch_event rows are written
server-side when a producer completes, so a run launched live had its producer
and freshly-dispatched children appear as live poll events with no connecting
edge — they rendered as separate ungrouped rows instead of one cascade.

Add an edges-only refetch and trigger it whenever the live poll's event id-set
changes, so live cascades converge to grouped like historic/scheduled ones.

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

* fix(frontend): address review nits on live dispatch-edge refetch

- Sequence same-scope edges-only refetches with a monotonic edgeSeq so a
  slower earlier response can't overwrite a newer one mid-cascade (the gen
  counter only guards scope changes).
- Condense the duplicated edge-refetch rationale: keep the canonical "why"
  in loadEdges, trim the page effect comment to its trigger/loop invariant.

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-19 16:43:23 +02:00
Ruben Fiszelandrubenfiszel b2ce475fc3 chore(main): release 1.733.0 (#9677)
* chore(main): release 1.733.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.733.0
2026-06-19 13:50:47 +00:00
Ruben FiszelandClaude Opus 4.8 5970510cf6 perf(backend): gate asset producer-change event on write-set changes (#9672)
* perf(backend): only emit asset producer-change event on write-set changes

Data Pipelines (#9193) made every script deploy emit a
`notify_asset_producer_change` event: `clear_static_asset_usage` inserted
into `notify_event` unconditionally on every clear, and the per-asset
insert path emitted nothing. So a plain script with no assets — the
overwhelming majority of deploys — wrote a `notify_event` row that made
every worker drop its `ASSET_PRODUCER_WRITES_CACHE` entry instance-wide,
needlessly thrashing the cache the feature added.

That cache only tracks script rows with write access (`usage_access_type
IN ('w','rw')`), so a deploy changes it only when the script gains or
loses a write producer. Gate the event on exactly that:

- `clear_static_asset_usage` / `clear_static_asset_usage_by_script_hash`
  emit only when the delete removed a 'w'/'rw' row (via `RETURNING`).
- `insert_static_asset_usage` emits only when it actually inserts a
  'w'/'rw' script row (no-op `ON CONFLICT`, read-only, and flow usage
  stay silent).

Plain, read-only, and flow deploys now emit nothing; producer-changing
deploys still invalidate, atomically and visible-only-on-commit as
before. Adds a test asserting the emit/no-emit matrix.

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

* perf(backend): dedup producer-change notify on write-asset redeploys

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

* fix(backend): derive replace write-set from persisted rows; document auth contract

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

* docs(backend): correct replace_static_asset_usage call-site comment

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-19 13:49:32 +00:00
6833a554ae fix: allow users to always discard their own drafts without write permission (#9659)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-06-19 13:34:38 +00:00
centdixandClaude Opus 4.8 4296a6ae1f feat(ai-chat): cap read_app_file + search_app grep tool to bound context in large raw apps (#9653)
* docs: add global AI chat context-optimization plan for raw apps

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

* test(ai-evals): add global raw-app debugging cases on a large fixture

Adds a ~20-file analytics_dashboard raw-app fixture (incl. a 5k-line data module
and a planted wrong-totals bug), two global cases (read-heavy debug + small-edit
baseline), app-seed support in the mock backend, directory-fixture loading, and a
decorateHelpers seam so read-dedupe is measurable. Records tokenUsage for before/
after comparison of the read-tool optimization.

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

* feat(ai-chat): cap and dedupe read_app_file to bound context in large apps

read_app_file now defaults to a head slice (1500 lines / 50k chars) with offset/
limit to page further, and skips resending a file whose earlier read is still in
context (per-conversation ledger keyed off the originating tool-call id, so it
self-heals after compaction). Bounds the file-content portion of global-chat
context when working in large raw apps.

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

* test(ai-evals): add read-heavy raw-app debug case (large data module)

global-test31 induces the model to inspect the 5k-line seedData module, exercising
the read_app_file cap/offset path. Baseline ~262k tokens vs ~200k with the cap+dedupe
change (-24%).

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

* docs: record A+B benchmark results and fixed-overhead finding

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

* fix(ai-chat): clearer read_app_file past-EOF message + unit tests for cap/dedupe

Addresses local-review nits: out-of-range offset now reports 'offset N is past the
end of the file' instead of a backwards 'lines 11-10' label; adds unit coverage for
the slicing (line cap, offset/limit window, char budget, past-EOF) and re-read dedupe
(hit + miss-when-not-retained).

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

* feat(ai-chat): char-level paging + per-range dedupe for read_app_file

Adds char_offset/char_limit so minified/long-line files can be paged within a line
window, keys the re-read ledger by range (so reading different ranges no longer
collides), and dedupes on the full-file hash (a cached range stub is invalidated
when any byte of the file changes, not just the returned range). Tests updated for
the char-slice behavior plus single-line capping, char paging, and out-of-window
change detection.

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

* test(ai-chat): add read_app_file context micro-benchmark + re-read eval case

Adds a deterministic micro-benchmark (no LLM) that drives read_app_file through a
realistic big-project read pattern (large file, re-read, minified bundle, paging)
and asserts the cap+dedupe cut returned context >50% vs the old whole-file behavior
— isolating the feature's effect from model nondeterminism and guarding against
silent weakening. Adds global-test32, a cross-file consistency investigation that
revisits overlapping files so re-read dedupe is exercised in a real run.

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

* test(ai-evals): clarify test32 measures the read cap, not dedupe

Verified: sonnet and haiku both read each file once per conversation and retain
it, so test32 never triggers read_app_file re-read dedupe. Dedupe is measured
deterministically by the micro-benchmark instead. Comment corrected to match.

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

* refactor(ai-chat): drop read_app_file re-read dedupe, ship the cap only

Benchmarking showed the per-conversation re-read dedupe never fires in practice:
across sonnet/opus/gpt-5.5/haiku, every model reads each file once per conversation
and keeps it in context (0 within-conversation re-reads). It was a correct but unused
guard, so this removes the ledger, full-file hash, retention predicate, the
AIChatManager wiring, and the eval decorateHelpers seam — keeping the read cap +
offset/limit/char paging (A), which is the lever that actually bounds context. The
micro-benchmark is now cap-only; test32 is kept as a multi-file read-load case.

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

* feat(ai-chat): add search_app grep tool for global raw-app chat (experimental)

Client-side grep over a raw app's frontend files and inline runnables (literal,
case-insensitive, optional file_glob/context_lines/max_matches, head-capped).
Completes the list -> search -> ranged-read triad. Includes the eval A/B gate
(WMILL_AI_EVAL_DISABLE_SEARCH_APP), unit tests + micro-benchmark, and a
find-all-usages eval case (global-test33).

Experimental: A/B benchmarking shows it is not an unconditional win — it helps
on find-all-usages but adds agentic iterations on navigable apps.

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

* test(ai-evals): accept search_app as a valid file-inspection tool in raw-app cases

Add requiredToolsAnyOf alternatives-group to ToolValidationSpec and switch
global-test29..32 to it so a model that locates files via search_app instead
of read_app_file no longer false-fails the tool assertion.

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

* docs: remove stale ai-chat context-optimization planning doc

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

* refactor(ai-chat): drop read_app_file char paging for a hard char cap

The char_offset/char_limit params guarded minified files (a single line over
the char budget) but were effectively unused in benchmarks. Remove them and the
in-window char paging; keep the hard 50k-char budget and, when a read hits it,
tell the model to narrow the line limit (or treat the file as unreadable if a
single line exceeds the budget). Proper long-line handling is left as a TODO.

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

* refactor(ai-chat): bake search_app context to 1 line, clarify query is literal

Drop the context_lines param (models varied it to little effect) for a fixed
SEARCH_APP_CONTEXT_LINES=1, and cap on matching lines instead of pushed rows so
max_matches stays accurate with context always on. Sharpen the query description
to state it is a literal (non-regex) substring and to suggest the call form
(e.g. formatCurrency() to hit call sites and skip formatCurrencyPrecise.

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

* refactor(ai-chat): widen baked search_app context to 2 lines

Models that set the old context_lines param leaned to 2; match the lean.

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

* fix(ai-chat): count every file with a match in search_app header

Move fileHadMatch ahead of the render cap so files whose matches fall past max_matches are still counted (with a regression test). Also swap the raw NUL globstar sentinel for a printable escape (the NUL bytes made core.ts read as binary to grep) and reword two comments to describe current constraints instead of drafting history.

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

* fix(ai-chat): drop redundant input echoes from app tool results

read_app_file and search_app no longer prefix results with the tool name or echo back the caller's own inputs (file path, query, file_glob) — the model already has them from the call args, and the unbounded query echo could push the search result past its output budget. Keeps the useful signals (line range, match/file counts, truncation) and the actionable advice. Also reword max_matches to 'matching lines' since it caps lines (each expands to context rows). Unit tests updated to the new format.

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-19 13:33:54 +00:00
Ruben FiszelandClaude Opus 4.8 924f9c7e8d fix(backend): strip NUL bytes from draft values on write (#9673)
draft.value is a json column (not jsonb), so a client could store a U+0000
escape in it. Any later text extraction (`->>` / `to_jsonb`) on such a value
raises 22P05 "unsupported Unicode escape sequence" — one poisoned draft 500'd
the whole GET /drafts/list, silently hiding the home-page "This workspace has N
drafts" banner (and breaking the global drafts page).

Prevent it at the source: sanitize the value in update_draft (the only path that
writes client-supplied draft content) so a NUL never reaches the column.
strip_json_nul does a single backslash-parity-aware byte pass that removes real
NUL escapes (values and keys alike) while leaving a legitimate escaped backslash
intact — O(n) with no serde_json::Value tree to allocate, important because the
slow path is also hit by any value legitimately containing the text after a
backslash (e.g. script source). The clean path is a single substring check.

A SQL migration scrubs rows written before this, gated to genuinely-poisoned
rows (a real NUL makes value::jsonb raise, distinguishing it from a legitimately
escaped backslash). With the data clean, no read-side query needs to change.

Tests: unit tests for the strip helper (escaped-backslash no-op, real+literal
collision, odd-backslash-run parity, nested keys/values) and an integration test
that POSTs a NUL-bearing draft and asserts it is stored and listed NUL-free
(fails without the strip).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:33:36 +00:00
PyraandClaude Opus 4.7 ab3bc97cd9 feat(python, windows): enable S3 to cache wheels (#5199)
* add .minio-data to .gitignore

* feat(python): make S3 cache arch specific

Current schema:

S3-Bucket
├── python_311
│   ├── wheel==1.0
│   └── wheel2==1.0
└── python_312
    ├── wheel==1.0
    └── wheel2==1.0

New schema:

S3-Bucket
├── linux_aarch64
│   └── ...
└── linux_x86_64
    ├── python_311
    │   ├── wheel==1.0
    │   └── wheel2==1.0
    └── python_312
        ├── wheel==1.0
        └── wheel2==1.0

* remove .minio-data from .gitignore

* remove unneeded tracing::error

* feat(python, windows): enable S3 to cache wheels

* fix(python, windows): drop residual unix cfg gates on S3 cache items

Post-merge, PIPTAR_UPLOAD_CHANNEL and its call sites were already
cross-platform, but the types/functions they reference (PiptarUploadTask,
handle_piptar_uploads, pull_from_tar, OBJECT_STORE_SETTINGS) remained
unix-gated, breaking the Windows build.

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-06-19 13:11:35 +00:00
Ruben Fiszel 496e770264 Revert "fix(backend): clean up unique_ext_jwt_token on workspace deletion (#9…" (#9678)
This reverts commit 9add719d93.
2026-06-19 13:01:15 +00:00
Ruben FiszelandClaude Opus 4.8 cafb473494 fix(python): split PIP_TRUSTED_HOST by whitespace to support multiple hosts (#9675)
* fix(python): split PIP_TRUSTED_HOST by whitespace for multiple hosts

When PIP_TRUSTED_HOST contains multiple space-separated hostnames, the
whole string was passed as a single --trusted-host argument
(--trusted-host "host1 host2") rather than one flag per host. This
matches pip's documented PIP_TRUSTED_HOST convention by emitting a
separate --trusted-host for each host, mirroring the existing
pip_extra_index_url handling.

Fixes WIN-2077

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

* fix(python): use shell word-splitting for nsjail trusted-host args

Address review feedback: the previous tr/sed pipeline only split on
single spaces, diverging from the Rust split_whitespace() paths. Repeated
or leading/trailing spaces produced empty --trusted-host flags and
tab-separated hosts were not split. Use an unquoted for-loop over
$TRUSTED_HOST so the shell's own IFS word-splitting handles arbitrary
whitespace and skips empty fields, matching the non-nsjail paths.

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-19 12:59:08 +00:00
Ruben FiszelandClaude Opus 4.8 9add719d93 fix(backend): clean up unique_ext_jwt_token on workspace deletion (#9676)
The workspace_id column on unique_ext_jwt_token (migration 20260409145556)
has no FK constraint on the workspace table, and delete_workspace did not
remove its rows. Deleted workspaces left orphaned external JWT token records
that kept appearing in the superadmin External JWTs listing.

Add a DELETE FROM unique_ext_jwt_token WHERE workspace_id = $1 alongside the
other per-table cleanup statements in delete_workspace.

Fixes WIN-2078

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:48:48 +00:00
Ruben Fiszelandrubenfiszel b24616dc44 chore(main): release 1.732.0 (#9670)
* chore(main): release 1.732.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.732.0
2026-06-19 11:34:59 +00:00
Ruben FiszelandClaude Opus 4.8 33617367d0 fix(backend): grant script_trigger access to windmill roles (#9674)
The script_trigger table (migration 20260423050000_script_trigger)
relied on ALTER DEFAULT PRIVILEGES to grant access to windmill_user and
windmill_admin. Those default privileges only apply to objects created
by the role that set them (migration 20250205131523), so deployments
whose migration runner is a different role leave script_trigger
ungranted.

Direct application writes run as the invoking role and fail with
"permission denied for table script_trigger" — notably
clear_script_triggers and insert_script_trigger in
windmill-common/src/assets.rs during every script save.

Add an explicit GRANT on script_trigger and its sequence, matching the
notify_event fix (#9665) and the asset table precedent.

Fixes WIN-2076

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:30:21 +00:00
Ruben FiszelandClaude Opus 4.8 3371265382 fix(frontend): ignore hash/assets in script diffs and drafts (WIN-2071) (#9664)
* fix(frontend): ignore hash/assets in script diffs and drafts

The script editor's draft value is seeded from the full `getScriptByPath`
DB row (since #9351), so it carries `hash` (the deployed version's
identity) and `assets` (re-derived from the script content by the
editor). Neither is editable draft content, yet both were persisted into
the draft row and surfaced as spurious changes in the workspace/fork
compare diff view.

- Add `hash`, `assets`, and the read-time-computed `inherited_labels` to
  `CLEANED_VALUE_KEYS` so the shared diff/unsaved-change strip ignores
  them everywhere (DiffDrawer + WorkspaceItemDiffViewer).
- Strip `hash` and `assets` from script drafts at the single persistence
  chokepoint (`UserDraftDbSyncer.save`) so every path — reactive
  autosave, Ctrl/Cmd+S flush, the pagehide keepalive — sends the same
  trimmed payload. On reload the deployed row re-supplies them.

Fixes WIN-2071

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

* docs(frontend): clarify draft sanitizer vs diff-strip relationship

The two field lists are intentionally not equal — only the hash/assets
overlap must stay consistent. Reword the comment so a future maintainer
doesn't add keys to one expecting parity with the other.

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-19 11:21:24 +00:00
Ruben FiszelandClaude Opus 4.8 017c3d3343 feat(ansible): add AI chat and editor bar buttons for ansible (#9671)
Ansible scripts previously lacked the AI assistant and the contextual
variable helper that other scripting languages expose in the script editor.

- Add 'ansible' to SUPPORTED_CHAT_SCRIPT_LANGUAGES so the AI chat button
  shows in the editor toolbar and the AI chat opens in SCRIPT mode without
  the "language not supported" warning.
- Add an Ansible system prompt (system_prompts/languages/ansible.md) plus a
  LANGUAGE_METADATA entry, and regenerate the auto-generated prompts/skills
  so the AI has tailored Ansible context.
- Show the contextual variable picker for ansible and insert references as
  `{{ lookup('env', 'NAME') }}`, matching how Windmill exposes reserved
  variables as environment variables to the ansible-playbook process.

Fixes WIN-2072

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:12:33 +00:00
centdixandClaude Opus 4.8 0cc2257596 fix(ai): emit token usage in gemini proxy streaming translation (#9669)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:57:52 +00:00
Ruben Fiszelandrubenfiszel 887a3076b2 chore(main): release 1.731.0 (#9668)
* chore(main): release 1.731.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.731.0
2026-06-19 10:16:35 +00:00
centdixandClaude Opus 4.8 c30bdecea7 fix(mcp): repair invalid type keywords in tool JSON schemas (#9667)
Drop empty/unknown `type` values (Windmill emits `type: ""` for untyped fields) and infer `type: array` for nodes carrying `items`, so generated MCP tool schemas validate against JSON Schema draft 2020-12. Anthropic's tool registration rejected the whole tool list otherwise.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:04:42 +00:00
Ruben FiszelandClaude Opus 4.8 7e4df02bd6 fix: trigger flow error handler on unrecoverable (OOM/zombie) step failures (#9662)
* fix: trigger flow error handler on unrecoverable (OOM/zombie) step failures

When a worker is OOM-killed mid-step, the zombie job handler fails the step
via handle_job_error with unrecoverable=true. update_flow_status_after_job_completion
had `false if unrecoverable => false`, which silently completed the flow with the
error and skipped the flow's failure module (error handler). It would also have
pinned the failure module to the dead worker via same_worker.

Unrecoverable failures now route to the failure module instead of being retried or
silently dropped, and the error-handler step is pushed as a regular queued job that
any live worker can pick up.

Fixes WIN-2070

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

* fix: skip retry on unrecoverable flow failures, add retry-skip regression test

Address review: the failure-module-on-unrecoverable change must also bypass the per-step retry policy in push_next_flow_job, otherwise an OOM/zombie-killed step with a retry config would be retried instead of routing to the error handler. Gate the retry evaluation on !unrecoverable and add a regression test.

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

* chore: add sqlx offline cache for new flow-step zombie test query

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

* fix: route unrecoverable continue_on_error step failures to the error handler

Addresses Codex/Pi review (P1): with continue_on_error on the failed step, the
step counter was advanced before the unrecoverable decision branch, so
push_next_flow_job pushed the next normal step instead of the failure module —
hiding the worker death and letting the flow complete successfully.

- Do not advance the step counter (inc) for an unrecoverable continue_on_error failure.
- Let the Failure arm in push_next_flow_job route to the failure step even on a
  continue_on_error module when unrecoverable.
- Add a regression test (a[continue_on_error] -> b + failure_module): asserts the
  failure module runs and step b does not.

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-19 09:57:05 +00:00