diff --git a/doc/connections/README.md b/doc/connections/README.md index f604effdba..dc1a34c586 100644 --- a/doc/connections/README.md +++ b/doc/connections/README.md @@ -15,6 +15,10 @@ Provider notes: [Google Workspace](./GOOGLE-WORKSPACE.md), [AgentMail](./AGENTMAIL.md), and [iMessage Photon](./IMESSAGE-PHOTON.md). Optional credential custody: [Vercel Connect](./VERCEL-CONNECT.md). +Gmail and Google Workspace connectors are temporarily hidden from the connector +catalog. Their definitions and existing connections are retained. Restore their +catalog visibility through `APP_STORE_HIDDEN_SLUGS` and the app branding manifest. + Post-read action: classify a new integration request, pick the right Paperclip layer to change, and avoid creating a parallel connection framework. diff --git a/packages/shared/src/app-definitions.test.ts b/packages/shared/src/app-definitions.test.ts index 7fd5e45630..9b0105fc52 100644 --- a/packages/shared/src/app-definitions.test.ts +++ b/packages/shared/src/app-definitions.test.ts @@ -663,7 +663,7 @@ describe("AppDefinition catalog", () => { ); } }); - it("withholds unverified and reserved providers from the app store without deleting their definitions", () => { + it("withholds hidden providers from the app store without deleting their definitions", () => { expect([...APP_STORE_HIDDEN_SLUGS].sort()).toEqual([ "beehiiv", "bitly", @@ -674,6 +674,15 @@ describe("AppDefinition catalog", () => { "context7", "egnyte", "embat", + "gmail", + "google-calendar", + "google-chat", + "google-docs", + "google-drive", + "google-people", + "google-sheets", + "google-slides", + "google-workspace-search", "kernel", "local-falcon", "make", @@ -687,7 +696,7 @@ describe("AppDefinition catalog", () => { "ticktick", "xero", ]); - expect(APP_STORE_DEFINITIONS).toHaveLength(47); + expect(APP_STORE_DEFINITIONS).toHaveLength(38); const connectableSlugs = new Set( CONNECTABLE_APP_DEFINITIONS.map((entry) => entry.slug), ); diff --git a/packages/shared/src/app-definitions.ts b/packages/shared/src/app-definitions.ts index 0ff98f037e..61983004d7 100644 --- a/packages/shared/src/app-definitions.ts +++ b/packages/shared/src/app-definitions.ts @@ -52,6 +52,15 @@ export const APP_STORE_HIDDEN_SLUGS = new Set([ "context7", "egnyte", "embat", + "gmail", + "google-calendar", + "google-chat", + "google-docs", + "google-drive", + "google-people", + "google-sheets", + "google-slides", + "google-workspace-search", "kernel", "local-falcon", "make", diff --git a/server/src/__tests__/tool-access-service.test.ts b/server/src/__tests__/tool-access-service.test.ts index 91203224d6..ca1883e94a 100644 --- a/server/src/__tests__/tool-access-service.test.ts +++ b/server/src/__tests__/tool-access-service.test.ts @@ -85,7 +85,7 @@ import { toolAccessRoutes } from "../routes/tool-access.js"; import { errorHandler } from "../middleware/index.js"; import type { ComposioClient } from "../services/composio.js"; import type { VercelConnectClient } from "../services/vercel-connect.js"; -import { invalidatePaperclipCloudConnectorCapabilities, type PaperclipCloudConnector } from "../services/paperclip-cloud-connector.js"; +import { appWithPaperclipCloudConnectorAvailability, invalidatePaperclipCloudConnectorCapabilities, type PaperclipCloudConnector } from "../services/paperclip-cloud-connector.js"; const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); const describeEmbeddedPostgres = embeddedPostgresSupport.supported @@ -5087,21 +5087,12 @@ describeEmbeddedPostgres("tool access service", () => { "sentry", "zapier", "linear", - "gmail", - "google-drive", - "google-docs", - "google-sheets", - "google-slides", - "google-calendar", - "google-chat", - "google-people", - "google-workspace-search", "github", ]), ); - expect(res.body.apps).toHaveLength(47); + expect(res.body.apps).toHaveLength(38); expect( - res.body.apps.find((app: { slug: string }) => app.slug === "gmail") + appWithPaperclipCloudConnectorAvailability(getConnectableAppDefinition("gmail")!, []) .ownershipAvailability, ).toEqual({ platform_shared: false, @@ -5137,17 +5128,16 @@ describeEmbeddedPostgres("tool access service", () => { }), ]), }), - expect.objectContaining({ - slug: "google-sheets", - methods: expect.arrayContaining([ - expect.objectContaining({ key: "local", transport: "local_stdio" }), - ]), - }), + ]), + ); + expect(getConnectableAppDefinition("google-sheets")!.methods).toEqual( + expect.arrayContaining([ + expect.objectContaining({ key: "local", transport: "local_stdio" }), ]), ); }); - it("exposes managed Google methods only for profiles signed for this enrolled instance", async () => { + it("hides Google from the gallery while retaining managed methods for signed profiles", async () => { const company = await createCompany(db); const userId = `gallery-pilot-${randomUUID()}`; const pilotConnector = fakeGoogleWorkspaceConnector( @@ -5169,8 +5159,11 @@ describeEmbeddedPostgres("tool access service", () => { ), ).get(`/api/companies/${company.id}/tools/gallery`); expect(nonPilot.status).toBe(200); - const nonPilotGmail = nonPilot.body.apps.find( + expect(nonPilot.body.apps.find( (app: { slug: string }) => app.slug === "gmail", + )).toBeUndefined(); + const nonPilotGmail = appWithPaperclipCloudConnectorAvailability( + getConnectableAppDefinition("gmail")!, await nonPilotConnector.getCapabilities(), ); expect(nonPilotGmail.ownershipAvailability.platform_shared).toBe(false); expect( @@ -5192,8 +5185,11 @@ describeEmbeddedPostgres("tool access service", () => { ), ).get(`/api/companies/${company.id}/tools/gallery`); expect(pilot.status).toBe(200); - const pilotGmail = pilot.body.apps.find( + expect(pilot.body.apps.find( (app: { slug: string }) => app.slug === "gmail", + )).toBeUndefined(); + const pilotGmail = appWithPaperclipCloudConnectorAvailability( + getConnectableAppDefinition("gmail")!, await pilotConnector.getCapabilities(), ); expect(pilotGmail.ownershipAvailability.platform_shared).toBe(true); expect( @@ -7044,7 +7040,7 @@ describeEmbeddedPostgres("tool access service", () => { ["local_trusted", "private", "http://127.0.0.1:3102"] as const, ["authenticated", "public", "https://tenant.paperclip.app"] as const, ].map(([deploymentMode, deploymentExposure, origin]) => ({ profile, deploymentMode, deploymentExposure, origin }))))( - "connects advertised Workspace $profile without mutating definitions in $deploymentMode", + "connects retained Workspace $profile without mutating definitions in $deploymentMode", async ({ profile, deploymentMode, deploymentExposure, origin }) => { const slug = GOOGLE_WORKSPACE_CONNECTOR_PROFILES[profile].appSlug; const methodKey = getConnectableAppDefinition(slug)!.methods.find((method) => method.connectorProfile === profile)!.key; @@ -7058,7 +7054,7 @@ describeEmbeddedPostgres("tool access service", () => { undefined, { deploymentMode, deploymentExposure, paperclipCloudConnector: connector }); const gallery = await request(app).get(`/api/companies/${company.id}/tools/gallery`); const workspaceApp = gallery.body.apps.find((entry: { slug: string }) => entry.slug === slug); - expect(workspaceApp.methods.map((method: { key: string }) => method.key)).toContain(methodKey); + expect(workspaceApp).toBeUndefined(); const connected = await request(app).post(`/api/companies/${company.id}/tools/apps/connect`).send({ galleryKey: slug, connectionMethodKey: methodKey, grantKind: "user", name: `Personal ${slug}`, }); @@ -7081,7 +7077,7 @@ describeEmbeddedPostgres("tool access service", () => { expect(JSON.stringify(getConnectableAppDefinition(slug))).toBe(definitionBefore); }); - it.each(GOOGLE_WORKSPACE_CONNECTOR_PROFILE_IDS)("connects advertised Workspace %s with a Cloud-delivered environment identity", async (profile) => { + it.each(GOOGLE_WORKSPACE_CONNECTOR_PROFILE_IDS)("connects retained Workspace %s with a Cloud-delivered environment identity", async (profile) => { const slug = GOOGLE_WORKSPACE_CONNECTOR_PROFILES[profile].appSlug; const methodKey = getConnectableAppDefinition(slug)!.methods.find((method) => method.connectorProfile === profile)!.key; const company = await createCompany(db); @@ -7119,8 +7115,7 @@ describeEmbeddedPostgres("tool access service", () => { deploymentMode: "authenticated", deploymentExposure: "public", }); const gallery = await request(app).get(`/api/companies/${company.id}/tools/gallery`); - expect(gallery.body.apps.find((entry: { slug: string }) => entry.slug === slug).methods - .map((method: { key: string }) => method.key)).toContain(methodKey); + expect(gallery.body.apps.find((entry: { slug: string }) => entry.slug === slug)).toBeUndefined(); const result = await request(app).post(`/api/companies/${company.id}/tools/apps/connect`).send({ galleryKey: slug, connectionMethodKey: methodKey, grantKind: "user", name: `Cloud ${slug}`, }); diff --git a/ui/public/brands/apps/manifest.json b/ui/public/brands/apps/manifest.json index afe72336f7..08b415c988 100644 --- a/ui/public/brands/apps/manifest.json +++ b/ui/public/brands/apps/manifest.json @@ -126,37 +126,37 @@ { "slug": "gmail", "provider": "Gmail", - "catalogVisible": true, + "catalogVisible": false, "localAsset": "/brands/apps/gmail.svg" }, { "slug": "google-sheets", "provider": "Google Sheets", - "catalogVisible": true, + "catalogVisible": false, "localAsset": "/brands/apps/google-sheets.svg" }, { "slug": "google-drive", "provider": "Google Drive", - "catalogVisible": true, + "catalogVisible": false, "localAsset": "/brands/apps/google-drive.svg" }, { "slug": "google-docs", "provider": "Google Docs", - "catalogVisible": true, + "catalogVisible": false, "localAsset": "/brands/apps/google-docs.svg" }, { "slug": "google-slides", "provider": "Google Slides", - "catalogVisible": true, + "catalogVisible": false, "localAsset": "/brands/apps/google-slides.svg" }, { "slug": "google-calendar", "provider": "Google Calendar", - "catalogVisible": true, + "catalogVisible": false, "localAsset": "/brands/apps/google-calendar.svg", "aliases": [ "gcal" @@ -165,13 +165,13 @@ { "slug": "google-chat", "provider": "Google Chat", - "catalogVisible": true, + "catalogVisible": false, "localAsset": "/brands/apps/google-chat.svg" }, { "slug": "google-people", "provider": "Google People", - "catalogVisible": true, + "catalogVisible": false, "localAsset": "/brands/apps/google-people.svg", "aliases": [ "google contacts" @@ -180,7 +180,7 @@ { "slug": "google-workspace-search", "provider": "Google Workspace Search", - "catalogVisible": true, + "catalogVisible": false, "localAsset": "/brands/apps/google-workspace-search.svg", "aliases": [ "workspace search"