mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 16:00:38 +00:00
04b47bf359fc67ef3a6cd4ca915fd4b67c19ca9c
8093 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
04b47bf359 |
fix(sessions): bypass UserDraft inside session panes + sessionUnread crash
After merging main's UserDraft PR (#9121) into the sessions branch, two integration issues surfaced: 1. AppEditor.svelte calls `UserDraft.use<App>('app', path)` at the component level — keyed by ($workspaceStore, 'app', path). Sessions that haven't materialized a fork yet stay at the user's main workspace, so a session targeting an app at the same path as a regular /apps/edit tab shared the same LS key. The session would read the regular tab's autosave and write its fork-edits back over it. Gate UserDraft.use on `!getContext('aiChatManager')` — sessions inject the manager via setContext, so inside a session pane the handle is `undefined`, stateApp falls through to the `app` prop the session loaded, and the auto-save $effect bails. Same gate on the four UserDraft.remove call sites in AppEditorHeader and RawAppEditorHeader so save/deploy from a session pane doesn't wipe the LS draft of a non-session tab at the same path. 2. sessionUnread.svelte.ts called useLocalStorageValue at module scope. Main's PR added a deep-mutation $effect inside that helper, which now requires component-initialization context — every page crashed at import time with `Svelte error: effect_orphan`. Replaced with a plain module-level $state + manual localStorage persist; same reactivity contract for callers. 3. ScriptEditorView.svelte was passing a `replaceStateFn` prop that ScriptBuilder dropped on main. Removed. Verified end-to-end with Playwright: - /flows/edit/{path} regression: UserDraft handle still created, no console errors - /sessions loads, sessionUnread doesn't crash - Session targeting non-raw app `u/admin/userdraft_collision_test` displays the fork content (FORK_ONLY_MARKER) even with an LS poison at `userdraft/w/local/app/{path}` containing a POISONED_BY_REGULAR_TAB_AUTOSAVE marker; poison remains untouched after the session loads and renders Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d64d055018 | Merge remote-tracking branch 'origin/main' into gl/layout-ai | ||
|
|
0f7dd86e5c |
feat: persistent in-editor drafts via UserDraft (#9121)
* refactor(frontend): remove localStorage-backed autosave drafts
Strip the per-editor localStorage autosave for flows, apps and raw apps,
along with the associated restore toasts and diff actions, so we can
replace them with a unified UserDraft service in a follow-up. The
backend DraftService (DB-backed drafts) is untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): add UserDraft service for per-workspace local drafts
Introduces UserDraft, a key-value store keyed by
`{workspace}/{itemKind}/{path}` and backed by localStorage. Supports
save/get/remove plus a reactive use() handle so multiple component
instances observing the same draft stay in sync via a shared $state
loaded through useLocalStorageValue. Designed to host drafts for
scripts, flows, apps, raw apps, resources, variables, and all trigger
kinds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* tests
* nit schedule_ prefix
* feat(frontend): persist deep mutations in useLocalStorageValue
Track the serialized value alongside the $state and add an $effect that
deep-reads it (via readFieldsRecursively). When a deep mutation produces
a serialization that differs from the last persisted blob, write it to
localStorage. The setter keeps writing synchronously so callers reading
localStorage right after assignment still see the new value; the effect
no-ops on those because lastSerialized was already updated by the setter.
Undefined values are persisted as a removal.
UserDraft no longer needs its own removeItem workarounds for undefined
values — useLocalStorageValue handles that uniformly now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): add defaultValue + empty-path handling to UserDraft
UserDraft.use() accepts an opts.defaultValue used when no localStorage
entry exists yet. It is not persisted on first read — only an actual
mutation writes through.
Empty paths (new items) bypass localStorage entirely. The entry still
lives in the in-memory Map so multiple components on the same /add page
share state, but save/get/remove/use never read or write localStorage
with an empty path. Once the item is saved and the route navigates to
its new URL, a fresh use() on the non-empty path takes over.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire script editor to UserDraft
The script editor's top-level state now lives in UserDraft.use(), keyed
on the route's path (page.params.path on /scripts/edit, '' on /scripts/add).
Deep edits inside ScriptBuilder persist automatically; deploy and draft
restore now call UserDraft.remove to clear the local autosave alongside
the backend draft.
Replaces the URL-hash autosave that ScriptBuilder used to write via
replaceStateFn — that prop is now gone, the encodeScriptState debounce
is gone, and Triggers no longer takes a saveSessionDraft callback.
Viewing a specific historical hash (?hash=...) is kept draft-free by
passing '' as the path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire flow editor to UserDraft
flows/add and flows/edit drive the flow value through a StateStore
adapter backed by UserDraft.use, so every edit auto-persists at
userdraft/w/{ws}/flow/{path} without touching FlowBuilder's internal
.val convention. On returning visits the local autosave wins and a
toast offers a diff against the latest backend draft/deployed version;
on a fresh visit the backend value is written into the handle. Deploy,
save-as-draft rename, restore-draft and restore-deployed each call
UserDraft.remove on the route path so the local autosave doesn't
outlive the action.
Adds UserDraft.has() for "is there already a local draft?" detection
in the load path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire app editor to UserDraft
AppEditor registers a UserDraft.use<App> handle for its current path
(empty path for /apps/add stays in-memory) and a single $effect
deep-tracks the internal stateApp and forwards every mutation to the
handle. useLocalStorageValue's lastSerialized check then dedupes the
actual localStorage writes per tick, so even fast drag/resize loops
only persist when the JSON output really changes.
/apps/edit overlays a local autosave from UserDraft.get on top of the
backend value when one exists, with the existing "Discard / Show diff"
toast wired to UserDraft.remove. Deploy, save-as-draft, restore-draft
and restore-deployed all call UserDraft.remove on the relevant path,
including the JSON editor save paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire raw app editor to UserDraft
/apps_raw/edit owns the canonical raw-app state (files, runnables,
data, summary) in four $state vars; a single $effect deep-tracks them
and forwards the bundle to a UserDraft.use<RawAppDraft> handle so each
mutation tick persists at userdraft/w/{ws}/raw_app/{path} (deduped by
useLocalStorageValue's serialized check). On load the route overlays
the local autosave on top of backend.draft/deployed and offers a
"Discard / Show diff" toast when they diverge; matching local entries
are silently dropped. Deploy, save-as-draft rename, restore-draft and
restore-deployed each call UserDraft.remove on the route path.
/apps_raw/add keeps the same shape (UserDraft.use with empty path)
so the draft is in-memory only and we drop it explicitly when the
initial save creates the real path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire resource editor to UserDraft
ResourceEditor registers a UserDraft.use<ResourceState> handle keyed
on the initialPath (empty for new resources, in-memory only). A
$effect deep-tracks the current workspace's edit state and forwards
mutations to the handle; on bootstrap and lazy backend-fetch the
local autosave wins over the backend value when they diverge. After
a successful save() we call UserDraft.remove so the local autosave
doesn't outlive the deploy. Cross-workspace deploys always start from
the live backend value rather than the local draft.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire variable editor to UserDraft
VariableEditor persists the current workspace's edit state via
UserDraft.save on every mutation, keyed on editPath ('' for new
variables → in-memory only). Backend fetches now overlay a matching
local autosave when one exists, and initNew() rehydrates from the
in-memory empty-path entry so opening a fresh "Add variable" drawer
keeps any unsaved work from the previous open. After a successful
save we drop the corresponding entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* editor external changes sync
* fix(frontend): don't UserDraft.remove flows while route is still mounted
The /flows/add and /flows/edit routes drive FlowBuilder from a flowStore
whose getter reads flowHandle.draft directly. Calling UserDraft.remove
synchronously before goto() therefore wiped the in-memory entry, made
flowStore.val collapse to emptyFlow(), and tripped
UnsavedConfirmationModal against the just-saved value — even though the
deploy/save-draft itself succeeded.
Drop those explicit removes in onSaveInitial, /add onDeploy, and
/edit onDeploy. The empty-path entry self-cleans on unmount via
onDestroy ref counting; for the non-empty edit path the next visit's
load-time diff will silently overwrite localStorage when the local
autosave matches the deployed value. Restore-draft/restore-deployed
keep their explicit remove because they navigate to the same route
(no modal) and loadFlow immediately rehydrates the handle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "fix(frontend): don't UserDraft.remove flows while route is still mounted"
This reverts commit
|
||
|
|
413404a788 | fix: collapse successful ai tool details (#9265) | ||
|
|
c4a86838fb |
set explicit cursor color in light editor theme (#9134)
The light Monaco theme ('myTheme') did not define editorCursor.foreground,
causing the cursor to be invisible on white backgrounds. The dark theme
('nord') already sets this explicitly.
Fixes #8876
|
||
|
|
7e38c01e64 |
feat(sessions): chat + editor side-by-side with multi-session state
Introduces the Sessions feature: a workspace where the AI chat and an editor (flow / script / app / raw-app) sit side-by-side, with each session having its own AIChatManager instance, history, and target item. Sessions are persisted across reloads and can be staged into forks for review. Key pieces: - sessions/ — SessionWrapper (the split-pane shell), SessionPicker (sidebar list), SessionForkBar, SessionWorkspaceBar, FlowEditorView / ScriptEditorView / AppEditorView / RawAppEditorView, ForkDiffDrawer, sessionRuntime (per-session AIChatManager + draft state), sessionState (in-memory + persisted index), sessionUnread, sessionScope, appDraftCodec / flowDraftCodec, forkEditUrl, /sessions route. - WorkspaceItemDrillPicker refactor — extracts WorkspaceItemRow + adds surfaceAI drafts, stale-while-revalidate. workspacePicker.ts drops explicit invalidate() in favor of always re-fetching in the background. - ForkDiffDrawer + WorkspaceItemDiffViewer — per-kind diff bodies reusable from the compare page. FlowGraphDiffViewer / FlowGraphV2 gain inlineDiff forwarding + onHeight callback for equal-height layout. - Global AI chat sessions plumbing — AIChatManager exports the class + adds disabledModes, beforeSend hook, scoped instance context. AIChat / AIChatDisplay accept session-only props (wideLayout, emptyHint, inputPreface, hideHeader, hideModeSelector, forceDisabled). Chat preserved across /flows/add → /flows/edit, /scripts/add → /scripts/edit. - Draft-first loaders — sessions open drafts when present, otherwise seed a draft from the last deployed value via globalDraftStore. RawAppEditor / AppEditor / AppEditorHeaderDeploy get newApp prop + fixes so draft-only apps can deploy. - Compare page (/forks/compare) — bigger overhaul to plug into the new drawer. - Sidebar — Sessions entry + unread badge + status dot in SidebarContent / MenuButton / SideBarNotification. - Misc fixes — chat group color palette constraint, deploy_workspace_item confirmation dropped, open_preview tool, picker drafts surfacing, fork archive/delete buttons on compare page. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
9c28bbfd69 |
feat(frontend): new path component (#9017)
* stash
* ui nits
* Fix contenteditable feedback look (duplicate typing)
* fix right icon wrong position with placeholder
* user editor in Path editor takes correct width
* nits
* nit
* chore: remove assets-operator changes (moved to separate PR)
These files were mistakenly included in this PR and belong in a dedicated PR
("Allow assets page to operators").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: remove sidebar assets-operator change (moved to separate PR)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix disabled
* border nit
* Fix disabled styling
* Apply suggestion from @cubic-dev-ai[bot]
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* nit
* Update frontend/src/lib/components/text_input/TextInput.svelte
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* Fix disabled tabindex and aria-disabled on contenteditable Select
The useContentEditable branch had an unconditional tabindex="0", keeping
a disabled Select in the tab order, and was missing aria-disabled.
Mirror the TextInput div branch.
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
* fix: drop obsolete hideFullPath prop from EditorHeader Path usage
* invalidate autocomplete paths on deploy
* nit pixel
* use Badge in auto complete
* nit prevent default
* fix(autocomplete): don't let stale fetch clobber forced refresh
A non-forced fetchWorkspacePaths() that started before invalidateWorkspacePaths()
could still resolve afterward, overwrite the cache, and clear forceNextFetch —
making the post-deploy refresh a no-op. Only write back from the promise that
is still the current pending one, and only clear the force flag when the
completing fetch was itself forced.
* refactor(path): drop unreachable 'group' branch in owner-kind setter
The Select only offers user/folder, so the 'group' branch was dead. Leave a
short note pointing at validateName which still accepts 'group' for
forward-compat.
* fix(path): respect disableEditing on owner-kind selector
Other path-editor controls disable on (disabled || disableEditing); the
owner-kind Select only checked `disabled`, so read-only users (trigger
editors with !can_write) could still toggle User/Folder and mutate the
bound path. Reuse the existing nameDisabled flag.
* Revert "fix(autocomplete): don't let stale fetch clobber forced refresh"
This reverts commit
|
||
|
|
9111f8908d |
feat(nsjail): make tmpfs size configurable via instance setting (#9261)
* feat(nsjail): make tmpfs size configurable via instance setting Adds a new `nsjail_tmpfs_size_mb` instance setting that overrides the size of the `/tmp` tmpfs mount inside the nsjail sandbox across all languages. When unset, the existing per-language defaults (500MB or 800MB) continue to apply, so no behavior change for existing deployments. The setting is exposed under Settings → Jobs and is read at job execution time, so changes take effect on the next job without a restart. Fixes WIN-1963 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(nsjail): unify default tmpfs size to 800MB Previously each executor passed its own per-language default (500MB or 800MB) to resolve_nsjail_tmpfs_size. Unify on a single DEFAULT_NSJAIL_TMPFS_SIZE_BYTES constant (800MB) so the placeholder behavior is consistent across languages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(nsjail): resolve tmpfs size outside ruby download closure The download.ruby config render runs inside a sync closure passed to par_install_language_dependencies_seq, so `.await` on resolve_nsjail_tmpfs_size() was a compile error under the `ruby` feature. Resolve the size once before the closure and capture the string instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(nsjail): rename resolver to *_bytes and clarify fallback Addresses CI review feedback: - Rename `resolve_nsjail_tmpfs_size` to `resolve_nsjail_tmpfs_size_bytes` so the returned unit is unambiguous at the call site (cubic P2). - Fix the `NSJAIL_TMPFS_SIZE_MB` doc comment that still said "per-language default" — there is no per-language fallback anymore, all unset values resolve to the unified 800MB `DEFAULT_NSJAIL_TMPFS_SIZE_BYTES` (codex/pi P2). - Expand the resolver doc to call out that `Some(0)` and negative values also fall back, since the match arm is `Some(mb) if mb > 0`. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
271f0cbd08 |
feat(debug): show ghost breakpoint and tooltip on gutter hover (#9150)
* feat(debug): show ghost breakpoint and tooltip on gutter hover * fix(debug): show ghost breakpoint only on glyph margin to match click handler * revert(debug): show ghost across entire gutter, not only glyph margin * refactor(debug): use MouseTargetType enum, short-circuit hover decoration |
||
|
|
b0ed27096d |
feat(editors): responsive top-bars + collapsible raw-app sidebar (#9237)
* feat(editors): responsive top-bars + script test-pane pixel-min + flow graph overlay
Editor top bars now collapse on narrow widths (measured via container
clientWidth, not viewport — they live inside drawers / session panes
where the viewport stays wide):
- FlowBuilder: Diff + Save draft fold into the ellipsis menu when
the top bar narrows below 720px (Save draft keeps its ⌘S / Ctrl+S
shortcut indicator). Test-flow button moves out of the top bar
and into a graph-pane overlay matching the dev page; the overlay
position flips from top-2 right-2 to top-14 left-1/2 when the
graph pane itself is narrower than 800px. FlowEditor exposes a
graphOverlay snippet prop for that.
- ScriptBuilder: Settings + Draft labels collapse to icon-only;
a new DropdownV2 ellipsis surfaces Tag / Settings / Save draft
when even icons don't fit. The ellipsis itself uses variant=subtle.
- AppEditorHeader / RawAppEditorHeader: fullscreen / dark-mode /
breakpoint toggle group + Debug-runs / Jobs buttons hide; Save
draft moves into the Deploy dropdown.
- EditorBar: a "Helpers" DropdownV2 collapse for Context var /
Variable / S3 / Resource / Git repo / Resource type / Database /
Ducklake / Data table / Reset when the bar narrows below 800px
(EDITOR_BAR_HELPERS_COMPACT_THRESHOLD). Above that, the existing
icon-only mode (1420px threshold) still applies.
- ScriptEditor's test pane gets a pixel-based minimum width (400px)
derived from the splitpane's clientWidth. The Pane uses Svelte 5
function-binding so the splitter writes to a raw $state while the
splitpane reads the clamped derived value — no $effect, no
release-time bounce, drag stops at the boundary. Cap raised to
80% so the test pane can take most of the editor on very narrow
layouts while leaving a sliver of code visible.
- VS Code button on ScriptEditor: collapses to icon-only below the
EDITOR_BAR_WIDTH_THRESHOLD (1420px) instead of being hidden
entirely by viewport `lg:` breakpoint; hidden completely when the
editor is rendered inside a session pane.
- AI wand button on ScriptEditor + RawAppEditorHeader: hidden inside
a session pane (detected via `getContext('aiChatManager')`) — the
session owns its own AI chat.
- DeployButton: drops the unused `newFlow` gate (callers updated).
- FlowDiffViewer / FlowGraphDiffViewer: inlineDiff prop forwarding
+ onHeight callback on FlowGraphV2 so diff viewers can equalize
side-by-side graph heights.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ui: unify Debug / wand / test-toggle button sizes + HideButton defaults
Two small consistency passes on shared button components:
- ScriptEditor's Debug, AI wand and Test-panel-toggle buttons all
use unifiedSize="sm" so they line up in the toolbar; Test toggle
switches from custom marine btnClasses to variant="accent-secondary".
HideButton gains a passthrough unifiedSize prop so the wand and
test toggle can match Debug without overriding btnClasses.
- HideButton's own defaults shift to variant="subtle" + sm
unifiedSize, dropping the legacy color="light" / variant="contained"
+ tailwind-merge background overlay; the selected (hidden) state
is now a tinted wrapper div instead of overriding btnClasses.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ui(script): editor toolbar polish
Small consistency tweaks on the script editor's top-right overlay:
- Lowercase "test" / "Exit debug" panel labels.
- `bg-surface` on the overlay container so the absolute-positioned
buttons read as a single panel over the graph rather than disjoint
pills.
- Debug button picks up `destructive={debugMode}` so the active
state reads as "you're in debug mode" instead of accent.
- Console and "Delegating to git repo" buttons drop the custom
`btnClasses` border-on-surface treatment and switch from
`size="xs"` to `unifiedSize="sm"` so they match the other buttons
in the cluster.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(raw-app): collapsible file sidebar, default-collapsed in session preview
The raw-app editor's left sidebar (file tree, runnables, history) ate a
lot of horizontal space — fine in the standalone editor, painful in the
session preview pane where the chat is already taking half the screen.
Add a small collapse / expand toggle. Persist the user's preference in
localStorage so it sticks across opens.
Two independent localStorage keys via the new `sidebarStorageKey` prop:
- standalone editor: `raw-app-sidebar-collapsed` (default expanded)
- session preview: `raw-app-sidebar-collapsed-preview` (default collapsed)
Otherwise the two contexts would race for the same key — whichever
opens first would dictate the other's default. Splitting the keys lets
each have its own remembered preference.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fixup(editors): use untyped getContext for AI-chat-manager session detection
The cherry-picks landed `getContext<AIChatManager>('aiChatManager')` to
hide per-editor AI/VSCode buttons when rendered inside a session pane.
The `AIChatManager` class is exported only on the sessions branch (used
for typing session-provided manager overrides). On `main` the manager
file exports only the singleton instance, so importing the class fails
the type-check.
The session-pane detection just needs a truthy/falsy probe — drop the
type parameter and the class import. `inSessionPane` ends up as
`getContext('aiChatManager')` (returns `unknown`, coerced to boolean
via `!!`). Same runtime behaviour, no class-export dependency.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ui(editors): flow test in top bar; ellipsis folds draft/jobs/tutorials
* refactor(app-editor): drop dead AppEditorTutorial button path
* ui(editors): wire compactHelpers in flow-step + raw-app inline editors
* ui(raw-app): sidebar Cmd/Ctrl+B toggle + uppercase section titles
* ui(editors): keep Diff/Settings inline as icon-only when narrow
* fix(editors): address review nits on test-pane/Helpers/thresholds
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
7909878313 |
feat(chat): waiting-for-user indicator + scroll-to-latest polish (#9252)
* feat(chat): waiting-for-user indicator and arrow polish
- Show "Waiting for your input" (text-accent + flipping Hourglass) instead
of the typing dots when the latest tool is staged for confirmation
(Run/Cancel) or has an active askUserQuestion. The dots imply the AI
is working, which is misleading when the loop is paused on the user.
- Scroll-to-latest arrow:
- Move up to bottom-12 when the flow Accept/Reject row is visible so
they no longer overlap.
- Wrap in a solid bg-surface + shadow + border badge so the icon
doesn't bleed into messages behind it.
- Bump unifiedSize xs → sm for a slightly larger target.
- Hourglass uses a custom CSS keyframe (:global so the rule reaches the
Lucide SVG root) with 4 s period and cubic-bezier(0.65, 0, 0.35, 1)
easing — feels like flipping the hourglass rather than spinning.
* fix(chat): raise waiting indicator above accept/reject row
* fix(chat): solid background behind reject all button
* feat(chat): @ picker in controls row, badges above input, polish
|
||
|
|
31b781000e |
feat(frontend): sync home search bar state to URL (#9256)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
78cf6c7f81 |
fix(saml): preserve deep links from /a/[...path] across SAML round-trip (#9259)
* [ee] fix(saml): preserve deep links from /a/[...path] across SAML round-trip Fixes WIN-1962. PR #9225 only covered users who pass through /user/login on their way to the IdP — that's where `redirectSaml()` runs and where the deep link gets stuffed into `RelayState`. The reported flow doesn't go through that page: it hits `/a/[...path]` (the public-app custom-path route, outside the `(logged)` layout) where `PublicApp.svelte` renders its own `<Login>` and was passing `page.url.toString()` as `rd` — the full URL. Three problems compounded: 1. `redirectSaml()` only set `RelayState` when `rd.startsWith('/')`, so a full URL silently fell through and the deep link was lost. The IdP echoed back the SP-library default (BASE_URL), which the ACS validator correctly rejected as a potential open-redirect. 2. `persistRd()` stored the full URL in `localStorage.rd`. On the fallback landing at `/user/login`, the post-login redirect saw an `http://...` value, hit the cross-origin branch, and bounced to `/` — which from a logged-in but workspace-less state shows the "Loading user…" modal forever (bug 2). 3. The EE `safe_relay_state_redirect` validator rejected any full URL, including same-origin ones, so even IdPs that prepend the origin or that pass a configured absolute deep link via IdP-initiated SSO got dropped on the floor. The fix is a single concept applied at every layer: reduce a redirect target to a safe same-origin relative path, or refuse it. Frontend: - `logoutRedirect.ts`: new `toSameOriginRelativePath(rd)` helper that accepts both `/foo` and `https://current-origin/foo`, with the same open-redirect guards as the backend (length cap, control chars, no protocol-relative or back-slash tricks). Returns `null` for cross-origin or malformed input. - `PublicApp.svelte`: pass `pathname + search + hash` to `<Login>` instead of the full URL — this alone fixes the happy path. - `Login.svelte`: `redirectSaml()`, `persistRd()`, and `redirectUser()` all route through the helper, so full URLs from `/a/[...path]` are reduced before being put in `RelayState`/`localStorage`/`goto()`. - `/user/login/+page.svelte`: the same reduction is applied to the resolved `rd` so any stale full-URL value in `localStorage.rd` still navigates to the intended page instead of falling into the cross-origin branch. Backend (EE companion: windmill-ee-private#TBD): - `safe_relay_state_redirect` now reduces a `RelayState` whose origin matches `BASE_URL` to its path before applying the same-origin path safety rules. Bare BASE_URL with no path still falls back to `/user/login` (no useful deep link to honor). - New `same_origin_relative_path` helper + expanded unit tests. Test plan: - [x] Frontend: `vitest run src/lib/logoutRedirect.test.ts` — 9 passed - [x] Backend: `cargo test -p windmill-api ... saml_ee::tests` — 3 passed (`honors_same_origin_relative_path`, `reduces_same_origin_full_url_to_path`, `falls_back_on_open_redirect_attempts`) - [ ] Manual e2e (needs configured SAML IdP — not on local CE): - Unauthenticated visit to `/a/<path>` → click SSO → SAML → land on `/a/<path>` (RelayState now carries the relative path). - IdP that echoes BASE_URL as default → ACS still falls back to `/user/login` (no useful path to honor), but the page no longer hangs: the stale full-URL `localStorage.rd` is reduced to its path and the post-login redirect navigates to it. - Tampered `RelayState` (`//evil.com`, `https://evil.com/x`) → ACS rejects, lands on `/user/login`. * chore: update ee-repo-ref to 3489c243b0e5a8eb0dbc86e90917fbe72843573b This commit updates the EE repository reference after PR #584 was merged in windmill-ee-private. Previous ee-repo-ref: 635ff3eeb8e47bb84d5686942605f67f8f6224b4 New ee-repo-ref: 3489c243b0e5a8eb0dbc86e90917fbe72843573b Automated by sync-ee-ref workflow. --------- Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
f6fcdb5599 |
feat: open ai chat path links in drawers (#9220)
* feat(ai-chat): link workspace paths and show tool item references Detect Windmill paths (u/..., f/...) in assistant messages and render them as clickable pills with the right icon, resolved against a per- workspace cache. Tool execution headers now list the script/flow/app paths referenced in tool parameters as external links. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ai-chat): linkify inline-code paths, refine pill styling - Inline-code spans whose value is exactly a Windmill path now render as a link pill (paths inside larger inline code or fenced blocks stay as code). - Tool-header chips moved to their own row to avoid overflow clipping when the title wraps. - Borderless pills, no default background (hover only), kind icons use the home-page palette (script blue, flow teal, app orange), and the external-link indicator only appears on hover. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ai-chat): linkify variables/resources/triggers + inline drawer - Workspace item registry now also lists variables, resources, schedules, and all 10 trigger kinds; resource wins over variable on path collisions (Windmill auto-creates a companion variable for every resource). - Pill icons delegated to the canonical RowIcon component so each kind matches the home-page styling (script blue, flow teal, app orange, resource boxes, schedule calendar, etc.). - Pill href includes the hash fragment each list page already consumes (#/resource/<path>, #<path> for variables/schedules/triggers), so opening the link puts the user on the list page with the matching editor drawer already open. - For variable and resource pills, a hover-revealed side-panel button opens (or toggles closed) the editor drawer inline next to the chat, without navigating away. VariableEditor and ResourceEditorDrawer gain a closeDrawer() export and forward their close event so the host can drive toggling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: simplify ai chat workspace item links * refactor: keep ai chat path linkification only * perf: avoid eager ai chat path cache loads * refactor: simplify ai chat path linking * feat: open ai chat path links in drawers * refactor: homogenize workspace item kinds * fix: toggle ai chat item drawer * refactor: trim ai chat path cache * fix: cancel ai chat drawer reopen --------- Co-authored-by: Guilhem Lemouel <guilhemlemouel@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> |
||
|
|
cc141effa3 |
fix(frontend): flow progress bar for early-stop completion and error handler (WIN-1961) (#9254)
Two FlowProgressBar bugs: 1. stop_after_if (without 'label as skipped') ends the flow with step < modules.length, leaving the bar at <100% with a spinner. 2. failure_module execution drives step past modules.length, so the bar overflows past 100% and never reflects the error. The fix clamps progress to the failed module when the error handler runs, and forces 100% Done when the flow completed successfully but stopped early. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
31a046973a |
feat(chat): visual redesign — input, streaming indicator, scroll polish (#9232)
* feat(chat): visual redesign — input, streaming indicator, scroll polish
Visual refresh of the AI chat surface used in both the global right-side
panel (Cmd+L) and inline editor panels. No new features, no system-prompt
or tool changes, no sessions code.
Input redesign
- Default textarea to `rows={1}` and autosize as the user types.
- Drop the separate Send button row in favour of a single
`<Button variant="subtle" iconOnly>` overlaid bottom-right of the
textarea — `ArrowUp` when idle (disabled until text is typed),
`Square` when loading (cancels via `aiChatManager.cancel()`).
- Padding `!pl-3 !pr-10 !py-2` keeps text clear of the floating button.
- Top spacing `mt-1` on the outer wrapper restores breathing room
above the input (lost when the old @-button row was removed).
- Context chip row renders only when something is selected.
- `ContextTextarea` `min-height: 2.25rem` so the empty textarea
collapses to a tight single line.
Streaming indicator
- Replace the old floating "Stop" button with a sticky-bottom badge
showing three animated typing dots and a formatted wall-clock
(`Xs`, `Xm Ys`, `Xh Ym`) — driven by `aiChatManager.loading`.
- CSS keyframes `chat-typing` with staggered animation-delay for the
wave effect.
Scroll behaviour
- Replace `onwheel`-based stick-to-bottom detection with `onscroll`
position check (8px threshold). Auto-scroll re-engages when the
user scrolls back near the tail.
- Smooth scroll → `behavior: 'auto'` so token-append doesn't race
the animation.
- New `enableAutomaticScroll` method on `AIChatManager`, complement to
the existing `disableAutomaticScroll`.
- Floating "scroll to latest" arrow (`ArrowDown` design-system Button,
`transition:fade`, `unifiedSize="xs"`, `iconOnly`) appears once the
user scrolls >200px above the tail; click re-enables auto-scroll
and jumps to bottom. Centered horizontally over the scroll viewport.
Message rendering
- Assistant markdown tuned: `prose-headings:font-medium`, h1 `text-sm`,
h2+ `text-xs`, plus `prose-p:text-xs prose-li:text-xs
prose-code:text-xs prose-pre:text-xs`. Stops AI replies blasting
oversized titles.
- Fenced code blocks shrink to `!text-xs` on the `not-prose` wrapper
so fenced code matches inline code at 12px.
- User-message wrapper switches to symmetric spacing (`mt-4 mb-6`)
with a new `isLast` prop that adds `!mb-12` to the latest message
— breathing room between the last bubble and the input without
affecting siblings.
Layout / padding
- Wide-layout messages tightened to `px-7` (was `px-8`); input outer
to `px-6`. The input box sits a touch left of the message text;
textarea's own `!pl-3` brings the typed text back into alignment
with the messages above.
Other
- `AIChatManager` class is now exported (was private). Allows callers
to type a `getContext<AIChatManager>('aiChatManager')` provider
override. No behaviour change for the global singleton.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(chat): restore @ picker, extract typing indicator and shared helpers
* feat(chat): cap non-wide chat at max-w-2xl, add side padding, drop input top border
* feat(chat): esc cancels active generation, tone down snapshot row
* fix(chat): only draw tool-content fade when content actually overflows
* style(chat): tighten non-wide side padding (px-4/px-3 -> px-3/px-2)
* fix(chat): inline ⌘K shows dots + stop button, swallow programmatic scroll events
* fix(chat): keep scroll-to-latest fresh during cooldown; ResizeObserver for tool-content fade
* fix(chat): contain wide content - propagate showFade, table scroll, bubble + inline code wrapping
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
d08f72b3e1 |
feat(vault): optional KV secret path prefix setting (WIN-1960) (#9249)
* feat(vault): add optional KV secret path prefix setting (WIN-1960) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 0189ba6504fd70eb4929e4881d624d48efd14aee This commit updates the EE repository reference after PR #581 was merged in windmill-ee-private. Previous ee-repo-ref: e32e8d6483550c67897e09b6f900dff1034bdae8 New ee-repo-ref: 0189ba6504fd70eb4929e4881d624d48efd14aee Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
285a78752a |
feat(indexer): observability for unavailable search index (WIN-1956) (#9239)
* [ee] feat(indexer): observability for unavailable search index A user hit `Not found: There is no index reader to search from` when searching service logs and could not tell whether it was a config error or a bug, and asked for visibility into the indexer status (WIN-1956). Backend (EE companion PR): - Replace the opaque error with an actionable message explaining the likely causes (indexer disabled, still starting, or blocked acquiring the indexer lock) and pointing to the status panel. - Add a coarse `state` (running | stale | never_started) to `/indexer/status`, derived from the lock row, distinguishing a never-configured indexer from a stale/blocked one. Frontend: - Instance Settings > Indexer now shows Running / Stale / Not started with a tooltip explaining what to check for each. - Service logs search now catches failures and shows an inline, actionable Alert instead of an unhandled rejection. Fixes WIN-1956 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 017d36418a65ce5c840c502e3174df0c393612ba This commit updates the EE repository reference after PR #580 was merged in windmill-ee-private. Previous ee-repo-ref: 18b7e1b30a1ff582c4a072580bbb8aec34e22cdc New ee-repo-ref: 017d36418a65ce5c840c502e3174df0c393612ba Automated by sync-ee-ref workflow. * fix(indexer): address review nits - IndexerMemorySettings: older backends without `state` reporting `is_alive: false` now show "Stopped" (red) again instead of falling through to "Unknown" (codex/cubic P2). - ServiceLogsInner: clear stale logs/counts on a failed search so the error isn't shown alongside results from a previous query (codex P2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
f51b51a9a1 |
fix(frontend): open customer portal in popup synchronously to bypass Safari blocker (#9242)
* fix(frontend): open customer portal in popup synchronously to bypass Safari blocker Safari blocks window.open() called after an await because it loses the user-gesture context. Open a blank tab synchronously on click, then assign location.href once the portal URL resolves. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(backend): wire dev_override feature flag in backend crate Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ba6fb7021b |
feat: export audit logs to a dedicated object store folder (#9207)
* feat: export audit logs to dedicated object store folder * fix: gap-free audit export via snapshot-xmin gate and stable object keys * test: add integration test for audit log object store exporter * fix: cursor audit export on snapshot xmin to prevent id-leapfrog loss * fix: protect audit s3 checkpoint from config sync and bound export interval * fix: anchor audit s3 checkpoint at enable time to not skip first-window rows * fix: anchor first audit export at the enable transaction's xid * fix: use epoch timestamp floor on first audit export run to not drop old backlog * fix: anchor audit export at startup for env-var enable path * fix: anchor audit export via enabling-txn snapshot xmin trigger * fix: bound the bootstrap audit export to MAX_XID_INTERVAL per tick * refactor: store audit export cursor in background_task_state, add status endpoint * docs: align store_audit_logs_s3 setting text with the actual enable-boundary contract * [ee] refactor: move audit s3 export core logic to EE, gate on Enterprise license * chore: update ee-repo-ref to ec3cd353245e1cdf6a290528dbd7f2ac2498386c This commit updates the EE repository reference after PR #579 was merged in windmill-ee-private. Previous ee-repo-ref: 4ffc6d5f874e64d7dc4a147b4e73baa6c44867a5 New ee-repo-ref: ec3cd353245e1cdf6a290528dbd7f2ac2498386c Automated by sync-ee-ref workflow. --------- Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
bd062825a2 |
fix: scope VSCode webview clipboard paste to focused editor (#9221)
* fix: scope SimpleEditor webview paste to focused editor instance * fix: scope webview clipboard paste to focused editor instance Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: bail on missing selection instead of pasting at document start Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: hide SimpleEditor paste sink input from a11y tree and tab order Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8c1f6ccc5d | fix: prevent undefined user flickering in multiplayer presence list (#9231) | ||
|
|
c8ab030aa4 |
chore(main): release 1.704.1 (#9226)
* chore(main): release 1.704.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
9c6deec8ff |
avoid stale localStorage rd when SAML RelayState carries the deep link (#9228)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
89306d7dbc |
fix: honor SAML RelayState to redirect to deep link after SSO login (#9225)
* fix: honor SAML RelayState to redirect to deep link after SSO login Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump ee-repo-ref for SAML RelayState validator test Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to a3fefe85f5f2f52bb473fa47acc9efa8fd0b2206 This commit updates the EE repository reference after PR #577 was merged in windmill-ee-private. Previous ee-repo-ref: 445a22536b1a6c342cde0baa6fbca9e25092f94b New ee-repo-ref: a3fefe85f5f2f52bb473fa47acc9efa8fd0b2206 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
11c03ca14e |
chore(main): release 1.704.0 (#9210)
* chore(main): release 1.704.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
49ebf6f8ba |
feat: add global chat selected context (#9216)
* feat: add global chat selected context * refactor: store workspace context as references * fix: refresh db context after global mode |
||
|
|
f965512c7a |
feat: add global ask user question tool (#9217)
* feat: add global ask user question tool * feat: add keyboard navigation to user questions * feat: simplify ask user question answers * fix: disable strict mode for optional tool schemas * fix: scope ask question keyboard events * fix: clean up ask question display state |
||
|
|
2e05bdd73a |
feat: show job status in favicon on the run page (#9206)
* feat: show job status in favicon on the run page * test: cover getJobStatusKind favicon status mapping * chore: remove favicon unit tests Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
156eb0b045 |
fix: resolve absolute-path imports in monaco ts editor (#9213)
* fix: resolve absolute-path imports in monaco ts editor * fix: dispose absolute-path extra libs on editor teardown and reset * fix: skip late ata local-file callbacks after editor teardown |
||
|
|
fec4008696 |
fix: preserve ai reasoning content (#9208)
* fix: preserve ai reasoning content * fix: avoid text-only reasoning replay * feat: add deepseek ai eval models |
||
|
|
4e91f83b8f |
chore(main): release 1.703.3 (#9200)
* chore(main): release 1.703.3 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
e1df6b45e9 | chore: remove alpha/beta warnings from tested frontend features (#9196) | ||
|
|
8bc2295b94 |
fix(mcp): validate oauth dynamic client registration redirect_uris (#9197)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
20719b4731 |
chore(main): release 1.703.2 (#9195)
* chore(main): release 1.703.2 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
25172bdc28 |
sidebar rendering expanded-but-empty at sub-pixel widths near 768px (#9191)
* fix: sidebar menu rendering expanded-but-empty near 768px width The desktop sidebar branch is gated by JS (`innerWidth < 768`), but its width was set only via Tailwind `md:` classes (`@media (min-width:768px)`). `window.innerWidth` rounds fractional viewport widths, so at e.g. 767.8px JS rounds to 768 and renders the desktop sidebar, while the CSS media query does not match and no width class applies — leaving the sidebar shell expanded with no width/content. Drop the now-redundant `md:` prefix so width tracks the JS branch decision. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: align content offset breakpoint with sidebar JS gate The sidebar width now follows the JS innerWidth gate, but the main content left-offset in AiChatLayout still used the `md:` CSS media query, leaving the two breakpoints out of sync in the same sub-pixel band. Pass an `isMobile` flag from the layout (mirroring the sidebar's `innerWidth < 768` condition) and gate the content padding on it with unprefixed classes so sidebar width and content offset always flip together. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
fa090f3081 |
chore(main): release 1.703.1 (#9182)
* chore(main): release 1.703.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
302ce58e98 |
harden UI builder artifact bootstrap with verified pinned metadata (#9189)
* feat: harden UI builder artifact bootstrap with verified pinned metadata * fix: emit tab-indented artifact json to match prettier config * refactor: rewrite artifact json with node instead of python * refactor: simplify bootstrap to flat script, drop test scaffolding |
||
|
|
e3a3dbb89c |
chore(main): release 1.703.0 (#9170)
* chore(main): release 1.703.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
b7bc9b44b4 | chore(frontend): update vite to 8.0.13 (#9179) | ||
|
|
d48d61cc79 |
feat(otel-tracing-proxy): configurable tracing MITM NO_PROXY hosts (#9169)
* feat(otel-tracing-proxy): configurable NO_PROXY hosts * refactor(otel-tracing-proxy): NO_PROXY only governs job-side bypass * fix(otel-tracing-proxy): restore empty NO_PROXY default * test(otel-tracing-proxy): unit tests for NO_PROXY normalization * fix(otel-tracing-proxy): gate normalize_no_proxy_hosts to EE features |
||
|
|
7f589a8c7d |
chore(main): release 1.702.1 (#9166)
* chore(main): release 1.702.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
90f494975d |
chore(main): release 1.702.0 (#9160)
* chore(main): release 1.702.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
e5286f4607 |
feat: include service accounts in instance settings users list (#9157)
* feat: include service accounts in instance settings users list Service accounts (workspace-scoped, no password row) now appear in the superadmin users list with a Bot icon, workspace badge, and a link to manage them in the workspace settings. Role is locked to Operator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: update sqlx offline cache Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: use composite key for users each block Service accounts can share emails across workspaces, so key by email + workspace_id to avoid Svelte each_key_duplicate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4f3a1e3109 |
chore(main): release 1.701.0 (#9131)
* chore(main): release 1.701.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
d666e8431c |
feat: read-only flag on API tokens (#9144)
* feat: read-only flag on API tokens, orthogonal to scopes Add a per-token `read_only` boolean set at creation time. When true, the token can only call HTTP methods classified as Read (GET/HEAD/OPTIONS). Mutating methods and job-run actions are rejected with 403, regardless of which scopes are attached. Surfaced as a prominent toggle in the standard token-creation flow and a discreet `2xs` toggle in MCP mode (where users often want write access, so we don't bias them toward enabling it). MCP enforcement: read-only tokens hide all script/flow/hub tools from `list_tools` and only see endpoint tools whose method is GET, and the runner rejects `call_tool` on anything mutating. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: review fixes for read-only token flag - Exempt /api/mcp/* and /mcp/* paths from the read-only middleware check. MCP transport runs over POST (streamable HTTP / SSE), so otherwise the middleware would 403 every MCP request before the runner could enforce read-only at the tool-call level. - Tighten is_endpoint_read_only to GET only, matching the read_only_hint that create_endpoint_annotations actually emits. - Add unit test for check_read_only_for_route covering GET/HEAD/OPTIONS, mutating methods, and run paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump ee-repo-ref to read-only-trigger-toggle Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(frontend): make read-only toggle discreet in both modes Match the MCP-mode treatment in standard mode: text-tertiary, 2xs, shared "Read-only" label. The tooltip switches per mode so the explanation still fits the context. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(frontend): gate read-only toggle behind Limit token permissions The read-only toggle now only shows when the user has limited the token's scopes (standard mode) or in MCP mode (which always picks an MCP scope). Turning the limit off also resets read-only so it doesn't silently stick. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(frontend): hide incompatible MCP tools when read-only is on When the read-only toggle is on in MCP mode: - Endpoint badges and the custom-mode endpoint MultiSelect filter to GET. - Already-selected non-GET endpoints are pruned from the scope. - The scripts/flows preview is replaced with a note explaining they're hidden (the runner already rejects script/flow runs for read-only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(frontend): place read-only toggle at top of limited scope area The previous gate required at least one scope to be picked before the read-only toggle appeared, which made it look missing while the user was still building their scope list. Move the toggle inside ScopesPicker: - Standard mode: sits directly under the "Limit token permissions" toggle whenever Limit is on, before the scope selector. - MCP mode: sits at the top of the MCP scope block. readOnly is now $bindable on ScopesPicker so CreateToken still owns the value. The auto-reset on un-limit moves into ScopesPicker too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(frontend): nest read-only toggle inside the scope list card Place the read-only toggle at the top of the scope list (between the Selected Scopes summary and the bordered domain list) via a new optional topSlot snippet on ScopeSelector. Keeps ScopeSelector decoupled from read-only specifics; ScopesPicker fills the slot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 9bc8160be50b3e57a60daf4e1b71c389a6e02b8a This commit updates the EE repository reference after PR #571 was merged in windmill-ee-private. Previous ee-repo-ref: f53d26e6685dfd60bfa67686fbd7358169cfd130 New ee-repo-ref: 9bc8160be50b3e57a60daf4e1b71c389a6e02b8a Automated by sync-ee-ref workflow. * fix: address CI review for read-only token flag - P1 (Codex): narrow the MCP middleware exemption from "any /api/mcp/*" to just the streamable HTTP transport endpoints (/api/mcp/gateway, /api/mcp/w/{ws}/{mcp,sse,list_tools}). Without this, a read-only token could POST /api/mcp/gateway/oauth/server/approve and mint a follow-on non-read-only MCP token via the OAuth code/token exchange. - P2 (Claude/cubic): fix test comment/assertion mismatch — the run-path assertion now exercises GET (which is what the RUN_PATH_ACTIONS elevation comment describes) in addition to POST. Add a regression assertion for /api/mcp/gateway/oauth/server/approve. - P2 (cubic): short-circuit script/flow/hub-script/resource fetches in MCP list_tools when read_only is on — they would only be discarded below, so skipping the DB and resource fan-out is pure win. - P2 (cubic): when scopes are pre-supplied via the CreateToken prop, the ScopesPicker isn't rendered, which previously hid the read-only toggle entirely. Render it next to the pre-supplied scopes display. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
110bef0a6e |
fix: Allow devops role to use all_workspaces runs filter in admins workspace (#9153)
Co-authored-by: windmill-internal-app[bot] <1429786+windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
c5092069cb |
fix: align script path existence check with deploy logic; hide Delete for non-admin (#9152)
- exists_script_by_path now filters archived = false, matching the conflict check in create_script_internal. Previously the frontend blocked creating a new script at a path occupied only by archived scripts, even though renaming to that same path was allowed. - Hide the Delete entry in the script details "..." menu unless the user is admin. The backend delete_script_by_hash already requires admin, so non-admins would always see an error after clicking. |
||
|
|
dd19e52a84 |
perf(dynselect): only retrigger when helper args actually change (#9148)
* perf(dynselect): only retrigger when helper-script args actually change Parse the inline helper's signature with the existing WASM parser and restrict the form-arg diff to keys the helper actually consumes. Typing into unrelated fields no longer queues a dynselect job every second. Falls back to the previous full-args comparison when the helper is deployed or parsing fails. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(dynselect): avoid double helper-script fetch on mount usePromise defaults to loadInit=true, so refresh() ran before the JobLoader child was bound (firing a no-op pending promise) and the $effect then fired a second refresh once the bind:this resolved. Disable loadInit so the effect owns the single first call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(dynselect): use parser directly instead of inferArgs inferArgs mutates a Schema object we never use and goes through a shared cache; when fed an empty schema for non-main entrypoints the caller cannot reliably read back the resulting properties. Add parseEntrypointArgs that just runs the parser and returns the parameter name Set (or undefined when unknown / unsupported / has rest args / function not found). DynamicInput uses that and keeps the previous params in flight while the next parse is computing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(dynselect): support deployed helpers in smart retrigger Add getHelperEntrypointArgs which dispatches on HelperScript.source: inline parses immediately; deployed fetches the script (or the flow's inline dyn-select code) once and caches per (workspace, kind, path, entrypoint). Without this the /scripts/get/* run view fell back to the full-args comparison and still retriggered on unrelated fields. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(dynselect): zero-arg helpers report empty deps, not unknown Codex review flagged that a valid zero-parameter entrypoint was being treated as "couldn't determine signature" and falling back to the full-args comparison. Distinguish "function found with no params" from "function not found" via the parser's auto_kind field — only the latter sets it, so empty args + auto_kind=null means a real zero-arg helper and we return an empty Set (no retrigger on unrelated fields). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d243e0cde8 | align global flow tool arguments (#9146) |