[codex] app-server: expose plugin install policy source (#31293)

## Summary

- Add `PluginInstallPolicySource` to the app-server v2 `PluginSummary`
payload.
- Propagate remote `installation_policy_source` through plugin
summaries, details, installed-plugin caching, and app-server responses.
- Preserve the supported source values and map unrecognized backend
values to `null`.
- Return `null` for local plugins and expose the field in generated JSON
and TypeScript schemas.
- Update app-server API documentation and plugin list/read coverage.

## Testing

- `just test -p codex-app-server-protocol` (251 passed)
- Targeted `codex-app-server` plugin list, installed, and read tests (4
passed)
- Targeted `codex-core-plugins` known and unknown policy source tests (2
passed)
- `just fmt`
- `git diff --check`
This commit is contained in:
Eric Ning
2026-07-06 17:12:39 -07:00
committed by GitHub
parent 3f61570044
commit c71895f63b
22 changed files with 269 additions and 6 deletions
@@ -13423,6 +13423,13 @@
],
"type": "string"
},
"PluginInstallPolicySource": {
"enum": [
"WORKSPACE_SETTING",
"IMPLICIT_CANONICAL_APP"
],
"type": "string"
},
"PluginInstallResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -14306,6 +14313,16 @@
"installPolicy": {
"$ref": "#/definitions/v2/PluginInstallPolicy"
},
"installPolicySource": {
"anyOf": [
{
"$ref": "#/definitions/v2/PluginInstallPolicySource"
},
{
"type": "null"
}
]
},
"installed": {
"type": "boolean"
},
@@ -9827,6 +9827,13 @@
],
"type": "string"
},
"PluginInstallPolicySource": {
"enum": [
"WORKSPACE_SETTING",
"IMPLICIT_CANONICAL_APP"
],
"type": "string"
},
"PluginInstallResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -10710,6 +10717,16 @@
"installPolicy": {
"$ref": "#/definitions/PluginInstallPolicy"
},
"installPolicySource": {
"anyOf": [
{
"$ref": "#/definitions/PluginInstallPolicySource"
},
{
"type": "null"
}
]
},
"installed": {
"type": "boolean"
},
@@ -63,6 +63,13 @@
],
"type": "string"
},
"PluginInstallPolicySource": {
"enum": [
"WORKSPACE_SETTING",
"IMPLICIT_CANONICAL_APP"
],
"type": "string"
},
"PluginInterface": {
"properties": {
"brandColor": {
@@ -489,6 +496,16 @@
"installPolicy": {
"$ref": "#/definitions/PluginInstallPolicy"
},
"installPolicySource": {
"anyOf": [
{
"$ref": "#/definitions/PluginInstallPolicySource"
},
{
"type": "null"
}
]
},
"installed": {
"type": "boolean"
},
@@ -63,6 +63,13 @@
],
"type": "string"
},
"PluginInstallPolicySource": {
"enum": [
"WORKSPACE_SETTING",
"IMPLICIT_CANONICAL_APP"
],
"type": "string"
},
"PluginInterface": {
"properties": {
"brandColor": {
@@ -489,6 +496,16 @@
"installPolicy": {
"$ref": "#/definitions/PluginInstallPolicy"
},
"installPolicySource": {
"anyOf": [
{
"$ref": "#/definitions/PluginInstallPolicySource"
},
{
"type": "null"
}
]
},
"installed": {
"type": "boolean"
},
@@ -242,6 +242,13 @@
],
"type": "string"
},
"PluginInstallPolicySource": {
"enum": [
"WORKSPACE_SETTING",
"IMPLICIT_CANONICAL_APP"
],
"type": "string"
},
"PluginInterface": {
"properties": {
"brandColor": {
@@ -629,6 +636,16 @@
"installPolicy": {
"$ref": "#/definitions/PluginInstallPolicy"
},
"installPolicySource": {
"anyOf": [
{
"$ref": "#/definitions/PluginInstallPolicySource"
},
{
"type": "null"
}
]
},
"installed": {
"type": "boolean"
},
@@ -37,6 +37,13 @@
],
"type": "string"
},
"PluginInstallPolicySource": {
"enum": [
"WORKSPACE_SETTING",
"IMPLICIT_CANONICAL_APP"
],
"type": "string"
},
"PluginInterface": {
"properties": {
"brandColor": {
@@ -445,6 +452,16 @@
"installPolicy": {
"$ref": "#/definitions/PluginInstallPolicy"
},
"installPolicySource": {
"anyOf": [
{
"$ref": "#/definitions/PluginInstallPolicySource"
},
{
"type": "null"
}
]
},
"installed": {
"type": "boolean"
},
@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PluginInstallPolicySource = "WORKSPACE_SETTING" | "IMPLICIT_CANONICAL_APP";
@@ -4,6 +4,7 @@
import type { PluginAuthPolicy } from "./PluginAuthPolicy";
import type { PluginAvailability } from "./PluginAvailability";
import type { PluginInstallPolicy } from "./PluginInstallPolicy";
import type { PluginInstallPolicySource } from "./PluginInstallPolicySource";
import type { PluginInterface } from "./PluginInterface";
import type { PluginShareContext } from "./PluginShareContext";
import type { PluginSource } from "./PluginSource";
@@ -24,7 +25,7 @@ localVersion: string | null, name: string,
/**
* Remote sharing context associated with this plugin when available.
*/
shareContext: PluginShareContext | null, source: PluginSource, installed: boolean, enabled: boolean, installPolicy: PluginInstallPolicy, authPolicy: PluginAuthPolicy,
shareContext: PluginShareContext | null, source: PluginSource, installed: boolean, enabled: boolean, installPolicy: PluginInstallPolicy, installPolicySource: PluginInstallPolicySource | null, authPolicy: PluginAuthPolicy,
/**
* Availability state for installing and using the plugin.
*/
@@ -294,6 +294,7 @@ export type { PluginDetail } from "./PluginDetail";
export type { PluginHookSummary } from "./PluginHookSummary";
export type { PluginInstallParams } from "./PluginInstallParams";
export type { PluginInstallPolicy } from "./PluginInstallPolicy";
export type { PluginInstallPolicySource } from "./PluginInstallPolicySource";
export type { PluginInstallResponse } from "./PluginInstallResponse";
export type { PluginInstalledParams } from "./PluginInstalledParams";
export type { PluginInstalledResponse } from "./PluginInstalledResponse";
@@ -567,6 +567,17 @@ pub enum PluginInstallPolicy {
InstalledByDefault,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum PluginInstallPolicySource {
#[serde(rename = "WORKSPACE_SETTING")]
#[ts(rename = "WORKSPACE_SETTING")]
WorkspaceSetting,
#[serde(rename = "IMPLICIT_CANONICAL_APP")]
#[ts(rename = "IMPLICIT_CANONICAL_APP")]
ImplicitCanonicalApp,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum PluginAuthPolicy {
@@ -613,6 +624,7 @@ pub struct PluginSummary {
pub installed: bool,
pub enabled: bool,
pub install_policy: PluginInstallPolicy,
pub install_policy_source: Option<PluginInstallPolicySource>,
pub auth_policy: PluginAuthPolicy,
/// Availability state for installing and using the plugin.
#[serde(default)]
@@ -3650,6 +3650,7 @@ fn plugin_share_list_response_serializes_share_items() {
installed: false,
enabled: false,
install_policy: PluginInstallPolicy::Available,
install_policy_source: Some(PluginInstallPolicySource::WorkspaceSetting),
auth_policy: PluginAuthPolicy::OnUse,
availability: PluginAvailability::Available,
interface: None,
@@ -3672,6 +3673,7 @@ fn plugin_share_list_response_serializes_share_items() {
"installed": false,
"enabled": false,
"installPolicy": "AVAILABLE",
"installPolicySource": "WORKSPACE_SETTING",
"authPolicy": "ON_USE",
"availability": "AVAILABLE",
"interface": null,
+2 -2
View File
@@ -213,8 +213,8 @@ Example with notification opt-out:
- `marketplace/add` — add a remote plugin marketplace from an HTTP(S) Git URL, SSH Git URL, or GitHub `owner/repo` shorthand, then persist it into the user marketplace config. Returns the installed root path plus whether the marketplace was already present.
- `marketplace/remove` — remove a configured marketplace by name from the user marketplace config, and delete its installed marketplace root when one exists.
- `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, 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. Clients can explicitly request the remote `workspace-directory`, `shared-with-me`, or `created-by-me-remote` marketplace kinds. `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. 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/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. Clients can explicitly request the remote `workspace-directory`, `shared-with-me`, or `created-by-me-remote` marketplace kinds. `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 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/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.
@@ -163,6 +163,7 @@ fn convert_configured_marketplace_plugin_to_plugin_summary(
share_context,
source: marketplace_plugin_source_to_info(plugin.source),
install_policy: plugin.policy.installation.into(),
install_policy_source: None,
auth_policy: plugin.policy.authentication.into(),
availability: PluginAvailability::Available,
interface: plugin.interface.map(local_plugin_interface_to_info),
@@ -1104,6 +1105,7 @@ impl PluginRequestProcessor {
installed: outcome.plugin.installed,
enabled: outcome.plugin.enabled,
install_policy: outcome.plugin.policy.installation.into(),
install_policy_source: None,
auth_policy: outcome.plugin.policy.authentication.into(),
availability: PluginAvailability::Available,
interface: outcome.plugin.interface.map(local_plugin_interface_to_info),
@@ -2180,6 +2182,7 @@ fn remote_plugin_summary_to_info(summary: RemoteCatalogPluginSummary) -> PluginS
installed: summary.installed,
enabled: summary.enabled,
install_policy: summary.install_policy,
install_policy_source: summary.install_policy_source,
auth_policy: summary.auth_policy,
availability: summary.availability,
interface: summary.interface,
@@ -9,6 +9,7 @@ use app_test_support::write_chatgpt_auth;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::PluginAuthPolicy;
use codex_app_server_protocol::PluginInstallPolicy;
use codex_app_server_protocol::PluginInstallPolicySource;
use codex_app_server_protocol::PluginInstalledParams;
use codex_app_server_protocol::PluginInstalledResponse;
use codex_app_server_protocol::PluginListMarketplaceKind;
@@ -196,6 +197,12 @@ enabled = true
]
);
assert_eq!(response.marketplace_load_errors, Vec::new());
assert!(
response.marketplaces[0]
.plugins
.iter()
.all(|plugin| plugin.install_policy_source.is_none())
);
Ok(())
}
@@ -289,11 +296,17 @@ enabled = true
remote_marketplace
.plugins
.iter()
.map(|plugin| plugin.id.clone())
.map(|plugin| (plugin.id.clone(), plugin.install_policy_source))
.collect::<Vec<_>>(),
vec![
"linear@openai-curated-remote".to_string(),
"remote-only@openai-curated-remote".to_string(),
(
"linear@openai-curated-remote".to_string(),
Some(PluginInstallPolicySource::WorkspaceSetting),
),
(
"remote-only@openai-curated-remote".to_string(),
Some(PluginInstallPolicySource::WorkspaceSetting),
),
]
);
assert_eq!(response.marketplace_load_errors, Vec::new());
@@ -462,6 +475,7 @@ async fn plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_
installed: false,
enabled: false,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
auth_policy: PluginAuthPolicy::OnInstall,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: None,
@@ -754,6 +768,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab
installed: false,
enabled: false,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
auth_policy: PluginAuthPolicy::OnInstall,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: Some(codex_app_server_protocol::PluginInterface {
@@ -794,6 +809,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab
installed: false,
enabled: false,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
auth_policy: PluginAuthPolicy::OnInstall,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: None,
@@ -1683,6 +1699,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
"name": "linear",
"scope": "GLOBAL",
"installation_policy": "AVAILABLE",
"installation_policy_source": "IMPLICIT_CANONICAL_APP",
"authentication_policy": "ON_USE",
"status": "ENABLED",
"release": {
@@ -1722,6 +1739,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
"name": "linear",
"scope": "GLOBAL",
"installation_policy": "AVAILABLE",
"installation_policy_source": "WORKSPACE_SETTING",
"authentication_policy": "ON_USE",
"status": "ENABLED",
"release": {
@@ -1845,6 +1863,10 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
);
assert_eq!(remote_marketplace.plugins[0].installed, true);
assert_eq!(remote_marketplace.plugins[0].enabled, true);
assert_eq!(
remote_marketplace.plugins[0].install_policy_source,
Some(PluginInstallPolicySource::ImplicitCanonicalApp)
);
assert_eq!(
remote_marketplace.plugins[0].availability,
codex_app_server_protocol::PluginAvailability::Available
@@ -1880,6 +1902,10 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
let cached_catalog: serde_json::Value =
serde_json::from_slice(&std::fs::read(&cache_files[0])?)?;
assert_eq!(cached_catalog["schema_version"], serde_json::json!(1));
assert_eq!(
cached_catalog["plugins"][0]["installation_policy_source"],
serde_json::json!("IMPLICIT_CANONICAL_APP")
);
assert_eq!(
cached_catalog["plugins"][0]["release"]["interface"]["default_prompts"],
serde_json::json!(["Create a Linear issue", "Review my Linear projects"])
@@ -3938,6 +3964,7 @@ fn remote_installed_plugin_body_with_optional_app_manifest(
"name": "linear",
"scope": "GLOBAL",
"installation_policy": "AVAILABLE",
"installation_policy_source": "WORKSPACE_SETTING",
"authentication_policy": "ON_USE",
"release": {{
"version": "{release_version}",
@@ -25,6 +25,7 @@ use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::PluginAuthPolicy;
use codex_app_server_protocol::PluginAvailability;
use codex_app_server_protocol::PluginInstallPolicy;
use codex_app_server_protocol::PluginInstallPolicySource;
use codex_app_server_protocol::PluginReadParams;
use codex_app_server_protocol::PluginReadResponse;
use codex_app_server_protocol::PluginShareDiscoverability;
@@ -144,6 +145,7 @@ apps = true
"name": "example-plugin",
"scope": "GLOBAL",
"installation_policy": "AVAILABLE",
"installation_policy_source": "IMPLICIT_CANONICAL_APP",
"authentication_policy": "ON_USE",
"release": {
"version": "1.2.1",
@@ -267,6 +269,10 @@ apps = true
assert_eq!(response.plugin.summary.name, "example-plugin");
assert_eq!(response.plugin.summary.source, PluginSource::Remote);
assert_eq!(response.plugin.summary.share_context, None);
assert_eq!(
response.plugin.summary.install_policy_source,
Some(PluginInstallPolicySource::ImplicitCanonicalApp)
);
assert_eq!(
response
.plugin
@@ -179,6 +179,7 @@ async fn plugin_share_save_uploads_local_plugin() -> Result<()> {
installed: true,
enabled: true,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
auth_policy: PluginAuthPolicy::OnUse,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: Some(expected_plugin_interface()),
@@ -583,6 +584,7 @@ async fn plugin_share_list_returns_created_workspace_plugins() -> Result<()> {
installed: true,
enabled: true,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
auth_policy: PluginAuthPolicy::OnUse,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: Some(expected_plugin_interface()),
@@ -1184,6 +1186,7 @@ async fn plugin_share_delete_removes_created_workspace_plugin() -> Result<()> {
installed: true,
enabled: true,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
auth_policy: PluginAuthPolicy::OnUse,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: Some(expected_plugin_interface()),
@@ -781,6 +781,7 @@ fn remote_installed_plugin_in_marketplace(
name: name.to_string(),
enabled: true,
install_policy: codex_app_server_protocol::PluginInstallPolicy::Available,
install_policy_source: None,
auth_policy: codex_app_server_protocol::PluginAuthPolicy::OnUse,
availability: codex_app_server_protocol::PluginAvailability::Available,
interface: None,
+31
View File
@@ -6,6 +6,7 @@ use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::PluginAuthPolicy;
use codex_app_server_protocol::PluginAvailability;
use codex_app_server_protocol::PluginInstallPolicy;
use codex_app_server_protocol::PluginInstallPolicySource;
use codex_app_server_protocol::PluginInterface;
use codex_app_server_protocol::SkillInterface;
use codex_login::CodexAuth;
@@ -152,6 +153,7 @@ pub struct RemoteInstalledPlugin {
pub name: String,
pub enabled: bool,
pub install_policy: PluginInstallPolicy,
pub install_policy_source: Option<PluginInstallPolicySource>,
pub auth_policy: PluginAuthPolicy,
pub availability: PluginAvailability,
pub interface: Option<PluginInterface>,
@@ -169,6 +171,7 @@ pub struct RemotePluginSummary {
pub installed: bool,
pub enabled: bool,
pub install_policy: PluginInstallPolicy,
pub install_policy_source: Option<PluginInstallPolicySource>,
pub auth_policy: PluginAuthPolicy,
pub availability: PluginAvailability,
pub interface: Option<PluginInterface>,
@@ -528,6 +531,26 @@ struct RemoteAppTemplateResponse {
reason: Option<RemoteAppTemplateUnavailableReason>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
enum RemotePluginInstallPolicySource {
#[serde(rename = "WORKSPACE_SETTING")]
WorkspaceSetting,
#[serde(rename = "IMPLICIT_CANONICAL_APP")]
ImplicitCanonicalApp,
#[serde(other)]
Unknown,
}
impl RemotePluginInstallPolicySource {
fn into_protocol(self) -> Option<PluginInstallPolicySource> {
match self {
Self::WorkspaceSetting => Some(PluginInstallPolicySource::WorkspaceSetting),
Self::ImplicitCanonicalApp => Some(PluginInstallPolicySource::ImplicitCanonicalApp),
Self::Unknown => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
struct RemotePluginDirectoryItem {
id: String,
@@ -544,6 +567,7 @@ struct RemotePluginDirectoryItem {
#[serde(default)]
share_principals: Option<Vec<RemotePluginDirectorySharePrincipal>>,
installation_policy: PluginInstallPolicy,
installation_policy_source: Option<RemotePluginInstallPolicySource>,
authentication_policy: PluginAuthPolicy,
#[serde(rename = "status", default)]
availability: PluginAvailability,
@@ -1050,6 +1074,7 @@ pub fn group_remote_installed_plugins_by_marketplaces(
installed: true,
enabled: plugin.enabled,
install_policy: plugin.install_policy,
install_policy_source: plugin.install_policy_source,
auth_policy: plugin.auth_policy,
availability: plugin.availability,
interface: plugin.interface.clone(),
@@ -1483,6 +1508,9 @@ fn build_remote_plugin_summary(
installed: installed_plugin.is_some(),
enabled: installed_plugin.is_some_and(|plugin| plugin.enabled),
install_policy: plugin.installation_policy,
install_policy_source: plugin
.installation_policy_source
.and_then(RemotePluginInstallPolicySource::into_protocol),
auth_policy: plugin.authentication_policy,
availability: plugin.availability,
interface: remote_plugin_interface_to_info(plugin),
@@ -1562,6 +1590,9 @@ fn remote_installed_plugin_to_cache_entry(
name: plugin.name.clone(),
enabled: installed_plugin.enabled,
install_policy: plugin.installation_policy,
install_policy_source: plugin
.installation_policy_source
.and_then(RemotePluginInstallPolicySource::into_protocol),
auth_policy: plugin.authentication_policy,
availability: plugin.availability,
interface: remote_plugin_interface_to_info(plugin),
@@ -648,6 +648,7 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {
installed: false,
enabled: false,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
auth_policy: PluginAuthPolicy::OnUse,
availability: PluginAvailability::Available,
interface: Some(expected_plugin_interface()),
@@ -687,6 +688,7 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {
installed: true,
enabled: true,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
auth_policy: PluginAuthPolicy::OnUse,
availability: PluginAvailability::Available,
interface: Some(expected_plugin_interface()),
+64
View File
@@ -33,6 +33,69 @@ fn build_remote_marketplace_preserves_directory_order_and_appends_installed_only
);
}
#[test]
fn installation_policy_source_is_preserved_across_remote_summary_paths() {
let mut directory_plugin = directory_plugin("plugin-linear", "linear");
directory_plugin.installation_policy_source =
Some(RemotePluginInstallPolicySource::ImplicitCanonicalApp);
let installed_plugin = RemotePluginInstalledItem {
plugin: directory_plugin.clone(),
enabled: true,
disabled_skill_names: Vec::new(),
};
let marketplace = build_remote_marketplace(
REMOTE_GLOBAL_MARKETPLACE_NAME,
REMOTE_GLOBAL_MARKETPLACE_DISPLAY_NAME,
vec![directory_plugin],
vec![installed_plugin.clone()],
/*include_installed_only*/ false,
)
.expect("marketplace should be valid")
.expect("marketplace should not be empty");
assert_eq!(
marketplace
.plugins
.into_iter()
.map(|plugin| plugin.install_policy_source)
.collect::<Vec<_>>(),
vec![Some(PluginInstallPolicySource::ImplicitCanonicalApp)]
);
let mut installed_plugin = installed_plugin;
installed_plugin.plugin.installation_policy_source =
Some(RemotePluginInstallPolicySource::WorkspaceSetting);
let installed_plugin = remote_installed_plugin_to_cache_entry(&installed_plugin)
.expect("installed plugin should be valid");
let marketplaces = group_remote_installed_plugins_by_marketplaces(
&[installed_plugin],
&[REMOTE_GLOBAL_MARKETPLACE_NAME],
);
assert_eq!(
marketplaces
.into_iter()
.flat_map(|marketplace| marketplace.plugins)
.map(|plugin| plugin.install_policy_source)
.collect::<Vec<_>>(),
vec![Some(PluginInstallPolicySource::WorkspaceSetting)]
);
}
#[test]
fn unknown_installation_policy_source_maps_to_none() {
let plugin = directory_plugin("plugin-linear", "linear");
let mut plugin_json = serde_json::to_value(plugin).expect("plugin should serialize");
plugin_json["installation_policy_source"] =
serde_json::Value::String("FUTURE_POLICY_SOURCE".to_string());
let plugin: RemotePluginDirectoryItem =
serde_json::from_value(plugin_json).expect("unknown source should deserialize");
let summary = build_remote_plugin_summary(&plugin, /*installed_plugin*/ None)
.expect("summary should be valid");
assert_eq!(summary.install_policy_source, None);
}
fn directory_plugin(id: &str, name: &str) -> RemotePluginDirectoryItem {
RemotePluginDirectoryItem {
id: id.to_string(),
@@ -44,6 +107,7 @@ fn directory_plugin(id: &str, name: &str) -> RemotePluginDirectoryItem {
share_url: None,
share_principals: None,
installation_policy: PluginInstallPolicy::Available,
installation_policy_source: None,
authentication_policy: PluginAuthPolicy::OnUse,
availability: PluginAvailability::Available,
release: RemotePluginReleaseResponse {
+1
View File
@@ -173,6 +173,7 @@ mod tests {
installed: true,
enabled: true,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
auth_policy: PluginAuthPolicy::OnInstall,
availability: PluginAvailability::Available,
interface: None,
@@ -1341,6 +1341,7 @@ pub(super) fn plugins_test_summary(
installed,
enabled,
install_policy,
install_policy_source: None,
auth_policy: PluginAuthPolicy::OnInstall,
availability: PluginAvailability::Available,
interface: Some(plugins_test_interface(
@@ -1370,6 +1371,7 @@ pub(super) fn plugins_test_remote_summary(
installed,
enabled: true,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
auth_policy: PluginAuthPolicy::OnInstall,
availability: PluginAvailability::Available,
interface: Some(plugins_test_interface(