mirror of
https://github.com/openai/codex.git
synced 2026-04-25 15:15:15 +00: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:
@@ -2272,6 +2272,7 @@ pub enum SessionSource {
|
||||
VSCode,
|
||||
Exec,
|
||||
Mcp,
|
||||
Custom(String),
|
||||
SubAgent(SubAgentSource),
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
@@ -2302,6 +2303,7 @@ impl fmt::Display for SessionSource {
|
||||
SessionSource::VSCode => f.write_str("vscode"),
|
||||
SessionSource::Exec => f.write_str("exec"),
|
||||
SessionSource::Mcp => f.write_str("mcp"),
|
||||
SessionSource::Custom(source) => f.write_str(source),
|
||||
SessionSource::SubAgent(sub_source) => write!(f, "subagent_{sub_source}"),
|
||||
SessionSource::Unknown => f.write_str("unknown"),
|
||||
}
|
||||
@@ -2309,6 +2311,23 @@ impl fmt::Display for SessionSource {
|
||||
}
|
||||
|
||||
impl SessionSource {
|
||||
pub fn from_startup_arg(value: &str) -> Result<Self, &'static str> {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Err("session source must not be empty");
|
||||
}
|
||||
|
||||
let normalized = trimmed.to_ascii_lowercase();
|
||||
Ok(match normalized.as_str() {
|
||||
"cli" => SessionSource::Cli,
|
||||
"vscode" => SessionSource::VSCode,
|
||||
"exec" => SessionSource::Exec,
|
||||
"mcp" | "appserver" | "app-server" | "app_server" => SessionSource::Mcp,
|
||||
"unknown" => SessionSource::Unknown,
|
||||
_ => SessionSource::Custom(normalized),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_nickname(&self) -> Option<String> {
|
||||
match self {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { agent_nickname, .. }) => {
|
||||
@@ -2332,6 +2351,24 @@ impl SessionSource {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn restriction_product(&self) -> Option<Product> {
|
||||
match self {
|
||||
SessionSource::Custom(source) => Product::from_session_source_name(source),
|
||||
SessionSource::Cli
|
||||
| SessionSource::VSCode
|
||||
| SessionSource::Exec
|
||||
| SessionSource::Mcp
|
||||
| SessionSource::Unknown => Some(Product::Codex),
|
||||
SessionSource::SubAgent(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches_product_restriction(&self, products: &[Product]) -> bool {
|
||||
products.is_empty()
|
||||
|| self
|
||||
.restriction_product()
|
||||
.is_some_and(|product| product.matches_product_restriction(products))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SubAgentSource {
|
||||
@@ -2923,6 +2960,21 @@ pub enum Product {
|
||||
#[serde(alias = "ATLAS")]
|
||||
Atlas,
|
||||
}
|
||||
impl Product {
|
||||
pub fn from_session_source_name(value: &str) -> Option<Self> {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
match normalized.as_str() {
|
||||
"chatgpt" => Some(Self::Chatgpt),
|
||||
"codex" => Some(Self::Codex),
|
||||
"atlas" => Some(Self::Atlas),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches_product_restriction(&self, products: &[Product]) -> bool {
|
||||
products.is_empty() || products.contains(self)
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case")]
|
||||
@@ -3423,6 +3475,100 @@ mod tests {
|
||||
.any(|root| root.is_path_writable(path))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_source_from_startup_arg_maps_known_values() {
|
||||
assert_eq!(
|
||||
SessionSource::from_startup_arg("vscode").unwrap(),
|
||||
SessionSource::VSCode
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::from_startup_arg("app-server").unwrap(),
|
||||
SessionSource::Mcp
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_source_from_startup_arg_normalizes_custom_values() {
|
||||
assert_eq!(
|
||||
SessionSource::from_startup_arg("atlas").unwrap(),
|
||||
SessionSource::Custom("atlas".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::from_startup_arg(" Atlas ").unwrap(),
|
||||
SessionSource::Custom("atlas".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_source_restriction_product_defaults_non_subagent_sources_to_codex() {
|
||||
assert_eq!(
|
||||
SessionSource::Cli.restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::VSCode.restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Exec.restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Mcp.restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Unknown.restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_source_restriction_product_does_not_guess_subagent_products() {
|
||||
assert_eq!(
|
||||
SessionSource::SubAgent(SubAgentSource::Review).restriction_product(),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_source_restriction_product_maps_custom_sources_to_products() {
|
||||
assert_eq!(
|
||||
SessionSource::Custom("chatgpt".to_string()).restriction_product(),
|
||||
Some(Product::Chatgpt)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Custom("ATLAS".to_string()).restriction_product(),
|
||||
Some(Product::Atlas)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Custom("codex".to_string()).restriction_product(),
|
||||
Some(Product::Codex)
|
||||
);
|
||||
assert_eq!(
|
||||
SessionSource::Custom("atlas-dev".to_string()).restriction_product(),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_source_matches_product_restriction() {
|
||||
assert!(
|
||||
SessionSource::Custom("chatgpt".to_string())
|
||||
.matches_product_restriction(&[Product::Chatgpt])
|
||||
);
|
||||
assert!(
|
||||
!SessionSource::Custom("chatgpt".to_string())
|
||||
.matches_product_restriction(&[Product::Codex])
|
||||
);
|
||||
assert!(SessionSource::VSCode.matches_product_restriction(&[Product::Codex]));
|
||||
assert!(
|
||||
!SessionSource::Custom("atlas-dev".to_string())
|
||||
.matches_product_restriction(&[Product::Atlas])
|
||||
);
|
||||
assert!(SessionSource::Custom("atlas-dev".to_string()).matches_product_restriction(&[]));
|
||||
}
|
||||
|
||||
fn sandbox_policy_probe_paths(policy: &SandboxPolicy, cwd: &Path) -> Vec<PathBuf> {
|
||||
let mut paths = vec![cwd.to_path_buf()];
|
||||
paths.extend(
|
||||
|
||||
Reference in New Issue
Block a user