Commit Graph
32 Commits
Author SHA1 Message Date
574775d50c fix: teach the AI the raw-app job bindings, the SDK reference and the draft/deployed split (#10754)
* feat: teach the AI the raw-app job bindings and the draft/deployed split

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

* fix: scope the raw-app deploy advice to the referenced item, and stop kind-conversion from stranding fields

The draft/deployed guidance added in the previous commit was read as "deploy the
app too": the agent asked for both the flow and the app and routed a one-item
dependency through the review-and-deploy page. Only the referenced flow or
script has to exist deployed — the preview runs the app's draft — so the prompts,
the `write_app_runnable` warning and the testing rule now say to offer that one
deploy and leave the app a draft.

`buildPersistedRunnable` spread the existing runnable when rewriting it, so
converting a path runnable to inline left `runType`/`path` behind (and the
reverse left `inlineScript`). `isRunnableByName` matches the inline branch
first, so an app "wired to a flow" silently ran stale inline code.

`test_run_app_runnable` now fills ctx-bound inputs with `$ctx:<prop>` the way
RawAppBackgroundRunner does, so a ctx argument no longer arrives missing.

The SDK-reference rationale claimed WM_TOKEN may be unset, that a missing base
URL falls back to localhost, and that a job token is scoped enough to 403 a
hand-rolled REST call. None of the three is true, and it shipped to every
write-script prompt; the text now only says the client configures itself.

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

* fix: address review round on the raw-app AI instructions

The eval case could pass on the exact answer it exists to reject. Every
`requiredMentionsAnyOf` alternative but one was flow-agnostic, so "the app must
be deployed" satisfied "must be deployed". All alternatives now name the flow,
and a unit test pins that the app-only phrasing fails.

`instanceLine` asserted "self-hosted Community Edition" outside the browser,
where `isCloudHosted()` reads false and the license store is unset — so every
global eval was told that regardless of what it pointed at. It is now emitted
only under BROWSER.

`assistantExpect.forbiddenMentions` defaulted a missing `assistantText` to "",
which passes every entry forever on a mode whose runner does not report it.
It now fails with that as the reason.

`buildPersistedRunnable` carried `schema` across a retarget, so a path runnable
pointed at a new flow kept the previous item's schema and `genWmillTs` typed
`backend.<key>(args)` from the wrong inputs. It survives only while kind and
path both match.

The SDK header claimed "a function that is not listed below does not exist".
`windmill-client` also exports the generated services, and the Python client
exposes `Windmill.get`/`.post`, so an endpoint without a helper had no legal
move. Each language now names its own escape hatch.

`getAppInstructions` said the attached reference carries the TypeScript SDK even
when `language: "python3"` had swapped in the Python one — on the very sentence
telling the model to make that call.

The kind-conversion comment claimed a hybrid runnable "silently runs stale
inline code". It does not: `isRunnableByName`, `isRunnableByPath`,
`convertPersistedToBackendRunnable` and `rawAppPolicy.processRunnable` all
dispatch on `type` alone. The leftovers contradict the runnable's kind rather
than override it, which is what the comment now says.

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

* fix: address round-2 review nits on the raw-app AI instructions

`flow is deployed` was satisfied both by "once the flow is deployed, the button
works" and by a hallucinated "done — the flow is deployed", which eval mode makes
impossible and the drafts-only judge cannot see. Every alternative now states an
outstanding obligation, and two more real phrasings ("will need to be deployed")
are accepted so a correct answer is not failed on wording.

Condenses the three comment blocks that ran past the four-line limit in
AGENTS.md, and drops two claims inside them that no longer hold: the
`testRunAppRunnable` doc said it runs a runnable the way the app's own frontend
does (it is the editor preview, which a deployed app's stored policy does not
match), and `undeployedRunnableTargets` described its argument as the write
tool's raw input when the call site passes the persisted runnable.

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

* fix: report the real cause when a test run fails, and label the app-runnable card

Driving `test_run_app_runnable` in a live session surfaced two defects the
API-level check could not see.

`executeTestRun` built its failure message from `error.message`, which the
generated client leaves as the bare status text while the server's message sits
in `body`. A path runnable aimed at an undeployed flow reported "Not Found"
instead of "Not found: flow not found at name u/admin/current_time" — dropping
the one diagnostic the run exists to produce. `formatToolError`, in the same
file and written for exactly this, now does it. This also applies to
test_run_script and test_run_flow, which had the same loss.

The completion card read "Flow test completed successfully" for an app runnable,
because `contextName` doubles as the jobs-tray kind and a path runnable pointing
at a flow really does queue a flow job. A `completionName` override now names
what ran without changing the kind.

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

* test: pin the deploy expectation against wrong answers, not just correct ones

`deploying the flow` was satisfied by "done deploying the flow" — a deploy the
agent only claims to have made, which eval mode makes impossible and the
drafts-only judge cannot see. Replaced with the prospective forms, and dropped
the same reading from the workflow variant.

Three review rounds each found this same class of hole in the phrasing list, so
the list is now exercised against the wrong answers themselves rather than
eyeballed: naming the app as what needs deploying, claiming the deploy is
already done, claiming to have deployed the flow, and saying nothing about
deploying all have to fail, while four real correct phrasings have to pass. The
test reads the case out of global.yaml, so a future edit to the alternatives is
checked by it.

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

* test: drop the tense-neutral deploy alternatives and cover completed claims

A gerund after a preposition carries no tense, so `before`/`after`/`by deploying
the flow` all match a deploy the agent only claims to have made ("after
deploying the flow, I clicked the button and it returns the greeting") just as
the bare gerund did. All three are gone rather than swapped for whichever reads
least badly, and the two completed-deploy phrasings are now negative fixtures.
The remaining alternatives are imperative or obligational, which a claim of
having already deployed cannot satisfy.

Condenses the two comments this list carries: the YAML block to four lines, and
the test's rationale to the durable constraint about substring matching.

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

* fix: encrypt sensitive inputs when test-running an app runnable

`test_run_app_runnable` sent `force_viewer_static_fields` but not
`force_viewer_sensitive_inputs`, which every other preview path derives from
the runnable's `sensitive` user fields. That list is the only thing driving the
encryption loop in apps.rs, so testing a runnable with a sensitive input wrote
the real value into the job's args in plaintext, readable by anyone with run
access to the workspace.

Verified against a running EE instance. With the list, `api_key` is stored as
`$encrypted:mvqtSRI9…` and the sentinel appears nowhere in the job record;
without it, the sentinel is readable in run details. A non-sensitive field is
left plaintext either way.

The tool claims parity with the editor preview, so it uses that same filter
(`type == 'user' && sensitive`) and omits the field entirely when empty.

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-08-20 11:17:33 +02:00
GuilhemandClaude Opus 5 2b4369d7cb fix: reject invalid AI agent tool names when the chat writes a flow (#10756)
* fix: reject invalid AI agent tool names when the chat writes a flow

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

* fix: address review nits on agent tool name validation

Share one AI-agent walk between the providerless-agent and invalid-tool-name
collectors, drop the unused validateToolName, and list every reserved id in the
tool naming rules.

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

* fix: describe an agent tool's summary as the name the agent calls it by

The OpenFlow schema described `AgentTool.summary` as a short description of
the tool, which is the same schema the flow write tools hand the model, so it
pulled against the naming rules. Narrow those rules to flowmodule tools, since
websearch and mcp tool names are never regex-checked, and let `kind` take
either vocabulary its callers resolve.

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

* fix: name-check only the agent tools whose summary the agent calls

An mcp tool exposes the MCP server's own tool names and a websearch tool's
summary is a plain label, so neither reaches the worker's name check. Both
default to an empty summary in the editor, which the chat then refused to
write back.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 10:49:01 +02:00
Ruben FiszelandClaude Opus 5 aeaea57ca1 fix(wac): one failure record for tasks and steps, in every round (#10368)
* fix(wac): hand a caught task and step failure the same shape in every round

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

* refactor(wac): decide the failure record once, server-side

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

* fix(wac): leave a legacy SDK's failure marker untouched, and ship wacError to jsr

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

* fix(wac): carry a step's custom error fields, and bound the stack in bytes

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

* fix(wac): keep a step's extra fields serializable and bounded

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

* fix(wac): record a non-Error throw the way a task records it

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

* fix(wac): guard the last unguarded throw site in the step marker

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

* fix(wac): make failure reporting non-throwing on both clients

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

* fix(wac): take the step traceback the way the executor takes it

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

* fix(wac): contain the reads that happen before a failure is checkpointed

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

* fix(wac): fall back to the checkpointed marker, not the live one

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

* fix(wac): keep non-finite fields and hostile proxies out of the checkpoint path

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

* fix(wac): keep the snapshot that passed the serialization probe

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

* chore(wac): keep the failure-record module's surface to what is used

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 13:15:24 +02:00
GuilhemandClaude Fable 5 0f62891d43 fix(ai): stop teaching nonexistent while-loop iter.value state-carrying (#10345)
* fix(ai): stop teaching nonexistent while-loop iter.value state-carrying

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

* fix(ai): scope while-loop results guidance to cross-iteration reads only

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

* fix(ai): drop unverified wmill state-helper fallback from while-loop guidance

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

* fix(ai): document supported cross-iteration results state in while loops

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

* fix(ai): rescope while-loop fast-path rule and add results-carrying example

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 10:42:33 +02:00
Ruben Fiszel e80fee86b3 feat: record and replay raw app sessions step by step (#10318)
* feat: record and replay raw app sessions step by step

* fix: address review findings on raw app session recorder

* fix: stamp replay target before pruning the snapshot clone

* fix: redact step metadata, lock down replayed frames, fix control pre-state

* feat: add a checkpoint timeline to the app recording player

* fix: parser-based replay CSP, fold label clicks, drop stale frame indices

* fix: scrub redacted attributes, keep scroll, neutralize replay navigation

* fix: bound replay payloads, strip namespaced nav links, keep control pre-frames

* fix: strip SMIL navigation, redact metadata sources, capture pre-edit on beforeinput

* fix: redact template content, drop shadow templates, make replays inert

* test: pin snapshot redaction and replay sanitization with DOM tests

* fix: allow-list no-record attributes and cover a marked document root

* fix: classify input types positively so pickers get pre-change frames

* fix: one step per control interaction and bound step metadata

* fix: keep button inputs recordable and coalesce only continuous controls

* fix: no frames for coalesced repeats and drop inline styles when redacting

* fix: fold only the label's own click and keep marked stylesheets out

* fix: keep label-forwarded and radio-group pre-frames, fold submitter clicks

* fix: bound key pre-frames to their gesture and clear ancestor pointer frames

* fix: age-bound pre-frames and treat a radio group as one target

* fix: consume pre-frames per interaction and coalesce on the browser repeat flag

* fix: spend only the pre-frame a step actually used

* fix: settle a step from its successor's pre-state and drop stale pointer frames

* fix: bound remote frame payloads and snapshot stylesheets as rendered

* fix: let a control change spend its own frame and dedupe Enter activations

* fix: record Escape on controls and drop disabled stylesheets

* feat: collapse the replay step list by default behind a toggle

* fix: neutralize disabled sheets in place and fold Enter submissions

* fix: withhold redacted control state, fold key repeats, validate remote metadata

* fix: drop noscript markup and fold implicit form submissions

* fix: mask a select whose chosen option is redacted

* fix: mask redacted select choices before the clone diverges

* fix: run clone-paired passes before removals and fold only Enter submissions

* feat: record a raw app demo from the publish flow instead of the viewer

* fix: wait for in-flight runnable jobs before settling a step

* feat: record from the editor menu and replay publicly at /replay

* feat: export the app recording player and its loader for the hub

* feat: publish from folders only, drop iframe sharing

* fix: observe runnable responses where they land and mount the hub recording route

* fix: respect the app's sandbox opt-in when recording a session

* fix: let stop wait for the runnable the last step is still running

* fix: filter redacted class/id to styled tokens and gate publish on admin

* fix: drop marked sheets from the token vocabulary and bound the replay error

* test: pin the remote app-recording validator

* fix: carry in-flight runnables across a reload and fold held keys into one step

* fix: bind runnable responses off the request and honor base in the replay handoff

* fix: close the settling step when a new fill starts and always re-read stylesheets

* fix: empty the no-record marker so it carries nothing of its own

* fix: decode css escapes so utility classes survive redaction

* fix: read keyDriven from the frame the change starts from

* docs: condense recorder comments to the invariant each protects

* fix: rewrite only real url() tokens and accept leading css escapes

* feat: play flow, script and pipeline recordings on the public /replay page (#10327)

* feat: play flow, script and pipeline recordings on the public /replay page

* fix: render a recorded approval result inert while replaying

* fix: bound an asset sample's cell product and validate recording headers

* fix: make a replayed approval step inert and bound nested recording structures

* fix: stop recorded markup from fetching and bound flow/script render trees

* fix: gate recorded markdown at its renderer and close remaining render-budget gaps

* fix: replace per-key render caps with one structural budget per recorded value

* fix: bound component fan-out and text alongside the structural budget

* fix: make component fan-out cumulative and cap the parsed data-test checklist

* fix: bound the whole recording, graph contents, metadata strings and timer bursts

* fix: keep the published loader path, charge object keys, refuse huge serialized fan-out

* fix: cap flat maps a renderer turns into rows (args, schema properties)

* fix: refuse structure hidden past the depth ceiling and bound errored samples

* fix: count array-shaped argument collections against the row cap

* feat: paint canvas pixels into the snapshot

* fix: budget canvas encoding per snapshot and bound the unknown-kind error

* fix: cap flow graph overlay fan-out and condense budget comments

* docs: teach the raw-app prompt about data-wm-no-record
2026-07-26 11:51:59 +02:00
Ruben FiszelandClaude Opus 5 9cef724ff2 feat: bind WAC approval urls to a named wait_for_approval step (#10317)
* feat: bind WAC approval urls to a named wait_for_approval step

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

* fix: reject duplicate WAC approval step keys instead of renaming them

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

* fix: reject WAC approval links minted for a step that is not awaiting approval

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

* fix: bind WAC approval links to the awaiting step and stop step key aliasing

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

* fix: reject empty approval keys and scope minted-key writes to the workspace

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

* fix: enforce WAC approval binding at consumption and reject colliding keys

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

* fix: make WAC approval binding and collision checks atomic, harden TS step keys

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

* fix: decrement WAC suspend atomically instead of from a pre-lock snapshot

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

* chore: add sqlx cache entry for the atomic WAC suspend decrement

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

* fix: omit empty approver param from python get_approval_urls

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

* test: pin the suspend-snapshot decrement and the colliding-mint race

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

* test: drop the suspend-snapshot interleave test, it cannot both be stable and discriminate

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

* fix: reject step keys that cannot be minted as a URL path segment

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 11:41:48 +02:00
Ruben Fiszel 1f912f4104 feat(ai): enable data pipelines in AI sessions with alpha notice (#10273)
* feat(ai): enable data pipelines in AI sessions with alpha notice

* test(ai): assert real pipeline instructions returned in session

* fix(ai): await pipeline editor registration in open_preview to end build_pipeline_node race

* test(ai): drop historical narration in pipeline instruction assertion

* docs(ai): clarify S3 asset-URI storage forms and literal-key rule in pipeline prompt

* docs(ai): document data_upload S3Object input mechanism in pipeline prompt

* test(ai): guard open_preview pipeline tool-registration wait seam

* feat(ai): report inferred asset lineage in build/edit_pipeline_node results

* test(ai): assert open_preview(pipeline) awaits async handler registration

* docs(ai): qualify S3Object as wmill.S3Object in pipeline prompt examples

* fix(ai): report failure when a backgrounded session's pipeline tools never register

* fix(ai): include output_kind-seeded outputs in build_pipeline_node lineage feedback

* fix(ai): report only body-detected lineage, never the output_kind seed placeholder

* fix(ai): include materialize target in lineage feedback, condense comments

* docs(ai): use default-storage s3:/// in canonical pipeline read example
2026-07-23 12:55:59 +02:00
Ruben FiszelandClaude Opus 4.8 0819641f3a feat(ai): improve data-pipeline building in AI sessions (prompt + evals + e2e) (#10270)
* test(ai_evals): pipeline coverage for AI sessions + editor e2e

Add a complex incremental DuckLake pipeline case, harden the two-node case,
and encode the declarative pipeline contract (`-- on` triggers, `-- materialize`
+ bare SELECT) in the pipeline judgeChecklists so the LLM judge stops
false-negativing correct nodes. Add a deterministic Playwright e2e that seeds
annotated pipeline scripts and asserts the /pipeline/<folder> editor derives
the lineage DAG.

Fixes WIN-2229

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

* feat(ai): steer pipeline chat to DuckDB+materialize and warn on missing storage

The pipeline authoring prompt (getPipelinePrompt, used by the global/session
chat and the /pipeline editor) was neutral on language choice and said nothing
about storage readiness. Default it to duckdb materializing into DuckLake unless
the work specifically needs postgres/data-tables or bun/python, and add a storage
prerequisites section: a DuckLake pipeline needs workspace object storage + a
DuckLake catalog, so warn when none is configured and give role-appropriate next
steps (admin: workspace settings; others: ask an admin). Drafting is not blocked.

A/B on sonnet (global pipeline cases): no regression, +639 finalContext tokens.

Fixes WIN-2229

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

* test(ai_evals): address review - e2e teardown, async-edge note, merge-mode hedge

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

* chore(ai): address review nits - drop phantom list_ducklakes tool ref, trim narration comments

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

* feat(ai): add list_ducklakes chat tool for pipeline storage readiness

The pipeline counterpart to list_datatables: lists the workspace's configured
DuckLake catalogs so the chat can detect the storage prerequisite before building
a DuckLake pipeline and warn with role-appropriate next steps when none exists
(drafting stays unblocked). Wired into the global tool set and referenced from the
pipeline authoring prompt. In the eval run all three pipeline cases called it
unprompted with no build regression.

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

* chore(ai): fix DuckDB annotation syntax in duckdb-default section (-- not //)

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 00:51:48 +02:00
68debab877 feat(triggers): add AMQP (RabbitMQ) trigger via lapin (#10230)
* feat(triggers): add AMQP (RabbitMQ) trigger using the lapin library

Fixes WIN-2214

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

* chore(triggers): defer AMQP cross-workspace deploy pending utils-internal publish

Revert the amqp_trigger additions to the shared windmill-utils-internal
TriggerDeployKind and the frontend cross-workspace deploy adapter: the
frontend installs the published npm package, which lacks the new kind
until a release is cut. AMQP create/edit/delete/list/sync/capture are
unaffected (they use local types); only cross-workspace deploy/merge of
AMQP triggers waits on the package bump. Also document the at-most-once
ack in the consumer loop.

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

* fix(triggers): address AMQP review — at-least-once ack, workspace cascade, contracts

- ack AMQP deliveries only after successful dispatch; nack+requeue on failure
- add ON DELETE CASCADE workspace FK so amqp_trigger rows are cleaned on
  workspace deletion (and the listener stops)
- fix the /amqp_triggers/test OpenAPI body and add amqp_trigger to
  WorkspaceDiffRow.kind
- register AMQP in the generated workspace trigger tool (create_trigger)
- drop banned $bindable defaults on optional props in the config section
- add build_uri unit tests (encoding, ports, vhost)

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

* fix(triggers): stop AMQP poison-message loop and reconnect on transient drops

Chaos testing against a live RabbitMQ broker showed the previous
nack(requeue) + immediate re-poll spun a tight redelivery loop (~1000
critical-error reports/sec) on a poison message, and any connection blip
permanently disabled the trigger (lapin has no built-in reconnect).

- on dispatch failure: nack+requeue then stop consuming; the listener
  framework re-lists the trigger after its ping goes stale (~15s), backing
  redelivery off to that cadence instead of a tight loop (verified: rate
  dropped from ~1000/s to ~1 per ~26s, message preserved)
- on connection/stream error: stop and let the framework reconnect instead
  of disabling; persistent failures are still disabled via get_consumer
  (verified: a forced connection close now auto-reconnects and resumes)
- finish the AI create-trigger action wiring for AMQP: add amqp to
  CreatedResourceTriggerKind, the action-card registry, and the drawer
  registry so the result card renders and its "Open" action works

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

* fix(triggers): complete AMQP frontend registries and defer merge rows

- add amqp to capturableTriggerTypes (so AmqpCapture mounts), the Runs
  jobTriggerKinds filter, and CLOUD_DISABLED_TRIGGER_TYPES
- wire AMQP into global AI chat mode: TRIGGER_KINDS, the request union,
  writeTriggerSchema, triggerServices, and the draft adapter
- stop emitting actionable AMQP fork-comparison rows (revert amqp_trigger
  from TRIGGER_OR_SCHEDULE_TABLES) since cross-workspace deploy is deferred
  until windmill-utils-internal is published — avoids a deploy that fails
  with "Unknown kind: amqp_trigger"
- use design-system TextInput instead of raw <input> in the config section

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

* fix(triggers): complete AMQP session/draft registries and constrain prefetch

- add amqp to the session-deploy, draft-compare, preview-router, and
  copilot workspace-item registries so AMQP drafts/deploys/nav/path
  resolution work
- include amqp_count in the MoveDrawer attached-trigger rename warning
- replace the raw prefetch <input> with a design-system TextInput bounded
  to an integer 1-65535 (backend u16) and block save on invalid values

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

* fix(triggers): make AMQP disconnect/reconnect consistent with the Kafka trigger

lapin, like rdkafka, has no transparent reconnect, so the AMQP listener now
mirrors the Kafka trigger's explicit reconnect loop instead of relying on the
framework re-list (which disabled the trigger once get_consumer failed on a
sustained outage):

- get_consumer returns cheaply; consume owns a (re)connect loop that retries
  with a 30s backoff, reports a critical error every 10 failed attempts, and
  reports a recovered critical error once it reconnects — never disabling the
  trigger on a connectivity failure
- a consumer/stream error breaks out to reconnect rather than disabling
- dispatch failure still nacks+requeues (at-least-once) with a short backoff
  to avoid a tight poison-message loop, keeping the connection alive

Verified against a live RabbitMQ broker: killing the broker keeps the trigger
enabled and retrying (attempt N), and restarting it auto-reconnects (logs
"reconnected after N attempts") and resumes dispatch.

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

* fix(triggers): complete AMQP capture registries and constrain prefetch contract

- add the 'amqp' case to triggerKindToTriggerType so opening the AMQP editor
  from a capture button no longer throws "Unknown TriggerKind: amqp"
- register AmqpIcon in CaptureTable's icon map and add an AMQP entry to the
  script/flow CaptureButton menu
- bound the OpenAPI prefetch_count to an integer 1-65535 (matches the Rust
  u16) and regenerate clients/prompts
- require a non-empty exchange name when the exchange binding is enabled
- build_uri: fall back to "/" on a blank vhost and bracket IPv6 hosts (+ tests)

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

* feat(triggers): wire AMQP into pipeline graph, git-sync, and preprocessor types

- asset_graph: discover attached amqp_trigger rows and emit an AMQP TriggerEdge
  so AMQP triggers render (and can be opened/deleted) on the data-pipeline canvas
- frontend pipeline graph: add amqp to NativeTriggerKind, the add-trigger menu,
  node presentation, event-trigger set, annotation keywords, and the
  editor/service registrations
- git-sync: add the amqp_trigger include pattern (+ test) so an AMQP git-sync
  deployment stages only its .amqp_trigger.* file, not an unrelated same-path object
- preprocessor starters: add the AMQP event to the generated TS/Python/PHP
  trigger event types (kind/payload/exchange/routing_key/queue_name/redelivered/
  delivery_tag)

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

* fix(triggers): finish AMQP pipeline/parser wiring, prefetch validation, source lists

- fix a stray edit that corrupted the pre-existing MqttTriggerEditor import
  ($lib/... path) in PipelineTriggerEditors.svelte
- reject prefetch_count = 0 server-side in validate_config (RabbitMQ treats 0
  as unlimited) and defensively skip basic_qos(0) in build_consumer (covers
  the capture path that bypasses CRUD validation)
- recognize `// on amqp` in the canonical parser (TriggerSpec::Amqp) and add
  amqp to the CLI non-autorun/event-trigger sets so a pipeline cascade never
  runs an AMQP-only node as a manual root without an event
- add amqp to the preprocessor intro lists and both pipeline AI instructions

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

* fix(triggers): reject zero AMQP prefetch in all paths and finish guidance lists

- extract a shared validate_amqp_options used by both CRUD validate_config
  and build_consumer, so capture configs (which bypass CRUD validation) also
  reject prefetch 0 instead of silently connecting with an unlimited buffer
  (+ unit tests for 0/1/65535/None)
- add AMQP to the main script-writing preprocessor-sources prompt and the CLI
  triggers-skill guidance list

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

* docs(triggers): de-duplicate AMQP prefetch comment and fix GET response text

- keep the zero-prefetch rationale only on the shared validate_amqp_options
  doc; drop the redundant call-site comments
- correct the getAmqpTrigger OpenAPI 200 description ("deleted" -> "retrieved")

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

* chore: update ee-repo-ref to aaa6cb89b05b76139252c64f057e53b94d12ac60

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

Previous ee-repo-ref: 5da5fd65aca9594b2611837a52e4677b544b0380

New ee-repo-ref: aaa6cb89b05b76139252c64f057e53b94d12ac60

Automated by sync-ee-ref workflow.

* chore(migrations): consolidate the four AMQP migrations into one

The table and the three enum ADD VALUE statements (trigger_kind, job_trigger_kind,
draft_kind) are one atomic feature. ALTER TYPE ... ADD VALUE runs inside the
migration transaction on PG >= 14 (Windmill's minimum) since the amqp_trigger
table doesn't reference those enum types, so they can share a single migration
instead of four. Verified applying cleanly in a single transaction on a fresh DB.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-07-21 15:10:29 +00:00
hugocasaandClaude Opus 4.8 b070f56c5e feat: detect server-handled git-sync so CLI picks git push vs wmill sync push (#10201)
* feat: detect server-handled git-sync so CLI picks git push vs wmill sync push

Add a non-admin GET /w/{w}/workspaces/git_sync_deploy_mode endpoint returning
{configured, deploy_on_push}, so any workspace member (not just admins, who
alone can read get_settings) can tell whether pushing to the git remote deploys
via server-side auto-pull. Surface it through `wmill gitsync-settings status`
and align the deploy guidance/skills to prefer git push when the repo deploys on
push, falling back to `wmill sync push` otherwise.

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

* fix: address review — clean JSON output, warn on git_sync parse failure

- gitsync-settings status --json-output now uses console.log so the JSON pipes
  cleanly to jq (log.info wraps it in ANSI color codes)
- get_git_sync_deploy_mode logs a warning on git_sync deserialize failure instead
  of silently reporting configured=false, and documents why it is not EE-gated

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

* fix: address codex review — license-gate and branch-match deploy-on-push

- get_git_sync_deploy_mode now reports deploy_on_push only on Enterprise-licensed
  instances (auto-pull can't run on CE/downgrade) and returns auto_pull_branches
  so the client knows which tracked branches actually deploy on push
- gitsync-settings status matches the local git branch against auto_pull_branches
  before recommending git push, so an untracked branch falls back to wmill sync push
- add an integration assertion for the endpoint's default (no git-sync) shape

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

* docs: point per-topic skills at the canonical Deploying section

The git-push-vs-wmill-sync-push decision lives in core.ts (AGENTS.wmill.md),
which is already in context. Have the per-topic skills reference the Deploying
section instead of re-encoding the detection, so there is one source of truth
and no drift (the compressed version also wrongly implied `gitsync-settings
status` detects the CI-workflow path, which only core.ts's filesystem check does).

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

* fix: match remote+branch server-side for deploy-on-push detection

Codex flagged that a workspace-level auto-pull signal recommends `git push` even
when the local checkout is a different repo/branch than the one that auto-deploys.
Match precisely instead, without exposing anything sensitive:

- git_sync_deploy_mode takes optional remote+branch query params. The backend
  normalizes each auto-pull repo's URL to host/path (dropping embedded
  user:token credentials by rebuilding from parsed components, never scrubbing
  the string) and compares to the caller's remote; deploy_on_push is true only on
  a licensed instance where an auto-pull repo matches that remote and tracked
  branch. The response is two booleans — no repo URLs or branches leave the server.
- Branchless (default-branch) and fork/sync_forks repos stay a safe fallback to
  `wmill sync push` rather than a wrong git-push recommendation.
- CLI status sends `git remote get-url` + current branch (new getGitRemoteUrl
  helper, --remote flag) and reports the matched result.
- Unit-test the URL normalization/credential-stripping directly, since a
  regression there would be a token-handling bug.

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

* fix: address codex security findings in deploy-mode detection

- Strip credentials from the local remote client-side before sending, so a
  token embedded in the URL never reaches the server's request-URI logs
- Fetch the remote via spawnSync arg array (not an interpolated shell string),
  removing a command-injection path from a caller-supplied --remote value
- Use the remote's push URL (`git remote get-url --push`) and recommend the
  qualified `git push <remote> <branch>`, so the pushed target matches the one
  the server checked
- Keep the port in remote normalization so different services on the same host
  don't collide into a false match
- Unit-test credential stripping (CLI) and port distinctness (backend)

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

* feat: resolve $var repos and fork sync_forks in deploy-mode detection

- Interpolate $var:/$res: references in the repo url/branch the same way the
  auto-pull poller does (system context, cached, only when a field is a
  reference), so variable-backed git URLs match instead of falling through
- For a fork workspace, evaluate the root ancestor's git-sync settings and treat
  its wm-fork/<base>/<id> branch as deploying when the root repo has
  auto_pull.enabled && sync_forks and its base matches the tracked branch
- Read settings/resources on the plain pool (a fork member may not belong to the
  root workspace); only booleans are returned
- Unit-test the fork/branch matching (base + sync_forks + workspace-id suffix)

A blank tracked branch (repo default) still needs a network ls-remote to resolve,
so it stays a safe fallback to `wmill sync push` rather than a wrong git push.

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

* refactor: share one git-repo resolver between poller and deploy-mode

The auto-pull poller and the deploy-mode endpoint both resolved a git-sync repo
resource (system context, $var:/$res: interpolation) with duplicated boilerplate.
Extract windmill_store::resources::resolve_git_repository_resource and have both
call it, so the interpolation lives in one place. Drops the endpoint's local
resolve_repo_url_branch helper and its raw SQL query (and cache entry).

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

* fix: address codex review — fork false-positives, shell-safety, auth contract

- Fork deploy detection now mirrors reconcile_fork_branch_pull: the wm-fork branch
  must route to this workspace (first existing of the id candidates) and the repo
  must be in the fork's own inherited settings, so a multi-repo root or an
  ambiguous id can't produce a false deploy_on_push
- Recommended deploy command is shell-quoted (branch/remote names may contain
  metacharacters and the output is agent-executed)
- Remote normalization folds only the host; repo paths stay case-sensitive
- Document the system/RLS-bypassing contract on the shared resolve helper and
  restore the head-fetch doc; fix the overclaiming integration-test comment
- Dev-workspace label and default-branch cases remain documented safe fallbacks

Also restores 5 sqlx cache entries an earlier cleanup dropped.

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

* fix: require a runnable auto-pull delivery path for deploy_on_push

enabled auto-pull alone doesn't mean a push deploys: a webhook-only repo with no
active hook (failed registration), or a repo that only polling could serve on an
SSH URL (the poller rejects SSH), delivers nothing. Gate deploy_on_push on an
actual delivery path — active webhook, or a pollable non-app HTTPS repo — per the
repo's auto-pull mode. Unit-tested across modes/webhook/URL-scheme/app.

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

* fix: read fresh repo config for on-demand deploy-mode detection

resolve_git_repository_resource took an implicit allow_cache=true (right for the
poller loop). An on-demand status could then match against a stale url/branch
cached by an earlier poll. Make allow_cache a parameter: poller keeps true, the
deploy-mode endpoint passes false so it reflects the current git-sync config.

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

* fix: defer the deploy fallback instead of assuming wmill sync push

When backend auto-pull doesn't match the checkout, `status` no longer flatly
recommends `wmill sync push` — a CI workflow may still deploy on push. It now
reports the backend signal and points at the Deploying guidance (check CI → git
push, else wmill sync push; record the choice as a `Deploy mode:` line in
AGENTS.md). deploy_command is null in JSON when undetermined. This resolves the
CI-backed false recommendation without the CLI re-implementing CI detection.

Also fix two review nits: restore the deploys_on_push_branch doc comment (it had
drifted onto has_runnable_delivery) and correct the app-repo comment (their
exclusion from the poll path is a conservative safe under-report, not "can't be
polled").

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

* refactor: drop the ambiguous-fork-id disambiguation from deploy-mode

The existence-query resolution guarded a very narrow case (a suffix owned by both
a coexisting wm-fork-<suffix> and <suffix> workspace, queried from the wrong one).
Not worth the per-fork query; keep the cheap candidate-family check plus the
inherited-repo membership test, which already close the real fork false-positive.

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

* refactor: drop remote-URL matching; disambiguate deploy-mode by repo count

Matching the caller's git remote URL against each repo dragged in the whole
remote-URL surface (sending the URL, credential stripping, shell-safe remote
handling, fetch/push URL, port/case normalization) — and the risk that came with
it. Replace it with a simpler rule that fits the actual question:

- deploy_on_push is true only when exactly ONE licensed, deliverable auto-pull
  repo tracks the pushed branch. With a single synced repo the local checkout is
  unambiguously it; with several we can't tell which is the caller's, so we
  return false and the CLI asks the user.
- The endpoint takes only `branch` (no `remote`); status no longer reads or
  sends the git remote.
- On the fallback, status now tells the agent to ASK the user how the repo
  deploys (CI git-push vs wmill sync push) and record it in AGENTS.md, instead of
  assuming wmill sync push. Guidance updated to match.

Removes normalize_git_remote (+url dep), getGitRemoteUrl, stripGitRemoteCredentials,
shellQuote, the --remote flag, and their tests.

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

* refactor: share fork-branch routing between reconciler and deploy-mode

Deploy-mode detection was re-deriving fork/dev routing (root walk, wm-fork/dev
branch parsing, descendant resolution, inherited-repo check) that the auto-pull
reconciler already owns — the source of repeated edge-case bugs. Extract it into
windmill_common::workspaces::resolve_fork_branch_target and have both the endpoint
and reconcile_fork_branch_pull (EE) call it, so they can't drift and dev
workspaces are handled by construction.

Endpoint now resolves the root via the canonical cached fork_ancestor_chain
(dropping a duplicate CTE) and routes forks/dev workspaces through the shared
resolver. The .sqlx cache is unchanged (the moved queries already existed).

Bumps ee-repo-ref for windmill-labs/windmill-ee-private companion.

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

* fix: exclude archived roots and frame deploy_on_push:false as unconfirmed

- deploy_on_push now requires the root workspace to be live; polling and webhook
  delivery both exclude deleted roots, so an archived root (or anything beneath
  one) with retained git-sync no longer reports deployable
- status and the OpenAPI now describe false as "not confirmed" (it also covers
  ambiguity and conservative false-negatives), not a definite no — the CLI asks
  the user rather than asserting the push won't deploy

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

* chore: bump ee-repo-ref for EE branch merge of main

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:44:24 +02:00
hugocasaandClaude Opus 4.8 24750e6ef1 fix(raw-apps): prevent and surface the silent blank screen from an unmounted #root (#10150)
* fix(raw-apps): prevent and surface the silent blank screen from an unmounted #root

An `index.tsx` written as a bare `export default function App() {...}` with
no mount call builds and runs without throwing: the preview executes the
bundle against an empty `<div id="root">` and auto-renders nothing, so the
JSX never runs, nothing reaches the console or the runtime-error overlay,
and the app is blank with no diagnostic.

Prevent it: the raw-app system prompt and the in-chat app prompt now state
that `index.tsx` is the mount entrypoint, show the mount shim for React,
Svelte and Vue, and call out that a bare component fails silently.

Surface it: when a build still mounts nothing, the preview harness posts
`emptyRender` and the editor shows an error overlay naming the missing
call. The harness reports only when nothing is on screen AND the app never
looked `#root` up, so an app that mounted but paints nothing yet (a fetch
in flight, an unresolved Suspense) is never flagged; `renderAppeared`
withdraws the overlay if a deferred mount lands late.

The handlers stay dormant until the builder tarball that emits these
messages is pinned via `ui_builder_artifact.json`.

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

* chore(raw-apps): pin the UI builder artifact that emits emptyRender

Activates the "Nothing was mounted" overlay: the pinned tarball predates
the harness change, so the host handlers were dormant until now.

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

* chore: drop a screenshot accidentally committed at the repo root

Not referenced anywhere; the PR's screenshots are hosted externally.

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

* fix(raw-apps): make the unmounted-app guidance framework-aware

The detector fires for every raw-app framework, but the overlay and the
prompts named React's `index.tsx` and `createRoot` unconditionally. Svelte
and Vue apps mount from `index.ts` via `mount` / `createApp`, so the
guidance pointed at a nonexistent entrypoint and an unavailable API.

Derive the entrypoint and mount call from the app's files, keyed off file
extensions rather than the template filenames, which users rename.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 15:48:50 +02:00
GuilhemandClaude Opus 4.8 6b01caaf26 fix(ai): flow writer builds approval steps as scripts, not identity (#9985)
* fix(ai): flow writer builds approval steps as scripts with getResumeUrls, not identity

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

* test(ai-evals): accept rawscript or script for approval step type

The flow-writer prompt allows an approval step to be `type: rawscript`
or `type: script`, but the topLevelStepTypes check pinned an exact
`rawscript` match, so a valid `type: script` approval would fail
deterministically. Let the check accept a list of allowed types.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 21:47:14 +02:00
GuilhemandClaude Opus 4.8 8df613b4d2 feat(raw-apps): runtime-error overlay + AI import-React instruction (#9966)
* feat(raw-apps): render runtime-error overlay + instruct AI to import React

Render the `runtimeError` message the raw-app preview frame now posts as a
prominent overlay, so an uncaught exception that blanks the app is visible
instead of silent. Cleared on the next successful build (via a shared
`feedPreviewIframe` helper so every preview-feed path resets it).

Add an AI app-generation instruction to begin React files with
`import React from 'react'`: raw apps bundle with the classic JSX transform,
so a missing import compiles fine but throws "React is not defined" at runtime.

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

* feat(raw-apps): add the import-React rule to the shared raw-app prompt

The global AI chat and the raw-app CLI skill draw their raw-app authoring
reference from system_prompts/base/raw-app.md — a separate surface from the
app chat's inline prompt (core.ts). Add the same "always begin JSX files with
`import React`" rule there (esbuild's classic transform needs React in scope,
or JSX throws "React is not defined" at runtime) and regenerate the derived
prompt files.

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

* chore(raw-apps): bump ui_builder tarball to f8cecf9 (runtime-error overlay)

Pins the ui_builder artifact to windmill-code-ui-builder#15, which pushes
uncaught runtime errors from the preview iframe to the parent so the raw-app
editor can render them in the error overlay.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 09:32:43 +02:00
Ruben FiszelandClaude Opus 4.8 c91027824b feat(pipeline): AI-chat data-pipeline editor (route + in-session) + home surfacing (#9805)
* feat(pipeline): AI chat tools to build pipeline nodes with diff/approval

Add a data-pipeline AI chat experience modeled on the flow editor and
surfaced through the dev-gated global chat (no new chat panel).

The /pipeline editor registers PipelineAIChatHelpers on the AIChatManager;
while it is open the global mode layers pipeline tools, a pipeline prompt
section, and the helpers on top of the full global tool set (behavior is
unchanged when no pipeline editor is open).

New tools (frontend/src/lib/components/copilot/chat/pipeline/core.ts):
- get_pipeline_graph / read_pipeline_node — read the live graph and bodies
- build_pipeline_node / edit_pipeline_node — stage changes as AI-pending drafts
- remove_pipeline_node — drop a staged proposal
- test_pipeline_node — preview-run a node (requires confirmation)

Tools never deploy: they stage drafts flagged aiPending, rendered on the
canvas with an accent ring and reviewed via Accept all / Reject all (the
flow editor's GlobalReviewButtons). Accept commits the drafts; Reject reverts
to a pre-AI snapshot, preserving earlier accepted drafts. Auto-accept is gated
on the chat autonomy mode.

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

* feat(pipeline): teach the global/session chat to author data pipelines

Without an open /pipeline editor the session chat had no pipeline concept, so
"create a data pipeline" loaded flow instructions and built a flow. Add a
first-class pipeline authoring path:

- system_prompts/base/pipeline-base.md — what a data pipeline is (a DAG of
  annotated scripts wired by storage assets, NOT a flow) and how to author the
  // pipeline / // on / // materialize annotations; wired through generate.py as
  getPipelinePrompt() (regenerated prompts.ts/index.ts).
- global/core.ts — new get_instructions subject "pipeline", and a global-prompt
  rule disambiguating data pipelines from flows so the model routes correctly.
- ai_evals/cases/global.yaml — two global cases (single node, two-node chain)
  asserting pipeline-annotated script drafts and forbidding write_flow, guarding
  the pipeline-vs-flow conflation.

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

* feat(pipeline): show & build pipelines in the AI session preview

Add a 'pipeline' session preview target so the session AI can show the
data-pipeline graph for a folder and build nodes in-pane:

- open_preview now accepts kind="pipeline" (path = folder); SessionTarget /
  EDITOR_TARGET_KINDS widen accordingly. The slot/codec load model stays
  flow|script|raw_app — pipeline bypasses it with its own fetch/draft state.
- New PipelineEditorView.svelte mounts in the session pane: fetches the
  folder graph, overlays AI drafts, renders AssetGraphCanvas + the
  Accept/Reject review buttons, and registers PipelineAIChatHelpers on the
  *session-scoped* manager (via getAiChatManager) so build_pipeline_node /
  edit_pipeline_node + the diff/approval work inside the session too.
- System prompt nudges the model to open the pipeline preview and use the
  staging tools while building.

Verified end-to-end with a real model: the session AI called open_preview,
the graph mounted in the side panel, then build_pipeline_node staged a node
on the session canvas with its schedule trigger and ducklake output.

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

* refactor(pipeline): share the AI editor logic between route page and session

Consolidate the duplicated data-pipeline AI logic onto a single shared layer so
the route editor and the in-session preview behave identically and the session
gains the full code editor.

- New pipelineAiHelpers.ts: createPipelineAiHelpers(deps) owns the propose/edit/
  remove/accept/reject/test staging + the per-turn snapshot bookkeeping that
  powers Reject. Callers inject accessors for their own draft Map and graph.
- Route page (/pipeline/[folder]) drops its ~250-line inline AI-helper block and
  wires the shared factory via deps (folder/workspace/graph/drafts + focus,
  ensureEditable, run-started). Its shell — persistence, navigation guard,
  activity, cascade, trigger drawers — is untouched.
- Session PipelineEditorView uses the same factory and now renders the real
  AssetGraphDetailsPane (code editor + live overlays + test), so a node built in
  a session opens with its source, matching the route editor.

Verified: route page hydrates/renders drafts unchanged; in a session the AI
opened the pipeline preview, built a node, and its code showed in the details
pane. check:fast clean, 197 unit tests pass.

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

* refactor(pipeline): externalize editor state into PipelineEditorState (step 1)

Introduce PipelineEditorState — the data-pipeline analogue of the flow editor's
flowStore. It owns the draft Map, the live editor overlays, and the selection,
with callback-safe methods (handleDraftPersist / handleAnnotationsChange / … ),
so a single editor can be rendered by both the route page and the session.

This commit lands the store and points the in-session PipelineEditorView at it
(no behaviour change — the session already had these inline). Next steps move the
route page onto the store and a shared <PipelineGraphEditor>.

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

* refactor(pipeline): point the route editor at PipelineEditorState (step 1)

Move the route page's draft Map, live editor overlays, selection, and the
draft-persist / live-change handlers onto the shared PipelineEditorState (`pe`),
referencing them as `pe.*` in place. No behaviour change — persistence, graph
resolution, run dispatch, AI staging, and deploy all stay on the page and now
read/write the externalized state.

This is the data-pipeline analogue of the flow editor's flowStore: the route
page and the in-session preview now share one source of editor truth, setting up
the shared <PipelineGraphEditor> in the next steps.

Verified: the page hydrates its DB draft, renders the overlay graph, the toolbar
counts (Save all (N)) track pe.drafts, and selecting a node opens it in the
details pane. check:fast clean, 84 unit tests pass.

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

* refactor(pipeline): render the route editor via shared PipelineGraphEditor (step 2)

Extract the canvas + details-pane editor body into PipelineGraphEditor.svelte,
the data-pipeline analogue of FlowBuilder. The route page now delegates its
Splitpanes block to it, passing the externalized PipelineEditorState plus its
run/cascade/trigger/deploy callbacks; the component owns pane sizing,
selection/details-open derivation, and the canvas+details rendering.

Root-caused the earlier ts2769 "$props() No overload" to a prop named `state`
colliding with the `$state` rune (`let x = $state(...)` parsed as a store
auto-subscription on the prop) — the prop is now `editor`.

Net: the route page sheds ~310 lines of template/state; behaviour preserved.
Verified: the page hydrates its DB draft, renders the graph, opens the draft in
the details pane (live code editor + Test), pane sizing works. check:fast clean,
24 pipeline tests pass.

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

* refactor(pipeline): move draft autosave into PipelineGraphEditor (step 3)

Fold the per-user `data_pipeline` DraftService bundle autosave (hydrate +
debounced persist + localStorage crash mirror) into PipelineGraphEditor, gated
by a `persistDrafts` prop — FlowBuilder's parameterized-autosave shape. The route
page passes `persistDrafts` + `folder` and reads `editor.loadedFromDbDraft` for
its AutosaveIndicator; the in-session preview will leave persistence off.

Also restores the `untrack(...)` wrapping on the pane-sizing $effect (dropped
when the editor body was extracted in step 2). Without it the Pane `bind:size`
feedback loops the effect and pegs the main thread when the details pane is
closed — a latent hang in the step-2 commit.

check:fast clean, 24 pipeline tests pass. Note: browser revalidation was not
possible this session (the Playwright MCP browser was reset); the autosave is a
verbatim port and the untrack fix is the original working form.

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

* refactor(pipeline): render the session preview via shared PipelineGraphEditor (step 4)

Point the in-session PipelineEditorView at the shared PipelineGraphEditor instead
of its own inline canvas + details pane. The session now renders the exact same
editor body as the route page — gaining the full details/code pane — while opting
out of persistence (persistDrafts=false) and the run/cascade/trigger/bounded
affordances (their callbacks are omitted, so those controls hide). Building nodes
+ the Accept/Reject diff still work via the AI helpers.

Also fixes issues surfaced by a full `svelte-check` while wiring this up:
- PipelineGraphEditor: edit mode opened the details pane unconditionally (a step-2
  regression); restored the route's "open only on selection/draft" behaviour.
- Route page passed an `isOperator` prop the component doesn't accept (step-2;
  caught only by full check, not check:fast).
- SessionItemNotFound: narrow its `kind` to exclude `pipeline` (pipeline targets
  never slot-load, so they can't 404 through it) — closes the SessionTarget-widen
  fallout.
- PipelineEditorView: cast the resolveGraph base to AssetGraphResponse.

Full `svelte-check` now clean across all pipeline/session files; 137 unit tests
pass. (Browser revalidation still pending — Playwright MCP was unavailable this
session; see the smoke-test note on the PR.)

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

* fix(pipeline): stop an infinite microtask loop when persisting a no-output draft

handleDraftPersist short-circuits when the open draft's content + inferred writes
are unchanged. The writes check compared `d.outputAssets?.length === writes.length`,
but a no-output draft has `outputAssets: undefined` (so `?.length` is `undefined`)
while the details pane infers an empty `writes: []` (length 0). `undefined === 0`
is false, so it never short-circuited: every persist re-wrote the drafts Map with
an equivalent object, which gave `activeDraft.script` a new identity → the pane
re-emitted its overlays → the graph re-derived → persist fired again. A self-
sustaining microtask loop that pegged the renderer and froze the tab on any
pipeline carrying a no-output draft (e.g. hydrating one from the saved
data_pipeline draft on load). It hangs rather than throwing effect_update_depth_
exceeded because it cycles across microtasks, not within one reactive flush.

Fix: coalesce the undefined length to 0 so "no outputs" compares equal to an empty
inferred-writes list. Adds pipelineEditorState.test.ts covering the idempotency
(fails without the fix) plus the change/no-change cases.

Root-caused by instrumenting the reactive churn: every iteration reassigned
drafts/liveContent/liveBodyAssets/liveAnnotations/displayGraph with identical
values — pure reference churn off the drafts re-write.

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

* fix(ai-chat): make the agent open the pipeline editor before building nodes

In a session, the GLOBAL system prompt only *advised* opening the pipeline preview
("show its graph with open_preview ... prefer those tools once it is open"), so
the agent routinely skipped it: on a plain "build a data pipeline" request it
reached for write_script and staged plain script drafts, and the canvas editor
never opened. build_pipeline_node / edit_pipeline_node are only registered once
the preview is open, so skipping open_preview also loses the canvas-staged
Accept/Reject diff-approval flow entirely.

Make the guidance imperative: open_preview(kind="pipeline", path=<folder>) is the
FIRST step before creating any node (an empty or not-yet-created folder is fine —
create_folder first if needed), and pipeline nodes go through build_pipeline_node
/ edit_pipeline_node, never write_script. This also clears the agent's "the folder
might not exist" hesitation that pushed it toward write_script.

Verified live (same plain prompt, before/after): before it used write_script with
no editor; after, the agent opens the editor first and stages a canvas-highlighted
node with Accept all / Reject all. The guidance is gated on previewTools
(session-only), so it doesn't affect the non-preview global eval cases.

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

* fix(pipeline): preserve in-session pipeline drafts across editor hide/show

The session preview's PipelineEditorState lived in the PipelineEditorView
component with persistDrafts=false. Hiding the editor sets editorVisible=false,
which makes `hasEditor` false and the `{#if hasEditor}` block unmount the view —
discarding its component-local store. Showing it again remounted a fresh, empty
one, so the pipeline the AI had built in the session vanished.

Move the PipelineEditorState onto the per-session SessionRuntime (like the flow /
script / raw_app editors, which already host their state there and take {runtime}),
so it survives the pane unmount on hide and across session switches. The runtime
is keyed by session id and only dropped on session deletion.

Because the instance is now reused, guard against a retarget to a different
folder: PipelineEditorView resets the state when `path` changes to a new folder
(a same-folder remount keeps the drafts). Adds `folder` + `reset()` to the store.

Verified: build a node in a session → Close editor → Show editor → the staged
node, its wiring, the details-pane code, and Accept/Reject all re-appear. Full
svelte-check clean; 139 pipeline tests pass.

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

* feat(pipeline-ai): clearer diff + persistent review banner on the canvas

The AI review affordance had two problems on the pipeline canvas:

- The floating Accept-all / Reject-all bar sat bottom-center, where it
  collided with the minimap once the canvas narrowed on node selection —
  reading as "the buttons vanished when I select a node".
- Every staged draft rendered with the same blue ring, so it wasn't clear
  what the review would actually change (a plain manual draft looked the
  same as an AI proposal).

Replace the floating bar with a top-left review banner (z-30, clear of the
controls and minimap) that stays put regardless of selection and spells out
the pending counts. Color the diff per node: a proposal that adds a node
that isn't deployed rings green with a "new" chip; one that edits an
already-deployed node rings amber with an "edited" chip. Plain manual
drafts keep the neutral gray dashed border, so only the green/amber nodes
read as part of the Accept/Reject set.

aiPendingKind is resolved in resolveGraph (deployed runnable present →
modified, else added) and forwarded through the canvas to the node.

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

* fix(pipeline-ai): persist in-session pipeline proposals across reload/switch

Staged AI proposals lived only in the per-session runtime's in-memory
PipelineEditorState (persistDrafts=false), so a page reload — and an
LRU-evicted runtime on session switch — dropped them, leaving the canvas
and the Accept/Reject review empty even though the chat still showed the
nodes as staged.

Enable the same per-folder DB-draft persistence the route page uses for the
in-session editor. To keep hide/show cheap and race-free, hydration is now
gated per editor instance (PipelineEditorState.hydratedFromDb) rather than
per component mount: the runtime-hosted instance hydrates ONCE when fresh
(reload / evicted runtime) and then keeps its in-memory drafts across the
editor pane unmounting on hide — re-reading the DB on every remount would
race a not-yet-flushed autosave and drop a just-staged draft. A folder
retarget resets the flag so the new folder re-hydrates.

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

* fix(pipeline-ai): make Reject all work for rehydrated proposals

rejectAll only reverted paths tracked in the in-memory aiSnapshots map,
which is rebuilt empty on each editor mount. After a reload (or session
switch into a fresh runtime) the proposals are restored from the persisted
draft but have no snapshot, so Reject all was a no-op on exactly the nodes
it should discard. Sweep any still-pending draft without a snapshot and
discard it (revertPath with no snapshot deletes the path; for an edit of a
deployed node that correctly falls back to the deployed body). Adds unit
coverage for accept/reject including the no-snapshot case.

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

* fix(pipeline-ai): keep proposals visible while the graph reloads on switch

The session editor pane is LRU-capped (MAX_WARM_EDITORS), so returning to a
session whose pane was evicted remounts PipelineEditorView with a fresh
graphRes resource (loading=true, current=undefined). The deployed-graph
loading spinner gated the whole canvas, so the staged proposals and the
Accept/Reject review banner vanished until the re-fetch resolved — read as
"the proposal disappears when I switch sessions".

Only show the loading/error placeholder when there are no drafts to display.
When the runtime already holds staged drafts, render the editor immediately:
resolveGraph overlays them on an empty base so the proposals + banner stay
visible, and the deployed nodes fill in when the fetch completes. Verified
with a 4s-delayed graph fetch — proposals render through the load with no
spinner.

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

* refactor(pipeline-ai): apply AI node edits directly as drafts, no approve/reject

The canvas-level Accept all / Reject all review (aiPending proposals, the
green/amber diff ring + "new"/"edited" chips, and the review banner) didn't
fit the pipeline editor. Match the flow/script editor instead: build/edit
apply directly as ordinary unsaved drafts on the canvas, which the user then
deploys — there is no separate approval step.

Removed across the surface:
- aiPending / aiPendingKind on the runnable node + resolveGraph seeding +
  canvas forwarding; AI-built nodes now render with the existing plain
  unsaved-draft dashed styling.
- the review banner, count derivations, and hasAiPending/onAccept/onReject
  props from PipelineGraphEditor and both consumers (route page + session
  view).
- acceptAll/rejectAll/hasPending and the per-turn snapshot bookkeeping from
  the shared helpers; removeProposedNode now just discards the unsaved draft
  at a path (undo a build). acceptAllProposals/rejectAllProposals/
  hasPendingProposals dropped from the PipelineAIChatHelpers interface and
  the manager's auto-accept hook.
- accept/reject language from the tool descriptions, return messages, and the
  system-prompt section.

Tests updated; pipeline + AssetGraph suites pass (142).

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

* fix(drafts-diff): support data_pipeline diffs + fix blank empty-summary row

Two issues in the session "Drafts" diff drawer (DraftDiffDrawer):

- Clicking a `data_pipeline` bundle row threw "Draft diff not supported for
  kind data_pipeline" (utils_draft_deploy.ts) — there was no handler for the
  kind, so it fell to the OVERLAY_GETTERS lookup and errored. The bundle has
  no deployed counterpart (each node deploys individually as a script), so
  diff it node-by-node: surface each node's draft body keyed by path, folding
  in the deployed body as the "before" when a node edits a deployed script.

- A draft row whose summary is an empty string (e.g. the app draft) rendered
  with no title at all: WorkspaceItemRow's single-line branch used
  `summary ?? secondary`, and `??` doesn't treat '' as absent, so it showed
  the empty summary instead of the path. Use `||` so an empty summary falls
  back to the path.

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

* feat(drafts-diff): explode data_pipeline bundle into per-node subitems

A data_pipeline draft is a bundle of node-script drafts, so a single row
diffed the whole thing as one blob. Explode it in DraftDiffDrawer into one
script row per node, nested under the bundle's `…/data_pipeline` folder so
they read as the pipeline's subitems — each with its own path and a proper
script Content/Metadata code diff. The node's draft body is the "after"; its
deployed body (when the node is already deployed) is the "before", so edits
show as line diffs and new nodes as added. A single bundle row (via the
getDraftDiffValues data_pipeline fallback) is kept only for the case where
the bundle can't be read.

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

* refactor(pipeline-ai): simplify — drop vestigial approve/reject scaffolding & redundant field

Review pass over the PR, removing complexity left from the approve/reject
removal and the shared-component refactor (all behavior-preserving):

- Inline the `acceptPendingEdits` pass-through into `acceptPendingFlowEdits`
  and revert the now-inert `autoAcceptEditsAvailable` GLOBAL+pipeline widening
  (pipeline edits are direct drafts — nothing to auto-accept).
- Fix the global system prompt: pipeline tools "apply directly as unsaved
  drafts (no accept/reject)", not "proposals the user Accepts or Rejects".
- Collapse the redundant `outputAsset` (singular) into `outputAssets`,
  removing a whole resolveGraph fallback tier; simplify propose/editNode.
- Drop the single-field `PipelineAiHelpersHandle` wrapper (callers just
  destructured `{ helpers }`); inline the misleading `isoNow()` helper.
- Remove the now-unreachable `data_pipeline` branch in getDraftDiffValues
  (the drafts drawer explodes bundles per-node; an unreadable bundle is
  skipped) and the "Step N consolidation" drafting narration.
- Un-export internal-only types; reuse `storageKey`; refresh stale comments
  that still referenced proposals / the review banner / diff-approval.

svelte-check clean; 141 unit tests pass.

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

* feat(pipeline): tooltip clarifying the Create/Save button deploys

The accent button in the asset-graph details pane ("Create" for a new script,
"Save" for an existing one) is really a deploy, but had no tooltip explaining
that. Add a title — "Deploy this new script to the workspace" / "Deploy your
changes to this script" — keeping the create-vs-update label distinction while
making clear both deploy.

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

* docs(pipeline-ai): document the `materialize` annotation in the pipeline prompt

The model invented "materialize run" because the prompt only mentioned
`// materialize <uri>` in passing. Spell out what it is in both the in-app
pipeline prompt (getPipelinePromptSection) and the base prompt
(pipeline-base.md, regenerated): a MANAGED output where the runtime writes the
table around a single SELECT (no manual CREATE/INSERT); replace (default) vs
`append` vs `key=<col>` strategies; `manual` to opt out (track-only); and its
pairing with `// partitioned …` (runs once per partition, `{partition}` token
substituted at run time). Explicitly: materialize is an output declaration,
not a command — there is no "materialize run".

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

* feat(pipeline-ai): trigger drawers in the AI session preview

Bring the route page's native-trigger affordances to the in-session pipeline
editor by reusing the shared <PipelineTriggerEditors> (no duplication of the
drawer UI). Clicking a "Schedule · Missing — no trigger row" node (or
edit/delete on an attached trigger, webhook, data-upload) now opens the same
drawers the full editor uses, instead of doing nothing. Draft nodes get the
same "save the script first" guard (a trigger row needs a deployed script).

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

* feat(pipeline-ai): run buttons + live run state in the AI session preview

Wire the per-node Run button and live run-state badges into the in-session
pipeline editor, reusing the shared folder-scoped job poll
(useActiveRunnableIds) the route page uses — node badges, the event log, and
the zero-latency "running" hint all come from it. The session runs one node at
a time (preview for an unsaved draft, the deployed version otherwise),
skipping the route page's cascade/deploy-queue machinery the AI-session UX
doesn't need. Verified: a node's Run button dispatches a job and the badge
updates live from the poll.

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

* feat(pipeline): label the node deploy button "Deploy" (was Create/Save)

Users read "Create" and asked whether it deploys. It does — and the main
script editor's DeployButton already says "Deploy", so this is the consistent
term. Use "Deploy" for both the new-script and existing-script cases; the
new-vs-changes nuance stays in the button's tooltip.

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

* feat(home): surface data pipelines as units, including bundle-phase drafts

Treat a data pipeline as one home entry instead of scattering its member
scripts:

- The home "Pipeline · f/<folder>" entry now also covers bundle-phase
  pipelines — a folder that so far only exists as a `data_pipeline` draft —
  not just deployed ones, so a pipeline shows up the moment its first node is
  drafted (union listPipelineFolders + data_pipeline draft folders).
- Pipeline-member scripts (`auto_kind='pipeline'`) are filtered out of the
  individual scripts list; they're represented by their pipeline's entry.
- Tree view injects pipeline folders so they (and their "Pipeline" entry)
  still appear when their only scripts are hidden members or they have none
  deployed yet.

Verified in both list and tree view: app_groups (deployed member folded) and
a draft-only nyc_transit both show as pipelines; the member script no longer
lists individually.

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

* feat(scripts): compute auto_kind for draft-only pipeline nodes

A never-deployed pipeline node (a script draft starting with `// pipeline`)
had no script row, so list_scripts synthesized it with `auto_kind: None` — and
the home page therefore couldn't tell it was a pipeline member, listing it
individually instead of folding it into its pipeline. Parse the draft content
the same way the create path does (`parse_pipeline_annotations(...).in_pipeline`)
and set `auto_kind = "pipeline"` on the synthesized draft-only row, so draft
nodes fold into their pipeline like deployed members.

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

* feat(search): hide pipeline-member scripts from global search

The Ctrl+k global search listed pipeline-member scripts (`auto_kind='pipeline'`)
individually. Filter them out — they're reached through their pipeline, matching
the home page. Deployed members carry auto_kind from the script row; draft-only
members now do too (computed from draft content in list_scripts), so both are
excluded here.

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

* fix(pipeline): address PR review findings

Session run dispatch (the one real bug):
- runNode now passes `_wmill_skip_asset_dispatch: true` for a single-node run
  of a deployed node unless the user chose "run + downstream" (cascade) —
  previously a single Run could fan out to downstream deployed scripts via the
  backend asset dispatcher and fire side-effecting production runs.
- onRunProducer guards `kind === 'script'`; onTestStateChange only clears the
  run hint for the script the pane finished (not a different in-flight node);
  clear the hint on folder retarget; gate the background poll on isActiveSession
  so hidden warm panes don't poll; note the PipelineTriggerEditors workspace
  coupling.

Home page pipeline surfacing:
- Fold pipeline-member folders into `pipelineFolders` (captured in loadScripts)
  so a members-only / draft-only-`// pipeline` folder still shows its pipeline
  entry instead of vanishing; and don't render the empty-state when only
  pipelines remain (they aren't part of the text filter).
- Insert injected tree folders in name order instead of prepending.

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

* docs(pipeline-ai): make clear `// materialize` is DuckDB + DuckLake only

The model put `// materialize` on a python3 node, which deploy rejects ("only
supported for DuckDB scripts"). The prompt only implied SQL ("write the body
as a single SELECT") without stating the hard constraint. Spell it out in both
the in-app prompt and pipeline-base.md: `// materialize` is DuckDB-only and its
target must be a DuckLake table; for python3/bun/postgresql nodes, write the
output via the SDK instead and let it be inferred — reach for duckdb when a
node should materialize a DuckLake table.

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

* docs(pipeline-ai): fix stale comment — session now wires run + trigger affordances

Addresses review: the comment still claimed the session 'opts out of the
run/cascade/trigger/bounded affordances', but run buttons + trigger drawers
were wired in. Describe the current state (wires run + triggers; omits only
cascade/bounded/add-script).

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

* fix(pipeline): address Codex review — test_pipeline_node dispatch + tree search

- [P1] testNode (the test_pipeline_node tool) ran a deployed node via
  runScriptByPath without `_wmill_skip_asset_dispatch`, so previewing one node
  could fan out to downstream deployed subscribers and run side-effecting
  scripts. Add the skip flag (test is always single-node) + a regression test.
- [P2] Home tree view injected pipeline folders — and rendered their Pipeline
  row — even during a text search, surfacing unrelated pipelines. Gate both the
  TreeViewRoot injection and TreeView's hasPipeline on `!isSearching`, matching
  the list view which hides pipeline rows on a query.

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

* fix(pipeline-ai): keep the pipeline prompt after update_user_instructions

rebuildGlobalSystemMessage (called by the update_user_instructions tool)
rebuilt only the base Global prompt, dropping the pipeline-editor section that
configureGlobalMode appends. So after the chat remembered an instruction, the
next GLOBAL turn lost the active /pipeline/<folder> context + direct-draft/
materialize guidance while pipeline tools stayed registered. Re-append the
pipeline section here when a pipeline editor is registered.

Addresses Codex review [P2].

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

* fix(home): gate pipeline entries by kind/archived/owner filters

Codex review [P2]: pipeline rows/folders rendered independently of the item
filters, so a pipeline still showed under the Flows/Apps tabs, in the archived
view, and outside a selected owner. Add `visiblePipelineFolders` applying the
same gates the items get (kind ∈ {all, script}, not archived, owner-prefix
match) and route the list rows, tree injection, and empty-state check through
it. Pipelines are always `f/<folder>`, so the user-folder toggle and kind=script
keep including them.

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

* fix(pipeline): address review — route folder-switch state, AI node guards, diff identity

claude[bot] [P1]: the route page's in-app folder switcher navigates same-route
(no remount), but nothing reset PipelineEditorState — so folder A's drafts
displayed under B and autosave persisted them into B's bundle, and B never
hydrated. Reset pe on folder change (mirror the session retarget), and guard the
shared hydrateDrafts against a stale folder result landing after a retarget.

codex/claude [P2]: build_pipeline_node (proposeNode) only checked drafts.has —
now rejects a path outside the open folder and one colliding with an existing
deployed node (model should edit_pipeline_node). + 3 regression tests.

codex/claude [P2]: exploded pipeline-node diff rows shared `script/<path>` with a
standalone script draft at the same path, colliding in the {#each} key + value
cache. Add an explicit unique `key` (the distinct bundle-nested path) on DiffRow;
pipeline nodes set/look up by it while `path` stays the real edit target.

claude [P2]: session AI test_pipeline_node now arms the live run badge
(onRunStarted), matching the route page.

nit: pipelineAiHelpers.test uses afterEach(restoreAllMocks) instead of an
unreachable inline mockRestore.

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

* fix(pipeline): harden AI node mutations + close home label-filter / rename gaps

Codex [P1] (AI mutations trust model paths) — fully scoped now:
- editNode validates the open folder too (proposeNode already did), via a shared
  assertInFolder; an edit_pipeline_node for f/other/* no longer persists an
  unrelated script into the current folder's data_pipeline bundle.
- both build_pipeline_node and edit_pipeline_node now require the `// pipeline`
  annotation (assertPipelineAnnotation) so a staged draft is definitionally a
  pipeline member, not a silently-non-member script. + tests.
  (proposeNode's folder + deployed-collision guards landed in the prior commit.)

Codex [P2] home label filter — visiblePipelineFolders ignored labelFilter, so a
label selection still showed every pipeline (and the empty-state fell through to
render pipeline rows). Pipelines carry no labels, so a label filter hides them.

Codex [P2] session rename — PipelineEditorView now wires onScriptRenamed
(repoint selection + refetch), matching the route page; a persisted-script
rename no longer leaves the canvas on the old path.

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

* docs(pipeline-ai): language-specific comment prefix for annotations

Codex [P2]: the tool schema and prompt told the model to write `// pipeline` /
`// on` / `// materialize` regardless of language, and pipeline-base.md grouped
SQL with `#`. A `//` (or `#`) annotation line is invalid in a DuckDB/Postgres
node — it passes the frontend parser (which strips `//`/`--`/`#`) but is a SQL
syntax error at deploy/run. Make the guidance language-specific everywhere:
`--` for SQL (duckdb/postgresql), `#` for python3/bash, `//` for bun/TS — the
`//` in examples is the TS form to translate. Regenerated the prompt outputs.

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

* fix(pipeline-ai): re-scope Global prompt on folder switch + language-aware base prompt

Codex [P2] x2:
- The route page resets editor state on an in-app folder switch, but the Global
  chat's system message kept the old `/pipeline/<folder>` scope (the helper
  methods read the reactive folder, but the prompt string is only rebuilt on
  Global-mode reconfigure). Rebuild it on folder change so the next turn targets
  the new folder.
- The pre-editor base Global prompt (seen before open_preview/get_instructions)
  still showed TS-only `// pipeline` / `// on`. Make it language-aware (`--` SQL,
  `#` Python/Bash, `//` TS) so the model can't draft invalid DuckDB/Postgres
  nodes before the pipeline tools are registered.

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

* fix(pipeline-ai): authoritative new-node probe + SQL-correct eval checklist

Codex [P2] x2:
- build_pipeline_node's collision check relied on the resolved graph, which can
  be empty while the session preview races open_preview (a build could shadow a
  deployed node before the graph loads) and only covered pipeline runnables, not
  a non-pipeline script at the same path. Add an authoritative backend probe
  (ScriptService.getScriptByPath): any deployed script at the path → reject with
  "use edit_pipeline_node". + regression test (empty graph, deployed script).
- The DuckLake eval judgeChecklist required the exact `// pipeline` annotation,
  which would penalize the now-correct `-- pipeline` SQL output (or reward
  invalid DuckDB syntax). Make both cases syntax-aware.

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

* fix(pipeline-ai): rebuild Global prompt on session preview folder retarget

Codex [P2]: open_preview(kind="pipeline", path="B") can retarget an existing
pipeline preview from folder A to B without remounting. The retarget effect
resets editor state and the helper methods read the new path, but the
registration effect only depends on isActiveSession, so the Global system
message stayed scoped to /pipeline/A. Mirror the route-page fix: rebuild the
global system message on retarget (gated on isActiveSession — only the active
session's helpers are registered; a hidden session reconfigures when it next
becomes active).

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

* fix(pipeline-ai): edit_pipeline_node preserves deployed script metadata

Codex [P1]: editNode kept only the deployed script's language and staged a fresh
makePipelineScript draft with empty hash/summary/description/tag/schema/settings.
Deploying that edit from the pane (auto_parent) would update the script while
wiping its metadata, and the route "Save all" path (no parent_hash) could hit
the backend path-conflict branch on the occupied path. Base the draft on the
existing draft's / deployed script object and replace ONLY content (+ inferred
output assets), preserving hash and metadata. + regression test.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 20:33:50 +00:00
hugocasaandClaude Opus 4.8 e80c62b958 docs(cli): improve generate-metadata guidance, fix description parser (#9635)
* docs(cli): improve generate-metadata guidance, fix description parser

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

* docs(cli): surface dependency version bumps after generate-metadata

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

* docs(cli): explain generate-metadata scope, import cascade, and --dry-run troubleshooting

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:08:50 +00:00
hugocasaandClaude Opus 4.8 5bdc4f83ce feat(cli): improve agent prompts/skills and workspace fork workflow (#9531)
* feat(cli): improve agent prompts/skills and workspace fork workflow

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

* fix(cli): refuse fork --from-branch rename of a base branch

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

* refactor(cli): auto-detect fork branch workflow, drop rt.d.ts refresh and legacy-name warning

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

* docs(skills): reconcile raw-app generate-metadata stance (agent offers+runs)

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

* docs(skills): agent runs all CLI commands, gated on intent not on user typing them

Extends #9467's safe-vs-destructive model: the agent runs consequential commands (sync push, generate-metadata) itself too, gated on explicit user intent rather than handed to the user to type. The explicit-intent rule is the safeguard; an approval prompt is treated as a possible backstop, not assumed (auto-approve/headless runs have none).

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

* Revert "docs(skills): agent runs all CLI commands, gated on intent not on user typing them"

Reverts 9225e1759b. That commit over-reached: #9467 already established the safe-vs-destructive split, and the targeted item-6 fix already removed the passive "tell the user they can run <safe next step>" phrasing. The blanket "agent runs everything" principle pushed deploys to be more eager and carried a wrong "permission layer prompts for approval" claim (untrue in auto-approve/headless mode). Keep deploys conservative.

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

* feat(cli): default fork workspace name/id to the current branch when renaming it

When 'wmill workspace fork' converts the current working branch into the fork branch, default the fork's name and id to that branch (sanitized to a slug, since branch names can contain '/'). Interactive: the prompt is pre-filled (enter to accept); non-interactive (--yes): used automatically. Adds a unit test for the slug derivation.

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

* fix(cli): address fork review — guard fork-branch rename, cap+validate fork id

Two P2s from review:
- --from-branch refused when the current branch is already a fork branch (would detach the existing fork by renaming its branch).
- fork id slug capped to 42 chars (backend max 50 incl. wm-fork- prefix); auto-derived id is slugged; full id validated client-side before existsWorkspace/datatable cloning so an invalid id fails fast instead of leaving cloned Postgres databases behind.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 00:16:16 +02:00
136c88a231 docs(skills): decouple safe local commands from destructive sync push (#9467)
* docs(skills): decouple safe local commands from destructive sync push

The schedules, triggers, and resources skill templates lumped every CLI
command under a blunt "do NOT run them yourself" directive. This conflated
two very different risk profiles and forbade the agent from running even
read-only/local commands, creating needless friction.

Align these three with the nuanced policy flow-cli.md already uses: keep
`wmill sync push` defensive (it deploys and can be destructive to remote
state — only run when the user explicitly asks to deploy/publish/push),
while letting read-only commands (`sync pull`, `schedule`, `resource
list`) be run freely. Regenerated auto-generated skills + skills.gen.ts.

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

* feat(cli): warn that sync push is destructive in dry-run output

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

* docs(skills): clarify sync pull mutates local files, not read-only

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: centdix <farhadg110@gmail.com>
2026-06-09 14:05:50 +00:00
hugocasa 979b086b08 refactor(cli): fold flow test-step into flow preview --step (#9330) 2026-05-26 11:21:54 +00:00
hugocasa 36f574ff95 feat(cli): add object-storage commands and flow test-step (#9326)
* feat(cli): add object-storage commands and flow test-step

* docs(cli): clarify flow test-step doesn't recurse into aiagent tools

* fix(cli): correct failure step id in docs, handle bare flow.yaml path
2026-05-26 10:43:43 +00:00
centdixandClaude Opus 4.5 110384580e refactor: add global ai chat mode with workspace-item draft tools (#9056)
* docs: add global ai mode plan

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

* feat: add global ai draft mode

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

* fix: scope global ai mode to scripts and flows

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

* refactor: simplify global ai workspace item shape

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

* refactor: split global ai write tool into per-type tools

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

* feat: add global ai schedule and trigger workspace item tools

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

* feat: add dev-only /global_drafts route to inspect ai draft store

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

* feat: add edit_script and patch_flow_json global ai tools

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

* feat: add deploy_workspace_item global ai tool with confirmation

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

* feat: emit open-resource action card after deploy_workspace_item

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

* feat: add delete_workspace_item global ai tool with confirmation

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

* chore(system_prompts): emit RESOURCES_BASE and resource/variable zod schemas

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

* feat: add global ai resource and variable workspace item tools

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

* fix: search_resource_types uses listResourceType to avoid embedding feature dep

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

* Revert "fix: search_resource_types uses listResourceType to avoid embedding feature dep"

This reverts commit 6d1d19514a.

* feat: emit open-resource action card for variable and resource deploys

* feat: add global ai raw app workspace item tools

* feat: split raw-app prompt into chat-only authoring and cli prefix

* feat: add init_app global ai tool to scaffold raw apps from templates

* fix: pass write_flow value as JSON string for gemini compat

* refactor: hoist countExactMatches and applyExactReplace to chat/shared

* refactor: extract editableFlowJson module shared with global mode

* fix(global): preserve flow schema and groups across draft and deploy

* feat: extract inline scripts from flow reads and patches in global mode

* refactor: add findAndReplace helper for match-validated text patches

* refactor: extract getInlineRunnableContent helper for app file tools

* refactor: extract assertNotGeneratedAppFile guard for /wmill.d.ts

* feat: gate global ai mode behind localStorage flag for dev rollout

* chore: bump svelte to ^5.55.5 in raw app template (sync with main)

* fix: isolate global ai draft rollout

* fix: preserve global ai deploy metadata

* fix: harden global ai draft tools

* chore: remove global ai plan doc

* fix: align raw app prompt guidance

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-12 09:42:07 +00:00
centdix 0d0557fc9d feat: add wac ai context for frontend chat (#9021)
* feat: add wac ai context

* fix: limit wac context languages

* fix: pass wac auto kind in flow script drawer
2026-05-05 15:00:06 +00:00
21411282bb fix: route email trigger path through standard info channel (#8996)
* docs(skill): document email triggers and S3 attachments

Add an "Email triggers" section to the triggers skill covering the
local-part config, the parsed_email/raw_email/email_extra_args payload,
the URL-style extras convention, where to find trigger_path (only with
a preprocessor, at event.trigger_path), and — most importantly — that
binary attachments are uploaded to the workspace S3 bucket and surface
as `{ s3: "windmill_emails/<job_id>/attachments/<filename>" }`. Scripts
must use wmill.loadS3File / wmill.load_s3_file to read them.

Also pulls EmailTrigger into the schema mappings so a real
`email_trigger.schema.yaml` is generated, and adds Email/Azure to the
trigger kinds list in the CLI agent guidance.

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

* chore: update ee-repo-ref for email trigger path fix

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

* chore: update ee-repo-ref to 26184ab7a4aadfc529dcedf038aa08d36c7ad381

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

Previous ee-repo-ref: 318a46897a605dc9be3817901f35ba5a99a0a525

New ee-repo-ref: 26184ab7a4aadfc529dcedf038aa08d36c7ad381

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-05-01 16:23:09 +00:00
8196857c8f add workflow-as-code skill (#8970)
* feat: add workflow-as-code skill

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

* fix: make system prompt freshness self-contained

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

* Revert "fix: make system prompt freshness self-contained"

This reverts commit 7d2fde9585.

* fix: refresh wac generated guidance

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

* test: add wac cli eval cases

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

* fix: align wac prompt imports

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-04-30 12:23:59 +00:00
centdixandClaude Opus 4.5 4098793db2 fix: split flow prompts for frontend chat (#8968)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-29 14:56:51 +00:00
eebe24d8b0 feat(cli): wmill dev with per-flow proxy and responsive Dev UI (#8529)
* feat(cli): add `wmill flow dev` subcommand with per-flow reverse proxy and launch.json

Also generates .claude/launch.json for existing flow folders during `wmill init`.

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

* feat: responsive dev layout and hide splitter for single-pane views

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

* fix: clamp flow graph height between minHeight and maxHeight

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

* feat(cli): enhance app new with Claude Desktop integration and better defaults

- Add .claude/launch.json to generated app scaffold for Claude Code preview support
- Add "Open in Claude Desktop?" prompt that creates a CLI session and opens it
  in Claude Desktop Code mode via the claude://resume deep link
- Improve default CSS template with body background, system fonts, and padding

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

* fix(cli): handle both .flow and __flow suffixes in wmill dev

The flow detection in loadPaths only checked the configured suffix
(dotted or non-dotted), so users with nonDottedPaths=true who had
.flow folders (or vice versa) would see inline script edits treated
as standalone script changes instead of flow changes.

Now checks both suffix forms everywhere: type classification,
folder path extraction, path stripping, and loadWmPath lookup.
Also adds raw_app launch.json generation to init and sync pull.

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

* docs(cli): update generated skills with dev workflow and preview commands

Update cli-commands, write-flow, and raw-app skills to document the new
local dev workflow (wmill dev --path, --proxy-port, .claude/launch.json).
Add wmill script preview and wmill flow preview to all script/flow skills
so agents know how to test without deploying.

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

* fix(cli): include path in dev URL and use open.default for browser

- Append &path= to the printed/opened URL when --path is specified
- Use open.default(url) instead of open.openApp for more reliable browser opening

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

* feat(cli): add Claude CLI/Desktop detection hints in wmill flow new

Show contextual instructions for previewing flows based on available tools.

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

* chore: regenerate auto-generated CLI skills for new dev flags

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

* fix(cli): handle mixed flow suffixes in dev file watcher

The ignore() function uses isFlowPath() which only checks the configured
suffix (__flow or .flow), causing files in the other variant to be silently
ignored. Bypass the ignore check for any file inside a flow folder and
force flow type detection regardless of suffix configuration.

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

* feat(cli): drop default proxy in flow folders, open browser, add --no-browser

Manual `wmill dev` in a flow folder should not implicitly enable the
reverse proxy. Both proxy and legacy modes now open the browser; the
new --no-browser flag opts out. Claude Code launch.json templates pass
--no-browser so the IDE preview doesn't fight a system browser window.

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

* feat(cli): gate dev broadcasts by --path and push currentLastEdit on connect

When --path (or auto-detected flow path) is set, drop file events for
any other path so the dev page stays locked to the requested resource
and currentLastEdit can never reflect an unrelated edit. The connection
handler proactively pushes currentLastEdit so the page renders without
waiting for the first file change.

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

* fix(frontend): prefer WebSocket for flow round-trip when wmill dev is connected

updateFlow used isInIframe priority, which routed Claude Code's iframe
preview through postMessage (no listener) and silently dropped flow
edits. Flip the priority: when the wmill dev WebSocket is open, use it
(covers standalone tabs and Claude Code's preview); fall back to
postMessage only when no WS is connected (the VS Code extension's iframe
URL has no `local=true`, so it never opens one). Also stop assigning
lastSent before a channel actually accepted the message, so a CONNECTING
WS doesn't silently swallow the first change.

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

* feat(dev): url is source of truth for path; add workspace file picker

Drops the server-side --path gate added in 3c2d5155e1. The dev page now
filters by its URL's ?path= and the CLI is a dumb broadcaster, which
lets multiple tabs each watch different paths. When the URL has no
?path=, the page asks the CLI for a list of workspace items (flows,
scripts, raw_apps) via a new {type:'listPaths'} WS message and renders
a picker. Clicking a flow or script soft-updates the URL via
history.pushState and loads it; raw_apps surface a hint to use
`wmill app dev` since they don't render here.

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

* feat(dev): picker uses homepage tree view with summaries

Replace the hand-rolled Button-list picker with a TreeView-style layout
that mirrors the Windmill homepage: folder/user tree grouping via
`groupItems`, item rows rendered through the shared `Row.svelte` (no
actions, no favourites, no link — just the visual), a `SearchItems`
fuzzy filter with the same search input styling and placeholder as the
homepage, and `group-open:` chevron toggling on native <details>.

The CLI's listWorkspacePaths now also reads summaries from each item's
metadata (flow.yaml for flows, <script>.script.yaml for scripts) in
parallel so the picker shows summaries as the primary row label, same
as the homepage. Raw apps have no standard manifest so they show the
path only.

Additional polish: title shows "<workspace> (local)" instead of
generic text, subtitle trimmed, item-wrapper owns the border-b so
Row's internal last:border-b-0 doesn't zero it out, summary border
gated on group-open: to avoid doubled lines when a folder is
collapsed.

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

* fix(cli): wmill dev --no-browser was a no-op

Cliffy's `.option("--no-browser", ...)` creates an option named
`browser` (boolean, default undefined) that becomes `false` when the
flag is passed. The previous code checked `opts.noBrowser`, which
Cliffy never populates, so the guard silently no-op'd and the browser
always opened. Rename to `browser` and check `=== false` explicitly,
matching the `wmill app dev --no-open` convention.

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

* feat(dev): picker warns when wmill dev server is unreachable

Track WS state in Dev.svelte (connecting/open/closed) — 'closed' is
set on either the WS error or close event. When closed, the picker
replaces the toggle + search + tree with a warning Alert telling the
user to run `wmill dev` from the workspace root. Toggle and search are
hidden rather than rendered disabled because there's nothing to filter
anyway.

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

* refactor(cli): rename wmill dev --no-browser to --no-open

Match the pre-existing `wmill app dev --no-open` flag. Having
`--no-browser` on one dev command and `--no-open` on the other was
just an oversight from my earlier change. All three launch.json
templates (init, flow new, sync pull) switch to `--no-open`.

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

* feat(cli): wmill init creates root .claude/launch.json for the picker

Adds a workspace-root .claude/launch.json so Claude Code can launch
`wmill dev` from the project root and land on the file picker (no
--path → picker mode). Per-flow and per-raw_app launch.json files are
already generated by the existing scans. Skipped (with a gray log) if
the file already exists, so the user's customizations are preserved.

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

* feat(cli): add skipClaudeAssets wmill.yaml flag

When `skipClaudeAssets: true` is set in wmill.yaml, all generators
that previously wrote Claude-specific assets become no-ops:

- writeAiGuidanceFiles skips CLAUDE.md and .claude/skills/
  (AGENTS.md is still written — vendor-neutral)
- wmill init skips the root .claude/launch.json + per-flow +
  per-raw_app launch.json scans
- wmill sync pull skips the per-flow + per-raw_app launch.json scans
- wmill flow new skips the new flow's .claude/launch.json
- wmill app new skips the new raw_app's .claude/ folder + launch.json

The flag is added to SyncOptions, DEFAULT_SYNC_OPTIONS, and the
generated wmill.yaml template (commented out — opt-in).

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

* feat(cli): wmill init removes existing Claude assets when skipClaudeAssets is set

Re-running `wmill init` with `skipClaudeAssets: true` now removes
previously-generated Claude assets so the workspace state matches the
config. Narrow scope, no confirmation:

- per-flow / per-raw_app .claude/launch.json (each parent .claude/
  collapsed if empty)
- root .claude/launch.json
- .claude/skills/ (wholly ours; safe to remove the subtree)
- root .claude/ collapsed if empty
- CLAUDE.md only if its content matches the default
  ("Instructions are in @AGENTS.md\n"); otherwise left in place
  with a note

Each removal is logged in yellow under a single gray intro line that
prints lazily on the first removal — a clean tree stays silent.

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

* fix(cli): wmill workspace add browser open silently no-ops

`open.openApp(open.apps.browser, { arguments: [url] })` resolves its
Promise even when the OS-level launch does nothing, so the CLI prints
"Opened browser for you" but no tab appears. Same pattern was already
fixed in `dev.ts` by commit 3272c29c2e — use `open.default(url)`,
which delegates to the native URL opener (`open` on macOS, `xdg-open`
on Linux, `start` on Windows) and actually rejects on failure.

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

* fix(cli): wmill init workspace prompt no longer duplicates active profile name

Cliffy's Select.prompt renders `default: X` as `(X)` next to the
question header, which duplicates whichever workspace name the
default points to. Drop `default` and instead reorder the list so
the active profile is first (cursor-preselected by virtue of position)
and append "— active" to its label so the indicator lives where it's
contextually relevant.

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

* docs(skills): expand preview-vs-run guidance for write-flow + all write-script-* skills

Both `wmill flow preview/run` and `wmill script preview/run` have the
same intent split — preview hits the local file, run hits the deployed
version, sync push deploys. The skills' "after writing" sections used
to terse-list the commands and just say "do not run them yourself",
which encouraged the wrong reflex of `sync push` + `run` to "test".

Rewrite the section in both `system_prompts/base/flow-base.md` (drives
write-flow) and the `script_cli_intro` block in
`system_prompts/generate.py` (drives all write-script-<lang>) to:

- explicitly list `preview` as the default for local iteration,
- spell out the few cases when `run` or `sync push` are appropriate,
- offer to test as a one-sentence next step (no multi-option menus),
- mark `preview` as safe to run autonomously.

Regenerate auto-generated/ + cli/src/guidance/skills.ts.

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

* refactor(cli): wmill dev — clearer mode names and accurate startup messaging

- Rename `startLegacyServer` to `startDirectServer`. "Legacy" implied
  it was on the way out; the two modes (proxy vs direct WS) actually
  serve different topologies and both stay. Add comments above each
  section spelling out who they're for: proxy mode for embedders that
  require a localhost origin (Claude Code preview), direct mode for
  standalone browser tabs and the VS Code extension iframe.

- Replace the stale "Dev server will automatically point to the last
  script edited locally" log line. Now print path-aware text:
  - with --path (or auto-detected): "Watching <path> — edits will live
    -reload in the dev page"
  - without: "Open the dev page and pick a flow or script to preview —
    edits will live-reload" plus a hint about --path
  Mirror the same in proxy mode after the listen callback.

- Drop the redundant "Go to <url>" line when --no-open isn't passed
  (maybeOpenBrowser already prints "Opened browser at <url>").

- Rename "Server listening on port 3001" to
  "Dev WebSocket listening on ws://localhost:<port>/ws" so the line's
  purpose is obvious.

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

* refactor(cli): drop per-folder .claude/launch.json generation

Stop creating `.claude/launch.json` inside every flow folder, raw_app
folder, and at `wmill flow new`/`wmill app new` time. The workspace-
root `.claude/launch.json` from `wmill init` stays — it's the picker
entry point and the one place where the deterministic "click → preview"
UX is high-value.

Removed from:
- `wmill init` — per-flow + per-raw_app scans
- `wmill sync pull` — per-flow + per-raw_app scans (also drops the
  now-unused `node:fs` mkdirSync/writeFileSync import)
- `wmill flow new` — bootstrap no longer scaffolds `.claude/`
- `wmill app new` — same; also drops the `.claude/launch.json` lines
  from the post-create directory listing

Skills already give the agent the right CLI commands, so per-folder
launch.json was redundant context. Existing files in user projects
keep working but won't be regenerated; `wmill init` with
`skipClaudeAssets: true` cleans them up.

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

* feat(cli): wmill app new flags + tighten raw-app skill for AI agents

`wmill app new` is interactive by default, which hangs forever when an
AI agent tries to use it. Add flags so the wizard can be bypassed
end-to-end:

- `--summary <text>`, `--path <path>`, `--framework <react19|react18|
  svelte5|vue>` (required for non-interactive)
- `--datatable <name>` (opt into the datatable wizard)
- `--schema <name>` (creates schema with CREATE SCHEMA IF NOT EXISTS
  if it doesn't already exist; only valid with --datatable)
- `--overwrite` (replace existing directory without prompting)
- `--no-open-in-desktop` (suppress the Claude Desktop offer)

Mode is auto-detected: providing any of --summary/--path/--framework
puts the run into non-interactive mode where the datatable wizard,
overwrite prompt, and Claude Desktop prompt all skip silently (or fail
fast on conflict instead of waiting for stdin). Each provided flag is
validated upfront with a clear error message.

Skill side: rewrite `system_prompts/base/raw-app.md`'s "Creating a Raw
App" section so the AI agent knows it should run the command itself
with flags (not tell the user to run it interactively). Direct the
agent to use `AskUserQuestion` with one bundled call to gather any
missing summary/path/framework — refuse to invent values, refuse to
default. Anti-patterns spelled out explicitly.

AGENTS.md template (`cli/src/guidance/core.ts`) had a contradicting
line ("MUST ask the user to run wmill app new in its terminal first")
that was loaded eagerly into agent context and overrode the skill —
replaced with the same agent-driven guidance, pointing to the
raw-app skill for the full procedure.

Regenerate auto-generated/ + cli/src/guidance/skills.ts.

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

* feat(cli): per-target preview launch.json + agent uses wmill flow new

Refactor the agent's dev/preview workflow:

- Drop root .claude/launch.json generation from `wmill init`. Sharing one
  generic entry across sessions caused preview collisions; agents now add
  per-target named entries (windmill: <wmill_path>) on demand.
- New `preview` skill in system_prompts/base/preview.md. Branches on
  whether `mcp__Claude_Preview__*` MCP tools are available: with them,
  add a per-target launch.json entry pinning its own port + --proxy-port
  + --path + --no-open and invoke the MCP preview tool; without them,
  start `wmill dev --path <X> --no-open` directly and hand the URL the
  CLI prints to the user. Never touch launch.json in the direct case.
- Agents must run `wmill flow new <path>` themselves to scaffold flows
  (folder + flow.yaml with the right suffix), parallel to the existing
  `wmill app new` rule. Missing path/summary trigger AskUserQuestion;
  no inventing values.
- write-flow skill: 4-step Creating a Flow procedure that opens the
  visual preview *before* editing flow.yaml so the user watches the
  flow take shape via live reload.
- `wmill flow new` always prints the `wmill dev --path <X>` preview
  hint; drop the Claude CLI/Desktop detection branches.

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

* docs(skills): open app preview before editing in raw-app skill

Mirrors the flow skill's Step 3 — opening `wmill app dev` via the
preview skill before touching App.tsx so the user watches the app
take shape via live reload, instead of seeing the finished result
at the end.

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

* fix(dev): guard WS replaceFlow with lockChanges to prevent echo

The postMessage handler at Dev.svelte:306-312 wraps replaceFlow with
lockChanges = true (cleared 500 ms later) so the $effect on
flowStore.val doesn't immediately re-serialize and re-send the freshly
received payload. The WebSocket handler did not, so on the initial
flow push (dev.ts:568-574 sends currentLastEdit on connect), the
client would echo back to handleFlowRoundTrip, which runs the
orphan-file scan. On content equality the write was a no-op, but the
scan could still delete files the server did not list.

Mirror the same lockChanges/timeout pattern in the WS replaceData
handler. Apply to both flow and script paths for symmetry.

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

* fix(cli): correct wmill dev description + gate broadcasts server-side

Two related fixes:

1. The 'auto-pushes them to the remote workspace' wording in the
   wmill dev description was wrong — the command never deploys, it
   only broadcasts file changes over WS for live preview. Reworded
   to call this out explicitly and point at 'wmill sync push' for
   the deploy case.

2. Move the path filter out of the client (Dev.svelte:491-495) and
   into broadcastChanges. Earlier the filter was client-side with
   the comment 'server stays a dumb broadcaster' even though commit
   3c2d5155 was titled 'gate dev broadcasts by --path'. Doing the
   compare server-side aligns the implementation with the commit
   narrative, cuts WS traffic when --path is set, and keeps the
   per-tab semantics for the picker (each picker tab still gets the
   full 'paths' listing on first connect).

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

* fix(cli): drop dead launch.json cleanup, fix description, narrow orphan scope

Three review fixes:

1. cleanupClaudeAssets removed both root and per-folder
   .claude/launch.json files that this CLI never generates anymore.
   Per the user's "feature hasn't been released yet" guidance, no
   migration is needed — drop the dead scan and the root rm. Also
   drop the now-unused nonDottedPaths argument (and its flowSuffix
   / rawAppSuffix locals).

2. The skipClaudeAssets description in template.ts listed
   .claude/launch.json among the assets it skips, but launch.json
   is no longer generated. Drop it from the description string.

3. The dev round-trip's orphan cleanup deleted any non-dot file in
   a flow folder that wasn't in extractedPaths — including
   README.md, fixtures, TODO.md, etc. Restrict the deletion to
   files whose extension is in a known inline-script set
   (.ts/.js/.py/.go/.sh/.sql/.ps1/.php/.rs/.java/.cs/.r/.graphql).

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

* refactor(cli, frontend): dedupe flow suffix helpers, use UI components

Five small follow-ups from the PR review:

1. dev.ts already had stripFolderSuffix() but three callsites were
   reimplementing the same .flow/__flow if-else inline. Add an
   isFlowFolderName(name) helper next to it and replace the duplicates
   in startProxyServer's cwd check, the file-watcher localPath strip,
   and normalizeWmPath.

2. Dev.svelte:866 was a <div onclick> with two svelte-ignore comments
   for the missing a11y handlers. Replace with a real <button
   type="button"> — kills the warnings, no visual change.

3. Dev.svelte:1283 was a raw <input type="text"> for the module
   summary. Replace with the existing <TextInput> component (same one
   the picker search at :1010 uses), per frontend/CLAUDE.md.

4. Dev.svelte:197 typed relativePaths as any[]; tighten to the actual
   union (string | [number, string])[] — the python helper returns
   tuples, the typescript one returns strings.

5. app/new.ts:822 fired exec("open <deeplink>") with no callback, so
   an OS that refused the URL scheme silently failed and we still
   logged "Opened in Claude Desktop!". Move the success log inside an
   exec callback that surfaces the error and prints the deep link for
   manual opening.

Plus a brief comment above parseWatchPath explaining its resync
contract (initial load + popstate + explicit pickPath, no generic
pushState listener).

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

* fix(cli): regenerate auto-gen for dev description; drop apostrophe to satisfy parser

generate.py:326 extracts .description() with the regex
[^"\']+ which bails on either quote type. Commit ff3a8e4ebd's new
description had an apostrophe inside double quotes ('wmill sync
push'), so the parser saw no description at all and the
auto-generated files dropped the line entirely — which is what
check-freshness caught on origin/main.

Quickest path to green CI: rephrase the description without the
inner apostrophe, then regenerate. The generator's regex is the
real bug but fixing it is out of scope here.

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

* feat(cli): seed app .claude/launch.json before opening Claude Desktop

When the user accepts "Open in Claude Desktop?" in wmill app new, write
a per-app .claude/launch.json (named "windmill: <appPath>") into the
freshly-created app folder before the deep link fires. Entry runs
'wmill app dev --no-open --port ${PORT:-4001}' from the app folder
(which is the cwd Claude Desktop opens with), so the user can hit play
right away to launch the preview.

Skip if .claude/launch.json already exists — never clobber user edits.

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

* fix toggles positions

* fix(dev): gate picker mode on ?local= so VS Code iframe still renders content

The VS Code extension iframe loads the dev page without ?path= and
without ?local=true. After the picker rework, an empty watchPath
flipped pickerMode on, so the page rendered the picker UI even
though the extension was sending replaceScript / replaceFlow
postMessages — leaving the user stuck on the picker forever.

Picker mode only makes sense on the local dev page, where the wmill
dev WebSocket can supply the workspace listing. Anywhere else (VS
Code iframe, plain remote tab) the picker has no data source and no
purpose. Add an isLocalDevPage check so the picker only shows when
?local=true is present.

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

* refactor(dev): mirror vscode extension's processFlowMessage round-trip

Three changes that bring our wmill dev round-trip into lockstep with
the windmill-vscode extension's processFlowMessage in src/extension.ts:

1. New cli/src/commands/dev/pathscript-restore.ts — verbatim port of
   the extension's src/utils/pathscript-restore.ts. Adds AI-agent tool
   walking that the previous local copy was missing (flows with
   PathScript-shaped tools weren't being preserved across round-trip).
   Header comment makes the cross-repo link explicit.

2. handleFlowRoundTrip rewritten to mirror processFlowMessage step-
   for-step: reads failure_module + preprocessor_module from the
   current flow.yaml, passes them to extractCurrentMapping, shares one
   pathAssigner across all extraction calls, extracts inline scripts
   from those special modules too, skips writing files whose content
   starts with !inline (treats as pointer directives), and only
   rewrites flow.yaml when the serialized YAML actually differs.

3. snapshotPathScripts / tagReplacedPathScripts callsites in loadPaths
   were passing the FlowFile wrapper instead of FlowFile.value — the
   helpers walk .modules / .failure_module / .preprocessor_module,
   which only exist on .value, so PathScript snapshots silently
   no-op'd on the file-watcher path. Pass .value at all four sites.

Deliberate divergence from the extension: orphan-cleanup keeps the
INLINE_SCRIPT_EXTS allow-list so README.md / fixtures aren't deleted.
The extension's version still over-deletes; that's tracked separately.

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

* docs(skills): offer visual preview after create instead of auto-opening

Both write-flow and raw-app skills used to instruct the agent to open
the visual preview without asking right after wmill flow new /
wmill app new, on the rationale that live reload is most useful when
the page is already up. In practice this surprised users — opening
the dev page has side effects (browser window pop, possibly a
launch.json entry under MCP-preview Branch A) that warrant consent.

Change Step 3 in both skills from "open it without asking" to "offer
it as a one-sentence next step" — same pattern the same skills
already use for programmatic wmill flow preview offers. Two then-
necessary anti-patterns ("just open it", "open it before editing")
are dropped along with the auto-open instruction.

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

* fix(cli): probe both ip stacks before binding wmill dev proxy / app dev port

Node's default listen() has platform-dependent dual-stack behaviour.
If the requested port is already held on the IPv4 stack, listen() can
silently fall back to binding IPv6-only ([::1]:N). The OS then routes
new localhost connections to the older IPv4 listener, so the user
opens http://localhost:N and sees a stale prior server with no signal
that anything is wrong. Bit us in practice: a leftover wmill dev
--proxy-port 4000 served traffic for a freshly-started wmill app dev
--port 4000.

New helper at cli/src/utils/port-probe.ts probes both 0.0.0.0 and ::
before binding. On collision it walks upward to the next free port
(up to +20) and logs a prominent warning naming the holder when lsof
/ ss can find it:

  Port 4000 is already in use (held by PID 91418 `bun`). Using
  port 4001 instead.

Wired into:
- wmill dev --proxy-port: the resolved port flows into both
  proxyServer.listen() and the &port=N parameter in the redirect
  URL, so they always match. Bind explicitly to 0.0.0.0.
- wmill app dev --port: only when the user passed --port explicitly
  (the default getPort.default(...) path already handles fallback).

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

* fix(dev): pass placeholder via TextInput inputProps not as top-level prop

`<TextInput>`'s top-level Props don't include `placeholder` — native
input attributes go through the `inputProps` field. The previous
`<TextInput placeholder="Summary" .../>` failed `npm run check` with
"Object literal may only specify known properties, and
'\"placeholder\"' does not exist in type 'Props<\"input\">'.".

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

* nit

* fix(cli): sequential port probe + sync dev test regex with renamed log

Two CI regressions on test-linux:

1. port-probe parallel race on Linux. isPortFreeOnBothStacks ran the
   IPv4 and IPv6 binds via Promise.all. On Linux the default is
   net.ipv6.bindv6only=0, so a bind(::, port) socket also takes the
   IPv4 stack on the same port. Concurrent v4 + v6 binds then race for
   v4 — one wins, the other gets EADDRINUSE on a port that is actually
   free. Walks 20 ports up, all fail the same way, throws, child exits.
   Tests that fetch http://localhost:port time out at 60s.
   Doesn't repro on macOS (bindv6only=1 by default — what I tested
   against). Probe sequentially so each bind fully releases before the
   next starts.

2. dev_server.test 1 regex out of sync. Commit 018dc3861a renamed the
   startup log from "Server listening on port N" to "Dev WebSocket
   listening on ws://localhost:N/ws" but didn't update the test, which
   times out at 30s waiting for the old string. Update the regex to
   match the current log.

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

* Update system_prompts/auto-generated/skills/write-script-graphql/SKILL.md

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* fix(cli): address dev/app PR review — bugs 1-7

Per code review:

1. app new.ts — wrap claude --session-id exec in try/finally so the
   spinner setInterval is always cleared. On rejection control jumped
   to the outer catch and the spinner kept writing \r forever, garbling
   subsequent output.

2. app new.ts — make --overwrite actually wipe the dir before
   re-creating. Previously logged "Overwriting" but only skipped the
   prompt; leftover files from a different framework (e.g. App.tsx
   from a prior react18 install when re-scaffolding as svelte5)
   survived and produced a hybrid scaffold.

3. dev/dev.ts — anchor the flow-folder match on path segments. The
   substring checks (cpath.includes(".flow/") / "__flow/") also fired
   on names like notes_about__flow_design/readme.md. New
   isInsideFlowFolder + findFlowFolderPrefix split on "/" and check
   segment suffixes. Drops the now-unreachable script→flow fallback
   inside the else branch.

4. dev/dev.ts — direct mode also routes through resolveBindPort so it
   detects dual-stack collisions like the proxy mode does. Bare getPort
   only probes one stack, defeating the whole point of port-probe.ts.
   Also bind to BIND_HOST explicitly. Drops the unused getPort import.

5. dev/dev.ts — normalize opts.path once after mergeConfigWithConfigFile.
   broadcastChanges compared against a non-normalized opts.path, so
   --path f/foo/ or --path f/foo.flow silently dropped every broadcast.
   Also pulls normalizeWmPath to module scope (was a closure inside dev()).

6. dev/dev.ts — guard the initial-state ws.send with readyState === OPEN,
   matching the other branches' pattern.

7. dev/dev.ts — typo: "givena" → "given a".

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

* fix(cli): address dev/app PR review — items 8-10

8. dev/dev.ts — derive INLINE_SCRIPT_EXTS from exts so adding a new
   script language to script.ts auto-extends orphan cleanup. Previously
   .gql, .nu, .rb were missing — flows using those languages would
   leave orphaned inline files behind. Excludes .yml because user
   fixtures commonly use it in flow folders, and leaving a stale
   .playbook.yml inline script is preferable to deleting a fixture.
   Keeps .js for hand-written flows that aren't in the exts list.

9. app/new.ts — wrap Claude Desktop install probe + prompt in
   process.platform === "darwin". The probe (ls /Applications/Claude.app)
   and the open command both only work on macOS — the explicit guard
   makes the platform scope grep-able.

10. app/new.ts — switch the deep-link spawn from exec(`open ${shell-
    escaped url}`) to execFile("open", [deepLink]). sessionId is a UUID
    and absAppDir is URI-encoded today so the old form was safe, but
    execFile removes the shell entirely.

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

* fix(cli,dev): cubic review — port-probe error semantics, pluralize spacing

[2] cli/src/utils/port-probe.ts — distinguish IPv6-unsupported from port
collision in isPortFree. Previously every error code returned false,
including EAFNOSUPPORT / EADDRNOTAVAIL on the IPv6 probe when the host
has no v6 stack at all (IPv4-only containers). resolveBindPort would
then walk all 20 ports getting the same error and throw. Treat only
EADDRINUSE / EACCES as "not free"; everything else as free.

[13] cli/src/commands/app/dev.ts — only probe both stacks when binding
to localhost. The dual-stack collision risk is specific to localhost
(which resolves to 127.0.0.1 + ::1); for an explicit IPv4 host there's
only one stack to worry about, so don't move the user's requested port
over a phantom v6 collision.

[14] frontend/src/lib/components/Dev.svelte — pluralize already inserts
a space between quantity and word, so " item" produced "3  items".
Drop the leading space in both call sites.

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

* docs(system_prompts): cubic review — preview args, skill scopes

Source changes in base/ + generate.py, then regenerated auto-generated/
via python system_prompts/generate.py. Per cubic review:

[4]+[7] generate.py — "pick plausible args from the `main` signature"
was language-blind. SQL queries and Bash scripts use $1/$2 positional
parameters, not a main(...) signature. Reword to call out both shapes
explicitly so the wording survives across all 19 generated language
skills (postgresql, bash, mysql, …) instead of just the ones that
happen to have main().

[5] base/raw-app.md — the "CLI Commands" table said "Tell the user
they can run these commands (do NOT run them yourself)" while the
"Creating a Raw App" section above (added in this PR) tells the agent
to run `wmill app new` itself. Carve `wmill app new` out of the table
and add a one-line note pointing back to the create flow, so the
guidance no longer self-contradicts.

[10] base/preview.md — "These print a `Go to <url>` line on stdout"
was wrong for `wmill app dev`, which prints
"🚀 Dev server running at <url>". List both line shapes explicitly and
suggest a loose http:// match for URL capture.

[12] base/flow-base.md — "regenerate lock files for the flow you
modified" misstated the default scope. `wmill generate-metadata`
scans scripts, flows, and apps by default
(see cli/src/commands/generate-metadata/generate-metadata.ts:71-73).
Update wording to call out the default scope and how to narrow it.

Also folds the cubic [1] graphql safety wording (originally a one-off
edit on the auto-generated file in a895db7) back into generate.py
itself, so it survives regeneration and applies to all language skills.

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

* docs(system_prompts): cubic round 2 — language-specific placeholder syntax

Round 1 wording was too narrow:

- "$1, $2 placeholders for SQL queries and Bash" was wrong for MySQL
  (`?`), Snowflake (`?`), MSSQL (`@P1`), BigQuery (`@name`), and
  PowerShell (which uses `param(...)`, not main()).
- The preview-skill URL match said "first `http://...` token" — remote
  workspaces serve HTTPS, so the regex would miss them.

Source-only fixes in generate.py and base/preview.md, then regenerated
auto-generated/ via python system_prompts/generate.py.

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

* fix(flow): track maxHeight in FlowGraphV2 height effect

cubic [3]: updateHeight() reads both minHeight and maxHeight, but the
$effect only tracked minHeight. Changing maxHeight alone (e.g. when a
parent shrinks the cap during a layout transition) left height frozen
at the previously clamped value.

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

* docs(system_prompts): tool-agnostic wording in base/

cubic [11]: system_prompts/README.md says these prompts must NOT
contain tool usage instructions. Three base files violated this:

- base/flow-base.md (4× AskUserQuestion). Worst offender — leaks into
  the frontend copilot via FLOW_BASE in prompts.ts (consumed by
  getFlowPrompt in frontend/src/lib/components/copilot/chat/flow/
  core.ts:1287). Frontend has no AskUserQuestion tool, so the wording
  was both irrelevant and confusing there.
- base/raw-app.md (5× AskUserQuestion + 1× mcp__Claude_Preview__).
  CLI-skill-only but covered by the same scope rule.
- base/preview.md (5× mcp__Claude_Preview__). CLI-skill-only, same.

Replaced with role descriptions: "ask the user (use a structured-
question tool if your runtime has one)" and "a tool that can embed a
localhost URL inside the IDE / chat surface". Kept one mention of
mcp__Claude_Preview__ in preview.md as an illustrative example, since
documentation of one runtime is fine — what's not fine is gating
behaviour on a specific tool name.

Source-only edits, then regenerated auto-generated/ via
python system_prompts/generate.py.

Verification: grep -r AskUserQuestion system_prompts/auto-generated/
now returns nothing. The remaining AskUserQuestion refs in
cli/src/guidance/core.ts are hand-written CLI-only AGENTS.md content
(not part of system_prompts), and Claude Code does have that tool, so
those are correctly scoped.

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

* fix(flow): drop .no-splitter CSS hack, use snippets to skip Splitpanes

cubic [8]: the previous fix for "top pane is empty in aiagent / noEditor
mode" was a CSS rule that hid `:global(.splitpanes__splitter)` inside
.no-splitter. That cascaded into nested splitpanes too — the aiagent
left/right tabs panel (line 1043), the debug-console editor split
(line 877), and the doubly-nested debug panel (line 1472) all lost
their resize handles.

Refactor the layout instead. Extract top-pane and bottom-pane content
as snippets, then conditionally render either:
  - just the bottom snippet (no Splitpanes wrapper) when the top pane
    would be empty (aiagent or noEditor), or
  - the original two-Pane Splitpanes layout otherwise.

This removes the splitter at its root rather than hiding it, so
nested splitters are unaffected. The bottom Pane's complex bind:size
getter/setter (which returned 100 when aiagent) collapses to a simple
binding now that the aiagent path no longer goes through the wrapping
Pane at all.

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

* nit

* fix(flow,preview): cubic round 3 — FlowPathViewer regression + preview skill rewrite

[3149192182] FlowModuleComponent.svelte: my last refactor's
"aiagent || noEditor" condition stripped the FlowPathViewer for
noEditor + type === 'flow', because the top-pane snippet was no longer
rendered. The flow-viewer pane is the only thing that *does* show in
that mode, so it shouldn't have been collapsed. Tighten the condition
to "aiagent || (noEditor && type !== 'flow')".

[3149060930] system_prompts/base/preview.md: Branch A detection was
too broad — "can embed or open a localhost URL" is strictly weaker
than "can read .claude/launch.json and launch a configuration". Only
the Claude Desktop / Code MCP integration does the latter; most
embedders only do the former. Restructure preview.md around two
orthogonal axes:

  1. Mode (proxy vs direct) — driven by "does the embedder need a
     localhost URL?". Direct is the default; proxy is for embedders
     that sandbox cross-origin loads.
  2. Who starts the server — you spawn `wmill dev` yourself, OR a
     launch.json-aware runtime (currently only the
     `mcp__Claude_Preview__*` MCP family) launches it on demand.

The two compose into four common cases (regular browser tab, generic
preview pane, localhost-only preview pane, Claude MCP), each with a
clear instruction. The launch.json/MCP machinery is now scoped to a
single section gated on actually having that tool in your tool list.

Source-only edit in base/preview.md, then regenerated auto-generated/
via python system_prompts/generate.py.

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

* Fix dev step display

* nit

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-04-27 20:14:21 +00:00
centdixandClaude Opus 4.5 d3cb0c6220 fix: improve flow chat and benchmark coverage (#8825)
* fix: support special flow modules in evals

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

* refactor: extract shared flow helper logic

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

* fix: make special flow tools openai-compatible

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

* fix: improve flow eval prompts and validation

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

* test: relax flow benchmark overfits

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

* test: record updated flow benchmark history

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

* fix: address flow review findings

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

* refactor: source flow chat special module prompt

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

* fix: narrow rawscript helper return type

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

* refactor: dedupe flow chat prompt guidance

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

* fix: relax flow test10 validation

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-15 16:22:39 +00:00
centdixandClaude Opus 4.5 b960598431 fix: hide deprecated cli metadata commands (#8699)
* fix: hide deprecated cli metadata commands

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

* fix: simplify generate-metadata guidance

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-03 15:38:01 +00:00
Ruben FiszelandClaude Opus 4.6 b1b9c984e3 make wmill init generated skills respect nonDottedPaths config (#8377)
* docs: add nonDottedPaths convention to CLAUDE.md

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

* docs(cli): update generated skills to use non-dotted path conventions

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

* fix(cli): make generated skills respect nonDottedPaths config

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

* fix(cli): inject nonDottedPaths placeholders in generate.py for skills.ts

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

* fix: default system prompts to non-dotted path conventions

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 05:05:31 +00:00
centdixandClaude Opus 4.5 7fb729cc84 fix(cli): instruct agent to tell user about generate-metadata and sync push instead of running them (#8318)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-12 10:18:03 +00:00
centdixandClaude Opus 4.6 b40cf80fdd fix: optimize flow lock generation and add rt.d.ts guidance for TS resource types (#8295)
Instruct AI to pass specific flow folder path to `wmill flow generate-locks`
instead of running it on all flows. Also add guidance for TypeScript language
files to check `rt.d.ts` for available resource types before using them.
Re-ran generate.py to propagate changes to all auto-generated files.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 13:03:21 +00:00
a7ce5484b8 feat(local-dev): create Claude skills when doing wmill init (#7699)
* use skills

* add prompts

* update system prompts

* generate skills on init

* add prompts in cli

* better for raw apps

* nit

* test pipeline draft

* better

* yaml for triggers and schedules

* cleaning

* better

* add descriptions to ai agent fileds

* adjust

* better openapi

* better

* nit

* feat: add typed provider and memory schemas for ai agent in openapi

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

* feat: improve zod validation errors with dynamic schema extraction

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

* regen

* fix

* cleaning

* refactor: deduplicate skill descriptions in generate_skills_ts_export

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

* cleaning

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-02-05 13:48:32 +00:00
centdix 31e002ad41 feat(ai): standardize and improve system prompts (#7346)
* init

* test in frontend

* copy files

* use in cli

* better

* add desc to sdks

* better

* fix ts parsing

* add docs to ts client

* add docs to python client

* use script prompt in frontend

* regen

* use in flow

* rm

* use in cli, create AGENTS.md instead of cursor rules

* remove apply

* better

* better

* simplify cli

* more docs

* cleaning

* update readme

* generate cli file

* better folder names

* fix ts

* fix multiline
2025-12-12 17:26:19 +00:00