[codex] Canonicalize shared workspace plugin IDs (#22564)

## Summary
- Canonicalize private and unlisted workspace shared plugin IDs to
`workspace-shared-with-me`.
- Keep `plugin/list` private/unlisted shared-with-me buckets as UI
grouping only.
- Update share read/list/checkout and cache cleanup coverage for the
canonical namespace.

## Tests
- `cargo test -p codex-app-server --test all
plugin_list_fetches_shared_with_me_kind`
- `cargo test -p codex-app-server --test all
plugin_read_returns_share_context_for_shared_remote_plugin`
- `cargo test -p codex-app-server --test all suite::v2::plugin_share`
- `cargo test -p codex-core-plugins
list_remote_plugin_shares_fetches_created_workspace_plugins`
- `cargo test -p codex-core-plugins
stale_remote_plugin_cleanup_removes_old_shared_with_me_cache_and_keeps_canonical_cache`
- `git diff --check`
This commit is contained in:
xl-openai
2026-05-13 16:29:47 -07:00
committed by GitHub
parent 3c3e18c222
commit e3bf0cfc63
7 changed files with 164 additions and 88 deletions
@@ -1823,6 +1823,18 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
/*enabled*/ None,
))?;
shared_plugin_body["plugins"][0]["share_principals"] = serde_json::Value::Null;
let shared_unlisted_body: serde_json::Value =
serde_json::from_str(&workspace_remote_plugin_page_body(
"plugins~Plugin_44444444444444444444444444444444",
"shared-unlisted-linear",
"Shared Unlisted Linear",
"UNLISTED",
/*enabled*/ None,
))?;
shared_plugin_body["plugins"]
.as_array_mut()
.expect("shared plugins should be an array")
.push(shared_unlisted_body["plugins"][0].clone());
let shared_plugin_body = serde_json::to_string(&shared_plugin_body)?;
let mut workspace_installed_body: serde_json::Value =
serde_json::from_str(&workspace_remote_plugin_page_body(
@@ -1878,10 +1890,10 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
.and_then(|interface| interface.display_name.as_deref()),
Some("Shared with me")
);
assert_eq!(marketplace.plugins.len(), 1);
assert_eq!(marketplace.plugins.len(), 2);
assert_eq!(
marketplace.plugins[0].id,
"shared-linear@workspace-shared-with-me-private"
"shared-linear@workspace-shared-with-me"
);
assert_eq!(
marketplace.plugins[0].remote_plugin_id.as_deref(),
@@ -1913,6 +1925,29 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
Some("https://chatgpt.example/plugins/share/share-key-1")
);
assert_eq!(share_context.share_principals, None);
assert_eq!(
marketplace.plugins[1].id,
"shared-unlisted-linear@workspace-shared-with-me"
);
assert_eq!(
marketplace.plugins[1].remote_plugin_id.as_deref(),
Some("plugins~Plugin_44444444444444444444444444444444")
);
assert_eq!(marketplace.plugins[1].name, "shared-unlisted-linear");
assert_eq!(marketplace.plugins[1].installed, false);
assert_eq!(marketplace.plugins[1].enabled, false);
let share_context = marketplace.plugins[1]
.share_context
.as_ref()
.expect("expected share context");
assert_eq!(
share_context.remote_plugin_id,
"plugins~Plugin_44444444444444444444444444444444"
);
assert_eq!(
share_context.discoverability,
Some(PluginShareDiscoverability::Unlisted)
);
let marketplace = response
.marketplaces
@@ -1929,7 +1964,7 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {
assert_eq!(marketplace.plugins.len(), 1);
assert_eq!(
marketplace.plugins[0].id,
"unlisted-linear@workspace-shared-with-me-unlisted"
"unlisted-linear@workspace-shared-with-me"
);
assert_eq!(
marketplace.plugins[0].remote_plugin_id.as_deref(),
@@ -300,74 +300,76 @@ async fn plugin_read_returns_share_context_for_shared_remote_plugin() -> Result<
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_plugin_read_request(PluginReadParams {
marketplace_path: None,
remote_marketplace_name: Some("workspace-shared-with-me-private".to_string()),
plugin_name: "plugins~Plugin_11111111111111111111111111111111".to_string(),
})
.await?;
for remote_marketplace_name in [
"workspace-shared-with-me-private",
"workspace-shared-with-me",
] {
let request_id = mcp
.send_plugin_read_request(PluginReadParams {
marketplace_path: None,
remote_marketplace_name: Some(remote_marketplace_name.to_string()),
plugin_name: "plugins~Plugin_11111111111111111111111111111111".to_string(),
})
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let response: PluginReadResponse = to_response(response)?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let response: PluginReadResponse = to_response(response)?;
assert_eq!(
response.plugin.marketplace_name,
"workspace-shared-with-me-private"
);
assert_eq!(
response.plugin.summary.id,
"shared-linear@workspace-shared-with-me-private"
);
assert_eq!(
response.plugin.summary.remote_plugin_id.as_deref(),
Some("plugins~Plugin_11111111111111111111111111111111")
);
let share_context = response
.plugin
.summary
.share_context
.as_ref()
.expect("expected share context");
assert_eq!(
share_context.remote_plugin_id,
"plugins~Plugin_11111111111111111111111111111111"
);
assert_eq!(share_context.remote_version.as_deref(), Some("2.3.4"));
assert_eq!(
share_context.discoverability,
Some(PluginShareDiscoverability::Private)
);
assert_eq!(
share_context.creator_account_user_id.as_deref(),
Some("user-gavin__account-123")
);
assert_eq!(share_context.creator_name.as_deref(), Some("Gavin"));
assert_eq!(
share_context.share_url.as_deref(),
Some("https://chatgpt.example/plugins/share/share-key-1")
);
assert_eq!(
share_context.share_principals,
Some(vec![
PluginSharePrincipal {
principal_type: PluginSharePrincipalType::User,
principal_id: "user-gavin__account-123".to_string(),
role: PluginSharePrincipalRole::Owner,
name: "Gavin".to_string(),
},
PluginSharePrincipal {
principal_type: PluginSharePrincipalType::User,
principal_id: "user-ada__account-123".to_string(),
role: PluginSharePrincipalRole::Reader,
name: "Ada".to_string(),
},
])
);
assert_eq!(response.plugin.marketplace_name, "workspace-shared-with-me");
assert_eq!(
response.plugin.summary.id,
"shared-linear@workspace-shared-with-me"
);
assert_eq!(
response.plugin.summary.remote_plugin_id.as_deref(),
Some("plugins~Plugin_11111111111111111111111111111111")
);
let share_context = response
.plugin
.summary
.share_context
.as_ref()
.expect("expected share context");
assert_eq!(
share_context.remote_plugin_id,
"plugins~Plugin_11111111111111111111111111111111"
);
assert_eq!(share_context.remote_version.as_deref(), Some("2.3.4"));
assert_eq!(
share_context.discoverability,
Some(PluginShareDiscoverability::Private)
);
assert_eq!(
share_context.creator_account_user_id.as_deref(),
Some("user-gavin__account-123")
);
assert_eq!(share_context.creator_name.as_deref(), Some("Gavin"));
assert_eq!(
share_context.share_url.as_deref(),
Some("https://chatgpt.example/plugins/share/share-key-1")
);
assert_eq!(
share_context.share_principals,
Some(vec![
PluginSharePrincipal {
principal_type: PluginSharePrincipalType::User,
principal_id: "user-gavin__account-123".to_string(),
role: PluginSharePrincipalRole::Owner,
name: "Gavin".to_string(),
},
PluginSharePrincipal {
principal_type: PluginSharePrincipalType::User,
principal_id: "user-ada__account-123".to_string(),
role: PluginSharePrincipalRole::Reader,
name: "Ada".to_string(),
},
])
);
}
Ok(())
}
@@ -169,7 +169,7 @@ async fn plugin_share_save_uploads_local_plugin() -> Result<()> {
PluginShareListResponse {
data: vec![PluginShareListItem {
plugin: PluginSummary {
id: "demo-plugin@workspace-shared-with-me-private".to_string(),
id: "demo-plugin@workspace-shared-with-me".to_string(),
remote_plugin_id: Some("plugins_123".to_string()),
local_version: None,
name: "demo-plugin".to_string(),
@@ -573,7 +573,7 @@ async fn plugin_share_list_returns_created_workspace_plugins() -> Result<()> {
PluginShareListResponse {
data: vec![PluginShareListItem {
plugin: PluginSummary {
id: "demo-plugin@workspace-shared-with-me-private".to_string(),
id: "demo-plugin@workspace-shared-with-me".to_string(),
remote_plugin_id: Some("plugins_123".to_string()),
local_version: None,
name: "demo-plugin".to_string(),
@@ -1123,7 +1123,7 @@ async fn plugin_share_delete_removes_created_workspace_plugin() -> Result<()> {
PluginShareListResponse {
data: vec![PluginShareListItem {
plugin: PluginSummary {
id: "demo-plugin@workspace-shared-with-me-private".to_string(),
id: "demo-plugin@workspace-shared-with-me".to_string(),
remote_plugin_id: Some("plugins_123".to_string()),
local_version: None,
name: "demo-plugin".to_string(),