Allow publishing plugins through share updates (#33908)

## What changed

- Accept `LISTED` discoverability in `plugin/share/updateTargets` and forward it to the remote plugin sharing API.
- Update the generated JSON schemas and TypeScript type with the new enum value.

## Testing

- Add an app-server integration test that publishes a workspace plugin and verifies the `LISTED` request and response.

GitOrigin-RevId: 978f4b94b318e772bf80347e58dd5b1d2819a653
This commit is contained in:
thomas
2026-07-17 21:29:51 +00:00
committed by copyberry
parent c1862b8db4
commit 3a06748458
9 changed files with 93 additions and 5 deletions
+2 -1
View File
@@ -2199,7 +2199,8 @@
"PluginShareUpdateDiscoverability": {
"enum": [
"UNLISTED",
"PRIVATE"
"PRIVATE",
"LISTED"
],
"type": "string"
},
@@ -14531,7 +14531,8 @@
"PluginShareUpdateDiscoverability": {
"enum": [
"UNLISTED",
"PRIVATE"
"PRIVATE",
"LISTED"
],
"type": "string"
},
@@ -10888,7 +10888,8 @@
"PluginShareUpdateDiscoverability": {
"enum": [
"UNLISTED",
"PRIVATE"
"PRIVATE",
"LISTED"
],
"type": "string"
},
@@ -38,7 +38,8 @@
"PluginShareUpdateDiscoverability": {
"enum": [
"UNLISTED",
"PRIVATE"
"PRIVATE",
"LISTED"
],
"type": "string"
}
@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PluginShareUpdateDiscoverability = "UNLISTED" | "PRIVATE";
export type PluginShareUpdateDiscoverability = "UNLISTED" | "PRIVATE" | "LISTED";
@@ -345,6 +345,9 @@ pub enum PluginShareUpdateDiscoverability {
#[serde(rename = "PRIVATE")]
#[ts(rename = "PRIVATE")]
Private,
#[serde(rename = "LISTED")]
#[ts(rename = "LISTED")]
Listed,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
@@ -255,6 +255,9 @@ fn remote_plugin_share_update_discoverability(
discoverability: PluginShareUpdateDiscoverability,
) -> codex_core_plugins::remote::RemotePluginShareUpdateDiscoverability {
match discoverability {
PluginShareUpdateDiscoverability::Listed => {
codex_core_plugins::remote::RemotePluginShareUpdateDiscoverability::Listed
}
PluginShareUpdateDiscoverability::Unlisted => {
codex_core_plugins::remote::RemotePluginShareUpdateDiscoverability::Unlisted
}
@@ -1078,6 +1078,82 @@ async fn plugin_share_update_targets_updates_share_targets() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn plugin_share_update_targets_publishes_workspace_plugin() -> Result<()> {
let codex_home = TempDir::new()?;
let server = MockServer::start().await;
write_remote_plugin_config(codex_home.path(), &format!("{}/backend-api", server.uri()))?;
write_chatgpt_auth(
codex_home.path(),
ChatGptAuthFixture::new("chatgpt-token")
.account_id("account-123")
.chatgpt_user_id("user-123")
.chatgpt_account_id("account-123"),
AuthCredentialsStoreMode::File,
)?;
Mock::given(method("PUT"))
.and(path("/backend-api/ps/plugins/plugins_123/shares"))
.and(header("authorization", "Bearer chatgpt-token"))
.and(header("chatgpt-account-id", "account-123"))
.and(body_json(json!({
"discoverability": "LISTED",
"targets": [],
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"principals": [
{
"principal_type": "user",
"principal_id": "owner-1",
"role": "owner",
"name": "Owner",
},
],
"discoverability": "LISTED",
})))
.expect(1)
.mount(&server)
.await;
let mut mcp = TestAppServer::builder()
.with_codex_home(codex_home.path())
.without_auto_env()
.build()
.await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_raw_request(
"plugin/share/updateTargets",
Some(json!({
"remotePluginId": "plugins_123",
"discoverability": "LISTED",
"shareTargets": [],
})),
)
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let response: PluginShareUpdateTargetsResponse = to_response(response)?;
assert_eq!(
response,
PluginShareUpdateTargetsResponse {
principals: vec![PluginSharePrincipal {
principal_type: PluginSharePrincipalType::User,
principal_id: "owner-1".to_string(),
role: PluginSharePrincipalRole::Owner,
name: "Owner".to_string(),
}],
discoverability: codex_app_server_protocol::PluginShareDiscoverability::Listed,
}
);
Ok(())
}
#[tokio::test]
async fn plugin_share_update_targets_rejects_when_plugin_sharing_disabled() -> Result<()> {
let codex_home = TempDir::new()?;
@@ -43,6 +43,7 @@ pub enum RemotePluginShareDiscoverability {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RemotePluginShareUpdateDiscoverability {
Listed,
Unlisted,
Private,
}
@@ -300,6 +301,7 @@ pub async fn update_remote_plugin_share_targets(
) -> Result<RemotePluginShareUpdateTargetsResult, RemotePluginCatalogError> {
let auth = ensure_chatgpt_auth(auth)?;
let target_discoverability = match discoverability {
RemotePluginShareUpdateDiscoverability::Listed => RemotePluginShareDiscoverability::Listed,
RemotePluginShareUpdateDiscoverability::Unlisted => {
RemotePluginShareDiscoverability::Unlisted
}