feat(connectors): make every connector installable into any workspace#376
Merged
Conversation
Remove `defaultBinding` and the Composio personal-workspace install
guard. Any connector — Composio, DCR, static, or mpak — now installs
into whichever workspace the request is for (personal or shared);
binding scope is a property of the target workspace, not the catalog
entry.
Why this is safe:
- Install (connector-tools.ts) already keys purely on the target `wsId`
and treats personal vs shared identically — same BundleRef shape,
same `credentials/composio/<connectorId>/` layout.
- Disconnect cleanup (lifecycle.ts `cleanupComposioBundle`) is also
keyed on `{ wsId, connectorId }` with no scope discrimination, so the
"orphan the upstream Composio account" footgun the guard claimed to
prevent does not exist. The guard's comment cited a
`lifecycle.disconnect` `isWorkspaceScope` gate that no longer exists.
- Install already targets the request's active workspace
(`ctx.getWorkspaceId()`, set from the `/w/<slug>` route); nothing
derives the target from `defaultBinding` anymore.
Changes:
- Drop the `isPersonal === true` Composio guard and its stale comments.
- Remove `defaultBinding` from the connector meta schema, projection,
registry/web types, catalog YAML, and the two synthesized
DirectoryEntry call sites.
- Collapse the Browse page to one unified list (no personal/workspace
split); drop the now-unused `mode` prop.
- Update tests: drop `defaultBinding` fixtures/assertions; reframe the
personal-workspace install test around the unified path.
Verification: backend + web tsc clean; biome format/lint clean;
check:catalog passes; connector unit + web component + integration
install suites all green.
…nvariant QA follow-up. The reframed personal-workspace test used a dcr fixture (Canva) — a path the removed guard never gated — so it gave false confidence about the path this change actually unblocks. Add two tests to the composio-install suite that exercise the composio branch: - (g) composio install into a personal workspace persists the ref (succeeds where the old isPersonal guard rejected). - (h) disconnect of a personal-workspace composio bundle runs cleanup keyed on that wsId — seeds a connection.json under the personal workspace, disconnects through the real lifecycle caller, and asserts it was removed. Pins the "cleanup resolves by wsId, not isPersonal" invariant the safety argument rests on, so a future isPersonal gate in a cleanup caller fails a test instead of silently orphaning upstream. Also retune the stale bootstrap.ts isPersonal passthrough comment: it described a T010 install-dialog preselection/picker this change removed.
QA round 2 follow-up. ConnectorBrowsePage lost its `mode` discriminator in the earlier commit, but three sibling surfaces still carried it despite only ever being mounted as `"workspace"`: - ConnectorDetailPage: drop the `mode` prop. Its `canManage` ternary (`mode === "personal" ? true : roleAtLeast(...)`) was a dead branch that would grant manage unconditionally if `mode="personal"` were ever wired — now just `roleAtLeast(role, "ws_admin")`, which already covers the personal owner (sole admin of their personal workspace). - ConnectorList, ToolPermissionsTable: both already ignored `mode` (`mode: _mode`); remove the prop and its mount-site args. Also fix the install start-warning message to distinguish "your personal workspace" vs "this workspace", matching the success path (was a generic "the target workspace"). No behavior change — every surface was already workspace-only.
…ace") The eager-start-warning branch already said "in this workspace"; the no-warning branch still said "for this workspace" for the same shared- workspace case. Use "in" across all four variants (both personal branches already do). Cosmetic only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes every connector installable into any workspace (personal or shared), as requested. Removes
defaultBindingand the Composio personal-workspace install guard. Binding scope is now a property of the target workspace, never the catalog entry — install targets whichever workspace the request is for (ctx.getWorkspaceId(), derived from the/w/<slug>route).Why removing the guard is safe (not just deleting a check)
The guard claimed personal-workspace Composio installs would orphan the upstream Composio account on disconnect. That footgun doesn't exist in the current code:
connector-tools.ts) already keys purely on the targetwsIdand treats personal vs shared identically — sameBundleRefshape, samecredentials/composio/<connectorId>/layout.lifecycle.ts→cleanupComposioBundle) is keyed on{ wsId, connectorId }with no scope discrimination. The guard's comment cited alifecycle.disconnectisWorkspaceScopegate that no longer exists in the file.So the guard was stale caution from before the install/disconnect pipelines were unified.
On "stop deriving the target from anything but the current workspace"
Already true for install: the dispatcher defaults the target to the request's active workspace and never consults
defaultBinding. ThedefaultBinding-selects-the-target comments were stale; this PR deletes both the comments and the field.Changes
isPersonal === trueComposio guard + stale comments (connector-tools.ts).defaultBindingfrom the connector meta schema (server-detail.ts), projection (projection.ts), registry/web types (registries/types.ts,web/src/api/client.ts), the catalog YAML, and the two synthesizedDirectoryEntrycall sites (OperatorOAuthSection,ConnectorStatusHero).modeprop and its route arg.defaultBindingfixtures/assertions; reframe the personal-workspace install test around the unified path.Verification
tsc --noEmitclean (backend + web)format+lintcleancheck:catalogpasses (11/11 DCR entries)Follow-up (not in this PR)
The error message that pointed users at a non-existent "install dialog" picker is gone with the guard. No separate copy fix needed.