Commit Graph
13993 Commits
Author SHA1 Message Date
Guilhem LemouelandClaude Opus 4.8 f09a96f88d fix(ai): seed React types for tsx linting so JSX resolves
A tsx script linted with `<div>` reported three false positives —
"JSX.IntrinsicElements does not exist" and "React not in scope" —
because type acquisition only ran for bun/bunnative, so @types/react
never loaded. The editor had the same gap: it creates an ATA instance
for tsx but never seeds react.

Add a shared ataSeedImport(scriptLang) (bun -> bun-types, tsx -> react)
that both the editor and the headless linter seed through, and extend
the headless trigger to tsx. Parity by construction: neither path can
seed differently. jsx routes through the lenient JavaScript worker,
which does not flag JSX, so it needs no seed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 11:07:16 +02:00
Guilhem LemouelandClaude Opus 4.8 468c3d73e9 test(ai): pin the owned-URI namespace and its disjointness from editor URIs
Covers computeOwnedUri for scripts, flow modules, runnables and app files,
and asserts the load-bearing invariant: an owned URI is always under the
reserved root and never equal to the editor URI for the same code, and the
same path in two workspaces yields two distinct owned models.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:30:20 +02:00
Guilhem LemouelandClaude Opus 4.8 f37a2b0439 refactor(ai): route app-frontend linting through the host, fix ATA sibling overwrite
The raw-app frontend file set now lives in owned models under the app's
`__wmlint__` namespace, created and disposed through the host; the in-flight
guard keeps a large app's files from evicting each other. ATA gains an
overwriteLocalModels option, off for apps, so a fetched sibling workspace
script can no longer replace an app file's model with unrelated content while
intra-app relative imports still resolve — the targeted fix the blunt
disable-local-models approach broke. Flow modules and runnables already
route through the host via their cell identity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:28:20 +02:00
Guilhem LemouelandClaude Opus 4.8 f92ece776b refactor(ai): own headless lint models behind a host, read the editor when present
Introduces HeadlessModelHost as the single owner of Monaco models for
linting. Owned models live under a reserved `__wmlint__/<workspace>/…`
namespace disjoint from every editor URI, keyed by the draft cell
(workspace, itemKind, storagePath) plus a sub-path for a flow module or app
runnable — so a headless model can never adopt, overwrite, or dispose one an
editor owns, and a fork and its parent never share a model. When an editor is
already showing the item in the same workspace, the lint reads its markers
instead of creating anything; those markers are settled first so a lint right
after an edit can't read a stale clean. The editor registers itself on mount.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:22:22 +02:00
Guilhem LemouelandClaude Opus 4.8 8ac43ed7e8 refactor(ai): model the lint result as a Complete | Incomplete outcome
Every headless lint path now returns one LintOutcome type. "Clean" is only
expressible from a completed analysis, so a timed-out worker, a stalled or
unavailable language server, or a failed pull can no longer be rendered as
"no issues" — the type forbids it. Replaces the ad-hoc unavailableServers /
incomplete flags scattered across the producers and consumers. A regression
test pins that an incomplete result never reads as clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:04:07 +02:00
Guilhem LemouelandClaude Opus 4.8 3be0369e54 fix(ai): never report an incomplete lint as clean
Addresses review findings on the headless linter: a TypeScript worker that
never initialized, an LSP pull that failed, and an LSP push that timed out
were each read as "no issues". Each now surfaces the checker as unavailable
so the tool reports the result as inconclusive rather than clean. The
collapsed status header reflects it too. Also guards the app-file setValue
against overwriting an attached editor buffer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 07:56:51 +02:00
Guilhem Lemouel 60e896fd9f Merge remote-tracking branch 'origin/main' into glm/ai-linter
# Conflicts:
#	frontend/src/lib/components/copilot/chat/global/core.ts
2026-07-20 15:43:05 +02:00
Guilhem LemouelandClaude Opus 4.8 cf3d487751 fix(editor): only adopt-and-overwrite a model no editor is showing
Adopting an existing model at the same URI now applies this editor's content,
so a model left behind by headless linting is not displayed as if it were the
item. Restrict that to unattached models: one another editor is showing may
hold edits the user has not saved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 15:37:37 +02:00
Guilhem LemouelandClaude Opus 4.8 bfe750b6ea feat(ai): type-check raw app frontend files, not just the build
Bundling reports syntax errors and unresolved imports, but esbuild strips
types without checking them, so a type error or a reference to an undefined
variable — a guaranteed runtime crash — compiled cleanly and the tool
answered that the frontend had no errors. Run the TypeScript worker over the
app's files as well and report problems per file.

