diff --git a/frontend/src/lib/components/copilot/AskAiButton.svelte b/frontend/src/lib/components/copilot/AskAiButton.svelte index 95ff47fb52..a141ee3125 100644 --- a/frontend/src/lib/components/copilot/AskAiButton.svelte +++ b/frontend/src/lib/components/copilot/AskAiButton.svelte @@ -5,7 +5,6 @@ import { AIBtnClasses } from './chat/AIButtonStyle' import { prefersSessionHandoff } from './chat/global/gate' import { startSessionWithPrompt } from '$lib/components/sessions/sessionSwitch.svelte' - import { userStore } from '$lib/stores' interface Props { label?: string initialInput?: string @@ -16,7 +15,7 @@ // The label stays short ("Ask AI") for the search bar's inline row; the hover // text is where "new AI session" fits. - const handsOffToSession = $derived(prefersSessionHandoff($userStore?.operator)) + const handsOffToSession = $derived(prefersSessionHandoff()) export function onClick() { // No item to preview here — this carries a question, not a target — so the diff --git a/frontend/src/lib/components/copilot/chat/global/gate.ts b/frontend/src/lib/components/copilot/chat/global/gate.ts index d1dafe29ed..e389283d8a 100644 --- a/frontend/src/lib/components/copilot/chat/global/gate.ts +++ b/frontend/src/lib/components/copilot/chat/global/gate.ts @@ -29,10 +29,9 @@ export function isGlobalAiEnabled(): boolean { * Whether an AI entry point hands off to a session instead of driving the docked * chat. Deliberately the same condition as the root layout's `disableAi`, so a * caller falling back on `false` always has a mounted pane to fall back to. - * Operators keep that pane (`/sessions` refuses them) until the operator chat ships. */ -export function prefersSessionHandoff(isOperator: boolean | undefined): boolean { - return isGlobalAiEnabled() && !isOperator +export function prefersSessionHandoff(): boolean { + return isGlobalAiEnabled() } /** Clear the opt-out, then hard-reload so every gated site re-reads it. */ diff --git a/frontend/src/lib/components/flows/FlowEditor.svelte b/frontend/src/lib/components/flows/FlowEditor.svelte index a9c3a40e8a..9d98d0535e 100644 --- a/frontend/src/lib/components/flows/FlowEditor.svelte +++ b/frontend/src/lib/components/flows/FlowEditor.svelte @@ -40,14 +40,9 @@ import FlowPanelPlacementPicker from './common/FlowPanelPlacementPicker.svelte' import { prefersSessionHandoff } from '../copilot/chat/global/gate' import { openSourceInSession } from '$lib/components/sessions/sessionSwitch.svelte' - import { - useOperatingUser, - useOperatingWorkspace - } from '$lib/components/operatingWorkspace.svelte' + import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte' const operatingWorkspace = useOperatingWorkspace() - const operatingUser = useOperatingUser() - const actingUser = $derived(operatingUser.current) const { flowStore, selectionManager, pathStore, opWorkspace } = getContext('FlowEditorContext') // Flow paths repeat across workspaces, and a session keeps every tab it has visited alive, so two @@ -408,11 +403,7 @@ // contain. Hand it to a session opened on that step rather than the // docked chat, which sessions leave unmounted. Sent on arrival: the // user already said what they wanted in the description field. - if ( - !sessionScopedManager && - sessionOpen && - prefersSessionHandoff(actingUser?.operator) - ) { + if (!sessionScopedManager && sessionOpen && prefersSessionHandoff()) { void openSourceInSession(sessionOpen, { previewParams: { selected: detail.moduleId }, seedPrompt: stepInstructionsPrompt(detail.moduleId, detail.instructions), diff --git a/frontend/src/lib/components/home/HomeAIChat.svelte b/frontend/src/lib/components/home/HomeAIChat.svelte index 62479b70d1..77eaa8dc07 100644 --- a/frontend/src/lib/components/home/HomeAIChat.svelte +++ b/frontend/src/lib/components/home/HomeAIChat.svelte @@ -137,13 +137,10 @@ // unlocked, so the far commoner unlocked workspace never pops the composer in mid-load. let runOnlyWorkspace = $derived(isRuleActive('DisableDirectDeployment')) - // The composer hands off to /sessions, which refuses operators and users opted out of the - // sessions beta — so hide it from them (the prompt would be silently dropped) while the - // AI-independent CLI/MCP row below stays. + // The composer hands off to /sessions, so users opted out of the sessions beta don't get it + // (the prompt would be silently dropped) while the AI-independent CLI/MCP row below stays. let showComposer = $derived( - prefersSessionHandoff($userStore?.operator) && - !runOnlyWorkspace && - !$copilotInfo.workspaceDisabled + prefersSessionHandoff() && !runOnlyWorkspace && !$copilotInfo.workspaceDisabled ) // The hero's margins and centered column are for the full block. The lone button row left diff --git a/frontend/src/lib/components/sessions/OpenInSessionButton.svelte b/frontend/src/lib/components/sessions/OpenInSessionButton.svelte index 00b784138f..01db5984d3 100644 --- a/frontend/src/lib/components/sessions/OpenInSessionButton.svelte +++ b/frontend/src/lib/components/sessions/OpenInSessionButton.svelte @@ -43,10 +43,6 @@ import { copilotInfo } from '$lib/aiStore' import { sendUserToast } from '$lib/toast' import { openSourceInSession } from './sessionSwitch.svelte' - import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte' - - const operatingUser = useOperatingUser() - const actingUser = $derived(operatingUser.current) let { source, @@ -80,13 +76,8 @@ // SessionEditorTarget / the session wrapper); iframe preview tabs are not // the top window. const inSessionPanel = !!getContext('aiChatManager') || (BROWSER && window.self !== window.top) - // prefersSessionHandoff carries the operator clause: the sessions page refuses - // them, so an entry point on a page they can reach (Runs, the trigger lists) - // would only route them into that refusal. const show = $derived( - !inSessionPanel && - !!(source?.target || source?.page) && - prefersSessionHandoff(actingUser?.operator) + !inSessionPanel && !!(source?.target || source?.page) && prefersSessionHandoff() ) // Not $state: only read inside open() as a re-entrancy latch, never rendered. diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte index 2403299e71..2873ed0f84 100644 --- a/frontend/src/routes/(root)/(logged)/+layout.svelte +++ b/frontend/src/routes/(root)/(logged)/+layout.svelte @@ -68,7 +68,10 @@ getFavoriteHref, getFavoriteLabel } from '$lib/components/sidebar/FavoriteMenu.svelte' - import { parseSuperadminSettingsHash, USER_SETTINGS_HASH } from '$lib/components/sidebar/settings' + import { + parseSuperadminSettingsHash, + USER_SETTINGS_HASH + } from '$lib/components/sidebar/settings' import { isCloudHosted } from '$lib/cloud' import { PanelLeft, @@ -300,11 +303,7 @@ // Session mode is route-derived: the rail shows the sessions sidebar on the // /sessions page and the workspace navigation everywhere else. The switch // (SessionModeSwitch) just navigates in and out of that route. - // The route decides, except for an operator: /sessions refuses them, so handing their - // sidebar over to a session list would offer what the page behind it is declining. - let sessionMode = $derived( - page.url.pathname.startsWith(base + '/sessions') && !$userStore?.operator - ) + let sessionMode = $derived(page.url.pathname.startsWith(base + '/sessions')) // Session mode points the bottom settings entry at the open session's own // workspace. An unsent draft hasn't committed one yet, so it falls back to // the family root. @@ -325,14 +324,8 @@ const globalAiEnabled = isGlobalAiEnabled() // A workspace that hid the assistant (`ai_config.copilot_disabled`) loses both entry // points: the Workspace ⇄ Sessions switch and the legacy Ask-AI button. - const sessionsSwitchShown = $derived( - globalAiEnabled && !$copilotInfo.workspaceDisabled && !$userStore?.operator - ) - // The legacy pane's entry point. An operator keeps that pane whatever the beta gate says - // (`disableAi` above), so the sidebar must still offer them a way into it. - const askAiShown = $derived( - (!globalAiEnabled || $userStore?.operator) && !$copilotInfo.workspaceDisabled - ) + const sessionsSwitchShown = $derived(globalAiEnabled && !$copilotInfo.workspaceDisabled) + const askAiShown = $derived(!globalAiEnabled && !$copilotInfo.workspaceDisabled) if (page.status == 404) { goto('/user/login') @@ -891,7 +884,7 @@ $effect(() => { const ws = $workspaceStore const ready = sessionState.hydrated && $usersWorkspaceStore !== undefined - if (globalAiEnabled && ready && ws && !$userStore?.operator) { + if (globalAiEnabled && ready && ws) { untrack(() => restoreSessionBackups(ws)) } }) @@ -1488,9 +1481,9 @@ { const user = get(userStore) const workspace = get(workspaceStore) // Neither is known yet on a cold load of this URL, which then opens the editor. - if (!user || !workspace || !prefersSessionHandoff(user.operator)) return undefined + if (!user || !workspace || !prefersSessionHandoff()) return undefined if ((url.searchParams.get('workspace') ?? workspace) !== workspace) return undefined if (get(copilotWorkspace) !== workspace) await loadCopilot(workspace) if (get(copilotWorkspace) !== workspace || !get(copilotInfo).enabled) return undefined diff --git a/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte index 9ed2ca296a..d4aa97b2b4 100644 --- a/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte @@ -529,7 +529,7 @@ policy: result.policy } const workspace = $workspaceStore - if (mode === 'ai' && prefersSessionHandoff($userStore?.operator) && workspace) { + if (mode === 'ai' && prefersSessionHandoff() && workspace) { // The session loads the app by path, so the picked setup is saved before it // opens. `new_draft` tells it the app is still the starter template. try { diff --git a/frontend/src/routes/(root)/(logged)/sessions/+page.svelte b/frontend/src/routes/(root)/(logged)/sessions/+page.svelte index 97ed4b95b6..ef358378f2 100644 --- a/frontend/src/routes/(root)/(logged)/sessions/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/sessions/+page.svelte @@ -44,7 +44,7 @@ import { withWorkspaceParam } from '$lib/components/sessions/sessionMode.svelte' import { enterSessionMode } from '$lib/components/sessions/sessionSwitch.svelte' import type { SessionPreviewTabs } from '$lib/components/sessions/sessionPreviewTabs.svelte' - import { userStore, userWorkspaces, usersWorkspaceStore, workspaceStore } from '$lib/stores' + import { userWorkspaces, usersWorkspaceStore, workspaceStore } from '$lib/stores' import { getOrCreateRuntime, getRuntime, @@ -205,7 +205,7 @@ // load. `recovering` also guards re-entry: recovery mutates the session list // this effect tracks, while the URL that would stop it only updates on `goto`. $effect(() => { - if (embedded || !globalEnabled || $userStore?.operator) return + if (embedded || !globalEnabled) return if (!sessionState.hydrated || recovering) return // A deliberate delete removes the open session ahead of its own navigation. // Claiming that gap would take over the URL and tell the user the session @@ -1021,24 +1021,6 @@ }}>Open sessions - {:else if $userStore?.operator} - -
-

AI Sessions are not available for operators

-

Use the Ask AI chat instead.

- -
{:else if !globalEnabled}