mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat: support product-scoped plugins. (#15041)
1. Added SessionSource::Custom(String) and --session-source. 2. Enforced plugin and skill products by session_source. 3. Applied the same filtering to curated background refresh.
This commit is contained in:
committed by
GitHub
Unverified
parent
01df50cf42
commit
db5781a088
@@ -42,6 +42,7 @@ use crate::skills::loader::SkillRoot;
|
||||
use crate::skills::loader::load_skills_from_roots;
|
||||
use codex_app_server_protocol::ConfigValueWriteParams;
|
||||
use codex_app_server_protocol::MergeStrategy;
|
||||
use codex_protocol::protocol::Product;
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use serde::Deserialize;
|
||||
@@ -461,16 +462,32 @@ pub struct PluginsManager {
|
||||
store: PluginStore,
|
||||
featured_plugin_ids_cache: RwLock<Option<CachedFeaturedPluginIds>>,
|
||||
cached_enabled_outcome: RwLock<Option<PluginLoadOutcome>>,
|
||||
restriction_product: Option<Product>,
|
||||
analytics_events_client: RwLock<Option<AnalyticsEventsClient>>,
|
||||
}
|
||||
|
||||
impl PluginsManager {
|
||||
pub fn new(codex_home: PathBuf) -> Self {
|
||||
Self::new_with_restriction_product(codex_home, Some(Product::Codex))
|
||||
}
|
||||
|
||||
pub fn new_with_restriction_product(
|
||||
codex_home: PathBuf,
|
||||
restriction_product: Option<Product>,
|
||||
) -> Self {
|
||||
// Product restrictions are enforced at marketplace admission time for a given CODEX_HOME:
|
||||
// listing, install, and curated refresh all consult this restriction context before new
|
||||
// plugins enter local config or cache. After admission, runtime plugin loading trusts the
|
||||
// contents of that CODEX_HOME and does not re-filter configured plugins by product, so
|
||||
// already-admitted plugins may continue exposing MCP servers/tools from shared local state.
|
||||
//
|
||||
// This assumes a single CODEX_HOME is only used by one product.
|
||||
Self {
|
||||
codex_home: codex_home.clone(),
|
||||
store: PluginStore::new(codex_home),
|
||||
featured_plugin_ids_cache: RwLock::new(None),
|
||||
cached_enabled_outcome: RwLock::new(None),
|
||||
restriction_product,
|
||||
analytics_events_client: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
@@ -483,6 +500,13 @@ impl PluginsManager {
|
||||
*stored_client = Some(analytics_events_client);
|
||||
}
|
||||
|
||||
fn restriction_product_matches(&self, products: &[Product]) -> bool {
|
||||
products.is_empty()
|
||||
|| self
|
||||
.restriction_product
|
||||
.is_some_and(|product| product.matches_product_restriction(products))
|
||||
}
|
||||
|
||||
pub fn plugins_for_config(&self, config: &Config) -> PluginLoadOutcome {
|
||||
self.plugins_for_config_with_force_reload(config, /*force_reload*/ false)
|
||||
}
|
||||
@@ -600,7 +624,11 @@ impl PluginsManager {
|
||||
&self,
|
||||
request: PluginInstallRequest,
|
||||
) -> Result<PluginInstallOutcome, PluginInstallError> {
|
||||
let resolved = resolve_marketplace_plugin(&request.marketplace_path, &request.plugin_name)?;
|
||||
let resolved = resolve_marketplace_plugin(
|
||||
&request.marketplace_path,
|
||||
&request.plugin_name,
|
||||
self.restriction_product,
|
||||
)?;
|
||||
self.install_resolved_plugin(resolved).await
|
||||
}
|
||||
|
||||
@@ -610,7 +638,11 @@ impl PluginsManager {
|
||||
auth: Option<&CodexAuth>,
|
||||
request: PluginInstallRequest,
|
||||
) -> Result<PluginInstallOutcome, PluginInstallError> {
|
||||
let resolved = resolve_marketplace_plugin(&request.marketplace_path, &request.plugin_name)?;
|
||||
let resolved = resolve_marketplace_plugin(
|
||||
&request.marketplace_path,
|
||||
&request.plugin_name,
|
||||
self.restriction_product,
|
||||
)?;
|
||||
let plugin_id = resolved.plugin_id.as_key();
|
||||
// This only forwards the backend mutation before the local install flow. We rely on
|
||||
// `plugin/list(forceRemoteSync=true)` to sync local state rather than doing an extra
|
||||
@@ -775,6 +807,7 @@ impl PluginsManager {
|
||||
AbsolutePathBuf,
|
||||
Option<bool>,
|
||||
Option<String>,
|
||||
bool,
|
||||
)>::new();
|
||||
let mut local_plugin_names = HashSet::new();
|
||||
for plugin in curated_marketplace.plugins {
|
||||
@@ -797,12 +830,14 @@ impl PluginsManager {
|
||||
.get(&plugin_key)
|
||||
.map(|plugin| plugin.enabled);
|
||||
let installed_version = self.store.active_plugin_version(&plugin_id);
|
||||
let product_allowed = self.restriction_product_matches(&plugin.policy.products);
|
||||
local_plugins.push((
|
||||
plugin_name,
|
||||
plugin_id,
|
||||
source_path,
|
||||
current_enabled,
|
||||
installed_version,
|
||||
product_allowed,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -841,11 +876,20 @@ impl PluginsManager {
|
||||
let remote_plugin_count = remote_installed_plugin_names.len();
|
||||
let local_plugin_count = local_plugins.len();
|
||||
|
||||
for (plugin_name, plugin_id, source_path, current_enabled, installed_version) in
|
||||
local_plugins
|
||||
for (
|
||||
plugin_name,
|
||||
plugin_id,
|
||||
source_path,
|
||||
current_enabled,
|
||||
installed_version,
|
||||
product_allowed,
|
||||
) in local_plugins
|
||||
{
|
||||
let plugin_key = plugin_id.as_key();
|
||||
let is_installed = installed_version.is_some();
|
||||
if !product_allowed {
|
||||
continue;
|
||||
}
|
||||
if remote_installed_plugin_names.contains(&plugin_name) {
|
||||
if !is_installed {
|
||||
installs.push((
|
||||
@@ -947,6 +991,9 @@ impl PluginsManager {
|
||||
if !seen_plugin_keys.insert(plugin_key.clone()) {
|
||||
return None;
|
||||
}
|
||||
if !self.restriction_product_matches(&plugin.policy.products) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ConfiguredMarketplacePlugin {
|
||||
// Enabled state is keyed by `<plugin>@<marketplace>`, so duplicate
|
||||
@@ -994,6 +1041,12 @@ impl PluginsManager {
|
||||
marketplace_name,
|
||||
});
|
||||
};
|
||||
if !self.restriction_product_matches(&plugin.policy.products) {
|
||||
return Err(MarketplaceError::PluginNotFound {
|
||||
plugin_name: request.plugin_name.clone(),
|
||||
marketplace_name,
|
||||
});
|
||||
}
|
||||
|
||||
let plugin_id = PluginId::new(plugin.name.clone(), marketplace.name.clone()).map_err(
|
||||
|err| match err {
|
||||
@@ -1017,7 +1070,10 @@ impl PluginsManager {
|
||||
path,
|
||||
scope: SkillScope::User,
|
||||
}))
|
||||
.skills;
|
||||
.skills
|
||||
.into_iter()
|
||||
.filter(|skill| skill.matches_product_restriction_for_product(self.restriction_product))
|
||||
.collect();
|
||||
let apps = load_plugin_apps(source_path.as_path());
|
||||
let mcp_config_paths = plugin_mcp_config_paths(source_path.as_path(), manifest_paths);
|
||||
let mut mcp_server_names = Vec::new();
|
||||
|
||||
@@ -146,6 +146,7 @@ impl MarketplaceError {
|
||||
pub fn resolve_marketplace_plugin(
|
||||
marketplace_path: &AbsolutePathBuf,
|
||||
plugin_name: &str,
|
||||
restriction_product: Option<Product>,
|
||||
) -> Result<ResolvedMarketplacePlugin, MarketplaceError> {
|
||||
let marketplace = load_raw_marketplace_manifest(marketplace_path)?;
|
||||
let marketplace_name = marketplace.name;
|
||||
@@ -168,7 +169,10 @@ pub fn resolve_marketplace_plugin(
|
||||
..
|
||||
} = plugin;
|
||||
let install_policy = policy.installation;
|
||||
if install_policy == MarketplacePluginInstallPolicy::NotAvailable {
|
||||
let product_allowed = policy.products.is_empty()
|
||||
|| restriction_product
|
||||
.is_some_and(|product| product.matches_product_restriction(&policy.products));
|
||||
if install_policy == MarketplacePluginInstallPolicy::NotAvailable || !product_allowed {
|
||||
return Err(MarketplaceError::PluginNotAvailable {
|
||||
plugin_name: name,
|
||||
marketplace_name,
|
||||
|
||||
@@ -30,6 +30,7 @@ fn resolve_marketplace_plugin_finds_repo_marketplace_plugin() {
|
||||
let resolved = resolve_marketplace_plugin(
|
||||
&AbsolutePathBuf::try_from(repo_root.join(".agents/plugins/marketplace.json")).unwrap(),
|
||||
"local-plugin",
|
||||
Some(Product::Codex),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -59,6 +60,7 @@ fn resolve_marketplace_plugin_reports_missing_plugin() {
|
||||
let err = resolve_marketplace_plugin(
|
||||
&AbsolutePathBuf::try_from(repo_root.join(".agents/plugins/marketplace.json")).unwrap(),
|
||||
"missing",
|
||||
Some(Product::Codex),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
@@ -297,6 +299,7 @@ fn list_marketplaces_keeps_distinct_entries_for_same_name() {
|
||||
let resolved = resolve_marketplace_plugin(
|
||||
&AbsolutePathBuf::try_from(repo_marketplace).unwrap(),
|
||||
"local-plugin",
|
||||
Some(Product::Codex),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -687,6 +690,7 @@ fn resolve_marketplace_plugin_rejects_non_relative_local_paths() {
|
||||
let err = resolve_marketplace_plugin(
|
||||
&AbsolutePathBuf::try_from(repo_root.join(".agents/plugins/marketplace.json")).unwrap(),
|
||||
"local-plugin",
|
||||
Some(Product::Codex),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
@@ -732,6 +736,7 @@ fn resolve_marketplace_plugin_uses_first_duplicate_entry() {
|
||||
let resolved = resolve_marketplace_plugin(
|
||||
&AbsolutePathBuf::try_from(repo_root.join(".agents/plugins/marketplace.json")).unwrap(),
|
||||
"local-plugin",
|
||||
Some(Product::Codex),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -740,3 +745,42 @@ fn resolve_marketplace_plugin_uses_first_duplicate_entry() {
|
||||
AbsolutePathBuf::try_from(repo_root.join("first")).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_marketplace_plugin_rejects_disallowed_product() {
|
||||
let tmp = tempdir().unwrap();
|
||||
let repo_root = tmp.path().join("repo");
|
||||
fs::create_dir_all(repo_root.join(".git")).unwrap();
|
||||
fs::create_dir_all(repo_root.join(".agents/plugins")).unwrap();
|
||||
fs::write(
|
||||
repo_root.join(".agents/plugins/marketplace.json"),
|
||||
r#"{
|
||||
"name": "codex-curated",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "chatgpt-plugin",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugin"
|
||||
},
|
||||
"policy": {
|
||||
"products": ["CHATGPT"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let err = resolve_marketplace_plugin(
|
||||
&AbsolutePathBuf::try_from(repo_root.join(".agents/plugins/marketplace.json")).unwrap(),
|
||||
"chatgpt-plugin",
|
||||
Some(Product::Atlas),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"plugin `chatgpt-plugin` is not available for install in marketplace `codex-curated`"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
//! Rollout module: persistence and discovery of session rollout files.
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
|
||||
pub const SESSIONS_SUBDIR: &str = "sessions";
|
||||
pub const ARCHIVED_SESSIONS_SUBDIR: &str = "archived_sessions";
|
||||
pub const INTERACTIVE_SESSION_SOURCES: &[SessionSource] =
|
||||
&[SessionSource::Cli, SessionSource::VSCode];
|
||||
pub static INTERACTIVE_SESSION_SOURCES: LazyLock<Vec<SessionSource>> = LazyLock::new(|| {
|
||||
vec![
|
||||
SessionSource::Cli,
|
||||
SessionSource::VSCode,
|
||||
SessionSource::Custom("atlas".to_string()),
|
||||
SessionSource::Custom("chatgpt".to_string()),
|
||||
]
|
||||
});
|
||||
|
||||
pub(crate) mod error;
|
||||
pub mod list;
|
||||
|
||||
@@ -516,7 +516,7 @@ async fn test_list_conversations_latest_first() {
|
||||
10,
|
||||
None,
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -665,7 +665,7 @@ async fn test_pagination_cursor() {
|
||||
2,
|
||||
None,
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -733,7 +733,7 @@ async fn test_pagination_cursor() {
|
||||
2,
|
||||
page1.next_cursor.as_ref(),
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -801,7 +801,7 @@ async fn test_pagination_cursor() {
|
||||
2,
|
||||
page2.next_cursor.as_ref(),
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -854,7 +854,7 @@ async fn test_list_threads_scans_past_head_for_user_event() {
|
||||
10,
|
||||
None,
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -880,7 +880,7 @@ async fn test_get_thread_contents() {
|
||||
1,
|
||||
None,
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -970,7 +970,7 @@ async fn test_base_instructions_missing_in_meta_defaults_to_null() {
|
||||
1,
|
||||
None,
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -1013,7 +1013,7 @@ async fn test_base_instructions_present_in_meta_is_preserved() {
|
||||
1,
|
||||
None,
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -1064,7 +1064,7 @@ async fn test_created_at_sort_uses_file_mtime_for_updated_at() -> Result<()> {
|
||||
1,
|
||||
None,
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -1148,7 +1148,7 @@ async fn test_updated_at_uses_file_mtime() -> Result<()> {
|
||||
1,
|
||||
None,
|
||||
ThreadSortKey::UpdatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -1188,7 +1188,7 @@ async fn test_stable_ordering_same_second_pagination() {
|
||||
2,
|
||||
None,
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -1256,7 +1256,7 @@ async fn test_stable_ordering_same_second_pagination() {
|
||||
2,
|
||||
page1.next_cursor.as_ref(),
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
@@ -1325,7 +1325,7 @@ async fn test_source_filter_excludes_non_matching_sessions() {
|
||||
10,
|
||||
None,
|
||||
ThreadSortKey::CreatedAt,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
INTERACTIVE_SESSION_SOURCES.as_slice(),
|
||||
Some(provider_filter.as_slice()),
|
||||
TEST_PROVIDER,
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
use codex_protocol::protocol::Product;
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use toml::Value as TomlValue;
|
||||
@@ -30,6 +31,7 @@ use crate::skills::system::uninstall_system_skills;
|
||||
pub struct SkillsManager {
|
||||
codex_home: PathBuf,
|
||||
plugins_manager: Arc<PluginsManager>,
|
||||
restriction_product: Option<Product>,
|
||||
cache_by_cwd: RwLock<HashMap<PathBuf, SkillLoadOutcome>>,
|
||||
cache_by_config: RwLock<HashMap<ConfigSkillsCacheKey, SkillLoadOutcome>>,
|
||||
}
|
||||
@@ -39,10 +41,25 @@ impl SkillsManager {
|
||||
codex_home: PathBuf,
|
||||
plugins_manager: Arc<PluginsManager>,
|
||||
bundled_skills_enabled: bool,
|
||||
) -> Self {
|
||||
Self::new_with_restriction_product(
|
||||
codex_home,
|
||||
plugins_manager,
|
||||
bundled_skills_enabled,
|
||||
Some(Product::Codex),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_restriction_product(
|
||||
codex_home: PathBuf,
|
||||
plugins_manager: Arc<PluginsManager>,
|
||||
bundled_skills_enabled: bool,
|
||||
restriction_product: Option<Product>,
|
||||
) -> Self {
|
||||
let manager = Self {
|
||||
codex_home,
|
||||
plugins_manager,
|
||||
restriction_product,
|
||||
cache_by_cwd: RwLock::new(HashMap::new()),
|
||||
cache_by_config: RwLock::new(HashMap::new()),
|
||||
};
|
||||
@@ -69,8 +86,10 @@ impl SkillsManager {
|
||||
return outcome;
|
||||
}
|
||||
|
||||
let outcome =
|
||||
finalize_skill_outcome(load_skills_from_roots(roots), &config.config_layer_stack);
|
||||
let outcome = crate::skills::filter_skill_load_outcome_for_product(
|
||||
finalize_skill_outcome(load_skills_from_roots(roots), &config.config_layer_stack),
|
||||
self.restriction_product,
|
||||
);
|
||||
let mut cache = self
|
||||
.cache_by_config
|
||||
.write()
|
||||
@@ -173,8 +192,7 @@ impl SkillsManager {
|
||||
scope: SkillScope::User,
|
||||
}),
|
||||
);
|
||||
let outcome = load_skills_from_roots(roots);
|
||||
let outcome = finalize_skill_outcome(outcome, &config_layer_stack);
|
||||
let outcome = self.build_skill_outcome(roots, &config_layer_stack);
|
||||
let mut cache = self
|
||||
.cache_by_cwd
|
||||
.write()
|
||||
@@ -183,6 +201,17 @@ impl SkillsManager {
|
||||
outcome
|
||||
}
|
||||
|
||||
fn build_skill_outcome(
|
||||
&self,
|
||||
roots: Vec<SkillRoot>,
|
||||
config_layer_stack: &crate::config_loader::ConfigLayerStack,
|
||||
) -> SkillLoadOutcome {
|
||||
crate::skills::filter_skill_load_outcome_for_product(
|
||||
finalize_skill_outcome(load_skills_from_roots(roots), config_layer_stack),
|
||||
self.restriction_product,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn clear_cache(&self) {
|
||||
let cleared_cwd = {
|
||||
let mut cache = self
|
||||
|
||||
@@ -20,4 +20,5 @@ pub use model::SkillError;
|
||||
pub use model::SkillLoadOutcome;
|
||||
pub use model::SkillMetadata;
|
||||
pub use model::SkillPolicy;
|
||||
pub use model::filter_skill_load_outcome_for_product;
|
||||
pub use render::render_skills_section;
|
||||
|
||||
@@ -42,6 +42,21 @@ impl SkillMetadata {
|
||||
.and_then(|policy| policy.allow_implicit_invocation)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn matches_product_restriction_for_product(
|
||||
&self,
|
||||
restriction_product: Option<Product>,
|
||||
) -> bool {
|
||||
match &self.policy {
|
||||
Some(policy) => {
|
||||
policy.products.is_empty()
|
||||
|| restriction_product.is_some_and(|product| {
|
||||
product.matches_product_restriction(&policy.products)
|
||||
})
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
@@ -115,3 +130,29 @@ impl SkillLoadOutcome {
|
||||
.map(|skill| (skill, self.is_skill_enabled(skill)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filter_skill_load_outcome_for_product(
|
||||
mut outcome: SkillLoadOutcome,
|
||||
restriction_product: Option<Product>,
|
||||
) -> SkillLoadOutcome {
|
||||
outcome
|
||||
.skills
|
||||
.retain(|skill| skill.matches_product_restriction_for_product(restriction_product));
|
||||
outcome.implicit_skills_by_scripts_dir = Arc::new(
|
||||
outcome
|
||||
.implicit_skills_by_scripts_dir
|
||||
.iter()
|
||||
.filter(|(_, skill)| skill.matches_product_restriction_for_product(restriction_product))
|
||||
.map(|(path, skill)| (path.clone(), skill.clone()))
|
||||
.collect(),
|
||||
);
|
||||
outcome.implicit_skills_by_doc_path = Arc::new(
|
||||
outcome
|
||||
.implicit_skills_by_doc_path
|
||||
.iter()
|
||||
.filter(|(_, skill)| skill.matches_product_restriction_for_product(restriction_product))
|
||||
.map(|(path, skill)| (path.clone(), skill.clone()))
|
||||
.collect(),
|
||||
);
|
||||
outcome
|
||||
}
|
||||
|
||||
@@ -169,18 +169,23 @@ impl ThreadManager {
|
||||
collaboration_modes_config: CollaborationModesConfig,
|
||||
) -> Self {
|
||||
let codex_home = config.codex_home.clone();
|
||||
let restriction_product = session_source.restriction_product();
|
||||
let openai_models_provider = config
|
||||
.model_providers
|
||||
.get(OPENAI_PROVIDER_ID)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| ModelProviderInfo::create_openai_provider(/*base_url*/ None));
|
||||
let (thread_created_tx, _) = broadcast::channel(THREAD_CREATED_CHANNEL_CAPACITY);
|
||||
let plugins_manager = Arc::new(PluginsManager::new(codex_home.clone()));
|
||||
let plugins_manager = Arc::new(PluginsManager::new_with_restriction_product(
|
||||
codex_home.clone(),
|
||||
restriction_product,
|
||||
));
|
||||
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
||||
let skills_manager = Arc::new(SkillsManager::new(
|
||||
let skills_manager = Arc::new(SkillsManager::new_with_restriction_product(
|
||||
codex_home.clone(),
|
||||
Arc::clone(&plugins_manager),
|
||||
config.bundled_skills_enabled(),
|
||||
restriction_product,
|
||||
));
|
||||
let file_watcher = build_file_watcher(codex_home.clone(), Arc::clone(&skills_manager));
|
||||
Self {
|
||||
@@ -236,12 +241,17 @@ impl ThreadManager {
|
||||
set_thread_manager_test_mode_for_tests(/*enabled*/ true);
|
||||
let auth_manager = AuthManager::from_auth_for_testing(auth);
|
||||
let (thread_created_tx, _) = broadcast::channel(THREAD_CREATED_CHANNEL_CAPACITY);
|
||||
let plugins_manager = Arc::new(PluginsManager::new(codex_home.clone()));
|
||||
let restriction_product = SessionSource::Exec.restriction_product();
|
||||
let plugins_manager = Arc::new(PluginsManager::new_with_restriction_product(
|
||||
codex_home.clone(),
|
||||
restriction_product,
|
||||
));
|
||||
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
||||
let skills_manager = Arc::new(SkillsManager::new(
|
||||
let skills_manager = Arc::new(SkillsManager::new_with_restriction_product(
|
||||
codex_home.clone(),
|
||||
Arc::clone(&plugins_manager),
|
||||
/*bundled_skills_enabled*/ true,
|
||||
restriction_product,
|
||||
));
|
||||
let file_watcher = build_file_watcher(codex_home.clone(), Arc::clone(&skills_manager));
|
||||
Self {
|
||||
|
||||
Reference in New Issue
Block a user