mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-07 16:03:21 +00:00
11bb37d7ca7fbd50cc66905f41851226a48e57fb
5924
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
11bb37d7ca |
fix(forks): show workspace settings link in sidebar for fork creators (#10216)
* fix(forks): show workspace settings link in sidebar for fork creators PR #10166 grants a fork's creator developer-management access to the workspace settings page, but every sidebar entry point to /workspace_settings stayed gated on is_admin || superadmin, so a non-admin fork creator could only reach the page by typing the URL. Widen the four sidebar guards (SettingsMenu, SidebarContent, WorkspaceMenu, WorkspaceScopeHeader) to also admit fork owners, via a shared isForkOwner() helper mirroring the fork-owner check already on the settings page and the backend authorize_fork_owner_add_user grant. Fixes WIN-2210 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(forks): check fork ownership against the settings entry's target workspace In session mode SettingsMenu's entry targets workspaceSettingsTarget, which can differ from the active workspace, but the fork-owner guard was checked against the active workspace — hiding the entry when the session targets the user's fork, or exposing a dead link when only the active workspace is theirs. Check ownership against the workspace the entry actually points at. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0593ff7d7d |
feat(ai): add npm package search tool to global chat (#10204)
* feat(ai): add npm package search tool to global chat Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai): encode npm search query before building registry URL Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(ai-evals): add global-mode npm package search case Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> |
||
|
|
c8870d36ae | fix(sessions): session bar badge readouts, job persistence, refresh bounce (#10217) | ||
|
|
2b58df57fc |
feat(ai): extract prompt cache token usage from OpenAI and Azure providers (#10214)
* feat(ai): extract prompt cache token usage from OpenAI and Azure providers Parse the nested cache token details OpenAI returns and thread them into TokenUsage, matching the Anthropic and Bedrock providers. - sse.rs: add OpenAIPromptTokensDetails / OpenAIInputTokensDetails and the optional prompt_tokens_details / input_tokens_details fields. - other.rs (Chat Completions) and openai.rs (Responses): populate cache_read via .with_cache(cached_tokens, None). OpenAI's prompt_tokens/input_tokens already include cached tokens (cached is a subset), so total/prompt are unchanged; cache_read is recorded separately for reporting. For the same reason the frontend token-usage conversions are left as-is (adding cached would double-count); optional cache fields and a clarifying comment are added to prevent a future incorrect Anthropic-style fix. Fixes WIN-2207 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(ai): pin OpenAI/Azure cache-token deserialization paths Add regression tests deserializing the real Chat Completions and Responses usage payloads, guarding the prompt_tokens_details.cached_tokens / input_tokens_details.cached_tokens paths against a silent rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ai): extract to_token_usage() and test the cache mapping Address review nit: move the usage->TokenUsage conversion into OpenAIChatUsage::to_token_usage / OpenAIResponsesUsage::to_token_usage so the providers call one method and the tests exercise the real mapping. Tests now assert cache_read is populated while input/total 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> |
||
|
|
0e04bc6991 |
fix: steer ai chat away from draft-blind api catalog reads and runs (#10202)
* fix: steer ai chat away from draft-blind api catalog reads and runs * fix: support api catalog tools in ai eval harness mock backend * fix: block remaining draft-blind read and list endpoints in api catalog * fix: scope eval fetch stub to handled benchmark api paths * feat: add deployed version read option to read_workspace_item * fix: include input schema in script workspace item reads * feat: add pagination to list_workspace_items * fix: paginate list_workspace_items per item type without cross-type loss * fix: window draft overlay in list_workspace_items by page and limit * refactor: simplify list draft overlay to capped page-1 merge * fix: label server-synthesized draft-only rows as drafts in listings * chore: fix stale eval comment and pin draft_only listing label |
||
|
|
f4308cf033 |
feat(ai): expose get_db_schema tool in global chat (#10207)
* feat(ai): expose get_db_schema tool in global chat * fix(ai): skip cross-workspace editor cache write in global get_db_schema * test(ai): add global eval case for get_db_schema resource lookup |
||
|
|
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 |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
fa03984a14 | fix(ai): show the question in askUserQuestion tool-call labels (#10153) | ||
|
|
568dbbee85 |
fix(frontend): show friendly draft path for draft-only items in pickers (#10136)
* fix(frontend): show friendly draft path for draft-only items in pickers * fix(frontend): dedupe current draft item and scope tab picker by friendly path * fix(frontend): key live draft picker entries by storage path * fix(frontend): fall back to the current leaf when the picker highlight key vanishes * fix(frontend): remount session tab picker when the friendly scope arrives * fix(frontend): stamp staged tab path for deployed items with undeployed renames * fix(frontend): expose staged flow/raw-app renames through the live draft registration |
||
|
|
7fda6a0534 |
feat(frontend): flatten workspace pickers, whole-tab picker trigger (#10145)
* fix(frontend): open session preview picker from whole tab, anchor to tab edge Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(frontend): flatten session preview picker to workspace home level Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(frontend): flatten chat context picker workspace tree Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): show root loading state in flat drill pickers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): make tab-strip keyboard activation work inside dnd zones Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): return focus to tab after active-tab picker closes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): move focus with selection on arrow-key tab navigation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cc305b1d97 |
refactor(frontend): reorder sidebar settings menu, move logout to user submenu (#10149)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4ee1d32101 |
feat: display openai reasoning summaries in ai chat (#10147)
* feat(frontend): display openai reasoning summaries in ai chat with unverified-org fallback Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): scope hidden-thinking hint per workspace/provider and skip summary on explicit reasoning-off Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): compose responses fallbacks in either error order and track all unavailable summary keys Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0694b84da7 | fix(frontend): surface real tool call errors in AI chat (#10146) | ||
|
|
0ea570570e |
feat(ai-sessions): CRUD markdown artifacts in sessions (#10046)
* feat: add IndexedDB persistence layer for AI-chat artifacts * feat: add reactive store for AI-chat artifacts * feat: add artifact chat tools and wire store lifecycle * feat: add markdown artifact viewer with source toggle * feat: surface session artifacts in the preview panel and chat list * feat: tell the copilot when to use artifacts in the session prompt * test(ai_evals): add artifact case and wire artifact helpers for session context * fix(copilot): keep in-memory artifacts across same-session resyncs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: unify session composer edits/artifacts/jobs into a status line Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: add an artifacts section to the session preview picker Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: share markdown prose presets and restyle the artifact viewer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor: unify session status popovers into one keyboard-navigable shell Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: reset first-block top margin in all markdown prose presets Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: open the preview picker on the artifacts branch for an active artifact Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: keep artifact picker scope independent of branch hydration state Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Guilhem Lemouel <guilhemlemouel@gmail.com> |
||
|
|
8c725d9e44 |
fix(apps): honor presigned S3 signature on app display/preview routes (#10141)
The app provenance gate short-circuits on a valid presigned signature, but only the raw download_s3_file route parsed it. The parquet/csv/table-count/file-preview/metadata routes discarded sig/exp and always fell through to the provenance gate, so a presigned S3 object rendered as a table showed "File restricted" for any viewer who did not produce it. Thread sig/exp through every apps_u S3 display route and forward the presigned bearer from ParqetCsvTableRenderer/DisplayResult. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
af177cefe0 |
fix(frontend): graceful small-screen timeframe picker on the runs page (#10073)
* fix(frontend): prevent runs timeframe calendar popover overflow on small screens The Runs page timeframe picker rendered its popover as a wide 3-column row (preset list + two side-by-side calendars). With the right-aligned trigger and a center-anchored `bottom` placement, the popup ran off the right edge on narrow viewports. Anchor the popover to the right edge (`placement="bottom-end"`) and make its content reflow to a vertical stack below the `sm` breakpoint, capped at `max-w-[calc(100vw-2rem)] max-h-[80vh] overflow-auto` so it can never exceed the viewport. The desktop side-by-side layout is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(frontend): compact runs timeframe picker with a Start/End toggle on small screens The two-calendar desktop popover needs ~780px (two min-w-9 grids + presets + popover padding); below that it overflows. Under 800px, show a single calendar with a Start/End toggle picking which bound it edits, using set-start/set-end so each bound keeps its date and HH:MM time inputs — the same precision the desktop start/end pair offers. On short/landscape viewports the compact panel is scroll-contained within the popover's fitViewport height (contentClasses overflow-y-auto, scoped to the small layout) so its lower controls stay reachable. The desktop two-calendar layout is unchanged. Presets are shared between both layouts via a snippet, and the active range is preserved across the breakpoint since both branches drive the same value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(frontend): let InlineCalendarInput month/year selects portal, use in compact timeframe picker Add an opt-in `portalSelects` prop to InlineCalendarInput that portals the month/year dropdowns to the body (default keeps them in-flow, so existing consumers are unchanged). The compact runs timeframe picker enables it so the dropdowns escape its scroll-contained (overflow-y-auto) popover instead of being clipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ebe31aeeac |
feat(dev-workspace): reflect existing protection rules in lock toggles (#10093)
* feat(dev-workspace): reflect existing protection rules in lock toggles When creating or attaching a dev workspace, the "block direct edits" and "prevent forking" toggles now check the root workspace's current protection rules. If a restriction is already enforced by an existing rule, its toggle is shown on but locked, with a note, instead of offering a fresh default that could misrepresent the effect. The value sent to the backend is derived so it stays consistent with what the locked toggle shows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: clarify fail-open comment on dev-workspace lock toggles Reword the protection-rule fetch comment so the fallback path isn't misread as dropping protection: a failed fetch falls back to the editable default-on toggle, and any real rule still enforces server-side. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dev-workspace): lock protection toggles until rules load The lock toggles derived alreadyBlocks* from an async fetch, so during the load window (and the first frame before loading flips) they were editable and the effective value could be false. A user could turn a lock off and submit before an existing rule was detected, omitting the reserved rule and silently leaving prod unprotected once that existing rule was later removed. Treat "rules not yet known" (loading || current === undefined) the same as "already enforced": lock the toggle on and keep the effective value true during that window, so the request can never submit false before the fetch resolves. Submission stays available (a hung fetch degrades to over-protection, not a blocked form). Also fixes the stale-value flash when switching base workspace. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dev-workspace): honor rule bypasses and guard stale protection fetches Two issues in the protection-rule awareness for the dev-workspace lock toggles: - Bypassable rules became unconditional locks. alreadyBlocks* used isRuleActiveInRulesets, which ignores bypass_users/bypass_groups, and forced the request flag to true. The reserved dev_workspace_lock rule is created with empty bypass lists, so layering it over an existing rule that let specific users through revoked their deploy/forking access. Switch to isRuleUnconditionallyActiveInRulesets so a toggle is only shown as already enforced (locked) when an existing rule has no bypasses; a bypassable rule stays editable, making the lock the user's explicit choice. - A stale protection fetch could apply another base's rules. The generated client can't take an abort signal, so a delayed response for a previous base could overwrite the newly selected one. Tag each result with its workspace and only trust a result matching the current base; also throw AbortError from a superseded fetch so it can't overwrite current. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: condense protection helper comment to four lines Trim the isRuleUnconditionallyActiveInRulesets doc comment to satisfy the AGENTS.md ≤4-line comment rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dev-workspace): align already-enforced note under the toggle label The note used ml-8, landing under the toggle switch rather than aligned with the switch edge or the label, so it read as floating. Bump to ml-11 so it lines up under the label as helper text for that toggle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
88030d0f55 |
fix(tree-view): align file indentation with sibling folders (#10115)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
af3e3fe667 |
fix(ai-chat): size AI-created flow notes to fit their text (#10091)
* fix(ai-chat): size AI-created flow notes to fit their text Free notes created via the flow AI chat omit `size` (the tool prompt tells the model to let the editor size them). validateFlowNotes seeded a fixed 275x60 box, but free notes never grow to fit content, so multi-line markdown overflowed the box. Estimate height from the text instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai-chat): stack auto-placed flow notes by height to avoid overlap Auto-placed free notes were staggered by a fixed index*84px step, but notes can now be up to 600px tall, so consecutive generated notes overlapped. Track a running y-cursor and advance it by each note's real height. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai-chat): advance note stack cursor past preserved column notes A round-tripped note keeps its existing auto-column geometry ({-375, y}); the stack cursor ignored it, so a newly added geometry-less note landed on top. Preserved notes overlapping the auto-stack column now advance the cursor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(ai-chat): trim estimateFreeNoteSize comment per AGENTS.md Keep only the non-obvious fixed-height renderer constraint; drop the implementation narration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7ebfad382a |
feat(ai-agent): give tools a real description instead of the tool name (#10083)
* feat(ai-agent): use a real tool description instead of the tool name Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai-agent): render tool-name error full width and hoist it above the description Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai-agent): make tool description field hug its content so a single line is vertically centered Add an optional minHeight param to the autosize action (default unchanged at 30px) and pass minHeight 0 for the tool description so an empty/one-line field no longer reserves the 30px floor and leaves dead space below the text. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(ai-agent): regenerate OpenFlow-derived prompts, CLI guidance, and copilot zod schema for tool description Fixes the check-freshness CI failure (system_prompts + skills.gen.ts) and makes the flow copilot's openFlow.json / openFlowZod.gen.ts aware of the new AgentTool.description field so AI-authored tools can set it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f3cd5d9f7f |
fix(sessions): pending-draft debounce follow-ups (delete-cancel, keystroke de-transient, teardown count) (#10087)
* fix(sessions): cancel pending draft-prompt flush on delete deleteSession removed the record from memory and IndexedDB but left the debounced draft-prompt flush timer running; it would fire afterward and persistTouched the deleted session back into IndexedDB, resurrecting a draft deleted inside the 400ms window on the next reload. Clear the per-session timer in deleteSession. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): de-transient drafts on keystroke; count pending in workspace teardown Two follow-ups to the pending-sessions feature (#10076), surfaced by codex review: - setSessionDraftPrompt clears `transient` synchronously so a draft typed into is no longer treated as a reusable blank by createSession. Previously the flag only cleared 400ms later via the debounced flush, so pressing `+` right after typing reopened the same draft instead of spawning a second pending session. Only the IndexedDB write stays debounced. - countSessionsForWorkspace counts on `workspace_id ?? pending_workspace_id`, so the archive/delete confirmation includes persisted unsent drafts, matching reconcileSessionsLifecycle which tears them down alongside committed sessions. Adds regression tests that drive the real keystroke transition and the pending draft count. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): reuse-guard on draftPrompt, not a synchronous transient clear Addresses codex-review P1 on #10087: the previous approach cleared `transient` synchronously on a keystroke to stop createSession reusing a just-typed draft. But `transient` also means "in-memory only, not yet in IndexedDB" — clearing it before the 400ms flush left the draft in neither bucket, so a reconcile landing inside the window (hydrateSessions rebuilds the list as in-memory-transients + DB rows) dropped the unsaved draft and dangled currentSessionId. Separate the two concepts instead: keep `transient` as pure persistence state (the draft survives hydration), and define a reusable blank as `transient && !draftPrompt`. createSession's reuse probe and its non-reuse drop both key on isReusableBlank, so a typed-but-unflushed draft is neither reused nor discarded, and setSessionDraftPrompt no longer touches `transient`. Adds a regression test that interleaves a first-touch debounce with reconcile and asserts the draft stays in memory (and currentSessionId intact); updates the keystroke test to the real transient-preserving transition. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): treat a typed-then-erased draft as touched, not a reusable blank Addresses codex-review P2 on #10087. isReusableBlank used `!s.draftPrompt`, so a draft typed into then erased back to '' (draftPrompt === '', flush still pending) was classed as a reusable blank: pressing `+` within 400ms reused it, but after the flush cleared `transient` the same `+` created a new session — inconsistent across the debounce boundary, and in another family the non-reuse drop removed the draft while its pending timer later persisted it back. setSessionDraftPrompt only sets draftPrompt on a genuine edit (mount-time '' is a no-op via the equality guard), so `draftPrompt === undefined` cleanly means "never edited". Key isReusableBlank on that instead of falsiness. Adds a type-then-erase-before-`+` regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(sessions): trim new comment blocks to AGENTS.md 4-line limit Addresses codex-review P2 on #10087: condense the setSessionDraftPrompt, countSessionsForWorkspace, and isReusableBlank comment blocks to <=4 lines per the AGENTS.md rule. Comment-only, no logic change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(sessions): trim last two comment blocks to AGENTS.md 4-line limit Follow-up to codex/pi P2 nits on #10087: condense the keystroke-transition test comment (5→4 lines) and the countSessionsForWorkspace comment (→3 lines). All new comment blocks in the PR are now ≤4 lines. Comment-only, no logic change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cfc3f292ad |
fix(apps): allow setting sandbox isolation and public access before first deploy (#10085)
* fix(apps): allow enabling sandbox isolation before first deploy Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): allow setting public access mode before first deploy Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
32f32d9a29 |
feat(ai-chat): port flow-group and sticky-note instructions to global chat (#10090)
* feat(ai-chat): port flow-group and sticky-note instructions to global mode Global-mode AI chat inherited only the bare FlowGroup schema and had no sticky-note support, so it never proactively segmented flows into groups and could not author flow-wide notes. Flow mode carried this guidance inline in its own prompt and set_flow_json tool. Bring global mode to parity: - Enrich write_flow's `groups` description (color palette + fields) and add a `notes` field mirroring flow mode's set_flow_json. - Thread `notes` through editableFlowToDraftValue and the write_flow handler so notes reach FlowValue.value and survive the deploy round-trip. Reads and patch_flow_json already carried notes via the shared editableFlowJson helpers. - Expand getFlowInstructions with the groups/notes organizing guidance (strongly-recommended proactive grouping, color palette, when-to-use-which) and mention notes in the write/read/compact-view/structural-edit bullets. Add a write_flow -> read_workspace_item notes round-trip test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ai-chat): trim write_flow groups/notes schema descriptions The write_flow tool schema is re-sent every chat loop iteration, so the verbose groups/notes descriptions were a per-iteration token tax that duplicated the on-demand getFlowInstructions() prose. Trim the .describe() calls to the correctness-critical bits (color palette, type "free", null semantics) and point to get_instructions for the full field reference, which getFlowInstructions() already carries. Addresses CI review feedback (Claude + Pi). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
eff9076e91 |
fix(sessions): reopen script test panel when preview goes full screen (#10082)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
15391f6399 |
perf(runs): index-bound batch re-run selection with a lossless completed_at bound (WIN-2168) (#10074)
"Re-run all jobs matching filters" selects completed jobs via list_filtered_uuids windowed by started_before/started_after (the timeframe). v2_job_completed has no index on started_at (only completed_at), so that filter alone forces a workspace-wide seq scan — a query observed at ~48s on a large instance. started_at >= minTs implies completed_at >= minTs (a job completes at/after it starts), so adding completedAfter = minTs is a lossless bound: it drops no row the started_at window keeps, but lets the (workspace_id, completed_at DESC) index start the scan at the window's lower edge instead of scanning the whole table. The selected cohort is unchanged (started_at stays the exact filter); this is purely a plan improvement. EXPLAIN: seq scan -> completed_at index scan. Not completedBefore: a job can start in-window but finish after maxTs, and bounding completed_at above would drop it. Scoped to re-run; batch cancel (v2_job_queue, small) is untouched. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bfcec7e8ac |
feat(sessions): support many pending sessions persisted in IndexedDB (#10076)
* feat(sessions): support many pending sessions persisted in IndexedDB Allow several unsent AI sessions to be set up in parallel. Split the transient flag into "in-memory, not yet persisted" (unsent is derived from workspace_id), persist a pending session to IndexedDB on first touch with its own draftPrompt, show pending sessions in the sidebar under the family filter, and reconcile them by pending_workspace_id. The + button reuses the untouched draft in the active family so idle clicks don't pile blank entries; touching one spawns a fresh blank. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(sessions): focus composer when + reuses the untouched draft When there are no pending changes, `+` reuses the active family's untouched draft instead of creating a new session (unchanged). But when the reused draft is the one already on screen, currentSessionId doesn't change, so nothing navigated and the click gave no feedback. Bump a composerFocusRequest nonce in the reuse branch and have SessionWrapper's focus effect depend on it, so the composer re-focuses and the user can type right away. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): per-session debounce for draft prompt flush A single module-level flush timer let a keystroke in one pending draft cancel a sibling draft's pending first-touch flush, so the earlier draft was never written and its typed prompt vanished on reload. Key the debounce per session so parallel drafts persist independently. Also collapse the touch rationale repeated across the preview-tab/collapse/size setters onto persistTouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
207ce8649c |
fix(ai-agent): don't mark repeated tool calls as failed in flow graph (#10075)
* fix(ai-agent): don't mark repeated tool calls as failed in flow graph Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(ai-agent): cover reporter's mixed repeated-tool-call scenario Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
710a13a59d |
fix(apps): cover script/flow component outputs in deployed-app S3 provenance gate (#10070)
* fix(apps): cover script/flow component outputs in deployed-app S3 provenance gate Deployed apps read S3 files on-behalf of the app author for logged-in viewers (#10048). A confused-deputy guard confines those reads to files the app "produced", but the recent-production check only matched inline `appscript`/ `preview` jobs nested under the app path. Files produced by the deployed script/flow components an app is wired to run (e.g. a SQL query persisted to S3) were therefore denied "File restricted" for every viewer, admins included. Expand the provenance check to also match completed `script`/`flow`/`flowscript`/ `flownode` jobs whose `runnable_path` is one of the app's declared triggerables, and accept the author identity via `permissioned_as = on_behalf_of` (not only `created_by = caller`) so files produced on-behalf of the author are covered. Reads outside the app's declared triggerables stay denied. Adds a regression test seeding a script-kind produced file that reproduces the "File restricted" denial before the fix and passes after. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): key S3 provenance on on-behalf identity + cover flow steps (review) Addresses the CI review on the S3 provenance gate: - P1 (confused deputy): the recent-production check keyed on `created_by = caller`, so a viewer who can run a declared script/flow directly (outside the app, with un-pinned inputs) could craft a result naming an author-only key and read it back through the app as the author. Key provenance instead on the producing job's `permissioned_as` matching the on-behalf identity the download reads as (the author in author-mode); a viewer's direct run has `permissioned_as = viewer` and no longer clears the gate. Drops `created_by` from both the appscript/preview and script/flow branches, closing the same latent hole in the pre-existing inline-script branch. - P2 (dead flow-step branch): `flowscript`/`flownode` jobs have `runnable_path = <flow_path>/<step_id>`, which exact `= ANY(...)` never matched. Split script vs flow triggerable paths; flow kinds now match the flow's own job (bare path) and its step jobs via a `<flow_path>/%` prefix, bounded to declared flows. - P2 (test realism): the regression test now uses the production component-prefixed triggerable key format (`<id>:script/...`), exercises a flow-step-produced key, and asserts a viewer's own direct run of a declared script stays denied (the P1 case). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): tie deployed-app S3 provenance to an app-origination marker (review) Second CI-review round flagged that `permissioned_as` still does not prove a job was app-launched: a runnable configured with its own `on_behalf_of` makes a direct `/jobs/run` resolve `permissioned_as` to that identity (the app author), so a viewer with run access could execute a declared runnable directly, craft an S3 result, and read it back through the app. The flow-path `LIKE fp || '/%'` match also let `_`/`%` in a declared path admit unrelated flows. Introduce a real app-origination marker instead of inferring provenance: - Add `JobTriggerKind::App`; `execute_component` stamps every app-launched job with `trigger_kind = 'app'` + `trigger = <app path>`. A direct `/jobs/run` cannot set this, so it is the authoritative signal that a file was produced *by the app*. - The provenance gate's recent-production check collapses to `trigger_kind = 'app' AND trigger = <this app path>` (+ the 3h window and result containment). This drops the forgeable `created_by`/`permissioned_as`/ `runnable_path`/kind logic entirely and removes the `LIKE` wildcard issue. - Provenance is scoped to THIS app's path, so another app's jobs (even same author) do not authorize this app's reads. Regression test rewritten to the marker model: an app-produced key clears for viewer and admin; a direct run whose `permissioned_as` resolves to the author stays denied (the forgery); another app's output stays denied. Adds `app` to the OpenAPI JobTriggerKind enum. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(apps): assert execute_component stamps trigger_kind='app' at runtime Adds an end-to-end test that runs a real script component through the app runtime (`apps_u/execute_component`) and asserts the enqueued job carries the app-origination marker `trigger_kind = 'app'` + `trigger = <app path>` (not the runnable path). The provenance-gate tests seed the marker directly; this proves the runtime actually produces the exact marker the gate depends on. execute_component commits the job row and returns its id, so the assertion reads the row directly — no worker needed to run the job. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(triggers): reject trigger_kind=app for suspended-job reassignment (review) `JobTriggerKind::App` (added for the app-origination S3 marker) became a valid value for the resume/cancel suspended-trigger routes, whose handler derives the table name `<kind>_trigger`. There is no `app_trigger` table, so both endpoints would fail with a missing-relation database error (500). Reject `App` in `get_suspended_trigger` alongside webhook/schedule so it returns a clean 400. Adds a regression test asserting the reassignment route returns 400 (not 500) for trigger_kind=app. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): don't stamp app-origination marker on preview runs (review) The app-origination marker (trigger_kind='app') was stamped unconditionally, including preview mode. A preview lets a `jobs:run` caller supply arbitrary `raw_code` against ANY app path without that app's deployed policy (raw_code with no path/id skips all app authorization), so a preview returning `{"s3":"<author-only-key>"}` would forge the exact marker the S3 provenance gate trusts and read the victim app author's file. Gate the marker on `!is_preview`: only deployed, policy-checked executions are app-provenanced. Preview/editor S3 display does not rely on this marker (the editor routes reads through the force_viewer allowlist), so nothing legitimate regresses. Adds a regression test asserting a preview run's job is not stamped trigger_kind='app'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): editor-authorize preview marker + per-viewer S3 provenance isolation (review) Closes the codex P1 (preview forgery) without breaking editor preview downloads, and adds cross-viewer isolation to the provenance gate. - Preview marker now requires app write: `execute_component` stamps the app-origination marker on a preview only when the caller can EDIT that app (`require_is_writer`), instead of never stamping previews. An app editor already wields the app's author identity (they can deploy a component that reads the same file), so marking their own preview is no escalation and keeps preview-produced S3 results downloadable in the editor; a `jobs:run`-only caller who cannot edit the app still cannot forge the marker. Deployed runs are unchanged (always marked). - Per-viewer isolation: the provenance gate now also requires `j.created_by = <this caller>`. The security boundary stays the un-forgeable `trigger_kind='app'` marker; `created_by` is an additional filter ANDed under it, so it only narrows — a viewer can only download keys their OWN app runs produced, not another viewer's result. Restores the per-caller scoping #10048 had, now safe on top of the marker. Tests: preview marked iff caller can edit the app; cross-viewer isolation (another viewer's app-marked key denied, no admin bypass); direct-run and other-app keys still denied; deployed run still stamped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): require apps:write scope (not just writer ACL) to mark preview provenance (review) require_is_writer checks the user's underlying ACL but ignores token scopes, so a writer's token deliberately scoped to apps:run/apps:read/jobs:run but WITHOUT apps:write could still mark a preview and forge provenance — even though that token cannot deploy the app (update_app requires apps:write), breaking the "any marked caller can deploy equivalent code" rationale. Require BOTH apps:write:<path> scope (check_scopes) AND the writer ACL (require_is_writer) before stamping a preview's app-origination marker. Deployed runs unchanged. Adds a scope-restricted-writer token to the test (apps:run/read + jobs:run, no apps:write) and asserts its preview stays unmarked; retains the full-editor positive case and the non-editor negative case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): never app-provenance preview runs; read editor S3 as the caller (review) Simplifies the preview handling: a preview executes as the *caller* (Viewer mode), never as the author, so its results must be read back as the caller — never author-mode — and must never carry the app-origination marker. This removes the whole `require_is_writer` / `apps:write` / `can_preserve_on_behalf_of` reasoning (which was also unsound: a writer's token or session may not be able to deploy a component running as the app's on-behalf identity, so marking their preview could still escalate). - Backend: mark the app-origination marker for deployed runs only (`!is_preview`). - Frontend: `getS3File` (AppImage/AppPdf/AppDownload) now routes editor/preview reads through the viewer-scoped `job_helpers/download_s3_file` endpoint (reads as the caller), matching what DisplayResult/ParqetCsvTableRenderer already do; only a deployed app view uses the provenance-gated `apps_u` endpoint. This is the path that previously relied on marking previews, so nothing regresses. Test: a preview is never app-provenanced (owner's own preview and a non-editor's both stay unmarked). Cross-viewer isolation, deployed marking, and the reassignment guard are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): app components run on-behalf of the app, not the referenced runnable (review) Root-causes codex's on-behalf-preview finding: `execute_component` was overriding the app's resolved on-behalf identity with the referenced script/flow's OWN `on_behalf_of` (its `on_behalf_of_email`). That is wrong in the app context — the app's execution mode should govern: - A Viewer-mode app could execute a component AS the referenced runnable's on_behalf identity (privilege confusion / escalation), instead of as the viewer. - A preview would run as that identity rather than as the caller, so its S3 output could not be read back as the caller — the download-identity mismatch codex flagged. Always use the app-resolved identity (author in author-mode, caller in viewer/preview); a referenced runnable's own `on_behalf_of` no longer leaks into app execution. Direct `/jobs/run` still honors a runnable's `on_behalf_of` (unchanged). With this, previews always run as the caller, so reading editor/preview S3 as the caller (viewer-scoped `job_helpers`) is unconditionally correct. - Test: the deployed-component e2e now seeds the script with a distinct on_behalf and asserts the component job's `permissioned_as` is the app identity, not the script's. - Also reword the getS3File `configuration` param comment to describe current state only (AGENTS.md comment rule). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(apps): surface 'app' trigger kind in Runs UI; condense provenance comments (review) Addresses codex review nits: - Add `app` to `jobTriggerKinds`, `triggerIconMap` (LayoutDashboard), and `triggerDisplayNamesMap` so app-component jobs (which now carry `trigger_kind = 'app'`) are filterable in Runs and render their trigger info. - Condense the app-origination marker, on-behalf-identity, and provenance-gate comments to state each invariant once in <=4 lines at its relevant site (AGENTS.md comment rule). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5cde2d5b67 |
fix(sessions): sync AI-session preview with workspace edits + stop phantom autosave (WIN-2160) (#10061)
* fix(sessions): sync AI-session editor preview with workspace edits + stop phantom autosave (WIN-2160) Two related draft-sync fixes surfaced by the new AI sessions preview. 1. Session preview went stale after a workspace edit. A session's editor runtime cell (content store + loadedPath) outlives the sessions page: it survives toggling to workspace mode and MRU tab eviction. The shared per-user draft can change while the editor is unmounted — most visibly by editing the same item in the classic workspace editor, or from another device — but on the next mount the load early-returns on the still-set loadedPath and the preview keeps showing the pre-toggle content. Fix: invalidate the cell's loadedPath when SessionEditorTarget unmounts, so the next mount re-fetches the draft as a clean first load. This also sidesteps a Monaco model-reuse race (a force-reload that remounts the editor while the old one is still disposing renders a stale model) and prevents the outbound draft-sync from posting the stale store back (ready() stays false until the reload lands). Applies to all three editor kinds (script, flow, raw app) since they share SessionEditorTarget. 2. Opening a deployed script in the full-page editor autosaved a phantom draft with no user change. The deployed baseline carries a server-derived assets: [] that the editor's draft value never reproduces, so draftValuesEqual never matched baseline, discardIf returned false, and the settle-time write posted a no-op draft. Fix: ignore assets in the draft-vs-baseline comparison (it's derived from content, so it can't mask a real change). Fixes WIN-2160 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(sessions): condense teardown-invalidation comment to repo comment-length rule --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ff774c46bf |
feat: add per-workspace job-retention override (#10050)
* feat: add per-workspace job-retention override (EE) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 2ba6a2a75b6fc97858b306b2c98ada481e363c10 This commit updates the EE repository reference after PR #658 was merged in windmill-ee-private. Previous ee-repo-ref: e7fb36acd813cd717bcf05f5aafbf81de271d618 New ee-repo-ref: 2ba6a2a75b6fc97858b306b2c98ada481e363c10 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> |
||
|
|
04eb7ddd39 |
fix: clearer errors on auto-draft save failure (WIN-2157) (#10053)
* fix: clearer errors on auto-draft save failure (WIN-2157) When an autosave draft save fails, the cloud indicator now surfaces the backend reason on hover (native title tooltip) in addition to the existing click popover, so the cause is discoverable without a click. Backend now returns a clearer, actionable message: - `require_can_write_path` distinguishes a malformed path (unrecognized namespace prefix -> BadRequest) from a genuine permission denial, and the deny message spells out where the user *can* write. - `require_owner_of_path` no longer panics with an out-of-bounds index on a malformed single-segment path (e.g. a bare `u`/`f`); it returns a clear BadRequest instead. Covered by a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: trim narrative comment to invariant in drafts.rs (WIN-2157) Address CI review (AGENTS.md: comments record constraints, not narration, ≤4 lines): keep the malformed-path invariant, drop the motivation tail. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: don't let a malformed stored draft 400 the draft listing (WIN-2157) Address CI review (P1): require_can_write_path can now return BadRequest for a malformed path, and list_drafts propagated it — so a single malformed stored draft row (the draft table has no path constraint; legacy/admin-authored rows may be malformed) would make GET /drafts/list return 400. Treat BadRequest like NotAuthorized there: the row is simply not writable. Verified e2e on EE — listing returns 200 with can_write false for the malformed rows. Also trim "unchanged"/"still" drafting-history narration from the regression test comments (P2, AGENTS.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: compress list_drafts comment to 4 lines (WIN-2157) Address CI review P2: keep the constraint (draft table has no path constraint) and the invariant (one malformed row must not 400 the listing) within the AGENTS.md ≤4-line limit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |