mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 16:02:11 +00:00
425e4ac4010101d16ba64affcdf336aa2b6d9f64
13981 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
425e4ac401 |
refactor(alerts): read the limiter's gate mark instead of re-deriving it
The alert reconstructed the limiter's admission test in SQL to tell a gate-parked backlog from worker starvation. That test spans the running counter, completions inside the time window, per-version setting fallbacks and three bypass paths, and nothing linked the two copies, so each divergence surfaced as a fresh false alert or a suppressed one. Adds v2_job_queue.concurrency_gated, which the limiter sets when it re-queues a job it could not admit; the alert counts unmarked jobs. The query drops to a single scan with no joins. Emitter and limiter changes live in windmill-ee-private (see ee-repo-ref.txt bump). |
||
|
|
0c09b4fda0 |
fix(alerts): keep soft-cancelled jobs in the capacity count
The pull path skips concurrency limiting whenever canceled_by is set, so a cancelled job waits for a worker rather than for a gate, and reading it off key saturation let a saturated key hide a cancelled backlog during a worker outage. Emitter change lives in windmill-ee-private (see ee-repo-ref.txt bump). Adds the cancellation regression test and refreshes the query cache. |
||
|
|
d805bca94d |
fix(alerts): honor limiter bypasses when classifying gates
The classifier derived saturation purely from stored counter and window state, but the limiter admits unconditionally when DISABLE_CONCURRENCY_LIMIT is set or the resolved key is empty, so excluding on a gate in those modes suppressed the alert during a genuine capacity outage. Emitter change lives in windmill-ee-private (see ee-repo-ref.txt bump). Adds the empty-key regression test and refreshes the query cache. |
||
|
|
39c0e7c4aa |
fix(alerts): count time-window completions when classifying gates
The saturation check read only concurrency_counter.job_uuids, but the limiter admits on running jobs plus those that ended inside concurrency_time_window_s, so a gate held shut purely by recent completions read as free and its backlog raised the same false capacity alert one completion later. Emitter change lives in windmill-ee-private (see ee-repo-ref.txt bump). Adds the regression test for that case and refreshes the query cache. |
||
|
|
4b3225e5f3 |
test(alerts): trim queue-depth alert tests to the essential pair
Keep the misfire repro and a single guard covering both the plain and the unsaturated-gated branches of the classification. The dropped test asserted alert-body wording and re-exercised counting behavior the fix does not change. |
||
|
|
70eb288551 |
fix(alerts): exclude concurrency-gated jobs from queue-depth alert
The "Jobs waiting in queue" critical alert counted every non-running job in v2_job_queue past the time threshold, conflating fleet capacity shortage with a workspace's own concurrency limit holding its jobs back. Only the former is actionable by whoever is paged, so a single runnable with concurrent_limit=1 and a large backlog pages repeatedly against a near-idle fleet. Emitter change lives in windmill-ee-private (see ee-repo-ref.txt bump). This commit carries the regression tests and the offline query cache. |
||
|
|
542a4842a3 |
feat(ai): live web-search source list on chat tool cards (#10210)
* feat(ai): live web-search source list on chat tool cards * fix(ai): restrict web-search source links to http(s), respect user collapse * fix(ai): read plural queries field for openai web-search labels |
||
|
|
b448af1da7 |
feat(sessions): live DOM access for the raw-app preview in AI sessions (#10129)
* feat(sessions): live DOM access for the raw-app preview in AI sessions Give the session chat read-only access to the rendered raw-app preview, plus inspector-picked element chips synced bidirectionally with the preview. - search_dom / read_dom tools: live, same-origin contentDocument reads by CSS selector (selector omitted = whole body), reusing the file engine over pretty-printed outerHTML (worker-guarded, bounded). Session-gated. - Inspector picks become app_dom_selector context chips (selector-only); the model fetches content on demand. Chips shown even in GLOBAL/session mode. - Multi-select chips synced with the preview: the chip list is the source of truth, pushed to the harness which renders one highlight per selector; add, chip-remove, and preview-× remove all stay in sync. Overlays stripped from search_dom output. The ui_builder harness changes (multi-select highlights + unique nth-of-type selectors) live in windmill-code-ui-builder and ship via the artifact re-pin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(sessions): inline element prompt + inspector polish for raw-app DOM Session preview: - Add InlineElementPrompt: a floating mini-composer anchored top-left over a selected element; sends a chat turn scoped to that element (its chip is context). Autofocus + remount per selection, borderless input, label gutter so it clears the harness name+size pill. - autosize action gains an optional minHeight (default 30) so the compact inline input renders a tight single line. - Fix exiting select mode: Esc / inspector toggle-off now fully clear the session's DOM-selector chips (source of truth) so the overlays AND the inline prompt are dismissed together, instead of leaving them stranded. Full-page raw-app editor: - Add inspectorHoverOnly: the inspector highlights on hover (outline + name/size) but a click selects nothing — no persistent selection, no app-mode context pick, no inline prompt. Context badges: - ContextElementBadge gains a compact prop (passed only by AIChatMessage): the DOM-selector chip and tagged workspace items (app/script/flow) render smaller above a sent message, while every badge stays the same size in the composer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): address Codex review P1s on raw-app DOM context - Inline element prompt: queue the turn when the chat manager is already streaming (mirror the composer) instead of a concurrent sendRequest that would race the shared abortController / streaming buffers. - DOM selector chips now carry the raw-app path they were picked from. Each preview tab renders/pushes only its own app's chips, and a tab clears cross-app chips when it becomes the active DOM target — a selector could otherwise silently resolve against whichever raw-app tab is active (search_dom/read_dom target the active preview). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(raw-apps): re-pin ui_builder artifact to f79e1c3 Bumps the UI Builder artifact to the release built from ui_builder main after windmill-code-ui-builder#18 merged (the multi-select DOM inspector synced with the host chat). This is the artifact re-pin step that gates merging this PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): inline-prompt vertical centering + auto-collapse DOM tools - InlineElementPrompt: bias the textarea padding (pt-[5px] pb-[3px], same total) so the single line sits centered in the pill — the shared autosize floor otherwise adds slack at the bottom and the text read 1px high. - search_dom / read_dom: drop the explicit `autoCollapseDetails: false` so the tool card collapses after a successful read like other tools (it was copied from get_app_runtime_logs, where staying open is intentional). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): address Codex review on raw-app DOM access - [P1] Release the DOM-requester slot when the active raw-app tab is hidden. Preview tabs stay mounted, so switching to a flow/script/page (or another raw-app) tab left search_dom/read_dom targeting the now-hidden raw app. The slot is now claimed with a per-tab owner token and released via the effect cleanup; the runtime only lets the claiming tab clear it, so a set/release race between two raw-app tabs can't blank the new owner. - [P2] Strip the inspector outline classes from the clone ROOT (the selected element), not just its descendants — querySelectorAll skips the root, so a scoped read of the selected element leaked inspector-picked in its outerHTML. - [P2] Reword the AIChatDisplay context-chip comment to drop drafting-history narration ("now", "not as rows here") per AGENTS.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): scope a queued inline prompt to its own element (Codex P1) An inline element prompt submitted while a turn was streaming got queued as plain text; if the user selected another element before the queue flushed, the queued turn snapshotted the new selection and sent scoped to the wrong element (or none). The queue now carries a context snapshot: - queueMessage(text, context?) pins the selection present at submit time. - sendRequest accepts contextOverride and uses it verbatim as the turn's selected context (feeds both the optimistic bubble and the API message). - All three queue-flush sites thread the snapshot through and restore it if the auto-send bails. - With an override, only the queued message's own DOM chips are consumed from the live selection — a newer selection made since is left intact — preserving the one-shot chip semantics. onInlinePrompt snapshots getSelectedContext() when it queues. (Codex's other P1 — full-page-editor hover-only no longer attaching elements to App AI — is intentional per an explicit product decision, left unchanged.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(raw-apps): restore full-page editor App-AI inspector context Codex/Pi flagged a real regression: passing inspectorHoverOnly disabled the full-page editor's existing App-AI (app-mode) inspector context. Clicking an inspected element no longer set inspectorElement, so the app-mode SelectedContext badge and the element attach in prepareAppUserMessage were dead. inspectorHoverOnly and the session path (onInspectorSelect) are mutually exclusive — the flag was only ever set in the full-page/app-mode context, so it only suppressed the app-mode path. Remove it entirely (prop, guard, and the pass in +page.svelte), restoring the original click -> inspectorElement -> app-mode context behavior. Session behavior is unchanged; the hover highlight still works in both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): snapshot inline-prompt context synchronously (Codex P1) The immediate inline-prompt path called sendRequest without a context override, so the selected element was read only after the async send preflight (attached-file refresh, beforeSend, global-skill refresh). Picking another element during that window attached the wrong one to the outgoing turn. onInlinePrompt now snapshots the selection synchronously at submit time and passes it as contextOverride on both the queued and immediate paths, so the prompt always rides with the element it was scoped to. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): drop a queued inline prompt's DOM chips when the app changes A queued inline prompt scoped to raw app A described A's elements, but the runtime's single DOM requester targets whichever preview is active. Switching to app B before the queue flushed left the turn describing A while search_dom / read_dom would query B. When a raw-app preview tab becomes active it now also strips the queued turn's DOM chips belonging to other apps (dropQueuedDomContextForOtherApps), so a queued prompt can't ask the model to read one app's selectors against another's live DOM. Non-DOM queued context is preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): scope inline prompt to its anchored element (Codex P1) The inline prompt sits over a single element but snapshotted EVERY selected DOM chip. With several elements selected (Shift-multi-select), a prompt shown over B sent both A and B as context, so "change this button" couldn't identify which one it meant. onInlinePrompt now uses its anchored `selector` argument (previously ignored) to keep only that element's DOM chip in the snapshot; non-DOM context is preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): preserve original context on edit/retry (Codex P1) restartGeneration resent with the live selection, so editing or retrying an element-scoped prompt lost its DOM selector (or adopted a newer one) — DOM chips are one-shot and cleared from the live selection after the first send. It now passes userMessage.contextElements as contextOverride, re-using the exact context the message was originally sent with. undefined (modes that don't attach contextElements) falls back to the live selection as before. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(sessions): route search_dom/read_dom to the selector's own app preview The DOM requester was a single active-tab slot, so search_dom/read_dom always read whichever preview was visible. A turn scoped to app A that read the DOM after the user switched to app B would silently read B (Codex P1). Preview tabs stay MOUNTED when hidden, so app A's DOM is still there — the fix is to route by app instead of "active tab": - Every mounted RawAppEditorView registers its DOM requester keyed by its app path (runtime holds a map, not one slot); the visible tab is tracked separately as the default target. - search_dom / read_dom gain an `app_path` (surfaced per chip in the SELECTED DOM ELEMENTS block and the tool schema); the runtime routes the query to that app's still-mounted preview. No app_path → the active preview, or the only one open. - If the named app's preview has been closed, the tool returns "The preview for X is no longer open…" so the chat can explain it, rather than reading the wrong app. This makes the queued-context drop-on-switch (dropQueuedDomContextForOtherApps) redundant — removed. The lightweight submit-time snapshot (which element the message is about) is kept. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): address Codex review on DOM access (script strip + app-scoped chips) - search_dom/read_dom: strip the app's compiled <script> bundle from the whole-body clone so queries only see rendered HTML, not source (P1). - DOM chip add-dedup and removal now match both selector AND app path, so an identical selector in another app can't drop or block a chip (P2). - Refresh stale single-slot requester comments to describe per-app routing; reword the foreign-chip reset to reflect why it stays (composer chips are unlabeled by app) (P2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sessions): scope DOM-chip clear to its app so a rebuild can't wipe another (Codex P2) Every mounted preview emits inspectorClear on rebuild, routed to onInspectorClearAll → clearSelectedDomElements(). Unscoped, a hidden app A rebuilding cleared app B's active chip and highlight. clearSelectedDomElements now takes an optional appPath; the preview-clear path passes its own path, while post-send and foreign-reset clears stay unscoped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sessions): reject a <script>-root DOM query so the app bundle can't leak (Codex P2) The descendant strip (querySelectorAll('script')) skips the clone root, so a search_dom / read_dom query whose selector targets `script` serialized the whole compiled bundle. Reject a script root with an explanatory result before cloning. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sessions): union queued inline-prompt DOM context + reject inspector-label root (Codex) - Multiple inline prompts queued during one stream accumulate their text, but queuedContext replaced (last wins) — dropping an earlier element's chip and misapplying its instruction. Union the DOM selector chips across queued prompts (non-DOM context still from the latest snapshot) (P1). - search_dom/read_dom: reject an .inspector-label clone root, like the <script> root — the descendant strip skips the root, so a `.inspector-label` query would return inspector chrome (P2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sessions): restore consumed DOM chips when a turn is cancelled unsent (Codex P1) DOM selector chips are consumed from the live selection before the request goes out. If the user hits Stop before any usable output, restoreUnsentTurn put the text/pastes/images back but not the chips, so resending the restored prompt lost its element scope. Re-add the consumed chips on rollback (skipped on a queued- message handoff, which carries its own context). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sessions): edit box edits the message's own context, sends what it shows (Codex P1) Editing a past message bound the edit box to the LIVE selection while the resend carried the message's original contextElements — so the box showed one set of chips (or none) but sent another, and add/remove in the box did nothing. The edit box now edits a copy seeded from the message's own contextElements, and restartGeneration sends that edited copy; a bare retry still falls back to the original. Drops the now-unused selectedContext prop from AIChatMessage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sessions): keep DOM context coherent on dequeue/cancel + per-chip edit identity (Codex) - dequeueMessage dropped the queued draft's pinned DOM context, so returning it to the composer sent the live selection instead. Restore the draft's context on dequeue (P1). - Cancel-rollback re-added this turn's chips additively, mixing in chips selected mid-stream. Replace instead so the restored draft stays coherent — shared helper #restoreDomContext used by both paths (P1). - The context chip row keyed/removed by (type, title); repeated DOM elements share a title (two button.btn), so editing a multi-select message gave duplicate keys and deleting one chip removed both. Identify DOM chips by (appPath, selector) (P1). The queued-prompt instruction↔element association (Codex #1) is the accepted union tradeoff and is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sessions): scope read_dom pagination + don't consume live chips on edit/retry Codex P1s: - read_dom's continuation note only renamed read_file, so following it with start_line alone dropped app_path/selector and re-read the active app's whole body instead of continuing inside the element. - contextOverride also carries an edit/retry's replayed copy of an older message's context. Consuming it from the live ContextManager stripped an identical chip the user had since selected in the composer, leaving their draft unscoped. Overrides now declare their origin ('pinned' vs 'replay'). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sessions): keep DOM context paired with the draft it belongs to Restoring a draft's pinned DOM chips replaced the live selection unconditionally, but both restore paths can leave a different draft in the composer: - Cancel: an occupied composer declines the rolled-back text (restoreInstructions bails), yet the cancelled turn's chips still replaced the live ones — the draft the user typed during the stream kept its text but got retargeted. - Dequeue: queued text is prepended onto an existing draft, so both instructions share one composer; replacing the chips dropped the standing draft's element. restoreInstructions/prependText now report whether the composer took the text. Cancel restores context only when it did; dequeue keeps both drafts' chips when it merged into one. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sessions): scope an inline prompt's chip filter to its own app The filter narrowing a multi-select down to the anchored element matched on selector alone. Selectors are generated per app and collide across them, so an inline prompt in app B also carried app A's identically-named chip, leaving the model two indistinguishable referents. Since draft restoration can now legitimately hold chips from several apps, match (selector, appPath) — as the sibling deselect/clear handlers in this file already do. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4f1d636005 |
fall back to createFlow when the update target does not exist (#10206)
FlowBuilder's deploy branches solely on the `newFlow` prop. When an embedder
mounts the builder with `newFlow` false for a flow that was never deployed,
Deploy issues `PUT /flows/update/{initialPath}` and the backend answers
`Flow not found at name <path>`, so the first deploy can never land.
Confirm against `GET /flows/exists/{path}` before taking the update branch,
and create instead when nothing is deployed there. The create path and the
full-page editor (where `newFlow` is derived from `no_deployed`) are
unchanged.
Fixes WIN-2206
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
71f2d47cb4 |
feat: cap queued jobs per concurrency key on cloud (#10197)
* feat: cap queued jobs per concurrency key on cloud * fix: close preprocessed-flow bypass and bound concurrency cap scan * fix: only cap concurrency keys with an active concurrent_limit * chore: only load concurrency key cap setting when cloud hosted * fix: reject queued-job import on cloud |
||
|
|
1abfe49f7e |
stop the app editor crashing on a grid-less draft (#10203)
* fix(apps): default a missing grid so the app preview does not crash * fix(apps): migrate the adopted draft so the editor matches the viewer * docs: tighten grid-normalization comments to durable constraints * test: drop the migrateApp grid-default test |
||
|
|
83a354f831 |
feat: expose windmill api endpoint catalog to global ai chat (#10199)
* feat: expose windmill api endpoint catalog to global ai chat * fix: guard variable reads and deletes in ai chat api catalog * fix: clarify api catalog prompt example for run result access * fix: block deleteScriptByHash and document resource read boundary |
||
|
|
b5e69ffba6 |
fix(security): enforce token scope filtering on folder list endpoints (#10193)
* fix(security): enforce token scope filtering on folder list endpoints * fix: apply folder scope filtering before pagination * fix: keep SQL pagination for unrestricted tokens on folder lists * fix: bound scoped folder-list scan with chunked pagination * fix: use keyset pagination for scoped folder-list scan * refactor: reduce folder scope fix to the minimal predicate filter * chore: drop folder list scope test |
||
|
|
70359e3a76 |
fix(schedules): apply scope-path filtering to schedule list endpoints (#10192)
`list_schedule` and `list_schedule_with_jobs` returned every schedule in the workspace regardless of the token's declared scope. A token limited to `schedules:read:<prefix>/*` could enumerate all schedule paths (and their script paths, cron expressions and recent job outcomes) through these two endpoints, while `get_schedule` correctly rejected them. Apply `build_scope_path_predicate(&authed, "schedules", "read")` to the returned rows, mirroring scripts, flows, apps, resources and variables. In `list_schedule` the filter runs after the draft-only append so both deployed and draft rows are covered. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f32d7702bc |
fix(inputs): add ownership check to saved-input args read endpoint (#10194)
`get_args_from_history_or_saved_input` looked up saved-input rows by `id + workspace_id` only, without checking ownership or `is_public`. The `input` table has no RLS policy, so any authenticated workspace member could read another member's private saved-input args (which may hold credentials) just by knowing the UUID, even though `inputs/list` already scopes to `is_public OR created_by = caller`. Add `AND (is_public IS true OR created_by = $4)` to both `input`-table queries in the handler (the `?input=true` single-query branch and the default `v2_job UNION ALL input` branch), matching the predicate already used by `list_saved_inputs`. The `v2_job` branches are already covered by the table's RLS policies enforced via `SET LOCAL ROLE windmill_user` in `UserDB::begin()`, so they are left unchanged. Fixes WIN-2202 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
60a7a23a04 |
chore(main): release 1.763.0 (#10186)
* chore(main): release 1.763.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>v1.763.0 |
||
|
|
89948bb441 | chore(git-sync): bump hub scripts to windmill-cli@1.762.3 (#10189) | ||
|
|
2d77e74207 |
fix(cli): stop emitting has_on_behalf_of/has_permissioned_as: false on pull (#10188)
With syncBehavior v1 the pull strips the user-specific on_behalf_of_email / permissioned_as from metadata and keeps a boolean marker so a later push can preserve remote ownership. The marker was written unconditionally as `!!<field>`, so every ownerless script, flow, schedule and trigger (the vast majority) got a `has_on_behalf_of: false` / `has_permissioned_as: false` line, producing a spurious diff on every pull. Absence of the marker already means "no owner" everywhere it's read on push, so only emit the key when true. Fixes WIN-2201 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4bddefc518 |
wire topBar.path and editablePath customUi in FlowBuilder (#10187)
Mirror the ScriptBuilder whitelabel pattern in FlowBuilder so the `customUi.topBar.path` and `customUi.topBar.editablePath` options actually take effect: - add `editablePath?: boolean` to `FlowBuilderWhitelabelCustomUi.topBar` - gate the EditorHeader block on `customUi?.topBar?.path != false` and pass `pathEditable`/`summaryEditable` (the latter wires the pre-existing but unused `editableSummary` flag) - hide the Path field in FlowSettings when `topBar.editablePath` is false Defaults are unchanged (`!= false`), so non-whitelabel flows still show the path header and settings field. Fixes WIN-2200 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
42da20ae97 |
feat(ai): gate data pipelines in sessions behind a dev flag (#10178)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d5b0a1b5c7 |
chore(main): release 1.762.2 (#10184)
* chore(main): release 1.762.2 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>v1.762.2 |
||
|
|
f6e36f862e |
chore(schedules): lower reconciler re-arm back-off cap to 8 passes (#10182)
Follow-up to #10179. The exponential back-off between reconciler re-arm retries of a persistently-failing schedule capped at 32 passes (~2.7h at the default 5-min reconcile cadence). Lower the cap to 8 (~40min) so a schedule fixed out of band (a lapsed license renewed, a bad cron corrected directly in the DB) auto-recovers within a few passes, while still cutting the retry rate sharply versus retrying every pass. Fixes via the UI/API re-arm immediately and are unaffected. Fixes WIN-2198 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cba5f0d8a8 |
fix(scripts): populate auto_kind from draft JSON for draft-only scripts (#10183)
The scripts list endpoint (include_draft_only=true) only populated auto_kind for the pipeline case, leaving library draft-only scripts (no `main` function) with auto_kind: null even though the frontend saves auto_kind: "lib" into the draft JSON. This made it impossible to distinguish library draft-only scripts from regular ones without a separate per-script API call. Fall back to the auto_kind saved in the draft value after the content-derived pipeline check, which keeps priority since it mirrors the deploy-time computation. Fixes WIN-2199 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
252ffd0401 |
chore(main): release 1.762.1 (#10181)
* chore(main): release 1.762.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>v1.762.1 |
||
|
|
c82056cfde |
fix(schedules): stop disabling schedules on transient push errors (#10179)
* fix(schedules): stop disabling schedules on transient push errors A scheduled flow whose next-occurrence push failed after retry exhaustion used to be disabled, killing a healthy schedule over a transient DB blip (pool contention, statement timeout). Now that the unarmed-schedule reconciler exists (#10174), transient failures no longer disable: the current occurrence runs to completion and the reconciler re-arms the next occurrence once this run leaves the queue. In the flow schedule-push path after retry exhaustion we now branch on the error: QuotaExceeded/NotFound still disable (the schedule's own fault, and rearm_schedule would otherwise leave them enabled-yet-unarmed forever), while transient errors are only reported and the flow continues. The previous iteration returned a SchedulePushZombieError to force a zombie restart; that is removed, because zombie detection cancels (does not restart) same-worker flows, so it would have lost the current run of a same-worker scheduled flow. The now-obsolete SchedulePushZombieError type and its catch in worker.rs are deleted. Fixes WIN-2198 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(schedules): back off and surface repeated reconciler re-arm failures The unarmed-schedule reconciler retried a schedule that could not be re-armed on every pass, forever, logging only to the server. With the flow schedule-push path no longer disabling on non-transient errors, a persistently-broken push (bad stored cron/timezone/args, lapsed license key) now stays enabled and would spin in that loop silently. The reconciler now tracks consecutive re-arm failures per schedule: exponential back-off (2, 4, 8, … passes, capped) between retries so a broken schedule is not hammered, and after 3 consecutive failures it surfaces the cause once (records schedule.error + raises a critical alert) without disabling. Both reset the moment the schedule re-arms, which also clears the recorded error. Verified end-to-end on a running server: a flow schedule with a corrupted cron stays enabled, retries back off, the error is surfaced after the third failure, and it re-arms and clears the error once the cron is fixed. Fixes WIN-2198 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
203f6c69dd |
fix(worker): gate ansible socket-dir name check to unix (#10180)
is_persistent_control_path_dir_name is only referenced from prepare_socket_root, which is #[cfg(unix)]. On Windows nothing used it, so -D dead-code failed the build. Gate the function and its unit test to unix, matching the reaping code they support. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3ffce7e2cc |
chore(main): release 1.762.0 (#10175)
* chore(main): release 1.762.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>v1.762.0 |
||
|
|
0b6bd376db |
ci: drop stale path_field_renames from mcp runner test constructor
#10176 removed `path_field_renames` from `EndpointTool`, while #10162 added a new `ep()` test constructor in runner.rs that still set it. Each passed CI against its own base, but together on main the test build fails with E0560. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8828341a2b |
fix(frontend): scope session pipeline trigger editors to the session workspace (#10032)
* fix(frontend): scope session pipeline trigger editors to the session workspace Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): hoist triggerWorkspace decl above GCP init-time getBaseUrl call Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): scope nested trigger pickers to the session workspace The triggerWorkspace resolver scoped direct trigger CRUD calls to the session's (forked) workspace, but nested pickers still defaulted to the nav `$workspaceStore`: in a fork session, resource lists/creation, variable creation, and path-existence checks ran against the parent workspace while save/delete targeted the fork — misleading options and false path-validation failures. Thread `wsId` into the nested controls of the 9 pipeline-canvas kinds: - `<Path workspaceOverride={wsId}>` (8 editors) — path + folder checks - `<ResourcePicker workspace={wsId}>` (6 config sections; add the resolver to MqttEditorConfigSection, which lacked `wsId`) - SQS `<VariableEditor workspace={wsId}>` — variable creation Also drop the per-site `wsId` rationale comment repeated across ~20 files; the invariant is documented once in triggerWorkspace.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): scope trigger runnable picker to the session workspace The runnable picker (`ScriptPicker`, reached via `TriggerRunnablePicker` and directly in the schedule editor) listed scripts/flows/apps from the nav `$workspaceStore` with no override, so a forked session offered the parent workspace's runnables when attaching a script/flow to a trigger. Add an optional `workspace` prop to `ScriptPicker` (defaults to `$workspaceStore` → no change for existing callers), pass it through `TriggerRunnablePicker`, and wire `wsId` from the 7 trigger editors that use it plus the schedule editor's 3 direct pickers. Completes the nested-picker workspace scoping; the excluded kinds (azure/http/websocket/native) keep their own ScriptPickers unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): scope trigger error-handler, folder default, and runnable actions Follow-up to the trigger-workspace scoping: three subtrees still read the nav `$workspaceStore` in a forked session. - ErrorOrRecoveryHandler (via TriggerRetriesAndErrorHandler in the 7 in-scope editors): add a `workspace` prop (defaults to `$workspaceStore`) and route handler lookup/schema, Slack/Teams settings, test jobs, and run links through it, so the error handler is resolved/tested/saved in the session workspace instead of A while the trigger lives in B. - useFolderDefaultPermissionedAs: accept an optional workspace getter so a `f/...` trigger's default permissioned-as is read from the session workspace, not the nav one (PermissionedAsLine passes `() => wsId`). - ScriptPicker actions: scope the View drawer (`getScriptByPath`) and `FlowPathViewer` to `effectiveWorkspace`, and carry `?workspace=` onto the Edit/View routes when an explicit override is set (the layout consumes the param, same mechanism as editInFork). The param is only appended when a workspace override is passed, so existing callers' links are unchanged. Also consolidate the repeated workspace-scoping comment in PipelineTriggerEditors (the invariant lives in triggerWorkspace.ts). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): scope error-handler/schedule nested consumers to the session workspace Address CI Codex review on #10032 — the error-handler and schedule subtrees still had nested consumers reading the nav workspace: - ErrorOrRecoveryHandler: pass the resolved workspace to its own nested `ScriptPicker` (custom-handler list + View/Edit) and add a `workspace` prop to `ChannelSelector` (Teams channel listing); carry the acting workspace onto the "create from template" link. - ScheduleEditorInner: pass `workspace={wsId}` to the error/recovery/ success `ErrorOrRecoveryHandler` panels, `workspaceId={wsId}` to `WorkerTagPicker`, and the workspace query param onto the dynamic-skip template link. Template-link and picker overrides only diverge from `$workspaceStore` when a session override is set, so non-session callers are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7a139ab23e |
feat(ai-chat): image attachments and agent raw-app screenshots (#10130)
* feat(ai-chat): add image attachments and agent raw-app screenshots Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(ai-chat): generalise take_screenshot fidelity caveat Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): keep compaction boundary on a displayed user message Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(raw-apps): count line boxes by vertical overlap, not rect count Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): enforce vision gating and bound image attachments Refuse images on known text-only models instead of warning and sending them anyway; cap input bytes before decode; keep clipboard text when it accompanies a bitmap; don't queue a message whose images can't ride the plain-text queue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(ai-chat): trim take_screenshot schema and shrink the card's copy Move the fidelity caveat from the tool def onto the tool result: the def is re-sent every global iteration (~258 tok), while the caveat only matters once a capture exists. Keep a downscaled copy in displayMessages when it is actually smaller — those are never compacted and are re-cloned on every saveChat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): carry attached images through the message queue Enter during a streaming turn queued the text and silently dropped the images, so the auto-send was not the message the user submitted. The queue now holds both, moved together via takeQueue/clearQueue/restoreQueue so none of the three flush sites, the dequeue-to-composer path, or the two conversation-switch drops can leak one without the other. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): gate screenshots on vision, narrow when the tool fires take_screenshot buffered an image unconditionally, so a text-only model got an image_url and rejected the turn; the attach-time check never covered it, nor a model switched after attaching. Gate before capture and again at send. Only reach for the tool when the user raises how the app looks, rather than after every UI edit. A collapsed preview keeps the iframe mounted at zero width, passing the ready checks and then failing inside the rasteriser as '[object Event]'. Name it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): hold sending while attachments decode addImages read the free-slot count before its await and appended after it, so a send during the ~50-800ms decode cleared images while the closure still wrote to them, landing the picture on the following message; two drops also claimed the same slots and could pass the cap. Reserve slots up front, block sending until they resolve, and show a placeholder so the held send is explained. Keep only a bounded copy in the transcript: displayMessages are never compacted and are re-cloned on every save. Measured 6.1x smaller per attachment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): route screenshots to the visible tab, resend full-res on retry Every mounted raw-app editor claimed the runtime's single screenshot slot, so take_screenshot could capture a background tab's app; ownership now follows the visible tab and only the owner releases it. restartGeneration resent displayMessages' images, which became a 384px thumbnail when the transcript copy was bounded — retries downgraded the model's own input. Recover the sent parts from the API message instead. Move modelSupportsVision to modelConfig: it was untestable behind lib.ts's monaco import chain, and the denylist missed bundled text-only defaults (Groq/Together Llama 3.3, Foundry Phi-4 and Mistral-Large). Llama 3.2 and Phi-4 split by variant, so both are matched narrowly. Pinned against the shipped defaultModels. Decode attachments one at a time and derive the preview from the bounded copy: a 12MP bitmap is ~48MB and the batch was held live at once, decoded twice each. The attach tooltip claimed nothing is uploaded, which is untrue for images. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): keep images out of text-only turns and bound the queue The vision gate only dropped the current turn's images, so history's image parts still went out after a switch to a text-only model and failed the request; strip the outbound copy instead, leaving history intact for a switch back. queueMessage had no cap, and each queued send clears the composer for another eight, so repeated sends stacked an unbounded batch into one message. Editing a message resent displayMessages' bounded copy, downgrading the model's own input; retries recovered the full-size one but then re-persisted it at full resolution. storedImages pairs the API message with its transcript entry so both paths resend the original and re-persist the bounded copy. Reserve image slots before awaiting text attachments: the gap left sending enabled with an image pending, measured ~90ms for a 40-file drop, now ~8ms regardless of batch size. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): treat deepseek-v4 as text-only deepseek-v4-pro ships as a bundled default and the gate let images through to it, so an attachment would fail the turn. DeepSeek's vision line is deepseek-vl. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): drop a rejected image instead of wedging the conversation A provider that refuses an image leaves it in history, so every later turn resends it and fails identically: the chat is stuck until the user edits the message or starts over, and Retry re-sends the same image. The vision gate only knows the models we ship, so this is the net for the rest. Strip the parts on an image-related rejection and say so; unrelated failures keep the image. Verified at the wire that no provider rejects a base64 data URL: anthropic (source.base64), openai/gpt-4o (input_image), googleai and aws_bedrock/claude (image_url passthrough) all 200 and read the image. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): match text-only models exactly, from provider API docs The gate guessed by substring over model names, which answers the wrong question. What matters is whether a provider's API accepts image parts, not whether the model can see: DeepSeek V4 ships vision in its chat product that its API has no content type for, and o3-mini gained vision in ChatGPT the API never exposed. Neither is inferable from a name. Substrings also block working models. 'mistral-large' matches Mistral Large 3, which takes images; 'phi-4' matches Phi-4-multimodal, which does too. A wrong entry blocks with no override, while a missing one costs a turn and recovers via the rejection path, so the list is now exact ids only, each backed by a provider doc. Verdicts verified against provider API docs rather than recall. Live-checked where a doc was contradicted: Bedrock's compatibility matrix claims no Anthropic model is served over chat completions, but it serves images fine. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): stop retry resurrecting a rejected image The rejection fallback strips the image from history but leaves the bubble's thumbnail so the user can still see what they sent. storedImages fell back to that thumbnail when the API message had no parts, so Retry re-attached the very image the provider had just refused and failed identically — the conversation stayed wedged through the one control offered to escape it. Found by retrying in the UI; unit tests, wire tests and four review passes all missed it, since it only exists between two separate fixes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ai-chat): harden image rejection recovery and drop-path attachment * fix(ai-chat): fix image drop race, mid-turn vision gate, retry aliasing * fix(ai-chat): key vision denylist by provider, flatten alpha before jpeg * feat(ai-chat): offer take_screenshot on chromium only, ask for one elsewhere * feat(ai-chat): image-only sends and click-to-expand image previews * fix(ai-chat): capture screenshots at 2x and expand tool images full-res * feat(frontend): expandable image previews in composer and result views * fix(ai-chat): image-only send edge cases from review round * fix(ai-chat): keep image-only drafts on rollback, track failing model id * fix(ai-chat): gate rejection recovery on the failing iteration's model * refactor(ai-chat): record iteration model via onBeforeIteration, trim tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): restore composer draft when beforeSend preflight fails Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): bound cumulative outbound image bytes per request Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): make the image byte bound part-granular so over-cap turns keep a subset Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): evict newest-first within a message in the image byte bound Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): prune over-cap images from stored history, not just requests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): bound history at every save boundary, keep thumbnail pairing across eviction Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): slot-align storedImages so the bubble expands the right image after eviction Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): match rejection keywords as whole words so provisioning errors keep images Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): match input_image rejections, restore images refused by non-GLOBAL modes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): defer non-GLOBAL image refusal restore past the composer clear Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): persist full tool screenshots for post-reload expansion Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(ai-chat): persist chat images out-of-band via blob-store refs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): scope image blobs per chat and stop cap-eviction rotation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): keep blob-cap chronology across drop-oldest compaction Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(ai-chat): derive blob eviction from the saved record, not write times Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): serialize chat history DB writes per manager Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): pin queued history writes to the enqueue-time user database Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): delete stale image blobs only after the chat record commits Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): don't double-restore a queued image-only draft on vision refusal Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): label image-only chats and evicted image-only bubbles Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): keep the in-memory chat mirror hydrated for DB-less sessions Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): converge the chat mirror to refs after a successful DB commit Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): guard mirror convergence against rewinding newer saves Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): invalidate pending convergences on identity re-init, keep retry image names Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-chat): bound the screenshot raster before rasterization Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(ai-chat): drop the no-IndexedDB in-memory image fallback Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ae3d9ce2c0 |
fix(mcp): apply token scopes consistently across mcp endpoint tools (#10162)
* fix(mcp): apply token scopes consistently across mcp endpoint tools Endpoint-tool authorization is now shared between single- and multi-workspace modes, honors the token's script/flow path patterns for every path-taking tool, and the JWT minted for proxied endpoint calls carries scopes derived from the caller's own. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(mcp): review nits: restore create_http_request doc, reword comments Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d0aa7dca13 |
fix: parse all names in grouped go param declarations (#10165)
* fix: parse all names in grouped go param declarations Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: bump windmill-parser-wasm-go to 1.761.0 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a9fc9f74b2 |
fix(ai): unbreak session chat compaction for Anthropic models (#10171)
* fix(ai): cap chat compaction summary output so Anthropic non-streaming calls succeed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: pin the compaction summarizer's maxTokensCap Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai): cap testKey completion so Anthropic key tests pass the SDK pre-flight Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7d2c5ceb0f |
fix(flows): make updateFlow body path optional so AI can update flows (#10176)
* fix(mcp): default a body field to its same-named path param so updateFlow works Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: trim mcp path-param fallback helper comment Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(mcp): keep path params un-mangled so update tools take plain `path` Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(flows): default update_flow body path from URL via EditFlow Harmonizes updateFlow with the EditVariable/EditResource/EditApp convention: the flow to update is identified by the URL, so the body path is optional and only needed to rename. Fixes the 422 at the API layer for every client (MCP, the in-app AI chat, raw HTTP), not just the MCP tool schema. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(mcp): drop redundant body-path fallback now that the server defaults it Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: fix stale generator comment after removing mcp body-path fallback Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(flows): mark updateFlow body path optional in the openapi contract Adds an `EditFlow` schema (path optional) for the update route so the public contract matches the server; createFlow keeps `OpenFlowWPath` (path required). Also trims two test comments to record constraints rather than history. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
2ff5a918d5 |
feat(worker-tags): add * fork marker to workspace-scoped custom tags (#10177)
* feat(worker-tags): add `*` fork marker to workspace-scoped custom tags Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(worker-tags): pin fork-marker tag admission through real lineage Also render WorkspaceMatcher in its authored form in the operator-facing "not in the allowed CUSTOM_TAGS" error, and correct the authorization note on workspace_with_fork_ancestors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(worker-tags): gate fork-lineage tag lookup on workspace membership exists_workers_with_tags is a global route whose workspace is a query param. Resolving its fork lineage for a caller who is not a member disclosed whether an arbitrary workspace descends from one named by a tag(parent*) rule. Require membership before the lineage lookup, and sharpen the workspace_with_fork_ancestors contract accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(sqlx): add offline cache for fork-marker test INSERT query The new test_fork_marker_tag_admission_through_lineage integration test inserts a fork workspace with a bind-parameterized query that had no cached entry, failing the SQLX_OFFLINE=true CI build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9762089fcb |
fix(schedules): re-arm enabled schedules left with no queued occurrence (#10174)
* fix(schedules): re-arm enabled schedules left with no queued occurrence * fix(schedules): lock schedule row while re-arming and report outcome * fix(schedules): make reconcile lock cancellation-safe, re-check armed under lock Address review feedback on the schedule reconciler: - Use a transaction-scoped advisory lock (pg_try_advisory_xact_lock) instead of a session-scoped one. monitor_db runs under a 600s timeout; on cancellation a session lock on a pooled connection would be stranded, wedging reconciliation on every replica. An xact lock releases when its transaction is dropped. - rearm_schedule re-checks for a queued occurrence under the row lock and returns NoOp if already armed, closing the scan→lock window that could double-push across a cron boundary. Add a regression test. - Make reconcile_unarmed_schedules private (its only caller is in monitor.rs) and document its system-only contract. - Log the disable only after the guarded UPDATE actually disables the schedule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(schedules): never disable from reconciliation and cap re-arms per pass --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
1edee8aa34 |
perf: lazy-load session preview editor views for snappy AI sessions switch (#10172)
* perf: lazy-load session preview editor views to speed up AI sessions switch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: stop editor prefetch chain on session page destroy and swallow chunk-load failures Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
396fb1c475 |
feat(otel-tracing-proxy): trust internal endpoints with untrusted CAs (#10139)
* [ee] feat(otel-tracing-proxy): trust internal endpoints with untrusted CAs Add `insecure_upstream_hosts` and `upstream_ca_certs` to the HTTP Request Tracing settings so the OTEL tracing proxy can reach internal endpoints with untrusted or private-CA certificates while keeping them traced. Wires the two settings through the worker config and live reload, adds the inputs to the instance settings UI, and pulls in the rustls upstream-client deps (hyper-rustls/tokio-rustls/rustls/ rustls-native-certs/rustls-pemfile; hyper-http-proxy switched to its rustls feature). The proxy-side implementation lives in the companion EE PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: bump ee-repo-ref to otel_ca companion commit Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(otel-tracing-proxy): expose new fields in declarative config; bump ee-ref Addresses code-review findings: - Add `insecure_upstream_hosts` and `upstream_ca_certs` to the declarative `OtelTracingProxySettings` in instance_config.rs so operator/GitOps-managed installs can set them and reconciliation no longer drops values saved via the UI. - Restore the trailing newline on ee-repo-ref.txt and bump it to the companion EE commit carrying the strict host-matching / port-ordering fixes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 51e50629f48dbc4f5520a787b4bdfb76f4cd38d3 This commit updates the EE repository reference after PR #665 was merged in windmill-ee-private. Previous ee-repo-ref: 49f458e4446395e98915c220baa757ab3b2ed2d8 New ee-repo-ref: 51e50629f48dbc4f5520a787b4bdfb76f4cd38d3 Automated by sync-ee-ref workflow. --------- 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> |
||
|
|
dcb9e40ea2 |
fix(extra): make the extra container runnable as a non-root UID (#10173)
* fix(extra): make extra container runnable as a non-root UID
The root-run installs in DockerfileExtra inherit UV_CACHE_DIR
(/tmp/windmill/cache/uv) and XDG_CACHE_HOME (/pyls/.cache) from the base
image and write to them *after* the base image's world-writable pass,
leaving ~320 root-owned 0755 dirs. A non-root UID then fails to debug:
uv venv failed: Failed to write to the client cache
Caused by: Permission denied (os error 13)
at path "/tmp/windmill/cache/uv/simple-v21/pypi/.tmpWxWzTi"
Re-apply the base image's world-writable convention after the installs,
and add the windmill UID/GID 1000 account the app image already has so
runAsUser: 1000 resolves to a real user with a writable $HOME. The
entrypoint also wrote $NETRC to the 0700 /root under `set -e`, killing
the container at startup; redirect $HOME when it is not writable.
Verified against the published image as root, uid 1000, and uid 4567:0:
all four services start and `windmill prepare-deps` succeeds in each.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(extra): scope the HOME fallback per-UID and cover non-root in CI
Review findings on the previous commit:
- `${HOME:-/root}` was used in the writability test but not the assignment,
so an unset HOME (writable /root, redirect skipped) silently wrote the
netrc to `/.netrc` instead of `/root/.netrc`. Normalize HOME once.
- `mkdir -p` succeeds on a dir owned by another UID, so a root-created
/tmp/windmill-home on a shared /tmp reintroduced the startup death this
fix exists to prevent. Scope the fallback to /tmp/windmill-home-$(id -u).
- The extra image's smoke test only ran as root, so nothing exercised the
non-root path this PR is about. Add a --user 1000 run of the same suite
plus a prepare-deps assertion, which is where the EACCES surfaced.
Also correct the DockerfileExtra comment: the proven requirement is the uv
cache, not runtime writes by gopls.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(extra): keep /tmp/monaco code read-only for non-root UIDs
Review nit: /tmp/monaco holds node_modules (code, not cache), so it is not
covered by the base image's world-writable cache convention. Give it a+rX
files with 777 dirs -- enough for the go.mod / ruff.toml the entrypoint and
pyls_launcher write into it -- instead of a+rw on every file.
/tmp/windmill and /pyls/.cache keep a+rw: that is the base convention for
caches (DockerfileSlimEe:69 chmod -R a+rw /tmp/windmill/cache; Dockerfile:339
"cache files already have 666"), and uv rewrites cache entries in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(extra): drop the non-root CI smoke test
Reverts publish_extra.yml to its root-only smoke test; the DockerfileExtra
and entrypoint fix is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
97f4477069 |
feat(forks): let a fork's creator manage developers on it without being an admin (#10166)
* feat(forks): let a fork's creator manage developers on it without being an admin Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(forks): bind the fork-creator grant to the member's parent username and lock the delete Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
be57dd91e4 |
fix(ansible): keep persistent-connection socket path under the AF_UNIX limit (#10167)
* fix(ansible): keep persistent-connection socket path under the AF_UNIX limit Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ansible): root socket dir at WINDMILL_DIR and verify we own it Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ansible): fail closed on an untrusted socket root and honor commented section headers Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ansible): validate the socket root after creating it, not before Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ansible): root socket dir at sticky /tmp so the image's 0777 windmill dir cannot disable it Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ansible): only sweep socket dirs the worker could have created Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ansible): drop the configurable socket root, fixing its ancestor and sweep hazards Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ansible): require the socket root be usable, not just safe Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ansible): do not override a control_path_dir the job set itself Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ansible): drop the dead parent-creation step for the fixed /tmp root Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6131f7d2ae |
chore(main): release 1.761.0 (#10148)
* chore(main): release 1.761.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>v1.761.0 |
||
|
|
91d6606868 |
fix(mcp): push granular scope patterns into SQL so scoped scripts/flows aren't truncated (#10140)
* fix(mcp): push granular scope patterns into SQL so scoped scripts/flows aren't truncated MCP `list_tools` fetched scripts/flows capped at the 100 newest by `created_at` and only *then* filtered by the token's granular folder/custom scope in Rust. In a workspace with more than 100 scripts/flows, in-scope items outside that newest-100 window were truncated before the scope filter ran, so a folder- or custom-scoped token could see zero tools even though matching items existed. Push the scope patterns into the query via a new `PathFilter::Patterns` (mirroring `is_resource_allowed`: `*` disables filtering, exact paths match by equality, `x/*` matches the folder or its subtree, empty grants nothing) so the filter applies before the `ITEMS_FETCH_MAX_LIMIT` cap. The existing hashed-name resolution path keeps its prefix behavior via `PathFilter::Prefix`, and the Rust post-filter stays as defense in depth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(mcp): warn in scope selector when a scope exceeds the MCP tool cap The server exposes at most ITEMS_FETCH_MAX_LIMIT (100) scripts and 100 flows per token; a scope matching more silently drops the overflow, which bloats the assistant's context with a partial, arbitrary tool set. McpScopeSelector now computes how many scripts/flows the current scope would expose (per type, mirroring the backend's is_resource_allowed) and shows a warning Alert when either exceeds the cap, so the user can narrow the scope before generating the URL/token. An async sequence guard keeps rapid scope changes from applying stale counts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(mcp): address review — dedup count fetches, boundary-aware folder counting, fix copy Follow-up to the MCP scope-selector truncation warning: - Reuse a single per-type (scripts/flows) cache for both the preview list and the exposed count, instead of a second concurrent fetch of the same rows. - Count a folder scope against the `f/{folder}/*` subtree (via the same boundary-aware matcher), so a folder like `team` no longer over-counts a sibling like `team2` and falsely warns. - Custom-mode counts are derived synchronously from the already-loaded scripts/flows — no fetch. - Reword the warning to "most recent" (flows are ordered by edited_at, not created_at). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(mcp): anchor folder count fetch at the folder boundary Follow-up to review: the folder count fetched the unbounded prefix `f/{folder}` (backend `path LIKE 'f/{folder}%'`), so a prefix-sharing sibling like `f/team2` shared the page. With a page limit, enough newer sibling rows could fill the first page ahead of the target folder's older rows; the client-side boundary filter then dropped them all, wrongly suppressing the warning and emptying the preview. Fetch `f/{folder}/` instead so the backend prefix (`LIKE 'f/{folder}/%'`) is anchored at the folder boundary and never returns siblings. The client-side matcher stays as a backstop for folder names whose LIKE wildcards (`_`, `%`) can still let the backend prefix over-match. 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: Ruben Fiszel <ruben@windmill.dev> |
||
|
|
c55ac5326f |
fix(raw-apps): full code ui builder improvements
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4fc3f304c6 |
feat(forks): add "Hide unchanged drafts" toggle to fork deploy-draft tab (#10022)
A fork clones the parent workspace's drafts on creation, so the fork's Deploy-draft tab listed all of them as deployable even though the fork never touched them. Add a "Hide unchanged drafts" toggle (fork-only, on by default) that hides drafts identical to the parent's. Backend: the drafts-list endpoint accepts an optional `compare_to_workspace` (honored only when it is the workspace's actual parent) and flags each row with `unchanged_from_parent` via a jsonb-equality subquery against the parent's draft at the same (path, kind, owner). Frontend: the fork compare page passes its parent as the compare workspace; CompareDrafts renders the toggle and filters out unchanged rows, which also drops them from the selection/deploy count. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4e63ca2cd4 |
gate PR ready on clean agent-driven review rounds (#10157)
* feat(ci): gate PR ready on clean review rounds driven from draft Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ci): robust review-round wait loop, require codex evidence for marker skip Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ci): require pre-marker codex evidence, fail open on marker fetch errors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0e547adf23 |
fix(migrations): grant zombie_job_counter to windmill roles (#10159)
The zombie_job_counter table (20250205131522) was never granted explicitly to windmill_user / windmill_admin. The generic GRANT ALL ON ALL TABLES in 20250205131523 swallows failures via EXCEPTION WHEN OTHERS, and the ALTER DEFAULT PRIVILEGES it sets only covers objects created by that same role, so external-database deployments whose migration runner differs from the init-script runner leave the table ungranted. This stayed invisible while the table was only reached through ON DELETE CASCADE, which bypasses caller permissions. 20260625092813 replaced those cascades with explicit DELETEs in delete_jobs(), which run as the invoking role and fail with "permission denied for table zombie_job_counter". Same fix already applied to notify_event (20260619091631), script_trigger (20260619112847) and dispatch_event (20260701080313). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2f6c35b15b |
fix(self-host): unbreak self-hosted Caddy after the caddy-l4 syntax change (#10156)
* fix(self-host): accept pre-2.11 Caddyfiles in the caddy-l4 image The Caddyfile is a bind-mounted file the user owns, so `docker compose pull` updates the image but never their config. #10106 and #10113 changed the syntax the image requires (native caddy-l4 `route { proxy { upstream } }`, and a non-empty `bind`), which strands every existing self-host on their next pull: Error: adapting config using caddyfile: parsing caddyfile tokens for 'layer4': wrong argument count or unexpected line ending after 'proxy', at line 4 Normalize legacy Caddyfiles in the entrypoint instead. Only rewrite when the config cannot be used as-is, and on any failure exec caddy against the user's original file so it reports a real error against what they wrote. The bind rewrite is not cosmetic: an empty `bind {$ADDRESS}` adapts and validates cleanly on caddy >= 2.9 but drops the whole HTTP site, so a syntax-only shim would trade a restart loop for a container that boots clean and serves nothing on :80. The reference for correctness is the image published before #10106 (sha-989c9e6): whatever it adapts today is what self-hosters run, so the shim must reproduce it byte for byte. docker/test-caddy-compat.sh asserts that over five legacy variants, plus the :80 listener under an unset ADDRESS, every --config spelling, relative and glob imports, and the no-op on the current Caddyfile. Details worth knowing: - `to a b` becomes one `upstream` per address; `upstream a b` would be a single upstream with two dials, which is a different load-balancing topology. - The rewrite lands next to the original, because caddy resolves `import` relative to the importing file and a glob import would otherwise silently expand to nothing. - The image has no ENTRYPOINT and CMD ["caddy", ...], so an existing `command:` override starts with a `caddy` token the entrypoint absorbs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(self-host): route ws_mp and ws_debug to the extra gateway reverse_proxy only reads its first argument as a matcher, so reverse_proxy /ws/* /ws_mp/* /ws_debug/* http://windmill_extra:3000 adapts to a single /ws/* route whose upstreams are `ws_mp/*:80`, `ws_debug/*:80` and `windmill_extra:3000`. LSP therefore round-robins across two garbage hostnames and connects only one time in three, while /ws_mp/* and /ws_debug/* match no route at all and fall through to windmill_server:8000. Use a named matcher so all three paths reach the gateway. Verified with traffic against separate windmill_server and windmill_extra backends: before, /ws/lsp fails and /ws_mp/room reaches windmill_server; after, all three reach the gateway with the path preserved and /user/login still reaches windmill_server. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(self-host): pin the caddy-l4 image to an explicit version :latest and the bind-mounted ./Caddyfile it has to agree with are updated by different mechanisms, so they drift. Publish an explicit version alongside :latest and pin docker-compose.yml to it, so a checkout is self-consistent: compose, Caddyfile and image version now move together in one commit. CI fails the build when docker/caddy-l4.version and the docker-compose.yml pin disagree, and runs the compatibility-shim tests before publishing. The path filter now covers the entrypoint, the normalizer, the Caddyfile and docker-compose.yml, so a change to any guarded input actually triggers the workflow rather than leaving the check unrun. :latest keeps being published, since existing deployments reference it and that is how they pick up the compatibility shim. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(self-host): make the caddy-l4 version tag publishable before the pin merges docker-compose.yml pins an exact tag, but the version tag was gated on the default branch, so the tag only appeared after the pin had already merged. Between the merge and the build finishing, a fresh `docker compose up -d` off main fails with "manifest unknown", and a failed build leaves main permanently referencing an image that does not exist. Drop the gate so the tag can be published from the branch via workflow_dispatch before merging the pin. The version is immutable, so republishing it from main is a no-op, and only pushes to main and manual dispatch run this workflow, so a branch cannot claim the tag by accident. :latest stays gated on main. Also check the version file against the caddy version the Dockerfile pins. Without it, a caddy bump that forgets the version file publishes a tag naming the wrong caddy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(self-host): do not log Caddyfile contents from the compat shim The shim logged a unified diff of the rewrite, which carries three lines of context around each change. A Caddyfile is user-owned and can hold basic_auth hashes, proxy Authorization headers or TLS provider tokens, and container logs are routinely shipped off the host, so normalizing a customized config could copy secrets into them. Reproduced with a basic_auth bcrypt hash landing in the log as context around the bind rewrite. Log the number of rewritten lines and the path to the rewritten file instead. It sits next to the original, so an operator can diff it themselves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
51d8db6602 |
feat: automatic git-to-windmill sync (polling, webhooks, in-app PRs + checks) (#9552)
* docs: add design doc for automatic git-to-windmill pull sync
* docs: add migration plan and implementation phases to git-sync pull design
* feat(git-sync): add auto_pull settings schema and pull enqueue primitive
Adds AutoPullSettings/AutoPullMode/AutoPullStatus on GitRepositorySettings
(workspace_settings.git_sync JSONB), the GIT_SYNC_PULL_SCRIPT_PATH constant,
and should_pull/effective_poll_interval_s helpers with unit tests. Exports the
EE enqueue_git_pull_job primitive. Foundation for repo→Windmill auto-pull.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(git-sync): poll repos and auto-pull new commits into the workspace
Phase 1 of automatic repo → Windmill sync. A monitor task (EE-licensed,
single-replica via advisory lock) git ls-remotes each auto-pull-enabled
repository ~every minute and enqueues a pull when the tracked branch moves,
reusing the {workspace_id}:git_sync concurrency key so pulls serialize with
in-flight push commits.
- windmill-store: background (no-authed) resolver get_git_repo_head_for_autopull
that resolves the repo resource (incl. $var: refs) and ls-remotes; GitHub-App
repos are skipped here and will sync via webhooks (phase 2).
- monitor.rs: poll/reconcile/persist with optimistic sha advance and failure
status; targeted jsonb update so concurrent settings edits aren't clobbered.
- edit_git_sync_repository: preserve server-owned auto_pull state on UI save.
- openapi: AutoPullSettings/AutoPullMode/AutoPullStatus + auto_pull field.
- frontend: per-repo "Automatically deploy changes from Git" toggle with last
sync status; demote the GitHub Actions link to an advanced CI option.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(git-sync): wire webhook lifecycle + receiver; share reconcile logic
OSS side of phase 2 auto-pull webhooks:
- edit_git_sync_repository creates/removes the repo webhook on save (EE-gated,
best-effort → falls back to polling).
- monitor poller now delegates to the shared windmill_git_sync reconcile/persist
helpers (also used by the webhook receiver), removing duplicated logic.
- export the shared reconcile/persist/failure helpers; bump EE ref.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(git-sync): bump EE ref for phase 3 in-app PR creation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(git-sync): show webhook vs polling status on the auto-pull toggle
When a repo has an active webhook (auto_pull.webhook_id set), the status line
reads "instant via webhook"; otherwise it reads the ~1-minute polling cadence.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(git-sync): post PR diff check on dry-run completion (phase 4)
Worker completion hook in process_completed_job: when a DeploymentCallback job
carrying the __git_sync_pr_check marker finishes, parse the dry-run SyncResponse
and patch the GitHub check run with the diff summary (success/neutral/failure).
Export enqueue_git_pull_dry_run; bump EE ref.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(git-sync): bump EE ref (drop unused GHES webhook_secret)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* revert(git-sync): defer phase 4 PR diff checks (OSS side)
Remove the worker completion hook that posted the PR check run, drop the
enqueue_git_pull_dry_run re-export and the orphaned sqlx cache, bump EE ref.
Phases 1-3 (polling, webhooks, in-app PR creation) are unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Revert "revert(git-sync): defer phase 4 PR diff checks (OSS side)"
This reverts commit
|
||
|
|
7b813d1f74 |
fix(frontend): sanitize job result markup, gate it on unsandboxed public apps (#10127)
* fix(frontend): sanitize html and svg result rendering Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(frontend): add dompurify to lockfile root deps Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): keep sanitizing rich results on public app surfaces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(frontend): gate risky app markup on unsandboxed public surfaces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): derive app markup isolation from the real origin Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(frontend): use the design-system danger alert for the markup gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |