feat: Add marketplace source filtering and plugin share context (#21419)

Adds marketplaceKinds to plugin/list for local, workspace-directory, and
shared-with-me; omitted params keep default local plus gated global
behavior, while explicit kinds are exact.

Exposes shareContext on plugin summaries from local share mappings and
remote workspace/shared responses, including remotePluginId and nullable
creator metadata.

Adds shared-with-me listing through /ps/plugins/workspace/shared,
renames the workspace remote namespace to workspace-directory, and keeps
direct remote read/share/install/update/delete paths gated by plugins
rather than remote_plugin.
This commit is contained in:
xl-openai
2026-05-06 16:12:23 -07:00
committed by GitHub
parent 9417cf9696
commit 11106016ff
28 changed files with 1357 additions and 201 deletions
@@ -132,6 +132,24 @@ pub struct PluginListParams {
/// only home-scoped marketplaces and the official curated marketplace are considered.
#[ts(optional = nullable)]
pub cwds: Option<Vec<AbsolutePathBuf>>,
/// Optional marketplace kind filter. When omitted, only local marketplaces are queried, plus
/// the default remote catalog when enabled by feature flag.
#[ts(optional = nullable)]
pub marketplace_kinds: Option<Vec<PluginListMarketplaceKind>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum PluginListMarketplaceKind {
#[serde(rename = "local")]
#[ts(rename = "local")]
Local,
#[serde(rename = "workspace-directory")]
#[ts(rename = "workspace-directory")]
WorkspaceDirectory,
#[serde(rename = "shared-with-me")]
#[ts(rename = "shared-with-me")]
SharedWithMe,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -501,6 +519,8 @@ pub enum PluginAvailability {
pub struct PluginSummary {
pub id: String,
pub name: String,
/// Remote sharing context associated with this plugin when available.
pub share_context: Option<PluginShareContext>,
pub source: PluginSource,
pub installed: bool,
pub enabled: bool,
@@ -514,6 +534,15 @@ pub struct PluginSummary {
pub keywords: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareContext {
pub remote_plugin_id: String,
pub creator_account_user_id: Option<String>,
pub creator_name: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -2843,7 +2843,33 @@ fn plugin_list_params_ignore_removed_force_remote_sync_field() {
"forceRemoteSync": true,
}))
.unwrap(),
PluginListParams { cwds: None },
PluginListParams {
cwds: None,
marketplace_kinds: None,
},
);
}
#[test]
fn plugin_list_params_serializes_marketplace_kind_filter() {
assert_eq!(
serde_json::to_value(PluginListParams {
cwds: None,
marketplace_kinds: Some(vec![
PluginListMarketplaceKind::Local,
PluginListMarketplaceKind::WorkspaceDirectory,
PluginListMarketplaceKind::SharedWithMe,
]),
})
.unwrap(),
json!({
"cwds": null,
"marketplaceKinds": [
"local",
"workspace-directory",
"shared-with-me",
],
}),
);
}
@@ -3099,6 +3125,7 @@ fn plugin_share_list_response_serializes_share_items() {
plugin: PluginSummary {
id: "plugins~Plugin_00000000000000000000000000000000".to_string(),
name: "gmail".to_string(),
share_context: None,
source: PluginSource::Remote,
installed: false,
enabled: false,
@@ -3118,6 +3145,7 @@ fn plugin_share_list_response_serializes_share_items() {
"plugin": {
"id": "plugins~Plugin_00000000000000000000000000000000",
"name": "gmail",
"shareContext": null,
"source": { "type": "remote" },
"installed": false,
"enabled": false,
@@ -3149,6 +3177,7 @@ fn plugin_summary_defaults_missing_availability_to_available() {
.unwrap();
assert_eq!(summary.availability, PluginAvailability::Available);
assert_eq!(summary.share_context, None);
}
#[test]