Commit Graph
1210 Commits
Author SHA1 Message Date
Ruben FiszelandClaude Opus 5.5 4f712318c3 feat: run previews as the runnable's on_behalf_of identity (#11778)
* feat: run script and flow previews on behalf of another user

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

* fix: refuse operators and detect servers that drop preview on_behalf_of

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

* feat: editor previews run as the configured on_behalf_of identity

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

* fix: probe before submitting, keep step and viewer data identity in previews

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

* fix: never let a job token request viewer data access on a preview

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

* feat: open impersonated preview logs with who they run as and who asked

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 10:13:27 +00:00
08319a004c feat: allow early return on steps inside branches (#11733)
* feat: allow early return on steps inside a branch to one

The "Early return for sync webhooks" flow setting could only point at a
top-level step. It can now also point at a step inside a branch to one,
in any branch including the default one, and through nested branches to
one.

When the branch is taken, the sync call returns that step's result as
soon as it completes while the rest of the flow keeps running. When it
is not taken, the call returns the flow's final result. The sync wait
loops already resolved nested steps and fell back to the final result,
so they are unchanged.

Worker: check_if_early_return_or_last_in_early_return_parent now
accepts a job whose step id matches early_return at any depth, as long
as every job between it and the root is a branch to one. A loop
iteration or a subflow reusing the same id does not take the stream.
The existing case, the last step of a branch to one that is itself the
early-return step, is unchanged.

Frontend: the native <select> is replaced by EarlyReturnPicker, a
searchable list in flow order where each nested step shows its path
(e.g. `a › Branch 1`). Branch to all and loops inside a branch to one,
and the steps inside them, are not offered: the results lookup cannot
resolve them when nested. Picking a nested step shows a hint that it
only runs if its branch is taken.

* fix: stream the last step when the early return branch is not taken

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

* fix: keep the stream off the last step once the early return step ran

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

* refactor: list early return steps by id only

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

* refactor: use the standard select for the early return step

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

* fix: match the early return step by its flow scope, not its path

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

* fix: keep branch to all and loop steps off the early return stream

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

* feat: allow early return on steps inside a branch to all

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

* fix: resolve a nested branch to all or loop as the early return step

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

* test: pin which step streams for an early return inside branches

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

* fix: return an empty nested loop or branch to all as its early return list

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

* revert: read an empty container's early return status from the root

An empty nested loop or branch to all falls back to the flow's final result again,
so an empty top-level loop that failed keeps its real status.

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

---------

Co-authored-by: stefan <stefan.radovanovic@ip-paris.fr>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 23:19:29 +02:00
hugocasaandClaude Opus 5.5 fe2571ea53 feat: list agents with runnables, let them be starred, and fill out their menu (#11651)
* feat: star agents and add runs, audit logs and copy path to their menu

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vo7HXMdgqXJhR7p8YyVgjB

* fix: list an agent's runs as the previews they are

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vo7HXMdgqXJhR7p8YyVgjB

* feat: list agents in the merged runnables stream so they sort with everything else

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vo7HXMdgqXJhR7p8YyVgjB

* perf: index agent paths so the merged listing and owner counts skip other resources

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vo7HXMdgqXJhR7p8YyVgjB

* feat: find agents in the ctrl+k search

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vo7HXMdgqXJhR7p8YyVgjB

* feat: open an agent's version history from its menu

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vo7HXMdgqXJhR7p8YyVgjB

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:59:01 +00:00
hugocasa 9249aa02d5 feat: let a linked agent step override model and reasoning effort (#11685)
* feat: let a linked agent step override the model and reasoning effort

* fix: only honor model and effort overrides the step authored

* fix: treat an authored empty effort override as no effort

* fix: keep a static empty override from reading caller arguments or the saved effort

* fix: keep step tests and multi-agent chats from inventing an effort override

* fix: keep the saved model fallback when linked steps differ only on effort

* fix: show the agent override hint on the input's own help row

* fix: align the override hint spacing between static and expression inputs

* fix: keep the override rows a dropdown whatever the model is set to

* fix: give the override hint the 4px form gap and drop its trailing period

* fix: space the override hint with a margin so it is not clipped

* test: pin the saved model fallback when linked steps differ only on effort

* fix: keep the saved model fallback across agents that saved different efforts
2026-10-14 10:23:56 +02:00
GuilhemandClaude Opus 5.5 3857ce3693 feat: shrink the page header in stages and fold page actions by measured width (#11732)
* feat: shrink the page header in stages and let the bar fold page actions

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

* feat: fold the runs header through the bar and wrap its filter to a second row

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

* feat: truncate the run id and fold run actions into one menu on a narrow bar

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

* feat: keep an item's summary and drop its path on a narrow page header

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

* feat: show the full summary and path read-only when they cannot be edited

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

* feat: keep secondary detail actions in menus and the star beside the summary

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

* feat: show secondary detail actions as buttons while the bar has room for them

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

* fix: fold embedded detail headers, refresh stale fold widths, keep the path beside a summary placeholder

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

* fix: drop the duplicate path on read-only detail pages and re-measure folded actions when they change

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

* fix: re-measure the runs header when its hidden counters change width

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

* revert: drop the runs header's counter version, the badges do not change its width

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

* fix: stop the header re-measuring its actions every frame while idle

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-10-13 23:13:22 +02:00
Ruben FiszelandClaude Opus 5.5 0758bdc46b refactor: remove the frontend build-time base url (#11674)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:22:46 +02:00
GuilhemandClaude Opus 5 338c4e921d feat: operators get AI sessions (#11443)
* feat: operators get AI sessions

`prefersSessionHandoff` stops taking the role, so every AI entry point an operator
can reach — Ask AI, the home composer, Open in session, the flow editor's hand-off,
the raw-app template picker — opens a session for them like anyone else. The
sessions page drops its refusal, and the layout stops keeping them on the docked
chat.

In the sidebar they now get the Workspace/AI Sessions switch and session mode, and
lose the Ask AI entry that stood in for them, since the pane behind it is no longer
the one they are sent to.

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

* feat: operators get run-oriented examples in the home composer

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

* docs: note builder_flows in the operator composer examples comment

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

* feat: gate session tools and editors on operator rights

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

* feat: keep operator AI sessions behind a browser opt-in

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

* fix: keep flows refused in the prompt for operators without builder rights

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

* refactor: decide every frontend edit check through one editRights module

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

* fix: let admins who are also operators open session editors, as drafts.rs does

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

* fix: mirror drafts.rs exactly in roleCanDraft and correct the edit-rights docs

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-09 18:22:34 +02:00
hugocasaandClaude Opus 5.5 c52ba2fd30 feat: agent nits: multi-select, memory-off notice, agent step label (#11622)
* feat: agent nits: multi-select, memory-off notice, agent step label

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

* fix: say saved, not renamed, on a new agent's first deploy

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

* fix: run the agent page form on cmd+enter, select agents by keyboard

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

* test: pin the agent step label

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:24:00 +02:00
Ruben FiszelandClaude Opus 5.5 a85d0b76cc feat: add Pi template to the AI sandbox picker (#11619)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:03:47 +02:00
41162b270c feat: one page header across routes, and one sidebar for operators and developers (#11310)
* wip: one sidebar for operators and developers, with a detach mode

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

* feat: snap-detach the operator sidebar, and join the handle to the picker

Dragging the sidebar into the left snap zone detaches it for an operator, who
has no icon-only rail to collapse into; the width the drag started from is what
gets persisted, so re-attaching restores the sidebar's size.

The handle's colour pastille slides onto the card's workspace pastille as the
card opens, alongside the workspace name, and both buttons that attach or
detach the sidebar carry a tooltip.

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

* feat: attach and detach from the sidebar header, open the card under its handle

The button that swaps the sidebar between docked and detached moves to the
header, next to the workspace name, so it stays in one place across the swap;
the icon-only rail has no room for it and keeps the footer copy.

The handle itself becomes a component of its own, carries the workspace icon
its picker uses, and opens the card rather than docking — docking is the header
button's job. The card is placed under the handle measured at open time instead
of at a fixed offset.

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

* feat: one page header across routes, with the workspace picker in it

Every route now wears the same bar above its content: the sidebar toggle, the
workspace picker, and whatever the route registers — an item's breadcrumb and
summary, or a section name — with the route's own buttons at the far end. The
bar's row is level with the sidebar's own top row, which the workspace ⇄
sessions switch now owns, so the two read as one band.

A route fills the bar by mounting PageHeaderContent, which registers a getter
rather than a snapshot: the layout calls it while rendering, so a path or
summary the page edits reaches the bar without an effect keeping two copies in
step. Nothing registers yet; the routes follow.

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

* feat: detail pages fill the page header, which now spans above the sidebar

A script or flow detail page no longer draws a bar of its own: its path becomes
the header's breadcrumb, its summary keeps the rename-and-labels popover next to
it, and its badges, menu and buttons ride along as the header's actions. The
band runs the full width with the sidebar hanging below it.

A snippet rendered in the header initialises its components under the header,
not under the page that wrote it, so anything the page's tree provides is lost —
the detail page's buttons read TriggerContext and threw. ContextBridge carries
the entries a registration names back around the render.

The floating handle is gone with it: the header's own toggle is the one control,
hovering it peeks the sidebar in and clicking pins it. The workspace picker in
the header is a denser variant of the sidebar's, and the card's outside-click
catcher starts at the card rather than the viewport top, so it no longer
swallows clicks meant for the toggle.

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

* feat: one breadcrumb in the page header, and the banners that used to sit under it

The header band now carries a single breadcrumb — workspace, fork, then the item's
path — built from one component, so every segment has the same size, weight and icon
size, and slashes separate them. The item's kind icon rides with its summary, after a
dot, rather than with the path.

The fork and drafts banners move into the band as badges plus a subtle Review button:
ahead/behind counts instead of a bare change total, and the per-kind breakdown stays on
the compare page. The home hero's CLI, MCP and Hub links move into an ellipsis menu,
alongside the toggle that pins the session chat to the page.

The flow editor and the app viewer hand their bar to the band too, and a route can drop
the band's bottom border when its own content draws one — the border stays in the box
with a transparent colour, so contents never shift a pixel across a navigation.

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

* feat: the runs page's own header row moves into the page header

The title, the queue counters, the kind and status filters, the schedule and
future-job switches, the search field and the timeframe picker all ride the band
now, and the page starts at the chart — one row of vertical space back.

The two toggle groups appear from 1500px: below that the bar cannot hold them next
to the search field, and the same filtering is reachable from the search field's own
kind and status chips. The search field is what gives width back, and the breadcrumb
yields last, so a page's own name stays whole while the workspace and fork names
truncate.

The queue counters keep their icon form at every width, since the bar has room for
two counters and never for the two labelled blocks. Their counts hang outside their
icons, so the group carries the margins that keep a badge off its neighbour, and the
suspended counter answers a hover at zero rather than sitting there as a dead icon.

The band is 44px rather than 40, and a page can hand the breadcrumb a hint — the
documentation tooltip that belongs with the page's name rather than with its buttons.

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

* feat: no border under the page header, and the peek card closes on the way out

The band draws no bottom edge and no page asks it to, so `barBorder` goes with it —
a page that needs the separation draws its own line, as the detail pages now do above
the run form and the tab strip. With nothing toggling a border, the band is a flat
44px on every route.

The detached sidebar's card is a hover affordance, so it leaves when the pointer does:
off the handle and off the card both, on the same debounce a hover menu uses, because
the two do not touch and the leave of one fires before the enter of the other. The
mobile drawer is opened deliberately and still stays until it is dismissed.

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

* feat: one band, one workspace part, and the pickers that hang off it

The band is a single element whose placement is styled rather than branched: a page
registering a side-panel inset used to swap it between two `{#if}` arms, destroying the
header and rebuilding it a frame later — a blink of no header with the content sliding
up into the gap.

The workspace and its fork become one breadcrumb part: the name leads home (inert in a
session, where the workspace is the session's scope rather than a place to go) and one
chevron opens the picker, which already expands a family to reach its forks. The
picker's panel hangs from the part's left edge, opens with the active family expanded,
and closes before the switch runs — the switch renames the workspace, which moves the
trigger the panel hangs from, and floating-ui was chasing it across the viewport.

The item's path is a plain label rather than a row of pickers: same separators and
weight, and a click copies it with its own confirmation. The kind icon goes, the page
below says what kind it is.

Around them: a "prod" badge for a root workspace, the session's acting fork beside the
session's name rather than in the breadcrumb, the edit-in-dev affordance as a button in
the header, the drafts count as a sentence, and a fade when the workspace changes under
a page that stays mounted.

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

* fix: keep a page's own controls when the workspace navigation is hidden

`menuHidden` means "this is an embed, don't show the workspace's navigation" — but
Runs, the detail pages and the flow editor now keep their controls in the band and
nowhere else, so hiding it took the filters, the timeframe and the page's buttons with
it. A session's preview iframe opens exactly that way. The band now renders for an
embed too, without its breadcrumb, so the page keeps its controls and the embed still
gains no way to navigate the host's workspace.

The flow editor's autosave indicator moves into the header's actions with the rest of
its bar: saving state, the autosave toggle and other people's drafts were left behind
in the branch the full-page editor no longer takes.

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

* fix: the header carries what it promised, and nothing it does not

The registry's item described pickers the breadcrumb no longer has: nine fields — the
editability flags, the change callbacks, the navigation hook — that nothing read, while
the flow editor passed eight of them and quietly lost its inline rename. The type now
says what the bar does: a path, and either a summary or the page's own control for one.
The flow editor renames through that control, as the detail page already did.

`barPlacement` went the same way: no page registered 'inline', so the layout's guard
never fired.

The sessions page registered an inset of 0 whenever its preview pane never mounted —
the "no session selected" and opted-out arms — and 0 reads as "a page owns the right
edge", floating the band over the page's first line. It registers an inset only once
the pane has a width.

The band's sidebar handle docks through `setDetached` like the card's own toggle, so
docking still suppresses the rail's entry animation and closes the card; a summary
without its breadcrumb no longer leads with a stray separator in an embed; a page's
hint sits with its name whichever way the page named itself; and `Menu.closeInstantly`
and `EditorHeader`'s `inline`/`hideSummary` go, their callers having been replaced.

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

* feat: a deployed app gets the viewport, and the header waits in the corner

An app is the page, not a page inside Windmill's chrome, so the band leaves the flow
and floats above the viewport until the handle in the app's corner calls it down. The
handle is faded until the pointer nears it, hovering peeks the band over the app, and a
click pins it — one flag for every deployed app, in this browser, because someone who
wants the breadcrumb over a dashboard wants it over the next one too.

Peeking overlays rather than pushes: the app is an iframe, and taking 44px off its box
on every pass of the pointer would re-lay-out whatever it draws. Pinned, the band is in
flow like any other route's and the app starts under it.

The sidebar is untouched — docked stays docked, detached stays a peek — since the pin
is about the header alone. A page says it wants the viewport with one registry field,
so the layout keeps asking the registry rather than knowing about apps.

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

* feat: one corner control for the chrome an app page hides

Hovering the button overlaid on a deployed app brings back whatever that page is
hiding: the band always, and the sidebar card too when the sidebar is detached. The
card hangs from this button rather than from the header's own handle, which is
off-screen with the band.

The handle in the band no longer attaches the sidebar — it reveals it and nothing more.
A control that hides the thing it sits on cannot also be the way to bring it back, so
attaching stays with the toggle in the sidebar's own footer, where detaching happens.

The band's two buttons go in the order the things nest: the sidebar's handle, then the
header's pin. The overlaid button stands where the pin will be once pinned — same 28px
box, and a slot further along when the sidebar's handle takes the first — so pinning
moves nothing. It wears the panel pair the sidebar's toggle uses: solid while the band
is away, dashed while it is shown.

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

* feat: the access point sits in the corner, and the header covers it

The button is in the page's top-left corner — 8px in, or just past the rail when the
sidebar is docked — rather than shifted along to where the header's pin would land.
Hovering brings the header down over it: an access point that the regular view covers
once it arrives, so there is nothing to line up and nothing to move.

Because the header lands on the button, the pointer leaves it the instant it arrives.
The band holds both itself and the sidebar card open while the pointer is anywhere on
it, or the card would retract under a header that stayed.

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

* feat: on an app page the floating button owns both, and they move together

The band drops its sidebar handle there: the floating control in the corner reveals the
sidebar along with the band, and two handles for one gesture is one too many. The two
are then linked — the pointer resting on either holds both, and leaving either collapses
both — so they behave as the single thing the one button revealed.

The card meets the header in a line: it hangs from the header's bottom edge rather than
from whichever control opened it, which had it starting a couple of pixels high. With no
header to sit under — an embed — it still hangs from the control that reached for it.

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

* feat: the revealed sidebar is a panel under the header, not a card over the page

No rounded corners, no inset from the window: it starts at the header's bottom edge and
runs to the left and bottom edges, with one border down its right side and a shadow to
lift it off the page. A card floating in from nowhere read as a separate thing; this
reads as the sidebar the header sits on top of.

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

* fix: only an app page gets the flush panel; elsewhere the card still floats

Over an app the sidebar reads as the panel the header sits on — flush left and bottom,
square, one border down its right side. On every other page it is what it was: a card
inset from the window with rounded corners and a border all round.

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

* fix: an app opens with the header already away

Opening an app showed the header and then slid it out: the page claimed the viewport on
mount, and the band animated from where it had been rendering. The slide belongs to the
hover, not to arriving, so it is armed a frame after the claim — the first hide is a cut.

The claim moves to the route, which mounts before the viewer waiting on the workspace to
resolve. A cold load now paints the band away from the first frame instead of showing a
header that leaves again.

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

* feat: an app's Edit sits with its name

At the far end of the bar it was a journey across the window from the thing it edits —
on a page whose header is only there while the pointer holds it, a journey with a time
limit. It goes beside the app's path instead.

The slot it uses is the one Runs' documentation tooltip already had, renamed from `hint`
to `afterName` and documented for both: what belongs to the page's name, whether that is
a mark or a control that acts on what the name points at.

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

* feat: developers collapse, operators detach

One control each, rather than three for everyone. A developer switches the sidebar
between icon-only and full width and never detaches it; an operator, who has no
icon-only rail to collapse to, hides it instead — by the button in its footer or by the
same drag that collapses it for a developer.

The way back survives either way: a detached sidebar keeps its "Show sidebar" button for
anyone, so a developer who detached before this was so is not left with a sidebar they
cannot return.

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

* fix: a page is as tall as it is given, not as tall as the screen

The band takes 44px off the top of every route, so a page sized to the
viewport overflows by exactly that much: h-screen inside a column that
already lost the band's height. These pages measure the box they are in
instead.

CenteredModal gains `inPage` for the five pages that render it inside the
layout rather than over it — the same modal, centred in what is left of
the page rather than in the window.

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

* feat: the band narrows, wraps, and draws its own edge

The band publishes its width, so the pages filling it can answer it. Two
tiers: under 1000px the breadcrumb keeps the workspace disc and its env
badge and drops the name, and under PHONE_BAR (800) the trail stops
yielding at 10rem — that floor is what makes the bar wrap, so a page's
buttons take the line below rather than squeezing the name to an ellipsis.

The band also draws the line between itself and the page, and only while
something is scrolled under it. One capture listener on the window sees
every scroller, since most pages scroll inside their own box and never
move the layout's. An inset shadow, not a border: a border would move
every page down a pixel the moment the line appeared.

DetailPageLayout and InWorkspaceAppViewer drew that first line themselves;
two lines would stack, so theirs go.

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

* feat: the script, app, runs and audit pages fill the band

Each of these kept a header of its own above the page. They register with
the band instead: the breadcrumb names what the page is on, and the page's
buttons sit at the far end of the one row.

They answer the band's width rather than the window's, since the buttons
share that row with the breadcrumb. The script editor folds Diff and
Settings into the menu it already shows when compact, the app editor drops
the panel buttons, Awareness and Export and goes icon-only on Deploy and
the mode switch, the flow editor menus its diff, and runs hides its
counters. Runs also declares `actionsFlexible`: its search is the one
control here that can give width back, and a box that refused to shrink
would make it take its maximum instead.

The run detail page loses its ActionRow, and the audit log its big title —
the breadcrumb says where you are, and the page keeps its tooltip.

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

* feat: one sidebar column, shown as a rail or as a panel

The rail and the drawer held two copies of the same column — the same
sections, in the same order, in two pieces of markup, so a change to one
silently skipped the other. Both now render `sidebarColumn`, and the
shells keep only what differs: the rail its width, resize handle and
collapse button, the panel its outside-click catcher and its transform.

A phone gets that panel rather than a modal. There was a width test
deciding between two presentations; the card under the band's handle works
at any width, so it goes. What is left of the width test is the choice
between a rail and a panel, plus a `(hover: hover)` test — the old
`innerWidth >= 768` was standing in for "has a pointer", which is what
decides whether the panel closes when the pointer leaves it.

With the card everywhere, the backdrop, the `aria-modal` (nothing dims and
nothing traps focus) and the drawer's close cross all go: the band's
handle opens the panel and a tap outside closes it. The burger row goes
with them. Entering panel width now collapses the rail transiently, so
coming back to a wide window does not land on a rail the page was resized
away from.

The band also stops easing its right edge. That transition belongs to the
rail, which moves the band's left; its right follows a splitter the page
owns, and easing it left the band lying over a session's side panel — a
blank strip across that panel's own header for 200ms after every drag.

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

* feat: the home composer flies to the menu that restores it

Removing the composer puts it away rather than throws it out, and the way
back is one item in the band's menu — easy to miss on a page the composer
has just vacated. A mark flies from the control that removed it to that
menu, and the menu answers with one ring as the mark dissolves, so where
it went is witnessed rather than explained.

The cross and the menu item now read "Remove session chat", one name for
one thing whichever a reader reaches for.

Reduced motion skips both.

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

* revert: keep operators out of AI sessions

That change is its own PR; this one is about the navigation. `prefersSessionHandoff`
takes the role again and every entry point passes it, the sessions page refuses an
operator arriving by URL, and the layout leaves them on the docked chat.

Two clauses are new rather than restored, because the nav rework is what gives an
operator a sidebar at all: the Workspace/AI Sessions switch and session mode are both
hidden from them, since the page behind them declines. `askAiShown` gains the operator
case for the same reason — they keep the docked pane whatever the beta gate says, so
the sidebar has to offer a way into it.

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

* fix: keep every control the band took over reachable

Four controls the page-header conversion could put out of reach, each found by a
review pass and reproduced in the browser:

- The flow editor's preview buttons. `topbarWidth` is bound only in the branch a
  full-page editor does not render, so `compactTopbar` was permanently false
  there; below the 800px phone bar `phoneTopbar` dropped the buttons inline and
  the graph overlay never took them, leaving FlowPreviewButtons mounted nowhere
  and step testing, whole-flow testing and "Test flow & record" no-ops through
  optional calls. It now measures the header the way ScriptBuilder and
  AppEditorHeader do, and the inline and overlay conditions are exact
  complements.
- The pin on a deployed app. The band lands over the corner handle that called
  it down, so the pointer that arrives there hits the band. The band carries the
  pin itself now, on the same pixels, in place of the sidebar handle it hides.
- The detached sidebar's own menus. They portal to `body`, so moving into one
  left the card and closed it 200ms later, sliding it out from under the menu
  being read. The card holds while the pointer is in a menu and takes the close
  from the leave of that.
- A deployed app's name for a read-only viewer. The breadcrumb was registered
  behind write access, so without it the trail fell back to the section name
  "Apps"; and the Edit button rode `afterName`, which the band drops for an
  embed along with the trail. The name is registered for whoever opened the app,
  and what sits after it survives `navHidden` as the page's own control.

navPageFor gets a table test: it answers both the sidebar links and the
breadcrumb label, and its longest-prefix and first-segment rules are what made
the "Apps" fallback look like a correct answer.

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

* docs: drop what the operator revert and this refactor left behind

The operator tour's last step named AI sessions among the pages the sidebar
opens, which operators have no way to reach. The nav handle's comment named the
AI chat layout's menu button as a second way into the card; that button and its
prop are gone, and the corner control on a page that hides the band is what
stands in for the handle now.

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

* style: prettier the three files this branch left unformatted

`prettier --check` fails on all three at the previous head. Whitespace only,
apart from one Button tag that fits on a line.

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

* fix: give a page without the band's chrome what the band was carrying

Second review round, three reachable defects plus one decision:

- A public raw app was 150px tall. The bundle's iframe is `h-full`, and dropping
  its `min-h-screen` left nothing to resolve against on the share, embed and
  custom-path routes — none of which has a layout above them. Measured: 150px in
  a 900px viewport. The floor goes back for those routes and stays off inside the
  workspace, where the layout hands a height down and `min-h-screen` would
  overflow it by the band.
- The band retracted out from under its own open menu on a deployed app. Last
  round taught the sidebar card to hold while the pointer is in a portaled menu
  and left its sibling alone, but the band opens menus too — the workspace disc,
  the fork family, every path level. One helper now, used by both closers.
- `bandPeeked` outlived the page it belonged to. Clicking a control in the band
  navigates away, and the next page may not be one the band floats over, so the
  hover handlers that would clear it are gone — leaving the band already down
  when a full-bleed page came back. `beforeNavigate` clears it.
- An embed has no band to carry a control, so the app's Edit floats over the
  canvas there, where it sat before the band existed. Registering it as the
  band's `afterName` under `navHidden` instead would have cost the app 44px of
  the iframe for a row holding one button.

Reading "is the workspace navigation hidden" now has one definition
(`isMenuHidden`), since a page has to answer it too: the flag is sticky inside an
iframe, so the raw query param is the wrong thing to read.

Also inset the band by the rail only when there is a rail — an embed reserved
its width and crowded its own controls into a compact layout early.

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

* fix: a path that names nothing yet has no breadcrumb leaf

`splitItemPath` documents that it gives up on a path still being typed, but
`f/demo/` is three parts and returned a leaf with an empty name — a blank
segment in the trail. The editor binds the path live, so the breadcrumb sees
every keystroke.

Tested here rather than `navPageFor`'s sort: no two `NAV_PAGES` paths nest, so
the longest-prefix case that test claimed to pin has only one candidate. Renamed
it to what it actually checks.

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

* style: prettier the four remaining files this branch left unformatted

All four fail `prettier --check` on the branch and pass at the merge base.
`raw_apps/utils.ts` also fails but already did at the base, so it is left alone.

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

* feat: the raw app editor fills the band like the other three

It was the one editor route still rendering its own bar under the band, so it
showed two header rows and the band named the app "Apps raw" — `navPageFor`'s
first-segment fallback, because nothing on the route registered an item. Its top
bar now becomes the page header the way the script, flow and low-code app
editors' do: the path and summary are the breadcrumb's, the file-sidebar toggle,
autosave cloud, presence mark, menu, Diff, Jobs, export, AI and Deploy are the
header's actions, and the path is a plain label since this editor renames from
its deploy drawer.

The actions render under the band, outside the subtree `RawAppEditor` declares
the operating workspace in, and `OpenInSessionButton` asks that context who the
acting user is — so the workspace travels with them.
`operatingWorkspaceContextEntry()` hands it over without exporting the key.

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

* fix: keep the folders a half-typed path already names

The trailing-separator guard gave up on `f/demo/` entirely, which dropped the
whole trail to the kind chip and brought `f/demo` back on the first character of
the name. The levels the path does name come back now; only the leaf waits.

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

* fix: pin holds the first slot, and the nav table says what it shares

Pinning brings the sidebar's handle back, and from behind it the unpin button sat
one slot over from where the pin had just been clicked — so a second click in the
same place opened the sidebar instead. An operator is detached by default, which
makes that the default state of the feature rather than an edge case.

`NAV_PAGES` claimed one table stops a page being called one thing in the nav and
another in the header, which holds for the seven entries the sidebar's main group
is built from and not for the four grouped separately — `/routes` was "Triggers"
in the header and "HTTP" in the nav. The comment now says which is which, and
that entry is named for the page rather than its group.

`sidebarPageAllowed` gets a table test: it decides what an operator sees, and its
`=== true` is what a future `Option`-style default would silently change.

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

* fix: the band names the path Deploy will create, and keeps the summary readable

A brand-new app is parked at a `draft_<uuid>` placeholder, and the band named
that rather than the path its Deploy will create — `newEditedPath` refuses the
placeholder for exactly this reason, and the editor's own bar read it. Both app
editors register it now. Verified: the trail reads `u/admin/gentle_app` while the
URL is still the draft.

The band's summary gets the `title` the editor's own bar had, so one the bar
truncates can still be read on hover.

The wrapper that hid AppExportButton on a phone never hid anything:
the component draws no trigger — it is the export drawer, which portals to
`body` — so the class toggle was inert in both states and the comment read as if
something moved. Dropped from both app editors.

Two comments corrected: `NAV_PAGES`' `icon` does reach the breadcrumb for all
eleven entries (`NavBreadcrumb` renders `routePage.icon`), so only `operatorKey`
goes unread for the four the sidebar groups separately; and the raw editor's
phone note described folding that does not happen — the file-sidebar toggle stays
in the band, Jobs and the export are already in the menu, and AI is what stands
down.

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

* fix: an item the picker files under no kind keeps its name in the band

The merge brought main's agents detail page, which reaches DetailPageHeader with
no `errorHandlerKind` — and the band's item required one. `WorkspaceItemKindIcon`
falls through to the script glyph for anything it does not know, so defaulting
would have labelled an agent a script; the kind is optional now and the trail
names the item without claiming it is one of the three.

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

* fix: the file sidebar's toggle leaves the band

The band is the workspace's header, not the raw editor's layout controls. ⌘B
still folds the file sidebar, and the editor's own bar keeps the button where it
draws one — a session pane.

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

* fix: the scroll edge stops measuring, and the comments describe the code

The band's edge listener captures every scroller in the app — Monaco, the audit
table, a run's logs — about once a frame while any of them moves, and it took two
`getBoundingClientRect()` reads before looking at `scrollTop`. It now answers from
`scrollTop` first and only measures when that answer differs from what the band
draws, which is the transition rather than the steady state. The rect check still
authorizes every change, so a scroller that does not start at the content's top
still cannot claim the edge.

The two agent routes main just added size themselves to the viewport, which is
44px more than the box the band leaves them. They measure 856 either way — the
layout's `min-h-0` column clamps them — so this is the sweep the rest of the
branch already had, making the height follow the box rather than depend on a
parent's clamp.

Six comments described the conversion rather than the code, which AGENTS.md calls
out by name. Each now states the constraint a reader could break: why the summary
keeps its popover here, why the audit hint is `afterName` and not an action, why a
run registers a `section` rather than an item, why the Runs filters are
`actionsFlexible`.

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

* Revert "fix: the file sidebar's toggle leaves the band"

This reverts commit 1da8d16740.

A folded file sidebar renders nothing of its own, so the toggle on the bar is the
only way back short of knowing ⌘B.

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

* feat: the editors name their item in the band, and a pen renames it

The band showed each editor's summary as an inline field, which put editing
where the page names what it is working on. The summary is text there now, with
its `title` for one the band truncates, and the pen that opens the path for
editing sits after it — the same popover the editors' own bar carries, at the end
of the name it renames. Every editor already has a field for the summary itself:
the script's settings, the flow's Settings tab, and both app editors' deploy
drawer.

The popover moves into `PathEditPopover` rather than being written out four more
times. It owns the open state and hands back the path as it was when it opened:
the band's trail reads that while the popover is open, so a path growing under
the pointer does not drag the pen — and floating-ui the popover — along with it.
`EditorHeader` renders the same component, so the session pane and the band
cannot drift apart.

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

* feat: the pen renames the item and its summary

The pen opened the path alone, which left the summary with nowhere to be edited
from the band. It carries a Summary field above the path now — the item's name
first, the address it lives at below — and the band's text follows it as the user
types.

`TextInput` with a label rather than the bar's inline field: `EditableInput`'s own
docs rule itself out inside a popover, and it wraps this same input in its edit
mode, so the field, the "Add a summary..." placeholder and the trim on commit are
the behaviour that was there, in the shape the deploy drawer's Summary field
already uses. The keydown stops at the field because every editor binds keys of
its own and a summary is prose.

`summaryEditable` carries the hosts' `customUi.topBar.editableSummary` through,
and the editors' own bar passes it false: that bar keeps its inline field, and
`hidePath` would take the pen with it, leaving a condensed session pane no way to
name the item at all.

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

* feat: the agent editor fills the band too

It was the last editor route drawing its own header row under the band, and the
band named it "Agents" — `navPageFor`'s first-segment fallback, because nothing
on the route registered an item. The agent's path is the breadcrumb now, its
version badge reads after that name, the sentence that was the row's second line
became the hint beside it, and the Chat/Form switch, Settings, Evals, version
history and Deploy are the band's actions.

No trail levels in the registration: this layout opens settings in a drawer and
evals over the page, so unlike the dialog it never stands on a level — `trail` is
its root alone here.

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

* feat: the pipeline page fills the band

Found by sweeping every route under the band: the pipeline editor drew its own
row beneath it, 48px of a second header, while the band named only the section.
Its name is the band's now, the folder it is open on sits with that name — the
switcher is how you change pipelines, so it belongs to the name rather than to
the actions — and the mode switch, deploy-all, activity, refresh and overflow
menu are the bar's actions. The arrow back to the list stays: nothing else on the
page points there, and the sidebar does not list pipelines.

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

* fix: the app canvas controls stay reachable, and an unnamed item says so

Codex found the app editor's canvas controls unreachable on an ordinary laptop:
the owning route collapsed at 1350px, and a 1440 window with the rail out leaves
1232, so the width, theme and breakpoint toggles were hidden with nothing in the
menu to replace them — and the breakpoint is the only way onto the mobile layout.
Measured on the band, the toggles are 240px beside 334 of other actions and a
~430 trail, so they fit to about 1050: the threshold is 1100 now, and below it
each of the three keeps a menu entry rather than disappearing.

An item with no summary read as a bare `·` with an invisible pen after it, since
the plain text replaced a field that had a placeholder. It now reads "Add a
summary..." in the italic SummaryPathDisplay uses for the same state, and the pen
stops hiding when there is nothing to hover.

Three more from the review:
- `actionsFlexible` says "these actions can give width back", which is true of the
  Runs filter row and not of the pipeline's row of fixed buttons.
- `pathSnapshot ?? (newEditedPath || …)` keeps the `||` chain the five rename
  sites read and the declaration documents; `??` had made the rest unreachable,
  since `newEditedPath` is a string that is never undefined.
- The band bridged `contexts` onto the actions alone, while the pen that decides
  whether a path is the caller's to change renders in `summaryContent`. The
  breadcrumb's `afterName` and the summary are bridged the same way now, so that
  permission check reads the tree that registered it.

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

* fix: the widest action rows scroll, and the canvas toggles show only where they fit

Measuring the band instead of estimating it: with the canvas toggles shown the
app editor's actions are 826-911px and the trail with the summary another ~500,
so the row overflowed the bar at every width up to about 1470 — the 1100 I set
from an estimate was wrong, and the 1350 before it was too. The toggles come out
at 1500 now, and the menu entries are what keeps all three reachable below that
rather than merely out of the way.

The two widest rows — this editor's and the pipeline's — scroll inside their own
box, which is the escape the rows they replaced had (`overflow-x-auto
whitespace-nowrap`). That answers pi's finding and clears the last 23-40px of
band overflow at middling widths, which predated the conversion. Measured after:
zero horizontal overflow from 852px of bar to 1392.

From the CI round's other findings:
- The pen is offered when either the summary or the path is editable, not the
  path alone: a host that allowed one and refused the other could no longer edit
  the summary from the band. `PathEditPopover` takes `pathEditable` to match, and
  its label names whichever it is given.
- The four editors' comments called the summary read-only, which stopped being
  true when the pen beside it started editing it.
- The pipeline's "centre group" comment described a layout it no longer has.

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

* docs: actionsFlexible covers both ways a row gives width back

It read as if a search field were the only way to earn it, which stopped being
true when the app and pipeline editors' rows started scrolling inside their own
box. Both are the same bargain — the box may shrink because its contents can —
and the flag is what the bar reads to allow it.

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

* fix: the app viewer names the page it owns, and stops claiming the viewport

A deployed app opened in an AI session's preview panel renamed the band from
the session to the app, and put the app's Edit beside the session's name. The
viewer registered wherever it was mounted; it takes `ownsPageHeader` now, the
way the detail pages already did, and keeps Edit over its own canvas otherwise.

Both viewers also claimed the whole viewport under a band that takes 44px of
it, so a deployed app scrolled by exactly the height of its own header (the
low-code one measured 900px in an 856px box). The height comes from the host
now: the three standalone routes floor themselves at the viewport and
everything inside them is `h-full`. A raw app reached through `/app_embed` had
no height above it at all and collapsed to nothing; it fills the frame now,
though that route still fetches no bundle secret, so what it shows is blank —
as it is on main.

The agent editor's pen was a second editor of the same path that reported
nothing: an invalid path never reached the deploy gate the settings drawer's
field reports to. It binds to the same slot now. Two things that surfaced
with it: a rename never reached the trail, which read the deployed path; and
typing the saved path back raised "path already used", since the popover's
`initialPath` moves with the rename — `allowedExistingPath` is what says the
item already occupies it.

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

* refactor: the band's controls come from the design system, and the row has one copy

`NavBreadcrumbTrigger` was `MeltButton` written again — same `use:melt` on a
plain button, same spread — so the workspace chevron uses the component that
already exists, and the sidebar handle is a `Button`.

`DetailPageHeader` carried its action row twice, once for the band and once
for the row it draws inside a session's preview panel, and the two had already
drifted: different dropdown size, no `unifiedSize` on the buttons, and the
deprecated `variant="contained" color="light" size="xs"` on the schedule
button. Both placements render the same two snippets now, at the size each
needs.

`OperatorPageKey` admitted every `operator_settings` field, including the three
write rights, which `sidebarPageAllowed`'s `=== true` rule answers wrongly: a
write right is granted unless withdrawn, so its absence means "never
configured". It is the ten visibility flags now.

A test over the registry's merge: later registration wins field by field,
actions sort by `actionsOrder` then registration, release gives its fields
back. That rule is why a component registering where it is not the page
renames someone else's band.

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

* feat: the band owns its bottom edge, and one pen renames everywhere

The page header decides whether a separator is drawn, so a page cannot draw a
second line under it. A registration asks for `onScroll` (the default, drawn
only while something passes under the bar), `always` for a page whose content
starts right beneath it, or `none` where the page genuinely draws its own first
line. Detail pages, the agent editor, workspace settings and the pipeline list
take `always`; the raw app editor takes `none`, since its splitpanes already
draw that line.

The pen that opens the rename popover becomes PathEditPen, shared by the
editors and the deployed pages so the two cannot drift, and the deployed pages
gain it. SummaryPathDisplay makes the pen the only trigger rather than the whole
name block, so the name is selectable text again. Renaming an agent goes through
ResourceService with only the fields the popover edits: the backend sets a
column per field it is given, so writing the config back would clobber a
concurrent edit. The popover also stops clearing a real collision verdict by
telling Path which path is its own.

Two Button fixes, both reaching call sites beyond this work:

- `dropdownItems` given as a function was asked for `.length`, which is its
  arity and therefore 0, so a split button built that way rendered no caret.
  Both app editors' Deploy dropdowns were unreachable.
- the anchor branch dropped `title`, so a link button had no tooltip.

The audit log filters move into the header actions at the design system's sizes
and trade absolutely positioned labels for the form layout in
brand-guidelines.md: a label above its input, 4px apart, fields 8px apart.

The deployed app viewer floors itself at `min-h-full` instead of claiming the
viewport, which would overhang the page box by the height of the band, and
carries Edit and a public-url link that fold into a menu on a narrow bar. The
root layout drops the legacy menu block, which rendered a second hidden sidebar
and 54 invisible tab stops, and marks the closed detached sidebar `inert`.

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

* feat: service logs and the audit filters move into the band

Service logs: the page's name is the breadcrumb's, so the `<h1>` is gone and the
search field, the timeframe and the two switches sit in the band beside it, with
the documentation tooltip after the name. ServiceLogsInner registers the band
itself, the way RunsPage does, since that route is its only caller, and the
`title` snippet the page used to hand it goes with the heading.

The search is the widest control there, because searching is what the page is
for, and it is the one that gives width back under pressure. The two switches
fold into a popover below a 1200px bar rather than at a phone's width, so the
field keeps its full width down to a much narrower window — folded rather than
dropped, because the page no longer carries a toolbar they could fall back to.

The timeframe control took no size of its own, so it defaulted to `md` beside a
`sm` field, and sat in a `w-48` box 20px narrower than its content, whose
overflow is visible and so spilled into its neighbours.

No `separator`: the two panes below start under the bar with borders of their
own, so at rest there is nothing for the band to divide.

Audit logs: the filters lose their labels. Each select says what it filters
while unset — "all usernames", "all resources" — and once set the value names
the field by itself. From and To become the TimeframeSelect the runs page uses,
which absorbs the standalone Refresh button, since that control's left half is
the refresh. `auditTimeframes` joins the runs and service-logs presets, with
"All time" first. The bounds stay the filters' own `after`/`before` rather than a
remembered preset label: the timeline's zoom writes them too, and a label could
then contradict the chart.

That takes the row from 1326px to 898px, so it stays inline down to a ~1500px
viewport instead of ~2500. The fold measures the row against the bar less what
the breadcrumb takes, plus a gap the two must never close — the breadcrumb
yields width grudgingly and the row not at all, so sized to just meet they
overlap rather than compress, and mid-resize the tooltip after the page's name
lands on the timeframe button.

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

* fix: an embedded page keeps its name in the band

A page in a session's preview panel loads in a frame that sets `nomenubar`, so the
layout renders the band with `navHidden`. That flag hid the whole breadcrumb —
which is what carries the page's name — while the actions beside it had no such
guard, leaving a row of controls belonging to nothing.

Hiding the trail is still right: it leads to the host's workspace and offers a
picker that would take the reader out of the session. So the breadcrumb gains a
`nameOnly` mode that drops the workspace part and its picker and keeps the page's
own name, and the band asks for it instead of rendering nothing. The summary line
loses the same guard, so an item in there shows its path and summary.

Only the pages whose own title the band took over are affected. Variables,
Resources, Schedules and Triggers still draw their own `PageHeader` and render no
band when embedded, so nothing is doubled.

The bar's left padding is there to sit a button against — the sidebar handle, or
the workspace disc, both of which carry their own margin. Embedded there is
neither, and the name started as bare text 8px from the edge, so that case takes
the same 16px the bar already keeps on its right.

Workspace settings drops the copy-id button from the band: the General tab names
the workspace ID with a field of its own, which is where someone looks for it.

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

* feat: a session in full screen keeps its tabs in the band

Full screen used to drop the session's name from the breadcrumb and leave the
tab strip inside the panel. The band now carries both: the session's name where
"Sessions" stood, and from the breadcrumb to the right edge one filled surface
holding the tabs and the preview's controls, the bar's full height.

The switch between the two layouts is the panel expanding to the left. The chat
column is resized to nothing rather than unmounted, so it can be seen taking its
place -- and so a toggle no longer tears down every mounted chat and rebuilds it
on the way back. Panes transition their width only while that switch is in
flight, since a splitter drag sets the same inline width and would trail the
pointer.

The band floats over the content in full screen as it already did beside a
panel, so only its right edge moves and the page below never shifts by the
bar's height. Leaving full screen, the panel travels back across ground the
band has already claimed, so it passes one layer above it for the length of the
move: pinned to the width the panel will end at, the bar would otherwise show
through as a white patch over the panel's left edge.

The breadcrumb also gains the acting fork, the way every other page shows it,
which replaces the "Acting on" control the session header carried.

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

* fix: the band's inset survives a resize made in full screen

The panel was remembered as a width, sampled while it stood still. A window
resized during full screen left that width describing a layout that no longer
existed, and the way out pinned the band to it for the length of the move. It
is now remembered as the share of the row the panel takes, which the new row
width turns back into the right inset: leaving full screen at 1200px after
entering it at 1600px, the band starts at 628 rather than at 909.

The pane transition's duration is read from the property the timer is set from,
so the class cannot outlive the movement it describes, and two comments that
described earlier drafts are rewritten: the band's name in full screen comes
from the session's own header once it has mounted, and the tab strip's wrapper
carries a fade, not a measured move.

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

* fix: an agent's pen keeps the labels the agent already carries

The pen on an agent's page was handed the summary and the path but not the
labels, so the popover offered an empty list over a resource that had some. It
saves every field it shows in one write, and the resource endpoint sets the
column it is given, so adding a label there replaced the agent's own: an agent
labelled `production` and `scheduled` came back from one edit labelled only with
the label just added. A flow's page and a script's page both pass theirs; this
one now does too.

Verified against the running instance: an agent created with
`["production","scheduled"]`, one label added through the pen, reads back
`["production","scheduled","new"]` — and `["new"]` with the prop removed again.

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

* fix: the sidebar remembers detaching per role, not per account

Whether the sidebar starts detached depends on the role: an operator gets it out
of the way, a developer gets it docked. That default held only until the control
was pressed once, because the choice went to a single key read before the role.
From then on both roles wore whichever was pressed last, and the one that had
not asked for it could never get its own default back — switch a workspace where
you are an operator for one where you are a developer, and the sidebar stayed
detached.

Each role now keeps its own choice, and falls back to its own default until it
has one.

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

* docs: the handle's comment describes what the handle does

It claimed a click holds the sidebar card open. Nothing distinguishes a click
from a hover there — both call the same opener, and the pointer leaving
schedules the close either way — so on a desktop the card follows the pointer
and goes when it goes. Measured: hovering slides the rail from -213 to 5, the
click moves nothing, and the pointer leaving puts it back at -213.

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

* feat: a dev workspace's badge wears its fork's own colour

Every surface that names a dev workspace drew the same blue badge, which said
nothing about which workspace it belonged to. One component, DevWorkspaceBadge,
now draws it: a fork with a colour fills the badge with that hue and writes the
label in white, and a fork without one keeps whatever its row had before.

The hue cannot carry white at a fixed lightness — HSL lightness is not perceived
brightness, so yellow and purple at the same L differ by several stops — so
forkAccentStyle solves the badge's lightness by bisection instead, taking the
lightest shade of the hue that still holds white at WCAG AA. Measured across the
wheel the contrast lands between 4.52 and 4.64.

The session picker opens with every family collapsed, since the fork a session
runs in is the session's own and not something that picker should offer to
change.

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

* feat: the name is what opens its rename editor, and the trail truncates from its start

The pen beside a name is gone. The name itself is the trigger, with a hover fill
behind it where the click opens an editor rather than going anywhere — the path
segment that only copies keeps no fill, which is what tells the two apart. A bar
with a summary field of its own (EditorHeader's condensed pane) still gets the
pen, since it has no name to hang the trigger on.

Clicking either half of a name opens the editor under that half with the cursor
in the matching field. The band draws the path and the page draws the summary, so
one popover cannot anchor under both: a page hands the band a `pathTrigger`
snippet and the band renders its path segment inside it, passing the classes its
own segment wears. Each editor supplies a second PathEditPopover for it, and
`focusField` names the field to focus — `#path` explicitly, since melt otherwise
lands on the row's first button.

Those snippets render from the band's tree, which outlives the values they read
by a flush, so each is gated on the value it reads. Without that an agent page
whose draft had not loaded threw inside the band and left the breadcrumb empty.

The trail now carries two weights: the segment that ends it is what the page is
about and takes `font-medium text-emphasis`, everything before it is a step
quieter. Which segment that is follows the route — the workspace on home (the
fork alone inside a fork), a list page's own name, an item's summary.

And it truncates instead of switching to icons at a breakpoint. The workspace
part yields first, the family name before the fork, then the path's folders, and
the item's own name last. The bar no longer wraps on a phone: a wrapping row
never squeezes anything, it hands the trail a line of its own and leaves it
whole.

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

* fix: a page keeps its path segment when it cannot offer a rename

Four things the local reviews caught in the rename work:

A detail page handed the band a `pathTrigger` whether or not it could save one.
Without `onSaved` — every script, flow or agent page the reader cannot write —
`SummaryPathDisplay` fell through to its read-only branch, which ignores the
`label` it was given and draws its own block: the trail lost its path and the
summary appeared twice, two lines deep, in a 44px bar. The page now offers the
trigger only where a rename can be saved, and the component renders what it was
handed rather than its own row.

The band gives up its own segment the moment a page offers a `pathTrigger`, so a
trigger whose own gate is closed left the trail with no path at all — for the
whole of an agent draft's load, not just a flush. Each gated snippet now draws
the path itself in that case.

`openFocus` named the path field as `#path`, which melt resolves with a
document-wide query; `Path` hardcodes that id at every call site, and the
popover is portalled to the body, so it lost document order to any other `Path`
on the page. It is scoped to the popover's own field now.

And `open` was an exported `$bindable(false)` on an optional prop, which the
frontend's own rules ban. No caller bound it, so it is local state.

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

* fix: the badge solves its lightness against the colour the browser paints

Three nits from the review round.

The contrast bisection measured the hue at full float precision, but a browser
paints 8-bit channels: rounding them moved the result up to 0.025 off the target,
which took #993388 to 4.478 against white — under the AA floor the comment
claims. Quantizing the channels inside `paintedLuminance` puts the worst case
across 2880 hue and saturation combinations at 4.500, and your own fork's badge
measures 4.53 as painted.

An agent nobody can write still offered the rename behind its path segment: an
editor with two read-only fields, in place of the click that copies the path.
It is gated on `readOnly` now, the way a detail page is gated on `onSaved`. The
script and flow editors take the same gate from the `customUi` flags that
already decide whether their summary offers one — unreachable today, since the
editors own the band only on routes that pass no `customUi`, but the four
editors now answer the question the same way.

And the registry's doc named an `onEditName` field that has never existed.

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

* fix: an operator is not offered a fork of a workspace

A fork does not carry the role. The server writes the forker's membership row in
the new workspace from the parent's `is_admin` and lets `operator` fall to its
`false` default, so an operator who forks lands in a full-rights copy of the
workspace they could only run things in.

`canCreateFork` refuses an operator, which takes the entry out of the workspace
picker, the session family picker and the session changes bar;
`editInForkAllowed` refuses one too, so no editor offers "Edit in fork". The
fork page refuses on its own, since a URL reaches it whatever the pickers show.
And a workspace the user is an operator in is no longer a base the fork form
will accept, so the base picker cannot route around the gate.

This hides the affordance; it does not enforce the rule. `create_fork` asks only
for membership — `require_admin` covers the dev-workspace case alone — so the
API still answers an operator who calls it directly. The server-side fix is to
carry `operator` into that membership row, the way the member-copy path already
does, and it belongs in a backend change rather than here.

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

* style: the fork refusal uses the design system's alert

The refusal panel was hand-built from a div and two paragraphs with literal
colors, copied from the Runs page's own operator refusal. The frontend's rules
say to use the design-system component, and `Alert` already draws exactly this.

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

* fix: frontend type errors failing npm_check

The dev workspace badge accepts the nullable label the API returns, the
session strip transitions take the node Svelte passes them, and an unused
import is dropped.

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

* fix: the frontend type-checks again

`check frontend build` has been red on every head for a day, through four review
rounds that each came back clean — none of the reviewers type-check, and they
say so in their own coverage sections. Ten errors:

The dev badge's `label` and `color` were typed `string | undefined`, but the
workspace rows carry `string | null`, so all seven of its call sites failed.
`forkAccentStyle` took the same widening.

A Svelte transition is called with the node it animates, and `stripArrive` and
`stripLeave` were declared with no parameters at all.

And `DevWorkspaceSetting` still imported `Badge` after its badge moved into the
shared component.

`svelte-check` now reports 0 errors. Note that it only agrees with CI once
`src/lib/gen` has been regenerated — the gitignored client goes stale against
main and invents errors CI never sees.

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

* feat: the home bar carries the CLI and hub buttons while it has the room

A menu is where a thing goes when the bar cannot hold it, not where it belongs.
Above 1024px of bar, "CLI / MCP" and "Hub" stand as their own buttons; below it
they fold back into the ellipsis, which is still where they live on a phone.

The threshold reads `pageHeader.barWidth`, not the viewport, so it tracks what
the sidebar actually leaves behind. Home's trail is ~250px and its other
registered actions ~400, so the two labelled buttons fit from about 850 up;
1024 leaves margin before the trail would start truncating, and it does not
truncate at any width between 760 and 1600.

The ellipsis hides itself when it would open an empty menu, which happens only
once both side trips are in the bar and the composer is already on the page.

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

* feat: a fork's band counts its drafts beside its drift, and every count opens what it names

A fork that had drifted stopped saying anything about its drafts: the banner
printed the draft count only while the fork was in sync, and the drift badges
replaced it the moment there was anything to deploy. A fork can be 86 ahead, 39
behind and holding 128 drafts; it now says so.

All four counts open the page that holds them — ahead the deploy direction,
behind `?dir=update`, conflicts the side they are resolved from before
deploying, drafts the drafts half. The button beside them still chooses a
direction on the reader's behalf; a badge means the one thing it names.

The colours and the icons are the compare page's, so the counts in the bar and
the rows behind them say the same thing the same way: ahead green, behind blue,
conflicts orange. Drafts share blue with behind and are told apart by a pencil
and by naming what they count, rather than by an arrow that would read as a
third direction. Both arrows are straight, here and on the compare page.

"Review changes" replaces "Review & Deploy Changes", and it renders in every
state. What the comparison has to say — still counting, or failed — is a mark
beside the button rather than a branch that replaces it: a fork whose count is
unfinished is exactly when a reader wants the page that counts properly.

The drafts banner outside a fork drops "This workspace has", so the count reads
the same wherever it appears.

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

* feat: the home band keeps its hero's own cross, and the side trips stay quiet

The CLI and hub buttons take the shape the hero gave them before the bar did —
`xs`, hint-coloured, the hub keeping its outbound mark — so a side trip does not
read as loudly as the thing the page is for.

The ellipsis is gone while the hero is on the page: the hero's cross already
puts it away, so a menu whose only item repeats that is a button that does
nothing new. It comes back when it has something of its own to carry, which is
the side trips on a narrow bar and the way back to a hero that has been put
away. That entry is now "Pin session hero to homepage", and "Remove session
chat" survives only as the cross's own words, where one action keeps one name.

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

* feat: a fork's counts and the way to review them are one control

Four badges beside a button read as four things that happen to sit together.
They are one thing — every segment opens the same page on the part it names — so
they now share a box: one outline, square corners inside it, and the rounding on
the box alone, which means no segment has to know where in the row it sits.

The only rule in the strip is the one before the end cap: everything left of it
is a reading, that is an action.

With room every count says what it counts; below 1280px of bar they keep their
icon and their number, which is the part a reader scans for, and the tooltip
names them either way. The end cap keeps its words at every width — the counts
have icons to fall back on and it has none.

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

* fix: the compare page asks once, says it is working, and knows its own name

The page re-requested the same summaries and on-behalf-of emails forever. Two
faults compounding:

`fetchOnBehalfOfInfo` skipped a key it had already answered with
`onBehalfOfInfo[k] !== undefined`, but stored `undefined` on failure — so
"asked, and there was nothing" could not be told from "never asked". An item
that is only ahead does not exist in the parent, so that side always fails:
a permanently un-cacheable set, re-requested on every pass.

And the effect that drives both fetchers depended on what they write. Each reads
its cache synchronously before its first `await`, which registers that cache as
a dependency; the write that follows re-runs the effect. Write, re-run, write.

Both now record what they asked for in a plain Set, marked before the request
rather than after the answer, and the effect is untracked so it depends on the
comparison alone. The caches are cleared when the compared pair changes, since a
summary's key carries no workspace and those Sets would otherwise never ask
again.

While the comparison was in flight the page printed "No changes between this
workspace and X" — an empty list and an unanswered one look alike from the
layout, and that is a claim with no answer behind it. The page tracks the
request now, and the layout spins instead.

The breadcrumb called the page "Forks", which is what `navPageFor` falls back to
when a route names itself nowhere: the first segment, capitalised. It registers
"Compare" instead — in the page rather than in NAV_PAGES, which is what the
sidebar renders from.

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

* Revert "feat: a fork's counts and the way to review them are one control"

This reverts commit e4504d2843. The counts go back to separate badges and
"Review changes" to a button of its own.

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

* fix: the compare page follows the fork picked in the trail

Switching a fork from the breadcrumb left the page comparing the pair it opened
with. Two reasons, neither reactive: the page seeds `currentWorkspaceId` from
`?workspace_id` once at mount and never reads it again, and the switch rewrites
`?workspace`, a different param that this page does not read.

It follows `$workspaceStore` now — picking a fork in the trail means comparing
that fork. The param is rewritten so a reload keeps the pair on screen, and an
arbitrary `?target=` is dropped, since it named a destination for the workspace
just left.

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

* feat: the band is a step taller, and the runs row sizes with it

The bar goes from h-11 to h-12, taken from one constant beside the top
padding a floating band owes the page under it, so the two cannot drift.

The runs page's controls now share the sm height the rest of a header's
actions use: FilterSearchbar learns the size, dropping the top margin
that only centres text at md and taking the shared leading instead, and
sm buttons get px-3 so a label reads as a button. The actions sit in
their own row with a wider gap, and the chart below meets the header
with no gap of its own.

The search field holds one width through typing. As flex-1 it was
whatever the row had left over, so committing a chip grew it from 226 to
288 and took those pixels off the breadcrumb.

A fork's count badges keep their labels while the bar is at least 1280
wide and fall back to icon and number below that.

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

* fix: the trail holds a path's place instead of naming its kind

A page registers its kind when it mounts and its path once it has loaded
one. In between the trail printed "script" or "flow", which reads as the
answer and then jumps when the real path lands. A skeleton of a
plausible path's width holds the place, with aria-busy and a label
naming what is loading.

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

* fix: the compare page reads the workspace it was linked with

The effect that follows the trail's fork also ran on mount, so a
`?workspace_id` that differs from the navigation workspace was thrown
away. That difference is the contract: a session's Review button passes
the fork it committed to and leaves the navigation workspace alone, as
does the prod -> dev link in UpdateDevWorkspaceModal. Both opened on the
sidebar's workspace instead, comparing the wrong pair or none at all.

Only a change in the store now retargets the page, measured against what
it held when the page opened.

The URL rewriting goes with it. The picker's items are links that carry
every other param forward, so their navigation lands after the effect
and put back the `workspace_id` it had just rewritten: switching fork
and reloading left the trail naming one workspace and the page comparing
another. The page reads `?workspace=` ahead of `?workspace_id=` instead
— the param the picker writes and the root layout already applies to the
store — and `?target=` lapses while the page is on another workspace
rather than being deleted from a URL someone else is about to replace.

Two 44px literals left over from the h-11 band: the detached sidebar
card's measured-top fallback and the rail morph's end state, both now
from BAR_HEIGHT_PX beside the class the band wears.

The runs search field keeps its 18rem without growing, but as `shrink-0`
nothing in that row could give width back, so a short bar had no answer
but the trail's truncation and an overlong row would have run past the
edge. `min-w-0` restores the yield without restoring the growth: a flex
item's floor is its min-content width, which the chips inside the field
raise.

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

* fix: the compare page's target picker works, and a switch survives reload

`?target=` was gated on the page still being on the workspace the URL
named. Opened from "Merge into another workspace" in the workspace
settings the URL names no workspace at all, so that comparison was
`<id> === undefined` and the param was ignored for good: picking a
target left the page on the "no parent workspace" prompt, which is the
only place the migration can be started from. Read the param as it was
read before.

The follow effect writes `?workspace=` again. A plain click in the
picker preventDefaults its own link and switches the store directly, so
nothing navigates afterwards and nothing overwrites the write — while
`fixupUrlAfterWorkspaceSwitch` rewrites the param only once it is
already there, which on a page opened with `workspace_id` alone it never
is. Without it, switching fork and reloading left the trail naming one
workspace and the page comparing another.

Exercised on a fork pair: the settings entry's picker loads the
comparison it names, a plain-click switch survives a reload, and a
`workspace_id` deep link still outranks the navigation workspace.

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

* refactor: the compare page reads its pair instead of following it

Net effect count: three removed, none added.

The page derives the workspace it compares from the URL and the store
rather than keeping it in state and running an effect to follow a
switch. The workspace-switch link is what moves it: the picker's items
carry every param forward, so a page-side rewrite raced that navigation
and lost — the trace showed the fixup's replaceState landing first and
the link's pushState putting the old params straight back. The href now
swaps `workspace_id` alongside `workspace` and drops `target`, and
`fixupUrlAfterWorkspaceSwitch` does the same for the paths that do not
navigate, so both writers agree and the order stops mattering.

Dropping `target` on a switch also closes the case where switching onto
the workspace that was the target left the page comparing it with
itself.

CompareWorkspaces remounts on either side of the pair changing, not just
the target: two forks of one parent shared a mount, since the target is
the parent they have in common, and with it the selection, the deploy
direction, the summaries, the pinned rows, the deploy permissions and
the CI results. The cache-clearing effect that stood in for that is
gone. Its summary loops outlive their mount, so they read the pair once
at the start and stop writing when the instance is destroyed.

The app viewer asked for a public secret on every app view to decide
whether to draw a button; it reads the mode off the app it has already
loaded and fetches the secret when the button is used.

The summary popover seeds its fields on the open transition rather than
from an effect that also read the item: a save landing while it was open
reseeded the fields over what was being typed.

Exercised against a running instance, 18 cases: idle issues nothing, a
switch moves page, trail and URL together and survives a reload, a deep
`workspace_id` link still outranks the navigation workspace, four fast
switches settle with no foreign row, and the migration target picker
loads the pair it names.

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

* fix: the public url opens the tab the click itself opened

The secret is fetched when the button is used, and the tab was opened
once it arrived — after an await, where Safari no longer counts the
click as the gesture that opened it and blocks the popup without saying
so. Open the tab synchronously and point it at the url once the secret
is known, closing it and saying why when there is none.

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

* fix: the public url button survives a sandboxed app

A sandboxed low-code app loads inside the opaque /app_embed iframe, so
`loadApp` never runs on this page and `app` stays undefined — taking the
button and its compact-menu entry with it, for the owner too. The mode
now comes from the lite read that already runs for the Edit button,
which is there for exactly this reason: it is the one description of the
app this page has whether or not the app itself loads here.

Exercised on four apps: anonymous sandboxed low-code, anonymous
unsandboxed low-code and an anonymous raw app all show the button and
fetch the secret only on click; a publisher app shows neither and asks
nothing. At 640px the button folds into the menu, which carries it.

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

* refactor: the removed hero leaves a blue flash on the ellipsis

The mark that flew from the cross to the menu is gone, with its two
element refs, its flight state and timer, and the `data-home-ai-menu`
hook that existed only as its destination. What is left is the thing it
was there to say: the ellipsis holds the way back, so it flashes blue as
it appears.

Written as a keyframe animation, not a transition. A transition needs
the element painted at its start value before the end value is set, and
this mounts in the same flush that collapses the hero, so the frame that
would have shown it at rest is the frame it is already told to leave —
one `requestAnimationFrame` is not reliably past that paint, and the
flash was never drawn at all. `animate-fade-out` already exists for this
(AssetsDropdownButton flashes a background the same way); mounting it
only while pulsing gets a fresh run per removal without that component's
reflow juggling.

The flash rides the ellipsis rather than the row: with the CLI and hub
buttons beside it the row is three controls wide, and marking all three
names none of them.

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

* fix: the flash rests invisible, and its comment describes what is there

`animate-fade-out` carries no fill mode, so at the end of the run the
element returns to its own opacity. At the default 1 that is the flash
coming back at full strength for as long as the unmount timer lags the
animation — which the hero's collapse, happening right there, is well
placed to cause. `opacity-0` at rest makes the revert land on invisible.

The comment above it still described the mark that used to fly to the
menu, and made its point three times over.

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

* fix: the compare page asks only about the pair it is showing

Switching workspace on this page fetched the previous pair's item paths
from the new workspace, where none of them exist: 97 of 174 requests
404ing on one switch. The comparison was plain state, and the card is
remounted when the pair changes — in the same render that changes it,
before the effect that cleared the comparison could run, so the new
mount started from the old pair's diffs. It now carries the pair it
describes and is read only while that matches, which leaves nothing to
clear.

`computeFullScan` had the same shape: it read the target again after its
await, so a switch mid-scan invalidated another pair's cache, named the
wrong workspace in its toast, and left the scan button spinning on a
pair that was never scanned. It captures the pair up front, re-compares
only while the page is still on it, and reports it as its own.

Each diff says which side holds the item, so the summary and
on-behalf-of loops stop asking the side that does not: an item that is
only ahead cannot answer in the parent, and asking was a guaranteed 404
and a console error per one-sided row.

Those two loops also read the same object — a summary and an identity
are fields of one payload — so every row was fetched twice per
workspace. One read per item per workspace now serves both. A failed
read is not kept: held, it would read as "this item has no identity",
which is what decides whether a deploy offers to preserve one, and an
item deployed without it silently becomes the deployer's.

The header names the workspace being compared when that is not the one
the app is pointed at, which is how a session's Review button and the
prod -> dev link open this page. The trail said the parent while the
page compared the fork.

Measured on a 90-item pair, before -> after: a cold load 247 -> 170
requests and 167 -> 90 item reads; a workspace switch 174 requests with
97 404s -> 71 with none. Sixteen cases over cold load, idle, switching
in both directions, four switches with no time to settle, a cross-family
switch, reload, deep link, the migration picker and leaving an arbitrary
target: no request in any of them names a pair the page has left.

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

* fix: the scan toast reports the pair it scanned

Off the scanned pair the re-compare is skipped, so there is nothing to
report as compared — but the success branch said "Compared N items"
anyway, and the error it checked first belonged to whichever pair the
page had moved to. Each outcome now says what happened: the seed alone
when the page has moved on, and the comparison's result only where one
was asked for.

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-10-08 08:42:46 +02:00
GuilhemandClaude Opus 5.5 b2ef8478c6 vertically center flow step header actions (#11601)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:41:01 +02:00
Ruben FiszelandClaude Opus 5.5 046df9e44d feat: run AI decision steps on OpenAI's Decisions API (#11590)
* feat: run AI decision steps on OpenAI's Decisions API

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

* fix: fail refused openai decisions and keep structured criteria

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

* fix: send only user message lists as openai decision messages

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

* fix: omit descriptions the openai decision step does not have

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

* fix: pass only exact openai user messages through as decision input

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

* fix: check the payload of openai decision message parts

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-10-07 12:05:04 +00:00
GuilhemandClaude Opus 5.5 866179730a fix: keep run status buttons from selecting the flow input node (#11579)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 12:20:57 +02:00
hugocasaandClaude Opus 5.5 c75113a235 capitalize AI Decision labels to match AI Agent (#11583)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:20:37 +02:00
Ruben FiszelandClaude Opus 5.5 9d55a9381d add customUi prop to hide save to workspace on flow steps (#11566)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 17:05:18 +02:00
Ruben Fiszel 8c74f75de3 fix: simplify the run page AI card to a prompt and a chat button (#11561)
* fix: drop the add a prompt for AI button from the run page

* fix: open the run page in the session preview from a plain chat button

* fix: rename additional prompt for AI to AI prompt

* fix: put the run page chat button beside advanced when no AI prompt
2026-10-06 14:18:13 +02:00
3cba8a7e4c feat: add an AI decision flow step (TypeSafe's Jev, Cloudflare's Clef) (#11474)
* feat: add an AI decision flow step on TypeSafe's Jev

An `aidecision` step asks TypeSafe's Jev typed questions (choice, score,
yes/no) about a state and returns calibrated answers. It is a plain step:
a flow routes on the answers with a Branch to one after it, which the
editor builds and keeps in step with the questions. It can also be an AI
agent tool, where the calling model supplies the state.

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

* fix: read single-quoted branch conditions and flag branches on a removed question

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

* fix: route decision tools by their tag and keep structured questions as JSON

The questions editor writes only on an edit, so opening a step leaves its
value untouched, and questions the cards cannot hold stay in a JSON editor.

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

* fix: re-pick the questions editor mode when the value is replaced from elsewhere

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

* fix: keep the questions JSON editor's text in step with the value

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

* fix: recreate the questions JSON editor when the value is replaced from elsewhere

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

* feat: run AI decisions on Cloudflare's Clef as well as TypeSafe's Jev

A `cloudflare` provider kind reads the hub's `cloudflare` resource (token and
account id), calls the model's Workers AI URL, and unwraps Workers AI's
`result` envelope. Like TypeSafe, it serves decision steps only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* feat: branch from each AI decision question, and align its editor with the brand guidelines

"Branch on this question" now sits in each choice question's card, which also
links to the Branch to one that routes it and flags options it misses or
branches it no longer matches. Labels, errors, cards and icons follow the form
guidelines and the Branch to one's cards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* feat: branch on an AI decision from its graph node

A decision with a choice question nothing branches on yet shows a button beside
its node that inserts the Branch to one, as its question card does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: resolve Cloudflare resources in the AI proxy, and use the design-system button on the graph

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: put the decision_inputs test's doc comment back on it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* chore: regenerate the flow schema in prompts and the chat after the AiDecision description edit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* feat: switch AI decision questions between the form and JSON

JSON is always available; the form is offered whenever its cards can hold the
questions, and a value they cannot hold still opens in JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: show step ids as badges and use the JSON toggle of the run forms

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: quote option names in the AI decision branch alerts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* feat: remove stale branches from the AI decision question card

An empty branch is removed directly; one with steps asks first, naming how many
steps go with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* feat: branch on yes/no AI decision questions, and test a decision step from the AI chat

A yes/no question branches like a choice: "Branch on this question" writes one
"yes" branch on `noul >= 0.5`, with the default as "no", and the helper still
reads it once the threshold is tuned. The AI chat's step test runs a decision
alone as a one-step preview, taking the state the chat passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: record removing several stale branches as one undo step

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: name the typesafe resource type TypeSafe

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: store TypeSafe as the typesafe resource type's display name, not in the word map

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: leave the typesafe and cloudflare resource types to the hub instead of seeding them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* docs(AGENTS): resource types ship through the hub, not migrations

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: show the questions JSON format beside the JSON toggle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: give the questions JSON format in the field's description

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: style the decision's graph branch button like Add branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: show the questions JSON format only in JSON mode

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* feat: show each question's branching under the questions JSON editor

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: drop the redundant In JSON prefix from the questions format

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: shorten the decision state's description and show an example as its placeholder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: escape a schema placeholder before the JSON editor renders it as HTML

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* fix: give the decision state's placeholder a text and an object example

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019sEUVZVYBjkzkaVkjbFeAL

* chore: update ee-repo-ref to 154cbccb6b8d47bf662b5507882917b5933d5a45

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

Previous ee-repo-ref: eaf38c1388f3cc5bece163404fffa959ede8cae0

New ee-repo-ref: 154cbccb6b8d47bf662b5507882917b5933d5a45

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-10-05 14:23:34 +00:00
AlexRV12andClaude Opus 5.5 c962d3766b fix: evaluate field conditions and step test inputs in QuickJS (#11487)
* fix: evaluate field conditions and step test inputs in QuickJS

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

* fix: answer single-quoted field conditions without loading the engine

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

* fix: bound expression recursion and ignore leading blank space

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:28:46 +02:00
e7fc1b2e2e feat: restricted job tokens per script and flow (#11484)
* feat: restricted job tokens (job_token_scopes on scripts and flows)

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

* fix: admit flow-run reads, skip dedicated workers, gate on worker version

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

* fix: keep restricted jobs off flow runners, preserve scopes on rename and promotion

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

* fix: keep restricted jobs off every dedicated handoff, confine progress flow id

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

* fix: exclude restricted runnables from dedicated worker startup

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

* fix: gate restrictions on the release after 1.821.0

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

* fix: store per-job scopes on job_perms instead of v2_job, pin inline runs to the checked version

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

* feat: step-level job_token_scopes for flow steps and agent tools

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

* fix: fail closed on perms read errors, refuse restricted queue imports, gate step scopes in previews

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

* fix: carry a job's scopes on its completion so a re-run keeps the caller's cap

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

* fix: carry a zombie job's scopes into its completion

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

* fix: leave a zombie for the next sweep when its scopes cannot be read

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

* docs: correct the QueuedJobV2 completion comment

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

* fix: validate step scopes in batch flows, fail closed on unvalidated step scopes

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

* fix: refuse flows with step or tool restrictions at push while an older worker is live

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

* fix: apply the step-scope worker gate to flow restarts

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

* fix: list the job token toggle with the other step and flow settings

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

* chore: pin the EE companion merged with EE main

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

* perf: skip scope lookups for unrestricted jobs; list job token setting last

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

* style: rustfmt scopes tests

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

* fix: confine restricted job tokens to their own run lineage; drop remaining extra lookups

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

* chore: update ee-repo-ref to 259ad3bfeef5285ba80eedc86309b11dca001220

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

Previous ee-repo-ref: 2b77c0225dca441235daf7bf0a06ba968df0c927

New ee-repo-ref: 259ad3bfeef5285ba80eedc86309b11dca001220

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-10-03 09:33:44 +02:00
Ruben Fiszel e952298f84 feat: replace the AI input filling toggle with an additional prompt (#11501)
* feat: replace the AI input filling toggle with an additional prompt for AI

* feat: pass the additional prompt for AI to MCP clients and shared AI guidance

* feat: shrink the run page AI card to a button and a collapsed prompt

* fix: offer writers a way to add a prompt for AI from the run page
2026-10-03 09:33:18 +02:00
Ruben FiszelandClaude Opus 5.5 f2393e5b24 keep flow priority input inline with its toggle on wide screens (#11500)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 09:00:32 +02:00
GuilhemandClaude Opus 5.5 88d0cd00e5 fix: restore the save to workspace button on inline flow steps (#11469)
* fix: restore the save to workspace button on inline flow steps

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

* fix: make the inline step save to workspace button icon only

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-10-01 18:25:25 +02:00
GuilhemandClaude Opus 5 b10f83c763 feat(sessions): show an item's deployed page in the preview panel (#11458)
* feat(sessions): show an item's deployed page in the preview panel

The preview panel showed an item's editor and nothing else. It now shows the
deployed page too, as a tab of its own.

**Two sides, two tabs.** The editor and the deployed page are different things
to look at, so `isItemTabFor` keys on the side rather than the item: asking for
a side already open focuses it, asking for the other opens it alongside. Two
editors for one item still cannot coexist — they would race its single
(kind, path) cell — but an editor beside a viewer is safe, since the viewer
reads the deployed version over the API and holds no cell. The tab breadcrumb
keeps re-pointing in place; `Exit & see details` and the detail page's `Edit`
open the other side instead of consuming the one you are on. A tab's label
carries `(edit)` when it is the editor side.

**The detail pages moved out of their routes.** `/scripts/get` and `/flows/get`
are now thin wrappers over `ScriptDetail` and `FlowDetail`, which the panel
renders too. Everything they reach — drawers, triggers, saved inputs — is scoped
to the viewer's workspace through `setOperatingWorkspace`, and every navigation
they attempt is caught by `interceptNav` and turned into a move inside the
panel, so nothing takes the browser out of the session. A plain click is
intercepted; ⌘-click and middle-click still open a real tab, which is why the
pages keep their `href`s.

**The picker opens what exists.** A row opens the deployed page, or the editor
when nothing is deployed there, and carries a Draft / Draft only badge in the
review dock's words. `WorkspaceItem` gained `draftOnly` and `hasDraft` from the
listers, which already returned both; deploys now invalidate the picker cache
through `itemDeployed`, so a just-deployed item stops reading as a draft. Rows
also carry a hover Edit action, reachable from the keyboard with the pick
modifier.

`open_preview` gained a `mode`, narrowed out of the advertised schema for a
session that cannot write drafts and re-checked per path in the handler, where
a refusal reports "couldn't check" apart from "denied". The mode is resolved
before those checks, so a call that omits it is gated as the editor it will
become.

Alongside, the workspace a detail page acts on is now the one it is showing
rather than the one the browser is navigated to: `MoveDrawer`,
`toggleWorkspaceErrorHandler` and the pages' own permission gates read the
operating workspace and its acting user, and `RunForm` mints a password
argument's ephemeral variable there too — in a fork session all of these
previously answered for the parent. `InWorkspaceAppViewer` hands the app's
`ctx` user down as a prop instead of writing the global `userStore`, which from
a session tab was re-pointing every permission check on the page.

Fixes found on the way: `DetailPageLayout` claimed `h-screen` inside a panel
that is not the viewport; Edit on a historical script version dropped the
version from the intercepted click; the Run button stayed spinning after a run
that left the page mounted; and the window-level run shortcut fired from a
collapsed panel and from keys another handler had already claimed.

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

* fix(sessions): historical edits, stale not-found, shared edit rights, tab labels

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

* fix(sessions): keep deployed-page links and workspace reads in the session

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

* fix(sessions): scope detail hrefs to the session, re-point the viewer's own tab

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

* fix(sessions): keep a previewed raw app's route out of the session URL

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

* fix(sessions): resolve edit-in-fork against the session workspace

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

* fix(sessions): read advanced run tags from the session workspace

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

* fix(sessions): fork from the session workspace, star only navigation items

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

* fix(sessions): mount only the side a preview tab shows

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

* fix(sessions): address CI review round 1 findings

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

* fix(sessions): route links in a deployed page's drawers through the panel

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

* fix(sessions): tell the chat which deployed page the panel shows

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

* fix(sessions): only a 404 reads as an undeployed raw app

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

* fix(sessions): scope the unparseable-JSON run gate to the form

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

* fix(sessions): a flow deleted from the panel stays in its tab

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

* fix(sessions): the picker's edit shortcut works on the current row

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

* fix(sessions): load-error toasts say what failed

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

* fix(sessions): keep an oversized invalid JSON editor in its form's run gate

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

* revert: keep main's draft_only description in openapi.yaml

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 17:43:52 +02:00
hugocasaandClaude Opus 5.5 8c7dcbbda5 feat: agents as a standalone kind with home listing, detail and editor pages (#11332)
* 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>

* 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>

* 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>

* feat: add agent detail and editor pages

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

* 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>

* fix: keep query params and a unique path when creating an agent

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

* fix: lead the home list with agents and keep rows while the agent view loads

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

* fix: treat a loading agent as undeployed when leaving the editor page

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

* 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>

* feat: agent configuration modal and draft paths like other new items

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

* fix: keep a new agent draft-free until the first input, land agents with runnables

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

* feat: place AI agent after apps in the new menu and describe chat and flow use

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

* feat: new agents start with managed memory, editor form says how to turn it off

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

* fix: clearer managed memory hint in the agent editor form

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

* refactor: share the agent editor's pane notice as a PaneNotice component

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

* fix: name a new agent after a path no resource holds

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

* fix: tell repeated tool names apart and hide permissions on draft-only agents

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

* feat: agent configuration beside the model in the chat composer and above the form

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

* feat: center the agent run form, configuration beside its Run button

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

* fix: icon-only agent configuration button beside Run

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

* feat: agents run on behalf of their deployer through a run-by-path endpoint

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

* feat: agents keep their run-as identity in their value, preserved by the CLI

The identity an agent runs as lives in `value.on_behalf_of` instead of a column. Every write
of an agent resolves it server-side as a flow's is: the writer's own, unless an admin or
wm_deployers member asks to keep it, and a folder default on create. Retyping a resource into
an agent resolves its value the same way. Export leaves it out; the run endpoint reads it and
drops it from the step's inputs.

The CLI follows the app model: a pushed agent never takes its identity from the tracked file,
an unchanged agent compares equal to the deployed one, and an admin or deployer push claims
the deployed identity back. The owner-change pre-check lists agents.

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

* feat: deploying an agent draft from review keeps its deployed identity

As for an app: an agent draft carries no identity, so the review page claims the deployed one
back, which the backend honours for an admin or wm_deployers member. The draft diff leaves the
deployed identity out, since a draft never holds one.

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

* feat: deploying an agent to another workspace offers the run-as choice

An agent deployed to prod/staging, merged from a fork or promoted with `wmill workspace merge`
gets the same identity choice as a flow or an app: the target's current one, the deployer, or
a picked user, sent as a principal the way a trigger's is. The source workspace's principal is
never copied, and a difference in identity alone is not a change in the workspace compare or
its diff.

The frontend consumes the published windmill-utils-internal, so its deploy provider carries the
same rewrite until that version ships.

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

* fix: agent identity on fork, agent-scoped job reads, and the run license gate

A fork re-points an agent's identity at its creator when they may not preserve someone else's,
and at the creator when it names nobody in the fork, as it does an app's. A token scoped to
`jobs:run:agents:<path>` reads back the runs it starts, chat turns included. The agent run
endpoint checks the enterprise license like every other run entrypoint.

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

* fix: a CLI push decides agent identity handling by the tracked file's type

An agent retyped into another resource by a push kept neither its value's `on_behalf_of` as the
file stated it nor clear of the old agent's identity. The file's type now decides, and only a
deployed agent's identity is claimed back.

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

* refactor: agents run as the caller, with a note on what a shared one needs

Drops the agent run-as identity: its storage in the agent value, the backfill, the resolution
on every write, and its handling in export, the CLI, draft and cross-workspace deploys, forks
and the workspace compare. The run endpoint runs as the caller, so an operator or reader runs an
agent with their own access. The editor tells the author of a folder agent that anyone running
it needs access to its AI resource and to what its tools use.

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

* docs: agent editor comments describe runs as the caller

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

* refactor: agent editor pane notes use Alert

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

* refactor: agent editor pane notes render as Alert

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

* fix: agent editor notes as regular Alerts, not banners

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

* feat: agent path as its own editor level, and evals on the agent page

The path leaves the agent form: the editor dialog opens it as a level, as it does evals, and the
editor page in a drawer. The agent page gains Evals, in a dialog. The page supplies the run
form, keeping it out of the editor the flow editor reaches.

The draft edit gate no longer throws when a focused, changed field is removed: the `change` that
removal fires lands mid-teardown, so the gate opens just after instead.

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

* feat: agent settings as the resource editor's header fields, behind a cog

The agent's own settings (path, labels, workspace specific, description) open from a cog as
the flow and script editors' do, laid out as the top of the resource editor. The folder note is
gone.

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

* feat: agent settings fields and cog, completing the rename

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

* feat: evals open as a dialog over the agent editor page

The editor page opens an agent's evals over itself, as the agent page does, with the unsaved
edits offered to a run; the editor dialog keeps evals as a level of its own. The two pages share
the dialog.

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

* fix: the unreachable model provider note reads like the unreachable agent one

Same warning level and wording as the note above it, with the path inline rather than in
parentheses that lost their spaces.

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

* fix: the unreachable model provider note offers editing the agent too

Editing the agent to use a provider the reader can access is often the simpler way out; offered
when the reader can write the agent, with Unlink and asking for access.

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

* fix: provider note lists asking for access as its own option

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

* fix: shorter provider note, without the header's buttons repeated

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

* fix: evals only for those who can edit the agent

Evaluating builds datasets and runs against the agent, which is authoring: the agent page and the
editor offer it only with write access to the agent.

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

* fix: agent page actions ordered as the script and flow pages

The menu leads and Edit comes last, as DetailPageHeader lays them out.

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

* fix: the agent editor dialog's levels slide in built on the first visit

Warmed, as the evals pane's levels are, so settings and evals are mounted before the first
navigation rather than inside its transition. Evals are only in the strip where they can be
opened.

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

* fix: the agent's settings report path errors, and hold nothing a reader can edit

The path field reads its error back, so it shows it and keeps deploy blocked on it.
Labels are shown rather than editable without write access. Evals wait for the load
to know they can be opened, and the page layout builds no levels it never shows.

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

* refactor: one builder for an agent's run flow, and the agent page on the shared header

The run endpoint and evals build the agent's one-step flow through the same function.
The agent page uses DetailPageHeader, whose error handler, tag and trigger context are
now optional, and whose menu items keep their disabled state. The home row and the
page share the agent's menu and delete.

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

* fix: the agent page's menu is built from its current path

Deploy settings are the workspace's, so they load once and the menu is derived from
them rather than fetched per path, where a superseded fetch could land after a
navigation. The shared run-flow builder lives with agent runs rather than evals.

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

* fix: the scoped-read comment names the run-flow builder as it is

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-30 18:40:05 +02:00
ca44043e12 feat: let operators compose flows when the workspace grants the right (#11228)
* feat: let a workspace withdraw operator schedule and trigger writes

Operators can create, edit and delete schedules and triggers today through the
API, CLI and MCP, while the operator_settings flags beside them only hide those
pages. An admin who wants operators to see what is scheduled without letting
them change it cannot express that. Add manage_schedules and manage_triggers as
enforced settings, gated at the schedule handlers and at the generic TriggerCrud
routes so every trigger kind is covered by one check.

They name capabilities operators already hold, so they are granted unless
withdrawn, and absence has to mean "never configured" rather than a value. The
read coalesces to true; the update endpoint merges into the stored jsonb with
the two fields as Option<bool>, so an omitted key keeps what is stored.
operator_settings is git-synced as a whole object, so a settings file written
before these keys existed reaches the endpoint on every pull, and a serde or SQL
default of either polarity would turn that pull into a silent withdrawal or
restoration.

The rights are read through a per-process cache, so withdrawing one publishes a
notify_event that drops the entry on every replica.

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

* feat: let operators compose flows when the workspace grants the right

Adds operator_settings.builder_flows: a workspace setting that lets every
operator compose flows out of runnables that already exist. It does not make
them authors. The boundary the operator role draws is authoring code and running
arbitrary code, and this does not move it: check_flow_is_composition_only walks
the value and refuses anything carrying code, including the shapes an obvious
walk misses (code hoisted into a flow_node, an AI agent step's tools, and a
linked ai_agent resource whose tool list is resolved at run time).

What the walk cannot settle it returns for the caller to authorize under RLS:
the worker tags the steps pin, every runnable they reference, and the (path,
hash) of every version-pinned step. Composing a path is enough to run it and to
run it as whoever it runs as, since the worker resolves a step's path with the
root DB handle and adopts that runnable's on_behalf_of. A pinned hash needs its
own check because dispatch ignores the path beside it.

The gate runs on every write and on both request-supplied-value paths, flow
preview and flow dependencies, or either becomes the way to run what the write
path refuses.

Operators of a builder workspace consume a full author seat; the EE companion
carries the counting.

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

* feat: enforce operator write rights on the router and in the UI

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

* fix: close the capture gap and gate the trigger editors' write actions

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

* fix: gate acl writes and the native trigger drawer behind manage rights

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

* fix: refuse operator writes with 403 and gate sharing at the drawer

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

* perf: resolve identity in the operator write gate only for writes

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

* fix: gate the suspended-jobs actions and stop the route check refusing reads

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

* fix: explain the empty-state create button when operator writes are withdrawn

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

* fix: audit operator settings changes and fold path writes into native rows

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

* fix: open locked editors read-only and group the operator settings

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

* fix: skip email and azure lookups on editor open while triggers are locked

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

* docs: state each operator-rights rationale once in comments

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

* fix: address CI review findings on operator write rights

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

* fix: keep capture move gated and skip it in the builders while locked

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

* fix: refuse builder-rights violations with 403 so operators stay logged in

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

* refactor: trim duplication in the operator builder gates

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

* fix: hide build app from builder operators on the flow page

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

* chore: point at the companion EE PR merged with EE main

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

* fix: keep a builder's drafts list loading past drafts they cannot write

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

* fix: hide saved agents from builder operators in the step picker

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

* chore: note the inlined seat rule and drop orphaned sqlx entries

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

* fix: stop a builder's step test from logging them out

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

* fix: refuse a builder's dependency job on a path it cannot write

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

* fix: keep builders from adding dynamic dropdown code to a flow

A flow's dropdown code runs as whoever loads its form, so a builder may keep or drop the code stored on the flow it updates, never add or change it. The builder's editor hides the dropdown types and code, and previews options through the deployed flow; the inline dropdown refusal is a 403 so it no longer logs operators out. Also trims rationale comments repeated across sites.

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

* fix: show why saving operator settings failed

The seat-cap refusal on granting builder rights explains what to do; the toast now carries the server's message instead of a generic failure.

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

* fix: check builder flow drafts like deploys and treat dropdown code as code

A developer who loads a builder's flow draft in the editor runs its dynamic dropdown code as themselves, so a builder's draft now passes the same checks as a deploy. Dropdown code is refused like step code rather than kept or dropped, which also removes the exact-match comparison that refused builders over whitespace.

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

* fix: bill a builder workspace's operators as developers on cloud

The cloud seat count behind the Premium page, the sidebar usage and the fork cap still weighed every operator at half a seat, while the builder right makes them authors. The out-of-repo invoicing job must follow the same rule.

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

* fix: check a builder's flow draft as it will be stored

Draft storage strips NUL escapes after the builder check, so a key ending in one (value\u0000, x-windmill-dyn-select-code\u0000) passed the check as an unknown field and was stored under its plain name. The check now reads the sanitized text.

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

* fix: list a builder's flow drafts and hide hub imports from builders

A builder's undeployed flows now appear in the home list, the flow list and the folder counts. Hub project imports and templates bring scripts and apps along, so builders are no longer offered them. The docs record builders' JavaScript expressions as an accepted risk.

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

* fix: keep the stored builder right when a settings payload omits it

A git-synced settings file written before the key existed withdrew the right on every push. builder_flows now follows the manage_* rights: an omitted key leaves the stored value, and the CLI does not count it as a difference.

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

* fix: word builder refusals by what the flow contains, test the tag refusal

A builder refused on a developer's flow never changed its code, so the refusals now describe the flow ("has inline code, so only a developer can edit this flow") rather than an authoring attempt. The grant confirmation uses the neutral dialog: granting changes billing but destroys nothing. The integration test pins the refusal of a worker tag the workspace cannot use.

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

* fix: read builder rights from the operating workspace, gate the flow page's audit logs entry

Builder rights now come from useOperatorBuilderFlows(), next to the schedule and trigger locks, so an editor embedded for another workspace answers about that workspace; the legacy AI chat, one instance for the whole app, reads the navigation workspace. The flow page's Audit logs entry follows the operator audit_logs setting now that builders open that menu. Operator settings reset every value on load, null settings included, so nothing carries over from the previous workspace.

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

* fix: pick a dynamic dropdown's code source by the operating user's role

The flow input editor, the flow test panel and the flow chat send the dropdown request to the operating workspace, so they now also choose inline versus deployed code by the role held there, through useOperatingUser(), instead of the navigation workspace's.

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

* chore: update ee-repo-ref to 40ac1c5f8cbce3843b582d9b392d3f3cc7eca3e6

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

Previous ee-repo-ref: 31c9e66884b8ca805b20bbfad41fc428fbedbc0e

New ee-repo-ref: 40ac1c5f8cbce3843b582d9b392d3f3cc7eca3e6

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-09-30 18:09:14 +02:00
hugocasaandClaude Opus 5.5 077709b914 feat: rename agent memory options to On and Legacy, explain each (#11442)
* feat: rename agent memory options to On and Legacy, explain each

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

* fix: default the chat agent example to On memory and align the schema copy

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

* feat: offer Legacy memory only to steps that hold it, explain under the toggle

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

* fix: keep the off memory hint true on the saved-agent editor

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

* feat: state the 128k fallback in the context window description

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:59:16 +02:00
c1b59f70dd feat(ai-agent): add compaction memory that summarizes older context (#10928)
* feat(ai-agent): add autocompacted memory that summarizes older context

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

* fix(ai-agent): compact on final-answer turns and count what a turn appended

Address the pre-push review findings on the compaction path:

- A turn the model answers without a tool call left the agent loop on its first
  iteration, so a chat-shaped step never compacted and reloaded the whole
  conversation on every later turn. Compaction now also runs after the loop.
- The trigger measured only the last request, so a single large tool result
  could carry the next one past the window without ever crossing 80%.
- The summarization call re-sent the usage-tracking request shape on endpoints
  the loop had already learned to drop it for.
- The flat 8000-token summary reserve swallowed the whole target on a small
  context window, leaving one message in the tail and summarizing the rest.
- A response cut off inside the <analysis> scratchpad was accepted as a summary.
- The chat-mode memory default was a shared object the step form edited in place.

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

* fix(ai-agent): keep Anthropic prompt counts and compact once per response

Address the first CI review round on the compaction path:

- Anthropic's streaming parser dropped `message_start`, the only event carrying
  the prompt-side counts, so a native Anthropic run reported no input tokens at
  all and compaction fell back to a character estimate.
- A loop that exits without issuing another request — a structured-output turn
  does — reached the post-loop pass still holding the previous measurement and
  compacted a second time, or retried a failure with nothing changed.
- The summarization call inherited the step's `max_completion_tokens`; a low one
  truncates the summary inside its scratchpad, which counts as a failure and
  disables compaction after three of them.
- A fired trigger that found nothing to summarize said nothing.
- Memory already over the window — a lowered `context_window`, or a step moved
  over from `auto` — had no way back, since compaction only ran after an
  accepted request. It now also runs once before the first one.

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

* fix(ai-agent): state the summary's own completion cap and drop the pre-flight pass

- The summarization call asked for no completion cap at all, which is "uncapped"
  only on the OpenAI-shaped providers: Anthropic substitutes 64000, over several
  Claude models' output ceiling, and Bedrock leaves the model's own small default,
  short enough to cut the response off inside its scratchpad. It now asks for the
  reserve the split already set aside, raised to the step's cap when that is larger.
- Compaction no longer runs before the first request. The fallbacks the loop learns
  from a rejection are not known that early, so on exactly the endpoints that need
  them the summarization was malformed by construction: it failed, spent a strike,
  and the first agent request still carried the oversized conversation. A memory
  already past the window is repaired on the turn after a request the endpoint
  accepts, rather than by a pass that cannot succeed there.

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

* fix(ai-agent): ask the summary for exactly the room the split reserved

The split scales its reserve down on a small window while the request asked for
a flat 8000, so the two diverged below an 80k window: on a 4k/8k model the cap
alone exceeded the window and every summarization was refused, and on a 20k one
a full-length summary could land the conversation back over the trigger and
compact its own previous summary on the next response. Both now read one
`summary_reserve_tokens`.

The call also no longer inherits the step's reasoning effort. Every provider
counts thinking against that same budget, so a high-effort model could spend the
whole reserve before writing anything and return a summary cut off inside its
scratchpad; the compaction prompt asks for an `<analysis>` block, which is the
reasoning this call needs.

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

* fix(ai-agent): charge the compaction budget for tools and the system prompt

The tail budget was the whole target, but a request also carries the system
prompt compaction keeps and the tool definitions, which are not in the message
list at all. On a small window those are most of it: a tail sized to the full
target left the next request back over the trigger, compacting again every
response, and the no-usage estimate missed the tool schemas entirely so it could
fail to trigger at all. Both now account for them.

The reserve also gains a floor. It is the summary's output cap as well as the
room the split leaves, and scaled down without one a small window gave a
structured nine-section summary a few hundred tokens — truncated inside its
scratchpad every time, which is discarded, which switches the mode off after
three.

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

* fix(ai-agent): count Gemini's tool-use prompt tokens in an agent step's usage

Gemini splits a tool-using turn's input across `promptTokenCount` and a disjoint
`toolUsePromptTokenCount`, and its thinking apart from `candidatesTokenCount`.
The agent step's parser read only the headline fields, so every tool-using turn
under-reported both — and the compaction trigger, which runs off the reported
prompt, could not see the tool results that grew it. It now goes through the same
helpers the proxy path already used.

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

* fix(ai-agent): calibrate the compaction estimate against the measured prompt

Two rounds running, the finding was "the character estimate cannot see input X"
— tool schemas, then S3 attachments, which are short paths in the message list
and whole images by the time a provider counts them. Enumerating those is a list
that only grows, so the estimate is now scaled to the one number that is ground
truth: what the provider charged for the last request. Attachments, tokenizer
drift and whatever comes next fall out of that, because the estimate is only
ever used relative to itself.

Also stop the Gemini helpers turning an absent count into `Some(0)`. Downstream,
absent means "fall back to estimating the conversation" while zero reads as an
empty prompt and would hold the trigger below its threshold for the whole run.

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

* fix(ai-agent): charge attachments what they cost and let a heavy short prefix compact

The calibration conserved the conversation's total cost but spread it by
character count, so an attachment — a short S3 path in the message list, a whole
image or PDF once a provider expands it — was charged to the text messages around
it and stayed nearly free in the split. It now carries a nominal cost of its own,
which the calibration corrects a residual on rather than the whole gap.

The four-message minimum also refused exactly the case that fix is for: an
attachment arriving on the first or second turn can pass the trigger before four
removable messages exist, and summarizing even one of them saves most of the
prompt. A prefix worth a quarter of the window is now enough on its own.

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

* fix(ai-agent): never summarize a prefix holding only a previous summary

The message-count floor was carrying a second job: a fresh summary sits in a one
or two message prefix, so requiring four declined it. The share threshold added
last commit admits it, and a summary is reserve-sized by construction — so the
post-compaction shape could spend one summarization per response swapping a
summary for another the same size, shrinking nothing and losing fidelity each
time. A previous summary no longer counts towards that threshold.

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

* fix(ai-agent): take the context window from the model and drop the estimate calibration

Brings compaction in line with how the AI session does the same job, which had
already answered these three questions.

- The window is looked up from the model. `MODEL_CONTEXT_WINDOWS` in
  `windmill-ai/src/model_context.rs` mirrors the session's table in
  `copilot/modelConfig.ts`, entry for entry and with the same matching rules;
  each side points at the other, since a model added to one and not the other
  compacts at two different sizes. A step's `context_window` becomes the
  override for what the lookup cannot serve, and chat mode writes none.
- Provider usage is normalized where the provider's quirk is, not at the
  consumer. `TokenUsage::with_cache_beside_input` raises `input_tokens` to the
  whole prompt for Anthropic and Bedrock, which report their cached prefix
  beside it; the OpenAI shape already counts it inside. `prompt_tokens()` is
  then just `input_tokens`, rather than inferring the shape from whether a
  write count is present.
- The estimator is no longer calibrated against the measured prompt. The
  session uses the provider's count when it has one and a chars/4 estimate
  otherwise, with nothing in between, and a tail sized a little wrong only
  compacts again a turn later.

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

* feat(ai-agent): summarize memory down to what the database can store

Without an instance object store, memory is a 100KB database row cut
from its oldest message, the summary included, so compaction on a
mainstream model never got to keep anything across runs. A step that
persists there now runs its post-loop compaction pass against the
smaller of the model's window and the cap at chars/4, about 25k tokens:
the loop keeps the whole window, and what is written is a summary plus a
tail that fits. The run logs when that pass summarizes, and how many
messages the write dropped when one still overshoots.

The editor's storage warning on the option is removed: nothing exposes
the instance storage to it, so it keyed on the workspace S3 setting,
which is unrelated to where memory goes.

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

* fix(ai-agent): get a complete, billed summary out of every provider

Compaction against the real providers turned up four things the stub
could not: Gemini and OpenAI's reasoning models think by default and
bill it against the same cap the summary must fit in, so the
summarization request now asks them for their least (none, low); an
OpenAI Responses call that hits max_output_tokens ends in
response.incomplete, whose usage the parser dropped, so that
summarization went unbilled; a summary that quotes </summary> when it
describes its own instruction was cut off at the quote, on the agent
step and the AI session alike; and the prefix could end on an unanswered
user message, after which the instruction reads as part of that turn
(Anthropic merges the two outright). The tail now starts on a user
message, and both prompts tell the model the instruction is not part of
the conversation.

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

* fix(ai-agent): compact down to half the window, on the agent step and the AI session

The gap between the 80% trigger and the target is what one compaction
buys, and every summarization request carries most of the window. At a
70% target a 128k model summarized about 13k tokens of prefix for a
summary of up to 8k, so each ~100k-token request bought a few turns of
room before the next one re-summarized the previous summary. At 50% the
same request frees about 30k.

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

* fix(ai-agent): drop the workspace-S3 memory hint and state the database bound in the tooltip

The memory field warned that memory is kept in the database whenever the
workspace had no S3 storage. That setting has no bearing on where memory
goes: the instance object store decides, and nothing exposes it to the
editor. The field's tooltip now describes both memory kinds and states
the database bound unconditionally; the run log says what happened.

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

* fix(ai-agent): send the summarizer its tool history as text

The summarization request carries no tool definitions, and Bedrock's
Converse API rejects toolUse/toolResult blocks that arrive without them,
so on Bedrock every summarization of a prefix holding a tool call failed
silently until the breaker tripped. The prefix's tool calls and results
now reach the summarizer rendered as text, on the agent step and in the
AI session's compaction, which goes through the same proxy.

Also drops the TokenUsage::prompt_tokens accessor, which had become a
plain read of the normalized input_tokens, and shortens the context
window field's description.

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

* fix(ai-agent): price attachments from the provider count, bound storage in bytes, effort per pro model

Addresses two Codex rounds and a leftovers audit.

- Attachments were priced at a flat 1500 tokens in the split, so a
  multi-page PDF (tens of thousands of tokens to the provider, a short
  S3 path in the message list) could be kept in the tail or leave no
  prefix worth summarizing. They are now priced from the provider's
  count for the request that carried them, less that request's text,
  with the 1500 floor where nothing was counted.
- The database storage bound measured the provider's token count, but
  the 100KB cap is bytes and repetitive text packs several characters
  per token. The persist pass now measures the serialized conversation.
- The summarizer forced `low` on every reasoning model, which the pro
  variants reject (gpt-5-pro takes only high, gpt-5.2-pro starts at
  medium); they now get no effort.
- Dropped the unused prompt_tokens accessor and its orphaned assert, an
  unused PartialEq, a needlessly public lookup, and fully-qualified
  Gemini calls; refreshed stale comments and the memory_id schema doc;
  regenerated the flow schema artifacts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix(ai-agent): evict a heavy attachment into the summarized prefix, not the tail

Pricing attachments from the provider count was not enough on its own: a
leading attachment is a user message, and the boundary rule pulled the
last unanswered user turn back into the kept tail to keep it with its
answer. For a heavy attachment that dragged it into the tail — or, at
the front, emptied the prefix — so it was never summarized and rode
every request. The boundary now moves forward instead, keeping that
user turn and its answer in the summarized prefix. Verified on the
running instance: a 25k-token PDF on a 30k window is summarized out on
the turn it overflows, and later turns drop from 26k to ~1.5k tokens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix(ai-agent): keep the forward boundary move off tool results and the prefix start

The forward move that keeps an unanswered user turn out of the tail had
two edges the third Codex round found: advancing past the user could
land the boundary on a tool result (its tool_calls then summarized away,
orphaning it), and with no system prompt the summarizable prefix starts
at 0, so a trigger firing while the tail estimate fit everything indexed
below the start and panicked the task. The forward scan now skips
tool-opening boundaries, and the move is guarded above the prefix start.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix(ai-agent): drop the step temperature from the summary request

OpenAI's reasoning models (gpt-5-mini, gpt-5.1, gpt-5.2) reject
`temperature` alongside any reasoning effort but their own default, so a
step configured with a temperature made every summarization fail once
the summarizer forced a low effort — history then grew unchecked. The
internal summary call now omits the step's temperature: a structured
extraction does not need a set one, and omitting it sidesteps each
provider's temperature-versus-reasoning rules. Confirmed against the API
that low + temperature is refused on those models.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix(ai-agent): compact an oversized loaded memory before the first request

Compaction was reactive, taken only after a request the endpoint
accepted, so the fallbacks the loop learns from a rejection are known
first. But a memory loaded from an earlier run can already exceed this
run's window — the step was switched to a smaller model, or a run under
a wider one persisted more than fits — and that first request then
overflows and fails the run, with every retry reloading the same
history and failing again. A pass is now taken up front, off the
character estimate, before the first request. It uses the default
request shape; an endpoint needing a fallback may reject this one
summary, which is non-fatal, and mainstream providers need none.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix(ai-agent): under the storage bound, trigger on the max of bytes and model tokens

The storage-bound pass measured only the serialized row size, so an
attachment — a few bytes as an S3 path but nearly the whole model
context — read as tiny and the pass skipped a compaction the model
needed. It now takes the larger of the byte measure and the model's
token count, since repetitive text is few tokens but many bytes and an
attachment is the reverse; either being over must fire a pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix(ai-agent): drop oldest turns when a summary cannot fit the window, as the AI session does

An oversized loaded memory (a step switched to a smaller model, or an
object-store run that persisted more than a later model's window holds)
left a prefix larger than the summarizer's own window, so the summary
request overflowed and failed, the memory was untouched, and every
retry failed the same way. The AI session handles this by falling back
from summarization to dropping the oldest turns down to the target;
compaction here now does the same. When a summary cannot run — it
failed, the breaker is tripped, or nothing is worth folding — the oldest
turns are dropped until the conversation fits and opens on a user
message, keeping the newest turn. The next request then always fits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix(ai-agent): drop whole turns only, keep the storage pass to bytes, refresh the count after a rewrite

Three edges the seventh Codex round found, all in the drop-oldest
fallback and the storage-bound measure:

- drop_oldest_to_fit dropped to any point that freed enough, which could
  strand a tool result whose tool_calls went with the messages before
  it. It now drops whole turns only, always landing the boundary on a
  user message and never splitting the newest turn; a lone turn too big
  for the window is left whole rather than broken.
- The storage-bound pass measured the whole model prompt against the
  shrunk 25k window, so a large tool roster and the system prompt —
  neither written to the row — tripped it on a conversation the row
  easily held. It measures the serialized bytes alone now; the model's
  own window is enforced by the in-loop passes and the pre-first-request
  pass, so the persisted size is all this pass is for.
- A compaction rewrites the message list, so the provider's count for
  the request that produced it no longer lines up. The count is now
  cleared after any pass that rewrites the conversation, so a later pass
  measures the estimate over the actual messages instead of a stale,
  larger prompt (which could decline a summary that already fit and then
  drop it). The step temperature, no longer sent to the summarizer on
  any path, is dropped from the request struct.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix(ai-agent): measure only the persisted messages against the storage cap

Persistence strips the system prompt before writing the memory row, but
the storage pass was serializing every message including it, so a large
system prompt with a tiny conversation reported far over the storage
trigger, and the fallback dropped the one real turn, run after run. The
storage measure now serializes only the non-system messages, matching
what the row actually holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix(ai-agent): run the model-window pass before the storage-bytes pass post-loop

A turn the model answered without a tool call broke before the in-loop
compaction check, so on database-backed memory its only pass was the
storage one, which measures bytes. An attachment fills the model context
but is a few bytes in the row, so that turn never compacted and a
follow-up could overflow the model. The post-loop now runs a
model-window pass first, off the provider's count, then the
storage-bytes pass when the row is smaller than the model — both limits
enforced for a chat-shaped step, not just the one that happens to bind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix: simplify agent compaction and preserve execution history

* fix: remove unused compaction history setting

* fix: preserve answers and recover rejected agent context

* refactor: make agent compaction transactional

* fix: skip agent summaries that cannot fit retained context

* fix: explain skipped agent context compaction

* fix: retain recent agent memory when storage compaction cannot fit

* fix: start retained agent memory at a user turn

* fix: reject unsafe agent memory truncation on storage fallback

* docs: clarify agent context window override scope

* fix: keep recent turns verbatim when compaction memory outgrows storage

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix: keep the compaction summary out of the agent's answers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* fix: shorten the agent context window help text

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ViJyjUmidDYV2m6ifQdLeH

* chore: update ee-repo-ref to 942d4013f36edac1fc9a9addbdb02198db1c7a05

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

Previous ee-repo-ref: 8ca1682ce6106ba6ea96894fbe606dac64102eb6

New ee-repo-ref: 942d4013f36edac1fc9a9addbdb02198db1c7a05

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-29 19:01:53 +02:00
Ruben FiszelandClaude Opus 5.5 6064aecdec feat: stop cross-origin isolating the raw app editor (#11411)
* feat: stop cross-origin isolating the raw app editor

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

* docs: drop stale raw app editor reload comments

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

* feat: drop wm_coep from the default raw app embed snippet

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

* chore: pin the UI builder that type-checks without cross-origin isolation

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

* docs: name the proxy-isolated case behind the remaining isolation checks

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 16:52:49 +02:00
GuilhemandClaude Opus 5 f367eaf6d0 feat: run turns in several flow chat conversations at once (#11202)
* feat: run turns in several flow chat conversations at once

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

* fix: keep finished turns finished and cached chats current in the flow chat pool

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

* fix: attribute a turn's rows by job id as well as sequence

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

* fix: count only real stream updates and retry the job-id read

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

* fix: keep a chat that holds an unsent draft, and take one back when its first turn is withdrawn

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

* fix: ignore a stale running-turn snapshot, keep a withdrawn chat's draft, poll after clean stream ends

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

* fix: follow the turn running now when the listing named one already over

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

* fix: keep replacement turns and SSE fallback moving

* fix: keep replacement turn handoffs active

* fix: preserve unread badge line height

* fix: settle local fallback handoffs

* fix: settle refused turn handoffs

* fix: scope turn handoffs to conversation

* fix: drop stale turn handoffs

* refactor: move the queued message and 409 handling into per-conversation turns

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

* fix: address cubic's review of the parallel flow chat turns

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

* fix: clear a stale failure on refresh, and tighten the docs and test waits

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

* fix: recover running rows past the first page, and drop the failure a re-read disproves

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

* refactor: check the running-turn query at compile time, and narrow what a refresh clears

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

* fix: settle a failed turn only from an answer that turn wrote

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

* fix: settle a failed turn from its own answer, and only while it is still the failure shown

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

* fix: drop a failure whose answer arrived even when a newer turn owns the error

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

* fix: free an answered failure whatever the turn that started meanwhile is doing

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

* fix: drop a rows read that a turn outran, rather than merging it under newer messages

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

* fix: drop a rows read whose conversation was left and opened again

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

* fix: hand over a file still being read when its composer goes

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

* fix: count a drop's routing as work in flight, so its file is handed over too

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

* fix: hold the send until every file a conversation is owed has landed

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

* refactor: keep a panel mounted per conversation instead of handing its draft over

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

* fix: keep the withdrawn chat whose composer was written in, not the empty one

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

* fix: keep the chat in front of the reader when both withdrawn composers were written in

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

* fix: keep a retry's own run arguments when a turn elsewhere refuses it

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

* fix: name panels apart across pools, and read a flow's inputs when its chat is built

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-29 11:36:30 +02:00
Diego Imbert 47525b211a perf: shrink the module graph that gates first paint in dev (#11373)
* perf: shrink the module graph that gates first paint in dev

* docs: drop the stale synchronous-icons claim on the import card

* fix: replay search opened before its modal loads, guard lazy icons

* fix: only intercept search before load where the modal mounts

* perf: mount app-shell modals on first open and keep monaco off the shell

* perf: load the icon map on first read, not at module evaluation

* fix: report stale chunks with a reload toast, guard the home page against monaco
2026-09-27 10:09:45 +00:00
hugocasaandClaude Opus 5 1d119e6b25 fix: correct the tool controls and name field of a nested AI agent (#11221)
* fix: hide tool controls a nested AI agent cannot use

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

* refactor: drop the tool navigation prop an agent tool now implies

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

* fix: ask for a tool name on every kind of agent tool

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

* fix: keep enabled_tools reachable on a linked nested agent tool

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

* docs: name the tool kinds the header's name field actually renders

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

* docs: state the tool-name rule without listing the kinds it covers

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-25 17:12:28 +02:00
AlexRV12andClaude Opus 5.5 1b74939952 feat: support aiagent steps in test_run_step (#11321)
* fix: support aiagent steps in test_run_step

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

* fix: offer the agent's tools as a picker in the step run form

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

* fix: run a blank tool list as no tools when the step form is bypassed

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-24 15:11:26 +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