React's types declare it as a UMD global, so without allowUmdGlobalAccess
every JSX line in a file that does not import React is reported as an error,
which is how these files are written. Enabling it also removes that false
error for tsx scripts in the editor.

Files the entry point never imports are checked too. They never ship, but
they are still the user's source and show the same problems in the app
editor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 15:07:05 +02:00
Guilhem LemouelandClaude Opus 4.8 88e3a41fa1 feat(ai): show lint details on demand in the chat
The lint tool call rendered as a bare summary line with nothing behind it.
Mark it as having details so the individual problems can be opened, and pass
the formatted output as the tool result — without it the details panel reads
"No result yet". Details stay collapsed on success, since the header already
carries the counts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:52:07 +02:00
GuilhemandClaude Opus 4.8 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
Guilhem LemouelandClaude Opus 4.8 2fdafdddaf feat(ai): check raw app frontend compilation from the chat
get_lint_errors on a raw app without a runnable key now compiles the app's
frontend files and reports where the build broke, so editing app files can
be checked the same way editing a runnable already was. A frontend is a
bundle rather than a set of independent documents, so there is no language
service to query per file — only the compiler's verdict, which also means
files unreachable from the entry point are not covered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:26:30 +02:00
Guilhem LemouelandClaude Opus 4.8 aaa7601950 fix(ai): correct headless lint ownership, staleness and timeouts
Review findings on the headless linter:

- An editor can adopt a model headless linting created, so attachment now
  decides who owns a buffer rather than who created it. The editor likewise
  applies its own content when adopting an existing model, instead of
  displaying whatever was last linted.
- Markers are cleared when an owned model is rewritten: they outlive the
  content they describe, and a matching count let the settle loop return
  the previous version's diagnostics.
- Eviction no longer drops the URI it is keeping, which had left a live
  model untracked and subsequently treated as belonging to an editor.
- A language server that stalls is named in the result rather than
  contributing an empty list, and no longer discards what other servers
  already reported.
- A client abandoned at its deadline is stopped, so it cannot keep holding
  the global command names every later lint of that language needs.
- Deno's import map is built from the path its document actually has.
- Pre-lint network calls are bounded; lints of one URI queue behind each
  other, so a hung request would otherwise wedge that item indefinitely.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:18:45 +02:00
Guilhem LemouelandClaude Opus 4.8 0236c3b145 feat(ai): headless linting for python, go, deno and bash
Extends get_lint_errors beyond the TypeScript worker to the language-server
languages. These servers are driven directly rather than through the editor's
clients: pyright and gopls implement LSP 3.17 pull diagnostics and only get
asked for documents visible in an editor, so headless the request has to be
made explicitly. Servers that answer with nothing are still given a bounded
window to publish instead, which is how ruff reports.

