Commit Graph
1185 Commits
Author SHA1 Message Date
hugocasaandClaude Opus 5.5 71d12a0cf1 feat: agent detail page config panel, run page on form runs, chat badge
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 10:15:38 +02:00
hugocasaandClaude Opus 5.5 161e19da44 fix: treat a loading agent as undeployed when leaving the editor page
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 10:33:41 +02:00
hugocasaandClaude Opus 5.5 0f60dca4bd fix: explain when an agent cannot be run and drop the broken agent move
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 09:55:26 +02:00
hugocasaandClaude Opus 5.5 ff42120888 feat: add agent detail and editor pages
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 09:48:17 +02:00
hugocasaandClaude Opus 5.5 09bc536b78 fix: autosave a new agent's first edit and list agents past one page
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 09:35:17 +02:00
hugocasaandClaude Opus 5.5 0d6f2c3bf1 fix: keep runnables out of the agents view and anchor a new agent once saved
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 09:31:52 +02:00
hugocasaandClaude Opus 5.5 252a086e97 feat: list agents on the home page and create them from the new menu
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 09:26:17 +02:00
GuilhemandClaude Opus 5.5 62d0088de5 feat: show restart from failed step in run page top bar (#11317)
* feat: show restart from failed step in run page top bar

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

* fix: keep failed-step restart button when selection is not restartable

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

* feat: add restart link to the failed run status line

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

* fix: use the input error color for the progress bar error state

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

* feat: mention restarting on a fixed flow version in the error line

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

* feat: show a chevron on the restart button when it opens a picker

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

* fix: shrink run page top bar buttons so the bar fits on one line

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

* fix: target the step that failed the run, not a tolerated failure

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

* fix: address review nits on the restart link

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

* fix: skip canceled runs and match the restart link label to its text

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 17:37:41 +02:00
GuilhemandClaude Opus 5 fa0fc24269 feat: render a tool's web search result as the session web search card (#11313)
* feat: render a tool's web search result as the session web search card

Any tool whose result is exactly `{ sources: [{ url, title? }], query? }`
now renders as the card the AI session shows for a provider-side web
search, in the agent step trace and in the flow chat. The provider-side
search itself gets the same card on both surfaces: its citations, which
the worker records on the assistant turn that follows, move onto the
search row.

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

* fix: keep the trace tool label off the lazily fetched job

The web search card's label read the tool's job, which is only fetched
once the row is expanded, so the query suffix appeared only after the
row had been opened. The shape of the result decides the label; the
job's failure still decides whether the sources or the error show.

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

* fix: keep unrenderable sources and tool hostnames out of the search card

A result whose urls the card cannot render — relative, or javascript: —
matched the shape and replaced the tool's own output with an empty list,
so the predicate now requires what the renderer accepts. A tool's sources
can name internal services, so they no longer resolve a favicon through
Google's service; provider-side search results still do. An absent
optional reaches JSON as null from a Python tool, which now reads as
absent rather than rejecting the whole result.

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

* fix: name the host when a source carries a blank title

A source whose title is an empty string rendered as a link with no text
at all, the empty title also suppressing the hostname beside it.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 12:33:12 +00:00
hugocasa d8aaa73573 feat: chat with a saved agent from the agent editor (#11292)
* feat: chat with a saved agent from the agent editor

* fix: keep agent chats apart from a same-path flow's conversations

* fix: point an agent editor chat's model gap at the agent, not a step

* fix: match the memory gate's icon to the chat's empty state
2026-09-22 17:07:43 +00:00
GuilhemandClaude Opus 5 4cbc944c0b perf: keep the sessions page fast with many ai sessions (#11224)
* perf: skip whole-document menu lookup on pointerdown when closed

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

* perf: load only a session's own chats in its chat history

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

* perf: stop mounting a chat per listed session on the sessions page

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

* fix: keep unsent input, init and chat rotation safe from runtime eviction

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

* refactor: cap mounted chat panes instead of evicting session runtimes

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

* refactor: read only message count and error for sessions without a runtime

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

* fix: keep a chat pane mounted while its composer reads attachments

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

* fix: hold a session's first send until its chat has been restored

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

* fix: queue a second send behind one waiting on the chat restore

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

* fix: queue raw-app inline prompts behind a send waiting on the restore

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

* fix: keep a chat pane mounted while its send waits on the restore

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

* fix: preserve session chat drafts across async updates

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 15:04:56 +00:00
6186a0645d feat: edit variables, resources and triggers in their own session tab (#11206)
* feat: edit variables, resources and triggers in their own session tab

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

* fix: take a trigger's flow kind from its config and name the written trigger

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

* refactor: one operating-workspace context for editors acting on a session's workspace

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

* fix: reload page item tabs only in sessions acting on the tool's workspace

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

* fix: resolve open in workspace for every preview tab kind in one place

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

* fix: guard every component under a session editor against the navigation workspace

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

* fix: show a page item tab whose item is gone instead of mounting its editor

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

* fix: follow a saved page item in the tab's own workspace and restore in-frame rows

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

* feat: close a page item tab from its editor header

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

* fix: judge trigger permissions by the user acting in the session workspace

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

* fix: judge every session-editor permission by the user acting in that workspace

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

* fix: keep session permissions reactive to the acting role and restore tabs as last seen

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

* fix: default Path's owner controls to the user acting in its workspace

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-09-19 23:29:14 +02:00
GuilhemandClaude Opus 5 3b4e13d1c5 fix: key the large root font size on screen width, not window width (#11216)
* fix: key the large root font size on screen width, not window width

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

* fix: move the flow preview button offset to the screen-width query

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 14:51:32 +02:00
hugocasaandClaude Opus 5 a08992834d feat: render an AI agent result as its answer, not as raw JSON (#11051)
* feat: render an AI agent result as its answer, not as raw JSON

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

* fix: sanitize agent markdown through the shared plugin chain

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

* refactor: fold agent stream events incrementally per poll

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

* fix: separate the agent meta line from the result toggle group

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

* feat: add a transcript view of an agent run's conversation

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

* refactor: retire AIAgentLogViewer in favour of the transcript

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

* feat: show the partial transcript a max-iterations failure carries

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

* fix: move the agent meta line and system prompt below the conversation

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

* refactor: show an agent run as what it did, not as a conversation

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

* refactor: name the agent run breakdown a trace

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

* fix(ai-agent): label the save-as-agent form fields per the guidelines

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

* fix(ai-agent): reuse the resource form's path and description fields

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

* fix(ai-agent): keep the action tags on a max-iterations failure

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

* fix(ai-agent): keep offline replay inert and the streamed answer to one turn

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

* fix(ai-agent): reset the streamed answer on providers that skip tool_call

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

* fix: carry the event type narrowing through the stream parser

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

* fix: survive a malformed message rather than take the viewer down

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

* refactor: coerce agent messages once at the parse boundary

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

* feat: show an agent run as one scroll ending in its output

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

* fix: keep every streamed turn instead of dropping the narration

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

* fix: share the chat divider and drop the unsafe run auto-scroll

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

* fix: give the labelled divider a border colour and the standard pretty icon

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

* fix: pad the agent run below its badges as well as above

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

* feat: follow a streaming run's pane without moving the page

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

* refactor: share the chat's stick-to-bottom mechanics with the agent run

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

* fix: keep the answer's citations and end a turn's reasoning with the turn

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

* refactor: read the agent stream through the windmill-chat sdk parser

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

* fix: show an agent run's thinking instead of falling back to raw json

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

* fix: stop a stream the fold cannot use from claiming the run pane

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

* fix: identify an agent run by more than the job id the replay withholds

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

* refactor: drop the tests and comment lines that were not earning their place

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

* fix: stop a streamed turn's text shifting when a tool call closes it

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 14:38:46 +02:00
Guilhem e2a91ca2b1 feat: flow chat job-backed detail, smooth streaming and answer chrome (#11186)
* feat: flow chat job-backed detail, smooth streaming and answer chrome

* chore: document setFlowPath, drop unused chatIdentity, tighten comments

* fix: read a nested agent tool's call from its own job

* fix: show only the model's arguments on a tool card read from its job

* feat: add attachments to the chat sdk message type

* refactor: read a flow chat message's files from the message, not its run

* fix: keep every job-backed answer when reads finish out of order

* refactor: build flow chat tool cards from the conversation row alone

* feat: keep the model picker's current choice when a flow chat retry replays its run

* fix: export ChatAttachment from the chat sdk entry point

* fix: drop a conversation list for the flow path before setFlowPath

* fix: return the current listing, not old-path rows, after setFlowPath

* fix: treat a listing stale on kind or path as one stale listing

* revert: drop setFlowPath and rebuild the chat when the flow path changes

* fix: refuse a flow chat retry once any turn ran while it read the run

* docs: say where a display message's createdAt comes from

* style: drop the rule between New chat and the conversation list
2026-09-18 11:31:00 +02:00
GuilhemandClaude Opus 5 c4c9677982 feat: attach files to a flow chat message (#11185)
* feat: attach files to a flow chat message

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

* fix: store chat uploads under windmill_uploads and withdraw refused sends cleanly

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

* fix: refuse extra files for a single-file input and keep attachments on retry

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

* fix: clean up partial upload batches, withdraw a stopped send once, free retry payloads

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

* fix: list a chat conversation only once its run starts and require files where the flow does

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

* feat: carry uploaded attachments on the pending chat user message

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

* fix: route rich-composer file paste through the attachment lanes and tighten comments

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

* fix: keep a stopped turn's files for retry and let an explicit media type win

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

* fix: refuse chat attachments sent without text and shorten comments

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

* fix: resolve the attachments input from real flow input reads, ignoring loop iteration

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

* refactor: read flow input references through one parser for the model and attachments controls

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

* fix: withdraw an attachment send stopped after its uploads answered

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

* fix: discard uploads when a send is stopped as its attachments are announced

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

* docs: keep uploadAttachments' doc comment on uploadAttachments

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

* refactor: never delete chat uploads from workspace storage

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

* test: pin that a failed upload aborts the rest of its batch

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 10:16:10 +02:00
GuilhemandClaude Opus 5 a571117f3f feat: store mcp tool call, result and reasoning on flow conversation rows (#11176)
* feat: store mcp tool call, result and reasoning on flow conversation rows

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

* fix: keep the failure reason and web search citations on tool rows

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

* fix: persist a structured answer as its own row and keep reasoning-only rows from closing a turn

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

* fix: keep a failed Windmill tool's error on its conversation row

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

* fix: let a structured answer row claim its streamed thinking

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

* fix: scope a structured answer's claim to its own turn and document the row fields as stored

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

* fix: claim a textless assistant row only within the newest turn

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

* refactor: store the thinking that led to a tool call on the tool row

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

* fix: keep tool calls in stream order and scope a structured answer to its turn

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

* feat: store the files a user message carried as object-storage references on its row

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

* feat(chat-sdk): read a message's attachments and build their download URL

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

* feat(chat-sdk): carry a loaded user message's attachments in AI SDK metadata

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

* feat: store the model's call and what it got back on every tool row

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

* fix: pass no extras in the orphaned-conversation test's message insert

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

* fix(chat-sdk): keep a stored JSON null tool result instead of the row text

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

* fix: label a tool call the turn finished without as not finished

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

* fix: keep a structured answer's streamed call out of the did-not-finish label

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

* fix: write a turn's conversation rows in order and describe stored tool calls

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

* fix: await the image answer row like the agent loop's other rows

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

* fix(chat-sdk): place a row nothing streamed by its sequence, not at the end

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

* fix(chat-sdk): place only tool rows by sequence, keep a closing row last

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

* fix(flow chat): show a failed tool's error, and no Retry on a running or stopped turn

A failed tool card showed the row label in place of the error the row now
stores. The live tool result now reports failures, so a failed call briefly
flagged a running turn as failed; a stopped turn, whose last row is the
failed tool or the cancelled flow's failure, offered Retry too.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 15:35:18 +02:00
hugocasaandClaude Opus 5 c297ed0052 feat: managed memory with an inherited or custom memory id per step (#11118)
* feat: split ai agent memory into agent policy, run memory id and step history

* fix: scope string memory ids to workspace and flow, keep nested tool history inputs

* chore: update sqlx cache for the flow context query

* docs: describe memory id scoping as collision-free rather than isolated

* chore: regenerate openflow json after merging main

* fix: offer no memory id for legacy manual memory, document linked history inputs

* fix: seed provided messages from legacy manual memory and hide its note once set

* fix: bypass memory when a provided messages expression evaluates to null

* fix: require a user message when provided messages are empty

* chore: keep the empty messages comment within the line width

* docs: name the history inputs wherever linked steps list their flow-local inputs

* docs: keep the memory storage path on one line

* feat: managed memory with an inherited or custom memory id per step

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

* fix: list a custom memory id in the test run form and name where an inherited one comes from

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

* fix: keep memory id out of the add-field menu and drop the memory id telemetry

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

* fix: keep legacy auto memory without an id working after an untouched redeploy

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

* fix: rename step messages to previous_messages and address review

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

* style: rewrap comments and docs lines lengthened by the previous_messages rename

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

* refactor: read agent memory as either a legacy shape or the current one

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

* fix: name the memory setting in ignored-input notes and keep conversions honest

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

* fix: keep a legacy memory count unset on open and read a cleared count as off

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

* fix: address review on cleared test history and zero-count memory

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

* fix: drop flow-local keys from a linked agent resource before interpolating it

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

* fix: keep a linked resource's own inputs as fallbacks and note ignored history on image runs

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

* fix: restore the linked agent draft tests and log ignored history on every image run

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

* fix: resolve the one-of variant from the value when the selected one leaves the list

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

* fix: treat zero-count managed memory as off when enabling chat mode and shorten comments

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

* fix: stop requiring user_message in the openflow agent contract when previous messages are the prompt

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 11:55:43 +02:00
GuilhemandClaude Fable 5.1 4eab995cf7 feat: tell test flow conversations from deployed ones and rename a chat (#11179)
* feat: mark test flow conversations apart from deployed ones and allow renaming a chat

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

* fix: keep the conversation kind across refreshes and reject NUL titles

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

* fix: ignore conversation lists for a kind no longer selected

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

* fix: start a fresh conversation listing when the kind changes on a later page

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

* fix: refuse sending into a conversation of the other kind

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

* fix: refuse cross-kind conversation continuations on the server

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

* fix: keep the conversation filter unavailable while an answer runs

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 11:41:49 +02:00
GuilhemandClaude Fable 5.1 189793c2e4 feat: flow chat model picker on a shared model-settings component (#11187)
* refactor: render the session chat model menu from a shared ChatModelSettings config

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

* feat: pick the flow chat's model and thinking from the provider fields the flow exposes

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

* fix: name only the thinking level the flow run will send on the model button

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

* fix: let the flow chat take a typed model id and keep a shared thinking input editable

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

* fix: promote a flow input to the model button only where its control can edit it

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

* fix: drop any reasoning token the chosen model rejects before a flow chat run

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 09:59:05 +02:00
GuilhemandClaude Fable 5.1 68f2248018 feat: keep flow inputs and seed the agent when chat mode is enabled (#11177)
* feat: keep flow inputs and seed the agent when chat mode is enabled

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

* fix: leave a linked agent's memory and streaming to the agent when enabling chat mode

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 09:29:05 +02:00
GuilhemandClaude Fable 5.1 a9ec0aec3a feat: render the flow chat through the shared session chat components (#11175)
* feat: render the flow chat through the shared session chat components

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

* fix: settle the flow chat queue on the turn's outcome and guard IME enter

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

* fix: keep copilot free-tier banners and deploy-locked sends out of 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-16 17:45:01 +02:00
GuilhemandClaude Fable 5.1 3d08197182 feat: badge chat-input flows on the home list (#11164)
* feat: badge chat-input flows on the home list

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

* fix: keep a malformed draft value from aborting the runnables list

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

* fix: only a JSON boolean marks a draft flow as chat-enabled

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 14:46:49 +02:00
hugocasa 57a99f66a8 feat: rename saved agents from the agent editor and flag broken links (#11147)
* feat: list flows that link a saved agent and flag broken agent links

* feat: rename saved agents from the agent editor and repoint the flow

* fix: show an unreadable linked agent as not accessible, not missing

* fix: address review nits on agent rename and missing-agent state

* fix: open content search above modals and keep Escape for it

* fix: register content search on the opener's overlay stack

* docs: scope the global search z-index comment to the bases it clears

* refactor: show linked agents' rename warning as for scripts and flows

* fix: keep the failed-lookup rename warning to resources
2026-09-16 10:45:01 +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
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
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
63cb46d7bb feat: add a minimal skin for the approval page and slack/teams (#11061)
* feat: add an approval skin to the approval page and slack/teams messages

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

* chore: point ee-repo-ref at the teams approval skin commit

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

* fix: resolve the approval skin from the step awaiting approval

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

* fix: shorten the slack approval message to fit the button value limit

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

* fix: rename skins to detailed/minimal and keep long slack messages

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

* feat: title the minimal approval page from the step and flow summaries

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

* feat: let wait_for_approval set the description approvers see

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

* fix: keep a finished workflow's approval description, still gated

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

* fix: keep a login-required approval locked after the run moves on

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

* feat: hide the windmill version on the approval page

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

* chore: update ee-repo-ref to e92abc9d1fba3ba898640df0cfeb0af8a50849b4

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

Previous ee-repo-ref: bf1766ff49458f62d3f11746f1a06893ae3c2325

New ee-repo-ref: e92abc9d1fba3ba898640df0cfeb0af8a50849b4

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-09-10 18:50:09 +00:00
GuilhemandClaude Opus 5 e63072c216 fix(frontend): restore heading sizes in note markdown and keep group notes on id change (#11047)
* fix(frontend): restore heading sizes in note markdown and keep group notes on id change

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

* fix(frontend): scope the note cleanup deferral per note

Address local review nits: the header comment overstated the heading ramp (h3
sits at body size in the xs and sm scales), and the mid-update deferral bailed
out of cleanup for every group note rather than the one holding the unrendered
module.

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

* perf(frontend): traverse modules without collecting a discarded array

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

* test(frontend): show the three markdown prose presets in the kitchen sink

The Markdown tab rendered only the default preset, so a change to the shared
heading scale could not be compared across the surfaces that use it. The chat
sample gains headings for the same reason: it is the only place the assistant
bubble renders at panel width without an AI provider.

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

* refactor(frontend): validate group notes against the flow, not the render

A group note's members are module ids, so the flow's own module list decides
whether one still exists. Validating against the rendered nodes instead needed
a special case for collapsed groups, and still dropped a live module in the
pass after its id changed. Both cases are the same mistake, and checking the
source of truth removes them together with the collapsedModuleIds parameter.

Path completion keeps working off the rendered graph, since it needs the edges,
and now skips a note whose members it cannot all see rather than dropping them.

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

* fix(frontend): name a new note "Note", not after its internal type

The prefill lands inside the user's own note, and "Free note" / "Group note"
are the serialized type, not words anyone says about a note they just drew.
The menus that create them already say "Add note".

The heading goes to h2 as well: h3 computes to the body size in the note scale,
so the prefill's own title did not read as one.

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

* docs(frontend): describe what collapsedModuleIds is still for

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 16:51:55 +00:00
fd35b47658 feat: create the cloud workspace in onboarding, and teach the empty home (#10959)
* [ee] feat: create a personal workspace on cloud signup instead of the demo invite

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

* feat(frontend): land cloud users in their workspace after onboarding

Cloud signup creates exactly one workspace for the new user, so the picker
that followed onboarding was a page with a single choice on it. Switch to
that workspace and go to the home page instead, falling back to the picker
whenever there is a real choice: an invite to accept, several workspaces,
or none.

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

* chore(frontend): remove the tutorial system

Deletes the guided-tour feature: the tutorials directory, the per-editor
wrappers, the home banner and button, the /tutorials route, and the
driver.js dependency they were built on. Also removes what only existed to
serve them — the `tutorialsToDo` / `skippedAll` / `isCurrentlyInTutorial`
stores, the `disableTutorials` prop chain through the flow editor, the
`?tutorial=` deep links, PopupV2's clickOutside exemption for the driver
popover, and the selector-anchor class on the flow editor tabs.

The backend `tutorial_progress` endpoints and table stay: nothing calls
them now, and removing them is a public-API break plus a migration that
would drop existing progress.

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

* feat(frontend): suggest Hub projects on an empty workspace home

A workspace with nothing in it showed only "Welcome to Windmill". Replace
that with a grid of ready-made Hub projects to import, and hide the search
box, kind toggles and the sort/filter row while the workspace is empty —
they would act on an empty list. A search that matches nothing still keeps
its controls and shows the no-match message.

The project list is seeded locally for now; the Hub endpoint that ranks
them is not there yet, and Import is still a placeholder.

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

* feat(frontend): make a new cloud workspace the thing onboarding produces

Signup already makes a personal workspace; nothing let its owner name it, and a
user who ended up without one landed on a workspace picker whose only action was
a button. Onboarding now ends on the workspace itself, and the empty home that
follows says what a workspace is for rather than "Welcome to Windmill".

Onboarding gains a third step that names the workspace signup created, prefilled
from the login provider's name or the email local part — `ruben@…` gives "Ruben's
workspace". Skipping the survey reaches it too: the questions are ours, the
workspace is theirs. Advanced settings swaps in the real creation form for
someone setting up for a team.

The workspace picker stands down when it has nothing to offer: no workspace to
enter and no invite to accept leaves one action on the page, so the page is that
action — one field, prefilled, "Create workspace". Both hand-overs hold a loading
state for 900ms and the app fades in behind them, so creating a workspace reads
as something that happened.

The empty home draws three static placeholder rows in the shape of real ones,
under a caption offering a template or the New menu. "Start from a template"
opens a popover listing the hub's projects, most-starred first, preloaded when
the empty state renders and paged as you scroll. Picking one opens the import
wizard in a dialog: its two destination steps are already answered by being in a
workspace, so it starts at the import itself and pages to the credentials step
with the animation the paged-modal pattern provides.

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

* fix(frontend): gate the empty state and repair the derived workspace id

Review findings from the first round.

The empty state offers a template import and the New menu, and neither checks a
permission — so an operator, or a workspace whose direct-deploy protection
cleared `showEditButtons`, was offered both. It now sits behind the same gate as
the create menu thirty lines above it.

`validateWorkspaceId` answers with the *reason* an id is unusable, so
`if (validateWorkspaceId(next)) break` stopped on the first invalid candidate and
returned it: someone named Global got the reserved `global`, and a 50-char seed
got a taken one. Invalid candidates are skipped instead, and when none works the
caller opens advanced settings rather than posting a name the server refuses.

Also: `rd` may be absolute (the CLI login sends one) and `goto` refuses those,
which would strand the caller on the "Creating …" screen with the workspace
already made; the hub host is parsed defensively, since the instance setting is
whatever an admin typed and `new URL` was throwing in render; `insert_workspace`
says which authorization its callers still own; and the two arrival animations'
comments now describe when they actually play.

Tests for the two pure helpers the review named: `defaultWorkspaceName` and
`hubProjectDescription`.

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

* fix(frontend): use unifiedSize on the new onboarding step's buttons

`size` and `color` are deprecated on Button; the new step copied them from the
survey steps above it. AGENTS.md: deprecated props survive at old call sites,
copying one forward is still a bug.

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

* fix(frontend): let Skip wait for the workspace onboarding names

`loadWorkspaceStep()` was fired and dropped, while Skip and the use-case Continue
branch on `ownWorkspace` in their `finally`. Skip awaits one POST that starts
after those two GETs and can finish before them, so a first-frame Skip fell
through to `leaveOnboarding()` and landed in the workspace with the backend's
name — the step this flow exists for, silently gone. Both exits await the load;
`isSubmitting` already covers the wait.

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

* refactor: create the workspace in onboarding rather than at signup

Signup no longer makes a personal workspace, so the last onboarding step creates
one instead of renaming it — the same one-field `SimpleCreateWorkspace` the
workspace picker falls back to, so a user who leaves onboarding early meets the
form again rather than something new. The id now comes from the name they type
rather than from their email, and there is one creation path instead of two.

`insert_workspace` goes back to being private: the extraction existed only so the
EE signup path could call it, and nothing outside `create_workspace` does now.

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

* fix(frontend): count a pending invite as somewhere to go

Onboarding read `listUserWorkspaces`, which returns membership. An invite is a
`workspace_invite` row until `accept_invite` runs, so an invited teammate reached
the last step owning nothing and was walked into creating a personal workspace,
with the invite nowhere on the page. Invites are fetched alongside the
workspaces, the way the picker already gates the same decision.

A failed load now reads as placed rather than not: the picker can work the
decision out, while the create step's only way forward is creating.

The create form reports when it is handing over, so the Previous button beside
it stands down for the ~900ms rather than offering a way back out of a workspace
that now exists.

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

* fix(frontend): open the template picker downward and size it from the popover

The popover's default positioning caps its height to the viewport, and the list
inside it carried a fixed one, so a capped box overflowed its own frame — visible
with the AI composer hidden, where the caption sits high and `placement: top`
left almost no room above it. It opens downward now, with flip fallbacks, at a
definite `min(72vh, 520px)`; the list fills what the header leaves, which is
still the definite height it needs to page.

`creating` on the create form becomes `onCreatingChange`: `$bindable(default)` on
an optional prop is banned, and this is something the form reports rather than
state it shares.

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

* refactor(frontend): drop the InfiniteList containerClass prop

Added for the template picker, which turns out not to need it: DataTable's own
container is already `h-full`, so `containerClass="h-full"` merged to nothing and
the height the list pages against comes from the flex chain above it. A prop with
no effect at its only call site is public surface for free. InfiniteList is back
to what it was.

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

* fix(frontend): stop a dismissed project import from running on

Modal reports dismissal only through its bindable `open` — the X, Escape
and the backdrop dispatch neither `confirmed` nor `canceled`. Bind it, so
clearing `pick` follows the dialog closing: re-picking the same project
opens it again, and a run still in flight is abandoned with a toast
instead of writing to the workspace with no UI in front of it.

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

* fix(frontend): stop a dismissed project import from running on

Modal reports dismissal only through its bindable `open` — the X, Escape
and the backdrop dispatch neither `confirmed` nor `canceled`. Bind it, so
clearing `pick` follows the dialog closing: re-picking the same project
opens it again, and a run still in flight is abandoned with a toast
instead of writing to the workspace with no UI in front of it.

Also mark the inline-link buttons as sanctioned rather than oversights,
and give Log out `text-accent` instead of `text-blue-500`.

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

* fix(frontend): keep the stopped-import toast off the Finish path

`done` survives a retry, so Finish is clickable while the run is going
again, and its own closing reaches the same falling edge the X does.
Abandon the run either way; say it was stopped only when that is what
the click asked for.

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

* feat(frontend): reach the hub importer from the create menu, and count the funnel

The picker only existed inside the empty state, which disappears as soon
as a workspace holds one item — nothing else in the product linked to
`/projects/import`. New → Import now offers a hub project, opening the
catalogue in a dialog: a popover anchored to an item inside an open
dropdown leaves two melt layers arguing over focus. The list and the
import dialog move up to ItemsList, so one dialog serves both doors.

`template_setup` records how the credentials step ended — `filled` only
when nothing was outstanding, `skipped` carrying how many rows were left
— and `template_abandon` records where a dismissed import was given up.
`template_picker_open` gains a key naming the entry point.

Also on the workspace picker: logging out is a text link on the line
that says who you are and an item in the settings menu, rather than the
page's accent action, and onboarding's Previous joins the row it belongs
to instead of hanging under the button that finishes the form.

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

* fix(frontend): make the import counters answer what they claim to

`template_abandon` folded a landed import closed with the X into `idle`,
the bucket read as "opened this and bounced" — three outcomes in one
number. It gets its own `done` stage.

`template_setup` counted skipped rows through `value`, which is an
increment: `skipped` accumulated rows while `filled` and `none` counted
imports, two units in one counter with no way to recover one from the
other. The row count becomes a bucket in the key, so every event is one
import and the buckets compare.

The import dialog also asked for the hub URL settings at init, and the
home list now mounts it for everyone on every arrival — two GETs for a
string only the project card renders. Deferred to the first pick.

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

* fix: sanitize the inferred username, and stop counting an unread setup step as clean

`loadUsernamePolicy` derived a username by stripping dots, so `O'Connor`
and `alice+demo` both produced values the `proper_username` constraint
refuses — posted invisibly by the simple form, which then failed with
nothing on screen explaining why. `usernameFromName` keeps only `[\w-]`
and answers undefined when nothing usable is left, which is already the
form's cue to open the full one.

The credentials step offers Finish when the export could not be read,
since it cannot tell what is outstanding — and that landed in
`template_setup` as `filled`, the bucket meaning the step came out
clean. It reports whether it checked anything, and an unread step counts
as `unchecked`.

Also drops an orphaned `.sqlx` entry left by the create-at-signup query
this branch abandoned, and rewrites the stepper's first-frame comment,
which argued from a meaning of `resourceCount` that main has narrowed.

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

* fix(frontend): cap the inferred username at what the column holds

`usr.username` is VARCHAR(50) while the provider name and email it is
derived from run to 255, and `create_workspace` inserts the value
untruncated — so a long first name failed the same way the invalid
characters did: posted invisibly, refused on insert, with nothing on
screen naming the field. Undefined instead, which the form already
routes to the full one.

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

* fix(frontend): show the import note where it applies, and drop onCreated's unused id

The note is about landing on top of what a workspace already holds, so
it belongs wherever the destination is an existing workspace. The route
already read it that way; the dialog, which always imports into the
current workspace, was hiding it. It costs one collapsed row.

`onCreated` was typed as taking the new workspace id, and the advanced
branch passed `''` because `CreateWorkspaceInner` does not report one.
No caller reads it — the form has already switched to the workspace by
then — so the argument goes rather than the lie staying.

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

* fix(frontend): keep the empty-state toolbar reachable, and name the view counters

"Empty" here means the default listing found nothing, and a workspace
whose items are all archived looks exactly the same. The searchbar
carries "Only archived", so taking it off the pointer left those items
unreachable without hand-writing a query URL. Dimmed still, never
`inert`.

The disclosure named the counters that fire on a creation or an import
and not the three that fire on merely seeing the empty home or opening
either picker.

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

* fix(frontend): respect disable_hub in both hub-project entry points

An instance with the hub turned off still got the catalogue preloaded on
every empty home and an "Import a hub project" entry in the create menu
— an outbound request the operator has said not to make, and a door to
somewhere unreachable. Both now observe `disableHubStore`, the store the
script and flow hub pickers already read. With the hub off the caption
reads "Create a new one." rather than continuing a sentence whose first
half is gone.

The telemetry disclosure also scoped the create menu and picker counters
to the empty home, when both fire from the toolbar in a populated one,
and said a creation was recorded when what is recorded is the menu
opening.

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

* refactor(frontend): drop the catalogue preload rather than gate it twice

Warming the hub catalogue when the empty state rendered bought the time
between the caption appearing and someone clicking it, and cost two
defects: the request fired on instances with the hub turned off, and the
gate added for that raced `disable_hub`'s own load, which starts false
and stays false if the settings request fails.

The picker fetches on open instead. Measured: nothing before the click,
one request after it, 411ms to a filled list. `disableHubStore` still
hides the link and the menu entry, which cost no request and correct
themselves if the setting lands late.

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

* docs: count the home actions, and state the lazy-fetch constraint without its history

The telemetry doc's tally is maintained by hand and main had just moved
it; this PR adds a feature, so it reads 48 across eighteen with `home`
in the list — verified against the pinned EE ref rather than counted by
eye.

The empty state's comment narrated a preload that no longer exists and
the defects it caused. What a future reader needs is the constraint:
`disable_hub` loads asynchronously, so a fetch from here goes out before
the setting forbidding it is known.

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

* fix(frontend): stop the prefill overwriting a typed name, and gate submit on the policy

`load()` assigned the suggested name unconditionally, so a name typed
while its two requests were in flight was replaced a moment later. It
now yields to anything already typed.

Nothing may be submitted before the username policy lands either:
`automateUsername` starts at the common case, and posting that guess to
an instance that derives no usernames sends none where one is required.
`policyLoaded` gates both the button and `create()`, and is set in a
`finally` so a failed load leaves the form usable rather than wedged.

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

* fix(frontend): settle the username policy on failure instead of guessing it

`policyLoaded` was set in a `finally`, so a failed policy load unblocked
the form with `automateUsername` still at its default — the exact submit
the flag exists to prevent. The failure now hands over to the full form,
which asks for a username outright rather than inferring one, so the
flag is never true while the answer is still a guess.

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

* fix(frontend): answer the username policy instead of rejecting it

Three rounds of this bug moved between call sites because the shared
loader rejects when it cannot read `automate_username_creation`, leaving
each caller to guess — and both guessed "automated", which hides the
username field and posts none to an instance that derives none.

`loadUsernamePolicy` now answers "ask for one" in that case, so
`SimpleCreateWorkspace` and `CreateWorkspaceInner` both render a field
someone can type into rather than submitting a guess. An instance that
does automate ignores a username it was sent, so asking is safe either
way.

The prefill and the policy are settled apart now too: a failed
`globalWhoami` costs the suggested name and nothing else.

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

* fix(frontend): block creation when the username policy is unknown

There is no safe default. `create_workspace` refuses a username on an
instance that automates them and requires one on an instance that does
not (`workspaces.rs:5820`), so a client that cannot read the setting has
two request shapes available and the server rejects both. Last round's
"ask for one" was as wrong as the "automated" guess it replaced.

So the loader reports the failure instead of inventing an answer, and
the form says so: Create stays disabled, with a line explaining why and
a link to try again. Verified in the browser both ways — unreadable
policy disables Create and shows the message, a healthy load prefills
the name and enables it.

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

* fix(frontend): close the advanced-settings bypass while the policy is unknown

Create was gated on knowing whether the instance derives usernames, and
the link beside it went to a form with no such gate — so the way around
the block sat next to it. It is disabled until the policy is known, with
a title saying why.

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

* fix(frontend): reload the list on dismissal, and never re-offer a workspace that exists

Closing a landed import with the X left the home list stale: only
`finish()` reloaded it, so a workspace that now holds a project kept
showing its placeholder rows. A run that started wrote items whether it
finished, was abandoned or failed partway, so any dismissal after one
reloads.

Creation reported failure for a failed *list refresh* too, and handed
the form back — where a retry picks the next free id and creates a
second workspace. Once `createWorkspace` returns, nothing may report
failure: the refresh is logged if it fails, and the hand-over proceeds,
since the workspace is real either way.

The disabled-link tooltip also claimed the settings could not be read
during the ordinary load, before anything had failed.

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

* fix(frontend): reload after the abandoned run stops, not when it is asked to

`abandon()` stops the run at the next phase boundary; the request already
sent still lands. Reloading the list at that moment could read it before
that write committed, leaving the caller stale again — the thing the
reload was added to fix. It now waits for `running` to clear, which is
immediate for the common case of dismissing a finished import, with a
cap so a run that never settles still ends in a reload.

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

* fix(frontend): give the list reload one owner, taken by both exits

Finish reloaded immediately while dismissal waited for the run to stop,
so Finish pressed during a retry — `done` survives one, which is what
makes the button clickable then — read the list mid-write, and its
`finishing` flag stopped the deferred reload from correcting it.

Both exits now go through the same wait. One reload per closing, always
after the writing stops, whichever way the dialog was left.

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

* feat(frontend): give ImportExecution a whenIdle(), and await it instead of polling

The reload waited on a 250ms poll of `running` with a 15s cap, because
the modal receives the execution after `run()` was already called and so
holds no promise to await. The cap was its own hole: a write slower than
15s reloaded early, and nothing followed.

`run()` now keeps the in-flight promise and `whenIdle()` hands it out —
resolved when nothing is being written, immediate when no run is in
flight. The modal awaits that: no poll, no cap, no window.

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

* fix(frontend): back the settlement reload with a bound, and keep reporting run failures

`installProject` writes serially and takes no signal, so a request left
pending after earlier items committed leaves those invisible until the
next page load — `whenIdle()` alone never resolves for it. A bound now
reloads once in that case, *without* replacing the settlement reload:
replacing it was the flaw in the timeout this grew out of, so a hung run
reloads on the bound and again if it ever finishes.

`whenIdle()`'s rejection handler also swallowed the only report an
unexpected throw had — `#runInternal` has no catch of its own, and a
throw outside its inner ones leaves a stalled run with nothing on
screen. It logs now instead of discarding.

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

* feat(frontend): say when a workspace holds only archived items

A workspace whose items are all archived read as empty, because the
placeholder is decided by the default listing. Reaching those items then
depended on the toolbar, which is why it had been left interactive while
dimmed — and that let a kind toggle replace the invitation with "no
items found" on a workspace that really was empty.

The state is named instead. When the default listing comes back empty,
one request asks whether anything archived exists, and the placeholder
says which of the two it is: "Everything in this workspace is archived"
with a link to show them, or the ordinary invitation. Held until that
answer lands rather than drawn and swapped, since the wrong one claims
the workspace is empty when it is not.

The toolbar is dimmed and `inert` again, its original design: the
archived case now carries its own way in.

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

* fix(frontend): make the archived route independent of write permissions

Three ways the archived-only placeholder failed to deliver what it
promised:

Reading archived items is not a write, but the notice offering them sat
behind the create-permission gate — so an operator, or a workspace whose
direct-deploy protection cleared `showEditButtons`, got "no items found"
over items it could see and a toolbar now inert. The gate governs the
create actions alone; the notice is shown to whoever the probe found
something for.

The probe answered once per workspace and was never invalidated, so
archiving the last item left a cached "nothing archived" claiming the
workspace was empty until a page load. `reloadItemsAndCounts` clears it.

And it omitted `includeWithoutMain`, which the backend reads as
excluding library scripts — a workspace holding only archived ones
answered "empty". Always true here: hiding library scripts puts a filter
in `activeFilters`, which `workspaceEmpty` requires to be empty.

`whenIdle()` gains the two tests its contract deserves, since the reload
correctness three rounds argued over rests on it.

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

* fix: harden the hub proxy, the workspace picker's gating and the rd hand-off

Findings from four local review passes over the branch:

- `list_projects` refuses when `disable_hub` is set, and `is_public_hub` now
  compares the parsed host, so no spelling of the public hub (mixed-case scheme
  or host, port, trailing dot, userinfo) forwards a member's bearer token there.
  Covered by a unit test table.
- The workspace picker waits on `usersWorkspaceStore` as well as `workspaces`,
  which derives to `[]` while the store is unloaded; with the create-form latch,
  one such frame swapped a member's picker for the create form until reload.
- `refreshSuperadmin` takes `force`, and the picker uses it: a `false` left over
  from a logged-out load decides whether the page is a picker or a create form.
  A cancelled call no longer publishes `false` over the live request's answer,
  and only its own request's handle is cleared.
- `rd` is sanitized once where it is derived rather than at each of the four
  hand-offs, so an absolute target keeps the OAuth callback's allowance and
  `https://evil.example/` is dropped.
- The archived-items probe answers "unknown" on failure, which keeps the
  ordinary caption and leaves the toolbar reachable.

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

* fix: answer the round-33 nits on the hub route and the empty state

- `empty_state_view` is no longer logged for an archived-only workspace, which
  is not the state the counter measures.
- `detail`'s fetcher keeps its last answer in a local instead of reading
  `detail.current`, a self-reference that typed the resource `any`.
- `list_projects`' comment, including its authorization contract, is back on the
  handler rather than on the predicate inserted above it.
- `listHubProjects` documents the 400 an instance with the hub disabled returns.

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

* feat(frontend): keep the operator onboarding tour

Operators cannot create anything, so the home page is the whole product to
them and its three tabs are worth naming. The tour that did that is the one
piece of the removed tutorial system that still has an audience.

Restored trimmed: driver.js, the driver wrapper and its controls, the
`.driver-popover` styling, and a module for the progress bit. The catalogue
machinery it used to sit in — the config, the role gating, the router, the
banner and the tutorials page — stays deleted, so the five steps are reached
directly instead of through a registry of one.

It runs on an operator's first home page visit and is recorded as seen
however it ends, including navigating away; afterwards it is in the sidebar
menu under Take the tour, which is where the last step points. Progress uses
the surviving `tutorial_progress` route, slot 6, read-modify-written so the
slots of the removed tutorials keep their state.

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

* fix(frontend): dim the home toolbar only where the placeholder replaces it

Standing the toolbar down depends on something else offering a way onwards.
An operator in a workspace that is simply empty gets no placeholder — they
cannot create, and there is nothing archived to reach — so the search and the
kind toggles were the only controls on the page, dimmed to 40% and `inert`.

They now follow the placeholder rather than emptiness, which also stops the
operator tour spending three of its five steps highlighting controls this
page had greyed out.

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

* docs(frontend): put the inline-link rationale on the first link, not the third

The caption's three links share one reason for being bare `<button>`s, and it
was written on the last of them. A reader — or a reviewer — meets the archived
one first.

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

* fix(frontend): keep private hub project names out of telemetry

An instance pointed at its own hub imports its own projects, and the slug
naming one is the customer's content — `template_import` was recording it
verbatim, which the disclosure ("the name of any public hub project") does
not cover and `hub_script` already avoids by collapsing a private script to
`private`.

`hubProjectUsageKey` gives projects the same treatment, deciding by the
configured hub's host so a port, a scheme's case or a trailing slash cannot
turn a private hub into a public one.

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

* fix(frontend): treat an unread hub setting as private, not as the public hub

`hubBaseUrlStore` is seeded with the public hub and written in one place, by a
loader with no catch and no retry. A settings read that threw therefore left
the store naming hub.windmill.dev for the rest of the session, and the import
counter read that as permission to report a private instance's project slug —
the leak the previous commit closed, narrowed to "after one failed read".

The fact has three states and the store held two, so `hubBaseUrlKnown` carries
the third: the loader sets it only once the value is the instance's own, and
the telemetry key requires it. Links keep rendering the default meanwhile,
which is what they always did.

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

* fix(frontend): tag each hub detail with the slug that asked for it

`resource()` assigns whatever its fetcher returns, with no guard for a run that
has been superseded, so handing back the previously fetched project on a stale
response published that project. With two slow requests in flight — pick A,
leave B loading, pick C — B's answer put A's name, author and counts on the
card while the plan underneath still said C, and Import wrote C.

Each answer now carries its own slug and is read only while that slug is the
chosen one, which also drops the local the previous shape needed to keep the
resource's type from going circular.

The hub-telemetry tests reset their shared fixture per case; the private-hub
one had been passing on what the case above it left behind.

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

* fix(frontend): stop the template picker spinning on a hub with no projects

Opening the picker against a reachable hub that has published nothing pinned
the renderer at full CPU and froze the tab. The effect arming the list called
`setLoader` and `loadData`, which read `InfiniteList`'s reactive state as well
as writing it, so the effect depended on what its own load changed and re-ran
itself; a list that stays empty never settles that cycle. It now arms the
loader once per workspace, untracked, and leaves the load to `setLoader`.

The same empty list also claimed the hub was unreachable, since one `empty`
snippet serves both. The loader records which happened, so a hub with nothing
on it says so.

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

* fix(frontend): keep a superseded hub answer from erasing the chosen project

`resource()` publishes whatever its fetcher returns, superseded or not, and
`fetchHubProject` takes no abort signal — so an answer for a project the user
had moved on from replaced the published value, the slug guard rejected it,
and the chosen project's item counts went off the card for good with nothing
left to ask for them again.

The fetch now records its own answer, tagged with its slug and only while that
slug is still the chosen one, and the card reads that. Nothing reads the
resource, so it is a `watch` — the same machinery without the value that was
the problem.

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

* chore: update ee-repo-ref to 81edd1382d951265ab3e9b67fc7ca7967676fd56

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

Previous ee-repo-ref: 21ace847ec1c1406bafc50153004e1874642bf6c

New ee-repo-ref: 81edd1382d951265ab3e9b67fc7ca7967676fd56

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-09-09 10:22:06 +02:00
hugocasaandClaude Opus 5 7feaf619cf feat: run a linked AI agent's draft when testing a flow, and offer to deploy it (#10993)
* feat(frontend): run a linked agent's draft when testing a flow, and offer to deploy it

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

* fix(frontend): settle an agent's autosave before reading it, and refresh its card on a draft save

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

* fix(frontend): deploy the agent draft that was validated, and make the draft-tools flag explicit

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

* fix(frontend): refuse a stale agent deploy, and warn when a never-deployed agent is kept as a draft

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

* docs: record what inlining an agent draft puts in a preview job

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

* fix(frontend): name the draft-changes dialog after what it lists

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

* fix(frontend): refuse a draft deploy when the draft row is gone

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

* fix(frontend): apply the missing-draft refusal to raw apps too

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

* fix(frontend): stop reading a deployed resource row as a draft on deploy

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

* fix(frontend): do not mistake an outage or a vanished draft for a deploy

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

* fix(frontend): refuse an agent read whose pending draft save failed

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

* fix(frontend): space the trigger badges and right-align the agent actions

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

* fix(frontend): surface a failed agent-draft read instead of dropping it

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

* fix(frontend): give the agent draft delete a baseline so a newer edit survives

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

* fix(frontend): drop the agent draft cell locally instead of deleting twice

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

* test(frontend): pass the withDraft flag the guard tests were missing

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

* refactor(frontend): deploy agent drafts the way Review & Deploy does

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

* fix(frontend): give the read-only flow graph its own linked-tools bucket

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

* fix(frontend): base the resource draft delete on the read that promoted it

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

* fix(frontend): refresh every step linking an agent when its draft is saved

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

* fix(frontend): write nothing at all when a resource draft has gone

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

* test(frontend): pin that the resource draft delete follows its baseline seed

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 19:04:38 +02:00
hugocasaandClaude Opus 5 48a56158c1 feat: report resource type picks to the hub and rank pickers by popularity (#10982)
* feat: report resource type picks to the hub and rank pickers by popularity

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

* fix: scope the hub pick route as a write and keep an alphabetical floor

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

* fix: rank the types a workspace already uses above the hub's own picks

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

* fix: total local usage per integration, not per resource type name

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

* fix: remember a failed hub index read briefly instead of retrying every open

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 18:59:24 +02:00
130a2f7408 feat: instrument sandbox isolation, data tables and in-flow script edits (#10981)
* feat: instrument sandbox isolation, data tables and in-flow script edits

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

* fix: address review findings on the new telemetry counters

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

* refactor: inline single-site telemetry helpers and trim what is collected

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

* docs: tighten the telemetry disclosure copy

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

* chore: update ee-repo-ref to 5921c03c8e28642efd1c390f590c0dab9834fa99

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

Previous ee-repo-ref: 548b5e0421a04a2d9a76cce6efc6c91b1d8560ee

New ee-repo-ref: 5921c03c8e28642efd1c390f590c0dab9834fa99

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-09-05 10:38:20 +00:00
hugocasaandClaude Opus 5 f037c73d10 feat(frontend): group the agent form and edit saved agents as drafts (#10880)
* feat(frontend): group the AI agent step form and edit saved agents in a modal

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

* feat: edit a saved AI agent through its own resource draft

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

* refactor: drop the agent fork-for-edit session now that edits live in a draft

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

* feat: edit ai_agent resources from the resources page with the agent editor

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

* fix: send a standalone agent's brain from the module when testing a step

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

* fix: keep the agent draft faithful to the resource it deploys to

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

* chore: add the sqlx cache entry for the eval subject rename

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

* refactor: share the module insert between the graph and the agent editor

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

* feat(frontend): open evals inside the agent editor, actions in its header

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

* feat(frontend): add tools from the agent editor and lighten its test pane

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

* fix(frontend): open an ai_agent deep link in the agent editor

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

* fix(frontend): drop the failed result badge on a step that never ran

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

* feat(frontend): head the agent editor's levels with a back control

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

* feat(frontend): drop connect and fill inputs from the agent editor

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

* feat(frontend): lighten the agent editor's run panel

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

* fix(frontend): stop a nested agent tool's config reading as AI-filled

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

* feat(frontend): offer only AI or static on an agent tool's inputs

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

* feat(frontend): give a saved agent's tool editor a static-only surface

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

* feat(frontend): open an agent tool in a drawer beside the agent

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

* feat(frontend): hide unset agent config in the run form

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

* refactor(frontend): share the input forms' pickers and s3 lookup

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

* chore(frontend): drop a dead agent-editor export and fix two stale comments

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

* fix(frontend): reach an ai_agent's resource-level settings and copilot

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

* fix(frontend): open an ai_agent's resource view as JSON, not the generic form

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

* fix(frontend): address review findings on the agent editor's draft and streaming

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

* fix(frontend): close the agent editor on a version restore, as the resource editor does

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

* fix(frontend): stop the provider picker auto-writing a kind, and clear review nits

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

* refactor(frontend): drop the fork-for-edit leftovers from the agent card

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

* fix(frontend): mount the agent editor in the dev flow editor and guard the deep-link race

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

* fix(frontend): deploy the agent config that was submitted, and refuse one no run could use

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

* refactor(frontend): build the agent editor's rows from the design-system button

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

* fix(frontend): keep a draft-only agent's draft, and let a blank MCP summary deploy

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

* fix(frontend): guard read-only agents, incomplete MCP tools and duplicate editor mounts

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

* fix: read-only agent editor, linked-card refresh, atomic eval rename

* fix: eval rename needs the privileged pool, per-workspace write access

* fix(frontend): drop the agent editor target when its mount goes away

* refactor: drop the agent rename work from this PR, unban the bindable defaults

* fix(frontend): refuse a renaming deploy and drop the copilot from static-only fields

* fix(frontend): mirror the worker's streaming rule and scope agent writes to their target

* fix(frontend): read runtime streaming as off and reset the drawer's json view

* fix(frontend): read an unsettled output_type as non-streaming too

* fix(frontend): let the showing modal claim an agent opened from inside it

* fix(frontend): keep in-flight edits, tool replacements and every linked step in sync

* fix(frontend): keep attachments in the run form and bind the agent ref to its tools

* fix(frontend): preview the agent as authored and re-evaluate step args on run

* fix(frontend): scope agent-editor ownership to the flow's workspace

* fix(frontend): drop the tool drill-in where there is no graph to select on

* fix(frontend): require a provider kind and keep one resource editor open at a time

* fix(frontend): keep legacy nulls, static-only text literal, and the handover anchor

* test(worker): pin the agent streaming default

* fix(frontend): let an AI-fillable input be switched to static

* fix(frontend): report agent editor background failures instead of floating them

* fix(frontend): keep the version pane's path alive while the editor closes

* fix(frontend): clear the anchor-keep flag at the start of each drawer session

* fix(frontend): preview the agent without its synthetic path, refresh the baseline on external writes

* refactor(frontend): drop the unverifiable baseline refresh, state the synthetic-path rule

* fix(frontend): keep the synthetic path out of agent tool test runs too

* refactor(frontend): host the agent editor under the agent's own path

* fix(frontend): mark an agent editor's host explicitly instead of inferring it from the path

* fix(frontend): discard linked-agent responses from before a deploy

* fix(frontend): keep a flow mount from claiming an agent editor's nested target

* feat(frontend): keep an agent used as a tool inside the agent being edited

* fix(frontend): reserve the agent editor's root module id

* docs(frontend): record why the agent editor previews under the agent's path

* fix(frontend): refuse to open or deploy a resource that is not an agent

* docs(frontend): put the scope-migration comment on the function it describes

* fix(frontend): refuse an agent path whose resource type is not proven

* fix(frontend): recheck the resource type before deploying, and keep expressions off static-only inputs

* fix(frontend): lazy-load the agent editor and slide its levels like the evals pane

* refactor: drop unreachable non-list tools check from agent deploy

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

* fix(frontend): clear text-only agent fields on image output, reserve the root id

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

* fix(frontend): keep the agent editor usable for a non-list tools value

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

* fix(frontend): stop the parked eval run list from taking arrow keys

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

* fix(frontend): report a non-list tools value on deploy instead of throwing

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

* fix(frontend): keep temperature editable for image output

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

* fix(frontend): skip non-object tool entries when rendering an agent

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

* fix(frontend): guard tool entry reads instead of copying the tool array

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

* fix(frontend): key tool rows by position so duplicate ids render

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:32:17 +02:00
Ruben FiszelandClaude Fable 5.1 fdd3b36423 feat: workspace setting to hide the AI assistant, agent steps unaffected (#10941)
* feat: workspace setting to hide the AI assistant, agent steps unaffected

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

* fix: load workspace AI config on cold /sessions load and say hidden, not disabled

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

* fix: follow workspace switches on /sessions gate and drop deprecated button size

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

* fix: key the /sessions hidden-assistant gate on the acting workspace's own config

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

* fix: tag the /sessions hidden-assistant verdict with its workspace and drop superseded reads

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

* fix: overlay the /sessions hidden-assistant gate so warm sessions survive workspace switches

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

* fix: hide the pipeline insert menu AI prompt and refuse chat turns where the assistant is hidden

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

* fix: shrink the home Build with AI / CLI / Hub line to a flush hint row

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

* fix: frame the workspace toggle as hide AI sessions at the bottom of the AI settings

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 17:21:10 +02:00
Ruben FiszelandClaude Fable 5.1 95b6bbd46a fix: preselect first row of AI agent and AI sandbox insert panes (#10937)
* fix: preselect first row of AI agent and AI sandbox insert panes

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

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

* fix: keep Enter for focused controls in the AI insert panes

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

Claude-Session: https://claude.ai/code/session_019vjSnhnewkUbx6mR9iCeK8
2026-09-02 13:49:31 +02:00
0bbd559ac8 feat: instrument AI fill/fix, evals, agents and the debugger (#10853)
* feat: track AI fill, AI fix, evals, reusable agents and debugger usage

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

* chore: pin ee ref to the feature_usage registry commit

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

* chore: update ee-repo-ref to c3b6f62ea579a3583d4b474e9885c77104cfc87e

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

Previous ee-repo-ref: 77992910929188a854eadc06ee45971877b6f954

New ee-repo-ref: c3b6f62ea579a3583d4b474e9885c77104cfc87e

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>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-08-28 16:31:13 +02:00
hugocasaandClaude Opus 5 8f349c032a fix: nested template literals in step inputs, and unresolvable $args tags (#10856)
* fix(frontend): keep nested template literals intact in template inputs

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

* fix: fail a flow step with an unresolvable $args tag instead of hanging

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

* fix(frontend): surface input expression errors when running a step test

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

* fix(frontend): treat an escaped \${ as literal text when escaping backticks

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

* fix: accept the string "null" as a tag component, reject only JSON null

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

* fix: leave a same_worker step's inert tag alone, log an unresolved flow tag

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

* fix(frontend): escape every backtick when the template walk desynchronizes

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

* fix: leave a dedicated runnable's inert step tag alone

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

* fix: reroute a step only when its own tag is what failed to resolve

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

* refactor: name the inert-tag guard step_is_pulled_by_tag

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

* fix: reject a tag only when it interpolates to nothing at all

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

* fix(frontend): validate the template walk instead of trusting a balanced stack

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

* docs: describe what an unresolvable tag actually interpolates to

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

* refactor(frontend): decide template escaping with a real parser, not a hand-rolled scan

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

* refactor: name is_flow_step on push now that it is load-bearing

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

* fix(frontend): heal an expression escaped before nested templates were handled

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

* fix: reroute a step whose tag reads args that failed to evaluate

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

* fix: never hand a job that failed before running to a dedicated runner

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

* fix: reroute only a step whose args failed, leave other tags untouched

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

* fix: drop the post-preprocessor tag fallback, leaving tag resolution untouched

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

* refactor: leave interpolate_args exactly as it was

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

* test: use a generic example in the template literal tests

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

* fix(frontend): show an expression escaped by the old rule as it was authored

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

* fix(frontend): surface input expression errors from every step-run entry point

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

* docs: state what is_dedicated_worker actually reads

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

* fix(frontend): heal only text whose backticks were all escaped by the old rule

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

* fix(frontend): match the old rule textually so an authored backslash still heals

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

* fix(frontend): heal only expressions the old rule broke, never ones that parse

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 16:27:54 +02:00
AlexRV12andClaude Opus 5 6b73145e72 fix(frontend): follow the operating workspace in step input forms (#10834)
* fix(frontend): follow the operating workspace in step input forms

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

* fix(frontend): rethrow auth errors and wire remaining variable pickers

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

* refactor(frontend): use runed watch for picker workspace reloads

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 00:47:46 +02:00
AlexRV12andClaude Opus 5 93081e255f fix(frontend): mint string password secrets in the operating workspace (#10815)
* fix(frontend): mint password secrets in the operating workspace

A `password: true` string argument is rendered by PasswordArgInput, which
mints an ephemeral secret variable on the first keystroke and rebinds the
argument to `$var:<path>`. It minted into `$workspaceStore` — the globally
active navigation workspace.

Session editors operate on a different, possibly forked workspace without
switching `$workspaceStore`, and thread that operating workspace explicitly
as a `workspace` prop. When the two diverged the secret landed where the
user was merely looking while the job ran elsewhere, and the backend failed
with `Variable not found`.

Add the `workspace` prop to PasswordArgInput and thread it through every hop
between a form mount and the minting field, plus the entry points that supply
it. Track `mintedIn` so updates target where the variable actually lives, and
re-mint when the operating workspace moves after a path already exists.

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

* fix(frontend): keep a password field consistent with its argument

A parent can replace the whole args object without remounting this field —
previewing a saved input, say — leaving `path` and `password` describing a
secret the argument no longer points at. Minting from them then copies the
old plaintext over the replacement, and the replacement is lost.

State that rule once as `argReplaced` and gate every mint on it. The
replacement can also land while the create is in flight, so the bound value
is captured before the request and re-checked after it resolves; the variable
that mint produced was never referenced, so it is deleted outright. A mint
that ends without binding re-seeds `password` from what the argument now
holds, so the field stops displaying a secret that will not be submitted and
a later workspace move cannot re-mint the stale plaintext. `updateValue`
returns early before anything is minted, since its 404 retry would otherwise
bind over a replacement it cannot see.

A failed initial mint now raises a toast rather than passing silently, which
also removes the component's last unhandled rejection.

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

* test(frontend): guard workspace forwarding to PasswordArgInput

Every hop between the form a caller mounts and the PasswordArgInput that
mints the secret must forward `workspace`, and so must the entry points that
supply it. A hop that drops the prop falls back to the navigation workspace
while the top-level case keeps passing, and no typechecker catches it because
every hop declares `workspace?: string | undefined`.

The forwarded expression is checked rather than the prop's presence, so
`workspace={$workspaceStore}` and `workspace={undefined}` fail. Two ways the
scan could stop guarding without failing are asserted too: an unterminated
mount raises instead of swallowing the rest of the file, and the number of
mounts parsed must equal the number of tag occurrences, so a mount written
inline rather than at the start of a line fails loudly.

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

* fix(frontend): list and create variables in the operating workspace

* fix(frontend): surface and bound a failed recovery mint

* test(frontend): end a mount at the first line closing it

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 22:36:07 +02:00
hugocasaandClaude Fable 5 9c557859c5 feat: AI agent evals: datasets, scored runs and comparison (#10633)
* feat: eval datasets and standalone runs for reusable AI agents

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

* feat: agent eval drawer with case editor, runs and capture entry points

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

* docs: document AI agent eval datasets and standalone runs

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

* fix: say how many eval cases the list is not showing

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

* fix: address review findings on eval datasets

- keep an edited case's conversation and tool inputs: serde(flatten) silently
  drops Box<RawValue> fields, so the update payload is spelled out
- remount the case editor per case so one case's turns cannot leak into another
- require jobs:read / flow_conversations:read on the capture endpoints, which
  UserDB does not gate by token scope
- take the dataset lock in create and update so a delete cannot be undone by a
  concurrent metadata write, and delete cases before metadata
- load more cases beyond the first page, and stop capping the agent picker
- record that the version stamp is taken at enqueue, not at resolution

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

* fix: address round-2 review findings on eval datasets

- block operators from dataset and case writes
- pass the editor's operating workspace through the drawer and the capture
  request, instead of assuming the navigation workspace
- discard superseded case-list responses so switching datasets cannot land the
  previous dataset's cases
- reject a dataset without a case_id (or vice versa) rather than running an
  inline case under a dangling association
- run unsaved edits inline instead of silently running the stored case
- surface the API error body on a failed run
- fetch dataset metadata concurrently when listing
- $bindable() without a default on the optional open prop
- correct the permission and enqueue-time-version wording in the docs

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

* fix: run an untouched saved case by reference again

The editor writes back keys the stored case omits, so comparing the raw objects
reported every unedited case as edited: the run went inline and lost the
dataset/case stamp its history depends on. Compare a normalized form, and pin it
with a test. Also scope the history query to the drawer's workspace and drop
superseded responses.

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

* feat: show a dataset's cases as a table, and fix round-4 review findings

The case list showed one case at a time with no overview. It is now a table with
the case, where it was captured from, and its last run — the last-run column is a
single jobs query on the path stamp rather than a request per row.

Review fixes in the same file:
- keep the edit baseline on the selected case rather than looking it up in the
  loaded page, so a case beyond page 1 is not treated as unedited and run stale
- release the loading state when a superseded case load returns early
- reload every loaded page after a write instead of collapsing to page 1
- last remaining 'resolved to' wording in the version tooltip

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

* feat: run a dataset as an experiment, with scorers as runnables

An experiment runs every case of a dataset against one subject and records the
exact case set it executed, so a result set stays reproducible while the dataset
keeps changing.

Each case runs as its own small flow — the agent, then a step per scorer — so a
case keeps the run stamp, history query and trajectory view a single run already
has, and scorers need no orchestration of their own. Results are read back per
step by node id rather than by walking a nested loop's status.

A scorer is any runnable taking (input, output, expected): a script, a flow, or a
reusable agent used as a judge. A judge is prompted with the case and the answer
as one JSON message; a script or flow receives them as named arguments. Scores
accept a bare number, a boolean or {score}.

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

* feat: results table for an experiment, with scorer columns

One row per case: status, the agent's answer, and a column per scorer, with the
mean per scorer above the table and a link into each case's run for its
trajectory. Averages skip cases a scorer produced no number for — counting a
missing score as zero would read as a regression.

The drawer's left pane becomes Cases / Results, and Results carries the scorer
picker and Run dataset.

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

* feat: compare an experiment against a baseline

Per-scorer deltas on each row and on the mean, and a filter down to the rows that
regressed. Rows join by case id, so a case added after the baseline ran has no
delta instead of counting as a change.

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

* fix: address round-5 review findings on experiments

- match scorers by label when diffing two experiments; joining by array position
  subtracted one scorer from another whenever the scorer sets differed
- report a row's status from the case job, not the agent step, so a case whose
  scorer failed no longer reads as a success
- delete a dataset's experiments with it: they hold copies of its cases, and a
  recreated dataset of the same path would have exposed them
- select the experiment that Run dataset just started instead of leaving the
  table on the previous one
- expected is scored now, so stop describing it as having no consumer

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

* fix: address round-6 review findings on experiments

- hold the dataset lock across an experiment launch, so a delete landing between
  reading the cases and writing the experiment cannot recreate the deleted
  dataset's inputs
- match scorers between experiments on kind and path, not on label: labels
  default to a path's last segment, so f/a/quality and f/b/quality compared
  against each other
- average mean deltas over the cases both runs scored; comparing each run's own
  average reported a regression from a case the baseline never ran, with no
  regressed row to point at
- openapi: the row status is the job's, which is also canceled/skipped; runEval
  takes scorers; the update-case body no longer advertises source, which the
  handler deliberately ignores
- record why the experiment prefix cannot reach a sibling dataset

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

* fix: address round-7 review findings on experiments

- release the dataset lock for the push loop and retake it for the write,
  re-checking the dataset still exists: holding it across the whole launch made
  every capture and case edit on that dataset 409 until the last job queued
- assemble experiment results with bounded concurrency; a 100-case, 3-scorer
  experiment was 400 sequential lookups, each itself several queries
- clear the baseline when it becomes the selected experiment, which was
  comparing a run against itself and reporting zero deltas
- take the header mean over the same cases as its delta while comparing, so the
  two numbers beside each other describe the same set
- a canceled or skipped case is no longer the same grey dot as a running one

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

* fix: address round-8 review findings on experiments

- verify the dataset's identity, not just its existence, before recording an
  experiment: the path can be deleted and recreated during the push loop, and
  the experiment holds copies of the old dataset's cases
- give the recording lock a longer budget than a case edit, since its jobs are
  already queued and giving up strands them, and say so when it fails
- keep score lookups sequential within a case: nesting two bounded streams
  multiplied into 32 in-flight queries against a 50-connection pool
- clear a baseline that no longer belongs to the loaded experiments, so
  switching datasets does not leave comparison mode on with nothing to compare
- keep a scorer's own mean when the baseline never ran it, instead of blanking a
  column full of numbers
- EvalCaseDraft.expected no longer claims nothing scores it

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

* fix: do not trust an experiment's job ids, and require write to record one

Experiment objects live in workspace object storage, which a script can write
directly, and results are read on the unrestricted pool — so a forged experiment
naming another flow job returned output the jobs API would have refused. Only
jobs this server stamped with that experiment's id are read now.

Also from round 9:
- recording an experiment requires write on the dataset, not read: it persists
  into the dataset's namespace and its shared list
- clear the results table when the selection changes and surface a failed load,
  instead of labelling the previous experiment's numbers as the new one's
- a storage fault is no longer reported as a deleted dataset
- the lock-timeout message at the recording site no longer says to retry, which
  would run the whole dataset again on top of the jobs already queued
- ExperimentRow.status documents canceled and skipped

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

* fix: bind the experiment trust check to the requested dataset

The previous check matched jobs on the experiment id alone, which the stored
object supplies — so copying another dataset's experiment JSON under a readable
key carried its jobs' output along with it. A job is now only read if it was
stamped for this experiment *and* for the dataset the caller's read access was
checked against, and an experiment that names a different dataset is not served
from this key at all.

Also from round 10:
- add the .sqlx entry for that query; without it every SQLX_OFFLINE build failed
- serve results over GET: as POST the route-scope middleware classified a read
  as ai_evals:write, locking read-only tokens out of their own results
- clear the selected and baseline experiments synchronously when the dataset
  changes, so the previous dataset's id is not requested under the new one

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

* fix: address round-11 review findings on experiments and scorers

- give scorers the whole case input, not just the message: an answer that came
  from attachments or a replayed conversation could not be judged on it
- accept a judge's boolean and structured {score} answers, including stringified
  ones, and pin every documented scorer shape with a test
- record an experiment for the cases that did launch when a later push fails,
  instead of leaving those jobs running with nothing to attribute them to
- do not capture a preview parent's synthetic runnable_path as a host flow; the
  saved case could not be rerun
- clear the case table before loading a dataset and surface a failed load, so a
  failure cannot leave the previous dataset's cases under the new name
- keep the results table through a refresh of the same experiment
- exclude flow-step jobs from the per-case last-run lookup
- drop case sets from the experiment list, which is only used to pick a run
- report a database failure at the recording lock as itself, not as contention

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

* fix: address round-12 review findings on capture and run history

- load flow_node.flow for flownode parents: an agent inside a deployed branch or
  loop captured without its agent, host flow or tool bindings
- decide host_flow_path by whether the path resolves to a flow, not by job kind:
  excluding previews wholesale also dropped the flow editor's step test, whose
  path is real
- page the per-case last-run lookup by created_before until the loaded cases are
  covered; one page of 200 reported older cases as never run
- do not record an experiment when nothing launched
- only attach the case input to a job when a scorer will read it
- keep the case table through a save; only a different dataset clears it
- drop the superseded duplicate comment on the score parser

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

* fix: stop refetching run history on every case write

Reading the case list before the first await made the whole job-history query a
dependency of it, so every save, delete and Load more refetched up to 1000 job
rows and blanked the column. Read untracked instead.

- an empty Last run cell now distinguishes never-ran from not-found-within the
  page bound, which the comment already claimed and the cell did not
- reloading a dataset no longer replaces a populated table with a skeleton
- keep the score-parser comment that describes every shape it handles

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

* refactor: keep eval datasets in Postgres instead of object storage

Datasets, cases and experiments become rows (`eval_dataset`, `eval_case`,
`eval_experiment`, `eval_experiment_case`) rather than objects under a
`wmill_eval_datasets/` prefix. What a run produced is still the job's:
only case inputs and an experiment's case snapshot are stored.

This removes the machinery the object store needed:

- The advisory lock and the read-modify-write of a per-dataset JSONL. A
  case is a row, so there is nothing to serialize.
- The launch-time identity check on the dataset. The foreign key makes a
  concurrent delete fail the transaction instead.
- The trust guard on an experiment's job ids, which existed because a
  script can write workspace object storage directly and could forge an
  experiment naming somebody else's job.

An experiment now chooses every job id and records itself before pushing
anything, so a launch that dies partway leaves a recorded case whose job
is missing rather than a running job nothing accounts for; cases that
never reached the queue are removed again.

Row-level security on `eval_dataset` is the authority on who may read or
write a dataset, so `extra_perms` grants work and the rule is not
mirrored in Rust. Cases and experiments carry a read policy derived from
their dataset and no write policy: they are written on the unrestricted
pool after the dataset row itself has been asked, with
`SELECT ... FOR UPDATE`, whether the caller may write it.

Cases are capped at 256 KiB each and 10 000 per dataset, refused rather
than truncated. Attachments are S3 references, not inline bytes, so a
case that approaches either cap is a mistake rather than a use case.

Evals no longer need the `parquet` feature or a configured workspace
object storage.

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

* style: align the eval drawer with the design system

- Scorer chips are `Badge`s rather than a hand-rolled bordered span, and
  the section header is a `Label` with its tooltip, as are the case
  editor's fields (which also gets the label colour right).
- The results table showed status as a coloured bullet, which says
  nothing to a colour-blind reader. It now carries the same icons the
  runs table uses, with the status as its accessible name.
- Feedback colours move to the `-500` shades the brand guidelines name.
- The conversation JSON error uses `TextInput`'s `error` prop for the
  border and the caption style for the message, as elsewhere.

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

* feat: author an expected answer, tags and attachments on a case

Every scorer is handed `(input, output, expected)`, but nothing could
produce an `expected` except a conversation capture: the case editor had
no field for it and a captured run left it empty. So:

- The editor gains Expected, Tags and a read-only list of the
  attachments a captured case carries. Expected is plain text, or JSON
  when the answer has structure.
- Capturing from an AI agent run keeps what that run answered, which is
  the only moment a reference answer exists for free.

The results table also laid itself out by content, so a long answer
pushed the scores — the numbers the table exists for — off the edge of
the pane. It is fixed-layout now, with the text columns bounded.

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

* docs: expected is captured from a run and can be authored

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

* feat: link a saved agent when inserting an ai agent step

"AI Agent" in the step picker was a leaf that always created a blank
step, so reusing a saved agent meant inserting a blank one, opening its
step input and linking it there. It is a category now, like Flow and AI
Sandbox, listing the workspace's `ai_agent` resources next to a blank
option, filtered by the picker's own search.

A picked agent produces a step that is already linked rather than one
linked afterwards: `agent` set, no tools, and only the flow-local
`user_message`/`user_attachments` transforms. Seeding the brain keys
there would leave transforms a linked step never reads and that
`AgentResourceBar` strips on its next link change.

Each `on:new` forwarder rebuilds the insert detail field by field
instead of spreading it, so a new field is dropped unless the forwarder
names it. `agentPath` is typed on both `GraphEventHandlers.insert` and
`FlowGraphV2`'s `onInsert` so the next one to forget it fails the check.

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

* feat: restore the link on cancel and simplify the agent bar

Cancel on an agent edit forked the step into a standalone copy, which is
the opposite of what the word means and needed a paragraph under the
card to explain. It discards the edits and re-links the step now,
leaving the agent untouched; diverging from an agent is Unlink's job, on
the linked card. This flow's `tool_inputs` survive the round trip as
overrides, so Cancel no longer folds them into the tools the way Unlink
does.

Linking a step to a saved agent happens in the step picker at insert
time, so the bar's own resource picker is gone and "Save as agent" is
the one action left. Its `+` button was a trap besides: it opened the
generic resource form, where an agent would have to be written as raw
JSON.

The card itself was `surface-secondary`, the sections token, so in dark
mode it was darker than the pane and read as a sunken well rather than
an elevated card. It uses `surface-tertiary` as the brand table
prescribes, its tool chips are `Badge`s, and the editing card no longer
overflows the pane and clips its own buttons. The remaining tooltip
follows the inline `Label` convention rather than sitting in a flex row
whose gap stacked on the trigger's own margin.

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

* feat: rework the AI agent evals surface into one table

Evals become a single pane: a dataset of cases, one column per scorer, one
row per case, with the run being looked at chosen from the toolbar.

Runs are permanent. Running the whole dataset opens one; running a single
case records nothing at all — it is a job, and looking at what it did is
not a claim that it belongs in the history. Its result and its scores sit
over the row until they are saved as a run, which carries the cases that
were not rerun and the scoring jobs themselves, so the number that is
saved is the number that was looked at.

A scorer is a runnable: a judge agent or a script, created in one click and
edited in place. Scores carry a reason and per-assertion checks, shown on
hover with a rescore button.

What ran is always named. A run records the agent version, or — for a
configuration that is not deployed — a hash of it, so a table can say that
its numbers describe an agent that no longer exists: those rows dim and the
table offers to rerun. An agent's draft can be run directly instead of the
deployed value, and once those edits are deployed the runs that made them
are recognised as that version. A step with no agent of its own is
evaluable too, and saving it as an agent moves its history onto it.

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

* feat: keep an agent's in-progress edits on the agent

Editing a linked agent forks it into the step, which is what makes the
edits runnable there — but the agent is what is being edited, so that is
where the unsaved state belongs. The edit is mirrored into the agent's own
resource draft as it is made.

It then survives leaving the flow, shows the agent as drafted wherever it
appears, and is what evals run when asked to run the draft rather than what
is deployed. Deploying or cancelling clears it; opening Edit without
changing anything does not mark the agent as drafted.

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

* feat: shape the evals surface around a saved agent

Evals hang off an `ai_agent` resource, so the surface is now only ever about
one: the `draft` subject kind, the standalone-step subject and the move that
carried a step's history onto a newly saved agent are gone.

- A run is permanent and numbered per agent. Running a single case is a trial:
  it answers in the panel and never touches the table.
- "Run scorers only" opens a run of its own that reuses the answers of the run
  you are looking at, so a scorer added later measures what already ran without
  calling the agent again.
- A draft run whose configuration is later deployed is stamped, once, to the
  version it became, so its label stops reading `v23 + edits` forever.
- A scorer can carry a pass threshold, read off the scores already recorded.
- The table is the case, its answer and one number per scorer; datasets are
  created and edited in a drawer; a run that executed an earlier state of the
  current draft says so above the table, in one line.
- Which agent a step is, whether it is being edited, and which version it is on
  is a strip above the step's tabs, because it is true of every tab.
- Capturing a case from a step test or a conversation is dropped, and with it
  the `memory` override on a linked step that nothing set.

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

* feat: run past versions of an agent, and number versions per resource

The evals home becomes one table of every run of the agent, whichever dataset
each is of, with one badge per scorer. A list spanning datasets cannot hold every
dataset's scorers to look a name up, so a score carries its name and kind with
its number, and thresholds are joined in per run and column.

Run now asks what to run: the latest agent, resolved when the run executes as a
flow step does, any past version, or the unsaved edits. Pinning is a subject kind
of its own, since a linked step resolves the resource live and inlining is the
only way to run a version that is no longer current.

Scorers move into the edit-dataset drawer. The column header over a run reports
and nothing else: a run is permanent, and a control there that changed the
columns would edit the past from the one place that must not. Adding one offers
four ways rather than two, writing and reusing being different jobs, and both new
kinds open with a summary filled in.

Versions are numbered per resource. `resource_version.id` is one identity
sequence for the whole table, so an agent saved nine times read v4 ... v24, and
the gaps counted writes in workspaces the reader cannot see. The id stays how a
version is addressed; the new number is what it is called, in the resource
history drawer as well as here. It is assigned on write rather than counted on
read because trimming past the cap and clearing a history both take the oldest
rows, and counting the survivors would renumber a version a run already names.

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

* fix: read the dataset a remembered selection names

Reopening the evals modal restored the last dataset from storage as a bare path,
without reading the row it names. Every "is this already the one?" test compared
against that selection, so all of them short-circuited and the dataset was never
loaded: editing it opened a drawer with no summary, no scorers and no cases.

The remembered path is now brought into context the same way any other choice is,
and the tests compare against the dataset that is loaded rather than the one that
is selected, so a selection can no longer stand for a read that did not happen.

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

* feat: give dialogs a trail in their header

A dialog deep enough to navigate had nowhere to say where you were: the header
held a fixed title, and the way back was a control each body placed for itself,
somewhere in a toolbar that moves with everything else the toolbar holds. The
header is the one part of the surface that does not move, which is where the
trail belongs.

`Modal` takes an optional `trail` of levels below its title, rendered as a
breadcrumb whose ancestors are the way back. Declarative on purpose: callers of
this depth already hold the state that says where they are, so the dialog reads
it rather than owning a stack they would have to push and pop in step with it.

Escape follows the trail. Leaving a level is what someone deep in a dialog means
by it, and closing the whole surface throws away the navigating they did to get
there; at the root it closes as before. That only works if a dialog can tell it
is the surface being addressed, so `Disposable` now answers `isTopmost()` and the
dialog asks before acting: it keeps Escape for itself, so nothing else was
arbitrating between it and a drawer opened from inside it, and both were acting
on one key press.

Evals is the first caller: its runs list is the root, a run is a level in it, and
the back button that used to sit above the table is gone.

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

* fix: portal dialogs out of wherever they were opened from

A dialog rendered in place inherits whatever the calling component happens to sit
inside. One `transform`, `filter` or `overflow` anywhere above it makes its
`fixed` positioning resolve against that ancestor instead of the viewport, and a
surface meant to cover the app is then confined to a box it never asked for: the
nav rail paints over it and its own edges are clipped.

Drawers have always portalled for this reason. Dialogs only did so when an
enclosing pane claimed them, and rendered in place otherwise, so the same screen
could show a drawer over everything and a dialog trapped behind the nav. They now
portal the same way: to the pane when one claims it, to `body` otherwise.

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

* fix: make the dialog's title the first step of its trail

The trail listed levels below the title, so a dialog one level deep read
"Evals > All runs > Run 20 · v6": three steps for two places, the first two of
them the same place under different names. The title is the root, so it is the
root's own segment, and the trail a dialog is given is now the whole path with
that segment at its head.

Its height stopped moving too. A heading carries a line-height of its own, so a
header holding only an h3 stood six pixels shorter than one holding segments as
well, and the dialog's whole top edge stepped as you navigated.

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

* fix: sharpen the evals controls around where you are standing

Each screen now offers what belongs to it. The list starts runs; a run is a
record, so it offers only the one thing that acts on the record itself, which is
measuring the answers it already stored. Starting a fresh run from inside one
asked which agent and which dataset from the screen least about either, and
scoring an existing run was offered from the list, where there is no run to
score. Which run and what it is read against are one question asked twice, so
they sit together rather than at opposite ends of a row.

Choosing what to run is now a toggle over the two states worth naming, the draft
and the saved agent, with every earlier version one click further: running an old
version is deliberate, and a list made all three look alike. The draft is read
when the dialog opens rather than taken from the caller's polled copy, which
could be seconds behind an agent edited a moment ago and would leave the option
out exactly when it is the reason for opening the dialog.

The dataset field carries its path under it and its edit button on hover, as a
resource picker does, so the closed field says what the open list said. Edits
waiting on an agent are a "draft" here as everywhere else in Windmill, rather
than "+ edits". The dialog runs an evaluation rather than "the agent", which is
what it was already called everywhere it is recorded. An agent being edited keeps
its evals button on a line of its own, clear of the decision to save or discard.

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

* fix: settle the evals controls on the patterns Windmill already has

The version choice uses ToggleButtonMore, as the AI provider picker does: the two
states worth naming stay in the group, the rest are behind the overflow menu, and
the one you pick joins the group rather than appearing in a second control below
it. The deployed one says which version it resolves to.

A run offers nothing to start. Scoring an existing run again was the last thing
left there, and it was one button explaining a distinction that the run and the
dataset already make between them.

The warning that a run executed an earlier draft is about the run on screen, so
it goes when the run does rather than following you back to the list, and it sits
against the table instead of inside a frame of its own.

A dataset just created stays open for its scorers and cases: those are what a
dataset is, they can only be added to one that exists, and closing on create sent
you to find it again to add them. Scorer settings are a cog rather than a word,
now that the row holds three actions.

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

* fix: close the gap in the version toggle and say what naming a dataset does

The overflow trigger is not a pill, so the room it reserves showed as a gap
between it and the button before it; it is pulled in by that much. The dataset
field gets its clear button, which is also the slot the edit button is positioned
against, so the two now sit where a resource picker puts them.

Naming a new dataset said nothing about what happens next, and the drawer looked
like it was missing the rest of itself. It says so instead: a scorer and a case
both belong to a dataset, so there is nothing to attach either to until this one
exists, and creating it leaves the drawer open on them.

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

* feat: choose a dataset's scorers while naming it

A scorer is a reference to a runnable, not a child of the dataset, so it needs
the dataset's name but not its row. The list is collected in the drawer while the
dataset is being named and sent with the create, which already accepts one, so a
dataset arrives holding the columns that were chosen for it rather than being
made empty and then edited to hold them.

Cases stay where they were: a case *is* a row of the dataset, so there is nothing
for it to be a row of until one exists. The drawer says which of the two is which
instead of leaving the screen looking like it is missing the rest of itself.

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

* fix: level the version toggle and name the dataset in its own field

The overflow trigger stands a row taller than a toggle button, so the group grew
to its height and left the sunken background showing under every pill beside it.
Every child of the group is the same height now, which is why the AI provider
picker never had the band: it sizes them all alike.

The dataset field says the summary with the path after it rather than carrying
the path on a line below. The list stacks the two, which a one-line field cannot
do, so it says both the other way round.

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

* fix: tidy the evals forms and the run's own controls

Picking a scorer that exists chooses between two sources rather than showing
both: the ones already measuring something, and everything else in the workspace.
The first list says what each is called with its path under it and what it
already measures on the right, instead of three columns that were the same path
truncated three ways whenever a scorer had no name of its own.

A dataset's drawer says what it is for on the page rather than under an icon, and
its summary is sized like the field beneath it.

The run's own row lines up with the table under it, the warning above that table
is spaced off the rule rather than sitting on it, and adding a case is gone from
a run: a run is a record of cases that were answered, so curating them from it is
editing what it measured.

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

* feat: create a dataset holding the cases written for it

Creating a dataset takes the cases to create it with, so one can be assembled in
a single act instead of made empty and then filled in. The drawer holds them
while the dataset is being named, gives them ids of its own to be edited by, and
sends them with the create.

Every case is checked before the dataset is written. `eval_case` grants users no
write, so the rows cannot be inserted in the transaction that creates the dataset
under the caller's own policies; validating first is what keeps "created holding
these cases" from becoming "created, holding some of them", and the rows that do
follow go in one transaction of their own.

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

* fix: name the button for what it opens, and say what each version is

Starting an evaluation asks which state of the agent and which dataset, and both
cost a provider bill, so a button that read as spending one on the way past was
lying about the click. It opens something, and says so. Running one case from the
panel keeps its own name and its play icon, because that one does run on click.

The version options say what they are rather than what they are not: what a flow
step would or would not run is a fact about somewhere else, and someone choosing
what to evaluate is not standing in it.

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

* fix: give the editing card two rows and mark evals as beta

At the width of a step panel the card's one row wrapped: the line naming the
agent, the line saying what saving does, and the two buttons deciding the edits'
fate all fought for it. Deciding gets a row of its own, and evals sits against the
line it is about, since evals of an agent being edited run the edits.

Evals is named wherever it is offered. It read as a word in one state of the card
and as an icon in the other, which is two things to recognise for one door.

The dialog carries a beta badge against its own name, before any level below it:
every way in lands there, so it is said once and stays put as you navigate.

The version toggle spells out which is which. Both are the agent at v2 and the
difference between them is the whole choice, so it is worth the width.

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

* fix: name a new dataset, and lay the scorer's settings out like a step's inputs

A new dataset arrives called "Dataset 1", which the path follows as it follows
any summary: a dataset with none was one every table could only call by its path,
and the two seeds are what the summary rule already produces.

Scorer settings put each field's description between its label and its input,
where a step's inputs put theirs, and its inputs are the size the rest of the
drawer uses. The runnable behind the column is a link to it with its kind's icon,
since it is a resource of its own and the one thing about it these fields cannot
change. The line explaining that a pass line re-reads recorded scores went: the
threshold is a number to set, and how it is applied is not a decision being made
here.

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

* feat: curate a dataset in the drawer and save it in one act

The drawer holds the cases while they are edited and writes them when it is
saved: added, changed and dropped, whichever it is. Typing no longer writes, so a
set is never half saved while someone is still deciding what is in it, and Save
means the same thing whether the dataset exists yet or not.

A case panel offers reading rather than acting. Running one case now and editing
one from a run were the last two ways to change a record from the screen showing
it, and the machinery behind the first went with it. The answer is rendered as
the prose it is, under what it is: the case's result, whichever run is selected
above it.

The rest is what the run's table was doing to its own edges: a column name is
clipped to its column rather than running into the next, the table squares off
against an open panel, and that panel closes with the run it belonged to.

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

* fix: one border above a table, and a link to the run's job

The row above the table drew a bottom border and the table draws its own top
edge, so every table sat under two lines. The row keeps its spacing and the table
keeps its edge.

A column header no longer spins while its scores arrive: the cells under it are
where the numbers are missing, and they say so themselves. The beta badge is the
height of the word beside it rather than of the line it sits on.

A run is one flow and therefore one job, so the run says where that job is: what
it is doing, what it cost and what it logged are all there rather than
reconstructed from the table.

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

* feat: stream scores as each scorer finishes, and show them per case

A scorer runs after the agent inside the case's own iteration, so its verdict can
be read as soon as its step is done. Waiting for the iteration to end held every
column of a case back until the last of them finished, which is why answers
arrived one at a time and scores all at once.

Reading a job that is still running needs one guard: a module with nothing in it
is a step that has not run, not one that produced nothing, and recording the
second makes a failure that never goes away.

The panel beside the table shows what each column made of the case and why. The
reason a judge gave was stored and never shown, which is the half of a score that
says anything. It stops repeating the question the header already asks, and a
case still running reads as waiting rather than as an answer that says "Running".

A run is a number beside a dataset, so the list puts the two together.

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

* feat: score a case with every scorer at once

The scorers of a case read the answer and never each other, so they ran one after
another for no reason: measuring a case now takes as long as its slowest column
rather than as long as all of them. Each is a branch of its own, kept from
failing the others, so a judge that errors costs its own column and no more.

An iteration is three steps again — answer, payload, scores — rather than one per
scorer, and each branch is named for the column it produces, so the graph of a
run says which scorer did what instead of spelling out an id.

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

* fix: read a judge's score out of the JSON it nearly wrote

A judge quoting the agent inside its own reason writes those quotes unescaped,
which is invalid JSON and also the most ordinary sentence for it to produce. The
whole verdict was being thrown away over it, so a column that had a number
reported having none.

The number and the reason are now read straight out of such text. Deliberately
not a second JSON parser: it finds the two keys and takes what follows, which is
what survives a quote in the middle of a sentence.

A case still running says so with a spinner rather than with the word "Running"
sitting where its answer goes.

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

* feat: ask a judge for a shape instead of trusting it to write one

A new judge carries an output schema, so the provider holds it to `{score,
reason}` rather than the prompt asking it to. Windmill already delivers a schema
whichever way the model takes it, a tool for Claude and Bedrock and the native
parameter elsewhere, so there is no list of models to keep here.

An agent with no runs offers its first one where the first row would be, rather
than from a toolbar above a table that has nothing in it.

Starting a run no longer picks a dataset for you. It fell back to whichever came
first, which on an agent that has never run means offering another agent's set as
though it were the obvious one; and with no dataset at all it says so and offers
the one move there is.

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

* feat: report a column that failed throughout, and hold the run dialog

The runs overview dropped any column that produced no number, so a judge
that failed on every case of a run vanished from the row and read as a
column nobody had asked for. The aggregate now reports every column that
has cells, with the count of the ones it failed on, and the badge says
"failed" where there is nothing to average. A column with no cells at all
is still left out: that one was added after the run and has nothing to say
about it.

Creating a dataset closes the drawer rather than turning it into an edit
of what it just made: scorers and cases already ship with the create, so
there is nothing left to stay open for. Reached from the run dialog, it
gives the screen back with the new dataset selected, and the dialog keeps
the version you had already chosen.

Also:
- the case panel's job link moves to the panel's own header, where its
  scope is: the job is the whole iteration, not the answer it sat over
- one action in the scorer drawer's header, as its neighbours have. The
  reuse list picks rather than adds, and says which dataset each column
  already measures
- adding a case is the last row of the list it lands in
- the pane shows what it has read rather than an empty state it has not
  earned yet, and its rows say they open
- the linked agent card loses a border it had inside another one

* fix: keep the linked agent card's outline

The card is a thing inside the step's inputs rather than a section of
them, and the outline is what says so. Only the rule inside it goes: the
detail it separates is already set apart by being detail.

* refactor: fit the eval surface to the shipped design

* feat: give a nested dialog a back control and the runs list its own moves

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

* feat: put a dialog's description under its title

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

* refactor: fold a dialog's back control into the crumb it returns to

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

* feat: edit a dataset's cases as a table rather than a list beside a form

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

* feat: edit a dataset's cases in the grid the data tables are edited in

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

* feat: edit a grid cell of prose in place, and cap a dataset at one page

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

* refactor: keep the cell editor's styles beside it, not in the vendored theme

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

* fix: keep an empty cell empty and cap the editor's growth

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

* feat: name the step that assembles a run for the scorers

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

* feat: run the payload step natively, and say so when nothing serves that tag

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

* fix: report an answer as answered while its scorers are still running

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

* feat: let a scorer say a case is not one it measures

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

* fix: score the answer, and leave a case with no expected answer unmeasured

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

* refactor: split the evals backend into modules

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

* fix: record what a run produced so it outlives its jobs

* fix: read only the agent step's own tool jobs into the payload

* fix: pin a run's configuration and give the judge the attachments

* feat: write a dataset's cases in one transaction

* chore: refresh the sqlx cache for the eval queries

* fix: drop results a newer selection has superseded

* fix: keep a draft the agent editor never opened on

* feat: let a run record what it produced instead of waiting to be read

* fix: serialize the replacements of a dataset's cases

* fix: stop the poller from superseding a read slower than its interval

* chore: refresh the sqlx cache

* fix: keep a failed read from settling a cell as a case with no answer

* fix: hold the case grid while its save is in flight

* fix: keep a failed collect step from failing the run it recorded

* chore: refresh the sqlx cache

* fix: commit an open cell into the save that reads it

* refactor: size the eval buttons with unifiedSize

* docs: describe a run as the one flow it is

* fix: show a run's recorded rows when part of it cannot be collected

* refactor: size the remaining PR-added buttons with unifiedSize

* fix: save the dataset name that was submitted, not the one typed after

* fix: force an open cell into the save that was pressed for it

* fix: refuse to score a run whose evidence could not be read

* fix: hold one lock over a dataset's case count and its writes

* fix: keep one unreadable run from costing the whole runs list

* refactor: drop the banned bindable-default from the eval props

* fix: hold the scorer controls while the dataset is written

* fix: read only the caller's own draft of an agent

* docs: say in the contract that a run pins its configuration

* fix: say a scorer did not run rather than blaming a missing answer

* feat: resume the agent draft you already had when you press Edit

* refactor: build the trail and dataset controls from Button

* fix: clear the open-cell flag when the drawer reopens

* chore: refresh the sqlx cache

* fix: read a run's configuration and its version from one snapshot

* fix: refuse a dataset path or summary the column cannot hold

* refactor: handle the agent draft the way the resource editor does

* fix: run only a configuration the launch actually read

* docs: bound dataset path and summary where they are submitted

* fix: surface a stalled agent draft instead of claiming it is kept

* fix: stop claiming a draft holds edits a failed write never sent

* fix: word a missing score only once the run says whether the case answered

* fix: let a breadcrumb crumb shrink so its truncation applies

* docs: describe where an agent's unsaved edits live and what drops them

* fix: keep harvesting scores when the run cannot yet word a missing one

* fix: report a refused draft write the card was reading as a save

* fix: drop the refused draft write when the server copy is taken instead

* refactor: build the scorer and dataset pickers from the design system

* fix: say what removing a scorer column actually does

* fix: drop a refused draft write wherever the server copy is read

* fix: let a picker row be as tall as the two lines it holds

* docs: record what removing a scorer column does to recorded runs

* fix: send a queued draft write before reopening, and drop only what it refuses

* refactor: write the agent draft at commit points instead of mirroring keystrokes

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

* refactor: run an agent's edits from the step instead of keeping them as a draft

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

* fix: make the diff badge keyboard operable and refuse an edits run without its edits

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

* fix: drop the dataset icon from the scorer picker rows

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

* fix: size the evals buttons like the rest of windmill and call a run of edits edits

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

* fix: count a brain expression as an edit of the linked agent

* fix: cap scorers per dataset and report a launched run as launched

* fix: harvest scores in one read, refuse duplicate case ids, allow group paths

* fix: mint scorer ids server-side, save a dataset edit in one request, check attachments

* fix: write a dataset edit and its cases in one transaction

* fix: atomic dataset create/edit, reset eval pane per agent, stable pending scorer ids

* refactor: govern eval_case writes by RLS so a dataset edit is one transaction

* fix: pin launch snapshot, order case locks, cap dataset size, guard stale load

* fix: cap dataset bytes on single-case writes, reset run-dialog flag on load failure

* feat: migrate eval datasets on username change, settle unspawned cases, drop unused case endpoints

* fix: resolve scorer scripts as the caller and pin their hash; migrate scorer paths on rename

* fix: bound a failed tool call's error to the payload truncation cap

* fix: pin scorer hash as a hex string, reject missing judges, migrate eval authorship

* fix: record an out-of-range scorer result as an error, not a score

* fix: resolve judges in one caller-scoped read, pin deployed scripts, bound pass_if

* fix: settle unspawned cases only when the run completes, and their score cells too

* feat: reassign eval datasets and their path references when offboarding a user

* fix: use the regex backreference in offboarding eval path rewrites

* fix: register eval datasets in offboarding registries, keep resource-version param name

* refactor: name the resource-version path param id, since it is the row id not the version

* fix: validate dataset paths canonically, clone eval data on fork, surface eval load and launch failures

* docs: note MCP tool results are not yet surfaced to eval scorers

* fix: show the eval error state on any load failure, not only an empty dataset list

* fix: preserve eval case order across a batched save

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjewUvXFEjNLLw7kxz41h

* docs: scope the eval launch delete-safety guarantee to the assembly window

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjewUvXFEjNLLw7kxz41h

* fix: only offer deployed scripts as eval scorers, drop unbuilt rescore claim

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjewUvXFEjNLLw7kxz41h

* fix: enforce 0-1 scorer threshold in the settings drawer and clear stale eval load errors

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjewUvXFEjNLLw7kxz41h

* fix: scope subject version/hash reads to the caller and keep a 0 pass threshold

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjewUvXFEjNLLw7kxz41h

* fix: select the saved dataset when creating or renaming from the Run dialog

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjewUvXFEjNLLw7kxz41h

* fix: gate eval dataset rename on path ownership, not just write access

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjewUvXFEjNLLw7kxz41h

* fix: tolerate a malformed agent config when resolving the deployed label

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjewUvXFEjNLLw7kxz41h

* refactor: trim eval code and comments, fix shared select and modal paths

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

* fix: drop the rename warning when editing an eval dataset path

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

* fix: add eval dataset delete, keep summary on partial edits, settle resultless scorer cells

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

* test: cover parseThreshold and subjectLabel

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

* fix: hold dataset Save during a scorer write, derive draft_hash only from the carried draft

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 11:08:56 +02:00
GuilhemandClaude Opus 5 449b1a6933 fix: ground the chat's AI agent provider in the workspace's models (#10774)
* fix: ground the chat's AI agent provider in the workspace's models

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

* fix: never reject an AI agent model the catalog could not confirm

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

* test: benchmark AI agent provider grounding in ai_evals

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

* fix: only reject an AI agent model an exhaustive listing rules out

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

* fix: keep instance-level AI settings out of the workspace provider catalog

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

* fix: keep untrusted model ids out of the chat's context

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

* refactor: carry completeness on the model listing itself

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

* docs: correct two comments left behind by the catalog rework

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

* fix: bound the model listing and verify the default against it

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

* fix: honour a workspace default a filtered listing names

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

* fix: recognise a workspace default past the prompt's model cap

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

* fix: keep an aliasing provider's unlisted model ids permissive

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 10:42:02 +02:00
Ruben FiszelandClaude Opus 5 a9112b72a5 fix: make workspace preprocessor scripts selectable in flow preprocessor steps (#10786)
* fix: pick workspace preprocessor scripts in the flow preprocessor step

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

* fix: explain the empty preprocessor list and keep the editor bar hub populated

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

* fix: derive the editor bar's script kind from the preprocessor slot

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

* fix: keep the preprocessor entrypoint when resetting a step's content

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

* docs: state the preprocessor reset invariant instead of the old control flow

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:05:26 +02:00
GuilhemandClaude Opus 5 2b4369d7cb fix: reject invalid AI agent tool names when the chat writes a flow (#10756)
* fix: reject invalid AI agent tool names when the chat writes a flow

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

* fix: address review nits on agent tool name validation

Share one AI-agent walk between the providerless-agent and invalid-tool-name
collectors, drop the unused validateToolName, and list every reserved id in the
tool naming rules.

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

* fix: describe an agent tool's summary as the name the agent calls it by

The OpenFlow schema described `AgentTool.summary` as a short description of
the tool, which is the same schema the flow write tools hand the model, so it
pulled against the naming rules. Narrow those rules to flowmodule tools, since
websearch and mcp tool names are never regex-checked, and let `kind` take
either vocabulary its callers resolve.

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

* fix: name-check only the agent tools whose summary the agent calls

An mcp tool exposes the MCP server's own tool names and a websearch tool's
summary is a plain label, so neither reaches the worker's name check. Both
default to an empty summary in the editor, which the chat then refused to
write back.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 10:49:01 +02:00
494e6f146e fix: route legacy AI entry points to sessions instead of the unmounted chat (#10705)
* fix: route legacy AI entry points to sessions instead of the unmounted chat

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

* fix: keep createSession's workspace choice and revert pipeline hand-off

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

* fix: guard in-session step generation and restore AI action labels

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

* fix: keep AI Fix usable in-session and stop silent no-op hand-offs

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

* fix: neutral AI form assistant heading to match both branches

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

* docs: state the AI form assistant branch rationale once

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

* feat: auto-send AI hand-offs and keep in-session step generation in global mode

* fix: name the AI session in the entry point labels

* fix: claim auto-send reactively and queue programmatic sends mid-turn

* test: pin the auto-send claim going stale

* fix: stop the script drawer hand-off from abandoning its unsaved script

* fix: keep a stale hand-off prompt and close the pre-loading send window

* fix: only blank the composer for an intent this wrapper can claim

* fix: report composer edits only, never the mount-time draft

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-08-18 17:06:51 +02:00
hugocasaandClaude Opus 5 3f07a1a803 feat: let the global AI chat call connected MCP servers as the user (#10656)
* feat: let the global AI chat call connected MCP servers as the user

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

* fix: address review findings on the chat MCP tools

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

* feat: connect MCP servers from a predefined list in chat and agent steps

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

* feat: show the OAuth redirect URL in the instance connect settings

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

* fix: clarify the OAuth redirect URL copy in instance settings

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

* fix: match the instance settings warning style and drop the redirect tooltip

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

* fix: use the standard warning alert for the redirect url mismatch

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

* fix: correct the GitHub token guidance in the MCP registry

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

* feat: warn when an OAuth connect lacks the scopes an MCP server needs

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

* fix: request the connect's scopes when the oauth popup is opened directly

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

* feat: connect an oauth-app MCP server without leaving the panel

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

* fix: seed connect scopes from the instance config only

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

* feat: make the chat use only the MCP servers you turn on

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

* refactor: align the MCP connect UI with the design system

* feat: make a pasted url the default way to connect an mcp server

* feat: show provider icons on the suggested mcp servers

* fix: make both mcp sign-in paths behave the same and stop reloading on toggle

* fix: clarify the mcp tool step's server field and drop its info alert

* fix: name the mcp resource in the tool step and move the transport note into the connect box

* fix: drop the redundant description on the mcp resource field

* fix: make the mcp connections trigger icon-only

* fix: scope enabled mcp servers to the account and address review nits

* fix: wait for connect scopes and create session connections in the operating workspace

* feat: move mcp connections into the chat's plus menu and fix review findings

* fix: show mcp servers as checkboxes so off reads as a state

* feat: give menu rows an on/off switch and use it for mcp servers

* fix: lead the mcp menu rows with the switch

* feat: keep the menu open while toggling and simplify the connect card

* fix: ask for the server before the credential in the connect card

* fix: show one credential path at a time in the connect card

* fix: label the path field and move token guidance into its tooltip

* fix: open straight into connect and keep the server menu scannable

* feat: warn when an mcp connection lands outside your own space

* refactor: require the workspace on the mcp connect components and rename the oauth child

* fix: replace the oauth variable on reconnect and bound every mcp result

* feat: show a connected server's provider icon in the connections list

* feat: resolve mcp provider icons from the url and clarify the path field

* style: align the mcp connect card with the design system surfaces

* style: drop the redundant oauth support line and name the scopes oauth scopes

* feat: keep the mcp connect card open in the connections drawer

* feat: preopen the mcp connect card under the agent step resource picker

* feat: resolve a typed mcp url to its registry entry and describe the token field

* style: name both mcp connect actions connect

* style: name the mcp oauth actions connect with the provider

* style: say in the path description what the connect action will save

* style: name the resource type in the mcp connect path description

* feat: cache mcp provider icons and confirm disconnect in a modal

* fix: keep the mcp menu switches live and the disconnect modal above the drawer

* style: fall back to the plug icon in the mcp menu rows

* fix: never destroy a foreign variable or resource when connecting an mcp server

* fix: prove a token variable is ours before writing it and bound mcp search failures

* fix: pin an mcp oauth popup to the target it was opened for

* fix: bind an mcp credential to the server and popup it was requested for

* fix: bound mcp tool calls with a deadline and drop stale server listings

* fix: keep the disconnect confirmation handler returning void

* fix: tie the mcp tool cache to the resource revision and the grant to its scopes

* fix: verify mcp read-only server-side, keep oauth connector mounted

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 18:51:19 +02:00
53eb94659b feat(telemetry): extend feature-usage tracking beyond AI features (#10681)
* feat(telemetry): extend feature-usage tracking to long-tail features

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

* docs: describe telemetry as product feature usage rather than AI usage

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

* refactor(telemetry): trim disclosure copy and drop unused pick origin

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

* fix(telemetry): count trigger fires per run and key hub picks from hub data

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

* fix(telemetry): slugify hub keys and order both writers' upserts

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

* fix(telemetry): key native trigger adoption by service so it matches fires

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

* chore: update ee-repo-ref for native trigger adoption fix

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

* refactor(telemetry): move feature-usage collection into the ee crate

* docs: point feature-telemetry at the moved registry and rust writer

* docs: correct the trigger-fire gate comment to match measured step counts

* docs: put the private-build caveat on the verification step

* chore: update ee-repo-ref to f079db9e7962a413b349c4ff8036080894f30771

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

Previous ee-repo-ref: 055adb80416f9339c9a28ae7fbaeadad30d74959

New ee-repo-ref: f079db9e7962a413b349c4ff8036080894f30771

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-08-14 18:50:38 +02:00
Ruben Fiszel 5f819cd344 fix(frontend): skip reserved ids when auto-assigning flow module ids (#10651)
* fix(frontend): skip reserved ids when auto-assigning flow module ids

* test: state the reserved-id invariant only beside the implementation
2026-08-12 08:41:30 +02:00
GuilhemandClaude Opus 5 676256bacc feat(flow-editor): measure step panel placement (#10543)
* feat(flow-editor): measure the redesigned step panels

Instruments the flow editor's step, loop and branch panels on the existing
anonymous `feature_usage` channel, so the redesign can be judged on how the
panels are actually used rather than on nothing.

Eight event kinds under a new `flow_editor` feature: panel opens and their
dwell (bucketed, per placement), placement-preference overrides, which
settings get configured or cleared, settings that read as invalid, the
prop-picker connect lifecycle, AI input suggestions, and the step header
menu that "Save to workspace" now lives behind.

Settings changes are diffed off `describeStepSettings`, the same view the
graph badges render, so the telemetry vocabulary cannot drift from the one
on screen. Only `panel_open` and `setting` carry an entity id — one opaque
id per editor mount — since a per-entity row is only worth its cost where
the spread per editing session is the question.

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

* fix(flow-editor): keep the panel telemetry honest

Review follow-ups on the instrumentation:

- The top dwell bucket was `120s+`, and `+` is outside the charset
  `is_identifier_shaped` accepts, so `log_feature_usage` skipped those
  events and still answered 204 — the longest visits vanished with no
  error on either side. Renamed to `120s_plus` and pinned every emittable
  key against the backend's charset in a test, since the producer is
  TypeScript and the validator is Rust.
- Dropped the per-session entity id from `setting`: it would pay a row per
  session per day across twenty-four keys, for a distribution its plain
  counter already largely answers.
- An armed connect that went away with its component never reported, so
  `open` did not balance against `insert` + `abandon`.
- Session preview tabs keep hidden editors mounted, which billed panel
  time nobody spent. `FlowEditorView` now publishes the visibility it
  already knows about.
- Re-picking the active placement row logged a move, which also made
  `auto:from_docked` mean two different things.
- The last dwell of a session was lost on tab close, since Svelte tears
  components down on navigation but not on `pagehide`.

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

* refactor(flow-editor): narrow the telemetry to panel placement

The eight-kind instrumentation measured more than could be read. With nothing
recorded before the redesign there is no baseline to compare panel opens, dwell
times, settings usage or connect funnels against, so those counters answered
questions nobody could act on while costing a row per key per day in an
instance-wide table.

What remains are the three numbers the modal panel is actually judged on: how
often the 1280px breakpoint puts the panel in a modal, and how often people
override that in each direction.

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

* fix(flow-editor): stop counting placement in session preview tabs

Preview tabs keep every flow editor mounted and laid out at panel width
whether or not it is the visible one, and that panel is narrower than the
breakpoint by construction. Each flow tab opened in a session therefore
emitted a `breakpoint_modal` on mount, and one drag of the session panel
across 1280px emitted one per mounted tab — with no host dimension in the
key to separate that from the crossings the counter exists to measure.

Also corrects the comment on the no-op placement guard, which justified
itself with a key vocabulary that no longer exists.

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

* fix(flow-editor): make the three placement counters comparable

Sessions were excluded from the breakpoint counter but not from the two
override counters, so a pin made in a session landed in the same bucket
used to judge the breakpoint, with no crossing in the denominator to read
it against. All three are now gated together.

An override is also only counted when it moves the panel. Choosing
"Detached" on an editor the width had already put in a modal states a
preference without changing anything, and the aggregate carries no width
to separate that from the wide-screen override that is the actual signal.

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

* docs(flow-editor): describe the two override keys by what emits them

They documented themselves as overriding `auto`, which is no longer the
rule: pinning Attached on a wide editor overrides `auto` and emits
nothing, while going from an Attached pin to Detached below the
breakpoint emits `force_detach` even though `auto` would have produced a
modal there too. This file is what someone reads when interpreting the
numbers, and "override of auto" is the misreading the emission rule
exists to prevent.

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

* fix(flow-editor): count the panel moving, not the breakpoint being armed

The tracker held "the breakpoint is responsible for this modal" rather
than "the panel is modal", so on a narrow editor pinning Detached and
releasing it back to Auto emitted a second breakpoint_modal for a panel
that never moved. It also died with the editor, which FlowBuilder rebuilds
through a `{#key}` on every reload — each rebuild re-armed it and counted
the same narrow editor again.

Both inflate the denominator that the two override counters are read
against, and both bias it the same way: toward concluding that nobody
overrides the breakpoint.

The tracker now follows the panel's placement across preference changes,
and FlowBuilder owns it from above the `{#key}`, which also puts the
session exclusion in one place instead of at each call site. The
moves-only rule moves into `forcedPlacementEvent` so both halves of it sit
in the module the tests can reach.

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

* fix(flow-editor): ignore placements measured before the editor is laid out

A reload rebuilds the editor through `{#key renderCount}`, and the panel
controller is rebuilt with it: its width restarts at zero, which resolves to
`docked` because that is what is safe to render rather than because the editor
is wide. The breakpoint tracker read that transient as the panel having docked
and counted the real width landing as a fresh crossing, inflating the
denominator both override ratios are read against.

`useFlowPanelMode` now exposes `measured`, and the tracker skips anything
unmeasured instead of recording it as a placement.

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

* docs(flow-editor): state the placement invariants once each

The width-zero rule had accumulated at four sites, two of which forward it
without being able to break it. Keep it beside the guards that enforce it.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 12:29:31 +02:00