From cf1e873ab24277d55ffd3ab06074f77014dc4015 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Wed, 16 Sep 2026 15:04:46 -0700 Subject: [PATCH] fix(apps): temporarily hide Google connectors (#13551) ## Thinking Path > - Paperclip helps people manage AI agents for work. > - The Connectors catalog lists services that agents can use. > - We need to temporarily remove Google connectors from the UI. > - The catalog already separates visibility from retained definitions. > - This PR uses that setting so Google can return with a small change. ## Linked Issues or Issue Description **What existing behavior does this improve?** The Connectors catalog and its setup entry points. **Current behavior** The catalog shows Gmail and eight Google Workspace connectors. **Proposed behavior** Temporarily hide those nine entries. Keep their definitions and existing connections. **Reason and benefit** Make the temporary UI removal easy to reverse. **Breaking changes** Fresh catalog setup no longer offers Google. Saved connections keep the existing management and reconnect paths. ## What Changed - Add the nine Google connector slugs to the existing hidden list. - Match the branding manifest visibility flags. - Update existing catalog and service tests. Keep backend Google connection coverage and document how to restore visibility. ## Verification - Passed: 507 targeted tests covering catalog definitions, URL matching, setup routing, connector UI, branding, and the connection service. - Passed: `pnpm --filter @paperclipai/ui... build` and `pnpm --filter @paperclipai/ui... typecheck`. - Passed: `pnpm check:token-gates` and `node scripts/check-app-brand-assets.mjs`. - Full local build and typecheck stop at the Rust runner because `cargo` is not installed. - Stopped the full local Vitest run after skill-cache permission failures. Three failures in `company-skills-service.test.ts` also reproduce on the unchanged base branch. The final connector service suite passes all 319 tests. - Greptile: 5/5 on the current commit, with no open review threads. CI is retrying one unrelated preview-server readiness timeout. That test file passes all seven tests locally. - Reviewer check: open Connectors in a company with no Google connections. Gmail and Google Workspace entries should be absent. Existing saved connections remain manageable. ## Risks Low risk. This uses the existing catalog visibility mechanism. No connector implementation, credential, or database schema is removed. Restoring visibility requires updating both the hidden list and branding manifest. ## Model Used OpenAI GPT-6 through Codex, with reasoning, tool use, and code execution. The exact deployment ID and context window size are not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- doc/connections/README.md | 4 ++ packages/shared/src/app-definitions.test.ts | 13 ++++- packages/shared/src/app-definitions.ts | 9 ++++ .../src/__tests__/tool-access-service.test.ts | 47 +++++++++---------- ui/public/brands/apps/manifest.json | 18 +++---- 5 files changed, 54 insertions(+), 37 deletions(-) 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"