A server whose client cannot start — its global command names are taken when
an editor is already running one — is named in the result instead of silently
shrinking it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 13:29:17 +02:00
Ruben FiszelandClaude Opus 4.8 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
Guilhem LemouelandClaude Opus 4.8 f5b9dc7f5c refactor(editor): extract language server configuration
Diagnostics for python, go, deno and shell are decided by which server runs
and with which initialization options, so headless linting has to build its
clients from the same descriptors. Move them to lint/lspLanguageConfig.ts;
the editor keeps owning the websocket lifecycle and reconnection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 12:59:37 +02:00
Guilhem LemouelandClaude Opus 4.8 11b58cacb5 feat(ai): get_lint_errors tool for global chat
Global chat edits drafts with no editor mounted, so until now it had no way
to type-check what it wrote. The tool resolves draft or deployed code for a
script, an inline flow module, or a raw app backend runnable, lints it
headlessly at the URI the editor would use, and reports unsupported
languages explicitly rather than a misleading clean result.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 12:48:50 +02:00
Guilhem LemouelandClaude Opus 4.8 7147261450 feat(ai): headless TypeScript lint service
Lints script, flow-step and raw-app runnable code with no editor mounted, by
driving the same Monaco model, extra libs and acquired types an editor would.
Settling waits for the worker's own diagnostic count rather than a fixed
sleep, which also terminates immediately on clean code, where no marker
change event is ever emitted. Editor.getLintErrors now reads markers through
the same helper, so the two cannot drift apart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 12:43:39 +02:00
Guilhem LemouelandClaude Opus 4.8 e47d760eba refactor(editor): extract ATA setup and lsp token root
Headless linting needs the same acquired types as the editor, npm
declarations and relative-import models alike. Move the ATA delegate and
token root to lint/typescriptAta.ts; the editor keeps its trigger logic and
supplies the model-revalidation nudge via onLocalFileRegistered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 12:38:04 +02:00
Guilhem LemouelandClaude Opus 4.8 35f3196c4a refactor(editor): extract typescript extra-libs registration
Headless linting must register the same rt.d.ts resource-type namespace and
custom windmill-client declarations as a mounted editor, since diagnostics
depend on them. Move the apply bodies to lint/typescriptExtraLibs.ts; the
editor keeps its reactive resource() wrapper around the fetch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 12:35:06 +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
Guilhem LemouelandClaude Opus 4.8 b89e082e87 refactor(editor): extract Monaco model path/URI computation
Headless linting needs to derive the exact model URI the editor uses;
computeUri/computePath were component-local closures over scriptLang and
lang. Move them to lint/monacoUri.ts with those as explicit parameters.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 12:30:13 +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 b5e69ffba6 fix(security): enforce token scope filtering on folder list endpoints (#10193)
* fix(security): enforce token scope filtering on folder list endpoints

* fix: apply folder scope filtering before pagination

* fix: keep SQL pagination for unrestricted tokens on folder lists

* fix: bound scoped folder-list scan with chunked pagination

* fix: use keyset pagination for scoped folder-list scan

* refactor: reduce folder scope fix to the minimal predicate filter

* chore: drop folder list scope test
2026-07-20 07:50:50 +02:00
Ruben FiszelandClaude Opus 4.8 70359e3a76 fix(schedules): apply scope-path filtering to schedule list endpoints (#10192)
`list_schedule` and `list_schedule_with_jobs` returned every schedule in
the workspace regardless of the token's declared scope. A token limited
to `schedules:read:<prefix>/*` could enumerate all schedule paths (and
their script paths, cron expressions and recent job outcomes) through
these two endpoints, while `get_schedule` correctly rejected them.

Apply `build_scope_path_predicate(&authed, "schedules", "read")` to the
returned rows, mirroring scripts, flows, apps, resources and variables.
In `list_schedule` the filter runs after the draft-only append so both
deployed and draft rows are covered.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 07:32:52 +02:00
Ruben FiszelandClaude Opus 4.8 f32d7702bc fix(inputs): add ownership check to saved-input args read endpoint (#10194)
`get_args_from_history_or_saved_input` looked up saved-input rows by
`id + workspace_id` only, without checking ownership or `is_public`. The
`input` table has no RLS policy, so any authenticated workspace member
could read another member's private saved-input args (which may hold
credentials) just by knowing the UUID, even though `inputs/list` already
scopes to `is_public OR created_by = caller`.

Add `AND (is_public IS true OR created_by = $4)` to both `input`-table
queries in the handler (the `?input=true` single-query branch and the
default `v2_job UNION ALL input` branch), matching the predicate already
used by `list_saved_inputs`.

The `v2_job` branches are already covered by the table's RLS policies
enforced via `SET LOCAL ROLE windmill_user` in `UserDB::begin()`, so they
are left unchanged.

Fixes WIN-2202

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 07:31:02 +02:00
Ruben Fiszelandrubenfiszel 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>
v1.763.0
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 FiszelandClaude Opus 4.8 2d77e74207 fix(cli): stop emitting has_on_behalf_of/has_permissioned_as: false on pull (#10188)
With syncBehavior v1 the pull strips the user-specific on_behalf_of_email /
permissioned_as from metadata and keeps a boolean marker so a later push can
preserve remote ownership. The marker was written unconditionally as
`!!<field>`, so every ownerless script, flow, schedule and trigger (the vast
majority) got a `has_on_behalf_of: false` / `has_permissioned_as: false` line,
producing a spurious diff on every pull.

Absence of the marker already means "no owner" everywhere it's read on push, so
only emit the key when true.

Fixes WIN-2201

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:30:49 +02:00
Ruben FiszelandClaude Opus 4.8 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
GuilhemandClaude Fable 5 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 Fiszelandrubenfiszel 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>
v1.762.2
2026-07-18 08:49:56 +02:00
Ruben FiszelandClaude Opus 4.8 f6e36f862e chore(schedules): lower reconciler re-arm back-off cap to 8 passes (#10182)
Follow-up to #10179. The exponential back-off between reconciler re-arm
retries of a persistently-failing schedule capped at 32 passes (~2.7h at
the default 5-min reconcile cadence). Lower the cap to 8 (~40min) so a
schedule fixed out of band (a lapsed license renewed, a bad cron corrected
directly in the DB) auto-recovers within a few passes, while still cutting
the retry rate sharply versus retrying every pass. Fixes via the UI/API
re-arm immediately and are unaffected.

Fixes WIN-2198

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 08:49:41 +02:00
Ruben FiszelandClaude Opus 4.8 cba5f0d8a8 fix(scripts): populate auto_kind from draft JSON for draft-only scripts (#10183)
The scripts list endpoint (include_draft_only=true) only populated
auto_kind for the pipeline case, leaving library draft-only scripts
(no `main` function) with auto_kind: null even though the frontend
saves auto_kind: "lib" into the draft JSON. This made it impossible
to distinguish library draft-only scripts from regular ones without a
separate per-script API call.

Fall back to the auto_kind saved in the draft value after the
content-derived pipeline check, which keeps priority since it mirrors
the deploy-time computation.

Fixes WIN-2199

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 08:39:22 +02:00
Ruben Fiszelandrubenfiszel 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>
v1.762.1
2026-07-17 22:52:46 +02:00
Ruben FiszelandClaude Opus 4.8 c82056cfde fix(schedules): stop disabling schedules on transient push errors (#10179)
* fix(schedules): stop disabling schedules on transient push errors

A scheduled flow whose next-occurrence push failed after retry exhaustion
used to be disabled, killing a healthy schedule over a transient DB blip
(pool contention, statement timeout). Now that the unarmed-schedule
reconciler exists (#10174), transient failures no longer disable: the
current occurrence runs to completion and the reconciler re-arms the next
occurrence once this run leaves the queue.

In the flow schedule-push path after retry exhaustion we now branch on the
error: QuotaExceeded/NotFound still disable (the schedule's own fault, and
rearm_schedule would otherwise leave them enabled-yet-unarmed forever),
while transient errors are only reported and the flow continues.

The previous iteration returned a SchedulePushZombieError to force a zombie
restart; that is removed, because zombie detection cancels (does not
restart) same-worker flows, so it would have lost the current run of a
same-worker scheduled flow. The now-obsolete SchedulePushZombieError type
and its catch in worker.rs are deleted.

Fixes WIN-2198

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

* fix(schedules): back off and surface repeated reconciler re-arm failures

The unarmed-schedule reconciler retried a schedule that could not be
re-armed on every pass, forever, logging only to the server. With the flow
schedule-push path no longer disabling on non-transient errors, a
persistently-broken push (bad stored cron/timezone/args, lapsed license
key) now stays enabled and would spin in that loop silently.

The reconciler now tracks consecutive re-arm failures per schedule:
exponential back-off (2, 4, 8, … passes, capped) between retries so a
broken schedule is not hammered, and after 3 consecutive failures it
surfaces the cause once (records schedule.error + raises a critical alert)
without disabling. Both reset the moment the schedule re-arms, which also
clears the recorded error.

Verified end-to-end on a running server: a flow schedule with a corrupted
cron stays enabled, retries back off, the error is surfaced after the
third failure, and it re-arms and clears the error once the cron is fixed.

Fixes WIN-2198

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 22:48:15 +02:00
Ruben FiszelandClaude Opus 4.8 203f6c69dd fix(worker): gate ansible socket-dir name check to unix (#10180)
is_persistent_control_path_dir_name is only referenced from
prepare_socket_root, which is #[cfg(unix)]. On Windows nothing used it,
so -D dead-code failed the build. Gate the function and its unit test to
unix, matching the reaping code they support.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 21:26:40 +02:00
Ruben Fiszelandrubenfiszel 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>
v1.762.0
2026-07-17 20:15:13 +02:00
Ruben FiszelandClaude Opus 4.8 0b6bd376db ci: drop stale path_field_renames from mcp runner test constructor
#10176 removed `path_field_renames` from `EndpointTool`, while #10162 added a
new `ep()` test constructor in runner.rs that still set it. Each passed CI
against its own base, but together on main the test build fails with E0560.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:11:49 +00:00
GuilhemandClaude Opus 4.8 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
hugocasaandClaude Opus 4.8 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
hugocasaandClaude Fable 5 ae3d9ce2c0 fix(mcp): apply token scopes consistently across mcp endpoint tools (#10162)
* fix(mcp): apply token scopes consistently across mcp endpoint tools

Endpoint-tool authorization is now shared between single- and
multi-workspace modes, honors the token's script/flow path patterns for
every path-taking tool, and the JWT minted for proxied endpoint calls
carries scopes derived from the caller's own.

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

* chore(mcp): review nits: restore create_http_request doc, reword comments

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:34:17 +02:00
hugocasaandClaude Fable 5 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
GuilhemandClaude Fable 5 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 FiszelandClaude Opus 4.8 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
Ruben FiszelandClaude Opus 4.8 2ff5a918d5 feat(worker-tags): add * fork marker to workspace-scoped custom tags (#10177)
* feat(worker-tags): add `*` fork marker to workspace-scoped custom tags

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

* test(worker-tags): pin fork-marker tag admission through real lineage

Also render WorkspaceMatcher in its authored form in the operator-facing
"not in the allowed CUSTOM_TAGS" error, and correct the authorization note
on workspace_with_fork_ancestors.

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

* fix(worker-tags): gate fork-lineage tag lookup on workspace membership

exists_workers_with_tags is a global route whose workspace is a query
param. Resolving its fork lineage for a caller who is not a member
disclosed whether an arbitrary workspace descends from one named by a
tag(parent*) rule. Require membership before the lineage lookup, and
sharpen the workspace_with_fork_ancestors contract accordingly.

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

* chore(sqlx): add offline cache for fork-marker test INSERT query

The new test_fork_marker_tag_admission_through_lineage integration test
inserts a fork workspace with a bind-parameterized query that had no
cached entry, failing the SQLX_OFFLINE=true CI build.

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 18:18:54 +02:00
9762089fcb fix(schedules): re-arm enabled schedules left with no queued occurrence (#10174)
* fix(schedules): re-arm enabled schedules left with no queued occurrence

* fix(schedules): lock schedule row while re-arming and report outcome

* fix(schedules): make reconcile lock cancellation-safe, re-check armed under lock

Address review feedback on the schedule reconciler:

- Use a transaction-scoped advisory lock (pg_try_advisory_xact_lock) instead of
  a session-scoped one. monitor_db runs under a 600s timeout; on cancellation a
  session lock on a pooled connection would be stranded, wedging reconciliation
  on every replica. An xact lock releases when its transaction is dropped.
- rearm_schedule re-checks for a queued occurrence under the row lock and
  returns NoOp if already armed, closing the scan→lock window that could
  double-push across a cron boundary. Add a regression test.
- Make reconcile_unarmed_schedules private (its only caller is in monitor.rs)
  and document its system-only contract.
- Log the disable only after the guarded UPDATE actually disables the schedule.

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

* fix(schedules): never disable from reconciliation and cap re-arms per pass

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:08:10 +02:00
hugocasaandClaude Fable 5 1edee8aa34 perf: lazy-load session preview editor views for snappy AI sessions switch (#10172)
* perf: lazy-load session preview editor views to speed up AI sessions switch

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

* fix: stop editor prefetch chain on session page destroy and swallow chunk-load failures

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 18:05:38 +02:00
396fb1c475 feat(otel-tracing-proxy): trust internal endpoints with untrusted CAs (#10139)
* [ee] feat(otel-tracing-proxy): trust internal endpoints with untrusted CAs

Add `insecure_upstream_hosts` and `upstream_ca_certs` to the HTTP Request Tracing
settings so the OTEL tracing proxy can reach internal endpoints with untrusted or
private-CA certificates while keeping them traced. Wires the two settings through
the worker config and live reload, adds the inputs to the instance settings UI,
and pulls in the rustls upstream-client deps (hyper-rustls/tokio-rustls/rustls/
rustls-native-certs/rustls-pemfile; hyper-http-proxy switched to its rustls
feature).

The proxy-side implementation lives in the companion EE PR.

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

* chore: bump ee-repo-ref to otel_ca companion commit

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

* fix(otel-tracing-proxy): expose new fields in declarative config; bump ee-ref

Addresses code-review findings:
- Add `insecure_upstream_hosts` and `upstream_ca_certs` to the declarative
  `OtelTracingProxySettings` in instance_config.rs so operator/GitOps-managed
  installs can set them and reconciliation no longer drops values saved via the UI.
- Restore the trailing newline on ee-repo-ref.txt and bump it to the companion
  EE commit carrying the strict host-matching / port-ordering fixes.

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

* chore: update ee-repo-ref to 51e50629f48dbc4f5520a787b4bdfb76f4cd38d3

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

Previous ee-repo-ref: 49f458e4446395e98915c220baa757ab3b2ed2d8

New ee-repo-ref: 51e50629f48dbc4f5520a787b4bdfb76f4cd38d3

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-17 15:57:00 +02:00