diff --git a/apps/desktop/src/renderer/hooks/useAcpChatEnabled/index.ts b/apps/desktop/src/renderer/hooks/useAcpChatEnabled/index.ts index fffa5d020ef..a2550a9abef 100644 --- a/apps/desktop/src/renderer/hooks/useAcpChatEnabled/index.ts +++ b/apps/desktop/src/renderer/hooks/useAcpChatEnabled/index.ts @@ -1,5 +1 @@ -export { - type AcpChatAvailability, - useAcpChatEnabled, - useAwaitAcpChatEnabled, -} from "./useAcpChatEnabled"; +export { useAcpChatEnabled } from "./useAcpChatEnabled"; diff --git a/apps/desktop/src/renderer/hooks/useAcpChatEnabled/useAcpChatEnabled.ts b/apps/desktop/src/renderer/hooks/useAcpChatEnabled/useAcpChatEnabled.ts index e8394ba6f78..a88e6ae7d32 100644 --- a/apps/desktop/src/renderer/hooks/useAcpChatEnabled/useAcpChatEnabled.ts +++ b/apps/desktop/src/renderer/hooks/useAcpChatEnabled/useAcpChatEnabled.ts @@ -1,39 +1,5 @@ -import { FEATURE_FLAGS } from "@superset/shared/constants"; -import { useFeatureFlagEnabled, usePostHog } from "posthog-js/react"; -import { useCallback } from "react"; import { useAcpChatStore } from "renderer/stores/acp-chat"; -export type AcpChatAvailability = "enabled" | "disabled" | "resolving"; - -export function useAcpChatEnabled(): AcpChatAvailability { - const flag = useFeatureFlagEnabled(FEATURE_FLAGS.ACP_CHAT); - const chosen = useAcpChatStore((state) => state.enabled); - if (chosen === false) return "disabled"; - if (flag === undefined) return "resolving"; - return flag ? "enabled" : "disabled"; -} - -const RESOLVE_TIMEOUT_MS = 2_000; - -export function useAwaitAcpChatEnabled(): () => Promise { - const availability = useAcpChatEnabled(); - const posthog = usePostHog(); - - return useCallback(() => { - if (availability !== "resolving") - return Promise.resolve(availability === "enabled"); - if (!posthog) return Promise.resolve(false); - return new Promise((resolve) => { - let unsubscribe: (() => void) | undefined; - const settle = (enabled: boolean) => { - clearTimeout(timer); - unsubscribe?.(); - resolve(enabled); - }; - const timer = setTimeout(() => settle(false), RESOLVE_TIMEOUT_MS); - unsubscribe = posthog.onFeatureFlags(() => { - settle(posthog.isFeatureEnabled(FEATURE_FLAGS.ACP_CHAT) === true); - }); - }); - }, [availability, posthog]); +export function useAcpChatEnabled(): boolean { + return useAcpChatStore((state) => state.enabled); } diff --git a/apps/desktop/src/renderer/hooks/useCreateAgentSession/useCreateAgentSession.ts b/apps/desktop/src/renderer/hooks/useCreateAgentSession/useCreateAgentSession.ts index 54dedf18aeb..826677708ea 100644 --- a/apps/desktop/src/renderer/hooks/useCreateAgentSession/useCreateAgentSession.ts +++ b/apps/desktop/src/renderer/hooks/useCreateAgentSession/useCreateAgentSession.ts @@ -4,7 +4,7 @@ import { errorMessage } from "@superset/i18n/errors"; import { toast } from "@superset/ui/sonner"; import { useNavigate } from "@tanstack/react-router"; import { useRef, useState } from "react"; -import { useAwaitAcpChatEnabled } from "renderer/hooks/useAcpChatEnabled"; +import { useAcpChatEnabled } from "renderer/hooks/useAcpChatEnabled"; import { useV2AgentChoices } from "renderer/hooks/useV2AgentChoices"; import { AGENT_STORAGE_KEY } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/types"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; @@ -25,7 +25,7 @@ export function useCreateAgentSession() { const { machineId, activeHostUrl } = useLocalHostService(); const { agents } = useV2AgentChoices(activeHostUrl); const { submit } = useWorkspaceCreates(); - const awaitAcpChatEnabled = useAwaitAcpChatEnabled(); + const acpChatEnabled = useAcpChatEnabled(); const busy = useRef(false); const [isPending, setIsPending] = useState(false); @@ -65,8 +65,8 @@ export function useCreateAgentSession() { try { const openAsChat = options.preferChat === true && - Boolean(acpHarnessForPreset(presetOf(choice))) && - (await awaitAcpChatEnabled()); + acpChatEnabled && + Boolean(acpHarnessForPreset(presetOf(choice))); const { workspaceId, completed } = submit({ hostId: machineId, snapshot: { diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/RunInWorkspacePopoverV2/RunInWorkspacePopoverV2.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/RunInWorkspacePopoverV2/RunInWorkspacePopoverV2.tsx index 721084b0c7a..270aa5fd1cb 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/RunInWorkspacePopoverV2/RunInWorkspacePopoverV2.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/RunInWorkspacePopoverV2/RunInWorkspacePopoverV2.tsx @@ -21,6 +21,7 @@ import { LuBox } from "react-icons/lu"; import { AgentSelect } from "renderer/components/AgentSelect"; import { useRecentProjects } from "renderer/hooks/host-projects/useRecentProjects"; import { useHostUrl } from "renderer/hooks/host-service/useHostTargetUrl"; +import { useAcpChatEnabled } from "renderer/hooks/useAcpChatEnabled"; import { useActiveOrganizationId } from "renderer/hooks/useActiveOrganizationId"; import { useCloudAgentChoices } from "renderer/hooks/useCloudAgentChoices"; import { useSelectedHostProjectIds } from "renderer/hooks/useSelectedHostProjectIds"; @@ -107,6 +108,7 @@ export function RunInWorkspacePopoverV2({ environments.find((environment) => environment.id === environmentId) ?? environments[0]; const createCloudWorkspace = cloudTrpc.cloudWorkspace.create.useMutation(); + const acpChatEnabled = useAcpChatEnabled(); const launchHostUrl = useHostUrl(isCloud ? null : hostId); const setUpProjectIds = useSelectedHostProjectIds(hostId); @@ -262,6 +264,7 @@ export function RunInWorkspacePopoverV2({ : { agent: selectedAgent, prompt: synthesizeTaskPrompt(task).slice(0, 20_000), + surface: acpChatEnabled ? "chat" : "terminal", }), }), ), diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAgentSessionLauncher/useAgentSessionLauncher.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAgentSessionLauncher/useAgentSessionLauncher.ts index ad6928fa9f4..742170c14ee 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAgentSessionLauncher/useAgentSessionLauncher.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAgentSessionLauncher/useAgentSessionLauncher.ts @@ -6,7 +6,7 @@ import { toast } from "@superset/ui/sonner"; import { useWorkspaceClient, workspaceTrpc } from "@superset/workspace-client"; import { useQueryClient } from "@tanstack/react-query"; import { useCallback } from "react"; -import { useAwaitAcpChatEnabled } from "renderer/hooks/useAcpChatEnabled"; +import { useAcpChatEnabled } from "renderer/hooks/useAcpChatEnabled"; import { useTerminalAppearance } from "renderer/hooks/useTerminalAppearance"; import { useV2AgentConfigs, @@ -63,14 +63,14 @@ export function useAgentSessionLauncher({ const { t } = useLingui(); const { mutateAsync: runAgent } = workspaceTrpc.agents.run.useMutation(); const appearance = useTerminalAppearance(); - const awaitAcpChatEnabled = useAwaitAcpChatEnabled(); + const acpChatEnabled = useAcpChatEnabled(); const { hostUrl } = useWorkspaceClient(); const { data: agentConfigs } = useV2AgentConfigs(hostUrl); const queryClient = useQueryClient(); const openAgentChat = useCallback( async (input) => { - if (!(await awaitAcpChatEnabled())) return null; + if (!acpChatEnabled) return null; const configs = await queryClient .ensureQueryData(v2AgentConfigsQueryOptions(hostUrl)) .catch(() => agentConfigs ?? []); @@ -104,7 +104,7 @@ export function useAgentSessionLauncher({ } return { terminalId }; }, - [awaitAcpChatEnabled, queryClient, hostUrl, agentConfigs, store], + [acpChatEnabled, queryClient, hostUrl, agentConfigs, store], ); const openNewChat = useCallback(async () => { diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptChatSessions/fixtures/checks.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptChatSessions/fixtures/checks.tsx index 8d47bcd997e..2a39443dca5 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptChatSessions/fixtures/checks.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptChatSessions/fixtures/checks.tsx @@ -50,7 +50,7 @@ let creates = 0; const changes = new Set<() => void>(); const connectionListeners = new Set<() => void>(); let refs = 0; -let chatAvailability: "enabled" | "disabled" = "enabled"; +let acpChatEnabled = true; type Binding = { terminalId: string; agentId: string; @@ -89,7 +89,7 @@ mock.module("@superset/workspace-client", () => ({ useWorkspaceClient: () => ({ hostUrl }), })); mock.module("renderer/hooks/useAcpChatEnabled", () => ({ - useAcpChatEnabled: () => chatAvailability, + useAcpChatEnabled: () => acpChatEnabled, })); mock.module("renderer/hooks/host-service/useWorkspaceHostUrl", () => ({ useWorkspaceHostUrl: () => hostUrl, @@ -243,7 +243,7 @@ afterEach(() => { reads = 0; outlineReads = 0; creates = 0; - chatAvailability = "enabled"; + acpChatEnabled = true; terminalAgents = new Map(); localStorage.clear(); }); @@ -368,7 +368,7 @@ test("a launch whose stored prompt differs is held back and attached at the dead }); test("turning agent chat off removes the waiting pane instead of leaving it stuck", async () => { - chatAvailability = "disabled"; + acpChatEnabled = false; const pending = await waitingPane(); show(pending.store, "disabled-workspace"); await waitFor(() => expect(pending.store.getState().tabs).toHaveLength(0)); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptChatSessions/useAutoAdoptChatSessions.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptChatSessions/useAutoAdoptChatSessions.ts index 0e8e066d5f4..8dfe4945b48 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptChatSessions/useAutoAdoptChatSessions.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptChatSessions/useAutoAdoptChatSessions.ts @@ -65,8 +65,8 @@ export function useAutoAdoptChatSessions({ isLayoutReady, pendingTimeoutMs = PENDING_CLOUD_CHAT_TIMEOUT_MS, }: UseAutoAdoptChatSessionsArgs): void { - const chatAvailability = useAcpChatEnabled(); - const enabled = chatAvailability === "enabled" && isLayoutReady; + const acpChatEnabled = useAcpChatEnabled(); + const enabled = acpChatEnabled && isLayoutReady; const { hostUrl } = useWorkspaceClient(); const { transport } = useChatWiring(); const queryClient = useQueryClient(); @@ -140,13 +140,13 @@ export function useAutoAdoptChatSessions({ // biome-ignore lint/correctness/useExhaustiveDependencies: tabs re-runs it when a waiting pane is restored useEffect(() => { - if (chatAvailability !== "disabled" || !isLayoutReady) return; + if (acpChatEnabled || !isLayoutReady) return; for (const pending of pendingPanes([store, ...linkedStores])) { pending.store .getState() .closePane({ tabId: pending.tabId, paneId: pending.paneId }); } - }, [chatAvailability, isLayoutReady, store, linkedStores, tabs]); + }, [acpChatEnabled, isLayoutReady, store, linkedStores, tabs]); useEffect(() => { const result = sessionsQuery.data; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useConsumeAutomationRunLink/useConsumeAutomationRunLink.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useConsumeAutomationRunLink/useConsumeAutomationRunLink.ts index f5fd43ad28c..80c0f7a0a23 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useConsumeAutomationRunLink/useConsumeAutomationRunLink.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useConsumeAutomationRunLink/useConsumeAutomationRunLink.ts @@ -56,14 +56,14 @@ export function useConsumeAutomationRunLink({ : []; return chatSessionTarget({ result, sessions, sessionId, workspaceId }); }, - enabled: !!sessionId && acpChat === "enabled" && isLayoutReady, + enabled: !!sessionId && acpChat && isLayoutReady, refetchOnWindowFocus: false, retry: false, }); useEffect(() => { - if (!sessionId || !isLayoutReady || acpChat === "resolving") return; + if (!sessionId || !isLayoutReady) return; if ( - acpChat === "enabled" && + acpChat && (chatQuery.isFetching || (!chatQuery.isSuccess && !chatQuery.isError)) ) return; @@ -74,8 +74,7 @@ export function useConsumeAutomationRunLink({ })}`; if (consumedRef.current.has(key)) return; consumedRef.current.add(key); - const target = - acpChat === "enabled" && chatQuery.isSuccess ? chatQuery.data : null; + const target = acpChat && chatQuery.isSuccess ? chatQuery.data : null; consumeSearch(target ? ["sessionId", "terminalId"] : ["sessionId"]); if (target) focusChatSession({ store, linkedStores, session: target }); }, [ diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useIsChatTabActive/index.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useIsChatTabActive/index.ts deleted file mode 100644 index 4119d2a8ed3..00000000000 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useIsChatTabActive/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useIsChatTabActive } from "./useIsChatTabActive"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useIsChatTabActive/useIsChatTabActive.test.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useIsChatTabActive/useIsChatTabActive.test.ts deleted file mode 100644 index aaa09440e28..00000000000 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useIsChatTabActive/useIsChatTabActive.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { afterEach, expect, test } from "bun:test"; -import { createWorkspaceStore } from "@superset/panes"; -import { act, cleanup, renderHook } from "@testing-library/react"; -import { attachCloudChatSession } from "renderer/stores/workspace-creates/pendingCloudChat"; -import { type PaneViewerData, PENDING_CLOUD_CHAT_PANE_KIND } from "../../types"; -import { useIsChatTabActive } from "./useIsChatTabActive"; - -afterEach(cleanup); - -test("the waiting chat keeps the chat layout before and after attaching", () => { - const store = createWorkspaceStore(); - store.getState().addTab({ - panes: [ - { - kind: PENDING_CLOUD_CHAT_PANE_KIND, - data: { draftId: "draft", agentId: "claude", promptHash: "hash" }, - }, - ], - }); - const paneId = Object.keys(store.getState().tabs[0]?.panes ?? {})[0]; - if (!paneId) throw new Error("missing pane"); - const hook = renderHook(() => useIsChatTabActive(store)); - expect(hook.result.current).toBe(true); - act(() => - store.getState().replaceState((state) => - attachCloudChatSession(state, paneId, { - sessionId: "chat", - terminalId: "terminal", - harnessSessionId: null, - title: null, - }), - ), - ); - expect(hook.result.current).toBe(true); -}); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useIsChatTabActive/useIsChatTabActive.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useIsChatTabActive/useIsChatTabActive.ts deleted file mode 100644 index 4e2aab4a653..00000000000 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useIsChatTabActive/useIsChatTabActive.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { WorkspaceStore } from "@superset/panes"; -import { useStore } from "zustand"; -import type { StoreApi } from "zustand/vanilla"; -import { type PaneViewerData, PENDING_CLOUD_CHAT_PANE_KIND } from "../../types"; - -export function useIsChatTabActive( - store: StoreApi>, -): boolean { - return useStore(store, (state) => { - const tab = state.tabs.find( - (candidate) => candidate.id === state.activeTabId, - ); - if (!tab) return false; - const panes = Object.values(tab.panes); - return ( - panes.length > 0 && - panes.every( - (pane) => - pane.kind === "chat-v3" || pane.kind === PENDING_CLOUD_CHAT_PANE_KIND, - ) - ); - }); -} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/hooks/useSurfaceForkSource/useSurfaceForkSource.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/hooks/useSurfaceForkSource/useSurfaceForkSource.ts index b2574ba9ad3..1c22d0965bc 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/hooks/useSurfaceForkSource/useSurfaceForkSource.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/hooks/useSurfaceForkSource/useSurfaceForkSource.ts @@ -3,6 +3,7 @@ import { FEATURE_FLAGS } from "@superset/shared/constants"; import { useFeatureFlagEnabled } from "posthog-js/react"; import { useTerminalAgentBinding } from "renderer/hooks/host-service/useTerminalAgentBindings"; import { useWorkspaceHostUrl } from "renderer/hooks/host-service/useWorkspaceHostUrl"; +import { useAcpChatEnabled } from "renderer/hooks/useAcpChatEnabled"; import { useV2AgentConfigs } from "renderer/hooks/useV2AgentConfigs"; import type { ChatPaneData, TerminalPaneData } from "../../../../types"; import type { SurfaceForkSource } from "../useSurfaceFork"; @@ -20,6 +21,7 @@ export function useSurfaceForkSource( ): { enabled: boolean; source: SurfaceForkSource | null } { const enabled = useFeatureFlagEnabled(FEATURE_FLAGS.AGENT_SURFACE_FORK) === true; + const acpChatEnabled = useAcpChatEnabled(); const binding = useTerminalAgentBinding(workspaceId, pane.data.terminalId); const hostUrl = useWorkspaceHostUrl(workspaceId); const { data: configs = [] } = useV2AgentConfigs(hostUrl); @@ -43,6 +45,7 @@ export function useSurfaceForkSource( } if ( + !acpChatEnabled || !binding?.agentSessionId || binding.endedAt || LOCKED_WHILE_CLI_RUNS.has(binding.agentId) || diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx index 8181c2c25ec..f316a0f2df4 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx @@ -66,7 +66,6 @@ import { useDefaultBrowserUrl } from "./hooks/useDefaultBrowserUrl"; import { useDefaultContextMenuActions } from "./hooks/useDefaultContextMenuActions"; import { useDefaultPaneActions } from "./hooks/useDefaultPaneActions"; import { useDiffPaneTarget } from "./hooks/useDiffPaneTarget"; -import { useIsChatTabActive } from "./hooks/useIsChatTabActive"; import { useOpenNewTabWhenRightPaneOpensEmpty } from "./hooks/useOpenNewTabWhenRightPaneOpensEmpty"; import { usePaneAreaMoveActions } from "./hooks/usePaneAreaMoveActions"; import { usePaneRegistry } from "./hooks/usePaneRegistry"; @@ -222,7 +221,6 @@ function V2WorkspaceContent() { const showPresetsBar = v2UserPreferences.showPresetsBar; const { store, isLayoutReady, hasRow } = useV2WorkspacePaneLayout(); useClearActivePaneAttention({ store }); - const isChatTabActive = useIsChatTabActive(store); const rightPaneAreaFlag = useRightPaneAreaEnabled(); const isRightPaneAreaEnabled = rightPaneAreaFlag === true; const { @@ -304,7 +302,7 @@ function V2WorkspaceContent() { const addChatTab = useCallback(() => { void openNewChat(); }, [openNewChat]); - const onAddChat = acpChat === "enabled" ? addChatTab : undefined; + const onAddChat = acpChat ? addChatTab : undefined; const { matchedPresets, newTabPresets, @@ -571,7 +569,7 @@ function V2WorkspaceContent() { isMobilePaneEnabled, }); useHotkey("QUICK_OPEN", handleQuickOpen); - useHotkey("NEW_CHAT", addChatTab, { enabled: acpChat !== "disabled" }); + useHotkey("NEW_CHAT", addChatTab, { enabled: acpChat }); useHotkey("RUN_WORKSPACE_COMMAND", () => { void workspaceRun.toggleWorkspaceRun(); }); @@ -701,7 +699,7 @@ function V2WorkspaceContent() { /> )} renderBelowTabBar={() => - showPresetsBar && !isChatTabActive ? ( + showPresetsBar ? ( state.setEnabled); const showChatTurnSnapshots = - acpChatFlag && + acpChatEnabled && isItemVisible( SETTING_ITEM_ID.EXPERIMENTAL_CHAT_TURN_SNAPSHOTS, visibleItems, diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx index d4b1aa42614..6f3c23f74ff 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx @@ -120,7 +120,6 @@ function SettingsLayout() { useFeatureFlagEnabled(FEATURE_FLAGS.MOBILE_LAUNCH) === true; const cloudWorkspacesEnabled = useFeatureFlagEnabled(FEATURE_FLAGS.CLOUD_WORKSPACES) === true; - const acpChatFlag = useFeatureFlagEnabled(FEATURE_FLAGS.ACP_CHAT) === true; const pluginsVisible = usePluginsSettingsVisible(); const searchQuery = useSettingsSearchQuery(); const setSearchQuery = useSetSettingsSearchQuery(); @@ -142,7 +141,6 @@ function SettingsLayout() { isV2CloudEnabled, cloudWorkspacesEnabled, mobileEnabled, - acpChatFlag, pluginsVisible, ) : {}, @@ -152,7 +150,6 @@ function SettingsLayout() { isV2CloudEnabled, cloudWorkspacesEnabled, mobileEnabled, - acpChatFlag, pluginsVisible, ], ); diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts index 5968aa9788e..262de1b7bf8 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts @@ -152,39 +152,15 @@ describe("settings search - mobile rollout", () => { }); }); -describe("settings search - chat", () => { - it("counts the Chat switch only when its flag is on", () => { - expect( - getVisibleMatchCountBySection("acp", true, false).experimental, - ).toBeUndefined(); - expect( - getVisibleMatchCountBySection("acp", true, false, false, true) - .experimental, - ).toBe(1); - }); -}); - describe("settings search - plugins visibility", () => { it("excludes plugin matches unless the plugins section is visible", () => { expect( - getVisibleMatchCountBySection( - "marketplace", - true, - false, - false, - false, - false, - ).plugins, + getVisibleMatchCountBySection("marketplace", true, false, false, false) + .plugins, ).toBeUndefined(); expect( - getVisibleMatchCountBySection( - "marketplace", - true, - false, - false, - false, - true, - ).plugins, + getVisibleMatchCountBySection("marketplace", true, false, false, true) + .plugins, ).toBeGreaterThan(0); }); }); diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts index b304c0a660e..0bf84a457a8 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts @@ -2156,14 +2156,12 @@ export function getVisibleMatchCountBySection( isV2: boolean, cloudWorkspaces: boolean, mobileEnabled = false, - acpChatFlag = false, pluginsVisible = false, ): Partial> { const matches = searchSettings(query).filter( (item) => isItemOffered(item, isV2, cloudWorkspaces) && (item.section !== "mobile" || mobileEnabled) && - (item.id !== SETTING_ITEM_ID.EXPERIMENTAL_ACP_CHAT || acpChatFlag) && (item.section !== "plugins" || pluginsVisible), ); const counts: Partial> = {}; diff --git a/apps/desktop/src/renderer/stores/acp-chat.ts b/apps/desktop/src/renderer/stores/acp-chat.ts index 1cd04c36dae..983273342c9 100644 --- a/apps/desktop/src/renderer/stores/acp-chat.ts +++ b/apps/desktop/src/renderer/stores/acp-chat.ts @@ -2,19 +2,14 @@ import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; /** - * EXPERIMENT: the ACP chat pane. Needs the `acp-chat` flag; with it on, - * `enabled` is null until the person sets it, and null means on. Read it + * EXPERIMENT: the ACP chat pane, opt-in from Settings → Experimental. Read it * through `useAcpChatEnabled`. * - * Desktop only: automations and cloud workspaces open chats from the flag - * alone. - * - * To conclude the experiment, remove: this store, its toggle in - * `ExperimentalSettings` and its `settings-search` entry - * (`EXPERIMENTAL_ACP_CHAT`), and read the flag directly again. + * Desktop only: automations, cloud workspaces and mobile still read the + * `acp-chat` flag. */ interface AcpChatState { - enabled: boolean | null; + enabled: boolean; setEnabled: (enabled: boolean) => void; } @@ -22,10 +17,17 @@ export const useAcpChatStore = create()( devtools( persist( (set) => ({ - enabled: null, + enabled: false, setEnabled: (enabled) => set({ enabled }), }), - { name: "acp-chat" }, + { + name: "acp-chat", + version: 1, + migrate: (persisted) => + ({ + enabled: (persisted as { enabled?: unknown })?.enabled === true, + }) as AcpChatState, + }, ), { name: "AcpChatStore" }, ), diff --git a/apps/desktop/src/renderer/stores/chat-turn-snapshots.ts b/apps/desktop/src/renderer/stores/chat-turn-snapshots.ts index 7a9e89fc43e..628243a6e63 100644 --- a/apps/desktop/src/renderer/stores/chat-turn-snapshots.ts +++ b/apps/desktop/src/renderer/stores/chat-turn-snapshots.ts @@ -1,12 +1,11 @@ -import { FEATURE_FLAGS } from "@superset/shared/constants"; -import { useFeatureFlagEnabled } from "posthog-js/react"; +import { useAcpChatEnabled } from "renderer/hooks/useAcpChatEnabled"; import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; /** * EXPERIMENT: snapshot the worktree around each ACP chat turn, for the turn's - * changes card and its Undo. Off by default; the toggle shows only with the - * `acp-chat` flag. + * changes card and its Undo. Off by default; the toggle shows only while the + * chat experiment is on. * * To conclude the experiment, remove: this store, moving its * `chat-turn-snapshots` key to `DEAD_KEYS` in `lib/persisted-keys`; its toggle @@ -33,7 +32,7 @@ export const useChatTurnSnapshotsStore = create()( ); export function useChatTurnSnapshotsEnabled(): boolean { - const flag = useFeatureFlagEnabled(FEATURE_FLAGS.ACP_CHAT) === true; + const chatEnabled = useAcpChatEnabled(); const enabled = useChatTurnSnapshotsStore((state) => state.enabled); - return flag && enabled; + return chatEnabled && enabled; } diff --git a/apps/mobile/lib/voice/tools/data.ts b/apps/mobile/lib/voice/tools/data.ts index 705ca8eaf9a..c8c9a447319 100644 --- a/apps/mobile/lib/voice/tools/data.ts +++ b/apps/mobile/lib/voice/tools/data.ts @@ -16,6 +16,7 @@ import { } from "@/lib/host-service/client"; import { ensureSandboxAccess } from "@/lib/sandbox-access"; import { apiClient } from "@/lib/trpc/client"; +import { useAcpChatStore } from "@/screens/(authenticated)/stores/acpChatStore"; import { htmlToText } from "./htmlToText"; import { type VoiceData, @@ -476,6 +477,13 @@ export function createVoiceData({ environmentId, prompt, agent, + ...(agent + ? { + surface: useAcpChatStore.getState().enabled + ? "chat" + : "terminal", + } + : {}), }), HOST_TIMEOUT_MS * 4, "The new workspace", diff --git a/apps/mobile/screens/(authenticated)/(home)/home/components/NewChatWidget/hooks/useCreateCloudWorkspace/useCreateCloudWorkspace.ts b/apps/mobile/screens/(authenticated)/(home)/home/components/NewChatWidget/hooks/useCreateCloudWorkspace/useCreateCloudWorkspace.ts index 4800465af79..baff9e7222e 100644 --- a/apps/mobile/screens/(authenticated)/(home)/home/components/NewChatWidget/hooks/useCreateCloudWorkspace/useCreateCloudWorkspace.ts +++ b/apps/mobile/screens/(authenticated)/(home)/home/components/NewChatWidget/hooks/useCreateCloudWorkspace/useCreateCloudWorkspace.ts @@ -10,6 +10,7 @@ import { useSession } from "@/lib/auth/client"; import { errorCopy, transportFailureKind } from "@/lib/errors"; import { posthog } from "@/lib/posthog"; import { apiClient } from "@/lib/trpc/client"; +import { useAcpChatEnabled } from "@/screens/(authenticated)/stores/acpChatStore"; import { useAppReviewStore } from "@/screens/(authenticated)/stores/appReviewStore"; interface CreateCloudWorkspaceArgs { @@ -38,6 +39,7 @@ export function useCreateCloudWorkspace() { const router = useRouter(); const queryClient = useQueryClient(); const { data: session } = useSession(); + const acpChatEnabled = useAcpChatEnabled(); const organizationId = session?.session?.activeOrganizationId ?? null; return useMutation({ @@ -68,6 +70,11 @@ export function useCreateCloudWorkspace() { agent: launchAgent, model: launchAgent ? (model ?? undefined) : undefined, effort: launchAgent ? (effort ?? undefined) : undefined, + surface: launchAgent + ? acpChatEnabled + ? "chat" + : "terminal" + : undefined, // Only with an agent to hand them to. ...(launchAgent && attachmentFileIds.length > 0 ? { attachmentFileIds } diff --git a/apps/mobile/screens/(authenticated)/(home)/home/hooks/useHostTerminals/useHostTerminals.ts b/apps/mobile/screens/(authenticated)/(home)/home/hooks/useHostTerminals/useHostTerminals.ts index 73629fe020f..9314d8014c9 100644 --- a/apps/mobile/screens/(authenticated)/(home)/home/hooks/useHostTerminals/useHostTerminals.ts +++ b/apps/mobile/screens/(authenticated)/(home)/home/hooks/useHostTerminals/useHostTerminals.ts @@ -1,13 +1,12 @@ import { presetForAnyHarness } from "@superset/chat/core"; -import { FEATURE_FLAGS } from "@superset/shared/constants"; import { useQueries } from "@tanstack/react-query"; -import { useFeatureFlag } from "posthog-react-native"; import { useMemo } from "react"; import { getChatTransport } from "@/lib/chat"; import { getHostServiceClientByUrl, hostServiceUrl, } from "@/lib/host-service/client"; +import { useAcpChatEnabled } from "@/screens/(authenticated)/stores/acpChatStore"; import { useTerminalSeenStore } from "@/screens/(authenticated)/stores/terminalSeenStore"; import { type LiveChat, listLiveChats } from "./utils/listLiveChats"; @@ -133,7 +132,7 @@ export function useHostsTerminals( hosts: TerminalsHost[], ): UseHostTerminalsResult { const terminalSeenAt = useTerminalSeenStore((state) => state.terminalSeenAt); - const acpChat = Boolean(useFeatureFlag(FEATURE_FLAGS.ACP_CHAT)); + const acpChat = useAcpChatEnabled(); const online = useMemo( () => diff --git a/apps/mobile/screens/(authenticated)/hooks/useCreateTerminalWorkspace/useCreateTerminalWorkspace.ts b/apps/mobile/screens/(authenticated)/hooks/useCreateTerminalWorkspace/useCreateTerminalWorkspace.ts index 291683faa79..bcef483a15e 100644 --- a/apps/mobile/screens/(authenticated)/hooks/useCreateTerminalWorkspace/useCreateTerminalWorkspace.ts +++ b/apps/mobile/screens/(authenticated)/hooks/useCreateTerminalWorkspace/useCreateTerminalWorkspace.ts @@ -5,11 +5,9 @@ import { getAgentEfforts, getAgentModelSupport, } from "@superset/shared/agent-models"; -import { FEATURE_FLAGS } from "@superset/shared/constants"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { randomUUID } from "expo-crypto"; import { useRouter } from "expo-router"; -import { useFeatureFlag } from "posthog-react-native"; import { Alert } from "react-native"; import { getHostWorkspacesQueryKey } from "@/hooks/useHostWorkspaces"; import { asAttachmentError } from "@/lib/attachments/errors"; @@ -19,6 +17,7 @@ import { getHostServiceClientByUrl } from "@/lib/host-service/client"; import { isMissingProcedureError } from "@/lib/host-service/errors"; import { posthog } from "@/lib/posthog"; import { getHostTerminalsQueryKey } from "@/screens/(authenticated)/(home)/home/hooks/useHostTerminals"; +import { useAcpChatEnabled } from "@/screens/(authenticated)/stores/acpChatStore"; import { useAppReviewStore } from "@/screens/(authenticated)/stores/appReviewStore"; import { usePendingChatLaunchStore } from "@/screens/(authenticated)/stores/pendingChatLaunchStore"; import { @@ -91,7 +90,7 @@ export function useCreateTerminalWorkspace() { const startPending = usePendingWorkspaceCreatesStore((state) => state.start); const failPending = usePendingWorkspaceCreatesStore((state) => state.fail); const clearPending = usePendingWorkspaceCreatesStore((state) => state.clear); - const acpChat = Boolean(useFeatureFlag(FEATURE_FLAGS.ACP_CHAT)); + const acpChat = useAcpChatEnabled(); const queueChatLaunch = usePendingChatLaunchStore((state) => state.queue); return useMutation({ diff --git a/apps/mobile/screens/(authenticated)/settings/SettingsScreen.tsx b/apps/mobile/screens/(authenticated)/settings/SettingsScreen.tsx index af529237c0c..9c928b3a9e4 100644 --- a/apps/mobile/screens/(authenticated)/settings/SettingsScreen.tsx +++ b/apps/mobile/screens/(authenticated)/settings/SettingsScreen.tsx @@ -8,7 +8,7 @@ import { } from "@superset/shared/constants"; import * as Application from "expo-application"; import { useRouter } from "expo-router"; -import { Alert, ScrollView, View } from "react-native"; +import { Alert, ScrollView, Switch, View } from "react-native"; import { SafeAreaView, useSafeAreaInsets, @@ -26,6 +26,7 @@ import { ListRow } from "@/screens/(authenticated)/components/ListRow"; import { ListRowValue } from "@/screens/(authenticated)/components/ListRowValue"; import { OrganizationAvatar } from "@/screens/(authenticated)/components/OrganizationAvatar"; import { useOrganizations } from "@/screens/(authenticated)/hooks/useOrganizations"; +import { useAcpChatStore } from "@/screens/(authenticated)/stores/acpChatStore"; import { SettingsSection } from "./components/SettingsSection"; import { UserAvatar } from "./components/UserAvatar"; import { usePlanName } from "./hooks/usePlanName"; @@ -54,6 +55,8 @@ export function SettingsScreen() { const { signOut, isSigningOut } = useSignOut(); const { deleteAccount, isDeleting } = useDeleteAccount(); const github = useGithubConnection(); + const acpChatEnabled = useAcpChatStore((state) => state.enabled); + const setAcpChatEnabled = useAcpChatStore((state) => state.setEnabled); const user = session?.user; const plan = session?.session.plan; @@ -329,6 +332,30 @@ export function SettingsScreen() { /> + + + } + label={t({ message: "Chat" })} + subtitle={t({ + message: "Open Claude and Codex sessions as chats", + })} + trailing={ + + } + isLast + /> + + void; +} + +export const useAcpChatStore = create()( + persist( + (set) => ({ + enabled: false, + setEnabled: (enabled) => set({ enabled }), + }), + { + name: "acp-chat-v1", + storage: createJSONStorage(() => AsyncStorage), + partialize: ({ enabled }) => ({ enabled }), + }, + ), +); + +export function useAcpChatEnabled(): boolean { + return useAcpChatStore((state) => state.enabled); +} diff --git a/apps/mobile/screens/(authenticated)/stores/acpChatStore/index.ts b/apps/mobile/screens/(authenticated)/stores/acpChatStore/index.ts new file mode 100644 index 00000000000..d53a728141a --- /dev/null +++ b/apps/mobile/screens/(authenticated)/stores/acpChatStore/index.ts @@ -0,0 +1 @@ +export { useAcpChatEnabled, useAcpChatStore } from "./acpChatStore"; diff --git a/apps/mobile/screens/(authenticated)/workspace/[id]/WorkspaceScreen/WorkspaceScreen.tsx b/apps/mobile/screens/(authenticated)/workspace/[id]/WorkspaceScreen/WorkspaceScreen.tsx index 167afd1c4c1..b26a1e6d37c 100644 --- a/apps/mobile/screens/(authenticated)/workspace/[id]/WorkspaceScreen/WorkspaceScreen.tsx +++ b/apps/mobile/screens/(authenticated)/workspace/[id]/WorkspaceScreen/WorkspaceScreen.tsx @@ -8,7 +8,6 @@ import type { } from "@superset/composer"; import { i18n } from "@superset/i18n"; import { BRAND } from "@superset/shared/brand"; -import { FEATURE_FLAGS } from "@superset/shared/constants"; import { TitlePress } from "@superset/title-press"; import { useQueryClient } from "@tanstack/react-query"; import * as Clipboard from "expo-clipboard"; @@ -24,7 +23,6 @@ import { SquareTerminal, TriangleAlert, } from "lucide-react-native"; -import { useFeatureFlag } from "posthog-react-native"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { ViewInstance } from "react-native"; import { @@ -64,6 +62,7 @@ import { import { useAgentIconUris } from "@/screens/(authenticated)/hooks/useAgentIconUris"; import { useCreateTerminalWorkspace } from "@/screens/(authenticated)/hooks/useCreateTerminalWorkspace"; import { useSlashCommands } from "@/screens/(authenticated)/hooks/useSlashCommands"; +import { useAcpChatEnabled } from "@/screens/(authenticated)/stores/acpChatStore"; import { workspaceDraftKey } from "@/screens/(authenticated)/stores/composerDraftsStore"; import { useLastSessionTabStore } from "@/screens/(authenticated)/stores/lastSessionTabStore"; import { useLastWorkspaceStore } from "@/screens/(authenticated)/stores/lastWorkspaceStore"; @@ -497,7 +496,7 @@ export function WorkspaceScreen() { ); const activeRow = rows.find((row) => row.terminalId === activeTerminalId); const activeIsChat = activeRow?.kind === "chat"; - const acpChat = Boolean(useFeatureFlag(FEATURE_FLAGS.ACP_CHAT)); + const acpChat = useAcpChatEnabled(); const { running: chatRunning, stop: stopChat } = useActiveChat( activeIsChat ? activeTerminalId : null, ); diff --git a/apps/mobile/screens/(authenticated)/workspace/[id]/new-session/NewSessionSheet.tsx b/apps/mobile/screens/(authenticated)/workspace/[id]/new-session/NewSessionSheet.tsx index 24a05450213..42472331375 100644 --- a/apps/mobile/screens/(authenticated)/workspace/[id]/new-session/NewSessionSheet.tsx +++ b/apps/mobile/screens/(authenticated)/workspace/[id]/new-session/NewSessionSheet.tsx @@ -1,11 +1,9 @@ import { Trans, useLingui } from "@lingui/react/macro"; import { acpHarnessForPreset, presetForAnyHarness } from "@superset/chat/core"; -import { FEATURE_FLAGS } from "@superset/shared/constants"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { randomUUID } from "expo-crypto"; import { Stack, useLocalSearchParams, useRouter } from "expo-router"; import { SquareTerminal } from "lucide-react-native"; -import { useFeatureFlag } from "posthog-react-native"; import { useState } from "react"; import { ActivityIndicator, Alert, ScrollView, View } from "react-native"; import { Button } from "@/components/ui/button"; @@ -28,6 +26,7 @@ import { resolveAgentLaunchPreferences, } from "@/screens/(authenticated)/hooks/useAgentLaunchPreferences"; import { useHostAgentConfigs } from "@/screens/(authenticated)/hooks/useHostAgentConfigs"; +import { useAcpChatEnabled } from "@/screens/(authenticated)/stores/acpChatStore"; import { usePendingChatLaunchStore } from "@/screens/(authenticated)/stores/pendingChatLaunchStore"; import { hostStartsChats } from "@/screens/(authenticated)/utils/hostStartsChats"; import { useHostCompatibility } from "../hooks/useHostCompatibility"; @@ -44,7 +43,7 @@ export function NewSessionSheet() { const router = useRouter(); const theme = useTheme(); const queryClient = useQueryClient(); - const acpChat = Boolean(useFeatureFlag(FEATURE_FLAGS.ACP_CHAT)); + const acpChat = useAcpChatEnabled(); const { workspace, host, isResolving } = useWorkspaceHost(id ?? null); const hostUrl = host ? hostServiceUrl(host.organizationId, host.machineId) diff --git a/apps/mobile/screens/(authenticated)/workspace/[id]/sessions/SessionsSheet.tsx b/apps/mobile/screens/(authenticated)/workspace/[id]/sessions/SessionsSheet.tsx index dee94cc50ec..022aba1ba38 100644 --- a/apps/mobile/screens/(authenticated)/workspace/[id]/sessions/SessionsSheet.tsx +++ b/apps/mobile/screens/(authenticated)/workspace/[id]/sessions/SessionsSheet.tsx @@ -1,8 +1,6 @@ import { useLingui } from "@lingui/react/macro"; -import { FEATURE_FLAGS } from "@superset/shared/constants"; import { useQueryClient } from "@tanstack/react-query"; import { Stack, useLocalSearchParams, useRouter } from "expo-router"; -import { useFeatureFlag } from "posthog-react-native"; import { useCallback, useMemo } from "react"; import { Alert } from "react-native"; import { useWorkspaceHost } from "@/hooks/useWorkspaceHost"; @@ -17,6 +15,7 @@ import { type TerminalRowData, useHostTerminals, } from "@/screens/(authenticated)/(home)/home/hooks/useHostTerminals"; +import { useAcpChatEnabled } from "@/screens/(authenticated)/stores/acpChatStore"; import { useTerminalTabOrderStore } from "@/screens/(authenticated)/stores/terminalTabOrderStore"; import { orderTerminalRows } from "../utils/orderTerminalRows"; import { SessionList } from "./components/SessionList"; @@ -30,7 +29,7 @@ import { SessionList } from "./components/SessionList"; export function SessionsSheet() { const { t } = useLingui(); const params = useLocalSearchParams<{ id: string; active?: string }>(); - const acpChat = Boolean(useFeatureFlag(FEATURE_FLAGS.ACP_CHAT)); + const acpChat = useAcpChatEnabled(); const id = params.id; const router = useRouter(); const queryClient = useQueryClient(); diff --git a/packages/i18n/locales/cs/messages.po b/packages/i18n/locales/cs/messages.po index 3d5cfbd8a5e..ded689945c1 100644 --- a/packages/i18n/locales/cs/messages.po +++ b/packages/i18n/locales/cs/messages.po @@ -13038,6 +13038,9 @@ msgstr "Otevřít nastavení správce ChatGPT" msgid "Open ChatGPT settings" msgstr "Otevřít nastavení ChatGPT" +msgid "Open Claude and Codex sessions as chats" +msgstr "Otevírat relace Claude a Codex jako chaty" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Otevírejte relace Claude a Codex v panelu chatu místo terminálu." diff --git a/packages/i18n/locales/de/messages.po b/packages/i18n/locales/de/messages.po index 06bd801e6f6..8b6c7ccdc5f 100644 --- a/packages/i18n/locales/de/messages.po +++ b/packages/i18n/locales/de/messages.po @@ -13038,6 +13038,9 @@ msgstr "ChatGPT-Admin-Einstellungen öffnen" msgid "Open ChatGPT settings" msgstr "ChatGPT-Einstellungen öffnen" +msgid "Open Claude and Codex sessions as chats" +msgstr "Claude- und Codex-Sitzungen als Chats öffnen" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Claude- und Codex-Sitzungen in einem Chat-Bereich statt in einem Terminal öffnen." diff --git a/packages/i18n/locales/en/messages.po b/packages/i18n/locales/en/messages.po index 3c1b7883ed4..ff7f5ae71fe 100644 --- a/packages/i18n/locales/en/messages.po +++ b/packages/i18n/locales/en/messages.po @@ -13038,6 +13038,9 @@ msgstr "Open ChatGPT admin settings" msgid "Open ChatGPT settings" msgstr "Open ChatGPT settings" +msgid "Open Claude and Codex sessions as chats" +msgstr "Open Claude and Codex sessions as chats" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Open Claude and Codex sessions in a chat pane instead of a terminal." diff --git a/packages/i18n/locales/es/messages.po b/packages/i18n/locales/es/messages.po index 632c83f87b8..c8989e0b3ad 100644 --- a/packages/i18n/locales/es/messages.po +++ b/packages/i18n/locales/es/messages.po @@ -13038,6 +13038,9 @@ msgstr "Abrir la configuración de administrador de ChatGPT" msgid "Open ChatGPT settings" msgstr "Abrir ajustes de ChatGPT" +msgid "Open Claude and Codex sessions as chats" +msgstr "Abrir las sesiones de Claude y Codex como chats" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Abre las sesiones de Claude y Codex en un panel de chat en lugar de en una terminal." diff --git a/packages/i18n/locales/fr/messages.po b/packages/i18n/locales/fr/messages.po index b3c1b3bd8d7..65517034cf1 100644 --- a/packages/i18n/locales/fr/messages.po +++ b/packages/i18n/locales/fr/messages.po @@ -13038,6 +13038,9 @@ msgstr "Ouvrir les paramètres d’administration de ChatGPT" msgid "Open ChatGPT settings" msgstr "Ouvrir les réglages ChatGPT" +msgid "Open Claude and Codex sessions as chats" +msgstr "Ouvrir les sessions Claude et Codex sous forme de chats" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Ouvrez les sessions Claude et Codex dans un volet de chat au lieu d'un terminal." diff --git a/packages/i18n/locales/id/messages.po b/packages/i18n/locales/id/messages.po index ced4c03530e..3614d3a58c9 100644 --- a/packages/i18n/locales/id/messages.po +++ b/packages/i18n/locales/id/messages.po @@ -13038,6 +13038,9 @@ msgstr "Buka pengaturan admin ChatGPT" msgid "Open ChatGPT settings" msgstr "Buka pengaturan ChatGPT" +msgid "Open Claude and Codex sessions as chats" +msgstr "Buka sesi Claude dan Codex sebagai obrolan" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Buka sesi Claude dan Codex di panel chat, bukan di terminal." diff --git a/packages/i18n/locales/it/messages.po b/packages/i18n/locales/it/messages.po index 43f2ec1b4f3..cbeaf24062e 100644 --- a/packages/i18n/locales/it/messages.po +++ b/packages/i18n/locales/it/messages.po @@ -13038,6 +13038,9 @@ msgstr "Apri le impostazioni di amministrazione di ChatGPT" msgid "Open ChatGPT settings" msgstr "Apri impostazioni ChatGPT" +msgid "Open Claude and Codex sessions as chats" +msgstr "Apri le sessioni di Claude e Codex come chat" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Apri le sessioni di Claude e Codex in un pannello di chat invece che in un terminale." diff --git a/packages/i18n/locales/ja/messages.po b/packages/i18n/locales/ja/messages.po index c9e309ddfe4..9e4b4209a98 100644 --- a/packages/i18n/locales/ja/messages.po +++ b/packages/i18n/locales/ja/messages.po @@ -13038,6 +13038,9 @@ msgstr "ChatGPT の管理者設定を開く" msgid "Open ChatGPT settings" msgstr "ChatGPT の設定を開く" +msgid "Open Claude and Codex sessions as chats" +msgstr "Claude と Codex のセッションをチャットとして開く" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Claude と Codex のセッションをターミナルではなくチャットペインで開きます。" diff --git a/packages/i18n/locales/ko/messages.po b/packages/i18n/locales/ko/messages.po index 32b97e5bad7..fcdef763169 100644 --- a/packages/i18n/locales/ko/messages.po +++ b/packages/i18n/locales/ko/messages.po @@ -13038,6 +13038,9 @@ msgstr "ChatGPT 관리자 설정 열기" msgid "Open ChatGPT settings" msgstr "ChatGPT 설정 열기" +msgid "Open Claude and Codex sessions as chats" +msgstr "Claude 및 Codex 세션을 채팅으로 열기" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Claude 및 Codex 세션을 터미널 대신 채팅 패널에서 엽니다." diff --git a/packages/i18n/locales/nl/messages.po b/packages/i18n/locales/nl/messages.po index 5e3ffd3f5ca..eaa96551815 100644 --- a/packages/i18n/locales/nl/messages.po +++ b/packages/i18n/locales/nl/messages.po @@ -13038,6 +13038,9 @@ msgstr "ChatGPT-beheerdersinstellingen openen" msgid "Open ChatGPT settings" msgstr "ChatGPT-instellingen openen" +msgid "Open Claude and Codex sessions as chats" +msgstr "Claude- en Codex-sessies als chats openen" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Open Claude- en Codex-sessies in een chatpaneel in plaats van in een terminal." diff --git a/packages/i18n/locales/pl/messages.po b/packages/i18n/locales/pl/messages.po index f4155ea0749..fffa0a2fbb0 100644 --- a/packages/i18n/locales/pl/messages.po +++ b/packages/i18n/locales/pl/messages.po @@ -13038,6 +13038,9 @@ msgstr "Otwórz ustawienia administratora ChatGPT" msgid "Open ChatGPT settings" msgstr "Otwórz ustawienia ChatGPT" +msgid "Open Claude and Codex sessions as chats" +msgstr "Otwieraj sesje Claude i Codex jako czaty" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Otwieraj sesje Claude i Codex w panelu czatu zamiast w terminalu." diff --git a/packages/i18n/locales/pt-BR/messages.po b/packages/i18n/locales/pt-BR/messages.po index 4256ca85514..8773cf1bb83 100644 --- a/packages/i18n/locales/pt-BR/messages.po +++ b/packages/i18n/locales/pt-BR/messages.po @@ -13038,6 +13038,9 @@ msgstr "Abrir configurações de administrador do ChatGPT" msgid "Open ChatGPT settings" msgstr "Abrir configurações do ChatGPT" +msgid "Open Claude and Codex sessions as chats" +msgstr "Abrir sessões do Claude e do Codex como chats" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Abra sessões do Claude e do Codex em um painel de chat em vez de um terminal." diff --git a/packages/i18n/locales/ru/messages.po b/packages/i18n/locales/ru/messages.po index 1a8e3ed6789..b625d8215ab 100644 --- a/packages/i18n/locales/ru/messages.po +++ b/packages/i18n/locales/ru/messages.po @@ -13038,6 +13038,9 @@ msgstr "Открыть настройки администратора ChatGPT" msgid "Open ChatGPT settings" msgstr "Открыть настройки ChatGPT" +msgid "Open Claude and Codex sessions as chats" +msgstr "Открывать сеансы Claude и Codex как чаты" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Открывайте сеансы Claude и Codex в панели чата, а не в терминале." diff --git a/packages/i18n/locales/tr/messages.po b/packages/i18n/locales/tr/messages.po index e0bc94f9ada..09493f592bb 100644 --- a/packages/i18n/locales/tr/messages.po +++ b/packages/i18n/locales/tr/messages.po @@ -13038,6 +13038,9 @@ msgstr "ChatGPT yönetici ayarlarını aç" msgid "Open ChatGPT settings" msgstr "ChatGPT ayarlarını aç" +msgid "Open Claude and Codex sessions as chats" +msgstr "Claude ve Codex oturumlarını sohbet olarak aç" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Claude ve Codex oturumlarını terminal yerine bir sohbet panelinde açın." diff --git a/packages/i18n/locales/vi/messages.po b/packages/i18n/locales/vi/messages.po index c5df06b8b6c..184f6e1d1bc 100644 --- a/packages/i18n/locales/vi/messages.po +++ b/packages/i18n/locales/vi/messages.po @@ -13038,6 +13038,9 @@ msgstr "Mở cài đặt quản trị ChatGPT" msgid "Open ChatGPT settings" msgstr "Mở cài đặt ChatGPT" +msgid "Open Claude and Codex sessions as chats" +msgstr "Mở phiên Claude và Codex dưới dạng trò chuyện" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "Mở phiên Claude và Codex trong một khung chat thay vì terminal." diff --git a/packages/i18n/locales/zh-CN/messages.po b/packages/i18n/locales/zh-CN/messages.po index af9759e5ca1..acf3ec2d5b6 100644 --- a/packages/i18n/locales/zh-CN/messages.po +++ b/packages/i18n/locales/zh-CN/messages.po @@ -13038,6 +13038,9 @@ msgstr "打开 ChatGPT 管理员设置" msgid "Open ChatGPT settings" msgstr "打开 ChatGPT 设置" +msgid "Open Claude and Codex sessions as chats" +msgstr "以聊天形式打开 Claude 和 Codex 会话" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "在聊天窗格而不是终端中打开 Claude 和 Codex 会话。" diff --git a/packages/i18n/locales/zh-TW/messages.po b/packages/i18n/locales/zh-TW/messages.po index 373852304a4..50d979399d2 100644 --- a/packages/i18n/locales/zh-TW/messages.po +++ b/packages/i18n/locales/zh-TW/messages.po @@ -13038,6 +13038,9 @@ msgstr "開啟 ChatGPT 管理員設定" msgid "Open ChatGPT settings" msgstr "開啟 ChatGPT 設定" +msgid "Open Claude and Codex sessions as chats" +msgstr "以聊天形式開啟 Claude 和 Codex 工作階段" + msgid "Open Claude and Codex sessions in a chat pane instead of a terminal." msgstr "在聊天窗格而非終端機中開啟 Claude 和 Codex 工作階段。" diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index 9354a117198..21d8f9ca97a 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -225,11 +225,10 @@ export const FEATURE_FLAGS = { */ MOBILE_LIVE_ACTIVITY: "mobile-live-activity", /** - * Shows the ACP chat pane: a chat UI that attaches to a Claude/Codex - * session in the workspace over the Agent Client Protocol. UI-only: the - * flag decides who sees the pane, not what the host can do. Off, unloaded, - * or offline all mean "no pane" — flips take effect live. Audience is a - * release condition on the flag, so widening never needs a release. + * Legacy gate for the ACP chat pane, now the Chat experimental setting on + * desktop and mobile. Read only by released builds and by + * `cloudWorkspace.create` when a caller sends no `surface`. Remove it once + * no live build reads it. */ ACP_CHAT: "acp-chat", /** diff --git a/packages/trpc/src/router/automation/cloudDispatch.ts b/packages/trpc/src/router/automation/cloudDispatch.ts index 524c2beaa7a..0f109cb38c9 100644 --- a/packages/trpc/src/router/automation/cloudDispatch.ts +++ b/packages/trpc/src/router/automation/cloudDispatch.ts @@ -11,7 +11,6 @@ import { } from "@superset/shared/cloud-agent-launch"; import { SUPERSET_USER_ID_HEADER } from "@superset/shared/host-routing"; import { and, eq } from "drizzle-orm"; -import { acpChatEnabled } from "../../lib/acp-chat"; import { resolveMachineSize } from "../../lib/billing/plan"; import { cloudAccess } from "../../lib/cloud-guards"; import { @@ -79,9 +78,6 @@ export async function runInCloud( const { automation, prompt, placed, runId } = args; if (launch.kind === "pinned") { - const chat = - supportsCloudAgentChat(automation.agent) && - (await acpChatEnabled(automation.ownerUserId)); const target = { baseUrl: launch.hostTarget, headers: launch.headers }; return launchAgentSession( { @@ -94,7 +90,7 @@ export async function runInCloud( workspaceId: launch.row.id, agent: automation.agent, prompt, - chat, + chat: supportsCloudAgentChat(automation.agent), previous: launch.previous, }, ); @@ -107,6 +103,7 @@ export async function runInCloud( name: automation.name.slice(0, 200), branch: launch.branch, launch: { agent: automation.agent, prompt, automationRunId: runId }, + chat: true, machineSize: ( await resolveMachineSize( automation.organizationId, diff --git a/packages/trpc/src/router/automation/dispatch.ts b/packages/trpc/src/router/automation/dispatch.ts index 25556bec78d..6983c468286 100644 --- a/packages/trpc/src/router/automation/dispatch.ts +++ b/packages/trpc/src/router/automation/dispatch.ts @@ -22,7 +22,6 @@ import { slugifyForBranch, } from "@superset/shared/workspace-launch"; import { and, eq, sql } from "drizzle-orm"; -import { acpChatEnabled } from "../../lib/acp-chat"; import { nudge } from "../../lib/realtime"; import { fetchRelayPresence } from "../../lib/relay-presence"; import { runInCloud } from "./cloudDispatch"; @@ -262,7 +261,6 @@ export async function dispatchAutomation( }) : undefined; - const chat = await acpChatEnabled(automation.ownerUserId); const hostCaller = relayHostCaller({ relayUrl, hostId: routingKey, jwt }); const runAgent = (targetWorkspaceId: string) => @@ -270,7 +268,7 @@ export async function dispatchAutomation( workspaceId: targetWorkspaceId, agent: automation.agent, prompt, - chat, + chat: true, // Only the pinned workspace holds that session; the stale-pin // recovery below branches a fresh one, which has none. ...(previous && targetWorkspaceId === automation.v2WorkspaceId diff --git a/packages/trpc/src/router/cloud-workspace/cloud-workspace.ts b/packages/trpc/src/router/cloud-workspace/cloud-workspace.ts index 95c46b606af..1698ae3c35d 100644 --- a/packages/trpc/src/router/cloud-workspace/cloud-workspace.ts +++ b/packages/trpc/src/router/cloud-workspace/cloud-workspace.ts @@ -21,6 +21,7 @@ import type { TRPCRouterRecord } from "@trpc/server"; import { TRPCError } from "@trpc/server"; import { and, asc, desc, eq, inArray, sql } from "drizzle-orm"; import { z } from "zod"; +import { acpChatEnabled } from "../../lib/acp-chat"; import { organizationMachineSizes } from "../../lib/billing/plan"; import { assertCloudAccess, assertMember } from "../../lib/cloud-guards"; import { nudge } from "../../lib/realtime"; @@ -368,6 +369,11 @@ export const cloudWorkspaceRouter = { taskIds: z.array(z.string().uuid()).max(10).optional(), /** A size the organization offers (see `machineSizes`); omitted = its default. */ machineSize: z.enum(MACHINE_SIZES).optional(), + /** + * Where `agent` opens. Omitted by builds older than the chat + * experimental setting; those keep reading the `acp-chat` flag. + */ + surface: z.enum(["chat", "terminal"]).optional(), }), ) .mutation(async ({ ctx, input }) => { @@ -393,6 +399,11 @@ export const cloudWorkspaceRouter = { taskIds: input.taskIds, attachmentFileIds: input.attachmentFileIds, machineSize: input.machineSize, + chat: + input.surface === "chat" || + (!input.surface && + !!input.agent && + (await acpChatEnabled(ctx.userId))), ...(input.agent ? { launch: { diff --git a/packages/trpc/src/router/cloud-workspace/start.ts b/packages/trpc/src/router/cloud-workspace/start.ts index 8965c50a930..465c3f28aa1 100644 --- a/packages/trpc/src/router/cloud-workspace/start.ts +++ b/packages/trpc/src/router/cloud-workspace/start.ts @@ -6,7 +6,6 @@ import { supportsCloudAgentChat, } from "@superset/shared/cloud-agent-launch"; import { and, eq, inArray, isNull } from "drizzle-orm"; -import { acpChatEnabled } from "../../lib/acp-chat"; import { anchorAttachments } from "../../lib/attachments"; import { resolveMachineSize } from "../../lib/billing/plan"; import { @@ -103,14 +102,15 @@ export async function startCloudWorkspace(args: { /** Omitted = the repo's default branch. */ branch?: string; launch?: CloudAgentLaunch; + /** Opens `launch` as a chat when its agent supports one. */ + chat?: boolean; taskIds?: string[]; attachmentFileIds?: string[]; machineSize?: string; beforeProvision?: (workspaceId: string) => Promise; }) { - const openAsChat = supportsCloudAgentChat(args.launch?.agent) - ? acpChatEnabled(args.userId) - : Promise.resolve(false); + const openAsChat = + args.chat === true && supportsCloudAgentChat(args.launch?.agent); const environment = await loadUsableEnvironment(args); const size = await resolveMachineSize(args.organizationId, args.machineSize); if (!size) throw machineSizeNotAvailable(args.machineSize); @@ -222,7 +222,7 @@ export async function startCloudWorkspace(args: { ? { launch: { ...args.launch, - surface: (await openAsChat) ? ("chat" as const) : undefined, + surface: openAsChat ? ("chat" as const) : undefined, }, } : {}),