Commit Graph
14759 Commits
Author SHA1 Message Date
Guilhem LemouelandClaude Opus 5 2199050518 feat(api): filter a conversation's messages by kind
A chat reopened while its flow is still running has to find the message
that started the turn: it carries the flow job, which is the only thing
that knows whether the run is over. An agent writes a row per round and
per tool call, so that message sits an unbounded number of pages back in
the transcript, and a client reading towards it is searching with no end
it can name.

`message_type` answers it in one request. The filter is applied inside
the paginated subquery, so `page=1&per_page=1&message_type=user` really
is the newest user row rather than the newest row that happens to be one.

`MessageType` carries a fourth kind, `System`, that nothing writes and
the Postgres enum has no label for. A request to filter on it is refused
before the database, so what reaches the caller is a word about their
request rather than an error about an enum.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 10:08:10 +02:00
Guilhem LemouelandClaude Opus 5 dda7ab2f30 refactor(chat): give a flow chat turn a module of its own
A turn was not a thing. What one is made of lived in three places — a
reactive status record, a non-reactive runtime map, and locals inside the
function following the job — so the question every piece of a turn's work
has to answer, "am I still the turn this chat is on?", had no owner. Each
await site answered it again from whatever abort flag it had in scope, and
each new await needed another answer; the counter this replaces was the
eighth such answer in as many review rounds.

`Turn` holds the run, the handle that stops it, the cursor its stream
resumes from, the rows it is writing and the timers it armed, and the chat
keys one per conversation. Work carries its turn and passes `isCurrent`
before writing, so a frame buffered before a Stop, a poll dispatched a tick
ago, or a settle that outlived its own turn all land nowhere instead of in
whatever replaced it. Ending a turn is one call rather than an abort, two
timers and a pair of reveals that every caller had to remember, and the
sites that release one say which turn they mean.

The chat itself is keyed on its flow and workspace, so changing flow builds
a new panel rather than re-pointing the old one. That is what makes the
guards removable: nothing survives a flow change to need them.

Fixes found while doing this, each of which the old shape allowed: a
two-minute polling deadline that outlived its turn and stopped the next
one's reading; a run launched into a stopped turn that nothing could name;
a conversation-list refresh on every poll tick of a new chat's first turn;
a failed page walk that freed a chat whose run it never got to ask about;
and a Stop during a launch that tore down the turn that came after it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 03:59:48 +02:00
Guilhem LemouelandClaude Opus 5 d3344eb3cd fix(chat): read a conversation forward from the start, and clear the polling deadline
A poll with no row to resume from sent no cursor at all, and the messages
endpoint answers that with the newest page rather than the oldest, so the
rows before it were never read and the sweep dropped the temp rows covering
them. Sequences start at 1, so reading from 0 puts every request on the
forward branch.

A read that stops at the request cap is now kept: each batch is a run of rows
from the cursor, so the next tick carries on from where it stopped. The last
poll of a turn has no tick after it, and there its prefix is dropped instead,
since it would sit beside the temp rows showing the turn's start twice.

`startPolling` armed a two-minute timeout it never held onto, and `stopPolling`
cleared only the interval. A turn ending inside two minutes left the timeout
armed to fire into the next turn on the same conversation and stop its polling
part-way, which a non-streaming turn feels as intermediate rows drying up. The
handle now lives on the turn's runtime and is cleared with the interval.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 18:56:35 +02:00
Guilhem LemouelandClaude Opus 5 b76485b0a1 fix(chat): count requests, not loop indices, in the partial-read warning
The counter was the loop variable, which leaves the cap exit one past the last
request and the stall exit on it — so the same expression printed 21 pages for a
cap of 20, in the one message whose job is to say which exit stopped it. It
counts requests that completed.

The constants shared a doc comment that described the cap while sitting on the
page size, and credited it with catching a cursor that stops advancing, which is
a separate check. Each says what it is.

The partial-read rationale is four lines rather than ten, and points at
`loadMessages` for why a reload is the recovery.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 18:11:15 +02:00
Guilhem LemouelandClaude Opus 5 0f2e7a3865 docs(chat): say what a partial read actually costs
The comment offered the next turn's poll as a recovery route. It is not one: the
cursor comes from the last persisted row, and applying none of them is exactly
what leaves it where it was, so the next poll reads the same window against a
conversation that has only grown. A reload is what recovers, and the comment says
so — it changes how the trade-off reads.

The warning blamed the page cap for both exits, so a cursor that stalled on the
second request reported twenty pages. It names which one stopped it and where it
got to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 18:04:20 +02:00
Guilhem LemouelandClaude Opus 5 3e90c8f5d9 fix(chat): a partial read of a conversation changes nothing
Keeping the temp rows on a capped read was half the answer: the rows that were
read got appended anyway, so the reader would have seen the same range twice with
the tail still missing. Reading that stops before the conversation does is not a
picture of it, so none of it is applied — the transcript keeps what it has, the
next turn's poll resumes from it, and a reload refetches.

A cursor that stops advancing takes the same route. It was marked a whole read,
which is the opposite of what it is: a full page came back, so rows almost
certainly remain. Unreachable against a backend that filters on the cursor, but
the two guards should not disagree about what they mean.

The warning carries how far it got, since the conversation id alone does not say
how far behind the transcript is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 17:56:38 +02:00
Guilhem LemouelandClaude Opus 5 ad847bfa78 fix(chat): make the page cap a state the transcript can survive
Stopping at the cap left the conversation looking fully read, and the sweep then
dropped the temp rows standing in for everything beyond it — the same vanished
answer the paging was added to prevent, one order of magnitude up. The cap is a
guard against a cursor that stops advancing, not evidence the conversation ends
there, so a poll that hit it keeps its temp rows and says so.

The paging test now pins the cursor as well as the rows: a second read that asked
for the same page again would have looked right from the rows alone.

