mirror of
https://github.com/openai/codex.git
synced 2026-04-24 06:35:50 +00:00
Compare commits
5 Commits
ruslan/cod
...
owen/clean
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f835a5af4 | ||
|
|
cf91ea808a | ||
|
|
82dcda2910 | ||
|
|
21e7cbb9cd | ||
|
|
8eb640f9c6 |
1
codex-rs/Cargo.lock
generated
1
codex-rs/Cargo.lock
generated
@@ -1676,6 +1676,7 @@ dependencies = [
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-exec",
|
||||
"codex-exec-server",
|
||||
"codex-execpolicy",
|
||||
"codex-features",
|
||||
"codex-login",
|
||||
|
||||
@@ -106,6 +106,7 @@ use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::format_allow_prefixes;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
@@ -134,6 +135,8 @@ use codex_rmcp_client::ElicitationResponse;
|
||||
use codex_rollout::state_db;
|
||||
use codex_shell_command::parse_command::parse_command;
|
||||
use codex_terminal_detection::user_agent;
|
||||
use codex_tools::ShellCommandBackendConfig;
|
||||
use codex_tools::UnifiedExecShellMode;
|
||||
use codex_tools::filter_tool_suggest_discoverable_tools_for_client;
|
||||
use codex_utils_output_truncation::TruncationPolicy;
|
||||
use codex_utils_stream_parser::AssistantTextChunk;
|
||||
@@ -284,7 +287,8 @@ use crate::network_policy_decision::execpolicy_network_rule_amendment;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::plugins::build_plugin_injections;
|
||||
use crate::plugins::render_plugins_section;
|
||||
use crate::project_doc::get_user_instructions;
|
||||
use crate::project_doc::UserInstructionExtras;
|
||||
use crate::project_doc::get_user_instructions_with_extras;
|
||||
use crate::resolve_skill_dependencies_for_turn;
|
||||
use crate::rollout::RolloutRecorder;
|
||||
use crate::rollout::RolloutRecorderParams;
|
||||
@@ -292,6 +296,8 @@ use crate::rollout::map_session_init_error;
|
||||
use crate::rollout::metadata;
|
||||
use crate::rollout::policy::EventPersistenceMode;
|
||||
use crate::session_startup_prewarm::SessionStartupPrewarmHandle;
|
||||
use crate::session_surface::CapabilityInclusions;
|
||||
use crate::session_surface::SessionSurfacePolicy;
|
||||
use crate::shell;
|
||||
use crate::shell_snapshot::ShellSnapshot;
|
||||
use crate::skills_watcher::SkillsWatcher;
|
||||
@@ -394,6 +400,55 @@ fn image_generation_tool_auth_allowed(auth_manager: Option<&AuthManager>) -> boo
|
||||
)
|
||||
}
|
||||
|
||||
fn apply_capabilities_to_tools_config(
|
||||
mut tools_config: ToolsConfig,
|
||||
capabilities: CapabilityInclusions,
|
||||
) -> ToolsConfig {
|
||||
if !capabilities.web_search {
|
||||
tools_config.search_tool = false;
|
||||
tools_config.web_search_mode = Some(WebSearchMode::Disabled);
|
||||
tools_config.web_search_config = None;
|
||||
}
|
||||
if !capabilities.apps || !capabilities.plugins {
|
||||
tools_config.tool_suggest = false;
|
||||
}
|
||||
if !capabilities.image_generation {
|
||||
tools_config.image_gen_tool = false;
|
||||
tools_config.can_request_original_image_detail = false;
|
||||
}
|
||||
if !capabilities.apply_patch {
|
||||
tools_config.apply_patch_tool_type = None;
|
||||
}
|
||||
if !capabilities.request_permissions {
|
||||
tools_config.exec_permission_approvals_enabled = false;
|
||||
tools_config.request_permissions_tool_enabled = false;
|
||||
}
|
||||
if !capabilities.code_mode {
|
||||
tools_config.code_mode_enabled = false;
|
||||
tools_config.code_mode_only_enabled = false;
|
||||
}
|
||||
if !capabilities.js_repl {
|
||||
tools_config.js_repl_enabled = false;
|
||||
tools_config.js_repl_tools_only = false;
|
||||
}
|
||||
if !capabilities.shell_zsh_fork {
|
||||
tools_config.shell_command_backend = ShellCommandBackendConfig::Classic;
|
||||
tools_config.unified_exec_shell_mode = UnifiedExecShellMode::Direct;
|
||||
}
|
||||
if !capabilities.unified_exec && tools_config.shell_type == ConfigShellToolType::UnifiedExec {
|
||||
tools_config.shell_type = ConfigShellToolType::ShellCommand;
|
||||
tools_config.unified_exec_shell_mode = UnifiedExecShellMode::Direct;
|
||||
}
|
||||
if !capabilities.subagents {
|
||||
tools_config.collab_tools = false;
|
||||
tools_config.multi_agent_v2 = false;
|
||||
tools_config.spawn_agent_usage_hint = false;
|
||||
tools_config.agent_jobs_tools = false;
|
||||
tools_config.agent_jobs_worker_tools = false;
|
||||
}
|
||||
tools_config
|
||||
}
|
||||
|
||||
/// The high-level interface to the Codex system.
|
||||
/// It operates as a queue pair where you send submissions and receive events.
|
||||
pub struct Codex {
|
||||
@@ -419,6 +474,7 @@ pub struct CodexSpawnOk {
|
||||
|
||||
pub(crate) struct CodexSpawnArgs {
|
||||
pub(crate) config: Config,
|
||||
pub(crate) surface_policy: SessionSurfacePolicy,
|
||||
pub(crate) auth_manager: Arc<AuthManager>,
|
||||
pub(crate) models_manager: Arc<ModelsManager>,
|
||||
pub(crate) environment_manager: Arc<EnvironmentManager>,
|
||||
@@ -473,6 +529,7 @@ impl Codex {
|
||||
async fn spawn_internal(args: CodexSpawnArgs) -> CodexResult<CodexSpawnOk> {
|
||||
let CodexSpawnArgs {
|
||||
mut config,
|
||||
surface_policy,
|
||||
auth_manager,
|
||||
models_manager,
|
||||
environment_manager,
|
||||
@@ -494,17 +551,24 @@ impl Codex {
|
||||
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
|
||||
let (tx_event, rx_event) = async_channel::unbounded();
|
||||
|
||||
let plugin_outcome = plugins_manager.plugins_for_config(&config);
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
|
||||
let loaded_skills = skills_manager.skills_for_config(&skills_input);
|
||||
if surface_policy.capabilities.skills {
|
||||
let effective_skill_roots = if surface_policy.capabilities.plugins {
|
||||
plugins_manager
|
||||
.plugins_for_config(&config)
|
||||
.effective_skill_roots()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
|
||||
let loaded_skills = skills_manager.skills_for_config(&skills_input);
|
||||
|
||||
for err in &loaded_skills.errors {
|
||||
error!(
|
||||
"failed to load skill {}: {}",
|
||||
err.path.display(),
|
||||
err.message
|
||||
);
|
||||
for err in &loaded_skills.errors {
|
||||
error!(
|
||||
"failed to load skill {}: {}",
|
||||
err.path.display(),
|
||||
err.message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let SessionSource::SubAgent(SubAgentSource::ThreadSpawn { depth, .. }) = session_source
|
||||
@@ -514,7 +578,8 @@ impl Codex {
|
||||
let _ = config.features.disable(Feature::Collab);
|
||||
}
|
||||
|
||||
if config.features.enabled(Feature::JsRepl)
|
||||
if surface_policy.capabilities.js_repl
|
||||
&& config.features.enabled(Feature::JsRepl)
|
||||
&& let Err(err) = resolve_compatible_node(config.js_repl_node_path.as_deref()).await
|
||||
{
|
||||
let _ = config.features.disable(Feature::JsRepl);
|
||||
@@ -531,7 +596,8 @@ impl Codex {
|
||||
warn!("{message}");
|
||||
config.startup_warnings.push(message);
|
||||
}
|
||||
if config.features.enabled(Feature::CodeMode)
|
||||
if surface_policy.capabilities.code_mode
|
||||
&& config.features.enabled(Feature::CodeMode)
|
||||
&& let Err(err) = resolve_compatible_node(config.js_repl_node_path.as_deref()).await
|
||||
{
|
||||
let message = format!(
|
||||
@@ -546,7 +612,16 @@ impl Codex {
|
||||
.current()
|
||||
.await
|
||||
.map_err(|err| CodexErr::Fatal(format!("failed to create environment: {err}")))?;
|
||||
let user_instructions = get_user_instructions(&config, environment.as_deref()).await;
|
||||
let user_instruction_extras = UserInstructionExtras {
|
||||
js_repl: surface_policy.capabilities.js_repl,
|
||||
child_agents_md: surface_policy.capabilities.subagents,
|
||||
};
|
||||
let user_instructions = get_user_instructions_with_extras(
|
||||
&config,
|
||||
environment.as_deref(),
|
||||
user_instruction_extras,
|
||||
)
|
||||
.await;
|
||||
|
||||
let exec_policy = if crate::guardian::is_guardian_reviewer_source(&session_source) {
|
||||
// Guardian review should rely on the built-in shell safety checks,
|
||||
@@ -595,7 +670,9 @@ impl Codex {
|
||||
|
||||
// Respect thread-start tools. When missing (resumed/forked threads), read from the db
|
||||
// first, then fall back to rollout-file tools.
|
||||
let persisted_tools = if dynamic_tools.is_empty() {
|
||||
let persisted_tools = if surface_policy.capabilities.dynamic_tools
|
||||
&& dynamic_tools.is_empty()
|
||||
{
|
||||
let thread_id = match &conversation_history {
|
||||
InitialHistory::Resumed(resumed) => Some(resumed.conversation_id),
|
||||
InitialHistory::Forked(_) => conversation_history.forked_from_id(),
|
||||
@@ -612,7 +689,9 @@ impl Codex {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let dynamic_tools = if dynamic_tools.is_empty() {
|
||||
let dynamic_tools = if !surface_policy.capabilities.dynamic_tools {
|
||||
Vec::new()
|
||||
} else if dynamic_tools.is_empty() {
|
||||
persisted_tools
|
||||
.or_else(|| conversation_history.get_dynamic_tools())
|
||||
.unwrap_or_default()
|
||||
@@ -640,6 +719,7 @@ impl Codex {
|
||||
personality: config.personality,
|
||||
base_instructions,
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
surface_policy,
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
@@ -892,6 +972,7 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) shell_environment_policy: ShellEnvironmentPolicy,
|
||||
pub(crate) tools_config: ToolsConfig,
|
||||
pub(crate) features: ManagedFeatures,
|
||||
pub(crate) surface_policy: SessionSurfacePolicy,
|
||||
pub(crate) ghost_snapshot: GhostSnapshotConfig,
|
||||
pub(crate) final_output_json_schema: Option<Value>,
|
||||
pub(crate) codex_self_exe: Option<PathBuf>,
|
||||
@@ -919,7 +1000,8 @@ impl TurnContext {
|
||||
.and_then(AuthManager::auth_cached)
|
||||
.as_ref()
|
||||
.is_some_and(CodexAuth::is_chatgpt_auth);
|
||||
self.features.apps_enabled_for_auth(is_chatgpt_auth)
|
||||
self.surface_policy.capabilities.apps
|
||||
&& self.features.apps_enabled_for_auth(is_chatgpt_auth)
|
||||
}
|
||||
|
||||
pub(crate) async fn with_model(&self, model: String, models_manager: &ModelsManager) -> Self {
|
||||
@@ -957,30 +1039,33 @@ impl TurnContext {
|
||||
/*developer_instructions*/ None,
|
||||
);
|
||||
let features = self.features.clone();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &models_manager
|
||||
.list_models(RefreshStrategy::OnlineIfUncached)
|
||||
.await,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: image_generation_tool_auth_allowed(
|
||||
self.auth_manager.as_deref(),
|
||||
),
|
||||
web_search_mode: self.tools_config.web_search_mode,
|
||||
session_source: self.session_source.clone(),
|
||||
sandbox_policy: self.sandbox_policy.get(),
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
})
|
||||
.with_unified_exec_shell_mode(self.tools_config.unified_exec_shell_mode.clone())
|
||||
.with_web_search_config(self.tools_config.web_search_config.clone())
|
||||
.with_allow_login_shell(self.tools_config.allow_login_shell)
|
||||
.with_has_environment(self.tools_config.has_environment)
|
||||
.with_spawn_agent_usage_hint(config.multi_agent_v2.usage_hint_enabled)
|
||||
.with_spawn_agent_usage_hint_text(config.multi_agent_v2.usage_hint_text.clone())
|
||||
.with_hide_spawn_agent_metadata(config.multi_agent_v2.hide_spawn_agent_metadata)
|
||||
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
|
||||
&config.agent_roles,
|
||||
));
|
||||
let tools_config = apply_capabilities_to_tools_config(
|
||||
ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &models_manager
|
||||
.list_models(RefreshStrategy::OnlineIfUncached)
|
||||
.await,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: image_generation_tool_auth_allowed(
|
||||
self.auth_manager.as_deref(),
|
||||
),
|
||||
web_search_mode: self.tools_config.web_search_mode,
|
||||
session_source: self.session_source.clone(),
|
||||
sandbox_policy: self.sandbox_policy.get(),
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
})
|
||||
.with_unified_exec_shell_mode(self.tools_config.unified_exec_shell_mode.clone())
|
||||
.with_web_search_config(self.tools_config.web_search_config.clone())
|
||||
.with_allow_login_shell(self.tools_config.allow_login_shell)
|
||||
.with_has_environment(self.tools_config.has_environment)
|
||||
.with_spawn_agent_usage_hint(config.multi_agent_v2.usage_hint_enabled)
|
||||
.with_spawn_agent_usage_hint_text(config.multi_agent_v2.usage_hint_text.clone())
|
||||
.with_hide_spawn_agent_metadata(config.multi_agent_v2.hide_spawn_agent_metadata)
|
||||
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
|
||||
&config.agent_roles,
|
||||
)),
|
||||
self.surface_policy.capabilities,
|
||||
);
|
||||
|
||||
Self {
|
||||
sub_id: self.sub_id.clone(),
|
||||
@@ -1016,6 +1101,7 @@ impl TurnContext {
|
||||
shell_environment_policy: self.shell_environment_policy.clone(),
|
||||
tools_config,
|
||||
features,
|
||||
surface_policy: self.surface_policy,
|
||||
ghost_snapshot: self.ghost_snapshot.clone(),
|
||||
final_output_json_schema: self.final_output_json_schema.clone(),
|
||||
codex_self_exe: self.codex_self_exe.clone(),
|
||||
@@ -1120,6 +1206,13 @@ pub(crate) struct SessionConfiguration {
|
||||
/// Compact prompt override.
|
||||
compact_prompt: Option<String>,
|
||||
|
||||
/// Session-scoped policy for model-visible startup context and runtime surfaces.
|
||||
///
|
||||
/// This is chosen at spawn time. It is intentionally separate from the
|
||||
/// loaded user config so internally spawned subagents can have narrower
|
||||
/// prompts/tools without mutating config.toml-derived settings.
|
||||
surface_policy: SessionSurfacePolicy,
|
||||
|
||||
/// When to escalate for approval for execution
|
||||
approval_policy: Constrained<AskForApproval>,
|
||||
approvals_reviewer: ApprovalsReviewer,
|
||||
@@ -1486,30 +1579,37 @@ impl Session {
|
||||
let auth_manager_for_context = auth_manager;
|
||||
let provider_for_context = provider;
|
||||
let session_telemetry_for_context = session_telemetry;
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &models_manager.try_list_models().unwrap_or_default(),
|
||||
features: &per_turn_config.features,
|
||||
image_generation_tool_auth_allowed,
|
||||
web_search_mode: Some(per_turn_config.web_search_mode.value()),
|
||||
session_source: session_source.clone(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.get(),
|
||||
windows_sandbox_level: session_configuration.windows_sandbox_level,
|
||||
})
|
||||
.with_unified_exec_shell_mode_for_session(
|
||||
crate::tools::spec::tool_user_shell_type(user_shell),
|
||||
shell_zsh_path,
|
||||
main_execve_wrapper_exe,
|
||||
)
|
||||
.with_web_search_config(per_turn_config.web_search_config.clone())
|
||||
.with_allow_login_shell(per_turn_config.permissions.allow_login_shell)
|
||||
.with_has_environment(environment.is_some())
|
||||
.with_spawn_agent_usage_hint(per_turn_config.multi_agent_v2.usage_hint_enabled)
|
||||
.with_spawn_agent_usage_hint_text(per_turn_config.multi_agent_v2.usage_hint_text.clone())
|
||||
.with_hide_spawn_agent_metadata(per_turn_config.multi_agent_v2.hide_spawn_agent_metadata)
|
||||
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
|
||||
&per_turn_config.agent_roles,
|
||||
));
|
||||
let tools_config = apply_capabilities_to_tools_config(
|
||||
ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &models_manager.try_list_models().unwrap_or_default(),
|
||||
features: &per_turn_config.features,
|
||||
image_generation_tool_auth_allowed,
|
||||
web_search_mode: Some(per_turn_config.web_search_mode.value()),
|
||||
session_source: session_source.clone(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.get(),
|
||||
windows_sandbox_level: session_configuration.windows_sandbox_level,
|
||||
})
|
||||
.with_unified_exec_shell_mode_for_session(
|
||||
crate::tools::spec::tool_user_shell_type(user_shell),
|
||||
shell_zsh_path,
|
||||
main_execve_wrapper_exe,
|
||||
)
|
||||
.with_web_search_config(per_turn_config.web_search_config.clone())
|
||||
.with_allow_login_shell(per_turn_config.permissions.allow_login_shell)
|
||||
.with_has_environment(environment.is_some())
|
||||
.with_spawn_agent_usage_hint(per_turn_config.multi_agent_v2.usage_hint_enabled)
|
||||
.with_spawn_agent_usage_hint_text(
|
||||
per_turn_config.multi_agent_v2.usage_hint_text.clone(),
|
||||
)
|
||||
.with_hide_spawn_agent_metadata(
|
||||
per_turn_config.multi_agent_v2.hide_spawn_agent_metadata,
|
||||
)
|
||||
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
|
||||
&per_turn_config.agent_roles,
|
||||
)),
|
||||
session_configuration.surface_policy.capabilities,
|
||||
);
|
||||
|
||||
let cwd = session_configuration.cwd.clone();
|
||||
|
||||
@@ -1553,6 +1653,7 @@ impl Session {
|
||||
shell_environment_policy: per_turn_config.permissions.shell_environment_policy.clone(),
|
||||
tools_config,
|
||||
features: per_turn_config.features.clone(),
|
||||
surface_policy: session_configuration.surface_policy,
|
||||
ghost_snapshot: per_turn_config.ghost_snapshot.clone(),
|
||||
final_output_json_schema: None,
|
||||
codex_self_exe: per_turn_config.codex_self_exe.clone(),
|
||||
@@ -1690,9 +1791,14 @@ impl Session {
|
||||
let auth_manager_clone = Arc::clone(&auth_manager);
|
||||
let config_for_mcp = Arc::clone(&config);
|
||||
let mcp_manager_for_mcp = Arc::clone(&mcp_manager);
|
||||
let surface_policy_for_mcp = session_configuration.surface_policy;
|
||||
let auth_and_mcp_fut = async move {
|
||||
let auth = auth_manager_clone.auth().await;
|
||||
let mcp_servers = mcp_manager_for_mcp.effective_servers(&config_for_mcp, auth.as_ref());
|
||||
let mcp_servers = if surface_policy_for_mcp.capabilities.mcp {
|
||||
mcp_manager_for_mcp.effective_servers(&config_for_mcp, auth.as_ref())
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
let auth_statuses = compute_auth_statuses(
|
||||
mcp_servers.iter(),
|
||||
config_for_mcp.mcp_oauth_credentials_store_mode,
|
||||
@@ -1840,7 +1946,11 @@ impl Session {
|
||||
config.active_profile.clone(),
|
||||
);
|
||||
|
||||
let use_zsh_fork_shell = config.features.enabled(Feature::ShellZshFork);
|
||||
let use_zsh_fork_shell = session_configuration
|
||||
.surface_policy
|
||||
.capabilities
|
||||
.shell_zsh_fork
|
||||
&& config.features.enabled(Feature::ShellZshFork);
|
||||
let mut default_shell = if let Some(user_shell_override) =
|
||||
session_configuration.user_shell_override.clone()
|
||||
{
|
||||
@@ -1862,7 +1972,12 @@ impl Session {
|
||||
shell::default_user_shell()
|
||||
};
|
||||
// Create the mutable state for the Session.
|
||||
let shell_snapshot_tx = if config.features.enabled(Feature::ShellSnapshot) {
|
||||
let shell_snapshot_tx = if session_configuration
|
||||
.surface_policy
|
||||
.capabilities
|
||||
.shell_snapshot
|
||||
&& config.features.enabled(Feature::ShellSnapshot)
|
||||
{
|
||||
if let Some(snapshot) = session_configuration.inherited_shell_snapshot.clone() {
|
||||
let (tx, rx) = watch::channel(Some(snapshot));
|
||||
default_shell.shell_snapshot = rx;
|
||||
@@ -2111,7 +2226,11 @@ impl Session {
|
||||
required_mcp_servers.sort();
|
||||
let enabled_mcp_server_count = mcp_servers.values().filter(|server| server.enabled).count();
|
||||
let required_mcp_server_count = required_mcp_servers.len();
|
||||
let tool_plugin_provenance = mcp_manager.tool_plugin_provenance(config.as_ref());
|
||||
let tool_plugin_provenance = if session_configuration.surface_policy.capabilities.plugins {
|
||||
mcp_manager.tool_plugin_provenance(config.as_ref())
|
||||
} else {
|
||||
codex_mcp::ToolPluginProvenance::default()
|
||||
};
|
||||
{
|
||||
let mut cancel_guard = sess.services.mcp_startup_cancellation_token.lock().await;
|
||||
cancel_guard.cancel();
|
||||
@@ -2597,17 +2716,26 @@ impl Session {
|
||||
&per_turn_config.to_models_manager_config(),
|
||||
)
|
||||
.await;
|
||||
let plugin_outcome = self
|
||||
.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(&per_turn_config);
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
let skills_input = skills_load_input_from_config(&per_turn_config, effective_skill_roots);
|
||||
let skills_outcome = Arc::new(
|
||||
self.services
|
||||
.skills_manager
|
||||
.skills_for_config(&skills_input),
|
||||
);
|
||||
let skills_outcome = if session_configuration.surface_policy.capabilities.skills {
|
||||
let effective_skill_roots = if session_configuration.surface_policy.capabilities.plugins
|
||||
{
|
||||
self.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(&per_turn_config)
|
||||
.effective_skill_roots()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let skills_input =
|
||||
skills_load_input_from_config(&per_turn_config, effective_skill_roots);
|
||||
Arc::new(
|
||||
self.services
|
||||
.skills_manager
|
||||
.skills_for_config(&skills_input),
|
||||
)
|
||||
} else {
|
||||
Arc::new(SkillLoadOutcome::default())
|
||||
};
|
||||
let mut turn_context: TurnContext = Self::make_turn_context(
|
||||
self.conversation_id,
|
||||
Some(Arc::clone(&self.services.auth_manager)),
|
||||
@@ -3685,7 +3813,7 @@ impl Session {
|
||||
previous_turn_settings,
|
||||
collaboration_mode,
|
||||
base_instructions,
|
||||
session_source,
|
||||
surface_policy,
|
||||
) = {
|
||||
let state = self.state.lock().await;
|
||||
(
|
||||
@@ -3693,18 +3821,21 @@ impl Session {
|
||||
state.previous_turn_settings(),
|
||||
state.session_configuration.collaboration_mode.clone(),
|
||||
state.session_configuration.base_instructions.clone(),
|
||||
state.session_configuration.session_source.clone(),
|
||||
state.session_configuration.surface_policy,
|
||||
)
|
||||
};
|
||||
if let Some(model_switch_message) =
|
||||
crate::context_manager::updates::build_model_instructions_update_item(
|
||||
previous_turn_settings.as_ref(),
|
||||
turn_context,
|
||||
)
|
||||
let prompt_context = surface_policy.prompt;
|
||||
let capabilities = surface_policy.capabilities;
|
||||
if prompt_context.model_update
|
||||
&& let Some(model_switch_message) =
|
||||
crate::context_manager::updates::build_model_instructions_update_item(
|
||||
previous_turn_settings.as_ref(),
|
||||
turn_context,
|
||||
)
|
||||
{
|
||||
developer_sections.push(model_switch_message.into_text());
|
||||
}
|
||||
if turn_context.config.include_permissions_instructions {
|
||||
if prompt_context.permissions && turn_context.config.include_permissions_instructions {
|
||||
developer_sections.push(
|
||||
DeveloperInstructions::from_policy(
|
||||
turn_context.sandbox_policy.get(),
|
||||
@@ -3722,17 +3853,17 @@ impl Session {
|
||||
.into_text(),
|
||||
);
|
||||
}
|
||||
let separate_guardian_developer_message =
|
||||
crate::guardian::is_guardian_reviewer_source(&session_source);
|
||||
// Keep the guardian policy prompt out of the aggregated developer bundle so it
|
||||
// stays isolated as its own top-level developer message for guardian subagents.
|
||||
if !separate_guardian_developer_message
|
||||
// Some internally spawned subagents keep their role policy separate from the
|
||||
// aggregated developer bundle so their top-level instructions are easy to audit.
|
||||
if prompt_context.developer_instructions
|
||||
&& !prompt_context.separate_developer_instructions
|
||||
&& let Some(developer_instructions) = turn_context.developer_instructions.as_deref()
|
||||
{
|
||||
developer_sections.push(developer_instructions.to_string());
|
||||
}
|
||||
// Add developer instructions for memories.
|
||||
if turn_context.features.enabled(Feature::MemoryTool)
|
||||
if capabilities.memory
|
||||
&& turn_context.features.enabled(Feature::MemoryTool)
|
||||
&& turn_context.config.memories.use_memories
|
||||
&& let Some(memory_prompt) =
|
||||
build_memory_tool_developer_instructions(&turn_context.config.codex_home).await
|
||||
@@ -3740,19 +3871,24 @@ impl Session {
|
||||
developer_sections.push(memory_prompt);
|
||||
}
|
||||
// Add developer instructions from collaboration_mode if they exist and are non-empty
|
||||
if let Some(collab_instructions) =
|
||||
DeveloperInstructions::from_collaboration_mode(&collaboration_mode)
|
||||
if prompt_context.collaboration
|
||||
&& let Some(collab_instructions) =
|
||||
DeveloperInstructions::from_collaboration_mode(&collaboration_mode)
|
||||
{
|
||||
developer_sections.push(collab_instructions.into_text());
|
||||
}
|
||||
if let Some(realtime_update) = crate::context_manager::updates::build_initial_realtime_item(
|
||||
reference_context_item.as_ref(),
|
||||
previous_turn_settings.as_ref(),
|
||||
turn_context,
|
||||
) {
|
||||
if prompt_context.realtime
|
||||
&& let Some(realtime_update) =
|
||||
crate::context_manager::updates::build_initial_realtime_item(
|
||||
reference_context_item.as_ref(),
|
||||
previous_turn_settings.as_ref(),
|
||||
turn_context,
|
||||
)
|
||||
{
|
||||
developer_sections.push(realtime_update.into_text());
|
||||
}
|
||||
if self.features.enabled(Feature::Personality)
|
||||
if prompt_context.personality
|
||||
&& self.features.enabled(Feature::Personality)
|
||||
&& let Some(personality) = turn_context.personality
|
||||
{
|
||||
let model_info = turn_context.model_info.clone();
|
||||
@@ -3771,7 +3907,10 @@ impl Session {
|
||||
);
|
||||
}
|
||||
}
|
||||
if turn_context.config.include_apps_instructions && turn_context.apps_enabled() {
|
||||
if capabilities.apps
|
||||
&& turn_context.config.include_apps_instructions
|
||||
&& turn_context.apps_enabled()
|
||||
{
|
||||
let mcp_connection_manager = self.services.mcp_connection_manager.read().await;
|
||||
let accessible_and_enabled_connectors =
|
||||
connectors::list_accessible_and_enabled_connectors_from_manager(
|
||||
@@ -3783,29 +3922,38 @@ impl Session {
|
||||
developer_sections.push(apps_section);
|
||||
}
|
||||
}
|
||||
let implicit_skills = turn_context
|
||||
.turn_skills
|
||||
.outcome
|
||||
.allowed_skills_for_implicit_invocation();
|
||||
if let Some(skills_section) = render_skills_section(&implicit_skills) {
|
||||
developer_sections.push(skills_section);
|
||||
if capabilities.skills {
|
||||
let implicit_skills = turn_context
|
||||
.turn_skills
|
||||
.outcome
|
||||
.allowed_skills_for_implicit_invocation();
|
||||
if let Some(skills_section) = render_skills_section(&implicit_skills) {
|
||||
developer_sections.push(skills_section);
|
||||
}
|
||||
}
|
||||
let loaded_plugins = self
|
||||
.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(&turn_context.config);
|
||||
if let Some(plugin_section) = render_plugins_section(loaded_plugins.capability_summaries())
|
||||
{
|
||||
developer_sections.push(plugin_section);
|
||||
if capabilities.plugins {
|
||||
let loaded_plugins = self
|
||||
.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(&turn_context.config);
|
||||
if let Some(plugin_section) =
|
||||
render_plugins_section(loaded_plugins.capability_summaries())
|
||||
{
|
||||
developer_sections.push(plugin_section);
|
||||
}
|
||||
}
|
||||
if turn_context.features.enabled(Feature::CodexGitCommit)
|
||||
if prompt_context.commit
|
||||
&& capabilities.git_commit
|
||||
&& turn_context.features.enabled(Feature::CodexGitCommit)
|
||||
&& let Some(commit_message_instruction) = commit_message_trailer_instruction(
|
||||
turn_context.config.commit_attribution.as_deref(),
|
||||
)
|
||||
{
|
||||
developer_sections.push(commit_message_instruction);
|
||||
}
|
||||
if let Some(user_instructions) = turn_context.user_instructions.as_deref() {
|
||||
if prompt_context.user_instructions
|
||||
&& let Some(user_instructions) = turn_context.user_instructions.as_deref()
|
||||
{
|
||||
contextual_user_sections.push(
|
||||
UserInstructions {
|
||||
text: user_instructions.to_string(),
|
||||
@@ -3814,7 +3962,7 @@ impl Session {
|
||||
.serialize_to_text(),
|
||||
);
|
||||
}
|
||||
if turn_context.config.include_environment_context {
|
||||
if prompt_context.environment_context && turn_context.config.include_environment_context {
|
||||
let subagents = self
|
||||
.services
|
||||
.agent_control
|
||||
@@ -3838,9 +3986,9 @@ impl Session {
|
||||
{
|
||||
items.push(contextual_user_message);
|
||||
}
|
||||
// Emit the guardian policy prompt as a separate developer item so the guardian
|
||||
// subagent sees a distinct, easy-to-audit instruction block.
|
||||
if separate_guardian_developer_message
|
||||
// Emit selected role policy as a separate developer item.
|
||||
if prompt_context.developer_instructions
|
||||
&& prompt_context.separate_developer_instructions
|
||||
&& let Some(developer_instructions) = turn_context.developer_instructions.as_deref()
|
||||
&& let Some(guardian_developer_message) =
|
||||
crate::context_manager::updates::build_developer_update_item(vec![
|
||||
@@ -4438,6 +4586,9 @@ impl Session {
|
||||
mcp_servers: HashMap<String, McpServerConfig>,
|
||||
store_mode: OAuthCredentialsStoreMode,
|
||||
) {
|
||||
if !turn_context.surface_policy.capabilities.mcp {
|
||||
return;
|
||||
}
|
||||
let auth = self.services.auth_manager.auth().await;
|
||||
let config = self.get_config().await;
|
||||
let mcp_config = config.to_mcp_config(self.services.plugins_manager.as_ref());
|
||||
@@ -5765,6 +5916,7 @@ async fn spawn_review_thread(
|
||||
environment: parent_turn_context.environment.clone(),
|
||||
tools_config,
|
||||
features: parent_turn_context.features.clone(),
|
||||
surface_policy: parent_turn_context.surface_policy,
|
||||
ghost_snapshot: parent_turn_context.ghost_snapshot.clone(),
|
||||
current_date: parent_turn_context.current_date.clone(),
|
||||
timezone: parent_turn_context.timezone.clone(),
|
||||
@@ -5913,39 +6065,46 @@ pub(crate) async fn run_turn(
|
||||
client_session.reset_websocket_session();
|
||||
}
|
||||
|
||||
let skills_outcome = Some(turn_context.turn_skills.outcome.as_ref());
|
||||
let capabilities = turn_context.surface_policy.capabilities;
|
||||
let skills_outcome = capabilities
|
||||
.skills
|
||||
.then_some(turn_context.turn_skills.outcome.as_ref());
|
||||
|
||||
sess.record_context_updates_and_set_reference_context_item(turn_context.as_ref())
|
||||
.await;
|
||||
|
||||
let loaded_plugins = sess
|
||||
.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(&turn_context.config);
|
||||
let loaded_plugins = if capabilities.plugins {
|
||||
sess.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(&turn_context.config)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
// Structured plugin:// mentions are resolved from the current session's
|
||||
// enabled plugins, then converted into turn-scoped guidance below.
|
||||
let mentioned_plugins =
|
||||
collect_explicit_plugin_mentions(&input, loaded_plugins.capability_summaries());
|
||||
let mcp_tools = if turn_context.apps_enabled() || !mentioned_plugins.is_empty() {
|
||||
// Plugin mentions need raw MCP/app inventory even when app tools
|
||||
// are normally hidden so we can describe the plugin's currently
|
||||
// usable capabilities for this turn.
|
||||
match sess
|
||||
.services
|
||||
.mcp_connection_manager
|
||||
.read()
|
||||
.await
|
||||
.list_all_tools()
|
||||
.or_cancel(&cancellation_token)
|
||||
.await
|
||||
{
|
||||
Ok(mcp_tools) => mcp_tools,
|
||||
Err(_) if turn_context.apps_enabled() => return None,
|
||||
Err(_) => HashMap::new(),
|
||||
}
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
let mcp_tools =
|
||||
if capabilities.mcp && (turn_context.apps_enabled() || !mentioned_plugins.is_empty()) {
|
||||
// Plugin mentions need raw MCP/app inventory even when app tools
|
||||
// are normally hidden so we can describe the plugin's currently
|
||||
// usable capabilities for this turn.
|
||||
match sess
|
||||
.services
|
||||
.mcp_connection_manager
|
||||
.read()
|
||||
.await
|
||||
.list_all_tools()
|
||||
.or_cancel(&cancellation_token)
|
||||
.await
|
||||
{
|
||||
Ok(mcp_tools) => mcp_tools,
|
||||
Err(_) if turn_context.apps_enabled() => return None,
|
||||
Err(_) => HashMap::new(),
|
||||
}
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
let available_connectors = if turn_context.apps_enabled() {
|
||||
let connectors = connectors::merge_plugin_apps_with_accessible(
|
||||
loaded_plugins.effective_apps(),
|
||||
@@ -5970,21 +6129,24 @@ pub(crate) async fn run_turn(
|
||||
)
|
||||
});
|
||||
let config = turn_context.config.clone();
|
||||
if config
|
||||
.features
|
||||
.enabled(Feature::SkillEnvVarDependencyPrompt)
|
||||
if capabilities.skill_dependencies
|
||||
&& config
|
||||
.features
|
||||
.enabled(Feature::SkillEnvVarDependencyPrompt)
|
||||
{
|
||||
let env_var_dependencies = collect_env_var_dependencies(&mentioned_skills);
|
||||
resolve_skill_dependencies_for_turn(&sess, &turn_context, &env_var_dependencies).await;
|
||||
}
|
||||
|
||||
maybe_prompt_and_install_mcp_dependencies(
|
||||
sess.as_ref(),
|
||||
turn_context.as_ref(),
|
||||
&cancellation_token,
|
||||
&mentioned_skills,
|
||||
)
|
||||
.await;
|
||||
if capabilities.skill_dependencies {
|
||||
maybe_prompt_and_install_mcp_dependencies(
|
||||
sess.as_ref(),
|
||||
turn_context.as_ref(),
|
||||
&cancellation_token,
|
||||
&mentioned_skills,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
let session_telemetry = turn_context.session_telemetry.clone();
|
||||
let thread_id = sess.conversation_id.to_string();
|
||||
@@ -6706,16 +6868,19 @@ async fn run_sampling_request(
|
||||
Arc::clone(&turn_context),
|
||||
Arc::clone(&turn_diff_tracker),
|
||||
);
|
||||
let _code_mode_worker = sess
|
||||
.services
|
||||
.code_mode_service
|
||||
.start_turn_worker(
|
||||
&sess,
|
||||
&turn_context,
|
||||
Arc::clone(&router),
|
||||
Arc::clone(&turn_diff_tracker),
|
||||
)
|
||||
.await;
|
||||
let _code_mode_worker = if turn_context.surface_policy.capabilities.code_mode {
|
||||
sess.services
|
||||
.code_mode_service
|
||||
.start_turn_worker(
|
||||
&sess,
|
||||
&turn_context,
|
||||
Arc::clone(&router),
|
||||
Arc::clone(&turn_diff_tracker),
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut retries = 0;
|
||||
loop {
|
||||
let err = match try_run_sampling_request(
|
||||
@@ -6813,17 +6978,25 @@ pub(crate) async fn built_tools(
|
||||
skills_outcome: Option<&SkillLoadOutcome>,
|
||||
cancellation_token: &CancellationToken,
|
||||
) -> CodexResult<Arc<ToolRouter>> {
|
||||
let mcp_connection_manager = sess.services.mcp_connection_manager.read().await;
|
||||
let has_mcp_servers = mcp_connection_manager.has_servers();
|
||||
let mut mcp_tools = mcp_connection_manager
|
||||
.list_all_tools()
|
||||
.or_cancel(cancellation_token)
|
||||
.await?;
|
||||
drop(mcp_connection_manager);
|
||||
let loaded_plugins = sess
|
||||
.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(&turn_context.config);
|
||||
let capabilities = turn_context.surface_policy.capabilities;
|
||||
let (has_mcp_servers, mut mcp_tools) = if capabilities.mcp {
|
||||
let mcp_connection_manager = sess.services.mcp_connection_manager.read().await;
|
||||
let has_mcp_servers = mcp_connection_manager.has_servers();
|
||||
let mcp_tools = mcp_connection_manager
|
||||
.list_all_tools()
|
||||
.or_cancel(cancellation_token)
|
||||
.await?;
|
||||
(has_mcp_servers, mcp_tools)
|
||||
} else {
|
||||
(false, HashMap::new())
|
||||
};
|
||||
let loaded_plugins = if capabilities.plugins {
|
||||
sess.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(&turn_context.config)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let mut effective_explicitly_enabled_connectors = explicitly_enabled_connectors.clone();
|
||||
effective_explicitly_enabled_connectors.extend(sess.get_connector_selection().await);
|
||||
@@ -6930,7 +7103,11 @@ pub(crate) async fn built_tools(
|
||||
.map(|inputs| inputs.tool_namespaces.clone()),
|
||||
app_tools,
|
||||
discoverable_tools,
|
||||
dynamic_tools: turn_context.dynamic_tools.as_slice(),
|
||||
dynamic_tools: if capabilities.dynamic_tools {
|
||||
turn_context.dynamic_tools.as_slice()
|
||||
} else {
|
||||
&[]
|
||||
},
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ use crate::mcp_tool_call::MCP_TOOL_APPROVAL_DECLINE_SYNTHETIC;
|
||||
use crate::mcp_tool_call::build_guardian_mcp_tool_review_request;
|
||||
use crate::mcp_tool_call::is_mcp_tool_approval_question_id;
|
||||
use crate::mcp_tool_call::lookup_mcp_tool_metadata;
|
||||
use crate::session_surface::SessionSurfacePolicy;
|
||||
use codex_login::AuthManager;
|
||||
use codex_models_manager::manager::ModelsManager;
|
||||
use codex_protocol::error::CodexErr;
|
||||
@@ -63,6 +64,7 @@ use crate::codex::completed_session_loop_termination;
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn run_codex_thread_interactive(
|
||||
config: Config,
|
||||
surface_policy: SessionSurfacePolicy,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
models_manager: Arc<ModelsManager>,
|
||||
parent_session: Arc<Session>,
|
||||
@@ -76,6 +78,7 @@ pub(crate) async fn run_codex_thread_interactive(
|
||||
|
||||
let CodexSpawnOk { codex, .. } = Codex::spawn(CodexSpawnArgs {
|
||||
config,
|
||||
surface_policy,
|
||||
auth_manager,
|
||||
models_manager,
|
||||
environment_manager: Arc::new(EnvironmentManager::from_environment(
|
||||
@@ -171,6 +174,7 @@ pub(crate) async fn run_codex_thread_one_shot(
|
||||
let child_cancel = cancel_token.child_token();
|
||||
let io = run_codex_thread_interactive(
|
||||
config,
|
||||
SessionSurfacePolicy::full(),
|
||||
auth_manager,
|
||||
models_manager,
|
||||
parent_session,
|
||||
|
||||
@@ -1993,6 +1993,7 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
surface_policy: SessionSurfacePolicy::full(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
@@ -2095,6 +2096,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
surface_policy: SessionSurfacePolicy::full(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
@@ -2444,6 +2446,7 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
surface_policy: SessionSurfacePolicy::full(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
@@ -2707,6 +2710,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
surface_policy: SessionSurfacePolicy::full(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
@@ -2810,6 +2814,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
surface_policy: SessionSurfacePolicy::full(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
@@ -3654,6 +3659,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
surface_policy: SessionSurfacePolicy::full(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::exec::ExecParams;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::guardian::GUARDIAN_REVIEWER_NAME;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::session_surface::SessionSurfacePolicy;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
@@ -244,6 +245,7 @@ async fn process_compacted_history_preserves_separate_guardian_developer_message
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.session_configuration.session_source = guardian_source.clone();
|
||||
state.session_configuration.surface_policy = SessionSurfacePolicy::guardian_review();
|
||||
}
|
||||
turn_context.session_source = guardian_source;
|
||||
turn_context.developer_instructions = Some(guardian_policy.clone());
|
||||
@@ -290,8 +292,7 @@ async fn process_compacted_history_preserves_separate_guardian_developer_message
|
||||
.iter()
|
||||
.any(|message| message.contains("stale developer message"))
|
||||
);
|
||||
assert!(developer_messages.len() >= 2);
|
||||
assert_eq!(developer_messages.last(), Some(&guardian_policy));
|
||||
assert_eq!(developer_messages, vec![guardian_policy]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -435,6 +436,7 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
|
||||
|
||||
let CodexSpawnOk { codex, .. } = Codex::spawn(CodexSpawnArgs {
|
||||
config,
|
||||
surface_policy: SessionSurfacePolicy::full(),
|
||||
auth_manager,
|
||||
models_manager,
|
||||
environment_manager: Arc::new(EnvironmentManager::new(/*exec_server_url*/ None)),
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
//! relevant recent assistant and tool context.
|
||||
//! 2. Ask a dedicated guardian review session to assess the exact planned
|
||||
//! action and return strict JSON.
|
||||
//! The guardian clones the parent config, so it inherits any managed
|
||||
//! network proxy / allowlist that the parent turn already had.
|
||||
//! The guardian runs with a sanitized review-session config. It may inherit
|
||||
//! the parent managed-network proxy / allowlist for read-only checks, but it
|
||||
//! does not inherit parent prompt context.
|
||||
//! 3. Fail closed on timeout, execution failure, or malformed output.
|
||||
//! 4. Apply the guardian's explicit allow/deny outcome.
|
||||
|
||||
|
||||
@@ -365,7 +365,7 @@ pub(super) async fn run_guardian_review_session(
|
||||
model: guardian_model,
|
||||
reasoning_effort: guardian_reasoning_effort,
|
||||
reasoning_summary: turn.reasoning_summary,
|
||||
personality: turn.personality,
|
||||
personality: None,
|
||||
external_cancel,
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -31,12 +30,10 @@ use crate::codex::TurnContext;
|
||||
use crate::codex_delegate::run_codex_thread_interactive;
|
||||
use crate::config::Config;
|
||||
use crate::config::Constrained;
|
||||
use crate::config::ManagedFeatures;
|
||||
use crate::config::NetworkProxySpec;
|
||||
use crate::config::Permissions;
|
||||
use crate::rollout::recorder::RolloutRecorder;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_features::Feature;
|
||||
use crate::session_surface::SessionSurfacePolicy;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
|
||||
use super::GUARDIAN_REVIEW_TIMEOUT;
|
||||
@@ -45,6 +42,11 @@ use super::prompt::guardian_policy_prompt;
|
||||
use super::prompt::guardian_policy_prompt_with_config;
|
||||
|
||||
const GUARDIAN_INTERRUPT_DRAIN_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
const GUARDIAN_BASE_INSTRUCTIONS: &str = concat!(
|
||||
"You are Codex Guardian, a focused approval-review agent. ",
|
||||
"Follow the guardian developer policy. ",
|
||||
"Return only the required final JSON."
|
||||
);
|
||||
const GUARDIAN_FOLLOWUP_REVIEW_REMINDER: &str = concat!(
|
||||
"Use prior reviews as context, not binding precedent. ",
|
||||
"Follow the Workspace Policy. ",
|
||||
@@ -111,19 +113,7 @@ struct GuardianReviewSessionReuseKey {
|
||||
model_reasoning_summary: Option<ReasoningSummaryConfig>,
|
||||
permissions: Permissions,
|
||||
developer_instructions: Option<String>,
|
||||
base_instructions: Option<String>,
|
||||
user_instructions: Option<String>,
|
||||
compact_prompt: Option<String>,
|
||||
cwd: PathBuf,
|
||||
mcp_servers: Constrained<HashMap<String, McpServerConfig>>,
|
||||
codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
main_execve_wrapper_exe: Option<PathBuf>,
|
||||
js_repl_node_path: Option<PathBuf>,
|
||||
js_repl_node_module_dirs: Vec<PathBuf>,
|
||||
zsh_path: Option<PathBuf>,
|
||||
features: ManagedFeatures,
|
||||
include_apply_patch_tool: bool,
|
||||
use_experimental_unified_exec_tool: bool,
|
||||
}
|
||||
|
||||
impl GuardianReviewSessionReuseKey {
|
||||
@@ -138,19 +128,7 @@ impl GuardianReviewSessionReuseKey {
|
||||
model_reasoning_summary: spawn_config.model_reasoning_summary,
|
||||
permissions: spawn_config.permissions.clone(),
|
||||
developer_instructions: spawn_config.developer_instructions.clone(),
|
||||
base_instructions: spawn_config.base_instructions.clone(),
|
||||
user_instructions: spawn_config.user_instructions.clone(),
|
||||
compact_prompt: spawn_config.compact_prompt.clone(),
|
||||
cwd: spawn_config.cwd.to_path_buf(),
|
||||
mcp_servers: spawn_config.mcp_servers.clone(),
|
||||
codex_linux_sandbox_exe: spawn_config.codex_linux_sandbox_exe.clone(),
|
||||
main_execve_wrapper_exe: spawn_config.main_execve_wrapper_exe.clone(),
|
||||
js_repl_node_path: spawn_config.js_repl_node_path.clone(),
|
||||
js_repl_node_module_dirs: spawn_config.js_repl_node_module_dirs.clone(),
|
||||
zsh_path: spawn_config.zsh_path.clone(),
|
||||
features: spawn_config.features.clone(),
|
||||
include_apply_patch_tool: spawn_config.include_apply_patch_tool,
|
||||
use_experimental_unified_exec_tool: spawn_config.use_experimental_unified_exec_tool,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -467,6 +445,7 @@ async fn spawn_guardian_review_session(
|
||||
let has_prior_review = initial_history.is_some();
|
||||
let codex = run_codex_thread_interactive(
|
||||
spawn_config,
|
||||
SessionSurfacePolicy::guardian_review(),
|
||||
params.parent_session.services.auth_manager.clone(),
|
||||
params.parent_session.services.models_manager.clone(),
|
||||
Arc::clone(¶ms.parent_session),
|
||||
@@ -643,6 +622,9 @@ pub(crate) fn build_guardian_review_session_config(
|
||||
let mut guardian_config = parent_config.clone();
|
||||
guardian_config.model = Some(active_model.to_string());
|
||||
guardian_config.model_reasoning_effort = reasoning_effort;
|
||||
guardian_config.personality = None;
|
||||
guardian_config.base_instructions = Some(GUARDIAN_BASE_INSTRUCTIONS.to_string());
|
||||
guardian_config.user_instructions = None;
|
||||
guardian_config.developer_instructions = Some(
|
||||
parent_config
|
||||
.guardian_policy_config
|
||||
@@ -650,6 +632,13 @@ pub(crate) fn build_guardian_review_session_config(
|
||||
.map(guardian_policy_prompt_with_config)
|
||||
.unwrap_or_else(guardian_policy_prompt),
|
||||
);
|
||||
guardian_config.compact_prompt = None;
|
||||
guardian_config.include_permissions_instructions = false;
|
||||
guardian_config.include_apps_instructions = false;
|
||||
guardian_config.include_environment_context = true;
|
||||
guardian_config.include_apply_patch_tool = false;
|
||||
guardian_config.use_experimental_unified_exec_tool = false;
|
||||
guardian_config.permissions.allow_login_shell = false;
|
||||
guardian_config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);
|
||||
guardian_config.permissions.sandbox_policy =
|
||||
Constrained::allow_only(SandboxPolicy::new_read_only_policy());
|
||||
@@ -668,25 +657,6 @@ pub(crate) fn build_guardian_review_session_config(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
)?);
|
||||
}
|
||||
for feature in [
|
||||
Feature::SpawnCsv,
|
||||
Feature::Collab,
|
||||
Feature::WebSearchRequest,
|
||||
Feature::WebSearchCached,
|
||||
] {
|
||||
guardian_config.features.disable(feature).map_err(|err| {
|
||||
anyhow::anyhow!(
|
||||
"guardian review session could not disable `features.{}`: {err}",
|
||||
feature.key()
|
||||
)
|
||||
})?;
|
||||
if guardian_config.features.enabled(feature) {
|
||||
anyhow::bail!(
|
||||
"guardian review session requires `features.{}` to be disabled",
|
||||
feature.key()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(guardian_config)
|
||||
}
|
||||
|
||||
@@ -777,6 +747,36 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_review_session_cwd_change_does_not_invalidate_cached_session() {
|
||||
let mut parent_config = crate::config::test_config();
|
||||
let cached_spawn_config = build_guardian_review_session_config(
|
||||
&parent_config,
|
||||
/*live_network_config*/ None,
|
||||
"active-model",
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect("cached guardian config");
|
||||
let cached_reuse_key =
|
||||
GuardianReviewSessionReuseKey::from_spawn_config(&cached_spawn_config);
|
||||
|
||||
parent_config.cwd =
|
||||
codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path("/tmp/guardian-cwd")
|
||||
.expect("absolute cwd");
|
||||
let next_spawn_config = build_guardian_review_session_config(
|
||||
&parent_config,
|
||||
/*live_network_config*/ None,
|
||||
"active-model",
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect("next guardian config");
|
||||
|
||||
assert_eq!(
|
||||
cached_reuse_key,
|
||||
GuardianReviewSessionReuseKey::from_spawn_config(&next_spawn_config)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn run_before_review_deadline_times_out_before_future_completes() {
|
||||
let outcome = run_before_review_deadline(
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -15,7 +15,10 @@ use crate::config_loader::NetworkDomainPermissionsToml;
|
||||
use crate::config_loader::RequirementSource;
|
||||
use crate::config_loader::Sourced;
|
||||
use crate::test_support;
|
||||
use codex_config::McpServerConfig;
|
||||
use codex_config::McpServerTransportConfig;
|
||||
use codex_config::config_toml::ConfigToml;
|
||||
use codex_features::Feature;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
use codex_protocol::approvals::NetworkApprovalProtocol;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
@@ -557,9 +560,20 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
|
||||
|
||||
let (mut session, mut turn) = crate::codex::make_session_and_context().await;
|
||||
let temp_cwd = TempDir::new()?;
|
||||
std::fs::write(
|
||||
temp_cwd.path().join("AGENTS.md"),
|
||||
"GUARDIAN_REPO_AGENTS_SHOULD_BE_VISIBLE",
|
||||
)?;
|
||||
let mut config = (*turn.config).clone();
|
||||
config.cwd = temp_cwd.abs();
|
||||
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||
config.base_instructions = Some("PARENT_BASE_INSTRUCTIONS_SHOULD_NOT_BE_VISIBLE".to_string());
|
||||
config.user_instructions = Some("PARENT_USER_INSTRUCTIONS_SHOULD_NOT_BE_VISIBLE".to_string());
|
||||
config.developer_instructions =
|
||||
Some("PARENT_DEVELOPER_INSTRUCTIONS_SHOULD_NOT_BE_VISIBLE".to_string());
|
||||
config.include_permissions_instructions = true;
|
||||
config.include_apps_instructions = true;
|
||||
config.include_environment_context = true;
|
||||
let config = Arc::new(config);
|
||||
let models_manager = Arc::new(test_support::models_manager_with_provider(
|
||||
config.codex_home.clone(),
|
||||
@@ -608,6 +622,30 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
|
||||
assert_eq!(assessment.outcome, GuardianAssessmentOutcome::Allow);
|
||||
|
||||
let request = request_log.single_request();
|
||||
let request_body = request.body_json();
|
||||
let request_body_text = request_body.to_string();
|
||||
for omitted_parent_text in [
|
||||
"PARENT_BASE_INSTRUCTIONS_SHOULD_NOT_BE_VISIBLE",
|
||||
"PARENT_USER_INSTRUCTIONS_SHOULD_NOT_BE_VISIBLE",
|
||||
"PARENT_DEVELOPER_INSTRUCTIONS_SHOULD_NOT_BE_VISIBLE",
|
||||
"<permissions instructions>",
|
||||
"<apps_instructions>",
|
||||
"<skills_instructions>",
|
||||
"<plugins_instructions>",
|
||||
] {
|
||||
assert!(
|
||||
!request_body_text.contains(omitted_parent_text),
|
||||
"guardian review request should omit inherited parent text: {omitted_parent_text}"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
request_body_text.contains("GUARDIAN_REPO_AGENTS_SHOULD_BE_VISIBLE"),
|
||||
"guardian review request should include repo AGENTS.md project docs"
|
||||
);
|
||||
assert!(
|
||||
request_body_text.contains("<environment_context>"),
|
||||
"guardian review request should include existing environment context"
|
||||
);
|
||||
let mut settings = Settings::clone_current();
|
||||
settings.set_snapshot_path("snapshots");
|
||||
settings.set_prepend_module_to_snapshot(false);
|
||||
@@ -1083,6 +1121,108 @@ fn guardian_review_session_config_overrides_parent_developer_instructions() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_review_session_config_strips_parent_prompt_and_tooling_surface() {
|
||||
let mut parent_config = test_config();
|
||||
parent_config.base_instructions = Some("parent base instructions".to_string());
|
||||
parent_config.user_instructions = Some("parent user instructions".to_string());
|
||||
parent_config.developer_instructions = Some("parent developer instructions".to_string());
|
||||
parent_config.compact_prompt = Some("parent compact prompt".to_string());
|
||||
parent_config.include_permissions_instructions = true;
|
||||
parent_config.include_apps_instructions = true;
|
||||
parent_config.include_environment_context = true;
|
||||
parent_config.project_doc_max_bytes = 4096;
|
||||
parent_config
|
||||
.mcp_servers
|
||||
.set(std::collections::HashMap::from([(
|
||||
"parent-mcp".to_string(),
|
||||
McpServerConfig {
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "parent-mcp".to_string(),
|
||||
args: Vec::new(),
|
||||
env: None,
|
||||
env_vars: Vec::new(),
|
||||
cwd: None,
|
||||
},
|
||||
enabled: true,
|
||||
required: false,
|
||||
disabled_reason: None,
|
||||
startup_timeout_sec: None,
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
scopes: None,
|
||||
oauth_resource: None,
|
||||
tools: std::collections::HashMap::new(),
|
||||
},
|
||||
)]))
|
||||
.expect("set parent mcp servers");
|
||||
parent_config.js_repl_node_path = Some(PathBuf::from("/parent/node"));
|
||||
parent_config.js_repl_node_module_dirs = vec![PathBuf::from("/parent/node_modules")];
|
||||
parent_config.zsh_path = Some(PathBuf::from("/parent/zsh"));
|
||||
parent_config.main_execve_wrapper_exe = Some(PathBuf::from("/parent/execve-wrapper"));
|
||||
parent_config.include_apply_patch_tool = true;
|
||||
parent_config.use_experimental_unified_exec_tool = true;
|
||||
parent_config
|
||||
.features
|
||||
.enable(Feature::JsRepl)
|
||||
.expect("enable js repl");
|
||||
parent_config
|
||||
.features
|
||||
.enable(Feature::CodeMode)
|
||||
.expect("enable code mode");
|
||||
parent_config
|
||||
.features
|
||||
.enable(Feature::MemoryTool)
|
||||
.expect("enable memory");
|
||||
parent_config
|
||||
.features
|
||||
.enable(Feature::Collab)
|
||||
.expect("enable collab");
|
||||
parent_config
|
||||
.features
|
||||
.enable(Feature::SpawnCsv)
|
||||
.expect("enable spawn csv");
|
||||
parent_config
|
||||
.features
|
||||
.enable(Feature::ApplyPatchFreeform)
|
||||
.expect("enable apply patch");
|
||||
|
||||
let guardian_config = build_guardian_review_session_config_for_test(
|
||||
&parent_config,
|
||||
/*live_network_config*/ None,
|
||||
"active-model",
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect("guardian config");
|
||||
|
||||
assert_eq!(
|
||||
guardian_config.base_instructions.as_deref(),
|
||||
Some(
|
||||
"You are Codex Guardian, a focused approval-review agent. Follow the guardian developer policy. Return only the required final JSON."
|
||||
)
|
||||
);
|
||||
assert_eq!(guardian_config.user_instructions, None);
|
||||
assert_eq!(guardian_config.compact_prompt, None);
|
||||
assert!(!guardian_config.include_permissions_instructions);
|
||||
assert!(!guardian_config.include_apps_instructions);
|
||||
assert!(guardian_config.include_environment_context);
|
||||
assert_eq!(guardian_config.project_doc_max_bytes, 4096);
|
||||
let guardian_surface = crate::session_surface::SessionSurfacePolicy::guardian_review();
|
||||
assert!(guardian_surface.prompt.developer_instructions);
|
||||
assert!(guardian_surface.prompt.separate_developer_instructions);
|
||||
assert!(guardian_surface.prompt.user_instructions);
|
||||
assert!(guardian_surface.prompt.environment_context);
|
||||
assert!(!guardian_surface.capabilities.mcp);
|
||||
assert!(!guardian_surface.capabilities.apps);
|
||||
assert!(!guardian_surface.capabilities.plugins);
|
||||
assert!(!guardian_surface.capabilities.skills);
|
||||
assert!(!guardian_surface.capabilities.memory);
|
||||
assert!(!guardian_config.include_apply_patch_tool);
|
||||
assert!(!guardian_config.use_experimental_unified_exec_tool);
|
||||
assert!(!guardian_config.permissions.allow_login_shell);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
let mut parent_config = test_config();
|
||||
@@ -1128,31 +1268,33 @@ fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_review_session_config_rejects_pinned_collab_feature() {
|
||||
fn guardian_review_session_config_allows_pinned_parent_extension_features() {
|
||||
let mut parent_config = test_config();
|
||||
parent_config.features = ManagedFeatures::from_configured(
|
||||
parent_config.features.get().clone(),
|
||||
Some(Sourced {
|
||||
value: FeatureRequirementsToml {
|
||||
entries: BTreeMap::from([("multi_agent".to_string(), true)]),
|
||||
entries: BTreeMap::from([
|
||||
("apps".to_string(), true),
|
||||
("multi_agent".to_string(), true),
|
||||
]),
|
||||
},
|
||||
source: RequirementSource::Unknown,
|
||||
}),
|
||||
)
|
||||
.expect("managed features");
|
||||
|
||||
let err = build_guardian_review_session_config_for_test(
|
||||
let _guardian_config = build_guardian_review_session_config_for_test(
|
||||
&parent_config,
|
||||
/*live_network_config*/ None,
|
||||
"active-model",
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect_err("guardian config should fail when collab is pinned on");
|
||||
.expect("guardian config should not fail when parent extension features are pinned on");
|
||||
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("guardian review session requires `features.multi_agent` to be disabled")
|
||||
);
|
||||
let guardian_surface = crate::session_surface::SessionSurfacePolicy::guardian_review();
|
||||
assert!(!guardian_surface.capabilities.apps);
|
||||
assert!(!guardian_surface.capabilities.subagents);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -41,6 +41,7 @@ mod hook_runtime;
|
||||
mod installation_id;
|
||||
pub(crate) mod instructions;
|
||||
pub(crate) mod landlock;
|
||||
mod session_surface;
|
||||
pub use landlock::spawn_command_under_linux_sandbox;
|
||||
pub(crate) mod mcp;
|
||||
mod mcp_skill_dependencies;
|
||||
|
||||
@@ -42,6 +42,12 @@ pub const LOCAL_PROJECT_DOC_FILENAME: &str = "AGENTS.override.md";
|
||||
/// be concatenated with the following separator.
|
||||
const PROJECT_DOC_SEPARATOR: &str = "\n\n--- project-doc ---\n\n";
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct UserInstructionExtras {
|
||||
pub(crate) js_repl: bool,
|
||||
pub(crate) child_agents_md: bool,
|
||||
}
|
||||
|
||||
fn render_js_repl_instructions(config: &Config) -> Option<String> {
|
||||
if !config.features.enabled(Feature::JsRepl) {
|
||||
return None;
|
||||
@@ -78,17 +84,19 @@ fn render_js_repl_instructions(config: &Config) -> Option<String> {
|
||||
|
||||
/// Combines `Config::instructions` and `AGENTS.md` (if present) into a single
|
||||
/// string of instructions.
|
||||
pub(crate) async fn get_user_instructions(
|
||||
pub(crate) async fn get_user_instructions_with_extras(
|
||||
config: &Config,
|
||||
environment: Option<&Environment>,
|
||||
extras: UserInstructionExtras,
|
||||
) -> Option<String> {
|
||||
let fs = environment?.get_filesystem();
|
||||
get_user_instructions_with_fs(config, fs.as_ref()).await
|
||||
get_user_instructions_with_fs(config, fs.as_ref(), extras).await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_user_instructions_with_fs(
|
||||
config: &Config,
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
extras: UserInstructionExtras,
|
||||
) -> Option<String> {
|
||||
let project_docs = read_project_docs_with_fs(config, fs).await;
|
||||
|
||||
@@ -111,14 +119,16 @@ pub(crate) async fn get_user_instructions_with_fs(
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(js_repl_section) = render_js_repl_instructions(config) {
|
||||
if extras.js_repl
|
||||
&& let Some(js_repl_section) = render_js_repl_instructions(config)
|
||||
{
|
||||
if !output.is_empty() {
|
||||
output.push_str("\n\n");
|
||||
}
|
||||
output.push_str(&js_repl_section);
|
||||
}
|
||||
|
||||
if config.features.enabled(Feature::ChildAgentsMd) {
|
||||
if extras.child_agents_md && config.features.enabled(Feature::ChildAgentsMd) {
|
||||
if !output.is_empty() {
|
||||
output.push_str("\n\n");
|
||||
}
|
||||
|
||||
@@ -11,7 +11,15 @@ use std::path::PathBuf;
|
||||
use tempfile::TempDir;
|
||||
|
||||
async fn get_user_instructions(config: &Config) -> Option<String> {
|
||||
super::get_user_instructions_with_fs(config, LOCAL_FS.as_ref()).await
|
||||
super::get_user_instructions_with_fs(
|
||||
config,
|
||||
LOCAL_FS.as_ref(),
|
||||
super::UserInstructionExtras {
|
||||
js_repl: true,
|
||||
child_agents_md: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn discover_project_doc_paths(config: &Config) -> std::io::Result<Vec<AbsolutePathBuf>> {
|
||||
@@ -101,7 +109,15 @@ async fn no_environment_returns_none() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
let config = make_config(&tmp, /*limit*/ 4096, Some("user instructions")).await;
|
||||
|
||||
let res = super::get_user_instructions(&config, /*environment*/ None).await;
|
||||
let res = super::get_user_instructions_with_extras(
|
||||
&config,
|
||||
/*environment*/ None,
|
||||
super::UserInstructionExtras {
|
||||
js_repl: true,
|
||||
child_agents_md: true,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(res, None);
|
||||
}
|
||||
@@ -247,6 +263,33 @@ async fn js_repl_image_detail_original_does_not_change_instructions() {
|
||||
assert_eq!(res, expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn instruction_extras_can_be_omitted_while_keeping_project_docs() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
fs::write(tmp.path().join("AGENTS.md"), "project guidance").unwrap();
|
||||
let mut cfg = make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await;
|
||||
let mut features = cfg.features.get().clone();
|
||||
features
|
||||
.enable(Feature::JsRepl)
|
||||
.enable(Feature::ChildAgentsMd);
|
||||
cfg.features
|
||||
.set(features)
|
||||
.expect("test config should allow instruction extras");
|
||||
|
||||
let res = super::get_user_instructions_with_fs(
|
||||
&cfg,
|
||||
LOCAL_FS.as_ref(),
|
||||
super::UserInstructionExtras {
|
||||
js_repl: false,
|
||||
child_agents_md: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("project doc expected");
|
||||
|
||||
assert_eq!(res, "project guidance");
|
||||
}
|
||||
|
||||
/// When both system instructions *and* a project doc are present the two
|
||||
/// should be concatenated with the separator.
|
||||
#[tokio::test]
|
||||
|
||||
136
codex-rs/core/src/session_surface.rs
Normal file
136
codex-rs/core/src/session_surface.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct SessionSurfacePolicy {
|
||||
pub(crate) prompt: PromptContextInclusions,
|
||||
pub(crate) capabilities: CapabilityInclusions,
|
||||
}
|
||||
|
||||
impl SessionSurfacePolicy {
|
||||
pub(crate) const fn full() -> Self {
|
||||
Self {
|
||||
prompt: PromptContextInclusions::full(),
|
||||
capabilities: CapabilityInclusions::full(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn guardian_review() -> Self {
|
||||
Self {
|
||||
prompt: PromptContextInclusions {
|
||||
model_update: false,
|
||||
permissions: false,
|
||||
developer_instructions: true,
|
||||
separate_developer_instructions: true,
|
||||
collaboration: false,
|
||||
realtime: false,
|
||||
personality: false,
|
||||
commit: false,
|
||||
user_instructions: true,
|
||||
environment_context: true,
|
||||
},
|
||||
capabilities: CapabilityInclusions::minimal_local(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct PromptContextInclusions {
|
||||
pub(crate) model_update: bool,
|
||||
pub(crate) permissions: bool,
|
||||
pub(crate) developer_instructions: bool,
|
||||
pub(crate) separate_developer_instructions: bool,
|
||||
pub(crate) collaboration: bool,
|
||||
pub(crate) realtime: bool,
|
||||
pub(crate) personality: bool,
|
||||
pub(crate) commit: bool,
|
||||
pub(crate) user_instructions: bool,
|
||||
pub(crate) environment_context: bool,
|
||||
}
|
||||
|
||||
impl PromptContextInclusions {
|
||||
pub(crate) const fn full() -> Self {
|
||||
Self {
|
||||
model_update: true,
|
||||
permissions: true,
|
||||
developer_instructions: true,
|
||||
separate_developer_instructions: false,
|
||||
collaboration: true,
|
||||
realtime: true,
|
||||
personality: true,
|
||||
commit: true,
|
||||
user_instructions: true,
|
||||
environment_context: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct CapabilityInclusions {
|
||||
pub(crate) mcp: bool,
|
||||
pub(crate) apps: bool,
|
||||
pub(crate) plugins: bool,
|
||||
pub(crate) skills: bool,
|
||||
pub(crate) memory: bool,
|
||||
pub(crate) dynamic_tools: bool,
|
||||
pub(crate) web_search: bool,
|
||||
pub(crate) js_repl: bool,
|
||||
pub(crate) code_mode: bool,
|
||||
pub(crate) apply_patch: bool,
|
||||
pub(crate) subagents: bool,
|
||||
pub(crate) image_generation: bool,
|
||||
pub(crate) request_permissions: bool,
|
||||
pub(crate) skill_dependencies: bool,
|
||||
pub(crate) shell_snapshot: bool,
|
||||
pub(crate) shell_zsh_fork: bool,
|
||||
pub(crate) unified_exec: bool,
|
||||
pub(crate) request_rule: bool,
|
||||
pub(crate) git_commit: bool,
|
||||
}
|
||||
|
||||
impl CapabilityInclusions {
|
||||
pub(crate) const fn full() -> Self {
|
||||
Self {
|
||||
mcp: true,
|
||||
apps: true,
|
||||
plugins: true,
|
||||
skills: true,
|
||||
memory: true,
|
||||
dynamic_tools: true,
|
||||
web_search: true,
|
||||
js_repl: true,
|
||||
code_mode: true,
|
||||
apply_patch: true,
|
||||
subagents: true,
|
||||
image_generation: true,
|
||||
request_permissions: true,
|
||||
skill_dependencies: true,
|
||||
shell_snapshot: true,
|
||||
shell_zsh_fork: true,
|
||||
unified_exec: true,
|
||||
request_rule: true,
|
||||
git_commit: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn minimal_local() -> Self {
|
||||
Self {
|
||||
mcp: false,
|
||||
apps: false,
|
||||
plugins: false,
|
||||
skills: false,
|
||||
memory: false,
|
||||
dynamic_tools: false,
|
||||
web_search: false,
|
||||
js_repl: false,
|
||||
code_mode: false,
|
||||
apply_patch: false,
|
||||
subagents: false,
|
||||
image_generation: false,
|
||||
request_permissions: false,
|
||||
skill_dependencies: false,
|
||||
shell_snapshot: false,
|
||||
shell_zsh_fork: false,
|
||||
unified_exec: false,
|
||||
request_rule: false,
|
||||
git_commit: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use crate::mcp::McpManager;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::rollout::RolloutRecorder;
|
||||
use crate::rollout::truncation;
|
||||
use crate::session_surface::SessionSurfacePolicy;
|
||||
use crate::shell_snapshot::ShellSnapshot;
|
||||
use crate::skills_watcher::SkillsWatcher;
|
||||
use crate::skills_watcher::SkillsWatcherEvent;
|
||||
@@ -899,6 +900,7 @@ impl ThreadManagerState {
|
||||
codex, thread_id, ..
|
||||
} = Codex::spawn(CodexSpawnArgs {
|
||||
config,
|
||||
surface_policy: SessionSurfacePolicy::full(),
|
||||
auth_manager,
|
||||
models_manager: Arc::clone(&self.models_manager),
|
||||
environment_manager: Arc::clone(&self.environment_manager),
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use anyhow::Context;
|
||||
use codex_features::Feature;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
use codex_protocol::AgentPath;
|
||||
@@ -221,16 +222,16 @@ pub(crate) fn build_agent_resume_config(
|
||||
}
|
||||
|
||||
fn build_agent_shared_config(turn: &TurnContext) -> Result<Config, FunctionCallError> {
|
||||
let base_config = turn.config.clone();
|
||||
let mut config = (*base_config).clone();
|
||||
let mut config = turn.config.as_ref().clone();
|
||||
config.model = Some(turn.model_info.slug.clone());
|
||||
config.model_provider = turn.provider.clone();
|
||||
config.model_reasoning_effort = turn.reasoning_effort;
|
||||
config.model_reasoning_summary = Some(turn.reasoning_summary);
|
||||
config.developer_instructions = turn.developer_instructions.clone();
|
||||
config.compact_prompt = turn.compact_prompt.clone();
|
||||
apply_spawn_agent_runtime_overrides(&mut config, turn)?;
|
||||
|
||||
apply_turn_runtime_to_config(&mut config, turn).map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("spawn config is invalid: {err}"))
|
||||
})?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -242,13 +243,16 @@ pub(crate) fn apply_spawn_agent_runtime_overrides(
|
||||
config: &mut Config,
|
||||
turn: &TurnContext,
|
||||
) -> Result<(), FunctionCallError> {
|
||||
apply_turn_runtime_to_config(config, turn)
|
||||
.map_err(|err| FunctionCallError::RespondToModel(format!("spawn config is invalid: {err}")))
|
||||
}
|
||||
|
||||
fn apply_turn_runtime_to_config(config: &mut Config, turn: &TurnContext) -> anyhow::Result<()> {
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(turn.approval_policy.value())
|
||||
.map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("approval_policy is invalid: {err}"))
|
||||
})?;
|
||||
.context("approval_policy is invalid")?;
|
||||
config.permissions.shell_environment_policy = turn.shell_environment_policy.clone();
|
||||
config.codex_linux_sandbox_exe = turn.codex_linux_sandbox_exe.clone();
|
||||
config.cwd = turn.cwd.clone();
|
||||
@@ -256,9 +260,7 @@ pub(crate) fn apply_spawn_agent_runtime_overrides(
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(turn.sandbox_policy.get().clone())
|
||||
.map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("sandbox_policy is invalid: {err}"))
|
||||
})?;
|
||||
.context("sandbox_policy is invalid")?;
|
||||
config.permissions.file_system_sandbox_policy = turn.file_system_sandbox_policy.clone();
|
||||
config.permissions.network_sandbox_policy = turn.network_sandbox_policy;
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user