revert(apps): restore Google connector visibility (#13552)

## Thinking Path

> - Paperclip helps people manage AI agents for work.
> - The Connectors catalog lists services that agents can use.
> - PR #13551 temporarily hid Google connectors.
> - We now want to restore their catalog visibility.
> - This PR reverts that change and restores the previous catalog
behavior.

## Linked Issues or Issue Description

Refs: #13551

Revert the temporary removal of Google connectors from the UI.

## What Changed

- Restore Gmail and eight Google Workspace entries to the catalog.
- Restore the matching branding flags and original catalog and service
tests.
- Remove the temporary-hiding documentation note.

This is an exact revert of commit
`cf1e873ab24277d55ffd3ab06074f77014dc4015`.

## Verification

- Passed: 507 catalog, UI, and connection service tests.
- Passed: `pnpm check:token-gates` and `node
scripts/check-app-brand-assets.mjs`.
- Passed: `pnpm --filter @paperclipai/ui... build` and `pnpm --filter
@paperclipai/ui... typecheck`.
- Full local build and typecheck stop at the Rust runner because `cargo`
is not installed.
- Full local Vitest was not repeated because the unchanged base has
confirmed macOS skill-cache permission failures. The full CI suites
passed.
- Passed: all GitHub CI gates; Greptile 5/5 on commit
`4e3dddef0ebfef1f99001e7735822ed4cba852ab`, with no review threads.
- Reviewer check: open Connectors and confirm that Gmail and Google
Workspace entries appear again.

## Risks

Low risk. This restores the previous catalog visibility and setup entry
points. Connector implementations and saved connection data are
retained.

## 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
- [x] 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 <noreply@paperclip.ing>
This commit is contained in:
Devin Foley
2026-09-16 15:35:16 -07:00
committed by GitHub
co-authored by Paperclip
parent cf1e873ab2
commit fae6980310
5 changed files with 37 additions and 54 deletions
-4
View File
@@ -15,10 +15,6 @@ 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.
+2 -11
View File
@@ -663,7 +663,7 @@ describe("AppDefinition catalog", () => {
);
}
});
it("withholds hidden providers from the app store without deleting their definitions", () => {
it("withholds unverified and reserved providers from the app store without deleting their definitions", () => {
expect([...APP_STORE_HIDDEN_SLUGS].sort()).toEqual([
"beehiiv",
"bitly",
@@ -674,15 +674,6 @@ 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",
@@ -696,7 +687,7 @@ describe("AppDefinition catalog", () => {
"ticktick",
"xero",
]);
expect(APP_STORE_DEFINITIONS).toHaveLength(38);
expect(APP_STORE_DEFINITIONS).toHaveLength(47);
const connectableSlugs = new Set(
CONNECTABLE_APP_DEFINITIONS.map((entry) => entry.slug),
);
-9
View File
@@ -52,15 +52,6 @@ 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",
@@ -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 { appWithPaperclipCloudConnectorAvailability, invalidatePaperclipCloudConnectorCapabilities, type PaperclipCloudConnector } from "../services/paperclip-cloud-connector.js";
import { invalidatePaperclipCloudConnectorCapabilities, type PaperclipCloudConnector } from "../services/paperclip-cloud-connector.js";
const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport();
const describeEmbeddedPostgres = embeddedPostgresSupport.supported
@@ -5087,12 +5087,21 @@ 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(38);
expect(res.body.apps).toHaveLength(47);
expect(
appWithPaperclipCloudConnectorAvailability(getConnectableAppDefinition("gmail")!, [])
res.body.apps.find((app: { slug: string }) => app.slug === "gmail")
.ownershipAvailability,
).toEqual({
platform_shared: false,
@@ -5128,16 +5137,17 @@ describeEmbeddedPostgres("tool access service", () => {
}),
]),
}),
]),
);
expect(getConnectableAppDefinition("google-sheets")!.methods).toEqual(
expect.arrayContaining([
expect.objectContaining({ key: "local", transport: "local_stdio" }),
expect.objectContaining({
slug: "google-sheets",
methods: expect.arrayContaining([
expect.objectContaining({ key: "local", transport: "local_stdio" }),
]),
}),
]),
);
});
it("hides Google from the gallery while retaining managed methods for signed profiles", async () => {
it("exposes managed Google methods only for profiles signed for this enrolled instance", async () => {
const company = await createCompany(db);
const userId = `gallery-pilot-${randomUUID()}`;
const pilotConnector = fakeGoogleWorkspaceConnector(
@@ -5159,11 +5169,8 @@ describeEmbeddedPostgres("tool access service", () => {
),
).get(`/api/companies/${company.id}/tools/gallery`);
expect(nonPilot.status).toBe(200);
expect(nonPilot.body.apps.find(
const nonPilotGmail = 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(
@@ -5185,11 +5192,8 @@ describeEmbeddedPostgres("tool access service", () => {
),
).get(`/api/companies/${company.id}/tools/gallery`);
expect(pilot.status).toBe(200);
expect(pilot.body.apps.find(
const pilotGmail = 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(
@@ -7040,7 +7044,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 retained Workspace $profile without mutating definitions in $deploymentMode",
"connects advertised 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;
@@ -7054,7 +7058,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).toBeUndefined();
expect(workspaceApp.methods.map((method: { key: string }) => method.key)).toContain(methodKey);
const connected = await request(app).post(`/api/companies/${company.id}/tools/apps/connect`).send({
galleryKey: slug, connectionMethodKey: methodKey, grantKind: "user", name: `Personal ${slug}`,
});
@@ -7077,7 +7081,7 @@ describeEmbeddedPostgres("tool access service", () => {
expect(JSON.stringify(getConnectableAppDefinition(slug))).toBe(definitionBefore);
});
it.each(GOOGLE_WORKSPACE_CONNECTOR_PROFILE_IDS)("connects retained Workspace %s with a Cloud-delivered environment identity", async (profile) => {
it.each(GOOGLE_WORKSPACE_CONNECTOR_PROFILE_IDS)("connects advertised 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);
@@ -7115,7 +7119,8 @@ 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)).toBeUndefined();
expect(gallery.body.apps.find((entry: { slug: string }) => entry.slug === slug).methods
.map((method: { key: string }) => method.key)).toContain(methodKey);
const result = await request(app).post(`/api/companies/${company.id}/tools/apps/connect`).send({
galleryKey: slug, connectionMethodKey: methodKey, grantKind: "user", name: `Cloud ${slug}`,
});
+9 -9
View File
@@ -126,37 +126,37 @@
{
"slug": "gmail",
"provider": "Gmail",
"catalogVisible": false,
"catalogVisible": true,
"localAsset": "/brands/apps/gmail.svg"
},
{
"slug": "google-sheets",
"provider": "Google Sheets",
"catalogVisible": false,
"catalogVisible": true,
"localAsset": "/brands/apps/google-sheets.svg"
},
{
"slug": "google-drive",
"provider": "Google Drive",
"catalogVisible": false,
"catalogVisible": true,
"localAsset": "/brands/apps/google-drive.svg"
},
{
"slug": "google-docs",
"provider": "Google Docs",
"catalogVisible": false,
"catalogVisible": true,
"localAsset": "/brands/apps/google-docs.svg"
},
{
"slug": "google-slides",
"provider": "Google Slides",
"catalogVisible": false,
"catalogVisible": true,
"localAsset": "/brands/apps/google-slides.svg"
},
{
"slug": "google-calendar",
"provider": "Google Calendar",
"catalogVisible": false,
"catalogVisible": true,
"localAsset": "/brands/apps/google-calendar.svg",
"aliases": [
"gcal"
@@ -165,13 +165,13 @@
{
"slug": "google-chat",
"provider": "Google Chat",
"catalogVisible": false,
"catalogVisible": true,
"localAsset": "/brands/apps/google-chat.svg"
},
{
"slug": "google-people",
"provider": "Google People",
"catalogVisible": false,
"catalogVisible": true,
"localAsset": "/brands/apps/google-people.svg",
"aliases": [
"google contacts"
@@ -180,7 +180,7 @@
{
"slug": "google-workspace-search",
"provider": "Google Workspace Search",
"catalogVisible": false,
"catalogVisible": true,
"localAsset": "/brands/apps/google-workspace-search.svg",
"aliases": [
"workspace search"