Moves the attachment test out of the middle of another test's comment, which it
had split across two unrelated cases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 17:49:18 +02:00
Guilhem LemouelandClaude Opus 5 81d843a24c fix(chat): give each attachment its own object, and read a long turn whole
Two attachments can arrive under one name — a reader picking `report.pdf` from
two folders. Keyed on the name alone they raced to a single object, so the agent
read whichever landed last twice and never saw the other. Each gets a segment of
its own inside the turn's prefix; the name stays the last segment, so anything
reading a name off the key still sees what was attached.

The messages endpoint answers oldest-first with a limit, so one request only ever
reached the start of a turn that wrote more rows than a page — an agent calling
several tools a round. Its answer was among the rows left behind, and the sweep
then dropped the temp rows standing in for it, so the answer vanished until the
conversation was next loaded. The poll reads on until a page comes back short.

Leaving uploaded objects behind when a send does not run is deliberate, and now
says so: deleting them is a request that can fail on a path already failing, and
a resend uploads its own, so a lost send costs one prefix rather than a growing
number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 17:42:12 +02:00
Guilhem LemouelandClaude Opus 5 4ee585e07d fix(chat): guard every write-back behind the re-point, and order the conversation locks
The streaming path had the same unguarded gap the settle path was fixed for: its
final poll is awaited and then `endTurn(settled)` runs regardless. Settling
releases the conversation's queue, so a message typed before a re-point was
started against the flow now loaded, writing it into the previous flow's
conversation and agent memory — the thing the forgetting exists to prevent.

The poller and the sidebar's loader were unguarded too. Both are guarded at their
own await now, which covers every caller rather than each call site.

The editor chats keyed off `$initialPathStore || $pathStore`, which still falls
back to the typed path on a flow that was never deployed — and there the summary
field rewrites the path on every keystroke, so naming a new flow emptied the
composer as you typed. They take the editor's stable identity instead, which is
also what a preview run records.

`FOR UPDATE` over several rows takes them in whatever order the plan plans, so
two purges could acquire two conversations in opposite orders. Both sites order
by id. The transaction the lock depends on is now stated on `delete_jobs`, which
takes a bare connection and would silently lose the serialisation on one that
autocommits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 16:17:15 +02:00
Guilhem LemouelandClaude Opus 5 53bb9ce99a fix(chat): make forgetting a flow hold against work already in flight
Clearing the state was not enough: a transcript fetch, a latest-conversation
lookup or a settle tail started before the chat was re-pointed resolved after it
and wrote the previous flow's rows, selection or status straight back — the bug
the clearing exists to prevent, reached through the gap instead.

Each of those now carries the generation it started in and drops what it fetched
if the chat has moved on since.

The re-point test was asserting two things that could not fail — an empty rows
map and an empty conversation list, both empty either way — so it pinned half the
fix. It loads rows now and checks they are gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 16:06:43 +02:00
Guilhem LemouelandClaude Opus 5 89a797d498 fix(chat): follow the saved flow path, and serialise the second purge path too
Widening `cleanup()` made it destructive on a prop the editor changes far more
often than the flow does: both editor chats were passed `$pathStore`, which is
bound to the rename field, so the teardown that forgets a flow could run while
someone renamed one. They take the saved path instead, as the preview drawer's
other two call sites already do, so forgetting happens when the flow does.

The conversation cleanup in `jobs_export` is the same logic as retention's and
had the same race: it now takes the conversation rows before asking whether they
are empty. One lock order across both paths — messages, then conversation.

The lock reads rather than executes because a checked `SELECT` has no `execute`;
the discarded ids are the cost of the compile-time check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 15:42:02 +02:00
Guilhem LemouelandClaude Opus 5 ec9c4054f3 fix(chat): release a stuck chat, forget a flow on re-point, serialise conversation cleanup
Three separate holes, all in code this PR added.

`endTurn` cleared every flag but `isDispatchingTurn`, so Stop could not release the
hold a failed transcript load leaves behind. That flag gates the whole surface, so
a conversation whose rows never load took New chat and every other chat with it.

The route component is reused between two flows, so the chat is re-pointed rather
than rebuilt, and `cleanup()` only ended turns. The selection survived, so
`selectLatestConversation` found one already open and the new flow rendered — and
would have sent into — the previous flow's conversation and agent memory. Cleanup
now forgets what it holds about a flow.

Two `delete_jobs` calls each removing one of a conversation's last messages could
not see each other's uncommitted deletes, so neither collected the conversation
and nothing tried again, stranding the row and its `ai_agent_memory`. The
conversation rows are taken before the emptiness check, which serialises them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 15:25:39 +02:00
Guilhem Lemouel c820961dcc Merge origin/main into glm/update-chat-mode-ui
#11134 landed squashed, so main carries its changes while this branch carries its
commits. The flow chat components resolve to this branch's, as they did when the
branch was stacked on it: the SDK keeps serving external frontends and raw apps,
and the in-app chat runs on these components.

`agentFormFields` takes main's version — it grew `seed` and the `enabled_tools`
rule there, and this branch never edited it — with `agentStreamingEnabled` and
its tests grafted back, since FlowInput, FlowPreviewContent and the flow page all
call it. `runInput` stays removed; nothing reads it.

