Commit Graph

8580 Commits

Author SHA1 Message Date
Ruben Fiszel e052d5ee45 fix(copilot): only warn on saved write_flow; add regression tests
Address review: writeFlowDraft reports conflicts/persistence errors as
{success:false} rather than throwing, so the empty-body warning must be
folded into the JSON result only on a successful save — otherwise the
model is told to set_flow_module_code on a flow that was never saved
(stale or nonexistent draft). Add core.test.ts coverage for the empty-body
warning (top-level, nested, preprocessor, failure; populated suppressed),
the no-warning-on-failed-save path, and the malformed-JSON escaping hint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 11:35:57 +00:00
Ruben Fiszel 71f17cea7f fix(copilot): stop write_flow forcing rawscript code into nested JSON
The global-chat write_flow tool made the model embed rawscript bodies
inside the modules JSON string, so code had to survive three levels of
escaping (tool arguments -> modules string -> content string). Models
routinely mangled the quotes/newlines and flow creation failed on the
first tries.

Bring write_flow to parity with flow mode's set_module_code escape hatch:
detect rawscript modules left empty or as inline_script placeholders and
tell the model to fill them via set_flow_module_code, add a code-escaping
hint to the JSON parse error, and update the guidance to keep code out of
the modules structure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 11:21:15 +00:00
Ruben Fiszel 5685981c99 fix(tutorials): repair broken frontend tutorials after UI redesigns (#10255)
* fix(tutorials): repair broken frontend tutorials after UI redesigns

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

* fix(tutorials): wait for New menu anchor and drop vestigial async

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-07-22 11:49:08 +02:00
Ruben Fiszel 703744fb8b feat(ai-sessions): show item preview cards for tools (#10254)
* feat(ai-sessions): show item preview cards for create/update/open-preview tools

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

* fix(ai-sessions): only show open_preview card when the preview actually opened

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

* feat(ai-sessions): render preview card as a chip on the tool-call header row

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-07-22 11:47:53 +02:00
Ruben Fiszel d2c5d6f4b4 feat: make content search a full CE feature (#10252)
Content search (the `#` mode of the home-page Ctrl+K search, which
searches scripts/flows/apps/resources by content) was capped on CE to 10
scripts and 3 each of flows/apps/resources, with an "EE feature" warning
in the UI. It is now a full CE feature: the CE result caps are lifted to
match the previous EE limits (10000 scripts, 1000 each of the rest) and
the EE warning is removed.

Fixes WIN-2218

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 11:30:23 +02:00
Ruben Fiszel 50f1375f18 chore(main): release 1.765.0 (#10223)
* chore(main): release 1.765.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-07-21 20:15:14 +02:00
Diego Imbert ec6324409d fix(s3): support instance-policy credentials in object storage tests (#10238)
* fix(s3): cache ambient aws credentials and surface credential chain errors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCczeVBDhDnLXWqxPWw6od

* fix(frontend): clarify that object storage connection tests run on the server

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCczeVBDhDnLXWqxPWw6od

* fix: render test connection tooltip in popup and harden cache test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCczeVBDhDnLXWqxPWw6od

* better doc

* fix(frontend): correct tooltip wording, tests run from the executing worker

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCczeVBDhDnLXWqxPWw6od

* fix(s3): single-flight ambient credential provider creation

* fix(frontend): clarify object storage tests run on the server process

* fix(frontend): make instance object storage test tooltip provider-neutral

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-07-21 20:02:34 +02:00
hugocasa 2ce21c9ef8 feat(git-sync): enable per-item promotion mode on dev workspaces (#10205)
* feat(git-sync): enable per-item promotion mode on dev workspaces

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

* style: keep unrelated git-sync Alert copy at its original wrapping

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

* fix(git-sync): fall back to parent_path on empty deploy path + bump ee ref

computeGitSyncDeployBranch used ?? so a backend-serialized empty path (rename out of the repo filter) skipped the deploy branch and could commit to the tracked base; use || to fall back to parent_path like the backend. Bumps ee-repo-ref for the single-object promotion_open_prs fix (windmill-ee-private#679).

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

* fix(git-sync): route dev-promotion non-branchable objects off the tracked base

user/group objects (and any unresolvable ref) returned null in promotion mode, so a dev-workspace deploy pushed them straight to the parent's tracked branch. Fall back to the dev's env-label branch instead; the backend opens no PR for them (isolated, not promoted).

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

* feat(git-sync): dev-workspace promotion via a toggle on the inherited repo

A dev workspace reuses the single repo it inherited from prod: a 'Promote to prod via Git' toggle flips it between sync mode (deploys to the dev branch) and promotion mode (per-item wm_deploy/** PRs to prod), with a per-item/per-folder sub-toggle. Removes the redundant separate-promotion-repo setup for dev workspaces.

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

* test(git-sync): dev-promotion regression test + widen git_sync_e2e path filter

Adds a CLI integration case covering dev-workspace promotion (script -> wm_deploy branch; user/group -> env-label branch, main never touched). Widens the git-sync-test.yml relevance filter to the deploy-branch derivation, git-sync guard, and CLI git-deploy files so the e2e suite runs on PRs like this one.

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

* fix(git-sync): gate dev promotion toggle on EE, fix card mode + workflow path filters

Codex review: (1) show the dev promotion toggle only under an active EE license and revert the optimistic save if the backend rejects it; (2) derive the dev card's display mode from use_individual_branch so promotion copy shows in promotion mode; (3) mirror the new relevance paths into the workflow's top-level push/pull_request filters so it actually triggers.

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

* fix(git-sync): only use the single-card dev promotion UX when the dev has one repo

Codex review: an attached dev workspace keeps its own repositories rather than inheriting prod's. Gating the single-card + toggle + hidden-secondaries UX on repositories.length <= 1 makes a multi-repo attached dev fall back to the normal layout, so no active repo is hidden and an unrelated repo isn't presented as prod's promotion target.

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

* fix(git-sync): runtime EE-plan gate for promotion mode, consistent with auto-pull/PR

Codex review: promotion mode only had the CE compile rejection, while auto-pull and PR creation runtime-gate on the active plan (check_git_sync_ee_license). Add check_promotion_license and call it from both edit_git_sync_config and edit_git_sync_repository, plus the matching CE rejection on edit_git_sync_config so the two endpoints are symmetric. Promotion is now gated like every other git-sync EE setting.

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

* fix(git-sync): dev promotion must reuse the parent workspace's repository

Codex review: repository count doesn't prove a dev inherited prod's repo — an attached dev keeps its own. check_dev_promotion_targets_parent_repo resolves the promotion repo's URL and rejects enabling promotion unless it matches one the parent (prod) tracks, so branches/PRs can't target an unrelated repository. Called from both git-sync edit endpoints.

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

* fix(git-sync): dev promotion save-time check uses shared parent-repo matcher (url+branch)

Delegates to windmill_common::git_sync_ee::dev_promotion_target_matches_parent so the settings gate and the deploy-time safety net share one url+branch identity check. Bumps ee-repo-ref for the EE deploy-time enforcement.

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

* chore: bump ee-repo-ref for private resolve_repo_url_and_branch

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

* chore: bump ee-repo-ref for promotion-target matcher authz doc

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

* chore(git-sync): bump hub scripts to gitsync-cli versions, fix promotion tooltips

Point LATEST_GIT_SYNC_SCRIPT_PATH (28790 -> 28796) and
GIT_SYNC_PULL_SCRIPT_PATH / gitInitRepo (28789 -> 28795) at the hub
versions pinning windmill-cli@1.763.1-gitsync.0, which carries the
dev-workspace promotion routing. Slugs unchanged, so the GitHub-App
token check and hub script cache are unaffected.

Tooltips: enabling promotion pushes a PR-ready wm_deploy/** branch;
Windmill only opens the pull request itself when automatic pull
requests are enabled. Reword both toggles to stop promising a PR.

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

* fix(git-sync): dev promotion mirrors to the env-label branch, PR toggles exclusive by branch type

Bump ee-repo-ref for the dispatcher changes: a promotion dev's deploys
now also push to its env-label branch (one extra mirror job per batch,
users/groups mirror-only), and `fork_open_prs` no longer applies to a
dev in promotion mode where `promotion_open_prs` governs.

Frontend: the fork-PR toggle tooltip states its actual coverage
(wm-fork/** and the dev branch of a dev workspace) and that a promotion
dev's own pull request toggle takes over for wm_deploy/** branches.

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

* fix(git-sync): reject dev promotion on pre-28796 pinned sync scripts

An older pinned sync script bundles a CLI that force-disables per-item
branches on every fork, so enabling promotion on a dev workspace with
such a pin would silently keep deploying to the env-label branch. Both
git-sync edit endpoints now reject the combination with an actionable
error; the EE dispatchers (via ee-repo-ref bump) demote inherited
configs to promotion-off semantics so markers, branch keys and the
mirror match the branch the CLI actually pushes. Roots and auto-managed
repositories are unaffected.

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

* fix(git-sync): serialize dev promotion toggle saves

The promotion and per-folder toggles persist immediately via whole-repo
saves; leaving them interactive while one is pending lets rapid flips
race, and the earlier save (enabling runs extra backend checks) can
commit last, silently reversing the state the UI shows. Both toggles now
disable while a save is in flight.

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

* fix(git-sync): lock auto-PR toggle during promotion save, rename-out branch routing

Frontend: the automatic-PR toggle is revealed by the promotion toggle's
in-flight save; an edit made mid-save was absorbed into the saved
baseline without reaching the backend. It now disables during that save.

EE (ee-repo-ref bump): dispatcher debounce/concurrency keys and PR
markers follow the CLI's parent_path fallback for rename-out items, so
their wm_deploy/** branches debounce per-branch and open their PR.

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

* chore(git-sync): condense comments to durable constraints

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

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

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

Previous ee-repo-ref: c2cd718cb53d234f909f485bd7cd43ed9605ffd1

New ee-repo-ref: 8bf73f803158bcbf7b8d55a36f4a1ebfcc1bbcd9

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-07-21 17:07:52 +00:00
Guilhem 9739d5a2c2 feat: unified read-only diff chat tool (drafts, fork vs parent, search) (#10211)
* feat(frontend): unified `diff` chat tool with cached snapshot, fork mode, and search

One read-only global-chat tool for every comparison: drafts vs deployed
(workspace index + per-item unified patches over stable YAML), deployed
fork vs parent workspace (against="parent_workspace", sharing the fork
banner's compareWorkspaces fetch through a single-flight store), and a
literal grep over changed diff lines. Multi-file raw apps split into
per-file text patches with folder-style index children and per-file
reads. Patches are materialized once into a per-workspace cache keyed on
draft created_at / comparison ahead-behind markers and the workspace
drafts version, so repeated queries never refetch unchanged content.

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

* fix(ai_evals): teach the mock draft backend what the diff tool reads

The diff tool reads drafts through the get_draft overlay, the drafts
listing's draft_only flag, and per-row created_at change markers — none
of which the benchmark mock modelled (fixed timestamp, always
draft_only, overlay ignored), so in evals every draft looked absent and
the model looped to max turns. Mirror production: monotonic
deterministic created_at bumped per upsert, draft_only computed against
the deployed stores, and draft/no_deployed overlays on script/flow/app
reads (404-shaped not-found). Also drop the diff case's judge items
about conversation content the judge never sees — tool usage is already
enforced deterministically.

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

* fix(ai): diff tool reads unsaved editor state instead of going stale

The pre-diff flush honors the auto-save toggle (a read-only tool must
not persist parked edits), which left a gap: with auto-save off — or
after a failed save — the persisted draft the diff reads is stale, and
a brand-new editor-only draft looks absent. Item reads now detect
unflushed parked edits (hasUnsavedDisabledChanges / failed save state)
and diff the in-memory editor value directly, bypassing the snapshot
cache (it must only hold persisted state) with an explicit unsaved-
changes note; index and search modes warn which items' unsaved edits
they exclude. Local values are canonicalized onto the persisted draft
shape so they never diff noisily against the deployed side.

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

* feat(ai): invalidate diff cache the moment any draft write lands

The snapshot cache leaned on time windows (5s listing throttle, 15s
read reuse) to notice writes it didn't trigger itself — an editor
autosave landing between two diff reads could serve the pre-edit patch.
The syncer now exposes onAnySaved (fires for landed upserts AND
deletes, all keys), and the snapshot subscribes once: a landed write
marks exactly that item's patch stale and expires the listing throttle,
so the next read refetches regardless of any reuse window.

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

* fix(ai): reject the diff file arg on single-document items

Passing file for a script/flow/classic-app diff was silently ignored
and returned the whole patch — an explicit error steers the model to
call again without it. Also declares the file arg on the item handlers'
signatures it was already flowing through.

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

* fix(ai): surface empty-file additions/deletions in app diffs

An empty file appearing or disappearing produces no text patch, so the
per-file split dropped it — a draft whose only change was such a file
read "unchanged". Presence changes now keep their added/deleted entry
(patch '', 0 lines), render as "(empty file)" in summaries, and a file
read states the presence change instead of an empty window.

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

* fix(ai): include classic-app drafts in the diff index and fix ++/-- search

itemTypeForKind now maps classic `app` draft rows to the chat app type
(mirroring the read path, which already pairs app/raw_app), so their
diffs materialize in the index and search instead of reporting "not
addressable". Changed-line search is hunk-aware: `---`/`+++` file
labels only occur before the first @@ marker, so a changed source line
like `++counter` (rendered `+++counter`) now matches instead of being
mistaken for a label.

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

* fix(ai): mask every variable value in chat diffs; compare classic apps value-to-value

Variable VALUES never reach a tool result — the chat-wide invariant
read_workspace_item enforces, not just for secrets. Draft-mode diffs
mask both sides with a placeholder pair that still marks WHETHER the
value changed; fork-mode masks at fetch (and still never decrypts);
item reads carry an explicit note. The former secret-only flag is now
valueMasked.

Classic-app drafts hold the bare grid value while the deployed row
nests it beside summary/policy — diffed raw, a one-field edit read as
a whole-document rewrite. Both sides now reduce to { value } via
classicAppDraftValue (pure, unwraps legacy wrapped drafts), which also
cleans the CompareDrafts drawer for classic apps.

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

* fix(ai): honest secret-draft reporting, classic-app metadata split, glob-safe file subjects

A secret variable's sides are both masked upstream, so an empty patch
cannot prove the value is unchanged — such drafts now report
"cannot be compared; may differ" (valueUncomparable) instead of
"matches deployed", in the index and item reads.

Classic-app drafts mirror summary/draft_path into the bare grid while
the deployed row keeps summary as a column: sides now reduce to
{summary, value} via classicAppDraftParts, applied to both sides, so a
summary edit diffs as one and draft-only markers never pollute the
grid diff.

Raw-app search subjects strip the file key's leading slash so
slash-anchored globs like f/x/*.tsx match.

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

* fix(ai): classic-app local edits, comparison-relevant fork fields, conflicts as unflushed

The chat app type spans two draft kinds: item mode now flushes and
probes both raw_app and classic app keys, and the flush sweep includes
classic-app editor cells (kept out of GLOBAL_DRAFT_KINDS so
clearGlobalDrafts never clears an open classic editor).

Fork projections gain the fields the backend comparison counts that
getItemValue drops: flow schema (with a taxonomy-agnostic inline-hash
strip) and resource-type description/format_extension/is_fileset.
Folder display_name is not exposed by the API's Folder type, so it
cannot be projected.

A conflicted save leaves its payload parked with state 'none', so
index/search now count conflicts among unflushed paths and say so in
their warnings.

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

* fix(ai): staged app renames diff as path; flush classic-app cells at their real keys

A staged rename (draft_path) changes where deploy lands an app, so both
app kinds now compare `path` on both sides — a rename-only draft diffs
instead of reading "matches deployed". classicAppDraftParts returns the
staged path separately from the grid.

Item mode resolves each draft kind's own storage path and additionally
asks the listing which row owns a friendly/renamed path — a renamed
classic app's cell lives at its ORIGINAL storage path, which only the
listing knows — so pending/failed/auto-save-off edits are flushed and
probed at the real keys. The appDiffSides rationale comment is
compressed to the repo's four-line limit.

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

* fix(ai): never claim folder parity the API cannot prove

folder.display_name exists only as a DB column — no folder endpoint
returns it — so an identical projection cannot prove a fork folder
matches its parent. Fork index and item reads for folders now say the
display name is not exposed and may be what differs, instead of
"content matches parent". Exposing the field on getFolder is a backend
follow-up.

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

* fix(ai): gap-free patch pagination; forced-fresh comparisons never join older fetches

When the char backstop cut inside a patch window, the continuation
offset still pointed past the requested window — silently skipping the
undelivered lines forever. windowPatch now cuts at the last complete
line and continues exactly there (a single over-budget line is
delivered truncated and stepped past so pagination always advances).

fetchWorkspaceComparison treats an in-flight request as being as old
as its start: maxAgeMs now gates joining it, so a freshness-forced
post-mutation read (maxAgeMs 0) always issues its own fetch instead of
adopting a tally that began before the mutation, and a superseded
request can no longer clobber a newer cached result.

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

* fix(ai): generation-ordered comparison writes; path-only fork reads for enum-less kinds

Concurrent comparison requests can share a Date.now() value, letting a
superseded request's late result overwrite a newer one and be reused
for 30s — cache writes are now ordered by a monotonic request
generation. Test pins the same-millisecond race with the newer request
resolving first.

Fork comparison kinds outside the chat type enum (folder,
resource_type, …) were listed and even advertised as readable but no
call could reach them: a fork item read without `type` is now a
path-only wildcard (ambiguous paths list their kinds and ask for
type), messages label entries by their comparison kind, and pending
index lines for enum-less kinds advertise the path-only read.

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

* fix(ai): grid-based wrapper detection, comparison invalidation on mutations, multi-kind wildcard reads, honest hidden-diff summaries

The classic-app wrapper heuristic keyed on metadata keys the editor
mirrors into every bare grid — a grid with a component named `value`
was reduced to that component. `grid` presence is the discriminator: a
bare App always has it, a legacy wrapper never does.

invalidateWorkspaceDrafts now also drops cached fork comparisons for
the workspace, so the FIRST post-deploy fork read cannot reuse a
banner-prewarmed pre-deploy tally (the snapshot-baseline check only
covered subsequent reads).

Wildcard fork reads return a section per matching kind instead of an
unactionable "pass type" for kinds the chat type enum cannot name, and
the fork index never summarizes ACL-hidden differences as parity —
hidden counts stay directional (a conflicted item counts in both).

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

* fix(ai): address cubic review batch — invalidation scope, races, edge output

Comparison cache: invalidation matches either side of the pair (a
parent deploy moves its forks' tallies), fences in-flight requests
(no new joins, late results rejected via a per-key generation floor),
and the map is LRU-capped. Eviction moves from every drafts-version
bump to deploy success only — draft saves never move the deployed
tally. Fork snapshots also baseline the PARENT's drafts version.

Draft materialization carries a stale-generation token so a save
landing mid-fetch discards that run's pre-save result instead of
repopulating the invalidated entry; a save/delete also expires the
fork cache's hasLocalDraft join. onAnySaved listeners are
error-isolated (a throwing listener must not mark a committed save
failed) and the pagehide keepalive flush notifies them on dispatch.

Output edges: folder fork lines drop the empty parenthetical, and a
patch-window offset past the end reports itself instead of an
impossible range. The eval case pins the diff call's path argument.

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

* refactor(ai): one fencing primitive per cache instead of per-surface races

Review rounds kept finding pairwise races between async producers and
invalidation — each patched with its own fence. Replace the class:

- diffSnapshot: a per-workspace mutation epoch, bumped by every
  invalidation. Both reconcilers run a bounded retry loop — joiners
  re-validate after awaiting, producers refuse to store results whose
  inputs predate a mutation. Covers in-flight listing adoption and
  pre-deploy fork tallies in one mechanism.
- workspaceComparison: per-WORKSPACE generation floors (either side of
  a pair). Any request started before an invalidation is fenced from
  joining and from landing in the cache — including superseded
  requests the inflight map no longer tracks.

Also: delete_workspace_item invalidates comparisons like deploy does
(deployed state moved), and empty FILTERED indexes say the filter
matched nothing instead of claiming workspace/fork parity.

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

* fix(ai): invalidate comparisons on every direct deploy; keep secret caveat with metadata changes

Direct chat deploys (schedule/trigger/resource/variable/app) bypass
deployDraftToWorkspace and never evicted cached fork comparisons — the
shared deploy tail now invalidates before the fallible draft cleanup.

A secret variable whose metadata also changed produced a non-empty patch
that silently dropped the value-uncomparable caveat; item reads, the
index, and fork sections now keep the caveat alongside the patch.

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

* fix(ai): scope diff caches to the authenticated identity; derive fork freshness from the comparison store

An SPA logout/login left workspace-keyed diff caches (per-user drafts,
permission-filtered fork patches) readable by the next account — both
cache modules now wipe on identity change, with a global generation
floor fencing requests started under the previous account.

The fork snapshot stamped its own fetchedAt over a comparison that
could already be near expiry, compounding the two 30s windows, and
survived comparison-store invalidation when draft cleanup failed after
a deploy. It now carries the comparison's own fetchedAt/generation and
stops reuse the moment the store fences it. Delete-item invalidation
moved before the fallible draft cleanup for the same reason.

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

* fix(ai): fence fork-reconciliation joins across account switches

ForkCache lacked the epoch stamp WorkspaceCache carries, so a joiner
arriving after an identity change (or any epoch bump landing before it)
compared its own post-bump epoch against itself and adopted the old
producer's in-flight tally. The cache now records its producer's epoch
for the joiner and reuse gates, and an identity change also discards
the in-flight reconciliation maps so no cross-identity join exists.

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

* fix(ai): surface swallowed fork-side fetch failures; include conflicted editor edits in item diffs

The shared getItemValue reads {} for any failed fetch, so a transient
API failure on a fork side rendered as a fabricated one-sided diff (or
parity when both sides failed). A fork side is only fetched when the
comparison lists it as existing, so an empty read now raises and shows
as a fetch-error entry.

Item reads promised conflicted local edits (the index says so) but the
local-override branch only covered autosave-off and failed saves — a
conflict silently fell back to the persisted draft. Conflicts now read
the in-memory editor value too, with a caveat naming which side is
shown either way.

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

* fix(ai): report failed diff materializations as unsearched instead of silently omitting them

A side-fetch failure left an index entry with status 'error' and no
patch; diff search skipped it and still presented definitive no-match
or complete-count results. Failed entries are now listed in a warning
naming what was not searched.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 17:06:32 +00:00
Guilhem d6cf1ef987 feat: attach text files to chat messages, read on demand via file tools (#10215)
* feat: attach text files to chat messages, read on demand via file tools

* fix: resolve name collisions and sync message files with the transcript

* refactor: render message file chips with the shared context badge

* fix: suffix same-name attachments, sync registry on compaction, bound file bytes

* feat: carry message files across compaction, drop them from the roster

* refactor: merge context, dom and file badges into one wrapping row

* fix: dedupe identical attachments and make badge list keys collision-proof

* fix: name carried files inside the collapsed summary instead of badges

* fix: serialize registry reconciliation and correct the attachment budget

* fix: reserve pending bytes so overlapping reads honor the attachment budget

* fix: share attachment byte budget across concurrent composers

The bottom composer and the edit box are both mounted while editing an
earlier message, but each enforced MAX_CONVERSATION_FILE_BYTES against
only its own staged files plus the transcript. Two attaches near the cap
could each pass independently and overflow the persisted transcript.

Each composer now publishes its staged bytes (committed attachments +
in-flight reads) to the manager, keyed per instance, and the attach-time
budget subtracts every other live composer's stage. A message an open
composer is editing is skipped from the transcript sum since that
composer's stage stands in for it.

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

* fix: charge an edited message at its persisted size until the edit commits

An edit is not committed until send, so the edited message's persisted
attachments return if the edit is cancelled. Substituting only the edit
box's (possibly emptied) stage let the always-mounted bottom composer
claim headroom that vanishes on cancel: remove the near-limit files in
the editor, fill the bottom draft, cancel, and the persisted transcript
overflows MAX_CONVERSATION_FILE_BYTES.

attachmentBytesExcluding now charges a message another composer is
editing at max(persisted size, editor stage), so freed space only
becomes available once the edit actually commits.

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

* fix: preserve a message file's exact name when a session row clashes on rebuild

syncMessageScoped rebuilds message-scoped rows from the transcript through
collision-suffixing addFiles. Session rows load first (on restore), so one
holding a wanted name pushed the rebuilt message row to a "(2)" suffix while
the persisted prompt still referenced the bare name — get() then resolved the
reference to the session asset and the model read the wrong content.

Free the name from the conflicting session row before the rebuild so the
message row reclaims its exact reference. The rename is in-memory only: it is
deterministic and re-applied on every load, and the session roster is
regenerated live each send, so the session asset stays addressable under the
suffix without a persisted-record update.

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

* fix: reserve resent files across the edit-resend gap

The edit box unmounts (dropping its staged-byte entry) the instant the user
submits an edit, but restartGeneration then awaits registry sync and beforeSend
before the optimistic bubble lands in the transcript. During that gap the
resent files were reserved nowhere, so the always-mounted bottom composer could
attach into the temporary headroom and the resend would then push the persisted
transcript past MAX_CONVERSATION_FILE_BYTES.

restartGeneration now reserves the resent files' bytes in shared manager state
before the transcript slice; sendRequest releases the reservation once it
installs the bubble (or restores the files to the composer on a pre-install
bail). The reservation bridges the gap so the budget stays honored throughout.

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

* fix: re-target an in-flight index when a session row is renamed on rebuild

#freeNameForMessageRow renames a restored session row so a same-named message
attachment can reclaim its exact name. But the row's #indexFile, started under
the old name during restore, stamps via #patchFile(oldName, file) — after the
rename that no longer matches, leaving the row stuck 'indexing' so read_file
rejects it and search_files excludes it.

Re-kick #indexFile under the new name when the renamed row is still indexing;
the stale completion then no-ops (its name is gone).

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

* fix: release resend reservation on local-command sends; surface compaction-orphaned files

Two follow-ups to the message-attachment work:

- A resend edited to /clear or /compact runs the local-command path and returns
  before installing a bubble, so the #RESEND_KEY reservation set by
  restartGeneration was never released and its bytes stayed charged, blocking
  later attachments. Release it on every sendRequest path that exits before
  install (via #releaseResendReservation).

- Drop-oldest compaction (summary fallback) removes API messages without a
  summary, so a folded message's `## ATTACHED FILES` reference no longer reaches
  the model even though the file stays readable. The roster omits message-scoped
  files, so the model loses awareness of them. orphanedMessageFileNames() finds
  message files whose only referencing message went negative-index, and the
  roster now advertises them (summary compaction already carries its own).

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

* fix: key the resend reservation per send so unrelated sends can't release it

The resend reservation used a single shared key, so a normal or concurrent
sendRequest released it at its own install/early-return even though it didn't
own it — dropping an in-flight resend's reservation and letting attachments
staged before the resend bubble lands under-count against the byte cap.

restartGeneration now mints a per-resend token, reserves under it, and threads
it through sendRequest as resendReservationKey; releases act only on that key.
A send with no token (every normal send) releases nothing.

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

* fix: reserve the whole wanted-name set when freeing session rows on rebuild

Freeing a session row for a message-scoped rebuild suffixed it against current
rows only. With the transcript referencing both notes (2).md and notes.md and a
session row named notes.md, freeing notes.md renamed it onto notes (2).md — also
a wanted reference — so that message row cascaded to notes (3).md while its
persisted reference stayed notes (2).md, and read_file returned the session file.

#uniqueName now accepts a reserved set; the rebuild frees each session clash
clear of the entire wanted-name set, so every message row reclaims its exact
reference regardless of collision order.

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

* fix: orphan summary-carried files when drop-oldest removes the summary

orphanedMessageFileNames classified summary rows as always-live, but a summary
carries its folded files' reference on its own API message. When summary
compaction succeeds and a later summarization fails, drop-oldest can remove that
API message, yet the files stayed off the roster — so the model lost their
reference even though they remained readable.

The summary display row now tracks its API index (slot 0 at creation, re-based by
drop-oldest); a negative index reads as "counterpart gone" and its files move to
the roster, mirroring user-message orphans. The index is used only for orphan
detection, never as a restart target.

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

* fix: reserve outgoing file bytes for normal and queued sends too

The resend reservation covered edit/retry, but a normal or queued send has the
same gap: the composer (or queue) clears its files the instant sendRequest is
called, dropping the staged-byte entry, while sendRequest then awaits
regrantLocked()/refreshFolders() before the bubble lands. With a locked or slow
linked folder the composer stays enabled, so a fresh drop can spend the same
headroom and overflow the 5 MB cap once the first bubble installs.

Generalize the reservation: sendRequest mints a per-send token and reserves the
outgoing files' bytes just before attachment upkeep (reusing restartGeneration's
token when present), and releases it on install or any pre-install exit.

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

* refactor: join attachment layers on a stable content-hash id

Sixteen review rounds kept finding the same bug family: a message file's
identity was its display name, joined by hand across the registry, the
transcript, the prompt reference, and the render keys — every same-name
collision or interleaved rebuild made two of those copies disagree.

Give each message attachment a deterministic id, attachedTextFileId(name,
content) — a synchronous pure-JS hash (works on plain-HTTP deployments) whose
exact value is pinned by test, since persisted transcripts reference it. The
prompt and roster list the id, the file tools resolve id-first (bare names
remain a fallback for legacy chats and session links), and pre-id transcripts
hydrate on load by recomputing the same hash — no migration state.

Names become display-only and may collide freely, which deletes the machinery
that defended them: the suffix-readback registration loop, session-row renaming
on rebuild (#freeNameForMessageRow, reserved-set #uniqueName), and the
reconciler's serialization guards (#syncSeq/#syncChain) — syncMessageScoped now
compares ids instead of awaiting blob text, so it is synchronous and cannot
interleave. A same-name clash within one draft gets a composer-local courtesy
rename before the id is minted.

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

* fix: resolve bare names to session rows and scope id searches to one row

A bare name is the roster's namespace: session links are advertised by
filename and have no other handle, so a same-named message attachment (which
is addressed by id) must not shadow them. get() now resolves session rows
first, keeping the message-row name lookup only as the fallback for
transcripts persisted before ids existed.

search_files restricted an id reference by mapping it back to the display
name and letting the worker filter on it — same-named files were then
searched together under one label. The tool now passes the resolved row
itself, so an id-scoped search can only ever hit its own file.

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

* fix: label search hits with resolvable ids, normalize attach batches at commit

An unscoped search_files reported hits by display name only. Names may
collide, so a hit could not be mapped back to the row that produced it —
a follow-up read_file on the bare name could return a different same-named
file. Rows carrying an id are now labeled `name (file id: …)` in hit lines,
so every hit names the reference that resolves to exactly that row.

addTextFiles normalized (deduped, courtesy-renamed) each file against a
snapshot taken during its read loop. Attach batches overlap, so a file
committed by another batch between reads escaped both checks — duplicate or
same-named unsuffixed entries in one message. Normalization now runs in the
single synchronous commit step against the live list (foldIntoDraft), where
nothing can interleave.

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

* fix: dedupe renamed re-drops in foldIntoDraft, truncate queued file chips

"Same file dropped twice" means same original (name, content), but a
courtesy rename erases the original name — an identical re-drop then missed
the duplicate check and landed as a further-suffixed copy. The dedupe now
also matches entries whose suffix-stripped base name equals the read's name.

Queued file chip labels get min-w-0 so long filenames truncate inside
max-w-36 instead of overflowing.

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

* fix: address cubic review — line counts, chip clicks, reference robustness

Five fixes from the cubic pass:

- The prompt advertised split('\n').length lines, one more than read_file
  reports for newline-terminated files — textLineCount now matches the tool's
  numbering (0 for empty, no phantom trailing line).
- Clicking a sent message's badge opened edit mode (the wrapper's
  click-to-edit), unmounting the preview popover as it opened; the badge row
  now keeps clicks to itself.
- resolve() accepts the composite label rosters and search hits print
  (`name (file id: x)`) — models echo references verbatim, so the printed
  form must resolve.
- fileToAttachedTextFile enforces MAX_TEXT_FILE_BYTES itself (raw size +
  decoded byte length), so no ingestion path can persist an oversized
  attachment past the composer's pre-check.
- Duplicate detection after a courtesy rename now uses an explicit sourceName
  instead of inferring provenance from the display name — a user's real
  `report (2).md` is not a rename of `report.md`.

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

* fix: exact names win over label parsing, commit recheck uses decoded bytes

resolve() parsed any `name (file id: x)`-shaped reference as a printed label,
so a session file literally named that way became unreachable by its exact
name (the dead-id fallback resolved the base name instead). Exact id and
exact-name lookups now run before label interpretation.

Attachment admission and the pending reservation use raw File.size, but the
committed charge is the decoded UTF-8 length — malformed input decodes each
invalid byte to a 3-byte replacement character, so a file passing the 8KB
text sniff could inflate past the conversation cap. The synchronous commit
step now re-checks the live budget against decoded sizes
(admitWithinByteBudget) and drops what no longer fits, with the budget toast.

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

* fix: normalize files folded into the queued message

Repeated submissions during a stream aggregate into one queued message, but
their files were concatenated raw: an identical re-attach duplicated its chip
and ate a slot (possibly displacing a distinct file at the eight-file cap),
and a same-name clash skipped the courtesy rename. The queue now folds new
files through the same commit normalization as the composer — the queued
entry is a message draft like any other.

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

* style: compaction boundary label uses text-normal text-2xs

* fix: fold provenance survives pass-through, dequeued files fold into the draft

foldIntoDraft recorded sourceName only for renames it performed itself, so a
file already courtesy-renamed by the composer lost its provenance when folded
into the queue — a later re-attach of the original escaped dedupe. Folds now
compose: the original source name rides through every fold, and dedupe
matches on it.

dequeueMessage restored queued files into a possibly-populated composer by
raw concatenation; prependText now folds them like every other draft
aggregation (dedupe, courtesy rename) before applying the cap.

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

* refactor: one MessageDraft owns the lanes that ship with a send

Review rounds kept finding the same P2 shape: an aggregation point where
files join a draft (composer commit, queue append, dequeue restore) that
forgot one of the draft rules — fold dedupe, courtesy rename, slot caps,
byte admission, lanes moving together. The rules existed only as convention
re-implemented per site.

MessageDraft owns them once: text, pastes, images, and text files live on
one object with addFiles (fold + optional decoded-byte admission + cap),
addImages (cap), prepend (restore-merge), replaceIfEmpty (occupied-guard
restore), and take (all lanes leave together). The composer holds a draft
instead of four state vars, and the queue is a draft behind the existing
queuedMessage/queuedImages/queuedFiles accessors — an aggregation point can
no longer skip a rule, because there is no raw array to concatenate into.

Deliberately not moved: @context and DOM picks (ContextManager owns their
lifecycle), the conversation byte budget's cross-composer ledger (store-side
follow-up), and sendRequest's options shape (it decomposes immediately and
is pinned by the manager test suite).

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

* docs: correct the drop-routing comment, condense the budget doc

* fix: address cubic review — name sanitization, drop hold, merge restores

Four fixes from the cubic pass:

- Attachment display names render into model-facing prompt blocks, and OS
  filenames may legally contain control characters — sanitizeAttachmentName
  strips them at attach and again at every prompt-render site (legacy names
  predate the attach-time pass), so a crafted name cannot inject prompt lines.
- Drop routing awaits handle/entry resolution before it can call
  addTextFiles; a send during that window landed the dropped files on the
  next message. The drop handler now holds sending (holdSendForIngestion,
  taken before the first await) until routing completes.
- Restoring a taken queue after a failed auto-send replaced the queued draft
  wholesale, silently losing a follow-up queued during the preflight. Both
  #restoreQueue and the unmounted-input requeue now merge via draft.prepend —
  the taken entry's text lands above the newer follow-up.
- restartGeneration validated the API restart index only after reserving the
  resend bytes and truncating the transcript, so a stale index threw with the
  reservation leaked and the display transcript half-mutated. The index is
  resolved and validated before anything is touched.

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

* fix: restored drafts keep chronological priority, store names stay resolvable

A failed auto-send's restore folded the taken (older) draft's attachments
AFTER a follow-up queued during preflight, so at the slot caps the older
attachments silently dropped despite the text landing first — and only one
entry's pinned context survived. prepend() now puts the restored lanes ahead
(the cap drops the newest additions) and #restoreQueue unions both pinned
contexts by identity.

Session filenames were sanitized only at prompt render, so an id-less file
whose stored name carries control characters was advertised under a name
that resolve() could not match. Names are now sanitized at every store
row-creation site (attach, folder expansion, refresh, and persisted-row
restore for pre-sanitization records), making the advertised name the stored
name everywhere — render-site sanitization remains as defense in depth.

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

* chore: update ee-repo-ref to aaa6cb89b05b76139252c64f057e53b94d12ac60

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

Previous ee-repo-ref: 4c08634af953db5c1125b1fb03f5af211fe21db3

New ee-repo-ref: aaa6cb89b05b76139252c64f057e53b94d12ac60

Automated by sync-ee-ref workflow.

* chore: repin ee-repo-ref to main's eb3690a34b (aaa6cb89 needs the unmerged AmqpTrigger OSS companion)

* Revert "chore: repin ee-repo-ref to main's eb3690a34b (aaa6cb89 needs the unmerged AmqpTrigger OSS companion)"

This reverts commit a782e6f1c5.

* refactor: one name seam per scope — claim on the store, sanitize in the fold

Name rules (sanitize, uniquify, dedupe, resolve) lived as convention at every
creation site: four sites carried verbatim copies, two skipped uniquify
(restore, file placeholders — legacy names could collapse to one display
name), and the dedupe check compared the raw name against sanitized stored
names, so re-linking a control-char file added a spurious copy.

Store side: #claimName(raw) = sanitize + uniquify is now the only way a
session row gets its display name; addFiles derives the sanitized name once
at the top of the loop, so dedupe, uniqueness, and the stored row all see the
same string, while relPath and folder keep the raw on-disk keys they must
match. Draft side: foldIntoDraft sanitizes its reads itself instead of
assuming the reader did, making the fold self-contained. Folder names — raw
grouping keys by design — are sanitized at their model-facing render sites
(roster folder lines, not-found listing), mirroring rosterLine.

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

* fix: a display-name collision is not a duplicate

Sanitizing names before the dedupe compare made two DISTINCT files whose raw
names sanitize identically look like re-links — the second was silently
discarded instead of claiming a suffixed name like the restore path does.

Dedupe now means "the same file re-linked": matching stats (size + mtime)
plus a matching name, pre-suffix sourceName, or raw path. #claimName records
the pre-suffix name when uniquifying renamed a row, so re-linking a suffixed
file's original still dedupes. A same-named file with different stats links
as its own row rather than being silently swallowed.

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

* fix: the re-link identity is the raw name, persisted with the row

Dedupe still compared sanitized display names, so two distinct raw names
that sanitize identically collapsed whenever their stats also matched — and
the provenance recorded for suffixed rows lived only in memory, so after a
reload re-linking the original behind a suffixed row stacked another copy.

sourceName now records the RAW pre-sanitization name on every session row
(display names lose information twice — sanitize, then suffix), rides the
persisted record, and is re-derived on restore. Dedupe matches stats plus
raw identity (sourceName, or relPath for folder children) and never compares
display names. #claimName returns to a pure name function; restore claims
the display name from the persisted raw identity.

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

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
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-07-21 17:06:21 +00:00
Guilhem 7ac27c1ef2 fix(frontend): limit compare & deploy rows to the active direction (#10234)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 17:04:15 +00:00
Diego Imbert 7fb8a2e390 fix(parsers): keep s3 asset path suffix verbatim to preserve storage distinction (#10241)
* fix(parsers): keep s3 asset path suffix verbatim to preserve storage distinction

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01An2pTqSmqJd2XwnagvX4kM

* package json

* fix(pipelines): preserve named storage in generated TS/Python S3 URIs

The TS/Python templates emitted `s3:///${s3Key(path)}`, stripping the
leading slash and pinning the URI to default storage. For a named-storage
asset path (`secondary/key`) that produced `s3:///secondary/key`, which
resolves to the default storage with key `secondary/key`, dropping the
named-storage dependency and reading/writing the wrong object.

Emit the path verbatim after `s3://` (matching the DuckDB template) so a
named-storage input/output keeps its storage; identical to the previous
output for default-storage paths. Removes the now-unused `s3Key` helper.

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

* chore(cli): align bun.lock parser versions with frontend

The PR bumped windmill-parser-wasm-asset (1.749.0→1.753.0) and
windmill-parser-wasm-regex (1.692.0→1.764.0) in package.json and the npm
package-lock.json for both cli and frontend, but cli/bun.lock was left
pinned to the old versions. Sync it so the CLI's wasm asset parser (used
by localGraph inference) matches the frontend and deploy-time parser.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-07-21 16:25:06 +00:00
Ruben Fiszel dc5b006e7f fix npm checks 2026-07-21 15:59:26 +00:00
Ruben Fiszel 68debab877 feat(triggers): add AMQP (RabbitMQ) trigger via lapin (#10230)
* feat(triggers): add AMQP (RabbitMQ) trigger using the lapin library

Fixes WIN-2214

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

* chore(triggers): defer AMQP cross-workspace deploy pending utils-internal publish

Revert the amqp_trigger additions to the shared windmill-utils-internal
TriggerDeployKind and the frontend cross-workspace deploy adapter: the
frontend installs the published npm package, which lacks the new kind
until a release is cut. AMQP create/edit/delete/list/sync/capture are
unaffected (they use local types); only cross-workspace deploy/merge of
AMQP triggers waits on the package bump. Also document the at-most-once
ack in the consumer loop.

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

* fix(triggers): address AMQP review — at-least-once ack, workspace cascade, contracts

- ack AMQP deliveries only after successful dispatch; nack+requeue on failure
- add ON DELETE CASCADE workspace FK so amqp_trigger rows are cleaned on
  workspace deletion (and the listener stops)
- fix the /amqp_triggers/test OpenAPI body and add amqp_trigger to
  WorkspaceDiffRow.kind
- register AMQP in the generated workspace trigger tool (create_trigger)
- drop banned $bindable defaults on optional props in the config section
- add build_uri unit tests (encoding, ports, vhost)

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

* fix(triggers): stop AMQP poison-message loop and reconnect on transient drops

Chaos testing against a live RabbitMQ broker showed the previous
nack(requeue) + immediate re-poll spun a tight redelivery loop (~1000
critical-error reports/sec) on a poison message, and any connection blip
permanently disabled the trigger (lapin has no built-in reconnect).

- on dispatch failure: nack+requeue then stop consuming; the listener
  framework re-lists the trigger after its ping goes stale (~15s), backing
  redelivery off to that cadence instead of a tight loop (verified: rate
  dropped from ~1000/s to ~1 per ~26s, message preserved)
- on connection/stream error: stop and let the framework reconnect instead
  of disabling; persistent failures are still disabled via get_consumer
  (verified: a forced connection close now auto-reconnects and resumes)
- finish the AI create-trigger action wiring for AMQP: add amqp to
  CreatedResourceTriggerKind, the action-card registry, and the drawer
  registry so the result card renders and its "Open" action works

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

* fix(triggers): complete AMQP frontend registries and defer merge rows

- add amqp to capturableTriggerTypes (so AmqpCapture mounts), the Runs
  jobTriggerKinds filter, and CLOUD_DISABLED_TRIGGER_TYPES
- wire AMQP into global AI chat mode: TRIGGER_KINDS, the request union,
  writeTriggerSchema, triggerServices, and the draft adapter
- stop emitting actionable AMQP fork-comparison rows (revert amqp_trigger
  from TRIGGER_OR_SCHEDULE_TABLES) since cross-workspace deploy is deferred
  until windmill-utils-internal is published — avoids a deploy that fails
  with "Unknown kind: amqp_trigger"
- use design-system TextInput instead of raw <input> in the config section

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

* fix(triggers): complete AMQP session/draft registries and constrain prefetch

- add amqp to the session-deploy, draft-compare, preview-router, and
  copilot workspace-item registries so AMQP drafts/deploys/nav/path
  resolution work
- include amqp_count in the MoveDrawer attached-trigger rename warning
- replace the raw prefetch <input> with a design-system TextInput bounded
  to an integer 1-65535 (backend u16) and block save on invalid values

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

* fix(triggers): make AMQP disconnect/reconnect consistent with the Kafka trigger

lapin, like rdkafka, has no transparent reconnect, so the AMQP listener now
mirrors the Kafka trigger's explicit reconnect loop instead of relying on the
framework re-list (which disabled the trigger once get_consumer failed on a
sustained outage):

- get_consumer returns cheaply; consume owns a (re)connect loop that retries
  with a 30s backoff, reports a critical error every 10 failed attempts, and
  reports a recovered critical error once it reconnects — never disabling the
  trigger on a connectivity failure
- a consumer/stream error breaks out to reconnect rather than disabling
- dispatch failure still nacks+requeues (at-least-once) with a short backoff
  to avoid a tight poison-message loop, keeping the connection alive

Verified against a live RabbitMQ broker: killing the broker keeps the trigger
enabled and retrying (attempt N), and restarting it auto-reconnects (logs
"reconnected after N attempts") and resumes dispatch.

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

* fix(triggers): complete AMQP capture registries and constrain prefetch contract

- add the 'amqp' case to triggerKindToTriggerType so opening the AMQP editor
  from a capture button no longer throws "Unknown TriggerKind: amqp"
- register AmqpIcon in CaptureTable's icon map and add an AMQP entry to the
  script/flow CaptureButton menu
- bound the OpenAPI prefetch_count to an integer 1-65535 (matches the Rust
  u16) and regenerate clients/prompts
- require a non-empty exchange name when the exchange binding is enabled
- build_uri: fall back to "/" on a blank vhost and bracket IPv6 hosts (+ tests)

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

* feat(triggers): wire AMQP into pipeline graph, git-sync, and preprocessor types

- asset_graph: discover attached amqp_trigger rows and emit an AMQP TriggerEdge
  so AMQP triggers render (and can be opened/deleted) on the data-pipeline canvas
- frontend pipeline graph: add amqp to NativeTriggerKind, the add-trigger menu,
  node presentation, event-trigger set, annotation keywords, and the
  editor/service registrations
- git-sync: add the amqp_trigger include pattern (+ test) so an AMQP git-sync
  deployment stages only its .amqp_trigger.* file, not an unrelated same-path object
- preprocessor starters: add the AMQP event to the generated TS/Python/PHP
  trigger event types (kind/payload/exchange/routing_key/queue_name/redelivered/
  delivery_tag)

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

* fix(triggers): finish AMQP pipeline/parser wiring, prefetch validation, source lists

- fix a stray edit that corrupted the pre-existing MqttTriggerEditor import
  ($lib/... path) in PipelineTriggerEditors.svelte
- reject prefetch_count = 0 server-side in validate_config (RabbitMQ treats 0
  as unlimited) and defensively skip basic_qos(0) in build_consumer (covers
  the capture path that bypasses CRUD validation)
- recognize `// on amqp` in the canonical parser (TriggerSpec::Amqp) and add
  amqp to the CLI non-autorun/event-trigger sets so a pipeline cascade never
  runs an AMQP-only node as a manual root without an event
- add amqp to the preprocessor intro lists and both pipeline AI instructions

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

* fix(triggers): reject zero AMQP prefetch in all paths and finish guidance lists

- extract a shared validate_amqp_options used by both CRUD validate_config
  and build_consumer, so capture configs (which bypass CRUD validation) also
  reject prefetch 0 instead of silently connecting with an unlimited buffer
  (+ unit tests for 0/1/65535/None)
- add AMQP to the main script-writing preprocessor-sources prompt and the CLI
  triggers-skill guidance list

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

* docs(triggers): de-duplicate AMQP prefetch comment and fix GET response text

- keep the zero-prefetch rationale only on the shared validate_amqp_options
  doc; drop the redundant call-site comments
- correct the getAmqpTrigger OpenAPI 200 description ("deleted" -> "retrieved")

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

* chore: update ee-repo-ref to aaa6cb89b05b76139252c64f057e53b94d12ac60

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

Previous ee-repo-ref: 5da5fd65aca9594b2611837a52e4677b544b0380

New ee-repo-ref: aaa6cb89b05b76139252c64f057e53b94d12ac60

Automated by sync-ee-ref workflow.

* chore(migrations): consolidate the four AMQP migrations into one

The table and the three enum ADD VALUE statements (trigger_kind, job_trigger_kind,
draft_kind) are one atomic feature. ALTER TYPE ... ADD VALUE runs inside the
migration transaction on PG >= 14 (Windmill's minimum) since the amqp_trigger
table doesn't reference those enum types, so they can share a single migration
instead of four. Verified applying cleanly in a single transaction on a fresh DB.

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

---------

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-07-21 15:10:29 +00:00
Ruben Fiszel c6fd6da971 repoint slack hubPaths to form-data 4.0.6 versions (#10239)
The image prebundles the hub scripts listed in hubPaths.json by their
pinned (immutable) version. windmill-integrations republished the four
image-cached slack scripts with form-data bumped 4.0.5 -> 4.0.6; point
hubPaths.json at the new versions so freshly built images cache the fixed
lock instead of the stale one.

Fixes part of #10219

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 14:37:10 +00:00
Diego Imbert d24e176816 fix(parser): spurious pg arg inferred from placeholders in comments (#10226)
* fix(parser): ignore pg placeholders in comments, strings and dollar quotes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NXWRDKzswMeDBP5THXpb9Q

* package json + lock

* chore: remove stray root npm lockfiles

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kuUhCPD9ph4ZuFd13SCWJ

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:36:44 +00:00
Diego Imbert b0bf25683b fix(frontend): prevent browser back-swipe navigation over monaco editors (#10229)
* fix(frontend): prevent browser back-swipe navigation over monaco editors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEP1T4UogYABBifdNJUUuQ

* fix: toggle root overscroll-behavior-x on editor hover instead of cancelling wheel events

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEP1T4UogYABBifdNJUUuQ

* refactor: replace per-editor swipe guard action with single global handler in root layout

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEP1T4UogYABBifdNJUUuQ

* style: compress swipe-guard comment to four lines

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q97WNWUT9mntEEVhg6kpzv

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:36:33 +00:00
Diego Imbert 32994df427 fix: return to parent workspace when a fork is deleted remotely (#9898)
* fix: return to parent workspace when a fork is deleted remotely

When a workspace fork was deleted remotely while a user had it open,
reloading stranded them: a regular member got logged out (whoami fails on
the vanished workspace) and a superadmin silently landed on a dead
workspace whose requests 404. Detect the deleted fork on load and redirect
to its parent (or the workspace picker) with a toast instead.

- forkParentMemory.ts: persist a bounded fork->parent map in localStorage
  while a fork is reachable (the parent is unrecoverable post-deletion).
- (logged) layout: record the current fork's parent via an effect.
- root layout: tryRecoverFromDeletedFork detects the vanished fork in
  loadUser and redirects to the remembered parent or the workspace picker,
  reusing the workspace list already fetched on mount.

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

* fix: verify fork existence for superadmins before deleted-fork redirect

* fix: only recover deleted fork on actual 404 from superadmin check

* fix: recover prefixless dev-workspace forks via remembered parent

* fix: use workspace exists check to detect deleted forks

* fix: restore wm-fork- detection for non-member superadmin forks

* fix: record fork parent for non-member superadmin dev workspaces

* Update frontend/src/routes/(root)/+layout.svelte

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

* fix: store fork parent map with null-prototype to handle __proto__ ids

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-07-21 14:35:59 +00:00
Guilhem 9bc1f62128 feat(ai): session chat nits — empty sends, command picker polish, session-state prompt (#10233)
* feat(ai): session chat nits: empty sends, picker polish, session state

* fix(ai): scope empty-send turns to global chat and pin new behavior

* fix(ai): align grouped search nav with display order, enable empty-send button

* fix(ai): fork fallback for unlisted workspaces, section headers across branches

* style(ai): hint-colored 3xs picker section headers, drop inline row descriptions

* style(ai): more spacing between picker sections

* fix(ai): require context elements for empty global-chat sends

* style(ai): no empty bubble for text-free messages

* fix(ai): review round 2 — keyboard tooltip access, requestedMode guard, no display names in prompt

* fix(ai): queue context-only drafts pressed while a response streams

* fix(ai): shared context identity for queued badges and full queue-context union
2026-07-21 14:01:44 +00:00
Guilhem 572d69e5ae feat(ai): open the Compare & Deploy page from chat with item preselection (#10232)
* feat(ai): open the Compare & Deploy page from chat with item preselection

* fix(ai): label the compare link card outside sessions

* fix(ai): scope untracked-chat compare links to explicit items

* style: drop narration comment on compare mask precedence

* fix(ai): match compare items mask against parked live-draft paths

* fix(ai): land maskless-mode compare on the view holding the masked drafts

* fix(ai): honor explicit fork mode over the draft-mask heuristic

* docs(ai): describe mask-aware compare mode auto-pick

* fix(ai): match legacy app fork diffs under their identity mask key
2026-07-21 12:58:18 +00:00
Ruben Fiszel 28966bdbf1 fix(frontend): curl fallback for +Variable/+Resource in bash sandbox mode (#10235)
* fix(frontend): use curl fallback for +Variable/+Resource in bash sandbox mode

When a bash script uses `# sandbox <image>` or `# docker`, the body runs
inside a custom container image that does not have the `wmill` CLI
installed, so the `wmill variable get` / `wmill resource get` snippets
inserted by the +Variable and +Resource pickers fail.

Detect `# sandbox`/`# docker` in the editor code and insert a curl-based
snippet using the BASE_INTERNAL_URL, WM_TOKEN and WM_WORKSPACE env vars
(available in sandbox) instead.

Fixes WIN-2215

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

* fix(frontend): mirror worker grammar and use curl/wget fallback in sandbox mode

Address review of the bash sandbox picker fallback:

- Extract detection into `bashRunsInCustomImage`, mirroring the worker's
  BashAnnotations grammar (leading comment lines only; `# sandbox <image>`
  or bare `# docker`). A bare `# sandbox` is the nsjail-bash modifier that
  still runs on the worker rootfs where `wmill` is available, so it now
  correctly keeps the `wmill` snippet. This also fixes the substring
  false positives (`# sandboxed`, `# docker` in prose/body) and false
  negatives (`#sandbox <image>`).
- The default `# sandbox alpine:latest` image ships busybox `wget`, not
  `curl`, so the snippet now tries `curl` then falls back to `wget`.
- `variables/get_value` returns a JSON-quoted string; strip the outer
  quotes with `sed` so the sandbox snippet matches the `jq -r .value`
  output of the non-sandbox branch. Resources return JSON either way.
- Add focused unit tests for the detection grammar.

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-07-21 10:53:52 +00:00
Ruben Fiszel fd51d40f12 feat(pipelines): catalog declared measures and dimensions (#10190) 2026-07-21 07:34:44 +02:00
Ruben Fiszel 858d9a5527 chore(main): release 1.764.0 (#10196)
* chore(main): release 1.764.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-07-20 22:56:04 +02:00
Ruben Fiszel ddec2abbb3 feat(jobs): cap total queued jobs per workspace on cloud (#10218)
* feat(jobs): cap total queued jobs per workspace on cloud

A workspace could flood the queue with an unbounded number of jobs across
many concurrency keys and scripts (or keyless jobs), which the per-key
cap from #10197 does not bound. Add a companion instance-wide ceiling on
a workspace's total queued jobs.

check_workspace_queue_cap rejects a push once the workspace has
WORKSPACE_MAX_QUEUED_JOBS (default 20000, superadmin-configurable, 0 to
disable) jobs queued, cloud-only and runtime-gated on CLOUD_HOSTED like
the per-key cap. It runs on every push, so it applies even to premium
workspaces and catches parallel for-loop floods. Jobs already queued
still drain; only new pushes past the ceiling are rejected, so an
in-flight flow only fails to push further work while at the ceiling.

The setting loader self-gates on CLOUD_HOSTED so it is never loaded off
cloud, from initial load or a settings-change reload. The depth count is
bounded by the cap via LIMIT so a runaway backlog never costs an
unbounded scan on the push path.

* docs(jobs): note the workspace cap is a soft ceiling and the depth helper is count-only

Records the two review points as constraints: the cap does not serialize
admission (a soft ceiling by design, like the per-key cap), and
workspace_queue_depth is pub only for the test, returns a count not job
data, and leaves authorization to the caller.
2026-07-20 22:09:59 +02:00
hugocasa 11fda89b52 feat(telemetry): generic feature-usage telemetry with AI session metrics (#10200)
* feat(telemetry): add generic feature_usage table and batched logging endpoint

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

* feat(telemetry): log AI session usage events and document them in telemetry settings

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

* fix(telemetry): use escape sequence instead of literal NUL bytes in buffer key

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

* fix(telemetry): validate dimensions, decouple retention, keepalive flush

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

* fix(telemetry): allowlist feature-usage dimensions and index retention scans

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

* fix(telemetry): pin tool-name allowlist and deploy session attribution

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

* refactor(telemetry): route AI chat usage through feature_usage and drop ai_chat_usage

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

* refactor(telemetry): slim dimension validation to registered kinds plus key shape

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

* fix(telemetry): backfill ai_chat_usage into feature_usage before dropping it

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

* fix(telemetry): disclose provider and model identifiers in telemetry settings text

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

* fix(telemetry): issue all flush chunks before awaiting so pagehide keeps them

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

* chore: update ee-repo-ref to 6306c072a50937ea9af44a5bcf42345543207486

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

Previous ee-repo-ref: 964f242a0eb44db7f7d26636cc8d76aeabea2b73

New ee-repo-ref: 6306c072a50937ea9af44a5bcf42345543207486

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Fable 5 <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-07-20 20:56:57 +02:00
Ruben Fiszel 11bb37d7ca fix(forks): show workspace settings link in sidebar for fork creators (#10216)
* fix(forks): show workspace settings link in sidebar for fork creators

PR #10166 grants a fork's creator developer-management access to the
workspace settings page, but every sidebar entry point to
/workspace_settings stayed gated on is_admin || superadmin, so a
non-admin fork creator could only reach the page by typing the URL.

Widen the four sidebar guards (SettingsMenu, SidebarContent,
WorkspaceMenu, WorkspaceScopeHeader) to also admit fork owners, via a
shared isForkOwner() helper mirroring the fork-owner check already on
the settings page and the backend authorize_fork_owner_add_user grant.

Fixes WIN-2210

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

* fix(forks): check fork ownership against the settings entry's target workspace

In session mode SettingsMenu's entry targets workspaceSettingsTarget,
which can differ from the active workspace, but the fork-owner guard was
checked against the active workspace — hiding the entry when the session
targets the user's fork, or exposing a dead link when only the active
workspace is theirs. Check ownership against the workspace the entry
actually points at.

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-07-20 20:39:48 +02:00
hugocasa 87be041c09 fix(git-sync): avoid percent-encoded colon in git-sync hub script path (#10213)
* fix(git-sync): avoid percent-encoded colon in git-sync hub script path

The git-sync init/detection hub script slug contained a colon stored as
`%3A` in the run-by-path URL. The generated API client re-encodes path
params with encodeURI, turning `%3A` into `%253A` (double-encoding). Some
hardened reverse proxies / WAFs reject double URL-encoding and return a
bare 400 before the request reaches Windmill, breaking git-sync repository
detection on those instances.

The hub resolves scripts by numeric id and ignores the slug, so dropping
the colon from the slug is behavior-neutral (same script, same id-keyed
worker cache) while producing a colon-free run URL.

Also force-cache GIT_SYNC_PULL_SCRIPT_PATH at build alongside
LATEST_GIT_SYNC_SCRIPT_PATH so the backend-driven pull script is always
baked into the image for airgapped workers, instead of relying on an
incidental hubPaths.json overlap.

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

* chore: bump ee-repo-ref for git-init slug match fix

Pulls in windmill-ee-private#676 so the EE is_git_init_script check matches
the colon-free git-init hub slug (GitHub App token grant for git-sync jobs).

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

* chore: bump ee-repo-ref for git-init slug helper + test

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

* chore: update ee-repo-ref to a3adea1ffb406e709cc480871df58fab6c51aca1

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

Previous ee-repo-ref: cef4e008ef62dec434aa9bb3ec783db8aff6a1c1

New ee-repo-ref: a3adea1ffb406e709cc480871df58fab6c51aca1

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-07-20 20:36:34 +02:00
Guilhem 0593ff7d7d feat(ai): add npm package search tool to global chat (#10204)
* feat(ai): add npm package search tool to global chat

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

* fix(ai): encode npm search query before building registry URL

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

* chore(ai-evals): add global-mode npm package search case

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-07-20 18:28:09 +00:00
Guilhem c8870d36ae fix(sessions): session bar badge readouts, job persistence, refresh bounce (#10217) 2026-07-20 18:27:56 +00:00
hugocasa 2b58df57fc feat(ai): extract prompt cache token usage from OpenAI and Azure providers (#10214)
* feat(ai): extract prompt cache token usage from OpenAI and Azure providers

Parse the nested cache token details OpenAI returns and thread them into
TokenUsage, matching the Anthropic and Bedrock providers.

- sse.rs: add OpenAIPromptTokensDetails / OpenAIInputTokensDetails and the
  optional prompt_tokens_details / input_tokens_details fields.
- other.rs (Chat Completions) and openai.rs (Responses): populate
  cache_read via .with_cache(cached_tokens, None).

OpenAI's prompt_tokens/input_tokens already include cached tokens (cached is
a subset), so total/prompt are unchanged; cache_read is recorded separately
for reporting. For the same reason the frontend token-usage conversions are
left as-is (adding cached would double-count); optional cache fields and a
clarifying comment are added to prevent a future incorrect Anthropic-style fix.

Fixes WIN-2207

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

* test(ai): pin OpenAI/Azure cache-token deserialization paths

Add regression tests deserializing the real Chat Completions and Responses
usage payloads, guarding the prompt_tokens_details.cached_tokens /
input_tokens_details.cached_tokens paths against a silent rename.

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

* refactor(ai): extract to_token_usage() and test the cache mapping

Address review nit: move the usage->TokenUsage conversion into
OpenAIChatUsage::to_token_usage / OpenAIResponsesUsage::to_token_usage so
the providers call one method and the tests exercise the real mapping.
Tests now assert cache_read is populated while input/total are 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-07-20 18:47:25 +02:00
hugocasa 0e04bc6991 fix: steer ai chat away from draft-blind api catalog reads and runs (#10202)
* fix: steer ai chat away from draft-blind api catalog reads and runs

* fix: support api catalog tools in ai eval harness mock backend

* fix: block remaining draft-blind read and list endpoints in api catalog

* fix: scope eval fetch stub to handled benchmark api paths

* feat: add deployed version read option to read_workspace_item

* fix: include input schema in script workspace item reads

* feat: add pagination to list_workspace_items

* fix: paginate list_workspace_items per item type without cross-type loss

* fix: window draft overlay in list_workspace_items by page and limit

* refactor: simplify list draft overlay to capped page-1 merge

* fix: label server-synthesized draft-only rows as drafts in listings

* chore: fix stale eval comment and pin draft_only listing label
2026-07-20 18:45:14 +02:00
Guilhem f4308cf033 feat(ai): expose get_db_schema tool in global chat (#10207)
* feat(ai): expose get_db_schema tool in global chat

* fix(ai): skip cross-workspace editor cache write in global get_db_schema

* test(ai): add global eval case for get_db_schema resource lookup
2026-07-20 17:32:59 +02:00
Guilhem 542a4842a3 feat(ai): live web-search source list on chat tool cards (#10210)
* feat(ai): live web-search source list on chat tool cards

* fix(ai): restrict web-search source links to http(s), respect user collapse

* fix(ai): read plural queries field for openai web-search labels
2026-07-20 15:47:04 +02:00
Guilhem b448af1da7 feat(sessions): live DOM access for the raw-app preview in AI sessions (#10129)
* feat(sessions): live DOM access for the raw-app preview in AI sessions

Give the session chat read-only access to the rendered raw-app preview,
plus inspector-picked element chips synced bidirectionally with the preview.

- search_dom / read_dom tools: live, same-origin contentDocument reads by CSS
  selector (selector omitted = whole body), reusing the file engine over
  pretty-printed outerHTML (worker-guarded, bounded). Session-gated.
- Inspector picks become app_dom_selector context chips (selector-only); the
  model fetches content on demand. Chips shown even in GLOBAL/session mode.
- Multi-select chips synced with the preview: the chip list is the source of
  truth, pushed to the harness which renders one highlight per selector; add,
  chip-remove, and preview-× remove all stay in sync. Overlays stripped from
  search_dom output.

The ui_builder harness changes (multi-select highlights + unique nth-of-type
selectors) live in windmill-code-ui-builder and ship via the artifact re-pin.

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

* feat(sessions): inline element prompt + inspector polish for raw-app DOM

Session preview:
- Add InlineElementPrompt: a floating mini-composer anchored top-left over a
  selected element; sends a chat turn scoped to that element (its chip is
  context). Autofocus + remount per selection, borderless input, label gutter
  so it clears the harness name+size pill.
- autosize action gains an optional minHeight (default 30) so the compact
  inline input renders a tight single line.
- Fix exiting select mode: Esc / inspector toggle-off now fully clear the
  session's DOM-selector chips (source of truth) so the overlays AND the inline
  prompt are dismissed together, instead of leaving them stranded.

Full-page raw-app editor:
- Add inspectorHoverOnly: the inspector highlights on hover (outline +
  name/size) but a click selects nothing — no persistent selection, no app-mode
  context pick, no inline prompt.

Context badges:
- ContextElementBadge gains a compact prop (passed only by AIChatMessage): the
  DOM-selector chip and tagged workspace items (app/script/flow) render smaller
  above a sent message, while every badge stays the same size in the composer.

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

* fix(sessions): address Codex review P1s on raw-app DOM context

- Inline element prompt: queue the turn when the chat manager is already
  streaming (mirror the composer) instead of a concurrent sendRequest that
  would race the shared abortController / streaming buffers.
- DOM selector chips now carry the raw-app path they were picked from. Each
  preview tab renders/pushes only its own app's chips, and a tab clears
  cross-app chips when it becomes the active DOM target — a selector could
  otherwise silently resolve against whichever raw-app tab is active
  (search_dom/read_dom target the active preview).

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

* chore(raw-apps): re-pin ui_builder artifact to f79e1c3

Bumps the UI Builder artifact to the release built from ui_builder main
after windmill-code-ui-builder#18 merged (the multi-select DOM inspector
synced with the host chat). This is the artifact re-pin step that gates
merging this PR.

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

* fix(sessions): inline-prompt vertical centering + auto-collapse DOM tools

- InlineElementPrompt: bias the textarea padding (pt-[5px] pb-[3px], same
  total) so the single line sits centered in the pill — the shared autosize
  floor otherwise adds slack at the bottom and the text read 1px high.
- search_dom / read_dom: drop the explicit `autoCollapseDetails: false` so the
  tool card collapses after a successful read like other tools (it was copied
  from get_app_runtime_logs, where staying open is intentional).

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

* fix(sessions): address Codex review on raw-app DOM access

- [P1] Release the DOM-requester slot when the active raw-app tab is hidden.
  Preview tabs stay mounted, so switching to a flow/script/page (or another
  raw-app) tab left search_dom/read_dom targeting the now-hidden raw app. The
  slot is now claimed with a per-tab owner token and released via the effect
  cleanup; the runtime only lets the claiming tab clear it, so a set/release
  race between two raw-app tabs can't blank the new owner.
- [P2] Strip the inspector outline classes from the clone ROOT (the selected
  element), not just its descendants — querySelectorAll skips the root, so a
  scoped read of the selected element leaked inspector-picked in its outerHTML.
- [P2] Reword the AIChatDisplay context-chip comment to drop drafting-history
  narration ("now", "not as rows here") per AGENTS.md.

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

* fix(sessions): scope a queued inline prompt to its own element (Codex P1)

An inline element prompt submitted while a turn was streaming got queued as
plain text; if the user selected another element before the queue flushed, the
queued turn snapshotted the new selection and sent scoped to the wrong element
(or none).

The queue now carries a context snapshot:
- queueMessage(text, context?) pins the selection present at submit time.
- sendRequest accepts contextOverride and uses it verbatim as the turn's
  selected context (feeds both the optimistic bubble and the API message).
- All three queue-flush sites thread the snapshot through and restore it if the
  auto-send bails.
- With an override, only the queued message's own DOM chips are consumed from
  the live selection — a newer selection made since is left intact — preserving
  the one-shot chip semantics.

onInlinePrompt snapshots getSelectedContext() when it queues.

(Codex's other P1 — full-page-editor hover-only no longer attaching elements to
App AI — is intentional per an explicit product decision, left unchanged.)

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

* fix(raw-apps): restore full-page editor App-AI inspector context

Codex/Pi flagged a real regression: passing inspectorHoverOnly disabled the
full-page editor's existing App-AI (app-mode) inspector context. Clicking an
inspected element no longer set inspectorElement, so the app-mode
SelectedContext badge and the element attach in prepareAppUserMessage were
dead.

inspectorHoverOnly and the session path (onInspectorSelect) are mutually
exclusive — the flag was only ever set in the full-page/app-mode context, so it
only suppressed the app-mode path. Remove it entirely (prop, guard, and the
pass in +page.svelte), restoring the original click -> inspectorElement ->
app-mode context behavior. Session behavior is unchanged; the hover highlight
still works in both.

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

* fix(sessions): snapshot inline-prompt context synchronously (Codex P1)

The immediate inline-prompt path called sendRequest without a context override,
so the selected element was read only after the async send preflight
(attached-file refresh, beforeSend, global-skill refresh). Picking another
element during that window attached the wrong one to the outgoing turn.

onInlinePrompt now snapshots the selection synchronously at submit time and
passes it as contextOverride on both the queued and immediate paths, so the
prompt always rides with the element it was scoped to.

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

* fix(sessions): drop a queued inline prompt's DOM chips when the app changes

A queued inline prompt scoped to raw app A described A's elements, but the
runtime's single DOM requester targets whichever preview is active. Switching
to app B before the queue flushed left the turn describing A while search_dom /
read_dom would query B.

When a raw-app preview tab becomes active it now also strips the queued turn's
DOM chips belonging to other apps (dropQueuedDomContextForOtherApps), so a
queued prompt can't ask the model to read one app's selectors against another's
live DOM. Non-DOM queued context is preserved.

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

* fix(sessions): scope inline prompt to its anchored element (Codex P1)

The inline prompt sits over a single element but snapshotted EVERY selected DOM
chip. With several elements selected (Shift-multi-select), a prompt shown over
B sent both A and B as context, so "change this button" couldn't identify which
one it meant.

onInlinePrompt now uses its anchored `selector` argument (previously ignored)
to keep only that element's DOM chip in the snapshot; non-DOM context is
preserved.

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

* fix(sessions): preserve original context on edit/retry (Codex P1)

restartGeneration resent with the live selection, so editing or retrying an
element-scoped prompt lost its DOM selector (or adopted a newer one) — DOM
chips are one-shot and cleared from the live selection after the first send.

It now passes userMessage.contextElements as contextOverride, re-using the
exact context the message was originally sent with. undefined (modes that don't
attach contextElements) falls back to the live selection as before.

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

* feat(sessions): route search_dom/read_dom to the selector's own app preview

The DOM requester was a single active-tab slot, so search_dom/read_dom always
read whichever preview was visible. A turn scoped to app A that read the DOM
after the user switched to app B would silently read B (Codex P1).

Preview tabs stay MOUNTED when hidden, so app A's DOM is still there — the fix
is to route by app instead of "active tab":

- Every mounted RawAppEditorView registers its DOM requester keyed by its app
  path (runtime holds a map, not one slot); the visible tab is tracked
  separately as the default target.
- search_dom / read_dom gain an `app_path` (surfaced per chip in the SELECTED
  DOM ELEMENTS block and the tool schema); the runtime routes the query to that
  app's still-mounted preview. No app_path → the active preview, or the only one
  open.
- If the named app's preview has been closed, the tool returns "The preview for
  X is no longer open…" so the chat can explain it, rather than reading the
  wrong app.

This makes the queued-context drop-on-switch (dropQueuedDomContextForOtherApps)
redundant — removed. The lightweight submit-time snapshot (which element the
message is about) is kept.

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

* fix(sessions): address Codex review on DOM access (script strip + app-scoped chips)

- search_dom/read_dom: strip the app's compiled <script> bundle from the
  whole-body clone so queries only see rendered HTML, not source (P1).
- DOM chip add-dedup and removal now match both selector AND app path, so an
  identical selector in another app can't drop or block a chip (P2).
- Refresh stale single-slot requester comments to describe per-app routing;
  reword the foreign-chip reset to reflect why it stays (composer chips are
  unlabeled by app) (P2).

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

* fix(sessions): scope DOM-chip clear to its app so a rebuild can't wipe another (Codex P2)

Every mounted preview emits inspectorClear on rebuild, routed to
onInspectorClearAll → clearSelectedDomElements(). Unscoped, a hidden app A
rebuilding cleared app B's active chip and highlight. clearSelectedDomElements
now takes an optional appPath; the preview-clear path passes its own path, while
post-send and foreign-reset clears stay unscoped.

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

* fix(sessions): reject a <script>-root DOM query so the app bundle can't leak (Codex P2)

The descendant strip (querySelectorAll('script')) skips the clone root, so a
search_dom / read_dom query whose selector targets `script` serialized the whole
compiled bundle. Reject a script root with an explanatory result before cloning.

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

* fix(sessions): union queued inline-prompt DOM context + reject inspector-label root (Codex)

- Multiple inline prompts queued during one stream accumulate their text, but
  queuedContext replaced (last wins) — dropping an earlier element's chip and
  misapplying its instruction. Union the DOM selector chips across queued prompts
  (non-DOM context still from the latest snapshot) (P1).
- search_dom/read_dom: reject an .inspector-label clone root, like the <script>
  root — the descendant strip skips the root, so a `.inspector-label` query
  would return inspector chrome (P2).

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

* fix(sessions): restore consumed DOM chips when a turn is cancelled unsent (Codex P1)

DOM selector chips are consumed from the live selection before the request goes
out. If the user hits Stop before any usable output, restoreUnsentTurn put the
text/pastes/images back but not the chips, so resending the restored prompt lost
its element scope. Re-add the consumed chips on rollback (skipped on a queued-
message handoff, which carries its own context).

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

* fix(sessions): edit box edits the message's own context, sends what it shows (Codex P1)

Editing a past message bound the edit box to the LIVE selection while the resend
carried the message's original contextElements — so the box showed one set of
chips (or none) but sent another, and add/remove in the box did nothing. The edit
box now edits a copy seeded from the message's own contextElements, and
restartGeneration sends that edited copy; a bare retry still falls back to the
original. Drops the now-unused selectedContext prop from AIChatMessage.

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

* fix(sessions): keep DOM context coherent on dequeue/cancel + per-chip edit identity (Codex)

- dequeueMessage dropped the queued draft's pinned DOM context, so returning it
  to the composer sent the live selection instead. Restore the draft's context on
  dequeue (P1).
- Cancel-rollback re-added this turn's chips additively, mixing in chips selected
  mid-stream. Replace instead so the restored draft stays coherent — shared helper
  #restoreDomContext used by both paths (P1).
- The context chip row keyed/removed by (type, title); repeated DOM elements share
  a title (two button.btn), so editing a multi-select message gave duplicate keys
  and deleting one chip removed both. Identify DOM chips by (appPath, selector) (P1).

The queued-prompt instruction↔element association (Codex #1) is the accepted union
tradeoff and is unchanged.

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

* fix(sessions): scope read_dom pagination + don't consume live chips on edit/retry

Codex P1s:
- read_dom's continuation note only renamed read_file, so following it with
  start_line alone dropped app_path/selector and re-read the active app's whole
  body instead of continuing inside the element.
- contextOverride also carries an edit/retry's replayed copy of an older
  message's context. Consuming it from the live ContextManager stripped an
  identical chip the user had since selected in the composer, leaving their
  draft unscoped. Overrides now declare their origin ('pinned' vs 'replay').

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

* fix(sessions): keep DOM context paired with the draft it belongs to

Restoring a draft's pinned DOM chips replaced the live selection unconditionally,
but both restore paths can leave a different draft in the composer:

- Cancel: an occupied composer declines the rolled-back text (restoreInstructions
  bails), yet the cancelled turn's chips still replaced the live ones — the draft
  the user typed during the stream kept its text but got retargeted.
- Dequeue: queued text is prepended onto an existing draft, so both instructions
  share one composer; replacing the chips dropped the standing draft's element.

restoreInstructions/prependText now report whether the composer took the text.
Cancel restores context only when it did; dequeue keeps both drafts' chips when
it merged into one.

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

* fix(sessions): scope an inline prompt's chip filter to its own app

The filter narrowing a multi-select down to the anchored element matched on
selector alone. Selectors are generated per app and collide across them, so an
inline prompt in app B also carried app A's identically-named chip, leaving the
model two indistinguishable referents. Since draft restoration can now
legitimately hold chips from several apps, match (selector, appPath) — as the
sibling deselect/clear handlers in this file already do.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:33:45 +02:00
Ruben Fiszel 4f1d636005 fall back to createFlow when the update target does not exist (#10206)
FlowBuilder's deploy branches solely on the `newFlow` prop. When an embedder
mounts the builder with `newFlow` false for a flow that was never deployed,
Deploy issues `PUT /flows/update/{initialPath}` and the backend answers
`Flow not found at name <path>`, so the first deploy can never land.

Confirm against `GET /flows/exists/{path}` before taking the update branch,
and create instead when nothing is deployed there. The create path and the
full-page editor (where `newFlow` is derived from `no_deployed`) are
unchanged.

Fixes WIN-2206

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 13:10:26 +02:00
Ruben Fiszel 71f2d47cb4 feat: cap queued jobs per concurrency key on cloud (#10197)
* feat: cap queued jobs per concurrency key on cloud

* fix: close preprocessed-flow bypass and bound concurrency cap scan

* fix: only cap concurrency keys with an active concurrent_limit

* chore: only load concurrency key cap setting when cloud hosted

* fix: reject queued-job import on cloud
2026-07-20 12:33:40 +02:00
Ruben Fiszel 1abfe49f7e stop the app editor crashing on a grid-less draft (#10203)
* fix(apps): default a missing grid so the app preview does not crash

* fix(apps): migrate the adopted draft so the editor matches the viewer

* docs: tighten grid-normalization comments to durable constraints

* test: drop the migrateApp grid-default test
2026-07-20 12:33:14 +02:00
hugocasa 83a354f831 feat: expose windmill api endpoint catalog to global ai chat (#10199)
* feat: expose windmill api endpoint catalog to global ai chat

* fix: guard variable reads and deletes in ai chat api catalog

* fix: clarify api catalog prompt example for run result access

* fix: block deleteScriptByHash and document resource read boundary
2026-07-20 11:31:51 +02:00
Ruben Fiszel 60a7a23a04 chore(main): release 1.763.0 (#10186)
* chore(main): release 1.763.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-07-19 10:45:40 +02:00
Ruben Fiszel 89948bb441 chore(git-sync): bump hub scripts to windmill-cli@1.762.3 (#10189) 2026-07-19 10:40:29 +02:00
Ruben Fiszel 4bddefc518 wire topBar.path and editablePath customUi in FlowBuilder (#10187)
Mirror the ScriptBuilder whitelabel pattern in FlowBuilder so the
`customUi.topBar.path` and `customUi.topBar.editablePath` options actually
take effect:

- add `editablePath?: boolean` to `FlowBuilderWhitelabelCustomUi.topBar`
- gate the EditorHeader block on `customUi?.topBar?.path != false` and pass
  `pathEditable`/`summaryEditable` (the latter wires the pre-existing but
  unused `editableSummary` flag)
- hide the Path field in FlowSettings when `topBar.editablePath` is false

Defaults are unchanged (`!= false`), so non-whitelabel flows still show the
path header and settings field.

Fixes WIN-2200

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:28:30 +02:00
Guilhem 42da20ae97 feat(ai): gate data pipelines in sessions behind a dev flag (#10178)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:13:27 +02:00
Ruben Fiszel d5b0a1b5c7 chore(main): release 1.762.2 (#10184)
* chore(main): release 1.762.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-07-18 08:49:56 +02:00
Ruben Fiszel 252ffd0401 chore(main): release 1.762.1 (#10181)
* chore(main): release 1.762.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-07-17 22:52:46 +02:00
Ruben Fiszel 3ffce7e2cc chore(main): release 1.762.0 (#10175)
* chore(main): release 1.762.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-07-17 20:15:13 +02:00
Guilhem 8828341a2b fix(frontend): scope session pipeline trigger editors to the session workspace (#10032)
* fix(frontend): scope session pipeline trigger editors to the session workspace

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

* fix(frontend): hoist triggerWorkspace decl above GCP init-time getBaseUrl call

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

* fix(frontend): scope nested trigger pickers to the session workspace

The triggerWorkspace resolver scoped direct trigger CRUD calls to the
session's (forked) workspace, but nested pickers still defaulted to the
nav `$workspaceStore`: in a fork session, resource lists/creation,
variable creation, and path-existence checks ran against the parent
workspace while save/delete targeted the fork — misleading options and
false path-validation failures.

Thread `wsId` into the nested controls of the 9 pipeline-canvas kinds:
- `<Path workspaceOverride={wsId}>` (8 editors) — path + folder checks
- `<ResourcePicker workspace={wsId}>` (6 config sections; add the
  resolver to MqttEditorConfigSection, which lacked `wsId`)
- SQS `<VariableEditor workspace={wsId}>` — variable creation

Also drop the per-site `wsId` rationale comment repeated across ~20
files; the invariant is documented once in triggerWorkspace.ts.

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

* fix(frontend): scope trigger runnable picker to the session workspace

The runnable picker (`ScriptPicker`, reached via `TriggerRunnablePicker`
and directly in the schedule editor) listed scripts/flows/apps from the
nav `$workspaceStore` with no override, so a forked session offered the
parent workspace's runnables when attaching a script/flow to a trigger.

Add an optional `workspace` prop to `ScriptPicker` (defaults to
`$workspaceStore` → no change for existing callers), pass it through
`TriggerRunnablePicker`, and wire `wsId` from the 7 trigger editors that
use it plus the schedule editor's 3 direct pickers.

Completes the nested-picker workspace scoping; the excluded kinds
(azure/http/websocket/native) keep their own ScriptPickers unchanged.

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

* fix(frontend): scope trigger error-handler, folder default, and runnable actions

Follow-up to the trigger-workspace scoping: three subtrees still read the
nav `$workspaceStore` in a forked session.

- ErrorOrRecoveryHandler (via TriggerRetriesAndErrorHandler in the 7 in-scope
  editors): add a `workspace` prop (defaults to `$workspaceStore`) and route
  handler lookup/schema, Slack/Teams settings, test jobs, and run links
  through it, so the error handler is resolved/tested/saved in the session
  workspace instead of A while the trigger lives in B.
- useFolderDefaultPermissionedAs: accept an optional workspace getter so a
  `f/...` trigger's default permissioned-as is read from the session
  workspace, not the nav one (PermissionedAsLine passes `() => wsId`).
- ScriptPicker actions: scope the View drawer (`getScriptByPath`) and
  `FlowPathViewer` to `effectiveWorkspace`, and carry `?workspace=` onto the
  Edit/View routes when an explicit override is set (the layout consumes the
  param, same mechanism as editInFork). The param is only appended when a
  workspace override is passed, so existing callers' links are unchanged.

Also consolidate the repeated workspace-scoping comment in
PipelineTriggerEditors (the invariant lives in triggerWorkspace.ts).

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

* fix(frontend): scope error-handler/schedule nested consumers to the session workspace

Address CI Codex review on #10032 — the error-handler and schedule
subtrees still had nested consumers reading the nav workspace:

- ErrorOrRecoveryHandler: pass the resolved workspace to its own nested
  `ScriptPicker` (custom-handler list + View/Edit) and add a `workspace`
  prop to `ChannelSelector` (Teams channel listing); carry the acting
  workspace onto the "create from template" link.
- ScheduleEditorInner: pass `workspace={wsId}` to the error/recovery/
  success `ErrorOrRecoveryHandler` panels, `workspaceId={wsId}` to
  `WorkerTagPicker`, and the workspace query param onto the dynamic-skip
  template link.

Template-link and picker overrides only diverge from `$workspaceStore`
when a session override is set, so non-session callers are 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-07-17 19:48:08 +02:00
hugocasa 7a139ab23e feat(ai-chat): image attachments and agent raw-app screenshots (#10130)
* feat(ai-chat): add image attachments and agent raw-app screenshots

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

* docs(ai-chat): generalise take_screenshot fidelity caveat

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

* fix(ai-chat): keep compaction boundary on a displayed user message

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

* fix(raw-apps): count line boxes by vertical overlap, not rect count

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

* fix(ai-chat): enforce vision gating and bound image attachments

Refuse images on known text-only models instead of warning and sending them
anyway; cap input bytes before decode; keep clipboard text when it accompanies
a bitmap; don't queue a message whose images can't ride the plain-text queue.

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

* perf(ai-chat): trim take_screenshot schema and shrink the card's copy

Move the fidelity caveat from the tool def onto the tool result: the def is
re-sent every global iteration (~258 tok), while the caveat only matters once a
capture exists. Keep a downscaled copy in displayMessages when it is actually
smaller — those are never compacted and are re-cloned on every saveChat.

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

* fix(ai-chat): carry attached images through the message queue

Enter during a streaming turn queued the text and silently dropped the images,
so the auto-send was not the message the user submitted. The queue now holds
both, moved together via takeQueue/clearQueue/restoreQueue so none of the three
flush sites, the dequeue-to-composer path, or the two conversation-switch drops
can leak one without the other.

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

* fix(ai-chat): gate screenshots on vision, narrow when the tool fires

take_screenshot buffered an image unconditionally, so a text-only model got an
image_url and rejected the turn; the attach-time check never covered it, nor a
model switched after attaching. Gate before capture and again at send.

Only reach for the tool when the user raises how the app looks, rather than
after every UI edit.

A collapsed preview keeps the iframe mounted at zero width, passing the ready
checks and then failing inside the rasteriser as '[object Event]'. Name it.

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

* fix(ai-chat): hold sending while attachments decode

addImages read the free-slot count before its await and appended after it, so a
send during the ~50-800ms decode cleared images while the closure still wrote to
them, landing the picture on the following message; two drops also claimed the
same slots and could pass the cap. Reserve slots up front, block sending until
they resolve, and show a placeholder so the held send is explained.

Keep only a bounded copy in the transcript: displayMessages are never compacted
and are re-cloned on every save. Measured 6.1x smaller per attachment.

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

* fix(ai-chat): route screenshots to the visible tab, resend full-res on retry

Every mounted raw-app editor claimed the runtime's single screenshot slot, so
take_screenshot could capture a background tab's app; ownership now follows the
visible tab and only the owner releases it.

restartGeneration resent displayMessages' images, which became a 384px thumbnail
when the transcript copy was bounded — retries downgraded the model's own input.
Recover the sent parts from the API message instead.

Move modelSupportsVision to modelConfig: it was untestable behind lib.ts's monaco
import chain, and the denylist missed bundled text-only defaults (Groq/Together
Llama 3.3, Foundry Phi-4 and Mistral-Large). Llama 3.2 and Phi-4 split by variant,
so both are matched narrowly. Pinned against the shipped defaultModels.

Decode attachments one at a time and derive the preview from the bounded copy: a
12MP bitmap is ~48MB and the batch was held live at once, decoded twice each.

The attach tooltip claimed nothing is uploaded, which is untrue for images.

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

* fix(ai-chat): keep images out of text-only turns and bound the queue

The vision gate only dropped the current turn's images, so history's image parts
still went out after a switch to a text-only model and failed the request; strip
the outbound copy instead, leaving history intact for a switch back.

queueMessage had no cap, and each queued send clears the composer for another
eight, so repeated sends stacked an unbounded batch into one message.

Editing a message resent displayMessages' bounded copy, downgrading the model's
own input; retries recovered the full-size one but then re-persisted it at full
resolution. storedImages pairs the API message with its transcript entry so both
paths resend the original and re-persist the bounded copy.

Reserve image slots before awaiting text attachments: the gap left sending
enabled with an image pending, measured ~90ms for a 40-file drop, now ~8ms
regardless of batch size.

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

* fix(ai-chat): treat deepseek-v4 as text-only

deepseek-v4-pro ships as a bundled default and the gate let images through to
it, so an attachment would fail the turn. DeepSeek's vision line is deepseek-vl.

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

* fix(ai-chat): drop a rejected image instead of wedging the conversation

A provider that refuses an image leaves it in history, so every later turn
resends it and fails identically: the chat is stuck until the user edits the
message or starts over, and Retry re-sends the same image. The vision gate only
knows the models we ship, so this is the net for the rest. Strip the parts on an
image-related rejection and say so; unrelated failures keep the image.

Verified at the wire that no provider rejects a base64 data URL: anthropic
(source.base64), openai/gpt-4o (input_image), googleai and aws_bedrock/claude
(image_url passthrough) all 200 and read the image.

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

* fix(ai-chat): match text-only models exactly, from provider API docs

The gate guessed by substring over model names, which answers the wrong
question. What matters is whether a provider's API accepts image parts, not
whether the model can see: DeepSeek V4 ships vision in its chat product that its
API has no content type for, and o3-mini gained vision in ChatGPT the API never
exposed. Neither is inferable from a name.

Substrings also block working models. 'mistral-large' matches Mistral Large 3,
which takes images; 'phi-4' matches Phi-4-multimodal, which does too. A wrong
entry blocks with no override, while a missing one costs a turn and recovers via
the rejection path, so the list is now exact ids only, each backed by a provider
doc.

Verdicts verified against provider API docs rather than recall. Live-checked
where a doc was contradicted: Bedrock's compatibility matrix claims no Anthropic
model is served over chat completions, but it serves images fine.

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

* fix(ai-chat): stop retry resurrecting a rejected image

The rejection fallback strips the image from history but leaves the bubble's
thumbnail so the user can still see what they sent. storedImages fell back to
that thumbnail when the API message had no parts, so Retry re-attached the very
image the provider had just refused and failed identically — the conversation
stayed wedged through the one control offered to escape it.

Found by retrying in the UI; unit tests, wire tests and four review passes all
missed it, since it only exists between two separate fixes.

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

* fix(ai-chat): harden image rejection recovery and drop-path attachment

* fix(ai-chat): fix image drop race, mid-turn vision gate, retry aliasing

* fix(ai-chat): key vision denylist by provider, flatten alpha before jpeg

* feat(ai-chat): offer take_screenshot on chromium only, ask for one elsewhere

* feat(ai-chat): image-only sends and click-to-expand image previews

* fix(ai-chat): capture screenshots at 2x and expand tool images full-res

* feat(frontend): expandable image previews in composer and result views

* fix(ai-chat): image-only send edge cases from review round

* fix(ai-chat): keep image-only drafts on rollback, track failing model id

* fix(ai-chat): gate rejection recovery on the failing iteration's model

* refactor(ai-chat): record iteration model via onBeforeIteration, trim tests

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

* fix(ai-chat): restore composer draft when beforeSend preflight fails

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

* fix(ai-chat): bound cumulative outbound image bytes per request

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

* fix(ai-chat): make the image byte bound part-granular so over-cap turns keep a subset

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

* fix(ai-chat): evict newest-first within a message in the image byte bound

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

* fix(ai-chat): prune over-cap images from stored history, not just requests

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

* fix(ai-chat): bound history at every save boundary, keep thumbnail pairing across eviction

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

* fix(ai-chat): slot-align storedImages so the bubble expands the right image after eviction

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

* fix(ai-chat): match rejection keywords as whole words so provisioning errors keep images

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

* fix(ai-chat): match input_image rejections, restore images refused by non-GLOBAL modes

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

* fix(ai-chat): defer non-GLOBAL image refusal restore past the composer clear

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

* fix(ai-chat): persist full tool screenshots for post-reload expansion

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

* refactor(ai-chat): persist chat images out-of-band via blob-store refs

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

* fix(ai-chat): scope image blobs per chat and stop cap-eviction rotation

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

* fix(ai-chat): keep blob-cap chronology across drop-oldest compaction

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

* refactor(ai-chat): derive blob eviction from the saved record, not write times

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

* fix(ai-chat): serialize chat history DB writes per manager

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

* fix(ai-chat): pin queued history writes to the enqueue-time user database

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

* fix(ai-chat): delete stale image blobs only after the chat record commits

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

* fix(ai-chat): don't double-restore a queued image-only draft on vision refusal

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

* fix(ai-chat): label image-only chats and evicted image-only bubbles

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

* fix(ai-chat): keep the in-memory chat mirror hydrated for DB-less sessions

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

* fix(ai-chat): converge the chat mirror to refs after a successful DB commit

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

* fix(ai-chat): guard mirror convergence against rewinding newer saves

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

* fix(ai-chat): invalidate pending convergences on identity re-init, keep retry image names

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

* fix(ai-chat): bound the screenshot raster before rasterization

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

* refactor(ai-chat): drop the no-IndexedDB in-memory image fallback

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:36:51 +02:00
hugocasa d0aa7dca13 fix: parse all names in grouped go param declarations (#10165)
* fix: parse all names in grouped go param declarations

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

* chore: bump windmill-parser-wasm-go to 1.761.0

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:24:07 +02:00
Guilhem a9fc9f74b2 fix(ai): unbreak session chat compaction for Anthropic models (#10171)
* fix(ai): cap chat compaction summary output so Anthropic non-streaming calls succeed

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

* test: pin the compaction summarizer's maxTokensCap

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

* fix(ai): cap testKey completion so Anthropic key tests pass the SDK pre-flight

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:23:31 +02:00
Ruben Fiszel 7d2c5ceb0f fix(flows): make updateFlow body path optional so AI can update flows (#10176)
* fix(mcp): default a body field to its same-named path param so updateFlow works

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

* docs: trim mcp path-param fallback helper comment

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

* refactor(mcp): keep path params un-mangled so update tools take plain `path`

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

* fix(flows): default update_flow body path from URL via EditFlow

Harmonizes updateFlow with the EditVariable/EditResource/EditApp convention: the
flow to update is identified by the URL, so the body path is optional and only
needed to rename. Fixes the 422 at the API layer for every client (MCP, the
in-app AI chat, raw HTTP), not just the MCP tool schema.

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

* refactor(mcp): drop redundant body-path fallback now that the server defaults it

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

* docs: fix stale generator comment after removing mcp body-path fallback

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

* fix(flows): mark updateFlow body path optional in the openapi contract

Adds an `EditFlow` schema (path optional) for the update route so the public
contract matches the server; createFlow keeps `OpenFlowWPath` (path required).
Also trims two test comments to record constraints rather than history.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:22:31 +02:00