Compare commits

..

15 Commits

Author SHA1 Message Date
Keyan Zhang
cf76b3b30d fix types 2026-03-13 02:41:22 -07:00
Keyan Zhang
d9b01350f7 up 1039 - Fri Mar 13 2026 00:05:59 2026-03-13 00:05:59 -07:00
Keyan Zhang
e535075adb up 1038 - Fri Mar 13 2026 00:04:58 2026-03-13 00:04:58 -07:00
Keyan Zhang
8070f0ba45 up 1037 - Thu Mar 12 2026 23:59:50 2026-03-12 23:59:50 -07:00
Keyan Zhang
e0d7587e8d no js 2026-03-12 23:32:42 -07:00
Keyan Zhang
85b8b1c2ef up 1019 - Wed Mar 11 2026 22:39:06 2026-03-12 23:25:44 -07:00
Keyan Zhang
04897e38e5 up 1018 - Wed Mar 11 2026 22:32:50 2026-03-12 23:25:44 -07:00
Keyan Zhang
28e7347730 up 1017 - Wed Mar 11 2026 22:28:26 2026-03-12 23:25:44 -07:00
Keyan Zhang
0cea343684 simplify 2026-03-12 23:25:44 -07:00
Keyan Zhang
4ae28cf0b0 . 2026-03-12 23:25:44 -07:00
Keyan Zhang
4c3fd18390 simplify 2026-03-12 23:25:44 -07:00
Keyan Zhang
0fcc19012d update 2026-03-12 23:25:43 -07:00
Keyan Zhang
caf4d07445 schema 2026-03-12 23:25:43 -07:00
Keyan Zhang
2134b74e6d script 2026-03-12 23:25:43 -07:00
Keyan Zhang
58d76fb96c add TS codegen 2026-03-12 23:25:43 -07:00
21 changed files with 7535 additions and 569 deletions

View File

@@ -702,24 +702,6 @@
}
]
},
"FunctionCallOutputPayload": {
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
"properties": {
"body": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
"success": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"body"
],
"type": "object"
},
"FuzzyFileSearchParams": {
"properties": {
"cancellationToken": {
@@ -1406,10 +1388,6 @@
"null"
]
},
"id": {
"type": "string",
"writeOnly": true
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
@@ -1425,7 +1403,6 @@
}
},
"required": [
"id",
"summary",
"type"
],
@@ -1559,7 +1536,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -1624,7 +1601,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [

View File

@@ -7249,24 +7249,6 @@
}
]
},
"FunctionCallOutputPayload": {
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
"properties": {
"body": {
"$ref": "#/definitions/v2/FunctionCallOutputBody"
},
"success": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"body"
],
"type": "object"
},
"GetAccountParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -9560,10 +9542,6 @@
"null"
]
},
"id": {
"type": "string",
"writeOnly": true
},
"summary": {
"items": {
"$ref": "#/definitions/v2/ReasoningItemReasoningSummary"
@@ -9579,7 +9557,6 @@
}
},
"required": [
"id",
"summary",
"type"
],
@@ -9713,7 +9690,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/v2/FunctionCallOutputPayload"
"$ref": "#/definitions/v2/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -9778,7 +9755,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/v2/FunctionCallOutputPayload"
"$ref": "#/definitions/v2/FunctionCallOutputBody"
},
"type": {
"enum": [

View File

@@ -3900,24 +3900,6 @@
}
]
},
"FunctionCallOutputPayload": {
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
"properties": {
"body": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
"success": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"body"
],
"type": "object"
},
"FuzzyFileSearchParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -6355,10 +6337,6 @@
"null"
]
},
"id": {
"type": "string",
"writeOnly": true
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
@@ -6374,7 +6352,6 @@
}
},
"required": [
"id",
"summary",
"type"
],
@@ -6508,7 +6485,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -6573,7 +6550,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [

File diff suppressed because it is too large Load Diff

View File

@@ -133,24 +133,6 @@
}
]
},
"FunctionCallOutputPayload": {
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
"properties": {
"body": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
"success": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"body"
],
"type": "object"
},
"GhostCommit": {
"description": "Details of a ghost commit created from a repository state.",
"properties": {
@@ -413,10 +395,6 @@
"null"
]
},
"id": {
"type": "string",
"writeOnly": true
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
@@ -432,7 +410,6 @@
}
},
"required": [
"id",
"summary",
"type"
],
@@ -566,7 +543,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -631,7 +608,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [

View File

@@ -183,24 +183,6 @@
}
]
},
"FunctionCallOutputPayload": {
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
"properties": {
"body": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
"success": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"body"
],
"type": "object"
},
"GhostCommit": {
"description": "Details of a ghost commit created from a repository state.",
"properties": {
@@ -471,10 +453,6 @@
"null"
]
},
"id": {
"type": "string",
"writeOnly": true
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
@@ -490,7 +468,6 @@
}
},
"required": [
"id",
"summary",
"type"
],
@@ -624,7 +601,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [
@@ -689,7 +666,7 @@
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
"$ref": "#/definitions/FunctionCallOutputBody"
},
"type": {
"enum": [

View File

@@ -17,6 +17,7 @@ use crate::protocol::common::EXPERIMENTAL_CLIENT_METHODS;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use codex_protocol::protocol::RolloutLine;
use schemars::JsonSchema;
use schemars::schema_for;
use serde::Serialize;
@@ -233,6 +234,11 @@ pub fn generate_json_with_experimental(out_dir: &Path, experimental_api: bool) -
filter_experimental_json_files(out_dir)?;
}
// internal only (not a public stable interface)
let internal_out_dir = out_dir.join("internal");
ensure_dir(&internal_out_dir)?;
write_json_schema::<RolloutLine>(&internal_out_dir, "RolloutLine")?;
Ok(())
}

