* feat: reusable AI agent steps with hybrid linking and evals
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: make linked AI agents rigid (read-only) with unlink-to-fork
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: show inherited agent config read-only on linked step
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: edit/update a saved agent in place via upsert
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: bind linked AI agent tool inputs to host flow context
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: rebind linked AI agent tool inputs via graph tool nodes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: linked AI agent tool nodes, step test, and read-only card
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: remove ai_agent resource type migration, sync from hub instead
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: remove AI agent eval suite and run endpoint, defer to later
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: unwire eval routes, types and UI (completes eval removal)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: update reusable AI agents guide for eval removal and tool rebinding
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts for AIAgent agent/tool_inputs schema
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: strip brain transforms on link, avoid dirtying flow on tool open
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: flow-local test form and linked-agent marker in read-only graph
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: store linked tool overrides as diff from resource base
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: resolve linked agent tools in read-only viewer with fallback
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: use operating workspace, block non-static provider, warn on unbound tool inputs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: resolve linked parent's tools from resource for nested agent tool lookup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: scope linked-agent tools by flow path, thread workspace to path check and embedded viewer
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: strip flow-context tool inputs on agent save, drop unbound-inputs warning
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: persist agent edit mode across tool selection, show linked tool code read-only
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: show linked agent resource path in node definition panel
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: edit linked tool inputs in step panel, make tool nodes display-only
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: wire step-panel tool bindings (completes display-only pivot)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: single scroll for linked card, agent path as node label, drop fill-inputs in tool cards
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style: align linked-agent UI with design tokens and components
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: separate linked tool select target from module id to unbreak agent clicks
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@aanthropic.com>
* fix: save agent tool inputs verbatim, host flows override via tool_inputs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: scope agent edit state by flow path, require linked-tools scope at init
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: block saving an agent whose static provider is incomplete
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: type errors in agent tool bindings and save drawer input
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: key agent edit state by workspace, resync tool bindings on external changes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: include workspace in linked-tools scope and tool schema fingerprint
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: remove unused workspace prop from FlowModuleSchemaMap
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: drop linked-agent placeholder tool node, path label suffices
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: workspace-qualified resource links, guard stale tool schema loads
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: keep flow tool overrides out of the agent on edit, fold only on unlink
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: fold preserved tool overrides into the step on edit cancel
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: refuse overwriting non-agent resources on save, show memory kind on linked card
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: consume picker value, invalidate edit state on undo/reinit, cap nested agent tools
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: guard in-flight edit fork against restores, migrate edit state on rename
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: validate agent edit state by fork identity instead of path keys
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: key agent edit entries by fork marker alone, immune to editor nesting
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: keep agent edit state across structural graph edits and flow renames
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6kq9PYqNdc5q7ubidBYAs
* fix: centralize agent edit reanchor, guard in-flight saves, seed rename scope from flow path
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6kq9PYqNdc5q7ubidBYAs
* fix: ancestry-keyed edit reanchor and doc-scope sweep for republished linked tools
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6kq9PYqNdc5q7ubidBYAs
* fix: guard stale linked-tool fetches and resolve while-loop nested linked agents
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6kq9PYqNdc5q7ubidBYAs
* fix: drop empty tool override entries on revert and correct stale viewer comment
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6kq9PYqNdc5q7ubidBYAs
* docs: drop stale eval mention from the linked-agent comment
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: deploy linked agent resource, guard viewer fetches, align tools schema
Address review findings on the reusable-agent branch:
- Cross-workspace deploy never collected a linked step's `agent` resource, so
the deployed flow failed at runtime unless the agent already existed there.
- The read-only viewer published resolved tools without the generation guard
flowState uses, letting a superseded link's tools win a race. Share one
guarded publisher (`publishLinkedAgentTools`) between both call sites.
- `tools` was still required in the OpenFlow AiAgent schema while the
deserializer defaults it, rejecting hand-authored linked steps; make it
optional and narrow the call sites.
- Overlay `tool_inputs` in the non-linked branch too, so a flow persisted
while a step sits in "Editing" mode still binds tools to this flow.
- Cap the linked-tools store's scope map; nothing evicted it before.
- Drop the orphaned `.sqlx` entry left by the eval removal, regenerate the
copilot OpenFlow schema, and fix the generator's nested-`z.record` arity.
- Move `refreshFlowStateStore` out of `agentEditStore` into its own module.
- Document that linked agents' tool scripts are outside the lock pipeline.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts for optional AIAgent tools
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: follow saved-agent deps on deploy, accept the linked shape in the schema
Round-18 review findings:
- Deploying a linked flow queued only the outer ai_agent resource. Follow
`$res:` refs inside a resource value (every UI-saved agent has a provider
resource) and the agent's own tools, which reference scripts, flows, MCP
resources and nested linked agents by bare path.
- The AiAgent input_transforms schema still required provider/output_type,
so it rejected the very shape linking persists (brain transforms stripped,
flow-local inputs kept). Only user_message is always present.
- dfs traversed `value.tools` unconditionally through a cast, which throws on
a linked module that omits it now that the field is optional.
- Trim the flow-refresh invariant comment to the 4-line limit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: recurse into inline nested agent tools on deploy, require provider when unlinked
Round-19 review findings:
- The deploy walk only inspected a saved agent's top-level tools, so an inline
nested agent tool's own scripts, flows and MCP resources were skipped.
Recurse into it; a linked one is still queued as a resource instead.
- Normalize a `$res:`-prefixed MCP tool resource_path like other refs.
- Dropping provider/output_type from the schema's required list also let a
standalone providerless agent validate, which deploys clean and then fails
on every run. The constraint can't go in the schema: an `anyOf` makes
AiAgent a union, which breaks the FlowModuleValue discriminated union it
belongs to (verified: zod throws "Invalid discriminated union option").
Enforce it in validateFlowModules instead, next to the other cross-module
checks, via a shared collectProviderlessAgentIds.
- Correct the deploy paragraph in the docs: provider resources are traversed
now.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: follow linked tool_inputs overrides on deploy, untrack vitest artifact
Round-20 review findings:
- A linked step's `tool_inputs` override replaces the resource tool's default
at runtime, so a static `$res:`/`$var:` override is the dependency the flow
actually uses. The deploy walk queued only the saved agent, leaving runs in
an empty target workspace to fail on the missing override target. It also
never scanned an aiagent module's own input_transforms, since the scan was
gated to script/rawscript/flow.
- Extract the pure walkers to deployDependencies.ts and cover them: three
rounds have each found a further gap in this one function.
- Untrack a vitest cache artifact committed by accident, and ignore a
repo-root node_modules/ (only per-package paths were listed).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: collect inline agent provider and tool deps, correct tool_inputs docs
Round-21 review findings:
- An inline agent's provider credential sits inside an object-valued static
transform, so the top-level string check missed it and such a flow deployed
without its provider. Walk transform values instead of string-matching them.
- An inline agent's own tools were only partly reachable: getAllModules drops
MCP and websearch tools, so their resources were never queued. A standalone
agent module now recurses through agentResourceDependencies, and the module's
own input_transforms are scanned inside aiAgentModuleDependencies so one
function owns the whole step rather than splitting it with the caller.
- `tool_inputs` was documented as empty/absent for non-linked steps, which
contradicts the runtime applying it when `agent` is unset so a flow persisted
mid-Edit keeps its bindings. Describe that case in both the Rust doc and the
OpenFlow description.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep linked steps brain-free on load, gate stale agent fetches, log linked tools
Round-22 review findings:
- loadSchemaFromModule filled every AI agent schema key with a placeholder
transform, re-adding provider/memory to a linked step that deliberately
carries none — persisted on the next save and rejected by the generated
Copilot schema. Fill only the flow-local keys when the step is linked.
- The linked-resource fetch was neither aborted nor tagged, so switching a
step from agent A to B could publish A's tools under B and show A's brain
next to B's link. Tag each result with the (workspace, path) it was fetched
for and drop the ones that no longer match.
- "Test this step" passed no tools for a linked agent, and the log viewer
drops tool_call entries it cannot resolve to a definition, so the agent's
invocations vanished from the log. Pass the resolved resource tools.
- Correct the cancel-edit comment: the runtime does apply tool_inputs on an
unlinked step, and folding is what leaves nothing for it to overlay.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: pin the edit session across saves, resolve linked tools in the run viewer
Round-23 review findings:
- Cancel stays enabled while a save awaits its requests, and it keeps the
`tools` array identity, so the old guard passed and the completing save
relinked the step and cleared the edits Cancel had just kept. It also
accepted any replacement edit marker. Pin the path being saved and require
the marker to still hold it, which still tolerates a content-preserving
refresh re-anchoring the marker onto a clone.
- Resolve linked agents' tools in the run/status viewer too: it reads
module.value.tools straight from raw_flow, which is empty for a linked step,
so AIAgentLogViewer dropped every tool_call it could not match and the graph
drew the agent with no tool nodes. Same gap the previous commit closed for
"Test this step" only.
- Drop the overlay call-site comment: it claimed resource defaults are
discarded and unmatched keys ignored, while overlay_tool_inputs preserves
defaults and inserts new keys, as its own test asserts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: scope linked tools without the trigger-node path, keep the standalone save guard
Round-24 review findings, both regressions from the previous commit:
- Passing `path` to the run viewer's graph also switched on its Trigger node
(`triggerNode ? path : undefined`), which reads a TriggerContext that
/run/[...run] does not provide — the page threw "Cannot read properties of
undefined (reading 'triggersCount')". Give the graph a separate
`linkedToolsPath` for the tools bucket so the two stay independent.
- The rewritten save guard tracked only the edit path, so a plain "Save as
agent" no longer noticed the step being replaced mid-request (undo, session
sync): the replacement has no edit path either, so the stale completion
relinked it and stripped its brain. Keep the array-identity check when there
is no edit session, and use path re-anchoring only when there is one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep recorded tool calls in run history, send tool_inputs from step previews
Round-25 review findings:
- The agent log viewer dropped any recorded tool_call whose definition it
could not find among the supplied tools, so renaming or removing a tool —
or losing read access to a linked agent's resource — erased calls that had
actually run. Render the recorded call labelled by its function name; its
args, logs and result come from the child job, not the definition.
- "Test this step" sent tool_inputs only for a linked step, but a step forked
for editing has no `agent` while still carrying the flow's bindings, which
the runtime overlays. The preview ran resource-authored defaults instead of
the bindings under test. Send them from both branches.
- Polling a running flow replaces `job` every tick, so the run viewer re-read
every linked agent's resource each time. Key the fetch on the set of linked
steps instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: never discard edits made during a save, isolate the run viewer tools bucket
Round-26 review findings:
- The agent editor stays live while a save is in flight, so edits made after
the snapshot were not in the resource yet linking stripped them from the
step too, losing them outright. Compare the config against the snapshot on
completion and, if it moved, leave the step alone and tell the user to save
again.
- The run viewer published into the editor's `${ws}:${flow path}` bucket, so
opening an older run in the preview pane could flip the edited flow's tool
nodes to that run's agent. Key it by job instead.
- Drop the now-unreachable undefined filter in the agent log viewer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: claim the linked-tools generation on direct publishes and clears
Round-27 review findings:
- The step editor wrote resolved tools (and cleared them on unlink) straight
into the store, leaving the fetch generation untouched. An older in-flight
load for the previous agent then still passed its own check and overwrote
them, so the graph and binding editor could show agent A while the step
links to B. Claim the generation before those writes.
- Correct two comments that still described unmatched tool calls as dropped;
they are kept and labelled by their recorded name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: retain the loaded linked agent, rebuild run logs when tools resolve
Round-28 review findings:
- Rejecting a superseded resource response left the card with nothing: a late
reply for a previous agent replaces `linkedResource.current` and no refetch
follows, so the linked step lost its brain, tools and provider warning until
remount. Retain the last response that matched the current link instead.
- The agent log viewer built its module list on mount only, so a linked
agent's asynchronously resolved tools never replaced the placeholders, and
switching between completed runs reused the first snapshot. Rebuild on a
value key — callers rebuild the agentJob object each render, so tracking its
identity would reload in a loop.
- Refresh a linked-tools scope's recency when it is read, not only when it is
published: a run viewer opens one bucket per nested job, which could
otherwise evict the bucket a still-displayed run is using.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: supersede stale log reloads and stale tools on a link change
Round-29 review findings, both on the reloads added last round:
- Every prop change starts another loadToolCalls, and it awaits child-job
requests before writing the shared view, so a slower reload for a previous
run could restore its logs and tool states over the run now selected — or
replace newly resolved definitions with an earlier empty-tools snapshot.
Build the states locally and let only the newest load publish, including the
parent's index-keyed job cache.
- While a newly linked agent resolves, the previous agent's tools stayed in
the store, so its bindings were editable against a step already linked
elsewhere, and a failed load left them indefinitely. Clear them once the
link moves away from what this component published; tools resolved at flow
load are untouched, so selecting a step still doesn't flicker.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: resolve a run's linked agents in the run's own workspace
Round-30 review finding: the run viewer fetched linked agent resources with
the navigation workspace, but session and fork previews render it with
`workspaceId` pointing elsewhere. Those runs resolved nothing — or an
unrelated resource sharing the path — losing tool nodes and log definitions.
Prefer the explicit override, then the job's own workspace. The store scope
stays keyed on `workspace` so it still matches what FlowGraphV2 reads; the
job id in the key already makes the bucket unique.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: refetch a run viewer's linked tools if its scope is evicted
Round-31 review nit: the viewer publishes one scope per mounted nested job,
hidden ones included, so a loop with many loaded iterations can push a
displayed scope past the store's cap. Nothing refetched it afterwards — the
set of linked steps had not changed — leaving the run without tool nodes or
log definitions. Track the store and republish when the bucket is gone;
publishing always writes a key, so this settles instead of looping.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: retain in-use linked-tool scopes instead of refetching evicted ones
Round-32 review findings. Republishing an evicted scope settles for one
scope but not against the cap: with more than 32 mounted nested jobs holding
linked agents, restoring one necessarily evicts another, and that mutation
reran every viewer's effect — an endless round of resource requests.
Hold a scope for as long as a viewer is mounted and skip retained scopes when
evicting, so buckets in use are never dropped and nothing has to refetch. The
cap yields to correctness when everything mounted is in use.
Dropping the publish key also restores refetching when the fetch workspace
changes for an otherwise unchanged job and link.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: guard non-static brain edits during save, retain every displayed scope
Round-33 review findings:
- The in-flight edit guard compared the saved config, which holds only static
brain values. A computed system prompt, memory or temperature changed while
the save was awaiting the API therefore compared equal, and linking stripped
it with no warning. Compare what linking actually discards — every brain
transform and the tools — leaving the flow-local inputs free to change.
- Retaining run-viewer scopes made them fill the cap, and eviction then picked
any unretained scope, including the editor bucket a user is looking at, with
nothing to refetch it. Retain the scope each graph draws from for as long as
it is mounted, so every displayed bucket is protected.
- A failed agent job has no parseable action list; the loader returned early
and left the previously selected step's tool tree under the new header.
Clear the view instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: resolve only flow modules in viewer scans, prune scopes on release
Round-34 review findings:
- Both viewer scans used the default dfs, which descends into agent tools, and
published each linked agent under its bare id. Tool ids imported from a
resource are not flow-global, so a nested linked agent sharing an id with a
top-level step superseded that step's fetch and showed its tools instead.
Scan flow modules only — the graph resolves the store per module node.
- Scopes skipped while retained were never reconsidered, so closing views left
the store over its cap for the tab's life. Prune on release too.
- Correct two comments that still argued the premises the retain mechanism and
the read-recency policy replaced.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: don't report success when a save left the step unlinked
Round-35 review nits:
- persist warns that changes made during the save are not in the resource and
leaves the step alone, but both callers then toasted success unconditionally,
burying the only actionable message. Report whether the step was linked.
- Condense the tool_inputs invariant to the four-line limit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: seed the published link at mount, keep run history for toolless agents
Round-36 review findings:
- `publishedFor` started unset, but initFlowState has already published for the
step's link by then. A link change landing before this component's own
request therefore skipped the clear, leaving the previous agent's tools under
the new link — indefinitely if the new one fails. Seed it from the link at
mount.
- A standalone agent that omits `tools` kept `undefined` here, and the gate
downstream then hid the AI message and tool-call history behind the generic
result view. Default to an empty list like the other consumers.
- A save that lands after the step was replaced writes the resource but leaves
the step alone; say so instead of closing the drawer with no outcome.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: qualify nested agent tool store keys, keep an empty tools identity stable
Round-37 review findings:
- The step editor keyed the linked-tools store by the bare module id for
nested agent tools too. Those ids come from a resource and are not
flow-global, so a nested linked agent sharing an id with a top-level step
read that step's tools — then overwrote them once its own fetch landed.
Qualify the key by the parent agent, as the edit store already does; flow
modules keep the bare id the graph looks up.
- The `tools` binding handed the editor a fresh [] on every read when the
module omits the field — a shape this PR made valid — so the save guard's
identity check never matched and such a step could never link. Read through
one shared empty array instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: accept the first tool on an agent module that omits tools
Round-38 review nit: the graph's tool insert required an existing `tools`
array, so a module authored without the field — valid since `tools` became
optional — swallowed the insert while still pushing history and dispatching a
change. Create the array on first use.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: don't evict a scope on the write that created it, and cover the store
Round-39 review findings:
- A rename removed the retained old key from the order but the new one is not
retained until readers re-run, so eviction deleted the fresh bucket
immediately. Reorder without evicting; the next publish or release enforces
the cap, by which point the new key is held.
- Writing the test for that surfaced the same shape in touchScope: it evicts
right after appending, so once every older scope is retained the scope just
published was the only eligible victim and was dropped at once. Exclude the
scope being written.
Add the store's first test: retention, eviction past the cap, pruning on
release, and the rename handoff — four rounds landed fixes here with nothing
pinning the behaviour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: re-resolve linked agents when a wholesale edit changes the links
Round-40 review findings:
- Undo/redo, YAML apply, AI apply and session restore swap a step's `agent`
without re-running initFlowState, and the step editor only watches the step
it is mounted on — so an unselected step kept showing, and binding against,
the previous agent's tools. Re-resolve from the editor whenever the set of
links changes.
- Document that linked resolution is live rather than pinned: an edit landing
mid-run affects steps that have not started, and a nested agent tool looks
its definition up by id when its own job starts, so it can run a changed
definition. Pinning would mean carrying the resolved definition into the
child job instead of its id; inline agents are unaffected because their
tools are snapshotted with the flow value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: per-module empty tools identity, invalidate tools when a link is replaced
Both findings are over-corrections in the two preceding commits:
- The shared empty-tools array made identity stable, but stable everywhere: a
wholesale edit that keeps the module id reuses the component, so when both
the old and the replacement module omit tools the save guard saw no change
and could link and clear the replacement. Hand out one empty array per
module value, which a replacement always renews.
- The editor's link watcher resolved the replacement agent without dropping
the previous one's tools first, so a step selected before the fetch landed
still showed agent A under link B — and the freshly mounted editor seeds
itself from B, so it could not tell. Clear the entry when the link for a
module changes, seeding the map from the graph so the first run doesn't
refetch what initFlowState just resolved.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: reserve graph space for linked tools, re-resolve only changed links
Round-41 review nits:
- The layout reservation read the module's own `tools`, which is empty for a
linked agent, so its display-only tool nodes were drawn over the node above
in read-only viewers. Count the resolved tools for a linked step.
- The editor's link watcher refetched every linked agent on each run. Resolve
only modules whose link actually changed, and skip the pass entirely on a
rename, where the scope sweep has already carried the buckets over.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: protect a renamed scope until it is retained, drop the phantom tool row
Round-42 review nits:
- Readers release the old scope before retaining the new one, so a migrated
bucket is unretained in between and, over the cap with everything else held,
was the only thing eviction could take. Protect a just-migrated scope until a
reader retains it, and cover that release/retain order in the store test.
- The layout reserved an add-tool row for linked agents, which have no add-tool
node, leaving dead vertical space. Match computeAIToolNodes.
- Re-resolving links no longer short-circuits on a rename: comparing each
module still costs nothing when only the path changed, and a restore that
renames and relinks in one tick now gets both.
- Hoist the duplicated linked-tools lookup in the graph's store update.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: kill a scope's in-flight fetches before migrating it
Round-43 review finding: fetch generations are keyed by (scope, module), so a
resolution still running against the pre-rename scope keeps a valid generation
there. It publishes into the old bucket after the rename, and the doc-scope
sweep — which gives the source precedence — carries it forward over a link
resolved since under the new scope, leaving the graph and binding editor on the
previous agent's tool ids with nothing to refetch them.
Invalidate the source scope's fetches before each migration, and pin the
behaviour: the new test fails without the invalidation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: re-resolve links a scope sweep cancelled, and only sweep a real bucket
Round-44 review findings, both on the previous commit:
- Invalidating the source scope killed fetches that were perfectly current —
a link still loading when the rename landed — and nothing restarted them,
because the watcher already records that link. Resolve again, in the
destination, every link the migration left without tools.
- The doc-scope sweep ran on every store version bump, so during a draft
refresh the first completed fetch cancelled the others mid-flight. Skip the
sweep entirely when the source scope holds nothing.
- Condense a six-line invariant to the four-line limit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: split rename from doc sweep, hide brain fields of nested linked agents
Round-45 review findings:
- Two reviewers disagreed about invalidating a scope whose bucket is empty,
because the two callers differ. A rename is a cut-off: every fetch still
running against the old scope is stale whether or not anything resolved
there, so it always invalidates. The doc-scope sweep has no cut-off — those
fetches belong to the refresh in progress — so it still waits until that
scope holds something.
- Recording the swept links as published undid the rename+relink fix: a
restore that renames and swaps a link in one tick would keep the previous
agent's tools with nothing to refetch them. Leave that comparison to the
watcher, which compares links rather than presence.
- A nested agent that is itself linked was offered the whole agent schema in
the tool bindings, but the runtime overlays only its flow-local inputs, so
the rest were collected and dropped. Show what actually applies.
- Condense the hybrid-linking comment to the constraint.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: don't resolve a shared agent's tool defaults when loading it
Round-46 review finding: the whole agent resource was interpolated before
tool_inputs was overlaid, so each tool's default `$res:`/`$var:` resolved
first. A host flow overriding a default that points at the author's resource
still had to resolve that resource, and an unused tool whose default is
unreadable in the consumer's permission context failed the agent outright —
defeating the point of sharing an agent across contexts.
Read the resource raw, overlay the host's overrides, and interpolate only the
brain; each tool resolves its effective inputs when it executes. The nested
tool lookup reads raw too, since it only needs definitions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: interpolate the brain before overlaying caller inputs
Round-47 review findings, all on the previous commit:
- user_message and user_attachments were inserted before interpolation, so
they went through it a second time: a user message of `$WM_TOKEN` expanded
to the job token and was sent to the model provider. Interpolate the
resource first, then overlay the already-resolved flow-local inputs.
- The relink watcher skips tool nodes, so a linked agent nested as a tool kept
the previous agent's entry through undo, YAML/AI apply or a session restore,
and the step editor seeds itself from the new link and cannot tell. Emit the
ancestry-qualified key for those too.
- Correct the guide, which still named the interpolation path this branch
replaced, and condense two invariants to the four-line limit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: deploy $jsonvar deps, key run logs by tool identity, seed only top links
Round-48 review findings:
- The deploy walkers recognised `$res:` and `$var:` but not `$jsonvar:`, which
the worker resolves too, so a secret referenced that way by an agent brain,
a saved tool default or a host override never reached the target workspace.
- The run log rebuilt only when a tool's name or the tool count changed, so a
refreshed resource that altered a tool's path, code or id behind the same
name kept showing the old definition. Key on the array identity instead: the
store swaps it exactly when the contents differ.
- Nested linked agents were seeded as already published, but initFlowState
resolves only top-level links, so their tools never loaded until their
editor was opened. Seed what initFlowState actually publishes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: let the watcher's fetch survive the step editor's stale-clear
Round-49 review nits:
- On a relink the step editor claimed the fetch generation before clearing the
previous agent's tools, which discarded the watcher's already-running fetch
for the new link. The tool nodes then only appeared if the step stayed
selected until the editor's own refetch landed. Clear without claiming: the
watcher superseded the old fetch when the link changed, so nothing stale can
return. Unlink still claims, since no watcher fetch covers it.
- Condense the store's opening invariant to the four-line limit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: condense the stale-clear invariant
Round-50 review nit. Also records why the branch deliberately doesn't claim a
fetch generation: a reviewer asked for the opposite this round, but writing
`agent` re-runs the editor's watcher, which supersedes the old fetch and
starts one for the new link — claiming here would discard it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: guard Edit/Unlink by step identity, not just the link path
Round-51 review finding: forkFromResource compared only the agent path after
its fetch, so a module replaced mid-request while keeping the same link passed
the check — the stale continuation then wrote the fetched brain and tools into
the replacement and unlinked it. Compare the step's own `tools` array too,
which is one instance per module value and so identifies the step.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: report an Edit or Unlink abandoned because the step changed
Round-52 non-blocking note: forkFromResource returns undefined when the step
was replaced mid-request, and both callers treated that as do-nothing, so the
click looked ignored. Say what happened, as the save path already does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: hugocasa <hugo@casademont.ch>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@aanthropic.com>
* feat(ai-agent): use a real tool description instead of the tool name
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): render tool-name error full width and hoist it above the description
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): make tool description field hug its content so a single line is vertically centered
Add an optional minHeight param to the autosize action (default unchanged at 30px) and pass minHeight 0 for the tool description so an empty/one-line field no longer reserves the 30px floor and leaves dead space below the text.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(ai-agent): regenerate OpenFlow-derived prompts, CLI guidance, and copilot zod schema for tool description
Fixes the check-freshness CI failure (system_prompts + skills.gen.ts) and makes the flow copilot's openFlow.json / openFlowZod.gen.ts aware of the new AgentTool.description field so AI-authored tools can set it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: accept bunnative language in AI chat flow step validation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: regenerate copilot flow schema from openflow spec
Run gen_openflow_schema.sh + minifiedOpenflowJson.sh instead of hand-patching. Also syncs three fields the checked-in generated files had drifted from since the last regen (reasoning_effort, reasoning_token_delta streaming event, aiagent tag).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts for bunnative openflow schema
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-agent): support reasoning effort in AI agent workflow steps
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): round-trip native Anthropic thinking blocks and fix DeepSeek/Mistral reasoning
Address review: native Anthropic now captures the signed thinking block during streaming and replays it before tool_use across iterations (prevents a 400 on multi-turn tool use). DeepSeek 'off' sends thinking:{type:disabled} instead of the rejected reasoning_effort:none, and Mistral drops temperature when reasoning is on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-agent): move reasoning effort into the provider/model selector
Store reasoning_effort on ProviderConfig (next to the model) instead of a separate flow arg, and render the selector inside AIProviderPicker under the model dropdown. Add an explicit 'off' option on models that disable reasoning by omission (e.g. Claude), so reasoning can always be turned off from the UI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai-agent): use DropdownV2 for reasoning effort, matching copilot chat
Replace the Select combobox with the same DropdownV2 action-menu the copilot chat reasoning selector uses. Each option carries an action instead of a bound value, so click selection is unambiguous and there is no typeahead/sentinel-value mismatch on the off/default entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(ai-agent): regenerate system prompts for ProviderConfig.reasoning_effort
Refresh system_prompts/auto-generated and cli skills.gen after adding reasoning_effort to the OpenFlow ProviderConfig schema (check-freshness).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): clear stale reasoning effort on model change; dedup bedrock reasoning folding
Address cubic review: (P1) the reasoning picker now clears the stored effort when the newly selected model doesn't accept it (e.g. carrying 'xhigh' from Opus onto a model that tops out at 'high'), not only when the model can't reason at all. (P3) the proxy's accumulate_reasoning_delta now delegates to the shared bedrock_stream_event_to_reasoning_delta so worker and proxy folding can't drift.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-agent): stream reasoning summary and show a thinking affordance in flow chat
Add StreamingEvent::ReasoningTokenDelta, emitted from every worker reasoning path (Anthropic native thinking deltas, Bedrock, Gemini thought parts, OpenAI-compatible reasoning_content, OpenAI Responses reasoning_summary_text with summary:auto). The flow chat parses it and renders a collapsible 'Thinking' affordance on the assistant message (thinking tokens bill regardless of display, so surfacing the summary is billing-neutral).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): persist streamed reasoning onto the finished chat message
Reasoning isn't stored server-side, so the completion re-poll (which swaps temp messages for the persisted ones) was dropping the streamed thinking summary. Carry it onto the final assistant message so the 'Thought process' affordance survives the run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-agent): match flow-chat thinking box to the copilot chat reasoning UI
Replace the plain <details> thinking affordance with the same bordered, collapsible reasoning box the copilot chat uses (chevron + Brain/spinner + 'Thinking' header, markdown body, expand-while-streaming/collapse-on-answer).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): attribute streamed reasoning per turn by content; drop duplicated comment
Address review: the completion-poll carry-over now matches each temp assistant turn's thinking summary to its persisted message by content, so a multi-turn response (reasoning -> tool call -> final answer) no longer misattributes an earlier turn's thinking to the final answer or drops intermediate turns. Also removes a leftover duplicated comment block above the AIReasoningEffortPicker effect.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): address review round 2 (carry-over edges, off-token validity, aria, test)
cubic round 2: (1) reasoning carry-over now consumes temp turns in order verifying content, so identical/empty-content multi-turn responses attribute thinking correctly and reasoning-only turns aren't dropped; (2) the picker's stale-value check only accepts the off token when the model can actually disable reasoning; (3) add aria-expanded to the Thinking toggle; (4) add a test for the failed tool_result path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): add bottom margin below the flow-chat thinking box
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): don't request OpenAI reasoning summary, matching the copilot chat
OpenAI gates reasoning summaries behind org verification, so requesting summary: auto would 400 for unverified orgs. The copilot chat requests effort only and never asks for a summary; align the worker with it (reasoning: { effort }) and drop the now-unreachable summary-delta parsing. OpenAI reasoning no longer streams a summary in flow chat (consistent with the copilot); Anthropic/Bedrock/Gemini/DeepSeek reasoning display is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): scope reasoning carry-over to newly persisted messages
cubic round 3: matching by content over the full history could attach a new turn's reasoning to an older message with identical text. Restrict eligible targets to the messages just fetched for this response (via afterSeq), so historical turns are never touched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): carry reasoning newest-first instead of gating on the final poll
cubic round 4: gating the carry-over on the final poll's filteredResponse dropped reasoning for messages already fetched by an earlier streaming poll (their id is excluded by afterSeq). Walk persisted newest-first and consume the newest matching pending summary, stopping once summaries run out. This response's turns are always at the end, so they claim their own reasoning (P1) before older history is reached (P2), regardless of which poll persisted them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai-agent): drop flow-chat reasoning display, keep backend + effort picker
The chat-side thinking box relied on non-deterministically matching streamed (ephemeral) reasoning back onto persisted messages, which kept spawning edge cases. Remove the flow-chat display entirely (ChatMessage box, FlowChatManager carry-over/threading, parseStreamDeltas reasoning) and keep the sound backend: per-provider reasoning-effort requests, thinking-block round-trips for tool calls, and ReasoningTokenDelta streaming. A display can be built on top later, deterministically (e.g. once the stream carries the persisted message id).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): include reasoning_effort in default-config compare; document reasoning_token_delta
Codex/Pi nits: isSameAsStoredConfig now compares reasoning_effort so the 'use as personal default' toggle reflects effort-only changes; openflow streaming-events doc lists the reasoning_token_delta event (regenerated auto prompts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai): add Azure AI Foundry as a native AI provider
Adds `azure_foundry` as a new AIProvider variant wired through the AI
chat (copilot) and AI agent flow steps. Foundry's chat completions API
is OpenAI-compatible and uses Azure conventions (api-key header, Azure
URL building), so it reuses the existing OpenAI-compatible query builder
and proxy path via the shared `is_azure` helper (renamed from
`is_azure_openai`).
Backend (windmill-ai):
- New `AzureFoundry` enum variant (serde `azure_foundry`)
- `get_base_url` requires a resource base URL (like Azure OpenAI / Custom)
- `is_azure()` covers Azure OpenAI + Foundry (api-key auth, Azure URL)
- Added to OpenAI-compatible proxy support and HttpForward proxy mode
- New proxy URL unit test
Frontend (copilot):
- New provider entry, completion config, model-token handling, streamed
usage tracking, and reasoning registry (all model-id-gated, so a no-op
for Foundry's non-OpenAI catalog)
- Treated as a chat-completions provider, not the OpenAI Responses API
OpenAPI:
- `azure_foundry` added to AIProvider (openapi.yaml) and AIProviderKind
(openflow.openapi.yaml); regenerated CLI guidance
Note: the `azure_foundry` resource type (base_url + optional api_key) is
hub-managed and must be published to the Windmill Hub separately.
Fixes WIN-2122
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): add azure_foundry to copilot flow Zod provider enum
The tracked copilot flow schema (openFlowZod.gen.ts and its openFlow.json
source) still carried the old AIProvider enum, so validateFlowModules /
validateSpecialFlowModule rejected AI-generated flow edits that create or
update an aiagent module with provider kind "azure_foundry" before they
could be saved. Add the value to both (preserving the generated single-line
format) and a regression test over the flow-module validation path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai): lead provider list with OpenAI, Anthropic, Google AI
Reorder AI_PROVIDERS so the three primary direct providers come first. The
AIProviderPicker renders the first three entries as quick-access buttons, so
these become the defaults (previously OpenAI, Azure OpenAI, Azure Foundry);
Azure OpenAI / Azure Foundry stay adjacent right after. No logic depends on
provider order (only per-provider defaultModels[0] is read).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>