diff --git a/.github/scripts/http-e2e.sh b/.github/scripts/http-e2e.sh index 865880e14e4..978b2f0488c 100755 --- a/.github/scripts/http-e2e.sh +++ b/.github/scripts/http-e2e.sh @@ -205,7 +205,6 @@ case "$group" in export COPILOT_TOOL_PERMISSIONS_ENABLED=true export KNOWLEDGE_MEMBER_ACCESS=true CREDENTIAL_GROUPS=true ACCESS_CONTROL_ENABLED=true export NEXT_PUBLIC_ACCESS_CONTROL_ENABLED=true NEXT_PUBLIC_ORGANIZATIONS_ENABLED=true - export MSHIP_SEARCH_INTEGRATION_TOOLS=true bunx --no-install playwright install --with-deps chromium export INTERNAL_API_SECRET=desktop-inbox-http-ci-local-secret-at-least-32-characters export DB_TX_TRIPWIRE=throw diff --git a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx index 4a530eeeec7..d5263cdb977 100644 --- a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx +++ b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx @@ -188,7 +188,6 @@ Plain JavaScript without imports runs in isolated-vm within Node child workers i | Variable | Description | |----------|-------------| | `COPILOT_API_KEY` | API key for the default Sim service. Required for Chat, the Sim Chat workflow block, and Inbox operations that use that service | -| `MSHIP_SEARCH_INTEGRATION_TOOLS` | Opt in to personal-account reads and reviewed actions in Search Assistant. Off by default. Applies when AppConfig is disabled or no feature-flags document is available; an available AppConfig document takes precedence. Requires organization account setup and configured provider credentials. Tool permissions and source restrictions still apply. See [Connect your account](/search/connect-your-account) | | `NEXT_PUBLIC_CHAT_DISABLED` | Set to `true` to hide the Chat module: the workspace lands on your first workflow, with no chats list or editor Chat panel. Chat is shown when unset; `bunx sim-setup` sets it for you if you skip the chat key | | `PII_URL` | Base URL of the Presidio service backing PII detection and redaction. The Helm chart wires it to its own `pii` Service when `pii.enabled`; on Compose point it at the PII service on your network. Local development and the Helm fallback use `http://localhost:5001`; that address works only when the service is reachable there. Point production at a running service to use redaction | | `ADMIN_API_KEY` | Admin API key for GitOps operations and organization provisioning | diff --git a/apps/docs/content/docs/search/ask-questions.mdx b/apps/docs/content/docs/search/ask-questions.mdx index 3776009270a..507654e8635 100644 --- a/apps/docs/content/docs/search/ask-questions.mdx +++ b/apps/docs/content/docs/search/ask-questions.mdx @@ -125,7 +125,7 @@ Copy the reviewed draft into another app, or ask Assistant to create it using yo ## Review actions -When integration actions are enabled, Assistant can answer questions and take supported actions in the same conversation. For example, ask it to draft an email in Gmail or create a Jira issue using your own connected account. +Assistant can answer questions and take supported actions in the same conversation. For example, ask it to draft an email in Gmail or create a Jira issue using your own connected account. Reads run without a prompt by default. Writes wait for review by default. Each proposal appears inline in the conversation with its app, account, destination, and proposed content. Select **Edit** to change supported fields, then choose **Allow** or **Cancel** for each action. After several proposals, **Allow N shown** or **Cancel N shown** applies your choice to that displayed batch. You can approve individual actions first, then cancel the remaining batch. diff --git a/apps/docs/content/docs/search/connect-your-account.mdx b/apps/docs/content/docs/search/connect-your-account.mdx index 53968005c5a..714e1800e44 100644 --- a/apps/docs/content/docs/search/connect-your-account.mdx +++ b/apps/docs/content/docs/search/connect-your-account.mdx @@ -46,7 +46,7 @@ Creating Jira sprints requires the Jira sprint permission. If your account was c ## Tool permissions -When Assistant integration actions are enabled, open the app in **Integrations** and select your account. Tool permissions appear in its details when the app has supported Assistant tools available to you. Set a preference for each operation or use the **Read-only tools** and **Write tools** group controls: +Open the app in **Integrations** and select your account. Tool permissions appear in its details when the app has supported Assistant tools available to you. Set a preference for each operation or use the **Read-only tools** and **Write tools** group controls: | Preference | Behavior | | --- | --- | diff --git a/apps/sim/app/o/[organizationId]/components/search-input-bar.tsx b/apps/sim/app/o/[organizationId]/components/search-input-bar.tsx index f6a6511cd27..bb53c7ad0fc 100644 --- a/apps/sim/app/o/[organizationId]/components/search-input-bar.tsx +++ b/apps/sim/app/o/[organizationId]/components/search-input-bar.tsx @@ -16,6 +16,7 @@ import { InputToolbar } from '@/app/workspace/[workspaceId]/home/components/user interface SearchInputBarProps { inputRef: RefObject value: string + disabled?: boolean onChange: (value: string) => void onSubmit: () => void onPaste?: ClipboardEventHandler @@ -36,6 +37,7 @@ export function SearchInputBar({ attachments, inputRef, value, + disabled = false, onChange, onSubmit, onPaste, @@ -106,6 +108,7 @@ export function SearchInputBar({ inputRef={inputRef} scrollerRef={scrollerRef} value={value} + disabled={disabled} compact={!hasAttachments && !expanded} className={hasAttachments ? 'min-h-[56px]' : undefined} maxHeight={200} diff --git a/apps/sim/app/o/[organizationId]/search/search.tsx b/apps/sim/app/o/[organizationId]/search/search.tsx index 6eb1813c337..bd08de7084b 100644 --- a/apps/sim/app/o/[organizationId]/search/search.tsx +++ b/apps/sim/app/o/[organizationId]/search/search.tsx @@ -26,6 +26,7 @@ import { MicButton } from '@/app/workspace/[workspaceId]/home/components/user-in import { MicrophonePermissionHelp } from '@/app/workspace/[workspaceId]/home/components/user-input/components/microphone-permission-help/microphone-permission-help' import { HomeFallback } from '@/app/workspace/[workspaceId]/home/home-fallback' import { knowledgeKeys } from '@/hooks/queries/utils/knowledge-keys' +import { useHydrated } from '@/hooks/use-hydrated' import { useVoiceInput } from '@/hooks/use-voice-input' import { useMothershipDraftsStore } from '@/stores/mothership-drafts/store' @@ -54,6 +55,7 @@ interface SearchFieldProps { /** Search commits a query on submit while retaining an independent editable draft. */ function SearchField({ userId, initialValue, onSubmit }: SearchFieldProps) { const inputRef = useRef(null) + const hydrated = useHydrated() const { organization } = useOrganizationContext() const queryClient = useQueryClient() const queryKey = knowledgeKeys.searchQuery( @@ -90,14 +92,17 @@ function SearchField({ userId, initialValue, onSubmit }: SearchFieldProps) { getValue: () => value, onChange: setValue, }) - const canSubmit = value.trim().length > 0 + const canSubmit = hydrated && value.trim().length > 0 - useEffect(() => inputRef.current?.focus(), []) + useEffect(() => { + if (hydrated) inputRef.current?.focus() + }, [hydrated]) const field = ( submit()} floating={!initialValue.trim()} diff --git a/apps/sim/lib/assistant-actions/catalog.ts b/apps/sim/lib/assistant-actions/catalog.ts index e5e4d3121b7..03b4930a0f3 100644 --- a/apps/sim/lib/assistant-actions/catalog.ts +++ b/apps/sim/lib/assistant-actions/catalog.ts @@ -7,7 +7,6 @@ import { projectIntegrationToolsForViewer } from '@/lib/integrations/tool-projec import { requireOrganizationSearchApproval } from '@/lib/knowledge/search/integration-policy' import { projectAssistantConnectedAccountTool } from '@/lib/mothership/assistant/connected-account-tool' import { isAssistantIntegrationTool } from '@/lib/mothership/assistant/tool-policy' -import { isSearchIntegrationToolsEnabled } from '@/lib/mothership/feature-flags' import { getUserPermissionConfigForOrganization } from '@/lib/permission-groups/resolve.server' import { requiresScopedRetrieval } from '@/lib/sim-search/live/policy-schema' import { livePolicyFor, loadLiveSearchPolicies } from '@/lib/sim-search/live/policy-store' @@ -17,17 +16,16 @@ export async function loadAssistantIntegrationTools( scope: { organizationId: string; userId: string }, executor?: DbOrTx ) { - const [visibility, config, enabled] = await Promise.all([ + const [visibility, config] = await Promise.all([ getBlockVisibility({ userId: scope.userId, orgId: scope.organizationId }), getUserPermissionConfigForOrganization(scope.organizationId, executor), - isSearchIntegrationToolsEnabled(), ]) const tools = projectIntegrationToolsForViewer(visibility, config) .tools.map((entry) => ({ ...entry, config: projectAssistantConnectedAccountTool(entry.config), })) - .filter(({ config: tool }) => enabled && isAssistantIntegrationTool(tool) && tool.oauth) + .filter(({ config: tool }) => isAssistantIntegrationTool(tool) && tool.oauth) const providers = [ ...new Set( tools.flatMap(({ config: tool }) => { diff --git a/apps/sim/lib/assistant-actions/tool-permissions.integration.ts b/apps/sim/lib/assistant-actions/tool-permissions.integration.ts index a5e0cad86cf..988be8bdb1c 100644 --- a/apps/sim/lib/assistant-actions/tool-permissions.integration.ts +++ b/apps/sim/lib/assistant-actions/tool-permissions.integration.ts @@ -74,7 +74,6 @@ import { getToolMetadata } from '@/tools/metadata' vi.hoisted(() => { Object.assign(process.env, { - MSHIP_SEARCH_INTEGRATION_TOOLS: 'true', JIRA_CLIENT_ID: 'assistant-fixture-client', JIRA_CLIENT_SECRET: 'assistant-fixture-client-secret', GOOGLE_CLIENT_ID: 'assistant-google-fixture-client', diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 64733e6325d..7f4d0abeb25 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -606,7 +606,6 @@ export const env = createEnv({ DASHBOARDS: z.boolean().optional(), PROJECTS: z.boolean().optional(), // Fallback for the `projects` feature flag off AppConfig MSHIP_COMPUTER_USE: z.boolean().optional(), - MSHIP_SEARCH_INTEGRATION_TOOLS: z.boolean().optional(), WORKFLOW_TESTS: z.boolean().optional(), // Fallback for the `workflow-tests` feature flag off AppConfig CHANGELOG: z.boolean().optional(), // Fallback for the `changelog` feature flag off AppConfig MSHIP_MODEL_SELECTOR: z.boolean().optional(), diff --git a/apps/sim/lib/core/config/feature-flags.ts b/apps/sim/lib/core/config/feature-flags.ts index 704ed0bb007..9a8777177df 100644 --- a/apps/sim/lib/core/config/feature-flags.ts +++ b/apps/sim/lib/core/config/feature-flags.ts @@ -57,11 +57,6 @@ const FEATURE_FLAGS = { 'personal workspaces follow the global switch. Each device must also opt in.', fallback: 'MSHIP_COMPUTER_USE', }, - 'mothership-search-integration-tools': { - description: - 'Enable personal-account integration reads and reviewed actions in Search Assistant. Global rollout with MSHIP_SEARCH_INTEGRATION_TOOLS fallback when AppConfig is unavailable; disabled by default.', - fallback: 'MSHIP_SEARCH_INTEGRATION_TOOLS', - }, 'workflow-tests': { description: 'Enable workflow tests: the workspace Tests page, tests/.test.js files, and the Sim tests tool. Supports global and organization rollout; disabled by default.', diff --git a/apps/sim/lib/mothership/chat/payload.test.ts b/apps/sim/lib/mothership/chat/payload.test.ts index eeaec1f385e..5574575d4bc 100644 --- a/apps/sim/lib/mothership/chat/payload.test.ts +++ b/apps/sim/lib/mothership/chat/payload.test.ts @@ -44,25 +44,17 @@ const { mockDashboardAvailability, mockSecretNames, mockComputerUseAvailable, - mockSearchIntegrationToolsEnabled, } = vi.hoisted(() => ({ mockDashboardAvailability: vi.fn(async () => false), mockComputerUseAvailable: vi.fn(async () => false), mockCreateUserToolSchema: vi.fn(() => ({ type: 'object', properties: {} })), mockSecretNames: vi.fn(async () => ({ names: [] as string[] })), - mockSearchIntegrationToolsEnabled: vi.fn(async () => true), })) vi.mock('@/lib/computer-use/application/availability', () => ({ isComputerUseAvailableFor: mockComputerUseAvailable, })) -vi.mock('@/lib/mothership/feature-flags', () => ({ - isSearchIntegrationToolsEnabled: mockSearchIntegrationToolsEnabled, -})) - -beforeEach(() => mockSearchIntegrationToolsEnabled.mockResolvedValue(true)) - // The inventory reads nine application worlds; these suites exercise the request shape, not the reads. vi.mock('@/lib/mothership/application/execute-organization-secret-use-case', () => ({ executeOrganizationSecretUseCase: mockSecretNames, @@ -948,49 +940,32 @@ describe('desktop request capabilities', () => { }) }) -it('carries only enabled MCP IDs without eager catalog discovery while preserving desktop capabilities', async () => { - const { getBlockVisibilityForCopilot } = await import('@/lib/mothership/block-visibility') - vi.mocked(getBlockVisibilityForCopilot).mockClear() - const principal = createSessionPrincipal({ userId: 'user-org' }) - const payload = await buildCopilotRequestPayload({ - message: 'Use my tools', - userId: 'user-org', - userMessageId: 'message-org', - organizationId: 'org-catalog', - chatId: 'chat-org', - principal, - mode: 'agent', - model: '', - mcpServerIds: ['server-a', 'server-b'], - browser: true, - terminalCapable: true, - }) - expect(getBlockVisibilityForCopilot).not.toHaveBeenCalled() - expect(payload.integrationCatalog).toEqual({ mcpServerIds: ['server-a', 'server-b'] }) - expect(payload).not.toHaveProperty('integrationTools') - expect(payload).not.toHaveProperty('mothershipTools') - expect(payload.desktop).toMatchObject({ browser: true, terminal: true }) -}) - -/** The worker derives both its gateway tools and prompt instructions from this capability. */ -it.each([{ organizationId: 'org-1' }, { workspaceId: 'ws-1' }])( - 'switches Search integration capability per turn while preserving Build for %j', - async (scope) => { - for (const enabled of [true, false, true]) { - mockSearchIntegrationToolsEnabled.mockResolvedValue(enabled) - for (const mode of ['assistant', 'agent', 'plan']) { - const payload = await buildCopilotRequestPayload({ - message: 'Find a person', - userId: 'person', - userMessageId: 'message', - mode, - model: '', - ...scope, - }) - expect(payload.integrationCatalog).toEqual( - mode === 'assistant' && !enabled ? undefined : { mcpServerIds: [] } - ) - } - } +it.each(['assistant', 'agent', 'plan'] as const)( + 'advertises the integration gateway while isolating MCP and desktop capabilities in %s mode', + async (mode) => { + const { getBlockVisibilityForCopilot } = await import('@/lib/mothership/block-visibility') + vi.mocked(getBlockVisibilityForCopilot).mockClear() + const principal = createSessionPrincipal({ userId: 'user-org' }) + const payload = await buildCopilotRequestPayload({ + message: 'Use my tools', + userId: 'user-org', + userMessageId: 'message-org', + organizationId: 'org-catalog', + chatId: 'chat-org', + principal, + mode, + model: '', + mcpServerIds: ['server-a', 'server-b'], + browser: true, + terminalCapable: true, + }) + expect(getBlockVisibilityForCopilot).not.toHaveBeenCalled() + expect(payload.integrationCatalog).toEqual({ + mcpServerIds: mode === 'assistant' ? [] : ['server-a', 'server-b'], + }) + expect(payload).not.toHaveProperty('integrationTools') + expect(payload).not.toHaveProperty('mothershipTools') + if (mode === 'assistant') expect(payload.desktop).toBeUndefined() + else expect(payload.desktop).toMatchObject({ browser: true, terminal: true }) } ) diff --git a/apps/sim/lib/mothership/chat/payload.ts b/apps/sim/lib/mothership/chat/payload.ts index 60e3856929f..b965774d649 100644 --- a/apps/sim/lib/mothership/chat/payload.ts +++ b/apps/sim/lib/mothership/chat/payload.ts @@ -32,7 +32,6 @@ import type { AssistantImageContent } from '@/lib/mothership/chat/assistant-imag import { buildUploadedFileContext } from '@/lib/mothership/chat/upload-context' import { buildWorkspaceInventory } from '@/lib/mothership/chat/workspace-inventory' import { computeEntitlements } from '@/lib/mothership/entitlements' -import { isSearchIntegrationToolsEnabled } from '@/lib/mothership/feature-flags' import type { AssistantSearchLevel } from '@/lib/mothership/generated/assistant' import { type ChatRequest, @@ -388,7 +387,6 @@ export async function buildCopilotRequestPayload(params: BuildPayloadParams): Pr params.principal !== undefined && computerUseOwner !== null && (await isComputerUseAvailableFor(params.principal, computerUseOwner)) - const integrationGateway = !isAssistant || (await isSearchIntegrationToolsEnabled()) // Track uploaded files in the DB and build context tags instead of base64 inlining. // Tracking writes `workspace_files` rows, so it needs the same write grant the @@ -539,13 +537,9 @@ export async function buildCopilotRequestPayload(params: BuildPayloadParams): Pr messageId: userMessageId, ...(chatId ? { chatId } : {}), ...(allContexts.length > 0 ? { context: allContexts } : {}), - ...(integrationGateway - ? { - integrationCatalog: { - mcpServerIds: isAssistant ? [] : [...new Set(params.mcpServerIds ?? [])], - }, - } - : {}), + integrationCatalog: { + mcpServerIds: isAssistant ? [] : [...new Set(params.mcpServerIds ?? [])], + }, ...(params.userTimezone ? { userTimezone: params.userTimezone } : {}), ...(params.effort ? { effort: params.effort } : {}), ...(params.modelSelection ? { modelSelection: params.modelSelection } : {}), diff --git a/apps/sim/lib/mothership/feature-flags.ts b/apps/sim/lib/mothership/feature-flags.ts index 20876ada065..ef7761bd4ac 100644 --- a/apps/sim/lib/mothership/feature-flags.ts +++ b/apps/sim/lib/mothership/feature-flags.ts @@ -12,11 +12,6 @@ export function isPlanModeEnabled(userId: string): Promise { return canUseBenchmarks(userId) } -/** One deployment gate controls Search integration discovery, execution, and prompt capability. */ -export function isSearchIntegrationToolsEnabled(): Promise { - return isFeatureEnabled('mothership-search-integration-tools') -} - /** Graph reads, writes and management use the same super-user gate as Plan and benchmarks. */ export function isMemorySpacesEnabled(userId: string, executor?: DbOrTx): Promise { return canUseBenchmarks(userId, executor) diff --git a/apps/sim/lib/mothership/integrations/application/catalog.test.ts b/apps/sim/lib/mothership/integrations/application/catalog.test.ts index fd2fb255593..e550623da89 100644 --- a/apps/sim/lib/mothership/integrations/application/catalog.test.ts +++ b/apps/sim/lib/mothership/integrations/application/catalog.test.ts @@ -40,9 +40,7 @@ import { readIntegrationCatalog, } from '@/lib/mothership/integrations/application/catalog' -const hoisted = vi.hoisted(() => ({ flag: vi.fn(async () => true) })) const mocks = { - ...hoisted, discover: mcpUseCasesMockFns.mockDiscoverMcpServerToolsUseCase, mcpPermission: permissionCheckMockFns.mockAssertPermissionsAllowed, build: mothershipChatPayloadMockFns.mockBuildIntegrationToolSchemas, @@ -63,7 +61,6 @@ vi.mock('@/lib/mothership/application/workspace-target', () => mothershipWorkspa vi.mock('@/lib/workspaces/application/context', () => workspaceContextMock) vi.mock('@/lib/auth/ban', () => authBanMock) vi.mock('@/lib/permission-groups/resolve.server', () => permissionGroupsResolveMock) -vi.mock('@/lib/mothership/feature-flags', () => ({ isSearchIntegrationToolsEnabled: hoisted.flag })) const input = { mode: 'assistant' as const, mcpServerIds: [], limit: 20 } const tools = [ @@ -100,7 +97,6 @@ function queueChat(mode = 'assistant', role = 'member') { } beforeEach(() => { resetDbChainMock() - mocks.flag.mockResolvedValue(true) mocks.banned.mockResolvedValue([]) mocks.config.mockResolvedValue(null) mocks.build.mockResolvedValue([...tools]) @@ -808,17 +804,6 @@ it('filters organization enabled servers to the authorized target before broad M expect(discoveredServerIds()).toEqual(['mcp-abc']) }) -it('removes previously discoverable Search operations when the runtime flag turns off', async () => { - for (const enabled of [true, false, true]) { - mocks.flag.mockResolvedValue(enabled) - queueChat() - const result = await readIntegrationCatalog.execute({ principal: principal(), input }) - expect(result.operations.map((operation) => operation.toolId)).toEqual( - enabled ? ['gmail_send', 'slack_send'] : [] - ) - } -}) - describe('tagged MCP servers that cannot be discovered', () => { const failures: Record Promise | Promise<{ tools: never[] }>> = { 'mcp-deleted': async () => { diff --git a/apps/sim/lib/mothership/integrations/application/catalog.ts b/apps/sim/lib/mothership/integrations/application/catalog.ts index 5fc754261c2..6d70bbebd04 100644 --- a/apps/sim/lib/mothership/integrations/application/catalog.ts +++ b/apps/sim/lib/mothership/integrations/application/catalog.ts @@ -32,7 +32,6 @@ import { createCopilotChatPrincipal } from '@/lib/mothership/auth/application-de import { defineAuthorizedChatUseCase } from '@/lib/mothership/chat/application/authorized-chat-use-case' import { resolveOwnedChatContext } from '@/lib/mothership/chat/application/context' import { buildIntegrationToolSchemas, type ToolSchema } from '@/lib/mothership/chat/payload' -import { isSearchIntegrationToolsEnabled } from '@/lib/mothership/feature-flags' import type { IntegrationCatalogRequest, IntegrationCatalogResponse, @@ -326,8 +325,6 @@ const catalogUseCase = defineAuthorizedChatUseCase({ delegation: { audience: INTEGRATION_CATALOG_AUDIENCE, isWithinScope: () => true }, }, async execute({ input, context }) { - if (context.mode === 'assistant' && !(await isSearchIntegrationToolsEnabled())) - return { total: 0, truncated: false, operations: [] } if (input.mcpExecution && context.organizationId) throw new OrchestrationError('forbidden', 'Executor catalogs require workspace agent scope') let workspaceId = context.workspaceId diff --git a/apps/sim/lib/mothership/tool-executor/executor.test.ts b/apps/sim/lib/mothership/tool-executor/executor.test.ts index 5449b9ace8b..be4a7955b82 100644 --- a/apps/sim/lib/mothership/tool-executor/executor.test.ts +++ b/apps/sim/lib/mothership/tool-executor/executor.test.ts @@ -10,8 +10,6 @@ import { import { toolsMock, toolsMockFns } from '@sim/testing/mocks/tools.mock' import { beforeEach, describe, expect, it, vi } from 'vitest' import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry' -import { getToolMetadata } from '@/tools/metadata' -import { slackGetUserTool } from '@/tools/slack/get_user' const { getToolEntry, isKnownTool, isSimExecuted, isClientExecuted } = vi.hoisted(() => ({ getToolEntry: vi.fn(), @@ -20,8 +18,7 @@ const { getToolEntry, isKnownTool, isSimExecuted, isClientExecuted } = vi.hoiste isClientExecuted: vi.fn(), })) -const { recordSecretUsage, searchIntegrationToolsEnabled } = vi.hoisted(() => ({ - searchIntegrationToolsEnabled: vi.fn(async () => true), +const { recordSecretUsage } = vi.hoisted(() => ({ recordSecretUsage: vi.fn(), })) @@ -35,14 +32,6 @@ vi.mock('./router', () => ({ })) vi.mock('@/tools', () => toolsMock) -vi.mock('@/lib/mothership/feature-flags', () => ({ - isSearchIntegrationToolsEnabled: searchIntegrationToolsEnabled, -})) -beforeEach(() => searchIntegrationToolsEnabled.mockResolvedValue(true)) -vi.mocked(getToolMetadata).mockImplementation((id) => - id === slackGetUserTool.id ? slackGetUserTool : undefined -) - vi.mock('@/lib/secrets/usage/record', () => ({ recordSecretUsage })) import { clearHandlers, executeTool, registerHandler } from './executor' @@ -761,29 +750,3 @@ describe('organization direct tool targets', () => { expect(targets.environment).not.toHaveBeenCalled() }) }) - -it.each([{ organizationId: 'org-1' }, { workspaceId: 'ws-1' }])( - 'stops a previously admitted Search integration call after flag revocation for %j', - async (scope) => { - isKnownTool.mockReturnValue(false) - isClientExecuted.mockReturnValue(false) - executeAppTool.mockResolvedValue({ success: true, output: { user: { id: 'U123' } } }) - for (const enabled of [true, false, true]) { - searchIntegrationToolsEnabled.mockResolvedValue(enabled) - const result = await executeTool( - 'slack_get_user', - { credentialId: 'own', userId: 'U123' }, - { - userId: 'person', - requestMode: 'assistant', - ...scope, - } - ) - expect(result).toEqual( - enabled - ? { success: true, output: { user: { id: 'U123' } } } - : { success: false, error: 'This operation is not available in Search Assistant.' } - ) - } - } -) diff --git a/apps/sim/lib/mothership/tool-executor/executor.ts b/apps/sim/lib/mothership/tool-executor/executor.ts index c911826b678..c51165d1fd1 100644 --- a/apps/sim/lib/mothership/tool-executor/executor.ts +++ b/apps/sim/lib/mothership/tool-executor/executor.ts @@ -9,7 +9,6 @@ import { resolveInvocationWorkspace } from '@/lib/mothership/application/workspa import { ASSISTANT_TOOLS, isAssistantIntegrationTool } from '@/lib/mothership/assistant/tool-policy' import { createRegistryContentGate } from '@/lib/mothership/content-delivery' import { prepareCopilotEnvironmentContext } from '@/lib/mothership/environment-context' -import { isSearchIntegrationToolsEnabled } from '@/lib/mothership/feature-flags' import { isOwnerScopedFile } from '@/lib/mothership/file-owners' import { projectToolErrorMessageForCopilot } from '@/lib/mothership/request/tools/resolved-secret-result' import { recordSecretUsage } from '@/lib/secrets/usage/record' @@ -172,8 +171,7 @@ async function executeBoundTool( if ( context.requestMode === 'assistant' && !ASSISTANT_TOOLS.has(toolId) && - (!isAssistantIntegrationTool(getToolMetadata(toolId)) || - !(await isSearchIntegrationToolsEnabled())) + !isAssistantIntegrationTool(getToolMetadata(toolId)) ) { return { success: false, diff --git a/apps/sim/scripts/test-organization-search-e2e.ts b/apps/sim/scripts/test-organization-search-e2e.ts index 69c247772a6..7aaf876fb86 100644 --- a/apps/sim/scripts/test-organization-search-e2e.ts +++ b/apps/sim/scripts/test-organization-search-e2e.ts @@ -715,17 +715,37 @@ try { await expect(page).toHaveURL(`${baseUrl.origin}${root}/search/chat/${assistantId}`) await expect(page.getByText(answer, { exact: true })).toBeVisible() }) - await check('New search clears its draft while Back restores the prior query draft', async () => { - await page.goto(`${baseUrl.origin}${root}/search?q=Launch`) - const input = page.getByRole('textbox', { name: 'Search your sources' }) - await input.fill('Edited launch query') - await page.getByRole('button', { name: 'New search', exact: true }).click() - await expect(page).toHaveURL(`${baseUrl.origin}${root}/search`) - await expect(input).toHaveValue('') - await page.goBack() - await expect(input).toHaveValue('Edited launch query') - assert.equal(new URL(page.url()).searchParams.get('q'), 'Launch') - }) + await check( + 'Search protects edits during hydration and restores the prior draft on Back', + async () => { + const releaseScripts = createDeferred() + const scripts = '**/_next/static/**/*.js' + await page.route(scripts, async (route) => { + await releaseScripts.promise + await route.continue() + }) + const input = page.getByRole('textbox', { name: 'Search your sources' }) + try { + await page.goto(`${baseUrl.origin}${root}/search?q=Launch`, { waitUntil: 'commit' }) + await expect(input).toBeVisible() + await expect(input).toBeDisabled() + await expect(page.getByLabel('Search', { exact: true })).toBeDisabled() + } finally { + releaseScripts.resolve() + await page.unrouteAll({ behavior: 'wait' }) + await page.waitForLoadState() + } + await expect(input).toBeFocused() + await input.fill('Edited launch query') + await expect(input).toHaveValue('Edited launch query') + await page.getByRole('button', { name: 'New search', exact: true }).click() + await expect(page).toHaveURL(`${baseUrl.origin}${root}/search`) + await expect(input).toHaveValue('') + await page.goBack() + await expect(input).toHaveValue('Edited launch query') + assert.equal(new URL(page.url()).searchParams.get('q'), 'Launch') + } + ) await check('Search URLs cannot change an agent conversation into an assistant', async () => { await page.goto(`${baseUrl.origin}${root}/search/chat/${agentId}`) await expect(page).toHaveURL(`${baseUrl.origin}${root}/chat/${agentId}`) diff --git a/scripts/check-test-types.baseline.json b/scripts/check-test-types.baseline.json index 23ed185abe0..9b8fb20b0dc 100644 --- a/scripts/check-test-types.baseline.json +++ b/scripts/check-test-types.baseline.json @@ -1953,7 +1953,7 @@ "TS18046": 2 }, "apps/sim/lib/mothership/tool-executor/executor.test.ts": { - "TS2345": 3, + "TS2345": 2, "TS2741": 3 }, "apps/sim/lib/mothership/tools/client/completion.test.ts": {