feat: add unified plugin marketplace - #7134
Conversation
- Add cross-harness plugin discovery, details, install, and removal - Expose marketplace settings UI and Codex plugin runtime integration - Support Computer Use macOS launching and permissions
- Add MCP OAuth management across Codex, Claude, and Cursor - Expose plugin authentication controls in settings
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Reviewed the new Effect service code under apps/server/src/plugins/ (plus its call sites) against the service conventions. Six convention violations found: service dependencies injected as plain Effect-returning objects in production construction, two error classes that carry a prose detail instead of structured attributes and drop the underlying cause, a pass-through error constructor helper, raw CLI stderr copied into a caller-visible error detail, and a named service-tag import at a service boundary.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Two consistency issues in the new plugin marketplace UI: the search field rebuilds the shared InputGroup composition instead of using it, and the four new disclosure rows put the group/* marker on the Collapsible root, where Base UI never sets data-panel-open, so the chevrons never rotate.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
Effect Service Conventions: one new finding on the marketplace contract error. The six findings from my previous review (service-instance injection through PluginMarketplaceOptions, unstructured CodexPluginRuntimeError/McpOAuthRuntimeError payloads with no cause, the commandError pass-through helper, raw CLI stderr in caller-visible detail, and the non-namespaced service import in plugins/http.ts) still apply to the current head and are not repeated here.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Follow-up on the earlier group-data-panel-open note: the same misplacement is present in three more collapsible rows, and the new fallback plugin logo palette only carries dark-theme text tones.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
Reviewed the new Effect services (CodexPluginMarketplace, McpOAuthRuntime, CodexPluginRuntime, plugins/http.ts) and the plugin marketplace contracts against the Effect service conventions. Earlier findings on namespace imports, runtime dependency acquisition, CodexPluginRuntimeError, and raw CLI output in caller-visible details are resolved. Three items remain.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Reviewed the new plugin marketplace services against the Effect service conventions. The earlier findings (missing cause on the contract errors, split McpOAuthRuntime error classes with make/layer in their owning module, runtime dependencies now acquired from the environment in production make, namespace import in plugins/http.ts) are resolved.
One convention issue remains across the three new error definitions: cause is now required on errors that frequently fail with no underlying failure, so ~30 sites construct a synthetic new Error("…") purely to fill it, and CLI exit statuses are embedded in those synthetic messages instead of being structural attributes.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Reviewed the new plugin marketplace UI for shared-primitive and theming consistency. The prior findings (Collapsible group markers on the trigger, InputGroup search field, fallback logo light/dark tones) are resolved.
Remaining findings: three new status/banner surfaces use the fill-tone tokens (text-success, text-warning) for body text instead of the -foreground tokens the theme pairs with text. In light theme --success is emerald-500 and --warning is amber-500, so this text renders at roughly 2:1 contrast on the near-white card/banner; the repo convention is fill tone for icons/graphics and *-foreground for text (Badge success/warning variants, AgentsPanel, FilePreviewPanel).
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One finding: the new marketplace card's focus ring uses a ring offset without an offset color, which falls back to white in dark theme. Prior findings from earlier runs (fill-tone vs. -foreground text tokens, group/* markers on the Collapsible root instead of the trigger, hand-rolled search field, fallback logo light-theme tone) are resolved on this head.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One remaining convention issue: PluginMarketplaceOperationError.detail is populated by copying cause.message at the MCP auth boundaries, and the wrapper's message is then derived from that copy. Everything else flagged in earlier runs (namespace import in plugins/http.ts, runtime services acquired from the environment via tags/layers, structured errors with preserved cause, sanitized process failure details) looks addressed.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Reviewed the new plugin marketplace UI against the shared primitives. Prior findings (success/warning text tones, Collapsible group markers on the trigger, InputGroup search field, fallback logo light-theme tone, ring offset color) are all resolved in this revision. One remaining composition issue: both empty states place their action buttons inside EmptyHeader, which has no gap, so the buttons render flush against the description text.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
Reviewed the new plugin marketplace UI against the shared primitives. Prior findings (success/warning text tones, Collapsible group markers on the trigger, InputGroup search field, fallback logo light-theme tone, ring offset color) are all resolved in this revision. One remaining composition issue: both empty states place their action buttons inside EmptyHeader, which has no gap, so the buttons render flush against the description text.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One composition inconsistency left in the plugin marketplace UI; the earlier EmptyContent, collapsible group marker, focus-ring offset, and success/warning token findings all look resolved.
Posted via Macroscope — UI Consistency
ApprovabilityVerdict: Needs human review 3 blocking correctness issues found. Diff is too large for automated approval analysis. A human reviewer should evaluate this PR. You can customize Macroscope's approvability policy. Learn more. |
There was a problem hiding this comment.
UI consistency review of the new plugin marketplace surfaces. Two findings; the earlier rounds' issues (Empty composition, ring offset color, success/warning tone tokens, Collapsible group markers, InputGroup search field, fallback logo light-theme tones) all look resolved.
Posted via Macroscope — UI Consistency
| { | ||
| id: "plugin-marketplace", | ||
| title: "Plugin marketplace", | ||
| to: "/settings/plugins", | ||
| }, |
There was a problem hiding this comment.
This is the only entry in SETTINGS_SEARCH_ITEMS without a rendered anchor. Every other item's page spreads searchableSetting(...) onto a SettingsSection/SettingsRow (ProviderSettingsPanel, KeybindingsSettings, ConnectionsSettings, SourceControlSettings, SettingsPanels), which is what useSettingsSearchTarget needs to scroll, focus and pulse the destination.
Because nothing on /settings/plugins carries the plugin-marketplace id, picking this result navigates to /settings/plugins#plugin-marketplace, no target is ever handled, so there is no scroll/focus indication and onTargetHandled never runs — the dangling hash stays in the URL until another settings link clears it.
Suggest giving the plugins page the matching anchor, e.g. wrapping the results in a SettingsSection that spreads searchableSetting("plugin-marketplace") in PluginMarketplace.tsx.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One finding on the new plugins settings page: the marketplace section suppresses SettingsSection's header from the call site, which couples the page to the primitive's child order and breaks the settings-search scroll anchor. Everything else (Empty composition, Collapsible group markers, success/warning/destructive tones, InputGroup, fallback-logo light/dark tones, sidebar and command-palette entries) now matches the shared component system.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
Reviewed the new Effect services (CodexPluginMarketplace, McpOAuthRuntime, CodexPluginRuntime, PluginProviderCommands) and their call sites. Module shape (tag → make → layer), namespace imports, environment-based dependency acquisition in make, and the split MCP OAuth error classes all look consistent with the conventions now.
One remaining issue: the new service constructors read host process state from Node globals instead of the shared HostProcess* references, which hides those dependencies from the Effect environment and makes them unoverridable in tests — notably inconsistent since makeWithOptions already resolves the platform via HostProcessPlatform.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
One convention finding on the new plugin marketplace service code: CodexPluginRuntimeError uses an optional outcome discriminator to pick between four distinct user-facing messages, which the conventions ask to model as separate error classes (as was already done for the McpOAuth* errors in McpOAuthRuntime.ts). Everything else in the touched Effect service code looks consistent — subpath namespace imports, Context.Service tags with inline interfaces, make/layer exports, dependencies acquired from the environment in production make, and structural errors that preserve cause.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
One finding in the plugin marketplace presentation: the compact harness badges expose no accessible name, so the per-card harness/support information is invisible to assistive tech. Everything else (SettingsSection hideHeader/contentClassName ownership, Empty composition, Collapsible group/* markers on the triggers, InputGroup search field, success/warning/destructive text tones, fallback logo tones) now matches the shared component system.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One accessibility/consistency finding in the plugin marketplace loading state. Everything else previously flagged (empty-state composition, collapsible group markers, success/warning token tones, InputGroup search field, searchable section anchor, fallback logo light-theme tones, badge naming role) now matches the shared component conventions.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One finding on the plugin detail loading state: the skeleton reserves a logo box that the loaded header never renders, so the page reflows when the request resolves.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One finding: the MCP callback row pairs a default-size Input with a size="sm" Button, so the two controls no longer line up once the row goes horizontal at sm.
Posted via Macroscope — UI Consistency
- Add harness icons to marketplace filters and support badges - Simplify marketplace section headers and spacing - Replace the macOS development launcher binary with a shell script
There was a problem hiding this comment.
Reviewed the new Effect service code (plugins/CodexPluginMarketplace.ts, plugins/McpOAuthRuntime.ts, plugins/http.ts, contract errors, and the CodexSessionRuntime call sites) against the service conventions. Imports, environment-based dependency acquisition, error modeling (separate tagged classes with structural attributes plus cause), sanitized caller-visible details, and the exported make/layer for the marketplace service all look consistent now. One residual module-layout note below.
Posted via Macroscope — Effect Service Conventions
- Add ChatGPT public plugin discovery and search - Merge same-name plugins into unified listings with shared install targets
| await get() | ||
| .loadCatalog(true) | ||
| .catch(() => undefined); | ||
| await get() | ||
| .loadDetail(pluginId, true) | ||
| .catch(() => undefined); |
There was a problem hiding this comment.
🟡 Medium pluginMarketplace/store.ts:173
setInstalled resolves successfully after the install/remove request even when either refresh fails, so callers can show success while the store retains stale installation state. The .catch(() => undefined) calls suppress those refresh errors; let them propagate so the mutation reports failure when state could not be refreshed.
await get()
- .loadCatalog(true)
- .catch(() => undefined);
+ .loadCatalog(true);
await get()
- .loadDetail(pluginId, true)
- .catch(() => undefined);
+ .loadDetail(pluginId, true);🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/pluginMarketplace/store.ts around lines 173-178:
`setInstalled` resolves successfully after the install/remove request even when either refresh fails, so callers can show success while the store retains stale installation state. The `.catch(() => undefined)` calls suppress those refresh errors; let them propagate so the mutation reports failure when state could not be refreshed.
| const harnessName = MARKETPLACE_HARNESS_LABELS[target.harness]; | ||
| const externalHost = externalMarketplaceLabel(target, harnessName); | ||
| const changeInstallation = (installed: boolean) => { | ||
| void setInstalled(target.pluginId, installed) |
There was a problem hiding this comment.
🟡 Medium pluginMarketplace/PluginDetail.tsx:164
A success toast is shown even when the install-state refresh fails, and target.installed remains stale, so the switch can continue to display the pre-operation state and prompt the user to repeat the wrong action. setInstalled resolves after swallowing forced loadCatalog and loadDetail errors; propagate those refresh failures (or return an explicit refresh result) before treating the operation as successful.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/pluginMarketplace/PluginDetail.tsx around line 164:
A success toast is shown even when the install-state refresh fails, and `target.installed` remains stale, so the switch can continue to display the pre-operation state and prompt the user to repeat the wrong action. `setInstalled` resolves after swallowing forced `loadCatalog` and `loadDetail` errors; propagate those refresh failures (or return an explicit refresh result) before treating the operation as successful.
- Detect missing MCP servers through nested causes - Close unused OAuth windows and document Codex sign-in behavior
| export function codexMcpLoginArgs(name: string, url?: string | null): ReadonlyArray<string> { | ||
| const trimmedUrl = url?.trim(); | ||
| return trimmedUrl | ||
| ? ["-c", `mcp_servers.${name}.url=${JSON.stringify(trimmedUrl)}`, "mcp", "login", name] |
There was a problem hiding this comment.
🟠 High plugins/McpOAuthRuntime.ts:248
For a server named acme.prod, codexMcpLoginArgs emits mcp_servers.acme.prod.url, so Codex interprets the name as nested key segments instead of configuring mcp_servers["acme.prod"]; the CLI fallback then runs without the intended URL override and authentication fails or targets an unconfigured server. Quote or otherwise escape the server-name segment in the -c key.
| ? ["-c", `mcp_servers.${name}.url=${JSON.stringify(trimmedUrl)}`, "mcp", "login", name] | |
| ? ["-c", `mcp_servers.${JSON.stringify(name)}.url=${JSON.stringify(trimmedUrl)}`, "mcp", "login", name] |
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/plugins/McpOAuthRuntime.ts around line 248:
For a server named `acme.prod`, `codexMcpLoginArgs` emits `mcp_servers.acme.prod.url`, so Codex interprets the name as nested key segments instead of configuring `mcp_servers["acme.prod"]`; the CLI fallback then runs without the intended URL override and authentication fails or targets an unconfigured server. Quote or otherwise escape the server-name segment in the `-c` key.
What Changed
Why
T3 Code users currently have to discover, inspect, and manage extensions independently in each provider. The providers expose different catalog schemas, installation identifiers, package layouts, and authentication commands, so there was no shared model the clients could render without flattening away provider-specific behavior.
This change keeps that complexity at the server adapter boundary. The web client consumes one typed environment API, while Codex, Claude, and Cursor continue to own their installation and credential stores. That makes the marketplace usable through local, desktop-hosted, and remote environments without copying OAuth tokens into T3 state.
The PR is intentionally opened as a draft because this is a large vertical slice. The implementation stays scoped to plugin discovery, package inspection, provider-native lifecycle management, authentication, and making installed Codex skills available to turns. Maintainer direction on decomposition is welcome before review for merge.
UI Changes
After:

Plugins page

Plugin details

Oauth authentication

Package information

Validation
pnpm --dir apps/server exec vitest run src/plugins/McpOAuthRuntime.test.ts src/plugins/CodexPluginMarketplace.test.ts src/provider/Layers/CodexSessionRuntime.test.ts src/server.test.ts— 166 passedpnpm --dir apps/web exec vitest run src/components/settings/pluginMarketplace/PluginMarketplacePresentation.test.tsx src/pluginMarketplace/filter.test.ts src/pluginMarketplace/store.test.ts src/components/settings/settingsSearch.test.ts— 19 passedpnpm exec vp test run apps/desktop/scripts/electron-launcher.test.mjs— 5 passedThe full desktop-artifact file has one existing Apple-Silicon-local failure in the unrelated
skips the primary native probe for cross-architecture Windows payloadstest. The Windows probe implementation is unchanged from upstream; the two macOS packaging cases changed here pass.Checklist
Generated with GPT-5.6 using the Codex harness.
Note
High Risk
Large new surface area spanning subprocess/CLI orchestration, OAuth callback handling, and macOS codesigning/entitlements; mistakes could affect install state, auth flows, or local dev bundle security posture.
Overview
Adds a server-side unified plugin marketplace that merges Codex, Claude Code, and Cursor catalogs into one environment API: catalog/detail/logo, install/remove via native CLIs (with a separate Codex app-server runtime for curated-remote plugins), GitHub/Cursor HTML remote previews, sanitized metadata, and Computer Use macOS setup that opens the bundled app and System Settings.
Introduces
McpOAuthRuntimefor provider-native remote MCP auth—Codex via app-server OAuth login, Claude viamcp loginand callback validation, Cursor reported as externally managed—with strict callback URL/state checks.On desktop macOS dev, replaces the shell Electron launcher with a compiled C stub (
execv), adds Apple Events usage text and dev entitlements, and ad-hoc codesigns nested frameworks and the app bundle so Automation permissions work for Computer Use.Reviewed by Cursor Bugbot for commit 328b8da. Configure here.
Note
Add unified plugin marketplace with catalog browsing, MCP OAuth flows, and plugin skill injection
/settings/pluginswith search, harness/kind/category filtering, discovery sections, and per-plugin detail pages viaPluginMarketplaceandPluginDetailcomponents./api/pluginsfor catalog, detail, logo, install/remove/setup, and MCP OAuth start/complete/disconnect flows defined inpackages/contracts/src/pluginMarketplace.ts.McpOAuthRuntime, a DI service that orchestrates OAuth login sessions against Codex, Claude Code, and Cursor CLIs/app-server, with typed error classes and per-provider status parsing.usePluginMarketplaceStore(Zustand) to manage catalog loading, search, per-plugin detail caching, and install/uninstall with in-flight request deduplication.makeCodexSessionRuntimeto resolve$-mention plugin skills from a user's prompt and inject them asskillinputs into Codex turn start parameters.Macroscope summarized 731d43d.