View File

@@ -172,8 +172,6 @@ use crate::error::CodexErr;
use crate::error::Result as CodexResult;
#[cfg(test)]
use crate::exec::StreamOutput;
use crate::network_proxy_registry::NetworkProxyRegistry;
use crate::network_proxy_registry::NetworkProxyScope;
use codex_config::CONFIG_TOML_FILE;
mod rollout_reconstruction;
@@ -278,7 +276,6 @@ use crate::skills::collect_explicit_skill_mentions;
use crate::skills::injection::ToolMentionKind;
use crate::skills::injection::app_id_from_path;
use crate::skills::injection::tool_kind_for_path;
use crate::skills::model::SkillManagedNetworkOverride;
use crate::skills::resolve_skill_dependencies_for_turn;
use crate::state::ActiveTurn;
use crate::state::SessionServices;
@@ -1185,61 +1182,6 @@ impl Session {
Ok((network_proxy, session_network_proxy))
}
pub(crate) async fn get_or_start_network_proxy(
self: &Arc<Self>,
scope: NetworkProxyScope,
sandbox_policy: &SandboxPolicy,
managed_network_override: Option<SkillManagedNetworkOverride>,
) -> anyhow::Result<Option<NetworkProxy>> {
let session = Arc::clone(self);
let started = self
.services
.network_proxies
.get_or_start(
scope.clone(),
move |spec, managed_enabled, audit_metadata| {
let session = Arc::clone(&session);
let managed_network_override = managed_network_override.clone();
let scope = scope.clone();
let sandbox_policy = sandbox_policy.clone();
async move {
let network_policy_decider = session
.services
.network_policy_decider_session
.as_ref()
.map(|network_policy_decider_session| {
build_network_policy_decider(
Arc::clone(&session.services.network_approval),
Arc::clone(network_policy_decider_session),
scope,
)
});
let spec = if let Some(managed_network_override) =
managed_network_override.as_ref()
{
spec.with_skill_managed_network_override(managed_network_override)
} else {
spec
};
spec.start_proxy(
&sandbox_policy,
network_policy_decider,
session
.services
.network_blocked_request_observer
.as_ref()
.map(Arc::clone),
managed_enabled,
audit_metadata,
)
.await
}
},
)
.await?;
Ok(started.map(|started| started.proxy()))
}
/// Don't expand the number of mutated arguments on config. We are in the process of getting rid of it.
pub(crate) fn build_per_turn_config(session_configuration: &SessionConfiguration) -> Config {
// todo(aibrahim): store this state somewhere else so we don't need to mut config
@@ -1709,10 +1651,9 @@ impl Session {
build_network_policy_decider(
Arc::clone(&network_approval),
Arc::clone(network_policy_decider_session),
NetworkProxyScope::SessionDefault,
)
});
let (default_network_proxy, session_network_proxy) =
let (network_proxy, session_network_proxy) =
if let Some(spec) = config.permissions.network.as_ref() {
let (network_proxy, session_network_proxy) = Self::start_managed_network_proxy(
spec,
@@ -1720,19 +1661,13 @@ impl Session {
network_policy_decider.as_ref().map(Arc::clone),
blocked_request_observer.as_ref().map(Arc::clone),
managed_network_requirements_enabled,
network_proxy_audit_metadata.clone(),
network_proxy_audit_metadata,
)
.await?;
(Some(network_proxy), Some(session_network_proxy))
} else {
(None, None)
};
let network_proxies = NetworkProxyRegistry::new(
config.permissions.network.clone(),
managed_network_requirements_enabled,
network_proxy_audit_metadata.clone(),
default_network_proxy,
);
let mut hook_shell_argv = default_shell.derive_exec_args("", false);
let hook_shell_program = hook_shell_argv.remove(0);
@@ -1790,9 +1725,7 @@ impl Session {
mcp_manager: Arc::clone(&mcp_manager),
file_watcher,
agent_control,
network_proxies,
network_policy_decider_session,
network_blocked_request_observer: blocked_request_observer,
network_proxy,
network_approval: Arc::clone(&network_approval),
state_db: state_db_ctx.clone(),
model_client: ModelClient::new(
@@ -1831,9 +1764,7 @@ impl Session {
js_repl,
next_internal_sub_id: AtomicU64::new(0),
});
if let Some(network_policy_decider_session) =
sess.services.network_policy_decider_session.as_ref()
{
if let Some(network_policy_decider_session) = network_policy_decider_session {
let mut guard = network_policy_decider_session.write().await;
*guard = Arc::downgrade(&sess);
}
@@ -2383,10 +2314,8 @@ impl Session {
model_info,
&self.services.models_manager,
self.services
.network_proxies
.get(&NetworkProxyScope::SessionDefault)
.await
.as_deref()
.network_proxy
.as_ref()
.map(StartedNetworkProxy::proxy),
sub_id,
Arc::clone(&self.js_repl),
@@ -2758,7 +2687,6 @@ impl Session {
&self,
amendment: &NetworkPolicyAmendment,
network_approval_context: &NetworkApprovalContext,
scope: &NetworkProxyScope,
) -> anyhow::Result<()> {
let host =
Self::validated_network_policy_amendment_host(amendment, network_approval_context)?;
@@ -2772,7 +2700,7 @@ impl Session {
let execpolicy_amendment =
execpolicy_network_rule_amendment(amendment, network_approval_context, &host);
if let Some(started_network_proxy) = self.services.network_proxies.get(scope).await {
if let Some(started_network_proxy) = self.services.network_proxy.as_ref() {
let proxy = started_network_proxy.proxy();
match amendment.action {
NetworkPolicyRuleAction::Allow => proxy

View File

@@ -11,11 +11,9 @@ use crate::exec::ExecToolCallOutput;
use crate::function_tool::FunctionCallError;
use crate::mcp_connection_manager::ToolInfo;
use crate::models_manager::model_info;
use crate::network_proxy_registry::NetworkProxyRegistry;
use crate::shell::default_user_shell;
use crate::tools::format_exec_output_str;
use codex_network_proxy::NetworkProxyAuditMetadata;
use codex_protocol::ThreadId;
use codex_protocol::models::FunctionCallOutputBody;
use codex_protocol::models::FunctionCallOutputPayload;
@@ -2154,14 +2152,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
mcp_manager,
file_watcher,
agent_control,
network_proxies: NetworkProxyRegistry::new(
None,
false,
NetworkProxyAuditMetadata::default(),
None,
),
network_policy_decider_session: None,
network_blocked_request_observer: None,
network_proxy: None,
network_approval: Arc::clone(&network_approval),
state_db: None,
model_client: ModelClient::new(
@@ -2803,14 +2794,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
mcp_manager,
file_watcher,
agent_control,
network_proxies: NetworkProxyRegistry::new(
None,
false,
NetworkProxyAuditMetadata::default(),
None,
),
network_policy_decider_session: None,
network_blocked_request_observer: None,
network_proxy: None,
network_approval: Arc::clone(&network_approval),
state_db: None,
model_client: ModelClient::new(

View File

@@ -1,5 +1,4 @@
use crate::config_loader::NetworkConstraints;
use crate::skills::model::SkillManagedNetworkOverride;
use async_trait::async_trait;
use codex_network_proxy::BlockedRequestObserver;
use codex_network_proxy::ConfigReloader;
@@ -82,28 +81,6 @@ impl NetworkProxySpec {
self.config.network.enable_socks5
}
pub(crate) fn with_skill_managed_network_override(
&self,
managed_network_override: &SkillManagedNetworkOverride,
) -> Self {
let mut spec = self.clone();
if let Some(allowed_domains) = managed_network_override.allowed_domains.clone() {
spec.config.network.allowed_domains = allowed_domains.clone();
if spec.constraints.allowed_domains.is_some() {
spec.constraints.allowed_domains = Some(allowed_domains);
}
}
if let Some(denied_domains) = managed_network_override.denied_domains.clone() {
spec.config.network.denied_domains = denied_domains.clone();
if spec.constraints.denied_domains.is_some() {
spec.constraints.denied_domains = Some(denied_domains);
}
}
spec
}
pub(crate) fn from_config_and_constraints(
config: NetworkProxyConfig,
requirements: Option<NetworkConstraints>,

View File

@@ -1,94 +1,6 @@
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn skill_managed_network_override_replaces_allowed_domains_and_keeps_other_settings() {
let mut config = NetworkProxyConfig::default();
config.network.enabled = true;
config.network.proxy_url = "http://127.0.0.1:4128".to_string();
config.network.socks_url = "socks5://127.0.0.1:5128".to_string();
config.network.enable_socks5 = true;
config.network.enable_socks5_udp = true;
config.network.allowed_domains = vec!["default.example.com".to_string()];
config.network.denied_domains = vec!["blocked.example.com".to_string()];
config.network.allow_upstream_proxy = true;
config.network.dangerously_allow_all_unix_sockets = false;
config.network.dangerously_allow_non_loopback_proxy = false;
config.network.mode = codex_network_proxy::NetworkMode::Full;
config.network.allow_unix_sockets = vec!["/tmp/default.sock".to_string()];
config.network.allow_local_binding = true;
config.network.mitm = false;
let spec = NetworkProxySpec {
config,
constraints: NetworkProxyConstraints {
allowed_domains: Some(vec!["default.example.com".to_string()]),
denied_domains: Some(vec!["blocked.example.com".to_string()]),
allowlist_expansion_enabled: Some(true),
denylist_expansion_enabled: Some(false),
allow_upstream_proxy: Some(true),
allow_unix_sockets: Some(vec!["/tmp/default.sock".to_string()]),
allow_local_binding: Some(true),
..NetworkProxyConstraints::default()
},
hard_deny_allowlist_misses: true,
};
let managed_network_override = crate::skills::model::SkillManagedNetworkOverride {
allowed_domains: Some(vec!["skill.example.com".to_string()]),
denied_domains: None,
};
let overridden = spec.with_skill_managed_network_override(&managed_network_override);
let mut expected = spec.clone();
expected.config.network.allowed_domains = vec!["skill.example.com".to_string()];
expected.constraints.allowed_domains = Some(vec!["skill.example.com".to_string()]);
assert_eq!(overridden, expected);
}
#[test]
fn skill_managed_network_override_replaces_denied_domains_and_keeps_default_allowed_domains() {
let mut config = NetworkProxyConfig::default();
config.network.enabled = true;
config.network.proxy_url = "http://127.0.0.1:4128".to_string();
config.network.socks_url = "socks5://127.0.0.1:5128".to_string();
config.network.enable_socks5 = true;
config.network.enable_socks5_udp = true;
config.network.allowed_domains = vec!["default.example.com".to_string()];
config.network.denied_domains = vec!["blocked.example.com".to_string()];
config.network.allow_upstream_proxy = true;
config.network.dangerously_allow_all_unix_sockets = false;
config.network.dangerously_allow_non_loopback_proxy = false;
config.network.mode = codex_network_proxy::NetworkMode::Full;
config.network.allow_unix_sockets = vec!["/tmp/default.sock".to_string()];
config.network.allow_local_binding = true;
config.network.mitm = false;
let spec = NetworkProxySpec {
config,
constraints: NetworkProxyConstraints {
allowed_domains: Some(vec!["default.example.com".to_string()]),
denied_domains: Some(vec!["blocked.example.com".to_string()]),
allowlist_expansion_enabled: Some(true),
denylist_expansion_enabled: Some(false),
allow_upstream_proxy: Some(true),
allow_unix_sockets: Some(vec!["/tmp/default.sock".to_string()]),
allow_local_binding: Some(true),
..NetworkProxyConstraints::default()
},
hard_deny_allowlist_misses: false,
};
let managed_network_override = crate::skills::model::SkillManagedNetworkOverride {
allowed_domains: None,
denied_domains: Some(vec!["skill-blocked.example.com".to_string()]),
};
let overridden = spec.with_skill_managed_network_override(&managed_network_override);
let mut expected = spec.clone();
expected.config.network.denied_domains = vec!["skill-blocked.example.com".to_string()];
expected.constraints.denied_domains = Some(vec!["skill-blocked.example.com".to_string()]);
assert_eq!(overridden, expected);
}
#[test]
fn build_state_with_audit_metadata_threads_metadata_to_state() {
let spec = NetworkProxySpec {

View File

@@ -39,7 +39,6 @@ use crate::config::Constrained;
use crate::config::NetworkProxySpec;
use crate::event_mapping::is_contextual_user_message_content;
use crate::features::Feature;
use crate::network_proxy_registry::NetworkProxyScope;
use crate::protocol::Op;
use crate::protocol::SandboxPolicy;
use crate::truncate::approx_bytes_for_tokens;
@@ -551,12 +550,7 @@ async fn run_guardian_subagent(
schema: Value,
cancel_token: CancellationToken,
) -> anyhow::Result<GuardianAssessment> {
let live_network_config = match session
.services
.network_proxies
.get(&NetworkProxyScope::SessionDefault)
.await
{
let live_network_config = match session.services.network_proxy.as_ref() {
Some(network_proxy) => Some(network_proxy.proxy().current_cfg().await?),
None => None,
};

View File

@@ -51,7 +51,6 @@ mod mcp_tool_approval_templates;
pub mod models_manager;
mod network_policy_decision;
pub mod network_proxy_loader;
mod network_proxy_registry;
mod original_image_detail;
pub use mcp_connection_manager::MCP_SANDBOX_STATE_CAPABILITY;
pub use mcp_connection_manager::MCP_SANDBOX_STATE_METHOD;

View File

@@ -1,77 +0,0 @@
use crate::config::NetworkProxySpec;
use crate::config::StartedNetworkProxy;
use anyhow::Result;
use codex_network_proxy::NetworkProxyAuditMetadata;
use std::collections::HashMap;
use std::future::Future;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) enum NetworkProxyScope {
SessionDefault,
Skill { path_to_skills_md: PathBuf },
}
pub(crate) struct NetworkProxyRegistry {
spec: Option<NetworkProxySpec>,
managed_network_requirements_enabled: bool,
audit_metadata: NetworkProxyAuditMetadata,
proxies: Mutex<HashMap<NetworkProxyScope, Arc<StartedNetworkProxy>>>,
}
impl NetworkProxyRegistry {
pub(crate) fn new(
spec: Option<NetworkProxySpec>,
managed_network_requirements_enabled: bool,
audit_metadata: NetworkProxyAuditMetadata,
default_proxy: Option<StartedNetworkProxy>,
) -> Self {
let mut proxies = HashMap::new();
if let Some(default_proxy) = default_proxy {
proxies.insert(NetworkProxyScope::SessionDefault, Arc::new(default_proxy));
}
Self {
spec,
managed_network_requirements_enabled,
audit_metadata,
proxies: Mutex::new(proxies),
}
}
pub(crate) async fn get(&self, scope: &NetworkProxyScope) -> Option<Arc<StartedNetworkProxy>> {
self.proxies.lock().await.get(scope).cloned()
}
pub(crate) async fn get_or_start<F, Fut>(
&self,
scope: NetworkProxyScope,
start: F,
) -> Result<Option<Arc<StartedNetworkProxy>>>
where
F: FnOnce(NetworkProxySpec, bool, NetworkProxyAuditMetadata) -> Fut,
Fut: Future<Output = std::io::Result<StartedNetworkProxy>>,
{
let mut proxies = self.proxies.lock().await;
if let Some(existing) = proxies.get(&scope).cloned() {
return Ok(Some(existing));
}
let Some(spec) = self.spec.clone() else {
return Ok(None);
};
let started = Arc::new(
start(
spec,
self.managed_network_requirements_enabled,
self.audit_metadata.clone(),
)
.await?,
);
proxies.insert(scope, Arc::clone(&started));
Ok(Some(started))
}
}

View File

@@ -6,13 +6,12 @@ use crate::RolloutRecorder;
use crate::agent::AgentControl;
use crate::analytics_client::AnalyticsEventsClient;
use crate::client::ModelClient;
use crate::codex::Session;
use crate::config::StartedNetworkProxy;
use crate::exec_policy::ExecPolicyManager;
use crate::file_watcher::FileWatcher;
use crate::mcp::McpManager;
use crate::mcp_connection_manager::McpConnectionManager;
use crate::models_manager::manager::ModelsManager;
use crate::network_proxy_registry::NetworkProxyRegistry;
use crate::plugins::PluginsManager;
use crate::skills::SkillsManager;
use crate::state_db::StateDbHandle;
@@ -22,11 +21,9 @@ use crate::tools::runtimes::ExecveSessionApproval;
use crate::tools::sandboxing::ApprovalStore;
use crate::unified_exec::UnifiedExecProcessManager;
use codex_hooks::Hooks;
use codex_network_proxy::BlockedRequestObserver;
use codex_otel::SessionTelemetry;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::path::PathBuf;
use std::sync::Weak;
use tokio::sync::Mutex;
use tokio::sync::RwLock;
use tokio::sync::watch;
@@ -58,9 +55,7 @@ pub(crate) struct SessionServices {
pub(crate) mcp_manager: Arc<McpManager>,
pub(crate) file_watcher: Arc<FileWatcher>,
pub(crate) agent_control: AgentControl,
pub(crate) network_proxies: NetworkProxyRegistry,
pub(crate) network_policy_decider_session: Option<Arc<RwLock<Weak<Session>>>>,
pub(crate) network_blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
pub(crate) network_proxy: Option<StartedNetworkProxy>,
pub(crate) network_approval: Arc<NetworkApprovalService>,
pub(crate) state_db: Option<StateDbHandle>,
/// Session-scoped model client shared across turns.

View File

@@ -46,7 +46,6 @@ use codex_protocol::protocol::RolloutItem;
use codex_protocol::user_input::UserInput;
use crate::features::Feature;
use crate::network_proxy_registry::NetworkProxyScope;
pub(crate) use compact::CompactTask;
pub(crate) use ghost_snapshot::GhostSnapshotTask;
pub(crate) use regular::RegularTask;
@@ -293,12 +292,7 @@ impl Session {
"false"
},
);
let network_proxy_active = match self
.services
.network_proxies
.get(&NetworkProxyScope::SessionDefault)
.await
{
let network_proxy_active = match self.services.network_proxy.as_ref() {
Some(started_network_proxy) => {
match started_network_proxy.proxy().current_cfg().await {
Ok(config) => config.network.enabled,

View File

@@ -4,7 +4,6 @@ use crate::guardian::GuardianApprovalRequest;
use crate::guardian::review_approval_request;
use crate::guardian::routes_approval_to_guardian;
use crate::network_policy_decision::denied_network_policy_message;
use crate::network_proxy_registry::NetworkProxyScope;
use crate::tools::sandboxing::ToolError;
use codex_network_proxy::BlockedRequest;
use codex_network_proxy::BlockedRequestObserver;
@@ -77,20 +76,14 @@ impl ActiveNetworkApproval {
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct HostApprovalKey {
scope: NetworkProxyScope,
host: String,
protocol: &'static str,
port: u16,
}
impl HostApprovalKey {
fn from_request(
request: &NetworkPolicyRequest,
protocol: NetworkApprovalProtocol,
scope: NetworkProxyScope,
) -> Self {
fn from_request(request: &NetworkPolicyRequest, protocol: NetworkApprovalProtocol) -> Self {
Self {
scope,
host: request.host.to_ascii_lowercase(),
protocol: protocol_key_label(protocol),
port: request.port,
@@ -286,7 +279,6 @@ impl NetworkApprovalService {
&self,
session: Arc<Session>,
request: NetworkPolicyRequest,
scope: NetworkProxyScope,
) -> NetworkDecision {
const REASON_NOT_ALLOWED: &str = "not_allowed";
@@ -296,7 +288,7 @@ impl NetworkApprovalService {
NetworkProtocol::Socks5Tcp => NetworkApprovalProtocol::Socks5Tcp,
NetworkProtocol::Socks5Udp => NetworkApprovalProtocol::Socks5Udp,
};
let key = HostApprovalKey::from_request(&request, protocol, scope.clone());
let key = HostApprovalKey::from_request(&request, protocol);
{
let denied_hosts = self.session_denied_hosts.lock().await;
@@ -395,7 +387,6 @@ impl NetworkApprovalService {
.persist_network_policy_amendment(
&network_policy_amendment,
&network_approval_context,
&scope,
)
.await
{
@@ -426,7 +417,6 @@ impl NetworkApprovalService {
.persist_network_policy_amendment(
&network_policy_amendment,
&network_approval_context,
&scope,
)
.await
{
@@ -516,18 +506,16 @@ pub(crate) fn build_blocked_request_observer(
pub(crate) fn build_network_policy_decider(
network_approval: Arc<NetworkApprovalService>,
network_policy_decider_session: Arc<RwLock<std::sync::Weak<Session>>>,
scope: NetworkProxyScope,
) -> Arc<dyn NetworkPolicyDecider> {
Arc::new(move |request: NetworkPolicyRequest| {
let network_approval = Arc::clone(&network_approval);
let network_policy_decider_session = Arc::clone(&network_policy_decider_session);
let scope = scope.clone();
async move {
let Some(session) = network_policy_decider_session.read().await.upgrade() else {
return NetworkDecision::ask("not_allowed");
};
network_approval
.handle_inline_policy_request(session, request, scope)
.handle_inline_policy_request(session, request)
.await
}
})

View File

@@ -1,5 +1,4 @@
use super::*;
use crate::network_proxy_registry::NetworkProxyScope;
use codex_network_proxy::BlockedRequestArgs;
use codex_protocol::protocol::AskForApproval;
use pretty_assertions::assert_eq;
@@ -8,7 +7,6 @@ use pretty_assertions::assert_eq;
async fn pending_approvals_are_deduped_per_host_protocol_and_port() {
let service = NetworkApprovalService::default();
let key = HostApprovalKey {
scope: NetworkProxyScope::SessionDefault,
host: "example.com".to_string(),
protocol: "http",
port: 443,
@@ -26,13 +24,11 @@ async fn pending_approvals_are_deduped_per_host_protocol_and_port() {
async fn pending_approvals_do_not_dedupe_across_ports() {
let service = NetworkApprovalService::default();
let first_key = HostApprovalKey {
scope: NetworkProxyScope::SessionDefault,
host: "example.com".to_string(),
protocol: "https",
port: 443,
};
let second_key = HostApprovalKey {
scope: NetworkProxyScope::SessionDefault,
host: "example.com".to_string(),
protocol: "https",
port: 8443,
@@ -53,19 +49,16 @@ async fn session_approved_hosts_preserve_protocol_and_port_scope() {
let mut approved_hosts = source.session_approved_hosts.lock().await;
approved_hosts.extend([
HostApprovalKey {
scope: NetworkProxyScope::SessionDefault,
host: "example.com".to_string(),
protocol: "https",
port: 443,
},
HostApprovalKey {
scope: NetworkProxyScope::SessionDefault,
host: "example.com".to_string(),
protocol: "https",
port: 8443,
},
HostApprovalKey {
scope: NetworkProxyScope::SessionDefault,
host: "example.com".to_string(),
protocol: "http",
port: 80,
@@ -89,19 +82,16 @@ async fn session_approved_hosts_preserve_protocol_and_port_scope() {
copied,
vec![
HostApprovalKey {
scope: NetworkProxyScope::SessionDefault,
host: "example.com".to_string(),
protocol: "http",
port: 80,
},
HostApprovalKey {
scope: NetworkProxyScope::SessionDefault,
host: "example.com".to_string(),
protocol: "https",
port: 443,
},
HostApprovalKey {
scope: NetworkProxyScope::SessionDefault,
host: "example.com".to_string(),
protocol: "https",
port: 8443,

View File

@@ -9,7 +9,6 @@ use crate::features::Feature;
use crate::guardian::GuardianApprovalRequest;
use crate::guardian::review_approval_request;
use crate::guardian::routes_approval_to_guardian;
use crate::network_proxy_registry::NetworkProxyScope;
use crate::sandboxing::ExecRequest;
use crate::sandboxing::SandboxPermissions;
use crate::shell::ShellType;
@@ -51,7 +50,6 @@ use codex_shell_escalation::ShellCommandExecutor;
use codex_shell_escalation::Stopwatch;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
@@ -144,7 +142,6 @@ pub(super) async fn try_run_zsh_fork(
ctx.session.services.exec_policy.current().as_ref().clone(),
));
let command_executor = CoreShellCommandExecutor {
session: Some(Arc::clone(&ctx.session)),
command,
cwd: sandbox_cwd,
sandbox_policy,
@@ -262,7 +259,6 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
ctx.session.services.exec_policy.current().as_ref().clone(),
));
let command_executor = CoreShellCommandExecutor {
session: Some(Arc::clone(&ctx.session)),
command: exec_request.command.clone(),
cwd: exec_request.cwd.clone(),
sandbox_policy: exec_request.sandbox_policy.clone(),
@@ -507,7 +503,26 @@ impl CoreShellActionProvider {
/// an absolute path. The idea is that we check to see whether it matches
/// any skills.
async fn find_skill(&self, program: &AbsolutePathBuf) -> Option<SkillMetadata> {
find_skill_for_program(self.session.as_ref(), self.turn.cwd.as_path(), program).await
let force_reload = false;
let skills_outcome = self
.session
.services
.skills_manager
.skills_for_cwd(&self.turn.cwd, force_reload)
.await;
let program_path = program.as_path();
for skill in skills_outcome.skills {
// We intentionally ignore "enabled" status here for now.
let Some(skill_root) = skill.path_to_skills_md.parent() else {
continue;
};
if program_path.starts_with(skill_root.join("scripts")) {
return Some(skill);
}
}
None
}
#[allow(clippy::too_many_arguments)]
@@ -616,32 +631,6 @@ impl CoreShellActionProvider {
}
}
async fn find_skill_for_program(
session: &crate::codex::Session,
cwd: &Path,
program: &AbsolutePathBuf,
) -> Option<SkillMetadata> {
let force_reload = false;
let skills_outcome = session
.services
.skills_manager
.skills_for_cwd(cwd, force_reload)
.await;
let program_path = program.as_path();
for skill in skills_outcome.skills {
// We intentionally ignore "enabled" status here for now.
let Some(skill_root) = skill.path_to_skills_md.parent() else {
continue;
};
if program_path.starts_with(skill_root.join("scripts")) {
return Some(skill);
}
}
None
}
// Shell-wrapper parsing is weaker than direct exec interception because it can
// only see the script text, not the final resolved executable path. Keep it
// disabled by default so path-sensitive rules rely on the later authoritative
@@ -876,7 +865,6 @@ fn commands_for_intercepted_exec_policy(
}
struct CoreShellCommandExecutor {
session: Option<Arc<crate::codex::Session>>,
command: Vec<String>,
cwd: PathBuf,
sandbox_policy: SandboxPolicy,
@@ -900,7 +888,6 @@ struct PrepareSandboxedExecParams<'a> {
command: Vec<String>,
workdir: &'a AbsolutePathBuf,
env: HashMap<String, String>,
network: Option<codex_network_proxy::NetworkProxy>,
sandbox_policy: &'a SandboxPolicy,
file_system_sandbox_policy: &'a FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
@@ -982,12 +969,10 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
arg0: Some(first_arg.clone()),
},
EscalationExecution::TurnDefault => {
let network = self.network_for_program(program).await?;
self.prepare_sandboxed_exec(PrepareSandboxedExecParams {
command,
workdir,
env,
network,
sandbox_policy: &self.sandbox_policy,
file_system_sandbox_policy: &self.file_system_sandbox_policy,
network_sandbox_policy: self.network_sandbox_policy,
@@ -1001,14 +986,12 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
EscalationExecution::Permissions(EscalationPermissions::PermissionProfile(
permission_profile,
)) => {
let network = self.network_for_program(program).await?;
// Merge additive permissions into the existing turn/request sandbox policy.
// On macOS, additional profile extensions are unioned with the turn defaults.
self.prepare_sandboxed_exec(PrepareSandboxedExecParams {
command,
workdir,
env,
network,
sandbox_policy: &self.sandbox_policy,
file_system_sandbox_policy: &self.file_system_sandbox_policy,
network_sandbox_policy: self.network_sandbox_policy,
@@ -1020,13 +1003,11 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
})?
}
EscalationExecution::Permissions(EscalationPermissions::Permissions(permissions)) => {
let network = self.network_for_program(program).await?;
// Use a fully specified sandbox policy instead of merging into the turn policy.
self.prepare_sandboxed_exec(PrepareSandboxedExecParams {
command,
workdir,
env,
network,
sandbox_policy: &permissions.sandbox_policy,
file_system_sandbox_policy: &permissions.file_system_sandbox_policy,
network_sandbox_policy: permissions.network_sandbox_policy,
@@ -1044,25 +1025,6 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
}
impl CoreShellCommandExecutor {
async fn network_for_program(
&self,
program: &AbsolutePathBuf,
) -> anyhow::Result<Option<codex_network_proxy::NetworkProxy>> {
let Some(session) = self.session.as_ref() else {
return Ok(self.network.clone());
};
let Some(skill) =
find_skill_for_program(session.as_ref(), &self.sandbox_policy_cwd, program).await
else {
return Ok(self.network.clone());
};
let (scope, managed_network_override) = network_proxy_scope_for_skill(&skill);
session
.get_or_start_network_proxy(scope, &self.sandbox_policy, managed_network_override)
.await
}
#[allow(clippy::too_many_arguments)]
fn prepare_sandboxed_exec(
&self,
@@ -1072,7 +1034,6 @@ impl CoreShellCommandExecutor {
command,
workdir,
env,
network,
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
@@ -1089,7 +1050,7 @@ impl CoreShellCommandExecutor {
network_sandbox_policy,
SandboxablePreference::Auto,
self.windows_sandbox_level,
network.is_some(),
self.network.is_some(),
);
let mut exec_request =
sandbox_manager.transform(crate::sandboxing::SandboxTransformRequest {
@@ -1111,8 +1072,8 @@ impl CoreShellCommandExecutor {
file_system_policy: file_system_sandbox_policy,
network_policy: network_sandbox_policy,
sandbox,
enforce_managed_network: network.is_some(),
network: network.as_ref(),
enforce_managed_network: self.network.is_some(),
network: self.network.as_ref(),
sandbox_policy_cwd: &self.sandbox_policy_cwd,
#[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions,
@@ -1133,23 +1094,6 @@ impl CoreShellCommandExecutor {
}
}
fn network_proxy_scope_for_skill(
skill: &SkillMetadata,
) -> (
NetworkProxyScope,
Option<crate::skills::model::SkillManagedNetworkOverride>,
) {
match skill.managed_network_override.clone() {
Some(managed_network_override) => (
NetworkProxyScope::Skill {
path_to_skills_md: skill.path_to_skills_md.clone(),
},
Some(managed_network_override),
),
None => (NetworkProxyScope::SessionDefault, None),
}
}
#[derive(Debug, Eq, PartialEq)]
struct ParsedShellCommand {
program: String,

View File

@@ -15,7 +15,6 @@ use crate::config::Permissions;
#[cfg(target_os = "macos")]
use crate::config::types::ShellEnvironmentPolicy;
use crate::exec::SandboxType;
use crate::network_proxy_registry::NetworkProxyScope;
use crate::protocol::AskForApproval;
use crate::protocol::GranularApprovalConfig;
use crate::protocol::ReadOnlyAccess;
@@ -99,35 +98,6 @@ fn test_skill_metadata(permission_profile: Option<PermissionProfile>) -> SkillMe
}
}
#[test]
fn network_proxy_scope_for_skill_without_override_reuses_session_default() {
let skill = test_skill_metadata(None);
assert_eq!(
super::network_proxy_scope_for_skill(&skill),
(NetworkProxyScope::SessionDefault, None),
);
}
#[test]
fn network_proxy_scope_for_skill_with_override_uses_skill_scope() {
let mut skill = test_skill_metadata(None);
skill.managed_network_override = Some(crate::skills::model::SkillManagedNetworkOverride {
allowed_domains: Some(vec!["skill.example.com".to_string()]),
denied_domains: Some(vec!["blocked.skill.example.com".to_string()]),
});
assert_eq!(
super::network_proxy_scope_for_skill(&skill),
(
NetworkProxyScope::Skill {
path_to_skills_md: PathBuf::from("/tmp/skill/SKILL.md"),
},
skill.managed_network_override.clone(),
),
);
}
#[test]
fn execve_prompt_rejection_uses_skill_approval_for_skill_scripts() {
let decision_source = super::DecisionSource::SkillScript {
@@ -681,7 +651,6 @@ host_executable(name = "git", paths = ["{allowed_git_literal}"])
async fn prepare_escalated_exec_turn_default_preserves_macos_seatbelt_extensions() {
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir()).unwrap();
let executor = CoreShellCommandExecutor {
session: None,
command: vec!["echo".to_string(), "ok".to_string()],
cwd: cwd.to_path_buf(),
env: HashMap::new(),
@@ -734,7 +703,6 @@ async fn prepare_escalated_exec_turn_default_preserves_macos_seatbelt_extensions
async fn prepare_escalated_exec_permissions_preserve_macos_seatbelt_extensions() {
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir()).unwrap();
let executor = CoreShellCommandExecutor {
session: None,
command: vec!["echo".to_string(), "ok".to_string()],
cwd: cwd.to_path_buf(),
env: HashMap::new(),
@@ -809,7 +777,6 @@ async fn prepare_escalated_exec_permission_profile_unions_turn_and_requested_mac
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir()).unwrap();
let sandbox_policy = SandboxPolicy::new_read_only_policy();
let executor = CoreShellCommandExecutor {
session: None,
command: vec!["echo".to_string(), "ok".to_string()],
cwd: cwd.to_path_buf(),
env: HashMap::new(),

View File

@@ -230,6 +230,7 @@ pub enum ResponseInputItem {
},
FunctionCallOutput {
call_id: String,
#[schemars(with = "FunctionCallOutputBody")]
output: FunctionCallOutputPayload,
},
McpToolCallOutput {
@@ -238,6 +239,7 @@ pub enum ResponseInputItem {
},
CustomToolCallOutput {
call_id: String,
#[schemars(with = "FunctionCallOutputBody")]
output: FunctionCallOutputPayload,
},
ToolSearchOutput {
@@ -305,6 +307,7 @@ pub enum ResponseItem {
Reasoning {
#[serde(default, skip_serializing)]
#[ts(skip)]
#[schemars(skip)]
id: String,
summary: Vec<ReasoningItemReasoningSummary>,
#[serde(default, skip_serializing_if = "should_serialize_reasoning_content")]
@@ -355,6 +358,7 @@ pub enum ResponseItem {
// We keep this behavior centralized in `FunctionCallOutputPayload`.
FunctionCallOutput {
call_id: String,
#[schemars(with = "FunctionCallOutputBody")]
output: FunctionCallOutputPayload,
},
CustomToolCall {
@@ -374,6 +378,7 @@ pub enum ResponseItem {
// text or structured content items.
CustomToolCallOutput {
call_id: String,
#[schemars(with = "FunctionCallOutputBody")]
output: FunctionCallOutputPayload,
},
ToolSearchOutput {