diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 4cea1b4a09..fa0496486d 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -17458,6 +17458,13 @@ } ] }, + "iconLargeUrl": { + "description": "Remote large icon URL from the plugin catalog.", + "type": [ + "string", + "null" + ] + }, "iconSmall": { "anyOf": [ { @@ -17468,6 +17475,13 @@ } ] }, + "iconSmallUrl": { + "description": "Remote small icon URL from the plugin catalog.", + "type": [ + "string", + "null" + ] + }, "shortDescription": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 41846f0c51..d0cc017025 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -15222,6 +15222,13 @@ } ] }, + "iconLargeUrl": { + "description": "Remote large icon URL from the plugin catalog.", + "type": [ + "string", + "null" + ] + }, "iconSmall": { "anyOf": [ { @@ -15232,6 +15239,13 @@ } ] }, + "iconSmallUrl": { + "description": "Remote small icon URL from the plugin catalog.", + "type": [ + "string", + "null" + ] + }, "shortDescription": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/json/v2/PluginReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/PluginReadResponse.json index b07eb287dd..c05dc55469 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/PluginReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/PluginReadResponse.json @@ -902,6 +902,13 @@ } ] }, + "iconLargeUrl": { + "description": "Remote large icon URL from the plugin catalog.", + "type": [ + "string", + "null" + ] + }, "iconSmall": { "anyOf": [ { @@ -912,6 +919,13 @@ } ] }, + "iconSmallUrl": { + "description": "Remote small icon URL from the plugin catalog.", + "type": [ + "string", + "null" + ] + }, "shortDescription": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/json/v2/SkillsListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/SkillsListResponse.json index 6c72bfbb68..3040d57e40 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/SkillsListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/SkillsListResponse.json @@ -64,6 +64,13 @@ } ] }, + "iconLargeUrl": { + "description": "Remote large icon URL from the plugin catalog.", + "type": [ + "string", + "null" + ] + }, "iconSmall": { "anyOf": [ { @@ -74,6 +81,13 @@ } ] }, + "iconSmallUrl": { + "description": "Remote small icon URL from the plugin catalog.", + "type": [ + "string", + "null" + ] + }, "shortDescription": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillInterface.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillInterface.ts index 2361afcf0f..1ac1db846c 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/SkillInterface.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillInterface.ts @@ -3,4 +3,12 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { AbsolutePathBuf } from "../AbsolutePathBuf"; -export type SkillInterface = { displayName?: string, shortDescription?: string, iconSmall?: AbsolutePathBuf, iconLarge?: AbsolutePathBuf, brandColor?: string, defaultPrompt?: string, }; +export type SkillInterface = { displayName?: string, shortDescription?: string, iconSmall?: AbsolutePathBuf, iconLarge?: AbsolutePathBuf, +/** + * Remote small icon URL from the plugin catalog. + */ +iconSmallUrl: string | null, +/** + * Remote large icon URL from the plugin catalog. + */ +iconLargeUrl: string | null, brandColor?: string, defaultPrompt?: string, }; diff --git a/codex-rs/app-server-protocol/src/protocol/v2/plugin.rs b/codex-rs/app-server-protocol/src/protocol/v2/plugin.rs index 146da5386c..97c9ad13fc 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/plugin.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/plugin.rs @@ -449,6 +449,10 @@ pub struct SkillInterface { pub icon_small: Option, #[ts(optional)] pub icon_large: Option, + /// Remote small icon URL from the plugin catalog. + pub icon_small_url: Option, + /// Remote large icon URL from the plugin catalog. + pub icon_large_url: Option, #[ts(optional)] pub brand_color: Option, #[ts(optional)] @@ -908,6 +912,8 @@ impl From for SkillInterface { default_prompt: value.default_prompt, icon_small: value.icon_small, icon_large: value.icon_large, + icon_small_url: None, + icon_large_url: None, } } } diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 9fd8933654..ab1dc22230 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -218,7 +218,7 @@ Example with notification opt-out: - `marketplace/upgrade` — upgrade all configured Git plugin marketplaces, or one named marketplace when `marketplaceName` is provided. Returns selected marketplace names, upgraded roots, and per-marketplace errors. - `plugin/list` — list discovered plugin marketplaces and plugin state, including effective marketplace install/auth policy metadata, nullable remote install-policy provenance in `installPolicySource` (`WORKSPACE_SETTING` or `IMPLICIT_CANONICAL_APP`), the remote marketplace `version` and locally materialized `localVersion` when available, plugin `availability` (`AVAILABLE` by default or `DISABLED_BY_ADMIN` for remote plugins blocked upstream), fail-open `marketplaceLoadErrors` entries for marketplace files that could not be parsed or loaded, and best-effort `featuredPluginIds` for the official curated marketplace. Every `PluginSummary` returned by plugin list, installed, read, and share-list methods includes `mustShowInstallationInterstitial`: remote service values preserve `true` or `false`, while local plugins and remote responses that omit the policy return `null`. Clients should fail closed when the value is `null`. Clients can explicitly request the remote `workspace-directory`, `shared-with-me`, or `created-by-me-remote` marketplace kinds. Set `forceRefetch: true` to bypass TTL-backed remote catalog caches for the requested marketplaces and wait for fresh data; cache entries are replaced only after a successful fetch. When local marketplaces are included, the request also waits for configured plugin caches to reconcile before marketplace summaries are returned. At app-server startup, existing cached catalogs remain available to `plugin/list` while they refresh in the background. `interface.category` uses the marketplace category when present; otherwise it falls back to the plugin manifest category (**under development; do not call from production clients yet**). - `plugin/installed` — list installed plugin rows plus any explicitly requested local install-suggestion plugin names, without fetching the broader remote catalog. Remote rows include nullable `installPolicySource`; local rows return `null`. Mention surfaces can use this narrower view when they need plugin mention payloads rather than plugin-page discovery data (**under development; do not call from production clients yet**). -- `plugin/read` — read one plugin by `marketplacePath` plus `pluginName`, returning marketplace info, a list-style `summary`, manifest descriptions/interface metadata, and bundled skills/hooks/apps/MCP server names. Remote plugin details can include scheduled task summaries from the catalog; `scheduledTasks: null` means the metadata is unavailable, while an empty array means the catalog found no scheduled tasks. Remote plugin details expose the canonical `shareUrl` supplied by the remote catalog when available; it is `null` for local plugins or when the catalog omits it. This field is separate from `summary.shareContext`, which continues to describe user and workspace sharing state. Returned plugin skills include their current `enabled` state after local config filtering; bundled hooks are returned as lightweight declaration summaries keyed for correlation with `hooks/list`. Use `plugin/install`'s `appsNeedingAuth` to drive post-install authentication and `app/list`'s `isAccessible` to determine current connector accessibility (**under development; do not call from production clients yet**). +- `plugin/read` — read one plugin by `marketplacePath` plus `pluginName`, returning marketplace info, a list-style `summary`, manifest descriptions/interface metadata, and bundled skills/hooks/apps/MCP server names. Remote plugin details can include scheduled task summaries from the catalog; `scheduledTasks: null` means the metadata is unavailable, while an empty array means the catalog found no scheduled tasks. Remote plugin details expose the canonical `shareUrl` supplied by the remote catalog when available; it is `null` for local plugins or when the catalog omits it. This field is separate from `summary.shareContext`, which continues to describe user and workspace sharing state. Remote skill interfaces expose `iconSmallUrl` and `iconLargeUrl` when the catalog supplies icon URLs. Returned plugin skills include their current `enabled` state after local config filtering; bundled hooks are returned as lightweight declaration summaries keyed for correlation with `hooks/list`. Use `plugin/install`'s `appsNeedingAuth` to drive post-install authentication and `app/list`'s `isAccessible` to determine current connector accessibility (**under development; do not call from production clients yet**). - `plugin/skill/read` — read remote plugin skill markdown on demand by `remoteMarketplaceName`, `remotePluginId`, and `skillName`. This lets clients preview uninstalled remote plugin skills without downloading the plugin bundle. - `skills/changed` — notification emitted when watched local skill files change. - `app/installed` — read installed connector runtime state from the last committed snapshot, optionally refreshing it first. diff --git a/codex-rs/app-server/src/request_processors/catalog_processor.rs b/codex-rs/app-server/src/request_processors/catalog_processor.rs index 860e188cfe..add2648806 100644 --- a/codex-rs/app-server/src/request_processors/catalog_processor.rs +++ b/codex-rs/app-server/src/request_processors/catalog_processor.rs @@ -33,6 +33,8 @@ fn skills_to_info( short_description: interface.short_description, icon_small: interface.icon_small, icon_large: interface.icon_large, + icon_small_url: None, + icon_large_url: None, brand_color: interface.brand_color, default_prompt: interface.default_prompt, } diff --git a/codex-rs/app-server/src/request_processors/plugins.rs b/codex-rs/app-server/src/request_processors/plugins.rs index a694a24ee4..61e0ab7a78 100644 --- a/codex-rs/app-server/src/request_processors/plugins.rs +++ b/codex-rs/app-server/src/request_processors/plugins.rs @@ -59,6 +59,8 @@ fn plugin_skills_to_info( short_description: interface.short_description, icon_small: interface.icon_small, icon_large: interface.icon_large, + icon_small_url: None, + icon_large_url: None, brand_color: interface.brand_color, default_prompt: interface.default_prompt, } diff --git a/codex-rs/app-server/tests/suite/v2/plugin_read.rs b/codex-rs/app-server/tests/suite/v2/plugin_read.rs index 1f2f381ae1..bd1e85e50b 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_read.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_read.rs @@ -39,6 +39,7 @@ use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ScheduledTaskSchedule; use codex_app_server_protocol::ScheduledTaskSummary; use codex_app_server_protocol::ScheduledTaskWeekday; +use codex_app_server_protocol::SkillInterface; use codex_config::types::AuthCredentialsStoreMode; use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; @@ -575,7 +576,9 @@ async fn plugin_read_includes_share_url_for_admin_disabled_remote_plugin() -> Re "plugin_release_skill_id": "skill-1", "interface": { "display_name": "Plan Work", - "short_description": "Create a plan from issues" + "short_description": "Create a plan from issues", + "icon_small_url": "https://example.com/plan-work-small.svg", + "icon_large_url": "https://example.com/plan-work-large.png" } } ] @@ -703,6 +706,19 @@ async fn plugin_read_includes_share_url_for_admin_disabled_remote_plugin() -> Re assert_eq!(response.plugin.skills[0].name, "plan-work"); assert_eq!(response.plugin.skills[0].path, None); assert_eq!(response.plugin.skills[0].enabled, false); + assert_eq!( + response.plugin.skills[0].interface, + Some(SkillInterface { + display_name: Some("Plan Work".to_string()), + short_description: Some("Create a plan from issues".to_string()), + icon_small: None, + icon_large: None, + icon_small_url: Some("https://example.com/plan-work-small.svg".to_string()), + icon_large_url: Some("https://example.com/plan-work-large.png".to_string()), + brand_color: None, + default_prompt: None, + }) + ); assert_eq!(response.plugin.apps.len(), 0); assert_eq!( response.plugin.app_templates, diff --git a/codex-rs/core-plugins/src/remote.rs b/codex-rs/core-plugins/src/remote.rs index da5816510f..ab19fd1086 100644 --- a/codex-rs/core-plugins/src/remote.rs +++ b/codex-rs/core-plugins/src/remote.rs @@ -1782,11 +1782,15 @@ fn remote_skill_interface_to_info( short_description: interface.short_description, icon_small: None, icon_large: None, + icon_small_url: interface.icon_small_url, + icon_large_url: interface.icon_large_url, brand_color: interface.brand_color, default_prompt: interface.default_prompt, }; let has_fields = result.display_name.is_some() || result.short_description.is_some() + || result.icon_small_url.is_some() + || result.icon_large_url.is_some() || result.brand_color.is_some() || result.default_prompt.is_some(); has_fields.then_some(result) diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 2404ad13ca..9aeb009170 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -7329,6 +7329,8 @@ mod tests { short_description: None, icon_small: None, icon_large: None, + icon_small_url: None, + icon_large_url: None, brand_color: None, default_prompt: None, }), @@ -7444,6 +7446,8 @@ mod tests { short_description: None, icon_small: None, icon_large: None, + icon_small_url: None, + icon_large_url: None, brand_color: None, default_prompt: None, }),