Brings the EE pin main already moved to, which is what the backend checks were
failing to compile against.
2026-09-15 15:01:47 +02:00
Ruben FiszelandClaude Fable 5.1 8b4f6220dc refactor: run the flow chat UI on the windmill-chat sdk (#11134)
* refactor: run the flow chat UI on the windmill-chat sdk

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

* fix: structural answer check, poll option and latest run in the chat sdk

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

* fix: count jobless tool rows and the loaded license in the flow chat

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 12:56:07 +00:00
Guilhem LemouelandClaude Opus 5 3b2eb1485c fix(chat): name the run before offering Stop, and hold a chat whose rows failed to load
Two holes in the window between opening a chat and its job answering.

Stop is on offer for that whole window, and it cancels whatever `status.jobId`
holds — which was not set until the answer came back. Pressing it then cancelled
nothing, ended the turn and freed the chat while the run carried on. The run is
named before the question, and unnamed again if the turn is not taken over.

A load that failed used to release the hold, on the reasoning that an empty
transcript has nothing to interleave with. It does: the run owns the
conversation's agent memory whatever the browser managed to fetch. The hold
stays; selecting the chat again retries the load, and Stop is the way out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 14:55:14 +02:00
Guilhem LemouelandClaude Opus 5 19af05b213 fix(chat): never free a chat on a guess about its run
The same rule the streaming path already follows, applied to the three places
that still decided for themselves:

- Non-streaming turns waited on `waitJob`, which stops answering without
  settling: on its fifth failed status request it cancels and returns, leaving
  its promise pending, and the chat busy for the session. They wait on the job
  the same way a stream-less turn does, and `pollJobResult` goes with it.
- A Stop whose cancel request failed still ended the turn, freeing the composer
  while the run it could not reach carried on. The turn is left to its job.
- Loading a conversation's rows was not covered: between opening a chat and the
  job answering, a send was accepted. The hold now spans the load and the
  question, and a failed question is asked again rather than taken as a no.

A turn with more rows than a page still has no user row on the page to name its
job, so it is not picked up. That needs the server to say which turn is running.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 14:38:28 +02:00
GuilhemandClaude Fable 5.1 c4e878e831 feat: return an ai agent step's thinking in its job result (#11140)
* feat: return an ai agent step's thinking in its job result

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

* docs: say when an agent result carries no reasoning

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 14:28:48 +02:00
Guilhem LemouelandClaude Opus 5 de8fd34334 feat(chat): pick a conversation's turn back up when its run is still going
The client was deciding whether a turn had ended from whether its own stream was
alive, which it cannot know. It asks the run instead.

Nothing in the browser survives a reload, so a chat opened while its flow was
running read as idle and the composer took a message — two turns writing one
agent memory. The row the server writes when a turn starts carries its flow job;
that job is asked whether it is over, and if not the turn is picked back up and
followed. The chat is held from the moment the question is asked rather than from
the answer, since a send accepted during that round trip is the thing this
prevents.

A stream resumed this way replays the rounds an agent has already persisted, and
the transcript cannot tell a replayed round from a second one, so the turn's own
rows are dropped before re-attaching; the final poll brings them back.

With that, a turn whose stream dies no longer needs a rule for giving up: it
polls its job until the run says it is over. The retry cap, the cancel fallback
and the settled/unsettled split go — a run that cannot be reached has said
nothing, and Stop is on screen throughout for the reader who wants out.

A turn with more rows than a page has no user row on the page to name its job and
is not picked up; finding it needs the server to say which turn is running.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 14:23:32 +02:00
Guilhem LemouelandClaude Opus 5 3de5a283fa fix(chat): cancel a run nothing can reach, and settle only a turn known to be over
An API that stops answering says the status is unknown, not that the run stopped,
and the run holds the conversation's agent memory. Marking the turn settled there
both freed the composer and flushed the queued message into a flow that might
still be writing.

The run is cancelled instead, which is what makes the chat safe to use again. If
even that cannot be delivered the turn ends unsettled: the chat is usable, but
its queue waits for the reader rather than going out beside a live run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 13:34:53 +02:00
Guilhem LemouelandClaude Opus 5 de876bbd86 fix(chat): settle a turn whose stream is gone, rather than waiting forever
The fallback awaited `waitJob`, which stops answering without settling: on its
fifth failed status request it cancels the run and returns from its inner poll,
leaving the outer promise pending. A turn handed to it never reached `endTurn`,
so the chat and its queued message stayed locked for the rest of the session.

The turn now waits on the job through the client it already has, and always
settles — either the run completes, or it stops answering enough times to call
it unreachable. `waitJob` itself is left alone; its other callers are outside
this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 13:25:44 +02:00
Guilhem LemouelandClaude Opus 5 3d60bf6e04 fix(chat): hand a turn whose stream is gone to its job, not to the composer
Running out of stream attempts freed the composer while the flow kept going, so
the next turn wrote the same agent memory — the thing the retry was added to
prevent, only deferred. The turn goes to `pollJobResult` instead, which keeps the
chat busy until the run reaches a terminal state and settles it on the rows;
`waitJob` cancels the run rather than waiting forever if the API stays down.

The budget is counted since the last attempt that delivered something, so a long
turn that blips once an hour is not treated like an endpoint that has gone.

An abort is checked inside the consumer loop: the SSE reader drains the frames it
has already buffered, and `endTurn` has by then reset the transcript state they
would be applied to.

The give-up toast truncates what it quotes — a 502 body is often a whole HTML
error page.

Drops the `flow_stream_job_id` scaffolding from the re-attach test: `followJob`
never surfaces that id, so the assertion could not fail for the reason its
comment claimed. Stop cancelling the flow rather than the streaming step is
verified against a running backend instead. Also drops the EventSource stub the
test no longer needs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 13:15:07 +02:00
Guilhem LemouelandClaude Opus 5 ec1c01d1bd fix(chat): re-open a failed stream rather than freeing the composer
`followJob` absorbs the server ending a stream on its own clock, but not the
request to open one failing — a restarting server, a 502. Ending the turn there
unlocked the composer while the flow kept running, and the next turn would write
the same agent memory.

Those attempts are retried from the offset already read, so the answer resumes
rather than replaying, and the turn stays busy across the gap. Bounded, so a
genuinely gone endpoint still reports itself instead of retrying forever under a
chat that looks live.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 13:05:18 +02:00
Ruben FiszelandClaude Fable 5.1 31c43255fd fix(worker): bound cache transfers and import fetches in bun jobs (#11138)
* fix(worker): bound object-store cache transfers and relative import fetches

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

* fix(worker): bound the codebase download and label slow-step warnings

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

* fix(worker): log a stalled cache transfer once and ignore a zero cache timeout

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 13:02:44 +02:00
Guilhem LemouelandClaude Opus 5 a6ec3914ed refactor(chat): follow a flow chat turn with the SDK's followJob
The flow chat kept its own EventSource loop: re-attach on the server's stream
timeout, resume from `stream_offset`, drop the offset when a retried step gets a
new stream job. `windmill-chat` does the same thing in `followJob`, which this
PR's base makes importable, so the transport moves there and ~190 lines go.

`followJob` is scoped to one job and holds no chat state, so turns still run in
several conversations at once — each drives its own generator, with an
AbortController in TurnRuntime where the EventSource was.

Three things it does that the loop did not: it buffers a chunk that ends
mid-line, where ours parsed the halves separately and dropped the token; it
reconnects on a close that carried no timeout frame, where ours ended the turn;
and it starts a fresh parser when the streaming step changes.

Stop now cancels the flow job rather than the streaming step. `followJob` never
surfaces `flow_stream_job_id`, so the line that overwrote `status.jobId` with it
goes — which is what the comment on the line that sets it always said should
happen, and what keeps the steps after the agent from running on.

`poll_delay_ms` is sent only with an enterprise licence; a CE server ignores it
and warns on every poll.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 12:59:35 +02:00
Guilhem LemouelandClaude Opus 5 0e20a5d751 feat(ai-chat): render flow chat mode through the AI session chat components
The flow chat ran on its own components; this points it at the ones the AI
session chat already uses, so the two surfaces share a transcript, a composer
and a sidebar instead of keeping two of each.

The seam is `ChatViewHost` (copilot/chat/chatViewHost.ts): the view components
read the host rather than `AIChatManager` directly, and `getChatViewHost()`
falls back to the session manager, so the copilot's call sites are unchanged.
`FlowChatViewHost` is the second adapter, over `FlowChatManager`.

What the flow chat gains from the move:

- turns running in several conversations at once, with a status, a queue and a
  Stop per chat, and an unread count on the rail
- attachments, uploaded to the workspace's object storage for the worker to read
- a composer that speaks for the agent steps a message is fed to: the model, the
  thinking effort, and the flow inputs the agent reads straight out of
  `flow_input`; everything else is asked for in a Configure-inputs modal
- tool cards with the call and the result, the model's reasoning, and a step name
  per answer once a conversation holds more than one agent
- Retry, which replays the failed turn's own run arguments read back from its job
- named and renamable conversations, and test chats kept out of the deployed
  flow's list

Backend: conversation rows carry an MCP tool's call and result and the model's
reasoning, which live nowhere else; every row gets a job so retention can empty
it; and the providers parse a non-streaming answer's reasoning.

Stacked on #11134, which keeps the windmill-chat SDK for external frontends and
raw apps while the in-app flow chat runs on these components.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 11:46:45 +02:00
Ruben Fiszelandrubenfiszel 9fe493311d chore(main): release 1.812.0 (#11111)
* chore(main): release 1.812.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.812.0
2026-09-15 11:40:01 +02:00
a78beff743 feat: dynamic AI agent toolsets (#11050)
* feat: dynamic ai agent toolsets, and memory as a step input

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

* fix: address review round 1 on dynamic ai agent toolsets

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

* refactor: tag enabled_tools and drop the memory step input

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

* fix: let an mcp server entry be named by the path the roster shows

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

* fix: keep $res: out of the tool names the enabled tools picker offers

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

* fix: name an mcp server by its bare path on the one side that can hold it

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

* fix: count the enabled tool names that matched nothing instead of logging them

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

* refactor: narrow an agent's roster in one pass, by whole entries

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

* test: pin that an mcp summary is rejected against a name that is not

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

* chore: regenerate the copilot flow schema after the merge

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

* docs: shorten the enabled tools list hint

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

* refactor: take enabled_tools back to a plain list of tool names

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

* docs: keep the enabled tools add-menu hint describing the unset field

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

* fix: name a websearch tool that carries no summary of its own

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

* fix: reserve the name web search is enabled by so no tool can share it

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

* refactor: spell the reserved web search name with a hyphen

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

* refactor: reserve __wm_web_search as the name web search is enabled by

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

* chore: advance ee-repo-ref past the git sync ci check work

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

* docs: shorten the enabled tools description the run form shows

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

* chore: update ee-repo-ref to e4c1b794d6c5e6e390987341b2840587bbb40348

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

Previous ee-repo-ref: af668462f0f06b02a5f4e0c22e6156858487a518

New ee-repo-ref: e4c1b794d6c5e6e390987341b2840587bbb40348

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-09-15 11:34:30 +02:00
hugocasaandClaude Opus 5 42f489685b feat: store resource type display names and label hub integrations (#11113)
* feat: label resource types and integrations with hub display names

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

* fix: load hub integration names in the app and flow pickers

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

* fix: load hub resource type names where drawers title a type

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

* feat: store resource type display names and drop the hardcoded list

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

* fix: leave display_name out of the fork comparison

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

* fix: ignore over-long synced display names, move name loaders

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

* fix: share the hub integration list cache, backfill admins only

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

* fix: keep a name over a nameless duplicate, retry failed hub reads

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 11:32:50 +02:00
69e6efd875 fix(git-sync): run auto-pull as the admin who enabled it (#11121)
* fix(git-sync): run auto-pull as the admin who enabled it

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

* fix(git-sync): audit the admin grant fork pulls make

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

* chore: bump ee ref for the post-commit fork grant audit

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

* fix(git-sync): address review nits on the auto-pull stamp

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

* chore: update ee-repo-ref to ccada062c072d7b74894b63863728fd1ef9bdffd

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

Previous ee-repo-ref: 7cee30f0cf12721cba551cd754dc817444810470

New ee-repo-ref: ccada062c072d7b74894b63863728fd1ef9bdffd

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-09-15 10:34:57 +02:00
Alexander PetricandClaude Opus 5 082d897328 stop the variables page opening a drawer for the instance settings hash (#11129)
Claude-Session: https://claude.ai/code/session_01LCt167dnWJ2EVnLpkHATh4

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 10:30:19 +02:00
Ruben FiszelandClaude Fable 5.1 3895e1d579 fix: count jobless tool rows and the loaded license in the flow chat
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 10:22:51 +02:00
Ruben FiszelandClaude Fable 5.1 dd7ee0cdbb fix: structural answer check, poll option and latest run in the chat sdk
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 10:08:53 +02:00
Ruben FiszelandClaude Fable 5.1 77740b278e refactor: run the flow chat UI on the windmill-chat sdk
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 09:47:26 +02:00
Ruben FiszelandClaude Fable 5.1 96963080f1 fix(python): parse wheel RECORD paths as RFC 4180 csv fields (#11133)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 09:45:50 +02:00
Ruben Fiszelandwindmill-internal-app[bot] e3e638f7f5 fix: skip instance group members that are not email addresses (#11128)
* fix: skip instance group members that are not email addresses

* fix: keep provisioned members whose address only proper_email accepts

* fix: judge instance group members by a mirror of the usr email constraint

* fix: fold ascii only in the proper_email mirror, like the constraint

* fix: let the database judge which instance group members usr will store

* fix: cut a derived username to the column width so a long local part can be provisioned

* chore: move the ee pin to the scim member doc fix

* chore: update ee-repo-ref to 0780955effb657807d14f0eb503cba1d49cee007

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

Previous ee-repo-ref: ee6452d489563204a98df883703f78d5e74cdd69

New ee-repo-ref: 0780955effb657807d14f0eb503cba1d49cee007

Automated by sync-ee-ref workflow.

---------

Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-09-15 09:45:10 +02:00
Ruben FiszelandClaude Fable 5.1 75ee497011 fix(cli): stage a rewritten shared lockfile on git-sync deploy push (#11126)
* fix(cli): stage a rewritten shared lockfile on git-sync deploy push

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

* test(cli): pin that a swept shared lockfile is committed as a deletion

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

* chore: bump the git sync hub script to 28969

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 23:16:37 +02:00
Ruben FiszelandClaude Fable 5.1 5dcf40cb4f chore: move the EE pin forward to the commit that claims pending oauth accounts (#11127)
Claude-Session: https://claude.ai/code/session_01GGciSSE5EFMiDf1dFWQq5M

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 23:06:36 +02:00
Ruben FiszelandClaude Opus 5 57a134e2de feat(ai-sessions): share session artifacts with the workspace by link (#11115)
* feat(ai-sessions): share session artifacts with the workspace by link

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pyjp67oR269QAx3b4yf4oH

* chore: cache the shared artifact queries for offline sqlx

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pyjp67oR269QAx3b4yf4oH

* fix: replace a literal NUL byte in the shared artifact body limit comment

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pyjp67oR269QAx3b4yf4oH

* test: pin that a shared artifact is confined to its workspace's path

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

* fix: sanitize shared artifact markdown and validate the artifact id on every route

The shared page renders another member's markdown, so ArtifactBody now runs the repo's rehype-raw + rehype-sanitize chain with the chat's link renderer on top; only the session viewer opts into the chat code block (mermaid, apply button). The link renderer keeps a link's text when its href is empty or unsafe, and the scheme check moves to a tested helper. The status route checks artifact_id like share does, so a NUL is a 400 rather than a 500.

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

* fix(ai-sessions): say which way re-sharing moves an artifact link

The popover offered "Update to v1" when a v2 link was open on a pinned v1, which reads as if v1 were newer. Each direction now has its own sentence and action: a newer version on screen updates the link, an older one shares that version instead, a rename updates the name.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 22:46:26 +02:00
Ruben FiszelandClaude Fable 5.1 e8c02c04cd feat: windmill-chat sdk for chat-mode flows in external frontends and raw apps (#11117)
* feat: windmill-chat sdk for chat-mode flows in external frontends and raw apps

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

* fix: keep streamed answers until persisted, finish turns after history fallback

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

* feat: ai sdk transport and assistant-ui runtime for windmill-chat

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

* fix: finish a turn from the flow result until its answer row lands, hash chat ids without crypto.subtle

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

* fix: judge a turn answered by a persisted assistant row, wherever it was fetched

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

* fix: attribute a turn's answer to its own jobs, keep a local turn when switching conversations

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

* fix: mirror local history on every change, attribute failure-handler answers to the turn

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

* fix: new chat per token string in the React hook, idle after destroy, no reorder on view

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

* fix: recreate the hook's chat on any credential change, namespace local history per user

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

* fix: send the latest inputs from the React hook

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 22:32:45 +02:00
d0cac0807f fix: set the enclosing span's trace context on exported log records (#11123)
* chore: pin the EE ref that stamps trace context on exported log records

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

* chore: pin the EE ref with the sampling-decision test

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

* chore: update ee-repo-ref to 04a9f1efb4a52c79fcd20258b34780c86103d27f

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

Previous ee-repo-ref: d48361d66580618cb7a934d3c5f56a0c7e39ffaa

New ee-repo-ref: 04a9f1efb4a52c79fcd20258b34780c86103d27f

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-09-14 22:15:37 +02:00
Ruben Fiszel a95e950529 feat(cli): list, get and restore trashed items with wmill trash (#11125)
* feat(cli): list, get and restore trashed items from the CLI

* docs(cli): tell agents a sync push deletion is restorable with wmill trash

* refactor(cli): share the ApiError formatting and type trash flags as integers
2026-09-14 20:06:56 +00:00
91e6dc39ce feat: pre-approved cloud accounts: login links, OAuth adoption, setup, and the trial bridge (#10875)
* feat: single-use login links and oauth-claimable pending accounts

* docs: capture the auth surface facts behind login links

* fix: accept stringified email_verified from oauth userinfo

* docs: describe the oauth claim rule in the auth surface notes

* fix: harden login-link redirects and sweep expired links

* chore: bump ee-repo-ref

* fix: keep expired login links a day so an open still reads as expired

* fix: refuse login links for superadmin and devops accounts

* fix: re-check the account's roles when a login link is opened

* feat: pre-approved cloud accounts finish their setup and start their trial from Windmill

* feat: dev-only localStorage opt-in to the cloud UI on localhost

* feat: finish-setup entry in the desktop settings menu

* style: pulse the settings row while account setup is pending; shorter, blue finish-setup entry

* fix: list the configured providers in the finish-setup modal

* fix: open the finish-setup modal after the menu has closed

* feat: finish-setup provider sign-in keeps the session when the provider asserts another address

* chore: pin the EE companion commit

* fix: plain toast for the finish-setup refusal

* style: format the dev cloud override

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

* feat: onboarding skips the source question an invite already answered

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

* fix: type the finish-setup icons and login_type as the frontend uses them

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

* feat: invited accounts get a workspace name, hub picks and starter prompts from their invite

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

* fix: the workspace form reads the invite's name itself, so the picker prefills it too

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

* feat: an empty workspace offers the projects its invite picked, one click from importing

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

* style: picked projects get identical import buttons

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

* feat: a pinned sidebar banner until an invited account has credentials of its own

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

* style: the account-setup row speaks the rail's language, tinted not filled

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

* chore: pin ee-repo-ref to the import fix

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

* refactor: picked projects live in the template picker only; account-setup row moves to the rail footer

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

* fix: review round — no portal login for job tokens, finish-setup failures keep the session, prompt labels deduped

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

* fix: CI round — trial start is a POST, profile cache follows the session, setup row on MenuButton

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

* fix: CI round — no password road where password login is off, cache note on the login form, trial refusal surfaced

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

* fix: CI round — set_password guarded on its read, refusal stays on the page, docs and formatting

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

* fix: CI round — popup OAuth clears the profile cache, portal helper crate-private, refusal toast stays

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

* fix: a refused trial is recorded inline in the rail, not in a day-long toast

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

* fix: the refusal notice uses the rail's button and has a collapsed form

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

* fix: CI round — SSO can finish account setup, with the same mismatch refusal as OAuth

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

* fix: SSO finish-setup rides in RelayState and the refusal notice is a status region

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

* fix: keep the finish-setup cookie beside RelayState, hoist the status region, pin session-keyed profile cache

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

* fix: empty live region for the trial refusal, drop the setup cookie once adopted, telemetry inventory

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

* fix: the trial refusal survives the responsive sidebar swap

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

* fix: the trial refusal is shown to the account it answers, modal open prop is required

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

* feat: an invited account skips the whole onboarding survey

Its source is the invite and its use case was researched before the invite went
out, so neither question is asked: the known source is recorded and onboarding
opens on naming the workspace. Accounts without an invite profile see the survey
exactly as before.

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

* fix: an invited account with a workspace leaves onboarding before anything paints

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

* fix: account-setup state resets on sign-out, onboarding shows a loading state while it settles

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

* style: keep the refresh doc comment on refresh

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

* fix: profile lists are distinct, and the offer table notes what a users-import does to it

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

* chore: update ee-repo-ref to 1ba6fe83451f0a1f8fafe04b7187087d51e0f769

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

Previous ee-repo-ref: be42722d09832ffff709a1f710f3e97e34d513b2

New ee-repo-ref: 1ba6fe83451f0a1f8fafe04b7187087d51e0f769

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>
2026-09-14 20:04:58 +00:00
Ruben FiszelandClaude Fable 5.1 0b1e9c0dda fix: wake a WAC parent from every path that completes its child (#11119)
* fix: wake a WAC parent from every path that completes its child

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

* fix: park a WAC parent before writing its checkpoint so lock order matches child completion

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

* fix: check the parent-child link before touching a WAC parent, wrap the fallback error, keep inline checkpoints in lock order

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

* docs: say the zombie fallback keeps the WAC parent notification in its transaction

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 18:51:21 +00:00
GuilhemandClaude Fable 5.1 a4ddbd733b lower the workspace creation handover floor to 500ms (#11109)
Claude-Session: https://claude.ai/code/session_015hBkusKsmiRiZ9VZVE314G

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 19:28:57 +02:00
AlexRV12andClaude Opus 5 5d32b61067 feat: run a flow step test through the chat's argument form (#11114)
test_run_step was the last run tool still starting a job on whatever the
model sent. Route it through runThroughForm, as test_run_script,
run_script and test_run_flow already are.

A step's arguments are its own, not the flow's: it is normally fed by its
input transforms, so the form is built from the step's target rather than
the flow's schema. loadSchemaFromModule resolves script and subflow steps
against the deployed version, which would offer the fields of code this
path is not about to run, so the schema comes from the same read the job
uses — inferred from a rawscript body, the draft script's content, or the
subflow's own schema.

The preprocessor's _ENTRYPOINT_OVERRIDE is declared by no schema, so it is
added inside the resolved startJob: proposed into the form instead, the
argument conforming would drop it and the preprocessor would silently run
its main. Its schema is inferred rather than read off the target for the
same reason a stored one cannot describe it: a schema speaks for the one
entrypoint it was inferred from.

executeFlowStepTestRun splits into resolveFlowStepRun plus a thin wrapper,
so the flow editor's own test_run_step keeps its behaviour but for one fix
it inherits: a deployed subflow step now runs with skipPreprocessor. The
flow editor's step test passes it too, and a parent flow pushes a subflow
step the same way (apply_preprocessor: false) — a preprocessor would take
the subflow's own inputs for a trigger event.


Claude-Session: https://claude.ai/code/session_018PBB2gw8FK4YmGPxu5Drbn

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 19:28:09 +02:00
GuilhemandClaude Fable 5.1 94c548cd5d fix: re-attach flow chat to the same job on SSE timeout instead of re-running it (#11122)
* fix: re-attach flow chat to the same job on SSE timeout instead of re-running it

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

* fix: restart the flow chat stream when the streaming sub-job changes across a reconnect

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 19:27:27 +02:00
hugocasaandClaude Opus 5 d54a66f15c fix(cli): say where a sync push deleted variable or resource went (#10851)
* fix(cli): keep variables and resources a sync push repo never tracked

`wmill sync push` archives a script it no longer finds locally, but hard-deletes
a variable or a resource: the credentials go for good. A remote-only one is
equally a deletion being deployed and one the repository never had, provisioned
on the instance or written by a script at runtime, and reading the second as a
deletion is unrecoverable.

Committed history tells them apart. A push whose changeset deletes a variable or
resource now asks what this branch has ever tracked at `*.variable.*` /
`*.resource.*`; anything it has never recorded is kept on the remote (prompted
for on a TTY), and a real deletion, recorded before the commit that removed it,
still applies. Where the history cannot be read (shallow clone, sparse checkout,
no repository) there is no evidence either way, so the deletion stands as before
with a warning naming the remedy — the git-sync "Pull from repo" job runs in a
depth-1 clone and must keep deploying the deletions it always has.

`--delete-untracked-secrets` / `deleteUntrackedSecrets` opts a mirror-semantics
pipeline back into deleting them unattended.

Fixes GIT-980

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

* fix(cli): classify secret-bearing deletions the way the push itself does

Three ways the suffix match missed:

- A fileset child can be any file, `inner.resource.yaml` included, and its
  deletion re-pushes the parent rather than deleting anything. Classifying with
  the push's own `getTypeStrFromPath`, behind the same fileset exclusion the
  apply loop uses, keeps the two in step.
- Deleting `f/x.resource.file.ini` deletes the resource `f/x` outright, so
  without that file in the pathspecs every file resource walked past the check.
  Its two files now count as the one resource they delete.
- A `specificItems` item is committed as `y.<workspace>.variable.yaml` while
  `elementsToMap` collapses it to the base path the changeset carries, so a
  deletion the user did commit read as never tracked. The history is searched
  under both names.

`gitRecordedPaths` also reads its history with `core.quotePath=false`: a path
with a non-ASCII byte came back C-quoted and matched nothing.

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

* fix(cli): judge held-back deletions per object, not per file

`DELETE /variables/delete` takes the resource at the same path down with it, and
`DELETE /resources/delete` does the same to the variables its value references,
so a tracked deletion could destroy an untracked object the push had just
reported it was keeping. A file resource had the same shape from the other end:
two files for one resource, either survivor deleting it.

The unit is the server-side object. One file left unaccounted for by history now
holds the whole object back, so nothing in a group reported as kept is deleted.
The residual is a resource whose value references a variable at another path,
which stays possible and is called out in the PR.

Also corrects what the messages claim. Deleting a variable or resource is not
irrecoverable: both move to the workspace trash, which keeps them for three days
(migrations/20260326000000_trashbin.up.sql, CE since v1.665.0). The asymmetry
with a script is real but narrower, and the prompt defaults to No, so it should
say what it actually costs.

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

* fix(cli): warn when sync push deletes variables the repo never tracked

`sync push` deletes a remote variable or resource that has no local file. An
object the repository has never tracked was provisioned outside it, by hand or
by a script at runtime, rather than deleted from it, and the change list said
nothing to tell the two apart.

The push still deploys every deletion — that is what the repo-is-the-mirror
contract means, and a shallow clone (the git-sync "Pull from repo" job, a
default actions/checkout) could not tell them apart anyway. What changes is that
the preview names the ones this branch's history has no record of, before the
prompt that confirms them, and points at the excludes that stop them recurring.

Deleting is also not final, which the CLI was alone in not saying: both handlers
move the item to the workspace trash first, restorable for three days
(migrations/20260326000000_trashbin.up.sql, CE since v1.665.0). A push that
deleted any now says so.

This replaces the earlier hold-back design. Keeping objects back changed what a
push deploys, needed a flag and a wmill.yaml key to opt out of, and could claim
to keep an object that a linked deletion then cascaded onto. Reporting cannot.

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

* fix(cli): name the paths the untracked-deletion warning is about

A push can delete a tracked and a never-tracked resource together, where "1
resource" identified neither. The warning lists the paths instead of counting
them, so the reader knows which one to exclude.

Outside a git checkout it no longer opens "This branch's history", which
contradicted the reason it went on to give, and it drops the pronouns that
disagreed with a plural count. The history walk is skipped under --json-output,
where both notices are silenced and its result had no reader.

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

* fix(cli): vouch for an object with any file in history, not just deleted ones

The tracked set was built from the deletions being judged, so a companion file
the push was not deleting could not vouch for its object: a file resource whose
`.resource.yaml` stays while its content file goes was reported as never
tracked, though the repository plainly owned it. It is built from the whole
history now, which also turns the workspace-specific lookup around — history is
normalized to base paths, the form the changeset already carries, instead of
each candidate being searched for under two names.

The warning also prints one line per server-side object rather than per file, so
a file resource is the one deletion it is rather than two.

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

* fix(cli): name both kinds at a shared path, gate the history path conversion

Three from review:

A variable and a resource at one path are judged together, since deleting either
takes both, but they are two objects to name — keying the printed lines by path
alone dropped one of them.

`fromWorkspaceSpecificPath` strips a `.<workspace>` segment wherever it finds
one, so a history entry that merely looks workspace-suffixed was re-keyed onto a
different object, whose history then vouched for it. Only a path `specificItems`
claims is converted now. Not reachable from a server object (the backend rejects
`.` in paths), but history holds whatever was committed.

`secretBearingKey` lost its last caller when the tracked set moved to object
paths; removed.

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

* fix(cli): identify a secret-bearing object by kind as well as path

A variable and a resource can share a path and are still two backend objects:
`DELETE /variables/delete` drops the same-path resource unconditionally, while
`DELETE /resources/delete` drops only the variables its value references. Keying
tracked history by path alone let a committed variable vouch for a resource the
repository never had, which then went unmentioned. The cascade is a reason to
report both, not to treat them as one. A file resource's two files keep one id.

`git log HEAD` also fails on a repository with no commits, which was reported as
"its history could not be read. Check that git runs correctly in this directory"
— true of neither.

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

* docs(cli): tighten the comments on the untracked-deletion warning

Halves the prose without dropping a constraint: the trashbin retention is
stated where the message says it rather than twice more in doc comments, and the
two stacked comments at the print site had come to contradict each other, one
still describing a same-path variable and resource as judged together.

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

* fix(cli): say where a deleted variable or resource went

`sync push` deletes a remote variable or resource that has no local file, and
said nothing more. Both handlers move the item to the workspace trash first,
restorable for three days (migrations/20260326000000_trashbin.up.sql, CE since
v1.665.0), and the CLI was the one surface never to mention it — the report
behind this concluded the deletion was final and there was nothing to restore.

A push that deleted any now ends with where they went and how long they have.

Drops the untracked-deletion warning this branch carried: distinguishing a
deletion the repo deployed from an object it never owned needs the branch's git
history, and roughly 130 lines to read it and be right about the answer, for a
claim the trash already softens. Two fixes it turned up in the data-table
migration guard, which reads history the same way, are kept: a path with a
non-ASCII byte came back C-quoted and matched nothing, and a repository with no
commits was reported as one where git does not run.

Fixes GIT-980

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

* fix(cli): name the trashbin correctly and say who can restore

The tab is labelled Trashbin, not Trash, and `restore_trash_item` requires
admin, so a non-admin reading the old line would go looking for a control they
do not have.

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

* refactor(cli): move the migration-guard git fixes to their own PR

They fix `gitRecordedDatatableMigrationPaths`, which this PR no longer touches.

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

* fix(cli): don't count a .lock deletion the push skips

The apply loop `continue`s past a non-raw-app, non-dbt `.lock` deletion before
reaching the delete switch, so nothing happens on the server. The classifier did
not mirror that, and `f/x.resource.file.lock` reaches it as a resource through
`isFileResource` — a resource type whose format_extension is literally `lock`
would have the notice announce a deletion the push never performed.

A raw-app or dbt `.lock`, the two that loop does not skip, classifies as its
bundle's own kind well before the file-resource check, so a suffix test is
enough.

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

* docs(cli): state what the classification tests protect

The header described the change rather than the invariant.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:07:33 +02:00
Davide Modolo 56e21bce83 fix(flows): stop re-evaluating skip_if once a loop is in progress (#11008)
* fix(flows): stop re-evaluating skip_if once a loop is in progress

skip_if is a one-time entry gate, but the flow stays at the same step
for a loop's whole lifetime, so it gets re-evaluated on every
iteration. previous_id stays pinned to the module preceding the loop,
but once the loop is InProgress the last completed job is an inner
iteration, and the results proxy in windmill-jseval aliases
results.<previous_id> to that job's result. skip_if then reads the
wrong value and can flip the loop's module to skipped after one
iteration.

Skip the check once status_module is already InProgress.

* fix(flows): match skip_if gate to sibling entry-state allowlists

Rewrite the skip_if gate as a positive allowlist (WaitingForPriorSteps
| WaitingForEvents | WaitingForExecutor), matching the shape already
used by the BranchOne/BranchAll predicate gates, instead of a negative
filter on InProgress. Restart-at-iteration also enters as InProgress;
document it as a separate case rather than folding it into the
aliasing reason, which does not apply there.

Add a regression test pinning skip_if to run once at while-loop entry.
2026-09-14 19:05:45 +02:00
GuilhemandClaude Fable 5.1 244ec13291 fix(ai-chat): hide other users' MCP servers from the chat unless shared (#11112)
* fix(ai-chat): hide other users' MCP servers from the chat unless shared

An admin's database role lets the resource listing return every user's
u/ MCP resource, so the chat's "+" menu and the assistant settings tab
offered servers that carry someone else's credentials. Both lists, and
the tool loader behind them, now keep a u/ server only when it belongs
to the current user or its extra_perms name them or one of their
groups. The Resources page is unchanged.

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

* fix(ai-chat): resolve the MCP viewer for the workspace being listed

Username and groups are per workspace, and a session chat can operate on
a workspace other than the one being browsed, so the filter now takes its
identity from that workspace's whoami rather than from userStore.

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

* fix(ai-chat): list the whole MCP catalog before filtering, one predicate

The visibility filter runs after the server's LIMIT, so a 100-row page
could drop the viewer's own servers behind foreign u/ rows. The three
call sites now ask for 1000 and call the tested predicate directly.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 15:55:52 +02:00
AlexRV12andClaude Opus 5 56dd940e34 fix: keep a script draft's password marking through the chat's run form (#11110)
* fix: keep a script draft's password marking through the chat's run form

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dx9oMWHnKywoqooZBeaPCy

* docs: describe what the inferArgs test mock actually lets the suite pin

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dx9oMWHnKywoqooZBeaPCy

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 15:55:40 +02:00